From 3add3cd39b9b5c0e6d9eb303091a921699afcf1f Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Mon, 19 Oct 2026 10:00:15 -0700 Subject: [PATCH 1/7] feat: add DELETE /v1/responses/{response_id} Stored responses could be retrieved but not deleted (405), unlike Conversations, which already support deletion. Deleting removes the response row only. A response-history item id is copied into the history_item_ids of every descendant, so items are not owned by a single response and stay in place. The existing foreign keys clear responses.previous_response_id and conversations.latest_response_id references to the deleted row, so descendants stay retrievable and can still be continued with their full history, while a new request naming the deleted id as previous_response_id is rejected as not found. The route shares retrieval's credential check because both act on local storage. The response body matches the OpenAI deletion object: {"id": ..., "object": "response", "deleted": true}. Refs #462 Signed-off-by: fengjiaqi --- .../src/executor/modes/response.rs | 8 ++ .../src/storage/models/response.rs | 17 ++++ .../src/storage/response.rs | 17 ++++ .../src/types/conversations.rs | 10 +++ crates/agentic-server/src/app.rs | 10 +-- crates/agentic-server/src/handler/http/mod.rs | 2 +- .../src/handler/http/responses.rs | 26 ++++++ crates/agentic-server/src/handler/mod.rs | 4 +- crates/agentic-server/src/openapi.rs | 1 + .../tests/response_retrieval_test.rs | 88 +++++++++++++++++++ 10 files changed, 175 insertions(+), 8 deletions(-) diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index 4975380ef..d9e689122 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -52,6 +52,14 @@ impl ResponseHandler { }) } + /// Deletes a response created with `store=true`. + /// + /// # Errors + /// Returns a storage error for missing IDs or unavailable storage. + pub async fn delete(&self, response_id: &str) -> ExecutorResult<()> { + self.store.delete(response_id).await.map_err(ExecutorError::Storage) + } + /// Retrieves the stored response for `previous_response_id`. /// /// Reads `previous_response_id` from `ctx.original_request`. diff --git a/crates/agentic-server-core/src/storage/models/response.rs b/crates/agentic-server-core/src/storage/models/response.rs index a63555f91..dc9efc07f 100644 --- a/crates/agentic-server-core/src/storage/models/response.rs +++ b/crates/agentic-server-core/src/storage/models/response.rs @@ -67,6 +67,23 @@ pub async fn get(pool: &DbPool, id: &str) -> DbResult> { .await } +/// Delete a response row, returning how many rows were removed. +/// +/// Items are left in place: a response-history item id is copied into the +/// `history_item_ids` of every descendant, so it is not owned by this row. +/// Foreign keys clear `responses.previous_response_id` and +/// `conversations.latest_response_id` references to the deleted row. +/// +/// # Errors +/// Returns `DbResult::Err` if the database statement fails. +pub async fn delete(pool: &DbPool, id: &str) -> DbResult { + let result = sqlx::query("DELETE FROM responses WHERE id = $1") + .bind(id) + .execute(pool) + .await?; + Ok(result.rows_affected()) +} + /// Get the response that committed a conversation turn. /// /// # Errors diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index 78dab433a..e0e204810 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -58,6 +58,23 @@ impl ResponseStore { row.try_into() } + /// Deletes a stored response. + /// + /// Descendants created with `previous_response_id` stay readable because each + /// one stores its own complete history item list; their reference to the + /// deleted response is cleared by the foreign key. + /// + /// # Errors + /// + /// Returns [`StorageError::NotFound`] if no response has this ID, or an error + /// if the database statement fails or the store is disabled. + pub async fn delete(&self, response_id: &str) -> StoreResult<()> { + if response::delete(self.pool()?, response_id).await? == 0 { + return Err(StorageError::not_found("Response", response_id)); + } + Ok(()) + } + /// Rehydrates a response with full history. /// /// Fetches all history items referenced by a response. diff --git a/crates/agentic-server-core/src/types/conversations.rs b/crates/agentic-server-core/src/types/conversations.rs index b4fbdc477..7aa5a1126 100644 --- a/crates/agentic-server-core/src/types/conversations.rs +++ b/crates/agentic-server-core/src/types/conversations.rs @@ -199,6 +199,16 @@ impl DeletedResponse { } } + /// Create a deleted response object, matching the `DELETE /v1/responses/{id}` endpoint. + #[must_use] + pub fn response(id: String) -> Self { + Self { + id, + object: "response".to_string(), + deleted: true, + } + } + /// Create a deleted item response. #[must_use] pub fn item(id: String) -> Self { diff --git a/crates/agentic-server/src/app.rs b/crates/agentic-server/src/app.rs index 1b66f2633..c80ee4e67 100644 --- a/crates/agentic-server/src/app.rs +++ b/crates/agentic-server/src/app.rs @@ -21,9 +21,9 @@ use crate::auth::api_key::require_api_key; use crate::auth::{ANTHROPIC_COUNT_TOKENS_PATH, ANTHROPIC_MESSAGES_PATH, OidcAuthenticator, require_oidc}; use crate::handler::{ chat_completions, compact_response, completions, count_tokens, create_conversation, create_item, - delete_conversation, delete_item, embeddings, health, list_items, messages, models, ready, responses, - responses_ws_with_auth, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, transcriptions, - translations, update_conversation, + delete_conversation, delete_item, delete_response, embeddings, health, list_items, messages, models, ready, + responses, responses_ws_with_auth, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, + transcriptions, translations, update_conversation, }; use crate::model_capabilities::ModelCapabilities; use crate::rate_limit::{RateLimitConfig, RateLimiter, enforce_rate_limit}; @@ -349,8 +349,8 @@ pub fn build_router_with_auth( } else { public_routes }; - // Retrieval reads local storage, so it cannot rely on upstream credential validation. - let mut retrieval_route = get(retrieve_response); + // Retrieval and deletion act on local storage, so they cannot rely on upstream credential validation. + let mut retrieval_route = get(retrieve_response).delete(delete_response); if authenticator.is_none() && let Some(key) = state.openai_api_key.as_ref().filter(|key| !key.is_empty()) { diff --git a/crates/agentic-server/src/handler/http/mod.rs b/crates/agentic-server/src/handler/http/mod.rs index bb0c33216..d2da5677d 100644 --- a/crates/agentic-server/src/handler/http/mod.rs +++ b/crates/agentic-server/src/handler/http/mod.rs @@ -14,4 +14,4 @@ pub use conversations::{create_conversation, delete_conversation, retrieve_conve pub use embeddings::embeddings; pub use messages::{count_tokens, messages}; pub use models::{health, models, ready, retrieve_model}; -pub use responses::{compact_response, responses, retrieve_response}; +pub use responses::{compact_response, delete_response, responses, retrieve_response}; diff --git a/crates/agentic-server/src/handler/http/responses.rs b/crates/agentic-server/src/handler/http/responses.rs index d796447d4..697a6ac83 100644 --- a/crates/agentic-server/src/handler/http/responses.rs +++ b/crates/agentic-server/src/handler/http/responses.rs @@ -154,3 +154,29 @@ pub async fn retrieve_response(State(state): State, Path(response_id): Err(error) => executor_error_response(error), } } + +/// Delete a locally persisted response without invoking inference. +/// +/// Descendants created with `previous_response_id` stay retrievable; a new request +/// naming the deleted ID as `previous_response_id` is rejected as not found. +#[cfg_attr(feature = "openapi", utoipa::path( + delete, + path = "/v1/responses/{response_id}", + params(("response_id" = String, Path, description = "Stored response ID")), + responses( + (status = 200, description = "Response deleted", body = agentic_core::types::conversations::DeletedResponse), + (status = 401, description = "Missing or invalid bearer token", body = crate::openapi::ApiErrorResponse), + (status = 404, description = "Response not found", body = crate::openapi::ApiErrorResponse), + ), + security(("bearer_auth" = [])), + tag = "responses", +))] +pub async fn delete_response(State(state): State, Path(response_id): Path) -> Response { + match state.exec_ctx.resp_handler.delete(&response_id).await { + Ok(()) => axum::Json(agentic_core::types::conversations::DeletedResponse::response( + response_id, + )) + .into_response(), + Err(error) => executor_error_response(error), + } +} diff --git a/crates/agentic-server/src/handler/mod.rs b/crates/agentic-server/src/handler/mod.rs index 20a445520..cc36cabe5 100644 --- a/crates/agentic-server/src/handler/mod.rs +++ b/crates/agentic-server/src/handler/mod.rs @@ -5,8 +5,8 @@ pub mod websocket; pub use common::{convert_response, executor_error_response}; pub use http::{ chat_completions, compact_response, completions, count_tokens, create_conversation, create_item, - delete_conversation, delete_item, embeddings, health, list_items, messages, models, ready, responses, - retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, transcriptions, translations, + delete_conversation, delete_item, delete_response, embeddings, health, list_items, messages, models, ready, + responses, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, transcriptions, translations, update_conversation, }; pub use websocket::responses_ws; diff --git a/crates/agentic-server/src/openapi.rs b/crates/agentic-server/src/openapi.rs index 71c296856..3fca06438 100644 --- a/crates/agentic-server/src/openapi.rs +++ b/crates/agentic-server/src/openapi.rs @@ -23,6 +23,7 @@ use utoipa::OpenApi; crate::handler::http::models::retrieve_model, crate::handler::http::responses::responses, crate::handler::http::responses::retrieve_response, + crate::handler::http::responses::delete_response, crate::handler::http::responses::compact_response, crate::handler::http::conversations::create_conversation, crate::handler::http::conversations::retrieve_conversation, diff --git a/crates/agentic-server/tests/response_retrieval_test.rs b/crates/agentic-server/tests/response_retrieval_test.rs index 7e11d449d..a6d67f076 100644 --- a/crates/agentic-server/tests/response_retrieval_test.rs +++ b/crates/agentic-server/tests/response_retrieval_test.rs @@ -265,3 +265,91 @@ async fn retrieval_allows_unauthenticated_access_without_configured_authenticati exec.storage_pool().unwrap().close().await; } } + +#[tokio::test] +async fn deleting_a_response_keeps_its_descendants_and_ends_its_own_continuations() { + let (upstream, requests) = response_retrieval_upstream(); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let mut config = common::test_config(&format!("http://{}", listener.local_addr().unwrap())); + let upstream = tokio::spawn(async move { axum::serve(listener, upstream).await.unwrap() }); + config.db_url = Some("sqlite://?mode=memory".into()); + let exec = Arc::new(ExecutionContext::from_config(&config).await.unwrap()); + let mut state = common::test_state(&config); + state.exec_ctx = Arc::clone(&exec); + let (url, gateway) = common::spawn_gateway(state).await; + let client = reqwest::Client::new(); + let create = |body: Value| { + let request = client.post(format!("{url}/v1/responses")).json(&body); + async move { request.send().await.unwrap() } + }; + let delete = |id: &str| { + let request = client + .delete(format!("{url}/v1/responses/{id}")) + .bearer_auth("test-key"); + async move { request.send().await.unwrap() } + }; + let retrieve = |id: &str| { + let request = client.get(format!("{url}/v1/responses/{id}")).bearer_auth("test-key"); + async move { request.send().await.unwrap() } + }; + + let parent: Value = create(json!({"model":"test-model", "input":"first", "store":true})) + .await + .json() + .await + .unwrap(); + let parent_id = parent["id"].as_str().unwrap(); + let child: Value = create(json!({"model":"test-model", "input":"second", "store":true, + "previous_response_id":parent_id})) + .await + .json() + .await + .unwrap(); + let child_id = child["id"].as_str().unwrap(); + + // Deletion reads local storage, so it carries the same credential check as retrieval. + let denied = client + .delete(format!("{url}/v1/responses/{parent_id}")) + .send() + .await + .unwrap(); + assert_eq!(denied.status(), StatusCode::UNAUTHORIZED); + assert_eq!(retrieve(parent_id).await.status(), StatusCode::OK); + + let deleted = delete(parent_id).await; + assert_eq!(deleted.status(), StatusCode::OK); + assert_eq!( + deleted.json::().await.unwrap(), + json!({"id":parent_id, "object":"response", "deleted":true}) + ); + assert_eq!(retrieve(parent_id).await.status(), StatusCode::NOT_FOUND); + assert_eq!(delete(parent_id).await.status(), StatusCode::NOT_FOUND); + assert_eq!(delete("resp_missing").await.status(), StatusCode::NOT_FOUND); + + // The deleted response can no longer be continued, and no inference runs for it. + let orphaned = create(json!({"model":"test-model", "input":"third", "store":true, + "previous_response_id":parent_id})) + .await; + assert_eq!(orphaned.status(), StatusCode::NOT_FOUND); + assert_eq!(requests.lock().unwrap().len(), 2); + + // A descendant stores its own complete history, so it survives its parent: + // still retrievable unchanged, and still continuable with the parent's turn. + let retrieved = retrieve(child_id).await; + assert_eq!(retrieved.status(), StatusCode::OK); + assert_eq!(retrieved.json::().await.unwrap(), child); + let grandchild = create(json!({"model":"test-model", "input":"third", "store":true, + "previous_response_id":child_id})) + .await; + assert_eq!(grandchild.status(), StatusCode::OK); + assert_eq!( + input_texts(&requests.lock().unwrap()[2]), + ["first", "answer", "second", "answer", "third"] + ); + + upstream.abort(); + let _ = upstream.await; + gateway.abort(); + let _ = gateway.await; + exec.storage_pool().unwrap().close().await; +} From 9f6c9a2d3b2e8c479febcf51df160d03290f169c Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Mon, 19 Oct 2026 10:25:21 -0700 Subject: [PATCH 2/7] feat: add GET /v1/responses/{response_id}/input_items Lists the context a stored response was generated from: items inherited through previous_response_id or its conversation, including earlier turns' outputs, followed by that turn's input. The response's own output is excluded, identified by the output item IDs in its stored snapshot. This matches the OpenAI listing, whose item type also covers prior outputs. Every stored response already records its full history_item_ids, on both the previous_response_id and the conversation path, so the listing reads that snapshot rather than the live conversation: a turn lists the conversation as it was at that turn, and a deleted parent does not break a child's listing. Pagination, validation and errors follow conversation item listing: limit 1..=100 (default 20), order desc by default, an unknown after cursor is a 404 naming `after`. The shared query validation moves onto ListItemsQuery so both listings use it. Records without a snapshot return 409, as retrieval does. Authentication matches retrieval. Closes #462 Signed-off-by: fengjiaqi --- README.md | 2 + .../src/executor/modes/response.rs | 64 ++++- .../src/storage/response.rs | 36 ++- crates/agentic-server/src/app.rs | 11 +- .../src/handler/http/conversation_items.rs | 61 +++-- crates/agentic-server/src/handler/http/mod.rs | 2 +- .../src/handler/http/responses.rs | 42 +++ crates/agentic-server/src/handler/mod.rs | 6 +- crates/agentic-server/src/openapi.rs | 1 + .../tests/response_input_items_test.rs | 257 ++++++++++++++++++ docs/api/index.md | 21 ++ 11 files changed, 454 insertions(+), 49 deletions(-) create mode 100644 crates/agentic-server/tests/response_input_items_test.rs diff --git a/README.md b/README.md index 3c021bb17..fdb51bab6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ flowchart LR | --- | --- | --- | | `POST /v1/responses` | OpenAI-compatible Responses API with state, tools, and streaming | ✅ | | `GET /v1/responses/{response_id}` | Retrieve a locally stored response | ✅ | +| `DELETE /v1/responses/{response_id}` | Delete a locally stored response | ✅ | +| `GET /v1/responses/{response_id}/input_items` | List the input items of a stored response | ✅ | | `GET /v1/responses` | WebSocket transport for the Responses API | ✅ | | `POST /v1/responses/compact` | Compact direct input or a stored response chain | ✅ | | `/v1/conversations` · `/v1/conversations/{id}/items` | Conversations API: create, retrieve, update, and delete conversations and their items | ✅ | diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index d9e689122..79aa7c090 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -1,6 +1,9 @@ //! Response storage handler — owns all response store operations. -use crate::storage::{ResponseData, ResponseMetadata, ResponseStore}; +use std::collections::HashSet; + +use crate::storage::{ResponseData, ResponseMetadata, ResponseStore, StorageError}; +use crate::types::conversations::{ItemOrder, ItemResponse, ListItemsResponse}; use crate::types::io::InOutItem; use crate::types::io::OutputItem; use crate::types::request_response::ResponsePayload; @@ -60,6 +63,65 @@ impl ResponseHandler { self.store.delete(response_id).await.map_err(ExecutorError::Storage) } + /// Lists the input items of a response created with `store=true`. + /// + /// Input items are the full context the response was generated from: items + /// inherited through `previous_response_id` or its conversation, including + /// earlier turns' outputs, followed by this turn's input. Only this response's + /// own output is excluded, identified by the IDs in its stored payload. + /// + /// Pagination follows conversation item listing: `after` is the public ID of + /// the last item on the previous page, and `limit` is clamped to `1..=100`. + /// + /// # Errors + /// Returns a storage error for a missing response, an unknown `after` cursor + /// or unavailable storage, and a conflict for older records without a + /// response snapshot, matching [`Self::retrieve`]. + pub async fn list_input_items( + &self, + response_id: &str, + limit: i64, + after: Option<&str>, + order: ItemOrder, + ) -> ExecutorResult { + let stored = self.store.get(response_id).await?; + let snapshot = stored.metadata.response_snapshot.as_deref().ok_or_else(|| { + ExecutorError::Conflict( + "stored response has no retrievable input items; create a new response with store=true".into(), + ) + })?; + let own_output: HashSet<&str> = snapshot.output.iter().filter_map(OutputItem::id).collect(); + let rows = self.store.history_rows(&stored).await?; + + let mut items = Vec::with_capacity(rows.len()); + for row in &rows { + let item = InOutItem::try_from(row)?; + let is_own_output = matches!(item, InOutItem::Output(_)) && own_output.contains(row.public_id()); + if !is_own_output { + items.push(ItemResponse::new(row.public_id().to_owned(), item)); + } + } + if order == ItemOrder::Desc { + items.reverse(); + } + let start = match after { + Some(cursor) => { + items + .iter() + .position(|item| item.id == cursor) + .ok_or_else(|| StorageError::ItemCursorNotFound { id: cursor.to_owned() })? + + 1 + } + None => 0, + }; + // Safe: limit is clamped to [1, 100]. + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let limit = limit.clamp(1, 100) as usize; + let page: Vec = items.into_iter().skip(start).take(limit + 1).collect(); + let has_more = page.len() > limit; + Ok(ListItemsResponse::new(page.into_iter().take(limit).collect(), has_more)) + } + /// Retrieves the stored response for `previous_response_id`. /// /// Reads `previous_response_id` from `ctx.original_request`. diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index e0e204810..5429561d8 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -84,28 +84,36 @@ impl ResponseStore { /// Returns an error if a history item is missing or invalid, the database query /// fails, or the store is disabled. pub async fn rehydrate(&self, response_id: &str) -> StoreResult> { - let pool = self.pool()?; let response = self.get(response_id).await?; - let rows = item::get_items(pool, &response.history_item_ids).await?; - let mut items_by_id: HashMap = rows - .into_iter() - .map(|row| { - let item = InOutItem::try_from(&row)?; - Ok((row.id, item)) - }) - .collect::>()?; + self.history_rows(&response) + .await? + .iter() + .map(InOutItem::try_from) + .collect() + } - let ordered_items = response + /// Loads the stored rows of a response's history, oldest first. + /// + /// The history is the full context recorded when the response was stored: + /// items inherited through `previous_response_id` or its conversation, this + /// turn's input, and this turn's output. + /// + /// # Errors + /// + /// Returns an error if a history item is missing, the database query fails, + /// or the store is disabled. + pub(crate) async fn history_rows(&self, response: &ResponseData) -> StoreResult> { + let rows = item::get_items(self.pool()?, &response.history_item_ids).await?; + let mut rows_by_id: HashMap = rows.into_iter().map(|row| (row.id.clone(), row)).collect(); + response .history_item_ids .iter() .map(|id| { - items_by_id + rows_by_id .remove(id) .ok_or_else(|| StorageError::InvalidHistoryItem { item_id: id.clone() }) }) - .collect::>()?; - - Ok(ordered_items) + .collect() } /// Persists a response with its items and metadata. diff --git a/crates/agentic-server/src/app.rs b/crates/agentic-server/src/app.rs index c80ee4e67..8d7819d4e 100644 --- a/crates/agentic-server/src/app.rs +++ b/crates/agentic-server/src/app.rs @@ -21,9 +21,9 @@ use crate::auth::api_key::require_api_key; use crate::auth::{ANTHROPIC_COUNT_TOKENS_PATH, ANTHROPIC_MESSAGES_PATH, OidcAuthenticator, require_oidc}; use crate::handler::{ chat_completions, compact_response, completions, count_tokens, create_conversation, create_item, - delete_conversation, delete_item, delete_response, embeddings, health, list_items, messages, models, ready, - responses, responses_ws_with_auth, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, - transcriptions, translations, update_conversation, + delete_conversation, delete_item, delete_response, embeddings, health, list_input_items, list_items, messages, + models, ready, responses, responses_ws_with_auth, retrieve_conversation, retrieve_item, retrieve_model, + retrieve_response, transcriptions, translations, update_conversation, }; use crate::model_capabilities::ModelCapabilities; use crate::rate_limit::{RateLimitConfig, RateLimiter, enforce_rate_limit}; @@ -351,10 +351,12 @@ pub fn build_router_with_auth( }; // Retrieval and deletion act on local storage, so they cannot rely on upstream credential validation. let mut retrieval_route = get(retrieve_response).delete(delete_response); + let mut input_items_route = get(list_input_items); if authenticator.is_none() && let Some(key) = state.openai_api_key.as_ref().filter(|key| !key.is_empty()) { retrieval_route = retrieval_route.route_layer(middleware::from_fn_with_state(key.clone(), require_api_key)); + input_items_route = input_items_route.route_layer(middleware::from_fn_with_state(key.clone(), require_api_key)); } let protected_routes = Router::new() .route("/v1/conversations", post(create_conversation)) @@ -384,7 +386,8 @@ pub fn build_router_with_auth( .route(ANTHROPIC_COUNT_TOKENS_PATH, post(count_tokens)) .route("/v1/responses", post(responses).get(responses_ws_with_auth)) .route("/v1/responses/compact", post(compact_response)) - .route("/v1/responses/{response_id}", retrieval_route); + .route("/v1/responses/{response_id}", retrieval_route) + .route("/v1/responses/{response_id}/input_items", input_items_route); // Layered before authentication, so it runs after it and sees the principal. let protected_routes = match server_config.rate_limit { Some(config) => protected_routes.route_layer(middleware::from_fn_with_state( diff --git a/crates/agentic-server/src/handler/http/conversation_items.rs b/crates/agentic-server/src/handler/http/conversation_items.rs index 676944603..c3f64644a 100644 --- a/crates/agentic-server/src/handler/http/conversation_items.rs +++ b/crates/agentic-server/src/handler/http/conversation_items.rs @@ -7,6 +7,7 @@ use serde::Deserialize; #[cfg(feature = "openapi")] use agentic_core::types::ConversationResponse; +use agentic_core::types::conversations::ItemOrder; use agentic_core::types::{CreateItemRequest, ItemResponse, ListItemsResponse}; use super::super::common::{error_response, executor_error_response, extract_json, read_bytes}; @@ -33,6 +34,38 @@ pub struct ListItemsQuery { pub include: Option, } +impl ListItemsQuery { + /// Validate the parameters shared by every item listing, returning the order. + /// + /// # Errors + /// Returns a 400 response for an out-of-range limit, an unknown order, or an + /// include expansion that stored items cannot serve. + pub(super) fn validate(&self) -> Result> { + let message = if self.limit < 1 || self.limit > 100 { + "limit must be between 1 and 100" + } else if self.order != "asc" && self.order != "desc" { + "order must be 'asc' or 'desc'" + } else if self + .include + .as_deref() + // Text output logprobs are already present in stored message items. Other + // include expansions need their own data sources before they can be served. + .is_some_and(|value| value != "message.output_text.logprobs") + { + "unsupported include value" + } else if self.order == "asc" { + return Ok(ItemOrder::Asc); + } else { + return Ok(ItemOrder::Desc); + }; + Err(Box::new(error_response( + StatusCode::BAD_REQUEST, + "invalid_request", + message, + ))) + } +} + fn default_limit() -> i64 { 20 } @@ -102,32 +135,8 @@ pub async fn list_items( Path(conversation_id): Path, Query(query): Query, ) -> Response { - // Validate limit parameter - if query.limit < 1 || query.limit > 100 { - return error_response( - StatusCode::BAD_REQUEST, - "invalid_request", - "limit must be between 1 and 100", - ); - } - - // Validate order parameter - if query.order != "asc" && query.order != "desc" { - return error_response( - StatusCode::BAD_REQUEST, - "invalid_request", - "order must be 'asc' or 'desc'", - ); - } - - // Text output logprobs are already present in stored message items. Other - // include expansions need their own data sources before they can be served. - if query - .include - .as_deref() - .is_some_and(|value| value != "message.output_text.logprobs") - { - return error_response(StatusCode::BAD_REQUEST, "invalid_request", "unsupported include value"); + if let Err(error) = query.validate() { + return *error; } match state diff --git a/crates/agentic-server/src/handler/http/mod.rs b/crates/agentic-server/src/handler/http/mod.rs index d2da5677d..cb64238bf 100644 --- a/crates/agentic-server/src/handler/http/mod.rs +++ b/crates/agentic-server/src/handler/http/mod.rs @@ -14,4 +14,4 @@ pub use conversations::{create_conversation, delete_conversation, retrieve_conve pub use embeddings::embeddings; pub use messages::{count_tokens, messages}; pub use models::{health, models, ready, retrieve_model}; -pub use responses::{compact_response, delete_response, responses, retrieve_response}; +pub use responses::{compact_response, delete_response, list_input_items, responses, retrieve_response}; diff --git a/crates/agentic-server/src/handler/http/responses.rs b/crates/agentic-server/src/handler/http/responses.rs index 697a6ac83..40416c635 100644 --- a/crates/agentic-server/src/handler/http/responses.rs +++ b/crates/agentic-server/src/handler/http/responses.rs @@ -180,3 +180,45 @@ pub async fn delete_response(State(state): State, Path(response_id): P Err(error) => executor_error_response(error), } } + +/// List the input items of a locally persisted response. +/// +/// The list is the full context the response was generated from — items inherited +/// through `previous_response_id` or its conversation, then this turn's input — +/// without the response's own output. +#[cfg_attr(feature = "openapi", utoipa::path( + get, + path = "/v1/responses/{response_id}/input_items", + params( + ("response_id" = String, Path, description = "Stored response ID"), + super::conversation_items::ListItemsQuery + ), + responses( + (status = 200, description = "Input items", body = agentic_core::types::ListItemsResponse), + (status = 400, description = "Invalid pagination parameters", body = crate::openapi::ApiErrorResponse), + (status = 401, description = "Missing or invalid bearer token", body = crate::openapi::ApiErrorResponse), + (status = 404, description = "Response not found", body = crate::openapi::ApiErrorResponse), + (status = 409, description = "Legacy response has no retrievable payload", body = crate::openapi::ApiErrorResponse), + ), + security(("bearer_auth" = [])), + tag = "responses", +))] +pub async fn list_input_items( + State(state): State, + Path(response_id): Path, + axum::extract::Query(query): axum::extract::Query, +) -> Response { + let order = match query.validate() { + Ok(order) => order, + Err(error) => return *error, + }; + match state + .exec_ctx + .resp_handler + .list_input_items(&response_id, query.limit, query.after.as_deref(), order) + .await + { + Ok(page) => axum::Json(page).into_response(), + Err(error) => executor_error_response(error), + } +} diff --git a/crates/agentic-server/src/handler/mod.rs b/crates/agentic-server/src/handler/mod.rs index cc36cabe5..41837c511 100644 --- a/crates/agentic-server/src/handler/mod.rs +++ b/crates/agentic-server/src/handler/mod.rs @@ -5,9 +5,9 @@ pub mod websocket; pub use common::{convert_response, executor_error_response}; pub use http::{ chat_completions, compact_response, completions, count_tokens, create_conversation, create_item, - delete_conversation, delete_item, delete_response, embeddings, health, list_items, messages, models, ready, - responses, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, transcriptions, translations, - update_conversation, + delete_conversation, delete_item, delete_response, embeddings, health, list_input_items, list_items, messages, + models, ready, responses, retrieve_conversation, retrieve_item, retrieve_model, retrieve_response, transcriptions, + translations, update_conversation, }; pub use websocket::responses_ws; pub(crate) use websocket::responses_ws_with_auth; diff --git a/crates/agentic-server/src/openapi.rs b/crates/agentic-server/src/openapi.rs index 3fca06438..0e6ff2d36 100644 --- a/crates/agentic-server/src/openapi.rs +++ b/crates/agentic-server/src/openapi.rs @@ -24,6 +24,7 @@ use utoipa::OpenApi; crate::handler::http::responses::responses, crate::handler::http::responses::retrieve_response, crate::handler::http::responses::delete_response, + crate::handler::http::responses::list_input_items, crate::handler::http::responses::compact_response, crate::handler::http::conversations::create_conversation, crate::handler::http::conversations::retrieve_conversation, diff --git a/crates/agentic-server/tests/response_input_items_test.rs b/crates/agentic-server/tests/response_input_items_test.rs new file mode 100644 index 000000000..ce06c62bf --- /dev/null +++ b/crates/agentic-server/tests/response_input_items_test.rs @@ -0,0 +1,257 @@ +//! `GET /v1/responses/{id}/input_items` lists the stored context a response was generated from. +#[allow(dead_code)] +mod common; + +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use agentic_core::executor::ExecutionContext; +use agentic_core::storage::{ResponseMetadata, ResponseStore}; +use axum::{Json, Router, routing::post}; +use http::StatusCode; +use serde_json::{Value, json}; +use tokio::net::TcpListener; + +/// Mock inference upstream answering every turn with one uniquely identified message. +fn answering_upstream() -> Router { + let next_message_id = Arc::new(AtomicUsize::new(0)); + Router::new().route( + "/v1/responses", + post(move |Json(_): Json| { + let message_id = next_message_id.fetch_add(1, Ordering::Relaxed); + async move { + Json(json!({"id":"resp_upstream", "object":"response", "created_at":123, + "model":"test-model", "status":"completed", "output":[{ + "type":"message", "id":format!("msg_answer_{message_id}"), "role":"assistant", + "status":"completed", "content":[{"type":"output_text", "text":format!("answer {message_id}")}]}], + "usage":{"input_tokens":3,"output_tokens":1,"total_tokens":4}})) + } + }), + ) +} + +struct Harness { + url: String, + client: reqwest::Client, + exec: Arc, + tasks: Vec>, +} + +impl Harness { + async fn start() -> Self { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let mut config = common::test_config(&format!("http://{}", listener.local_addr().unwrap())); + let upstream = tokio::spawn(async move { axum::serve(listener, answering_upstream()).await.unwrap() }); + config.db_url = Some("sqlite://?mode=memory".into()); + let exec = Arc::new(ExecutionContext::from_config(&config).await.unwrap()); + let mut state = common::test_state(&config); + state.exec_ctx = Arc::clone(&exec); + let (url, gateway) = common::spawn_gateway(state).await; + Self { + url, + client: reqwest::Client::new(), + exec, + tasks: vec![upstream, gateway], + } + } + + async fn create(&self, body: Value) -> Value { + let response = self + .client + .post(format!("{}/v1/responses", self.url)) + .json(&body) + .send() + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + response.json().await.unwrap() + } + + async fn input_items(&self, id: &str, query: &str) -> reqwest::Response { + self.client + .get(format!("{}/v1/responses/{id}/input_items{query}", self.url)) + .bearer_auth("test-key") + .send() + .await + .unwrap() + } + + async fn page(&self, id: &str, query: &str) -> Value { + let response = self.input_items(id, query).await; + assert_eq!(response.status(), StatusCode::OK); + response.json().await.unwrap() + } + + async fn stop(self) { + for task in self.tasks { + task.abort(); + let _ = task.await; + } + self.exec.storage_pool().unwrap().close().await; + } +} + +/// The text of each item on a page: input text, or the first content part's text. +fn texts(page: &Value) -> Vec { + page["data"] + .as_array() + .unwrap() + .iter() + .map(|item| { + item["content"] + .as_str() + .or_else(|| item["content"][0]["text"].as_str()) + .expect("text item") + .to_owned() + }) + .collect() +} + +#[tokio::test] +async fn input_items_list_the_inherited_context_without_the_response_own_output() { + let harness = Harness::start().await; + let first = harness + .create(json!({"model":"test-model", "input":"first", "store":true})) + .await; + let first_id = first["id"].as_str().unwrap(); + let second = harness + .create(json!({"model":"test-model", "input":"second", "store":true, "previous_response_id":first_id})) + .await; + let second_id = second["id"].as_str().unwrap(); + + // The first turn's own answer is input to the second, but neither lists its own output. + assert_eq!(texts(&harness.page(first_id, "?order=asc").await), ["first"]); + let ascending = harness.page(second_id, "?order=asc").await; + assert_eq!(texts(&ascending), ["first", "answer 0", "second"]); + assert_eq!(ascending["object"], "list"); + assert_eq!(ascending["has_more"], false); + assert_eq!(ascending["data"][1]["id"], "msg_answer_0"); + assert!( + ascending["data"] + .as_array() + .unwrap() + .iter() + .all(|item| item["id"] != "msg_answer_1"), + "a response's own output is not one of its inputs" + ); + // Like conversation item listing, the default order is newest first. + assert_eq!( + texts(&harness.page(second_id, "").await), + ["second", "answer 0", "first"] + ); + + // Pagination walks the same sequence one item at a time. + let mut cursor = String::new(); + let mut walked = Vec::new(); + loop { + let page = harness.page(second_id, &format!("?order=asc&limit=1{cursor}")).await; + walked.extend(texts(&page)); + assert_eq!(page["first_id"], page["last_id"]); + if page["has_more"] == false { + break; + } + cursor = format!("&after={}", page["last_id"].as_str().unwrap()); + } + assert_eq!(walked, ["first", "answer 0", "second"]); + + harness.stop().await; +} + +#[tokio::test] +async fn input_items_of_a_conversation_turn_reflect_the_conversation_at_that_turn() { + let harness = Harness::start().await; + let conversation: Value = harness + .client + .post(format!("{}/v1/conversations", harness.url)) + .bearer_auth("test-key") + .json(&json!({})) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let conversation_id = conversation["id"].as_str().unwrap(); + let first = harness + .create(json!({"model":"test-model", "input":"first", "conversation":conversation_id})) + .await; + let second = harness + .create(json!({"model":"test-model", "input":"second", "conversation":conversation_id})) + .await; + // A later turn does not leak into an earlier response's inputs. + harness + .create(json!({"model":"test-model", "input":"third", "conversation":conversation_id})) + .await; + + assert_eq!( + texts(&harness.page(first["id"].as_str().unwrap(), "?order=asc").await), + ["first"] + ); + assert_eq!( + texts(&harness.page(second["id"].as_str().unwrap(), "?order=asc").await), + ["first", "answer 0", "second"] + ); + harness.stop().await; +} + +#[tokio::test] +async fn input_items_reject_what_retrieval_rejects() { + let harness = Harness::start().await; + let stored = harness + .create(json!({"model":"test-model", "input":"first", "store":true})) + .await; + let stored_id = stored["id"].as_str().unwrap(); + let transient = harness + .create(json!({"model":"test-model", "input":"transient", "store":false})) + .await; + + let unauthenticated = harness + .client + .get(format!("{}/v1/responses/{stored_id}/input_items", harness.url)) + .send() + .await + .unwrap(); + assert_eq!(unauthenticated.status(), StatusCode::UNAUTHORIZED); + for missing in [transient["id"].as_str().unwrap(), "resp_missing"] { + assert_eq!(harness.input_items(missing, "").await.status(), StatusCode::NOT_FOUND); + } + for query in [ + "?limit=0", + "?limit=101", + "?order=sideways", + "?include=reasoning.encrypted_content", + ] { + assert_eq!( + harness.input_items(stored_id, query).await.status(), + StatusCode::BAD_REQUEST, + "{query}" + ); + } + // Same contract as conversation item listing: an unknown cursor is a not-found naming `after`. + let unknown_cursor = harness.input_items(stored_id, "?after=msg_unknown").await; + assert_eq!(unknown_cursor.status(), StatusCode::NOT_FOUND); + assert_eq!(unknown_cursor.json::().await.unwrap()["error"]["param"], "after"); + + // A record without a response snapshot cannot tell its output from its input. + let store = ResponseStore::new(Arc::new(harness.exec.storage_pool().unwrap().clone())); + store + .persist("resp_legacy", None, Vec::new(), &ResponseMetadata::default()) + .await + .unwrap(); + assert_eq!( + harness.input_items("resp_legacy", "").await.status(), + StatusCode::CONFLICT + ); + + // Deleting a response removes its input listing with it. + let deleted = harness + .client + .delete(format!("{}/v1/responses/{stored_id}", harness.url)) + .bearer_auth("test-key") + .send() + .await + .unwrap(); + assert_eq!(deleted.status(), StatusCode::OK); + assert_eq!(harness.input_items(stored_id, "").await.status(), StatusCode::NOT_FOUND); + harness.stop().await; +} diff --git a/docs/api/index.md b/docs/api/index.md index cfa7ae858..31aaf039c 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -436,6 +436,27 @@ history-only APIs, return `409`. Request-scoped MCP credentials are stripped before storage. When OIDC is disabled and `OPENAI_API_KEY` is nonempty, callers must send that key as a bearer token. +### `DELETE /v1/responses/{response_id}` + +Deletes a stored response and returns +`{"id": "", "object": "response", "deleted": true}`. Later +retrieval returns `404`, and a new request naming the deleted ID as +`previous_response_id` is rejected. Responses created from it stay retrievable +and can still be continued: each stored response keeps its own complete +history. Items are not removed, because one item can belong to the history of +many responses. Unknown IDs return `404`. Authentication matches retrieval. + +### `GET /v1/responses/{response_id}/input_items` + +Lists the context a stored response was generated from: items inherited through +`previous_response_id` or its conversation (including earlier turns' outputs), +followed by that turn's input. The response's own output is not included. A +conversation turn lists the conversation as it was at that turn, not as it is +now. Pagination matches conversation item listing: `limit` (1–100, default 20), +`order` (`desc` by default), and `after` (the ID of the last item on the +previous page, `404` if unknown). Unknown IDs return `404`, and records without +a stored response snapshot return `409`. Authentication matches retrieval. + ### `POST /v1/responses/compact` Compacts direct input or a stored previous-response chain into a canonical From 6801461099b9092dcec7459aad24841e20f48ac3 Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Mon, 19 Oct 2026 11:15:57 -0700 Subject: [PATCH 3/7] fix: record own output row IDs; document WebSocket exception to deletion input_items excluded a response's own output by the wire IDs in its snapshot. Output kinds whose wire id is optional (compaction, shell_call) get a generated ID when stored, so they were missing from that set and were listed as input. And since #455 a public ID can recur in history, so matching by public ID would also drop an earlier turn's output that shares this turn's ID. Persistence now records the row IDs of the rows it inserts as output in ResponseMetadata.output_item_ids, on both the previous_response_id and conversation paths, and input_items matches rows by row ID. Records written before this fall back to the snapshot's public IDs. Regression tests cover an ID-less compaction and an upstream reusing one message ID across turns; each fails when its half of the fix is removed. Deleting a response does not invalidate a WebSocket session that caches it as its latest checkpoint. Checking storage before reusing a cached durable parent was tried and reverted: the extra await reordered automatic steering successors, and the edge-input-lifecycle/old-parent-before-successor replay failed 5 of 6 runs (6/6 on main and after the revert). Sessions are per connection and may be on another replica, so the exception is documented on ResponseStore::delete and in the API docs. Signed-off-by: fengjiaqi --- .../src/executor/compaction.rs | 1 + .../src/executor/modes/conversation.rs | 1 + .../src/executor/modes/response.rs | 20 ++++- .../src/executor/persist.rs | 1 + .../src/storage/conversation.rs | 8 +- .../src/storage/response.rs | 25 +++++- .../src/storage/types/response.rs | 20 +++++ .../tests/response_input_items_test.rs | 84 ++++++++++++++++++- docs/api/index.md | 6 ++ 9 files changed, 156 insertions(+), 10 deletions(-) diff --git a/crates/agentic-server-core/src/executor/compaction.rs b/crates/agentic-server-core/src/executor/compaction.rs index 1eb0b8f84..294cc6645 100644 --- a/crates/agentic-server-core/src/executor/compaction.rs +++ b/crates/agentic-server-core/src/executor/compaction.rs @@ -1265,6 +1265,7 @@ mod tests { effective_tools: None, tool_search_loaded_tools: None, response_snapshot: None, + output_item_ids: None, effective_tool_choice: crate::ToolChoice::Auto, effective_instructions: None, }, diff --git a/crates/agentic-server-core/src/executor/modes/conversation.rs b/crates/agentic-server-core/src/executor/modes/conversation.rs index f13556d27..c9504368c 100644 --- a/crates/agentic-server-core/src/executor/modes/conversation.rs +++ b/crates/agentic-server-core/src/executor/modes/conversation.rs @@ -318,6 +318,7 @@ impl ConversationHandler { effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot: None, + output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index 79aa7c090..7eb0df76c 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -68,7 +68,8 @@ impl ResponseHandler { /// Input items are the full context the response was generated from: items /// inherited through `previous_response_id` or its conversation, including /// earlier turns' outputs, followed by this turn's input. Only this response's - /// own output is excluded, identified by the IDs in its stored payload. + /// own output is excluded, identified by the output item IDs recorded when it + /// was stored (for older records, the IDs in its stored payload). /// /// Pagination follows conversation item listing: `after` is the public ID of /// the last item on the previous page, and `limit` is clamped to `1..=100`. @@ -90,13 +91,25 @@ impl ResponseHandler { "stored response has no retrievable input items; create a new response with store=true".into(), ) })?; - let own_output: HashSet<&str> = snapshot.output.iter().filter_map(OutputItem::id).collect(); + // Persistence records the row IDs of this response's own output, including rows + // for output kinds whose wire `id` is optional. Older records only have the + // snapshot's public IDs, matched against each row's public ID instead. + let recorded: Option> = stored + .metadata + .output_item_ids + .as_deref() + .map(|ids| ids.iter().map(String::as_str).collect()); + let snapshot_ids: HashSet<&str> = snapshot.output.iter().filter_map(OutputItem::id).collect(); let rows = self.store.history_rows(&stored).await?; let mut items = Vec::with_capacity(rows.len()); for row in &rows { let item = InOutItem::try_from(row)?; - let is_own_output = matches!(item, InOutItem::Output(_)) && own_output.contains(row.public_id()); + let is_own_output = matches!(item, InOutItem::Output(_)) + && match &recorded { + Some(ids) => ids.contains(row.id.as_str()), + None => snapshot_ids.contains(row.public_id()), + }; if !is_own_output { items.push(ItemResponse::new(row.public_id().to_owned(), item)); } @@ -184,6 +197,7 @@ impl ResponseHandler { effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot: None, + output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/executor/persist.rs b/crates/agentic-server-core/src/executor/persist.rs index 5387ed2ab..7f077b595 100644 --- a/crates/agentic-server-core/src/executor/persist.rs +++ b/crates/agentic-server-core/src/executor/persist.rs @@ -168,6 +168,7 @@ pub(crate) async fn persist_prepared_turn( effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot, + output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/storage/conversation.rs b/crates/agentic-server-core/src/storage/conversation.rs index ad92fa5c3..44ab7abd5 100644 --- a/crates/agentic-server-core/src/storage/conversation.rs +++ b/crates/agentic-server-core/src/storage/conversation.rs @@ -205,8 +205,11 @@ impl ConversationStore { ) -> StoreResult<()> { let pool = self.pool()?; + let is_output: Vec = new_items + .iter() + .map(|item| matches!(item, InOutItem::Output(_))) + .collect(); let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; - let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; @@ -229,7 +232,8 @@ impl ConversationStore { }); } } - item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; + let created = item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; + let metadata_json = metadata.to_stored_json(super::response::output_row_ids(&is_output, &created))?; // A response branch must see the conversation as it existed at this turn, // including manually added items that may later be detached or deleted. let history_item_ids = item::ids_for_conversation_in_tx(&mut tx, conversation_id).await?; diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index 5429561d8..ca86c591a 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -64,6 +64,11 @@ impl ResponseStore { /// one stores its own complete history item list; their reference to the /// deleted response is cleared by the foreign key. /// + /// Deletion does not reach WebSocket sessions. A session that already caches this + /// response as its latest checkpoint keeps continuing from it without a storage + /// read; those sessions live per connection, possibly on another replica, and a + /// storage check on that path would reorder automatic steering successors. + /// /// # Errors /// /// Returns [`StorageError::NotFound`] if no response has this ID, or an error @@ -153,13 +158,17 @@ impl ResponseStore { Some(prev_id) => self.get(prev_id).await?.history_item_ids, None => Vec::new(), }; + let is_output: Vec = new_items + .iter() + .map(|item| matches!(item, InOutItem::Output(_))) + .collect(); let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; - let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; // History rows get their own row IDs, so repeated public IDs keep every position. let created = item::create_in_tx(&mut tx, items_, None).await?; + let metadata_json = metadata.to_stored_json(output_row_ids(&is_output, &created))?; item_ids.extend(created.into_iter().map(|item| item.id)); let history_item_ids_json = serialize_to_string(&item_ids)?; @@ -178,6 +187,20 @@ impl ResponseStore { } } +/// Row IDs of the output items among rows returned by [`item::create_in_tx`]. +/// +/// `is_output` must be taken from the batch before serialization; `create_in_tx` +/// returns rows in that same order. Row IDs are used rather than public IDs because +/// a public ID can recur in history, while each occurrence has its own row. +pub(super) fn output_row_ids(is_output: &[bool], created: &[item::Item]) -> Vec { + is_output + .iter() + .zip(created) + .filter(|(is_output, _)| **is_output) + .map(|(_, row)| row.id.clone()) + .collect() +} + #[cfg(test)] mod tests { use super::super::types::ResponseMetadata; diff --git a/crates/agentic-server-core/src/storage/types/response.rs b/crates/agentic-server-core/src/storage/types/response.rs index 816645045..d93877699 100644 --- a/crates/agentic-server-core/src/storage/types/response.rs +++ b/crates/agentic-server-core/src/storage/types/response.rs @@ -22,6 +22,13 @@ pub struct ResponseMetadata { /// Exact terminal Responses payload, absent for legacy and non-Responses records. #[serde(default, skip_serializing_if = "Option::is_none")] pub response_snapshot: Option>, + /// Item row IDs of this response's own output, in history order. + /// + /// Recorded by persistence from the rows it actually writes, so it includes IDs + /// generated for output kinds whose wire `id` is optional. Absent for records + /// written before it existed; readers then fall back to the snapshot's output IDs. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub output_item_ids: Option>, pub model: String, pub previous_response_id: Option, pub effective_tools: Option>, @@ -110,6 +117,18 @@ pub(in crate::storage) fn decode_metadata( }) } +impl ResponseMetadata { + /// Serialize for one stored row, recording which written items are its own output. + /// + /// # Errors + /// Returns [`StorageError::Serialization`] if the metadata cannot be encoded. + pub(in crate::storage) fn to_stored_json(&self, output_item_ids: Vec) -> Result { + let mut persisted = self.clone(); + persisted.output_item_ids = Some(output_item_ids); + String::try_from(&persisted) + } +} + impl TryFrom<&ResponseMetadata> for String { type Error = StorageError; @@ -222,6 +241,7 @@ mod tests { effective_tools: None, tool_search_loaded_tools: None, response_snapshot: None, + output_item_ids: None, effective_tool_choice: ToolChoice::Auto, effective_instructions: Some("be helpful".to_string()), }; diff --git a/crates/agentic-server/tests/response_input_items_test.rs b/crates/agentic-server/tests/response_input_items_test.rs index ce06c62bf..e05a8e3ce 100644 --- a/crates/agentic-server/tests/response_input_items_test.rs +++ b/crates/agentic-server/tests/response_input_items_test.rs @@ -14,16 +14,24 @@ use tokio::net::TcpListener; /// Mock inference upstream answering every turn with one uniquely identified message. fn answering_upstream() -> Router { + answering_upstream_with(json!([])) +} + +/// Like [`answering_upstream`], but each turn's output starts with `extra_output`. +fn answering_upstream_with(extra_output: Value) -> Router { let next_message_id = Arc::new(AtomicUsize::new(0)); Router::new().route( "/v1/responses", post(move |Json(_): Json| { + let extra_output = extra_output.clone(); let message_id = next_message_id.fetch_add(1, Ordering::Relaxed); + let mut output = extra_output.as_array().cloned().unwrap_or_default(); + output.push(json!({ + "type":"message", "id":format!("msg_answer_{message_id}"), "role":"assistant", + "status":"completed", "content":[{"type":"output_text", "text":format!("answer {message_id}")}]})); async move { Json(json!({"id":"resp_upstream", "object":"response", "created_at":123, - "model":"test-model", "status":"completed", "output":[{ - "type":"message", "id":format!("msg_answer_{message_id}"), "role":"assistant", - "status":"completed", "content":[{"type":"output_text", "text":format!("answer {message_id}")}]}], + "model":"test-model", "status":"completed", "output":output, "usage":{"input_tokens":3,"output_tokens":1,"total_tokens":4}})) } }), @@ -39,9 +47,13 @@ struct Harness { impl Harness { async fn start() -> Self { + Self::start_with(answering_upstream()).await + } + + async fn start_with(upstream: Router) -> Self { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let mut config = common::test_config(&format!("http://{}", listener.local_addr().unwrap())); - let upstream = tokio::spawn(async move { axum::serve(listener, answering_upstream()).await.unwrap() }); + let upstream = tokio::spawn(async move { axum::serve(listener, upstream).await.unwrap() }); config.db_url = Some("sqlite://?mode=memory".into()); let exec = Arc::new(ExecutionContext::from_config(&config).await.unwrap()); let mut state = common::test_state(&config); @@ -255,3 +267,67 @@ async fn input_items_reject_what_retrieval_rejects() { assert_eq!(harness.input_items(stored_id, "").await.status(), StatusCode::NOT_FOUND); harness.stop().await; } + +#[tokio::test] +async fn input_items_exclude_own_output_items_that_carry_no_wire_id() { + // A compaction item's wire `id` is optional; storage generates one for the row. + let harness = Harness::start_with(answering_upstream_with( + json!([{"type":"compaction", "encrypted_content":"opaque"}]), + )) + .await; + let first = harness + .create(json!({"model":"test-model", "input":"first", "store":true})) + .await; + let first_id = first["id"].as_str().unwrap(); + assert!( + first["output"][0].get("id").is_none(), + "the upstream compaction item has no id" + ); + let second = harness + .create(json!({"model":"test-model", "input":"second", "store":true, "previous_response_id":first_id})) + .await; + + let own = harness.page(first_id, "?order=asc").await; + assert_eq!(own["data"].as_array().unwrap().len(), 1, "{own}"); + assert_eq!(texts(&own), ["first"]); + // The same item, under its generated ID, is input to the next turn. + let next = harness.page(second["id"].as_str().unwrap(), "?order=asc").await; + let types: Vec<&str> = next["data"] + .as_array() + .unwrap() + .iter() + .map(|item| item["type"].as_str().unwrap()) + .collect(); + assert_eq!(types, ["message", "compaction", "message", "message"], "{next}"); + assert!(next["data"][1]["id"].as_str().unwrap().starts_with("cmp_")); + harness.stop().await; +} + +#[tokio::test] +async fn input_items_keep_an_earlier_turn_output_that_shares_this_turn_output_id() { + // An upstream may reuse a message ID across turns. Each occurrence is its own history + // row, so only this turn's row is excluded, not the earlier turn's answer. + let upstream = Router::new().route( + "/v1/responses", + post(|Json(_): Json| async { + Json(json!({"id":"resp_upstream", "object":"response", "created_at":123, + "model":"test-model", "status":"completed", "output":[{ + "type":"message", "id":"msg_reused", "role":"assistant", "status":"completed", + "content":[{"type":"output_text", "text":"same id"}]}], + "usage":{"input_tokens":3,"output_tokens":1,"total_tokens":4}})) + }), + ); + let harness = Harness::start_with(upstream).await; + let first = harness + .create(json!({"model":"test-model", "input":"first", "store":true})) + .await; + let second = harness + .create(json!({"model":"test-model", "input":"second", "store":true, + "previous_response_id":first["id"]})) + .await; + assert_eq!( + texts(&harness.page(second["id"].as_str().unwrap(), "?order=asc").await), + ["first", "same id", "second"] + ); + harness.stop().await; +} diff --git a/docs/api/index.md b/docs/api/index.md index 31aaf039c..4d93f5cd7 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -446,6 +446,12 @@ and can still be continued: each stored response keeps its own complete history. Items are not removed, because one item can belong to the history of many responses. Unknown IDs return `404`. Authentication matches retrieval. +Deletion does not reach live WebSocket sessions: a connection whose latest +response is the deleted one can still continue from it, because the session +caches that checkpoint and does not read storage again. A `store: true` turn +from it then fails at persistence. New connections and HTTP requests are +rejected as described above. + ### `GET /v1/responses/{response_id}/input_items` Lists the context a stored response was generated from: items inherited through From a5f5e0b3f71921c7c4c0756a9e29e0f5ead8f4ed Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Wed, 21 Oct 2026 11:15:13 -0700 Subject: [PATCH 4/7] refactor: type item listing queries and written item roles - Parse item listing queries once into a typed ItemLimit, ItemOrder and a repeatable ItemInclude list, shared by conversation item listing and response input items; malformed queries return structured 400 errors. - Resume an input_items cursor after the furthest occurrence of a repeated public ID, as conversation item listing does, so paging always advances. - Return "object": "response.deleted" from DELETE /v1/responses/{id}, per OpenAI's published OpenAPI definition. - Serialize new items into NewItem (public ID, data, input/output role) and return WrittenItem rows that keep that role, deriving output row IDs from them instead of a parallel boolean array. - Record output row IDs through the one sanitizing metadata conversion, now consuming, so persistence clones the metadata once. Signed-off-by: fengjiaqi --- .rust-file-sizes.json | 4 - .../src/executor/modes/conversation.rs | 31 +--- .../src/executor/modes/response.rs | 31 ++-- .../src/storage/conversation.rs | 14 +- .../src/storage/models/item.rs | 130 +++++++---------- .../src/storage/models/mod.rs | 1 + .../src/storage/models/new_item.rs | 86 +++++++++++ .../src/storage/response.rs | 24 +--- .../src/storage/schema/readiness.rs | 6 +- .../src/storage/types/response.rs | 26 +++- .../src/types/conversations.rs | 107 +++++++++++++- .../conversation_branch_response_test.rs | 13 +- .../tests/conversation_storage_regressions.rs | 8 ++ .../tests/conversations/edge_replay.rs | 21 +-- .../src/handler/http/conversation_items.rs | 134 ++++++++++-------- .../src/handler/http/responses.rs | 13 +- crates/agentic-server/src/openapi.rs | 1 + .../tests/response_input_items_test.rs | 97 +++++++++++-- .../tests/response_retrieval_test.rs | 2 +- docs/api/index.md | 15 +- 20 files changed, 518 insertions(+), 246 deletions(-) create mode 100644 crates/agentic-server-core/src/storage/models/new_item.rs diff --git a/.rust-file-sizes.json b/.rust-file-sizes.json index ac99f168a..4d2015984 100644 --- a/.rust-file-sizes.json +++ b/.rust-file-sizes.json @@ -28,10 +28,6 @@ "crates/agentic-server/src/main.rs": { "limit": 644, "reason": "Server configuration resolves the web_fetch settings with the same file and environment precedence as the other tool settings." - }, - "crates/agentic-server-core/src/storage/models/item.rs": { - "limit": 510, - "reason": "The item CRUD now owns response-history row identity, as requested in review; consolidating the insert here adds the row/public-ID bookkeeping. Splitting the item storage module is a separate change." } }, "generated": {} diff --git a/crates/agentic-server-core/src/executor/modes/conversation.rs b/crates/agentic-server-core/src/executor/modes/conversation.rs index c9504368c..b759a9bf3 100644 --- a/crates/agentic-server-core/src/executor/modes/conversation.rs +++ b/crates/agentic-server-core/src/executor/modes/conversation.rs @@ -3,7 +3,7 @@ use crate::storage::{ ConversationData, ConversationSnapshot, ConversationStore, ConversationVersion, ResponseMetadata, StorageError, }; -use crate::types::conversations::{ConversationMetadata, ItemResponse, ListItemsResponse}; +use crate::types::conversations::{ConversationMetadata, ItemPage, ItemResponse, ListItemsResponse}; use crate::types::io::InOutItem; use crate::types::io::OutputItem; @@ -225,35 +225,16 @@ impl ConversationHandler { &self, tenant_id: &str, conversation_id: &str, - limit: i64, - after: Option<&str>, - order: &str, + page: ItemPage<'_>, ) -> ExecutorResult { - // Validate order parameter - if order != "asc" && order != "desc" { - return Err(ExecutorError::InvalidRequest("order must be 'asc' or 'desc'".into())); - } - - let limit = limit.clamp(1, 100); - let order_enum = if order == "desc" { - crate::types::conversations::ItemOrder::Desc - } else { - crate::types::conversations::ItemOrder::Asc - }; - - // Fetch limit + 1 to determine if there are more items + // Fetch one extra item to determine if there are more items let mut items = self .store - .list_items(tenant_id, conversation_id, limit + 1, after, order_enum) + .list_items(tenant_id, conversation_id, page.limit.probe(), page.after, page.order) .await .map_err(ExecutorError::Storage)?; - - // Safe: limit is clamped to [1, 100], far below usize range - #[allow(clippy::cast_possible_wrap)] - let has_more = (items.len() as i64) > limit; - if has_more { - items.pop(); // Remove the extra item - } + let has_more = items.len() > page.limit.get(); + items.truncate(page.limit.get()); // Perform fallible conversion - propagate errors instead of silently dropping items let mut item_responses = Vec::with_capacity(items.len()); diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index 7eb0df76c..b7108343b 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; use crate::storage::{ResponseData, ResponseMetadata, ResponseStore, StorageError}; -use crate::types::conversations::{ItemOrder, ItemResponse, ListItemsResponse}; +use crate::types::conversations::{ItemOrder, ItemPage, ItemResponse, ListItemsResponse}; use crate::types::io::InOutItem; use crate::types::io::OutputItem; use crate::types::request_response::ResponsePayload; @@ -72,19 +72,13 @@ impl ResponseHandler { /// was stored (for older records, the IDs in its stored payload). /// /// Pagination follows conversation item listing: `after` is the public ID of - /// the last item on the previous page, and `limit` is clamped to `1..=100`. + /// the last item on the previous page. /// /// # Errors /// Returns a storage error for a missing response, an unknown `after` cursor /// or unavailable storage, and a conflict for older records without a /// response snapshot, matching [`Self::retrieve`]. - pub async fn list_input_items( - &self, - response_id: &str, - limit: i64, - after: Option<&str>, - order: ItemOrder, - ) -> ExecutorResult { + pub async fn list_input_items(&self, response_id: &str, page: ItemPage<'_>) -> ExecutorResult { let stored = self.store.get(response_id).await?; let snapshot = stored.metadata.response_snapshot.as_deref().ok_or_else(|| { ExecutorError::Conflict( @@ -114,25 +108,26 @@ impl ResponseHandler { items.push(ItemResponse::new(row.public_id().to_owned(), item)); } } - if order == ItemOrder::Desc { + if page.order == ItemOrder::Desc { items.reverse(); } - let start = match after { + // A public ID can name several occurrences of an item in the history. Like + // conversation item listing, a cursor resumes after the furthest occurrence in + // this ordering, so paging always makes progress. + let start = match page.after { Some(cursor) => { items .iter() - .position(|item| item.id == cursor) + .rposition(|item| item.id == cursor) .ok_or_else(|| StorageError::ItemCursorNotFound { id: cursor.to_owned() })? + 1 } None => 0, }; - // Safe: limit is clamped to [1, 100]. - #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let limit = limit.clamp(1, 100) as usize; - let page: Vec = items.into_iter().skip(start).take(limit + 1).collect(); - let has_more = page.len() > limit; - Ok(ListItemsResponse::new(page.into_iter().take(limit).collect(), has_more)) + let limit = page.limit.get(); + let has_more = items.len() - start > limit; + let data = items.into_iter().skip(start).take(limit).collect(); + Ok(ListItemsResponse::new(data, has_more)) } /// Retrieves the stored response for `previous_response_id`. diff --git a/crates/agentic-server-core/src/storage/conversation.rs b/crates/agentic-server-core/src/storage/conversation.rs index 44ab7abd5..6e63ef4d3 100644 --- a/crates/agentic-server-core/src/storage/conversation.rs +++ b/crates/agentic-server-core/src/storage/conversation.rs @@ -205,10 +205,6 @@ impl ConversationStore { ) -> StoreResult<()> { let pool = self.pool()?; - let is_output: Vec = new_items - .iter() - .map(|item| matches!(item, InOutItem::Output(_))) - .collect(); let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; let mut tx = pool.begin().await?; @@ -232,8 +228,8 @@ impl ConversationStore { }); } } - let created = item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; - let metadata_json = metadata.to_stored_json(super::response::output_row_ids(&is_output, &created))?; + let written = item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; + let metadata_json = metadata.serialize_with_output_rows(&written)?; // A response branch must see the conversation as it existed at this turn, // including manually added items that may later be detached or deleted. let history_item_ids = item::ids_for_conversation_in_tx(&mut tx, conversation_id).await?; @@ -327,7 +323,11 @@ impl ConversationStore { let items = item::serialize_new_items(items, item::ItemSource::ConversationApi)?; let mut tx = self.pool()?.begin().await?; lock_owned(&mut tx, tenant_id, id).await?; - let rows = item::create_in_tx(&mut tx, items, Some(id)).await?; + let rows: Vec = item::create_in_tx(&mut tx, items, Some(id)) + .await? + .into_iter() + .map(|written| written.row) + .collect(); if !rows.is_empty() { conversation::bump_revision_in_tx(&mut tx, id).await?; } diff --git a/crates/agentic-server-core/src/storage/models/item.rs b/crates/agentic-server-core/src/storage/models/item.rs index 7933c3fb8..3f75ec0a4 100644 --- a/crates/agentic-server-core/src/storage/models/item.rs +++ b/crates/agentic-server-core/src/storage/models/item.rs @@ -1,7 +1,6 @@ //! Conversation history item stored in the database. use serde_json::Value; -use std::convert::TryFrom; use std::fmt::Write; use tracing::warn; @@ -14,6 +13,9 @@ use crate::types::io::code_interpreter::CodeInterpreterCallOrigin; use crate::types::io::{InputItem, OutputItem, ReasoningOutput}; use crate::utils::common::{deserialize_from_str_opt, utcnow_str, uuid7_str}; +pub use super::new_item::{ItemRole, NewItem, WrittenItem}; +pub(crate) use super::new_item::{ItemSource, serialize_new_items}; + const ITEM_COLUMN_COUNT: usize = 5; const SEQUENCE_COLUMN_INDEX: usize = 4; const MAX_BIND_PARAMETERS: usize = 999; @@ -164,49 +166,6 @@ fn item_values_clause(row_count: usize, first_bind_index: usize, sequence_from_c clause } -/// Distinguishes client-supplied conversation items from persisted model history. -#[derive(Clone, Copy, PartialEq, Eq)] -pub(crate) enum ItemSource { - ConversationApi, - ResponseHistory, -} - -/// Serialize new history items, retaining public IDs already assigned to input or output items. -/// Items without IDs receive a storage-generated ID. -/// -/// # Errors -/// Returns an error if serialization fails or a supplied ID is empty or has an invalid prefix. -pub(crate) fn serialize_new_items(items: Vec, source: ItemSource) -> StoreResult> { - items - .into_iter() - .enumerate() - .map(|(index, item)| { - let (existing_id, prefix) = match &item { - InOutItem::Input(input) => (input.id(), input.id_prefix()), - InOutItem::Output(output) => (output.id(), output.id_prefix()), - }; - // Upstream model IDs are preserved; some vLLM reasoning items use - // msg_ IDs. Prefix errors apply to supplied Conversation Items IDs. - if source == ItemSource::ConversationApi - && let (Some(id), Some(prefix)) = (existing_id, prefix) - && !id.starts_with(prefix) - { - return Err(StorageError::InvalidItemId { - param: format!("items[{index}].id"), - id: id.to_owned(), - prefix: prefix.trim_end_matches('_'), - }); - } - if existing_id == Some("") { - return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); - } - let id = existing_id.map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); - let data = String::try_from(&item)?; - Ok((id, data)) - }) - .collect() -} - /// Create items in a transaction with optional conversation context. /// /// If `conversation_id` is provided, the next sequence range is computed in the insert statement so @@ -220,14 +179,14 @@ pub(crate) fn serialize_new_items(items: Vec, source: ItemSource) -> /// Returns an error if insertion fails or an item already belongs to the conversation. pub async fn create_in_tx( tx: &mut DbTransaction<'_>, - items: Vec<(String, String)>, + items: Vec, conversation_id: Option<&str>, -) -> StoreResult> { +) -> StoreResult> { if items.is_empty() { return Ok(Vec::new()); } - let mut created: Vec = Vec::with_capacity(items.len()); + let mut created: Vec = Vec::with_capacity(items.len()); for batch in items.chunks(MAX_ITEMS_PER_INSERT) { let mut rows = if let Some(conversation_id) = conversation_id { // Later SQL batches check history from before this call, excluding @@ -236,7 +195,7 @@ pub async fn create_in_tx( tx, batch, conversation_id, - created.iter().filter_map(|item| item.seq).min(), + created.iter().filter_map(|item| item.row.seq).min(), ) .await? } else { @@ -245,15 +204,17 @@ pub async fn create_in_tx( if conversation_id.is_some() { rows.sort_by_key(|row| row.seq); } - created.append(&mut rows); + // Both inserts return exactly one row per item, in input order. + created.extend( + rows.into_iter() + .zip(batch) + .map(|(row, item)| WrittenItem { row, role: item.role }), + ); } Ok(created) } -async fn create_in_tx_without_conversation( - tx: &mut DbTransaction<'_>, - items: &[(String, String)], -) -> DbResult> { +async fn create_in_tx_without_conversation(tx: &mut DbTransaction<'_>, items: &[NewItem]) -> DbResult> { // Each occurrence gets a fresh row ID; the supplied ID is public and may repeat. let now = utcnow_str(); let row_ids = items.iter().map(|_| uuid7_str("item_")).collect::>(); @@ -268,8 +229,8 @@ async fn create_in_tx_without_conversation( ); let mut query = sqlx::query_as::<_, Item>(&sql).bind(now); - for ((public_id, data), row_id) in items.iter().zip(&row_ids) { - query = query.bind(row_id).bind(public_id).bind(data); + for (item, row_id) in items.iter().zip(&row_ids) { + query = query.bind(row_id).bind(&item.id).bind(&item.data); } let mut rows = query.fetch_all(&mut **tx).await?; @@ -280,7 +241,7 @@ async fn create_in_tx_without_conversation( async fn create_in_tx_with_next_conversation_seq( tx: &mut DbTransaction<'_>, - items: &[(String, String)], + items: &[NewItem], conversation_id: &str, first_inserted_sequence: Option, ) -> StoreResult> { @@ -326,10 +287,10 @@ async fn create_in_tx_with_next_conversation_seq( .bind(conversation_id) .bind(first_inserted_sequence); #[allow(clippy::cast_possible_wrap)] - for (idx, (id, data)) in items.iter().enumerate() { + for (idx, item) in items.iter().enumerate() { query = query - .bind(id) - .bind(data) + .bind(&item.id) + .bind(&item.data) .bind(now) .bind(conversation_id) .bind(idx as i64) @@ -510,6 +471,14 @@ mod tests { InputItem, OutputItem, OutputMessage, ReasoningOutput, ReasoningTextContent, ResponsesInput, }; + fn history_item(id: &str) -> NewItem { + NewItem { + id: id.to_owned(), + data: "{}".to_owned(), + role: ItemRole::Input, + } + } + #[test] fn new_items_reuse_public_ids_and_generate_storage_ids() { let input: InputItem = serde_json::from_value(serde_json::json!({ @@ -536,10 +505,10 @@ mod tests { ItemSource::ConversationApi, ) .unwrap(); - assert_eq!(rows[0].0, "msg_supplied"); - assert_eq!(rows[1].0, "msg_generated"); - assert!(rows[2].0.starts_with("msg_")); - assert_eq!(rows[3].0, "fc_supplied"); + assert_eq!(rows[0].id, "msg_supplied"); + assert_eq!(rows[1].id, "msg_generated"); + assert!(rows[2].id.starts_with("msg_")); + assert_eq!(rows[3].id, "fc_supplied"); } #[test] @@ -556,8 +525,8 @@ mod tests { let rows = serialize_new_items(vec![InOutItem::Output(output)], ItemSource::ResponseHistory) .expect("serialize code interpreter response history"); - assert_eq!(rows[0].0, "ci_public"); - let data: Value = serde_json::from_str(&rows[0].1).expect("stored output JSON"); + assert_eq!(rows[0].id, "ci_public"); + let data: Value = serde_json::from_str(&rows[0].data).expect("stored output JSON"); assert_eq!(data["id"], "ci_public"); assert_eq!(data["type"], "code_interpreter_call"); } @@ -606,14 +575,14 @@ mod tests { ItemSource::ResponseHistory, ) .expect("serialize response history"); - let gateway_data: Value = serde_json::from_str(&stored[2].1).expect("stored gateway call"); - let native_data: Value = serde_json::from_str(&stored[3].1).expect("stored native call"); + let gateway_data: Value = serde_json::from_str(&stored[2].data).expect("stored gateway call"); + let native_data: Value = serde_json::from_str(&stored[3].data).expect("stored native call"); assert_eq!(gateway_data[STORED_CODE_INTERPRETER_ORIGIN_KEY], "gateway"); assert!(native_data.get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); let history = stored .into_iter() - .map(|(id, data)| { + .map(|NewItem { id, data, .. }| { Item { id, data, @@ -665,7 +634,7 @@ mod tests { assert!(result.is_ok()); let rows = result.unwrap(); assert_eq!(rows.len(), 1); - assert!(rows[0].0.starts_with("msg_")); + assert!(rows[0].id.starts_with("msg_")); } #[test] @@ -691,13 +660,16 @@ mod tests { .await .expect("create in-memory database"); let items = (0..=MAX_BIND_PARAMETERS) - .map(|index| (format!("item_{index}"), "{}".to_owned())) + .map(|index| history_item(&format!("item_{index}"))) .collect::>(); - let public_ids = items.iter().map(|(id, _)| id.clone()).collect::>(); + let public_ids = items.iter().map(|item| item.id.clone()).collect::>(); let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, None) .await - .expect("insert item batches"); + .expect("insert item batches") + .into_iter() + .map(|written| written.row) + .collect::>(); transaction.commit().await.expect("commit item batches"); let row_ids = created.iter().map(|item| item.id.clone()).collect::>(); let loaded = get_items(&pool, &row_ids).await.expect("load item batches"); @@ -716,11 +688,14 @@ mod tests { let pool = crate::storage::create_pool_with_schema(Some("sqlite://?mode=memory")) .await .expect("create in-memory database"); - let items = vec![("item_repeated".to_owned(), "{}".to_owned()); 2]; + let items = vec![history_item("item_repeated"), history_item("item_repeated")]; let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, None) .await - .expect("insert repeated public ids"); + .expect("insert repeated public ids") + .into_iter() + .map(|written| written.row) + .collect::>(); transaction.commit().await.expect("commit history rows"); assert_eq!(created.len(), 2); @@ -742,12 +717,15 @@ mod tests { .expect("create conversation"); let item_count = MAX_ITEMS_PER_INSERT + 1; let items = (0..item_count) - .map(|index| (format!("conversation_item_{index}"), "{}".to_owned())) + .map(|index| history_item(&format!("conversation_item_{index}"))) .collect::>(); let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, Some(conversation_id)) .await - .expect("insert conversation item batches"); + .expect("insert conversation item batches") + .into_iter() + .map(|written| written.row) + .collect::>(); transaction.commit().await.expect("commit item batches"); let stored = get_items_by_conversation(&pool, conversation_id) .await diff --git a/crates/agentic-server-core/src/storage/models/mod.rs b/crates/agentic-server-core/src/storage/models/mod.rs index e0f450859..80f19e026 100644 --- a/crates/agentic-server-core/src/storage/models/mod.rs +++ b/crates/agentic-server-core/src/storage/models/mod.rs @@ -2,6 +2,7 @@ pub mod conversation; pub mod item; +mod new_item; pub mod response; pub use conversation::Conversation; diff --git a/crates/agentic-server-core/src/storage/models/new_item.rs b/crates/agentic-server-core/src/storage/models/new_item.rs new file mode 100644 index 000000000..a6c4abc6e --- /dev/null +++ b/crates/agentic-server-core/src/storage/models/new_item.rs @@ -0,0 +1,86 @@ +//! Items prepared for insertion, and the rows written for them. + +use super::item::Item; +use crate::storage::{StorageError, StoreResult}; +use crate::types::io::InOutItem; +use crate::utils::common::uuid7_str; + +/// Distinguishes client-supplied conversation items from persisted model history. +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum ItemSource { + ConversationApi, + ResponseHistory, +} + +/// Whether an item was supplied as input or produced as model output. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ItemRole { + Input, + Output, +} + +/// An item serialized for insertion. +#[derive(Debug)] +pub struct NewItem { + /// Public item ID: the supplied one, or one generated for the item's kind. + pub id: String, + pub data: String, + pub role: ItemRole, +} + +/// The row written for a [`NewItem`], keeping that item's role. +/// +/// The row ID identifies this stored occurrence; the public ID it carries may be +/// shared by other occurrences of the same item. +#[derive(Debug)] +pub struct WrittenItem { + pub row: Item, + pub role: ItemRole, +} + +impl WrittenItem { + /// Row IDs of the written output occurrences, in write order. + pub(crate) fn output_row_ids(written: &[Self]) -> Vec { + written + .iter() + .filter(|item| item.role == ItemRole::Output) + .map(|item| item.row.id.clone()) + .collect() + } +} + +/// Serialize new history items, retaining public IDs already assigned to input or output items. +/// Items without IDs receive a storage-generated ID. +/// +/// # Errors +/// Returns an error if serialization fails or a supplied ID is empty or has an invalid prefix. +pub(crate) fn serialize_new_items(items: Vec, source: ItemSource) -> StoreResult> { + items + .into_iter() + .enumerate() + .map(|(index, item)| { + let (existing_id, prefix, role) = match &item { + InOutItem::Input(input) => (input.id(), input.id_prefix(), ItemRole::Input), + InOutItem::Output(output) => (output.id(), output.id_prefix(), ItemRole::Output), + }; + // Upstream model IDs are preserved; some vLLM reasoning items use + // msg_ IDs. Prefix errors apply to supplied Conversation Items IDs. + if source == ItemSource::ConversationApi + && let (Some(id), Some(prefix)) = (existing_id, prefix) + && !id.starts_with(prefix) + { + return Err(StorageError::InvalidItemId { + param: format!("items[{index}].id"), + id: id.to_owned(), + prefix: prefix.trim_end_matches('_'), + }); + } + if existing_id == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = existing_id.map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok(NewItem { id, data, role }) + }) + .collect() +} diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index ca86c591a..b790eab6c 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -158,18 +158,14 @@ impl ResponseStore { Some(prev_id) => self.get(prev_id).await?.history_item_ids, None => Vec::new(), }; - let is_output: Vec = new_items - .iter() - .map(|item| matches!(item, InOutItem::Output(_))) - .collect(); let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; let mut tx = pool.begin().await?; // History rows get their own row IDs, so repeated public IDs keep every position. - let created = item::create_in_tx(&mut tx, items_, None).await?; - let metadata_json = metadata.to_stored_json(output_row_ids(&is_output, &created))?; - item_ids.extend(created.into_iter().map(|item| item.id)); + let written = item::create_in_tx(&mut tx, items_, None).await?; + let metadata_json = metadata.serialize_with_output_rows(&written)?; + item_ids.extend(written.into_iter().map(|item| item.row.id)); let history_item_ids_json = serialize_to_string(&item_ids)?; response::create_in_tx( @@ -187,20 +183,6 @@ impl ResponseStore { } } -/// Row IDs of the output items among rows returned by [`item::create_in_tx`]. -/// -/// `is_output` must be taken from the batch before serialization; `create_in_tx` -/// returns rows in that same order. Row IDs are used rather than public IDs because -/// a public ID can recur in history, while each occurrence has its own row. -pub(super) fn output_row_ids(is_output: &[bool], created: &[item::Item]) -> Vec { - is_output - .iter() - .zip(created) - .filter(|(is_output, _)| **is_output) - .map(|(_, row)| row.id.clone()) - .collect() -} - #[cfg(test)] mod tests { use super::super::types::ResponseMetadata; diff --git a/crates/agentic-server-core/src/storage/schema/readiness.rs b/crates/agentic-server-core/src/storage/schema/readiness.rs index 18aff5101..b41a301ad 100644 --- a/crates/agentic-server-core/src/storage/schema/readiness.rs +++ b/crates/agentic-server-core/src/storage/schema/readiness.rs @@ -18,7 +18,11 @@ pub(crate) async fn verify_persistence_writable(pool: &DbPool) -> DbResult<()> { crate::storage::models::conversation::lock_in_tx(&mut transaction, &conversation_id).await?; crate::storage::models::item::create_in_tx( &mut transaction, - vec![(item_id.clone(), "{}".to_owned())], + vec![crate::storage::models::item::NewItem { + id: item_id.clone(), + data: "{}".to_owned(), + role: crate::storage::models::item::ItemRole::Input, + }], Some(&conversation_id), ) .await diff --git a/crates/agentic-server-core/src/storage/types/response.rs b/crates/agentic-server-core/src/storage/types/response.rs index d93877699..1fcdba5a4 100644 --- a/crates/agentic-server-core/src/storage/types/response.rs +++ b/crates/agentic-server-core/src/storage/types/response.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use super::super::models::Response as StorageDbResponse; +use super::super::models::item::WrittenItem; use super::errors::StorageError; use crate::types::agent_tree::StoredTreeSnapshot; use crate::types::io::ToolChoice; @@ -118,14 +119,19 @@ pub(in crate::storage) fn decode_metadata( } impl ResponseMetadata { - /// Serialize for one stored row, recording which written items are its own output. + /// Encode the metadata of a response whose items were just written, recording + /// the row IDs of its own output occurrences. /// /// # Errors /// Returns [`StorageError::Serialization`] if the metadata cannot be encoded. - pub(in crate::storage) fn to_stored_json(&self, output_item_ids: Vec) -> Result { - let mut persisted = self.clone(); - persisted.output_item_ids = Some(output_item_ids); - String::try_from(&persisted) + pub(in crate::storage) fn serialize_with_output_rows( + &self, + written: &[WrittenItem], + ) -> Result { + String::try_from(Self { + output_item_ids: Some(WrittenItem::output_row_ids(written)), + ..self.clone() + }) } } @@ -133,7 +139,15 @@ impl TryFrom<&ResponseMetadata> for String { type Error = StorageError; fn try_from(metadata: &ResponseMetadata) -> Result { - let mut persisted = metadata.clone(); + Self::try_from(metadata.clone()) + } +} + +/// Sanitizes stored tool credentials in place, then encodes the metadata. +impl TryFrom for String { + type Error = StorageError; + + fn try_from(mut persisted: ResponseMetadata) -> Result { if let Some(tools) = persisted.effective_tools.as_mut() { for tool in tools { tool.sanitize_for_persistence(); diff --git a/crates/agentic-server-core/src/types/conversations.rs b/crates/agentic-server-core/src/types/conversations.rs index 7aa5a1126..9b0200da0 100644 --- a/crates/agentic-server-core/src/types/conversations.rs +++ b/crates/agentic-server-core/src/types/conversations.rs @@ -1,7 +1,9 @@ //! Types for the OpenAI-compatible Conversations API. use std::collections::BTreeMap; +use std::str::FromStr; +use serde::de::IntoDeserializer as _; use serde::{Deserialize, Serialize, Serializer}; use super::event::MessageStatus; @@ -129,6 +131,109 @@ pub enum ItemOrder { Desc, } +/// Page size for item listings, validated once to `1..=100`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ItemLimit(u8); + +impl ItemLimit { + const MAX: u8 = 100; + + /// The number of items a page holds. + #[must_use] + pub fn get(self) -> usize { + usize::from(self.0) + } + + /// One more than the page size: fetching it tells whether another page follows. + #[must_use] + pub fn probe(self) -> i64 { + i64::from(self.0) + 1 + } +} + +impl Default for ItemLimit { + fn default() -> Self { + Self(20) + } +} + +impl TryFrom for ItemLimit { + type Error = InvalidItemLimit; + + fn try_from(value: i64) -> Result { + u8::try_from(value) + .ok() + .filter(|limit| (1..=Self::MAX).contains(limit)) + .map(Self) + .ok_or(InvalidItemLimit) + } +} + +/// A page size outside `1..=100`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +#[error("limit must be between 1 and 100")] +pub struct InvalidItemLimit; + +/// Validated pagination for item listings. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct ItemPage<'a> { + pub limit: ItemLimit, + /// Public ID of the last item on the previous page. + pub after: Option<&'a str>, + pub order: ItemOrder, +} + +/// Extra item fields a listing can be asked to include, as `OpenAI` defines them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum ItemInclude { + #[serde(rename = "file_search_call.results")] + FileSearchCallResults, + #[serde(rename = "web_search_call.results")] + WebSearchCallResults, + #[serde(rename = "web_search_call.action.sources")] + WebSearchCallActionSources, + #[serde(rename = "message.input_image.image_url")] + MessageInputImageUrl, + #[serde(rename = "computer_call_output.output.image_url")] + ComputerCallOutputImageUrl, + #[serde(rename = "code_interpreter_call.outputs")] + CodeInterpreterCallOutputs, + #[serde(rename = "reasoning.encrypted_content")] + ReasoningEncryptedContent, + #[serde(rename = "message.output_text.logprobs")] + MessageOutputTextLogprobs, +} + +impl FromStr for ItemInclude { + type Err = serde::de::value::Error; + + fn from_str(value: &str) -> Result { + Self::deserialize(value.into_deserializer()) + } +} + +impl ItemInclude { + /// Whether stored items already carry this field. + /// + /// Stored items keep every field their item had, so expansions that read the + /// item itself are served as stored. Hosted tool results come from data sources + /// the gateway does not keep, so those expansions cannot be served. + #[must_use] + pub const fn is_stored_with_items(self) -> bool { + match self { + Self::MessageInputImageUrl + | Self::ComputerCallOutputImageUrl + | Self::ReasoningEncryptedContent + | Self::MessageOutputTextLogprobs => true, + Self::FileSearchCallResults + | Self::WebSearchCallResults + | Self::WebSearchCallActionSources + | Self::CodeInterpreterCallOutputs => false, + } + } +} + /// Response for successful deletion. #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] @@ -204,7 +309,7 @@ impl DeletedResponse { pub fn response(id: String) -> Self { Self { id, - object: "response".to_string(), + object: "response.deleted".to_string(), deleted: true, } } diff --git a/crates/agentic-server-core/tests/conversation_branch_response_test.rs b/crates/agentic-server-core/tests/conversation_branch_response_test.rs index b4bf5f160..ee3029dd6 100644 --- a/crates/agentic-server-core/tests/conversation_branch_response_test.rs +++ b/crates/agentic-server-core/tests/conversation_branch_response_test.rs @@ -8,6 +8,7 @@ use std::sync::Arc; use agentic_core::executor::ExecuteRequest; use agentic_core::executor::session::ResponseSession; use agentic_core::types::RequestPayload; +use agentic_core::types::conversations::{ItemLimit, ItemOrder, ItemPage}; use either::Either; use futures::StreamExt; use serde_json::Value; @@ -95,7 +96,7 @@ async fn assert_branch_is_detached(streaming: bool) { let before = fixture .exec_ctx .conv_handler - .list_items("default_tenant", &conversation_id, 100, None, "asc") + .list_items("default_tenant", &conversation_id, all_ascending()) .await .unwrap(); let parent_id = parent["id"].as_str().unwrap(); @@ -118,7 +119,7 @@ async fn assert_branch_is_detached(streaming: bool) { let after = fixture .exec_ctx .conv_handler - .list_items("default_tenant", &conversation_id, 100, None, "asc") + .list_items("default_tenant", &conversation_id, all_ascending()) .await .unwrap(); assert_eq!( @@ -150,3 +151,11 @@ async fn previous_response_branch_omits_conversation_in_json_and_retrieval() { async fn previous_response_branch_omits_conversation_in_every_stream_response() { assert_branch_is_detached(true).await; } + +fn all_ascending() -> ItemPage<'static> { + ItemPage { + limit: ItemLimit::try_from(100).unwrap(), + after: None, + order: ItemOrder::Asc, + } +} diff --git a/crates/agentic-server-core/tests/conversation_storage_regressions.rs b/crates/agentic-server-core/tests/conversation_storage_regressions.rs index 9d6034b73..36ed5b99c 100644 --- a/crates/agentic-server-core/tests/conversation_storage_regressions.rs +++ b/crates/agentic-server-core/tests/conversation_storage_regressions.rs @@ -186,6 +186,14 @@ async fn insert_with_ids( ) -> agentic_core::storage::StoreResult<()> { let mut tx = pool.begin().await?; agentic_core::storage::models::conversation::lock_in_tx(&mut tx, id).await?; + let items = items + .into_iter() + .map(|(id, data)| item_model::NewItem { + id, + data, + role: item_model::ItemRole::Input, + }) + .collect(); item_model::create_in_tx(&mut tx, items, Some(id)).await?; tx.commit().await?; Ok(()) diff --git a/crates/agentic-server-core/tests/conversations/edge_replay.rs b/crates/agentic-server-core/tests/conversations/edge_replay.rs index 72411afd7..4356ff012 100644 --- a/crates/agentic-server-core/tests/conversations/edge_replay.rs +++ b/crates/agentic-server-core/tests/conversations/edge_replay.rs @@ -4,7 +4,8 @@ use super::{IdPairs, Turn, Value, cassette, compare_value, remap_error_message}; use agentic_core::executor::{ConversationHandler, ExecutorError}; use agentic_core::storage::{ConversationData, ConversationStore, create_pool_with_schema}; use agentic_core::types::conversations::{ - ConversationResponse, CreateConversationRequest, CreateItemRequest, ListItemsResponse, + ConversationResponse, CreateConversationRequest, CreateItemRequest, ItemLimit, ItemOrder, ItemPage, + ListItemsResponse, }; pub(super) fn remap_ids(value: &Value, ids: &IdPairs) -> Value { @@ -57,15 +58,17 @@ pub(super) async fn execute_step( Ok(serde_json::to_value(ListItemsResponse::new(items, false)).unwrap()) } ("GET", 4) => { - let limit = query["limit"].as_str().map_or(20, |limit| limit.parse().unwrap()); + let page = ItemPage { + limit: query["limit"].as_str().map_or_else(ItemLimit::default, |limit| { + ItemLimit::try_from(limit.parse::().unwrap()).unwrap() + }), + after: query["after"].as_str(), + order: query["order"].as_str().map_or_else(ItemOrder::default, |order| { + serde_json::from_value(Value::from(order)).unwrap() + }), + }; let items = handler - .list_items( - "default_tenant", - conversation.unwrap(), - limit, - query["after"].as_str(), - query["order"].as_str().unwrap_or("desc"), - ) + .list_items("default_tenant", conversation.unwrap(), page) .await?; Ok(serde_json::to_value(items).unwrap()) } diff --git a/crates/agentic-server/src/handler/http/conversation_items.rs b/crates/agentic-server/src/handler/http/conversation_items.rs index c3f64644a..b93cc199f 100644 --- a/crates/agentic-server/src/handler/http/conversation_items.rs +++ b/crates/agentic-server/src/handler/http/conversation_items.rs @@ -1,77 +1,104 @@ //! HTTP transport for conversation item CRUD. -use axum::extract::{Path, Query, Request, State}; +use std::borrow::Cow; + +use axum::extract::{Path, RawQuery, Request, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use serde::Deserialize; #[cfg(feature = "openapi")] use agentic_core::types::ConversationResponse; -use agentic_core::types::conversations::ItemOrder; +use agentic_core::types::conversations::{ItemInclude, ItemLimit, ItemOrder, ItemPage}; use agentic_core::types::{CreateItemRequest, ItemResponse, ListItemsResponse}; use super::super::common::{error_response, executor_error_response, extract_json, read_bytes}; use super::conversations::{DEFAULT_TENANT_ID, conversation_response}; use crate::app::AppState; -/// Query parameters for listing conversation items. -#[derive(Debug, Deserialize)] +/// Query parameters for item listings. +/// +/// Parsed from the raw query string by [`ListItemsQuery::parse`]: `include` may +/// repeat, which form deserialization into a struct does not support. +#[derive(Debug, Default)] #[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))] +#[cfg_attr(feature = "openapi", into_params(parameter_in = Query))] pub struct ListItemsQuery { /// Maximum number of items to return (default: 20, max: 100). - #[serde(default = "default_limit")] - pub limit: i64, + #[cfg_attr(feature = "openapi", param(value_type = Option, minimum = 1, maximum = 100))] + pub limit: ItemLimit, /// Cursor for pagination (item ID to start after). pub after: Option, /// Sort order for items (default: desc). Ascending order is oldest-first. - #[serde(default = "default_order")] - pub order: String, + #[cfg_attr(feature = "openapi", param(value_type = Option))] + pub order: ItemOrder, - /// Optional output expansion, encoded as `include[]=value` by `OpenAI` clients. - #[serde(alias = "include[]")] - pub include: Option, + /// Extra item fields to include, repeatable and also accepted as `include[]`. + #[cfg_attr(feature = "openapi", param(value_type = Option>))] + pub include: Vec, } impl ListItemsQuery { - /// Validate the parameters shared by every item listing, returning the order. + /// Parse and validate the parameters shared by every item listing. + /// + /// Unknown parameters are ignored, as typed query extraction does. /// /// # Errors - /// Returns a 400 response for an out-of-range limit, an unknown order, or an - /// include expansion that stored items cannot serve. - pub(super) fn validate(&self) -> Result> { - let message = if self.limit < 1 || self.limit > 100 { - "limit must be between 1 and 100" - } else if self.order != "asc" && self.order != "desc" { - "order must be 'asc' or 'desc'" - } else if self - .include - .as_deref() - // Text output logprobs are already present in stored message items. Other - // include expansions need their own data sources before they can be served. - .is_some_and(|value| value != "message.output_text.logprobs") - { - "unsupported include value" - } else if self.order == "asc" { - return Ok(ItemOrder::Asc); - } else { - return Ok(ItemOrder::Desc); - }; - Err(Box::new(error_response( - StatusCode::BAD_REQUEST, - "invalid_request", - message, - ))) + /// Returns a 400 response naming the offending parameter for a repeated + /// single-valued parameter, an out-of-range limit, an unknown order, or an + /// include value that is unknown or that stored items cannot serve. + pub(super) fn parse(raw: Option<&str>) -> Result> { + let invalid = |message: String| Box::new(error_response(StatusCode::BAD_REQUEST, "invalid_request", &message)); + let mut query = Self::default(); + let (mut limit, mut after, mut order) = (None, None, None); + for (key, value) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) { + let slot = match key.as_ref() { + "limit" => &mut limit, + "after" => &mut after, + "order" => &mut order, + "include" | "include[]" => { + let include = value + .parse::() + .map_err(|_| invalid(format!("unknown include value '{value}'")))?; + if !include.is_stored_with_items() { + return Err(invalid(format!("include value '{value}' is not supported"))); + } + query.include.push(include); + continue; + } + _ => continue, + }; + if slot.replace(value.clone()).is_some() { + return Err(invalid(format!("'{key}' must be given at most once"))); + } + } + if let Some(limit) = limit { + query.limit = limit + .parse::() + .ok() + .and_then(|limit| ItemLimit::try_from(limit).ok()) + .ok_or_else(|| invalid("limit must be between 1 and 100".to_owned()))?; + } + if let Some(order) = order { + query.order = match order.as_ref() { + "asc" => ItemOrder::Asc, + "desc" => ItemOrder::Desc, + _ => return Err(invalid("order must be 'asc' or 'desc'".to_owned())), + }; + } + query.after = after.map(Cow::into_owned); + Ok(query) } -} -fn default_limit() -> i64 { - 20 -} - -fn default_order() -> String { - "desc".to_string() + /// The validated pagination, borrowing the cursor. + pub(super) fn page(&self) -> ItemPage<'_> { + ItemPage { + limit: self.limit, + after: self.after.as_deref(), + order: self.order, + } + } } #[cfg_attr(feature = "openapi", utoipa::path( @@ -133,22 +160,17 @@ pub async fn create_item(State(state): State, Path(conversation_id): P pub async fn list_items( State(state): State, Path(conversation_id): Path, - Query(query): Query, + RawQuery(raw): RawQuery, ) -> Response { - if let Err(error) = query.validate() { - return *error; - } + let query = match ListItemsQuery::parse(raw.as_deref()) { + Ok(query) => query, + Err(error) => return *error, + }; match state .exec_ctx .conv_handler - .list_items( - DEFAULT_TENANT_ID, - &conversation_id, - query.limit, - query.after.as_deref(), - &query.order, - ) + .list_items(DEFAULT_TENANT_ID, &conversation_id, query.page()) .await { Ok(response) => axum::Json(response).into_response(), diff --git a/crates/agentic-server/src/handler/http/responses.rs b/crates/agentic-server/src/handler/http/responses.rs index 40416c635..6a92ba7d1 100644 --- a/crates/agentic-server/src/handler/http/responses.rs +++ b/crates/agentic-server/src/handler/http/responses.rs @@ -1,4 +1,4 @@ -use axum::extract::{Path, Request, State}; +use axum::extract::{Path, RawQuery, Request, State}; use axum::http::request::Parts; use axum::response::{IntoResponse, Response}; use bytes::Bytes; @@ -17,6 +17,7 @@ use agentic_core::types::request_response::{CompactRequest, PromptCacheOptions, use super::super::common::{ convert_response, executor_error_response, extract_bearer, read_bytes, read_json, sse_response, }; +use super::conversation_items::ListItemsQuery; use crate::app::AppState; // Only executor requests interpret text and prompt-cache options. Proxy requests retain their raw body. @@ -191,7 +192,7 @@ pub async fn delete_response(State(state): State, Path(response_id): P path = "/v1/responses/{response_id}/input_items", params( ("response_id" = String, Path, description = "Stored response ID"), - super::conversation_items::ListItemsQuery + ListItemsQuery ), responses( (status = 200, description = "Input items", body = agentic_core::types::ListItemsResponse), @@ -206,16 +207,16 @@ pub async fn delete_response(State(state): State, Path(response_id): P pub async fn list_input_items( State(state): State, Path(response_id): Path, - axum::extract::Query(query): axum::extract::Query, + RawQuery(raw): RawQuery, ) -> Response { - let order = match query.validate() { - Ok(order) => order, + let query = match ListItemsQuery::parse(raw.as_deref()) { + Ok(query) => query, Err(error) => return *error, }; match state .exec_ctx .resp_handler - .list_input_items(&response_id, query.limit, query.after.as_deref(), order) + .list_input_items(&response_id, query.page()) .await { Ok(page) => axum::Json(page).into_response(), diff --git a/crates/agentic-server/src/openapi.rs b/crates/agentic-server/src/openapi.rs index 0e6ff2d36..e14cbd79c 100644 --- a/crates/agentic-server/src/openapi.rs +++ b/crates/agentic-server/src/openapi.rs @@ -178,6 +178,7 @@ use utoipa::OpenApi; agentic_core::types::conversations::ListItemsResponse, InOutItem, agentic_core::types::conversations::ItemOrder, + agentic_core::types::conversations::ItemInclude, )), modifiers(&SecurityAddon), tags( diff --git a/crates/agentic-server/tests/response_input_items_test.rs b/crates/agentic-server/tests/response_input_items_test.rs index e05a8e3ce..d33bcd6cb 100644 --- a/crates/agentic-server/tests/response_input_items_test.rs +++ b/crates/agentic-server/tests/response_input_items_test.rs @@ -227,17 +227,23 @@ async fn input_items_reject_what_retrieval_rejects() { for missing in [transient["id"].as_str().unwrap(), "resp_missing"] { assert_eq!(harness.input_items(missing, "").await.status(), StatusCode::NOT_FOUND); } - for query in [ - "?limit=0", - "?limit=101", - "?order=sideways", - "?include=reasoning.encrypted_content", + for (query, message) in [ + ("?limit=0", "limit must be between 1 and 100"), + ("?limit=101", "limit must be between 1 and 100"), + ("?limit=ten", "limit must be between 1 and 100"), + ("?limit=1&limit=2", "'limit' must be given at most once"), + ("?order=sideways", "order must be 'asc' or 'desc'"), + ("?include=no.such.field", "unknown include value 'no.such.field'"), + // Hosted tool results come from data sources the gateway does not keep. + ( + "?include[]=web_search_call.results", + "include value 'web_search_call.results' is not supported", + ), ] { - assert_eq!( - harness.input_items(stored_id, query).await.status(), - StatusCode::BAD_REQUEST, - "{query}" - ); + let response = harness.input_items(stored_id, query).await; + assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{query}"); + let body: Value = response.json().await.expect("a structured error body"); + assert_eq!(body["error"]["message"], message, "{query}"); } // Same contract as conversation item listing: an unknown cursor is a not-found naming `after`. let unknown_cursor = harness.input_items(stored_id, "?after=msg_unknown").await; @@ -331,3 +337,74 @@ async fn input_items_keep_an_earlier_turn_output_that_shares_this_turn_output_id ); harness.stop().await; } + +#[tokio::test] +async fn input_items_accept_repeated_include_values_that_stored_items_carry() { + let harness = Harness::start().await; + let stored = harness + .create( + json!({"model":"test-model", "store":true, "input":[{"role":"user", "content":[ + {"type":"input_text", "text":"look"}, + {"type":"input_image", "image_url":"data:image/png;base64,AAAA"}]}]}), + ) + .await; + let page = harness + .page( + stored["id"].as_str().unwrap(), + "?include[]=message.input_image.image_url&include[]=message.output_text.logprobs\ + &include=reasoning.encrypted_content", + ) + .await; + assert_eq!( + page["data"][0]["content"][1]["image_url"], "data:image/png;base64,AAAA", + "{page}" + ); + harness.stop().await; +} + +#[tokio::test] +async fn input_item_pages_make_progress_past_a_repeated_public_id() { + // The upstream reuses one message ID every turn, so the third turn's input holds + // that public ID twice. A cursor naming it resumes after its furthest occurrence, + // as conversation item listing does, instead of returning the same page forever. + let upstream = Router::new().route( + "/v1/responses", + post(|Json(_): Json| async { + Json(json!({"id":"resp_upstream", "object":"response", "created_at":123, + "model":"test-model", "status":"completed", "output":[{ + "type":"message", "id":"msg_reused", "role":"assistant", "status":"completed", + "content":[{"type":"output_text", "text":"same id"}]}], + "usage":{"input_tokens":3,"output_tokens":1,"total_tokens":4}})) + }), + ); + let harness = Harness::start_with(upstream).await; + let mut previous = Value::Null; + for input in ["first", "second", "third"] { + previous = harness + .create(json!({"model":"test-model", "input":input, "store":true, "previous_response_id":previous["id"]})) + .await; + } + let third = previous["id"].as_str().unwrap(); + assert_eq!( + texts(&harness.page(third, "?order=asc").await), + ["first", "same id", "second", "same id", "third"] + ); + + for (order, expected) in [ + ("asc", ["first", "same id", "third"]), + ("desc", ["third", "same id", "first"]), + ] { + let mut cursor = String::new(); + let mut walked = Vec::new(); + for _ in 0..10 { + let page = harness.page(third, &format!("?order={order}&limit=1{cursor}")).await; + walked.extend(texts(&page)); + if page["has_more"] == false { + break; + } + cursor = format!("&after={}", page["last_id"].as_str().unwrap()); + } + assert_eq!(walked, expected, "{order}"); + } + harness.stop().await; +} diff --git a/crates/agentic-server/tests/response_retrieval_test.rs b/crates/agentic-server/tests/response_retrieval_test.rs index a6d67f076..a951b3bea 100644 --- a/crates/agentic-server/tests/response_retrieval_test.rs +++ b/crates/agentic-server/tests/response_retrieval_test.rs @@ -320,7 +320,7 @@ async fn deleting_a_response_keeps_its_descendants_and_ends_its_own_continuation assert_eq!(deleted.status(), StatusCode::OK); assert_eq!( deleted.json::().await.unwrap(), - json!({"id":parent_id, "object":"response", "deleted":true}) + json!({"id":parent_id, "object":"response.deleted", "deleted":true}) ); assert_eq!(retrieve(parent_id).await.status(), StatusCode::NOT_FOUND); assert_eq!(delete(parent_id).await.status(), StatusCode::NOT_FOUND); diff --git a/docs/api/index.md b/docs/api/index.md index 4d93f5cd7..c81d71404 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -439,7 +439,7 @@ must send that key as a bearer token. ### `DELETE /v1/responses/{response_id}` Deletes a stored response and returns -`{"id": "", "object": "response", "deleted": true}`. Later +`{"id": "", "object": "response.deleted", "deleted": true}`. Later retrieval returns `404`, and a new request naming the deleted ID as `previous_response_id` is rejected. Responses created from it stay retrievable and can still be continued: each stored response keeps its own complete @@ -460,8 +460,17 @@ followed by that turn's input. The response's own output is not included. A conversation turn lists the conversation as it was at that turn, not as it is now. Pagination matches conversation item listing: `limit` (1–100, default 20), `order` (`desc` by default), and `after` (the ID of the last item on the -previous page, `404` if unknown). Unknown IDs return `404`, and records without -a stored response snapshot return `409`. Authentication matches retrieval. +previous page, `404` if unknown). An item can occur more than once in a +history; an `after` cursor naming it resumes after its last occurrence in the +requested order, so paging always advances. `include` (repeatable, also as +`include[]`) accepts `message.input_image.image_url`, +`computer_call_output.output.image_url`, `reasoning.encrypted_content`, and +`message.output_text.logprobs`, which stored items already carry; hosted tool +results (`web_search_call.*`, `file_search_call.results`, +`code_interpreter_call.outputs`) and unknown values return `400`, as do a +repeated `limit`, `order`, or `after`. Conversation item listing parses its +query the same way. Unknown IDs return `404`, and records without a stored +response snapshot return `409`. Authentication matches retrieval. ### `POST /v1/responses/compact` From fd0cdef6d436cd1bde39cd061848e0ba3b855692 Mon Sep 17 00:00:00 2001 From: fengjiaqi Date: Thu, 22 Oct 2026 01:01:11 -0700 Subject: [PATCH 5/7] fix: erase unreferenced history on response deletion - Delete the deleted response's history rows that nothing else keeps: rows another response lists, rows that belong to a conversation, and rows a conversation item references stay. - When the response was its conversation's latest, point the conversation at its newest remaining response instead of clearing it, so the next turn keeps the closest stored state. - Report an unknown previous_response_id on the HTTP path as 400 previous_response_not_found, as OpenAI does and the WebSocket path already did. Signed-off-by: fengjiaqi --- .../tests/split_execution_integration.rs | 7 +- .../src/executor/rehydrate.rs | 30 ++-- .../src/storage/models/response.rs | 111 +++++++++++++-- .../src/storage/response.rs | 26 +++- .../tests/execution_trace_test.rs | 2 +- .../tests/response_deletion_storage_test.rs | 131 ++++++++++++++++++ .../tests/stateful_responses_integration.rs | 4 +- .../tests/response_retrieval_test.rs | 11 +- docs/api/index.md | 15 +- 9 files changed, 304 insertions(+), 33 deletions(-) create mode 100644 crates/agentic-server-core/tests/response_deletion_storage_test.rs diff --git a/crates/agentic-llm-d/tests/split_execution_integration.rs b/crates/agentic-llm-d/tests/split_execution_integration.rs index 3f2adaabf..67e216142 100644 --- a/crates/agentic-llm-d/tests/split_execution_integration.rs +++ b/crates/agentic-llm-d/tests/split_execution_integration.rs @@ -609,7 +609,8 @@ async fn a_turn_that_cannot_be_stored_is_refused() { let ctx = exec_ctx().await; let unknown = hydrate(request("hi", Some("resp_missing")), &ctx).await; - assert_eq!(status_of(&unknown.expect_err("unknown id")), 404); + // As OpenAI reports it: a request error naming `previous_response_id`, not a missing resource. + assert_eq!(status_of(&unknown.expect_err("unknown id")), 400); // A caller-supplied body gets none of the in-process parser's defaults, // including the catch-all used for compatibility on trusted internal paths. @@ -746,7 +747,7 @@ async fn a_turn_that_is_not_written_still_returns() { [agentic_core::types::io::OutputItem::FunctionCall(call)] if call.call_id.is_empty() )); let orphan = hydrate(request("and then?", Some(&id)), &ctx).await; - assert_eq!(status_of(&orphan.expect_err("never stored")), 404); + assert_eq!(status_of(&orphan.expect_err("never stored")), 400); // A failed response remains returned-but-unstored even when its partial // item representations disagree; no unusable continuation can be created. @@ -762,7 +763,7 @@ async fn a_turn_that_is_not_written_still_returns() { .expect("failed partial output is not a persistence error"); assert_eq!(payload.status, "error"); let orphan = hydrate(request("and then?", Some(&failed_id)), &ctx).await; - assert_eq!(status_of(&orphan.expect_err("failed SSE was never stored")), 404); + assert_eq!(status_of(&orphan.expect_err("failed SSE was never stored")), 400); } /// Until an identical retry can be proven identical, a reused id is refused. diff --git a/crates/agentic-server-core/src/executor/rehydrate.rs b/crates/agentic-server-core/src/executor/rehydrate.rs index 3f945bc0b..82302233b 100644 --- a/crates/agentic-server-core/src/executor/rehydrate.rs +++ b/crates/agentic-server-core/src/executor/rehydrate.rs @@ -239,11 +239,20 @@ async fn from_response(ctx: &mut RequestContext, exec_ctx: &ExecutionContext) -> if ctx.continuation.is_some() { return from_session_response(ctx, exec_ctx).await; } - let stored = exec_ctx.resp_handler.get(ctx).await?; + let previous_id = ctx.original_request.previous_response_id.clone().unwrap_or_default(); + let stored = exec_ctx + .resp_handler + .get(ctx) + .await + .map_err(|error| previous_response_missing(error, &previous_id))?; if restore_agent_tree(ctx, &stored.metadata, exec_ctx.responses_config.max_retained_bytes)? { return Ok(()); } - let history = exec_ctx.resp_handler.rehydrate(ctx).await?; + let history = exec_ctx + .resp_handler + .rehydrate(ctx) + .await + .map_err(|error| previous_response_missing(error, &previous_id))?; let mut items = InOutItem::into_input_items(history); items.reserve(ctx.new_input_items.len()); @@ -260,6 +269,16 @@ async fn from_response(ctx: &mut RequestContext, exec_ctx: &ExecutionContext) -> Ok(()) } +/// A missing `previous_response_id` is a request error, as `OpenAI` reports it, not a missing resource. +fn previous_response_missing(error: ExecutorError, previous_id: &str) -> ExecutorError { + match error { + ExecutorError::Storage(ref source) if source.is_not_found() => ExecutorError::PreviousResponseNotFound { + id: previous_id.to_owned(), + }, + other => other, + } +} + async fn from_session_response(ctx: &mut RequestContext, exec_ctx: &ExecutionContext) -> ExecutorResult<()> { let cached = ctx .continuation @@ -274,12 +293,7 @@ async fn from_session_response(ctx: &mut RequestContext, exec_ctx: &ExecutionCon id: previous_id.to_owned(), }); } - let map_missing = |error| match error { - ExecutorError::Storage(ref source) if source.is_not_found() => ExecutorError::PreviousResponseNotFound { - id: previous_id.to_owned(), - }, - other => other, - }; + let map_missing = |error| previous_response_missing(error, previous_id); let stored = exec_ctx.resp_handler.get(ctx).await.map_err(map_missing)?; let history = exec_ctx.resp_handler.rehydrate(ctx).await.map_err(map_missing)?; let checkpoint = ResponseCheckpoint { diff --git a/crates/agentic-server-core/src/storage/models/response.rs b/crates/agentic-server-core/src/storage/models/response.rs index dc9efc07f..185894711 100644 --- a/crates/agentic-server-core/src/storage/models/response.rs +++ b/crates/agentic-server-core/src/storage/models/response.rs @@ -67,21 +67,116 @@ pub async fn get(pool: &DbPool, id: &str) -> DbResult> { .await } -/// Delete a response row, returning how many rows were removed. +/// History items checked per query when looking for references that keep them alive. +const REFERENCE_CHECK_BATCH: usize = 64; + +/// Read a response row inside a transaction. +/// +/// # Errors +/// Returns `DbResult::Err` if the query fails. +pub async fn get_in_tx(tx: &mut DbTransaction<'_>, id: &str) -> DbResult> { + sqlx::query_as::<_, Response>("SELECT * FROM responses WHERE id = $1") + .bind(id) + .fetch_optional(&mut **tx) + .await +} + +/// Delete a response row. /// -/// Items are left in place: a response-history item id is copied into the -/// `history_item_ids` of every descendant, so it is not owned by this row. /// Foreign keys clear `responses.previous_response_id` and -/// `conversations.latest_response_id` references to the deleted row. +/// `conversations.latest_response_id` references to it. /// /// # Errors /// Returns `DbResult::Err` if the database statement fails. -pub async fn delete(pool: &DbPool, id: &str) -> DbResult { - let result = sqlx::query("DELETE FROM responses WHERE id = $1") +pub async fn delete_in_tx(tx: &mut DbTransaction<'_>, id: &str) -> DbResult<()> { + sqlx::query("DELETE FROM responses WHERE id = $1") .bind(id) - .execute(pool) + .execute(&mut **tx) .await?; - Ok(result.rows_affected()) + Ok(()) +} + +/// Point a conversation that named `deleted_id` as its latest response at its newest remaining response. +/// +/// The latest response carries the conversation's state (effective settings, tools loaded by +/// tool search, the multi-agent tree), so the conversation keeps the closest state it still has. +/// +/// # Errors +/// Returns `DbResult::Err` if a database statement fails. +pub async fn repoint_latest_in_tx(tx: &mut DbTransaction<'_>, conversation_id: &str, deleted_id: &str) -> DbResult<()> { + sqlx::query( + "UPDATE conversations SET latest_response_id = ( \ + SELECT id FROM responses WHERE conversation_id = $1 AND id <> $2 \ + ORDER BY created_at DESC, id DESC LIMIT 1 \ + ) WHERE id = $1 AND latest_response_id = $2", + ) + .bind(conversation_id) + .bind(deleted_id) + .execute(&mut **tx) + .await?; + Ok(()) +} + +/// Delete the history rows of a deleted response that nothing else keeps. +/// +/// Each batch scans the `responses` table once; deletion is rare enough to pay that. +/// +/// A row stays when it belongs to a conversation, when a conversation item references it, +/// or when another response's history lists it (descendants and siblings copy inherited +/// history). Call after the response row itself is deleted. Returns the number of rows removed. +/// +/// # Errors +/// Returns `DbResult::Err` if a database statement fails. +pub async fn delete_unreferenced_items_in_tx(tx: &mut DbTransaction<'_>, history_item_ids: &[String]) -> DbResult { + let mut removed = 0; + for batch in history_item_ids.chunks(REFERENCE_CHECK_BATCH) { + let placeholders = (1..=batch.len()) + .map(|index| format!("${index}")) + .collect::>() + .join(", "); + let detached = format!( + "SELECT id FROM items WHERE id IN ({placeholders}) AND conversation_id IS NULL \ + AND NOT EXISTS (SELECT 1 FROM items referencing WHERE referencing.reference_id = items.id)" + ); + let mut query = sqlx::query_scalar::<_, String>(&detached); + for id in batch { + query = query.bind(id); + } + let mut candidates: Vec = query.fetch_all(&mut **tx).await?; + if candidates.is_empty() { + continue; + } + + // `LIKE` narrows the scan; parsing confirms an exact entry (`_` in IDs is a wildcard). + let patterns = (1..=candidates.len()) + .map(|index| format!("history_item_ids LIKE ${index}")) + .collect::>() + .join(" OR "); + let sql = format!("SELECT history_item_ids FROM responses WHERE {patterns}"); + let mut query = sqlx::query_scalar::<_, Option>(&sql); + for id in &candidates { + query = query.bind(format!("%\"{id}\"%")); + } + for history in query.fetch_all(&mut **tx).await?.into_iter().flatten() { + let listed: Vec = serde_json::from_str(&history).unwrap_or_default(); + candidates.retain(|id| !listed.contains(id)); + } + if candidates.is_empty() { + continue; + } + + let placeholders = (1..=candidates.len()) + .map(|index| format!("${index}")) + .collect::>() + .join(", "); + let delete = format!("DELETE FROM items WHERE id IN ({placeholders})"); + let mut query = sqlx::query(&delete); + for id in &candidates { + query = query.bind(id); + } + removed += query.execute(&mut **tx).await?.rows_affected(); + } + Ok(removed) } /// Get the response that committed a conversation turn. diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index b790eab6c..242b22d40 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::sync::Arc; -use super::models::{item, response}; +use super::models::{conversation, item, response}; use super::pool::DbPool; use super::types::{ResponseData, ResponseMetadata, StorageError, StoreResult}; use crate::types::io::InOutItem; @@ -58,11 +58,13 @@ impl ResponseStore { row.try_into() } - /// Deletes a stored response. + /// Deletes a stored response and the history rows nothing else keeps. /// /// Descendants created with `previous_response_id` stay readable because each /// one stores its own complete history item list; their reference to the - /// deleted response is cleared by the foreign key. + /// deleted response is cleared by the foreign key, and the rows they list are kept. + /// Rows that belong to a conversation stay with it. When the response was its + /// conversation's latest, the conversation moves to its newest remaining response. /// /// Deletion does not reach WebSocket sessions. A session that already caches this /// response as its latest checkpoint keeps continuing from it without a storage @@ -74,9 +76,25 @@ impl ResponseStore { /// Returns [`StorageError::NotFound`] if no response has this ID, or an error /// if the database statement fails or the store is disabled. pub async fn delete(&self, response_id: &str) -> StoreResult<()> { - if response::delete(self.pool()?, response_id).await? == 0 { + let mut tx = self.pool()?.begin().await?; + let Some(row) = response::get_in_tx(&mut tx, response_id).await? else { return Err(StorageError::not_found("Response", response_id)); + }; + // Same lock order as conversation persistence: the conversation, then its responses. + if let Some(conversation_id) = row.conversation_id.as_deref() { + match conversation::lock_in_tx(&mut tx, conversation_id).await { + Ok(_) | Err(sqlx::Error::RowNotFound) => {} + Err(error) => return Err(error.into()), + } + } + let history_item_ids = row.history_item_ids_vec().map_err(StorageError::Serialization)?; + // Before the delete: its foreign key would clear the pointer this matches on. + if let Some(conversation_id) = row.conversation_id.as_deref() { + response::repoint_latest_in_tx(&mut tx, conversation_id, response_id).await?; } + response::delete_in_tx(&mut tx, response_id).await?; + response::delete_unreferenced_items_in_tx(&mut tx, &history_item_ids).await?; + tx.commit().await?; Ok(()) } diff --git a/crates/agentic-server-core/tests/execution_trace_test.rs b/crates/agentic-server-core/tests/execution_trace_test.rs index 992552216..481d860b9 100644 --- a/crates/agentic-server-core/tests/execution_trace_test.rs +++ b/crates/agentic-server-core/tests/execution_trace_test.rs @@ -426,7 +426,7 @@ async fn failure_before_execution_starts_is_not_delivered() { let Err(error) = run(&traces, Arc::clone(&fixture.exec_ctx), payload).await else { panic!("unknown previous_response_id fails rehydration"); }; - assert_eq!(error.http_status(), 404, "{error}"); + assert_eq!(error.http_status(), 400, "{error}"); let span = traces.execute_span().await; assert_execute_attributes(&span, false, "failed", "not_started"); diff --git a/crates/agentic-server-core/tests/response_deletion_storage_test.rs b/crates/agentic-server-core/tests/response_deletion_storage_test.rs new file mode 100644 index 000000000..c772aa18a --- /dev/null +++ b/crates/agentic-server-core/tests/response_deletion_storage_test.rs @@ -0,0 +1,131 @@ +//! Deleting a stored response erases the history rows nothing else keeps, and leaves a +//! conversation on its newest remaining response. + +use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; + +use agentic_core::storage::models::conversation as conversation_model; +use agentic_core::storage::{ConversationStore, DbPool, ResponseMetadata, ResponseStore, create_pool_with_schema}; +use agentic_core::types::io::InOutItem; +use serde_json::json; + +static COUNTER: AtomicU64 = AtomicU64::new(0); + +async fn pool() -> Arc { + let id = COUNTER.fetch_add(1, Ordering::Relaxed); + let url = format!("sqlite:file:response_deletion_{id}?mode=memory&cache=shared"); + create_pool_with_schema(Some(&url)).await.unwrap() +} + +fn message(text: &str) -> InOutItem { + InOutItem::Input(serde_json::from_value(json!({"type": "message", "role": "user", "content": text})).unwrap()) +} + +fn answer(id: &str) -> InOutItem { + InOutItem::Output( + serde_json::from_value( + json!({"type": "message", "id": id, "role": "assistant", "status": "completed", + "content": [{"type": "output_text", "text": "answer", "annotations": []}]}), + ) + .unwrap(), + ) +} + +async fn item_count(pool: &DbPool) -> i64 { + sqlx::query_scalar("SELECT COUNT(*) FROM items") + .fetch_one(pool) + .await + .unwrap() +} + +async fn turn(store: &ResponseStore, id: &str, previous: Option<&str>) { + store + .persist( + id, + previous, + vec![message(id), answer(&format!("msg_{id}"))], + &ResponseMetadata::default(), + ) + .await + .unwrap(); +} + +#[tokio::test] +async fn deleting_a_response_erases_its_prompt_and_answer() { + let pool = pool().await; + let store = ResponseStore::new(Arc::clone(&pool)); + turn(&store, "resp_only", None).await; + assert_eq!(item_count(&pool).await, 2); + + store.delete("resp_only").await.unwrap(); + assert_eq!(item_count(&pool).await, 0); +} + +#[tokio::test] +async fn rows_another_response_lists_survive_until_the_last_one_is_deleted() { + let pool = pool().await; + let store = ResponseStore::new(Arc::clone(&pool)); + // `resp_a` and `resp_b` are siblings: both inherit the parent's two rows. + turn(&store, "resp_parent", None).await; + turn(&store, "resp_a", Some("resp_parent")).await; + turn(&store, "resp_b", Some("resp_parent")).await; + assert_eq!(item_count(&pool).await, 6); + + // Children still list every parent row. + store.delete("resp_parent").await.unwrap(); + assert_eq!(item_count(&pool).await, 6); + // Only `resp_a`'s own rows go; `resp_b` still lists the inherited ones. + store.delete("resp_a").await.unwrap(); + assert_eq!(item_count(&pool).await, 4); + assert_eq!(store.rehydrate("resp_b").await.unwrap().len(), 4); + store.delete("resp_b").await.unwrap(); + assert_eq!(item_count(&pool).await, 0); +} + +#[tokio::test] +async fn deleting_a_conversation_latest_response_keeps_its_items_and_previous_state() { + let pool = pool().await; + let conversations = ConversationStore::new(Arc::clone(&pool)); + let responses = ResponseStore::new(Arc::clone(&pool)); + let conversation = conversations + .create_with_metadata_and_items(Some("default_tenant"), None, vec![]) + .await + .unwrap(); + let id = conversation.conversation_id.as_str(); + for (response, text) in [("resp_first", "first"), ("resp_second", "second")] { + conversations + .persist( + id, + response, + None, + vec![message(text), answer(&format!("msg_{response}"))], + &ResponseMetadata::default(), + ) + .await + .unwrap(); + } + let latest = |pool: Arc| async move { + conversation_model::get(&pool, id) + .await + .unwrap() + .unwrap() + .latest_response_id + }; + assert_eq!(latest(Arc::clone(&pool)).await.as_deref(), Some("resp_second")); + + responses.delete("resp_second").await.unwrap(); + assert_eq!( + latest(Arc::clone(&pool)).await.as_deref(), + Some("resp_first"), + "the conversation keeps the newest state it still has" + ); + assert_eq!( + item_count(&pool).await, + 4, + "conversation items stay with the conversation" + ); + + responses.delete("resp_first").await.unwrap(); + assert_eq!(latest(Arc::clone(&pool)).await, None); + assert_eq!(item_count(&pool).await, 4); +} diff --git a/crates/agentic-server-core/tests/stateful_responses_integration.rs b/crates/agentic-server-core/tests/stateful_responses_integration.rs index c560ddbed..309ef6349 100644 --- a/crates/agentic-server-core/tests/stateful_responses_integration.rs +++ b/crates/agentic-server-core/tests/stateful_responses_integration.rs @@ -646,7 +646,7 @@ async fn test_store_false_with_previous_response_id_hydrates_but_does_not_persis panic!("store=false response must not be persisted"); }; assert!( - matches!(&error, ExecutorError::Storage(source) if source.is_not_found()), + matches!(&error, ExecutorError::PreviousResponseNotFound { .. }), "expected a missing previous response, got {error}" ); assert_eq!( @@ -730,7 +730,7 @@ async fn test_public_persist_steps_honor_the_storage_policy() { async fn is_continuable(exec_ctx: &ExecutionContext, id: &str) -> bool { match rehydrate_conversation(make_request("next", true, false, Some(id.to_owned()), None), exec_ctx).await { Ok(_) => true, - Err(ExecutorError::Storage(source)) if source.is_not_found() => false, + Err(ExecutorError::PreviousResponseNotFound { .. }) => false, Err(error) => panic!("unexpected rehydration error for {id}: {error}"), } } diff --git a/crates/agentic-server/tests/response_retrieval_test.rs b/crates/agentic-server/tests/response_retrieval_test.rs index a951b3bea..edff7ee76 100644 --- a/crates/agentic-server/tests/response_retrieval_test.rs +++ b/crates/agentic-server/tests/response_retrieval_test.rs @@ -209,8 +209,10 @@ async fn store_false_continuation_of_a_stored_response_is_not_retained() { let orphaned = create(json!({"model":"test-model", "input":"third", "store":true, "previous_response_id":child_id})) .await; - assert_eq!(orphaned.status(), StatusCode::NOT_FOUND); - assert!(orphaned.json::().await.unwrap().get("error").is_some()); + assert_eq!(orphaned.status(), StatusCode::BAD_REQUEST); + let body = orphaned.json::().await.unwrap(); + assert_eq!(body["error"]["code"], "previous_response_not_found"); + assert_eq!(body["error"]["param"], "previous_response_id"); assert_eq!( requests.lock().unwrap().len(), 2, @@ -330,7 +332,10 @@ async fn deleting_a_response_keeps_its_descendants_and_ends_its_own_continuation let orphaned = create(json!({"model":"test-model", "input":"third", "store":true, "previous_response_id":parent_id})) .await; - assert_eq!(orphaned.status(), StatusCode::NOT_FOUND); + // As OpenAI reports it: a request error naming `previous_response_id`. + assert_eq!(orphaned.status(), StatusCode::BAD_REQUEST); + let body = orphaned.json::().await.unwrap(); + assert_eq!(body["error"]["code"], "previous_response_not_found", "{body}"); assert_eq!(requests.lock().unwrap().len(), 2); // A descendant stores its own complete history, so it survives its parent: diff --git a/docs/api/index.md b/docs/api/index.md index c81d71404..d97cbd430 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -441,10 +441,17 @@ must send that key as a bearer token. Deletes a stored response and returns `{"id": "", "object": "response.deleted", "deleted": true}`. Later retrieval returns `404`, and a new request naming the deleted ID as -`previous_response_id` is rejected. Responses created from it stay retrievable -and can still be continued: each stored response keeps its own complete -history. Items are not removed, because one item can belong to the history of -many responses. Unknown IDs return `404`. Authentication matches retrieval. +`previous_response_id` returns `400` with code `previous_response_not_found` +and `param: "previous_response_id"`, as for any unknown previous response. +Responses created from it stay retrievable and can still be continued: each +stored response keeps its own complete history. The response's prompt and +output rows are erased unless something still uses them: another response's +history (descendants and siblings inherit history), a conversation, or a +conversation item that references them. Deleting a conversation's latest +response moves the conversation to its newest remaining response, so the next +turn keeps the closest stored state (effective settings, tools loaded by tool +search, the multi-agent tree). Unknown IDs return `404`. Authentication matches +retrieval. Deletion does not reach live WebSocket sessions: a connection whose latest response is the deleted one can still continue from it, because the session From 11e41c34e6b946d6555e5e756dd57823a6e6f82d Mon Sep 17 00:00:00 2001 From: maral Date: Fri, 23 Oct 2026 17:57:59 +0800 Subject: [PATCH 6/7] fix: align response deletion and input items with OpenAI recordings Signed-off-by: maral --- ARCHITECTURE.md | 3 + .../tests/split_execution_integration.rs | 1 - .../src/executor/compaction.rs | 2 +- .../agentic-server-core/src/executor/error.rs | 39 +- .../src/executor/modes/conversation.rs | 14 +- .../src/executor/modes/response.rs | 119 +- .../src/executor/pending_calls.rs | 1 + .../src/executor/persist.rs | 1 - .../src/executor/rehydrate.rs | 36 +- .../response_budget/client_outputs.rs | 1 + .../src/storage/conversation.rs | 25 +- .../src/storage/models/item.rs | 180 +- .../src/storage/models/mod.rs | 1 - .../src/storage/models/new_item.rs | 86 - .../src/storage/models/response.rs | 142 +- .../src/storage/response.rs | 114 +- .../src/storage/schema/readiness.rs | 6 +- .../src/storage/types/item.rs | 21 + .../src/storage/types/response.rs | 36 +- .../src/types/conversations.rs | 260 +- .../src/types/conversations/tests.rs | 30 +- .../agentic-server-core/src/types/io/input.rs | 4 + .../agentic-server-core/src/types/io/item.rs | 221 +- .../src/types/io/item/item_id.rs | 63 +- .../src/types/io/item/tests.rs | 87 + .../agentic-server-core/src/types/io/mod.rs | 4 +- crates/agentic-server-core/src/types/mod.rs | 5 +- .../tests/cassettes/README.md | 107 + .../tests/cassettes/record_cassette.py | 232 +- .../record_responses_api_cassettes.sh | 143 + .../response-conversation-openai.yaml | 977 +++++ .../response-conversation-stream-openai.yaml | 1052 +++++ .../responses/response-edge-cases-openai.yaml | 666 +++ .../response-edge-cases-stream-openai.yaml | 727 ++++ .../responses/response-lifecycle-openai.yaml | 829 ++++ .../response-lifecycle-stream-openai.yaml | 916 ++++ .../responses/response-pagination-openai.yaml | 722 ++++ .../response-pagination-stream-openai.yaml | 809 ++++ .../response-replayed-items-openai.yaml | 622 +++ ...response-replayed-items-stream-openai.yaml | 657 +++ .../responses/response-tool-items-openai.yaml | 3739 +++++++++++++++++ .../conversation_branch_response_test.rs | 19 +- .../tests/conversation_storage_regressions.rs | 10 +- .../tests/conversations/edge_replay.rs | 6 +- .../tests/conversations/item_references.rs | 2 +- .../tests/conversations_api_cassette_test.rs | 2 +- .../tests/postgres_tenant_isolation_test.rs | 2 +- .../tests/relayed_stream_validation_test.rs | 17 +- .../tests/response_deletion_storage_test.rs | 104 +- .../tests/responses_api/recordings.rs | 362 ++ .../tests/responses_api_test.rs | 429 ++ .../tests/stateful_responses_integration.rs | 2 +- .../src/handler/http/conversation_items.rs | 113 +- .../agentic-server/src/handler/http/items.rs | 106 + crates/agentic-server/src/handler/http/mod.rs | 1 + .../src/handler/http/responses.rs | 14 +- crates/agentic-server/src/openapi.rs | 9 +- .../tests/conversations/regressions.rs | 17 +- .../tests/response_input_items_test.rs | 88 +- .../tests/response_retrieval_test.rs | 17 +- docs/api/index.md | 68 +- 61 files changed, 14100 insertions(+), 988 deletions(-) delete mode 100644 crates/agentic-server-core/src/storage/models/new_item.rs create mode 100755 crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-conversation-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-pagination-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-openai.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml create mode 100644 crates/agentic-server-core/tests/responses_api/recordings.rs create mode 100644 crates/agentic-server-core/tests/responses_api_test.rs create mode 100644 crates/agentic-server/src/handler/http/items.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6d572f72b..fe3ce7b63 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -596,6 +596,9 @@ access happen — those live in `tool/`, `executor/`, and `storage/` respectivel `InOutItem::into_input_items` uses the existing output-to-input conversion and client-tool lowering for model history. Storage row markers and legacy decoding remain in `storage/`; there is no separate conversation-item enum. + Item retrieval and listing return `InOutItem` directly. `into_public` assigns the + storage identity and expands shorthand messages on the returned item; + serialization uses the item types themselves, without an item-response wrapper. `ResponsesInput::input_items` uses `InOutItem::as_input` to borrow submitted input items and project outputs. The existing compaction-window selection accepts these borrowed items, so estimation avoids copying message and image payloads and model diff --git a/crates/agentic-llm-d/tests/split_execution_integration.rs b/crates/agentic-llm-d/tests/split_execution_integration.rs index 67e216142..05b8f4fe1 100644 --- a/crates/agentic-llm-d/tests/split_execution_integration.rs +++ b/crates/agentic-llm-d/tests/split_execution_integration.rs @@ -609,7 +609,6 @@ async fn a_turn_that_cannot_be_stored_is_refused() { let ctx = exec_ctx().await; let unknown = hydrate(request("hi", Some("resp_missing")), &ctx).await; - // As OpenAI reports it: a request error naming `previous_response_id`, not a missing resource. assert_eq!(status_of(&unknown.expect_err("unknown id")), 400); // A caller-supplied body gets none of the in-process parser's defaults, diff --git a/crates/agentic-server-core/src/executor/compaction.rs b/crates/agentic-server-core/src/executor/compaction.rs index 294cc6645..678a0b55b 100644 --- a/crates/agentic-server-core/src/executor/compaction.rs +++ b/crates/agentic-server-core/src/executor/compaction.rs @@ -462,6 +462,7 @@ mod tests { fn custom_image_output(encoded_bytes: usize) -> InputItem { InputItem::CustomToolCallOutput(CustomToolCallOutputMessage { + id: None, call_id: "call_view_image".to_owned(), name: Some("view_image".to_owned()), output: ToolCallOutput::Content(vec![ToolOutputContent::InputImage(inline_image(encoded_bytes))]), @@ -1265,7 +1266,6 @@ mod tests { effective_tools: None, tool_search_loaded_tools: None, response_snapshot: None, - output_item_ids: None, effective_tool_choice: crate::ToolChoice::Auto, effective_instructions: None, }, diff --git a/crates/agentic-server-core/src/executor/error.rs b/crates/agentic-server-core/src/executor/error.rs index 5b91595de..dc00700a8 100644 --- a/crates/agentic-server-core/src/executor/error.rs +++ b/crates/agentic-server-core/src/executor/error.rs @@ -117,10 +117,13 @@ pub enum ExecutorError { /// The request's `max_tool_calls` is not an integer of at least one. #[error(transparent)] InvalidMaxToolCalls(#[from] MaxToolCallsError), - /// A recognized request value that cannot be used in this combination, reported with - /// `code: unsupported_value` and the offending request parameter. + /// A rejected request parameter value, with its public code and parameter name. #[error("{message}")] - UnsupportedValue { param: &'static str, message: &'static str }, + UnsupportedValue { + param: String, + code: Option<&'static str>, + message: String, + }, /// A recognized request member this gateway cannot honour (`background: true`, `prompt`). #[error(transparent)] UnsupportedParameter(#[from] UnsupportedParameterError), @@ -240,6 +243,9 @@ impl ExecutorError { | Self::JsonError(_) | Self::PayloadTooLarge(_) | Self::Storage(StorageError::ItemCursorNotFound { .. }) => "invalid_request_error", + Self::Storage(StorageError::NotFound { resource_type, .. }) if resource_type == "Response" => { + "invalid_request_error" + } Self::Storage(e) if e.is_not_found() => "not_found", Self::Storage(e) if e.is_validation() => "invalid_request_error", Self::Conflict(_) => "conflict_error", @@ -270,7 +276,7 @@ impl ExecutorError { Self::ResourceLimitExceeded { .. } => "response_resource_limit_exceeded", Self::InvalidPromptCacheOptions(PromptCacheOptionsError::Unsupported) => "unsupported_parameter", Self::InvalidMaxToolCalls(error) => error.code(), - Self::UnsupportedValue { .. } => "unsupported_value", + Self::UnsupportedValue { code, .. } => code.unwrap_or("unsupported_value"), Self::UnsupportedParameter(error) => error.code(), Self::Tool(ToolError::UnknownParameter { .. }) => "unknown_parameter", other => other.error_type(), @@ -282,7 +288,8 @@ impl ExecutorError { pub fn error_param(&self) -> Option<&str> { match self.client_visible_error() { Self::Storage(StorageError::InvalidItemId { param, .. }) - | Self::Tool(ToolError::UnknownParameter { param }) => Some(param), + | Self::Tool(ToolError::UnknownParameter { param }) + | Self::UnsupportedValue { param, .. } => Some(param), Self::Storage(StorageError::ItemAlreadyInConversation) => Some("items"), Self::Storage(StorageError::ItemCursorNotFound { .. }) => Some("after"), Self::ConversationLocked { .. } => Some("conversation"), @@ -290,7 +297,6 @@ impl ExecutorError { Self::Tool(ToolError::MissingOutput { .. } | ToolError::UnknownCallOutput { .. }) => Some("input"), Self::InvalidPromptCacheOptions(_) => Some("prompt_cache_options"), Self::InvalidMaxToolCalls(_) => Some(MAX_TOOL_CALLS_PARAM), - Self::UnsupportedValue { param, .. } => Some(param), Self::UnsupportedParameter(error) => Some(error.param()), _ => None, } @@ -317,11 +323,15 @@ impl ExecutorError { /// Builds the OpenAI-compatible error object shared by HTTP and SSE. pub(crate) fn response_error(&self) -> serde_json::Value { - let code = if matches!( - self.client_visible_error(), - Self::Tool(ToolError::MissingOutput { .. } | ToolError::UnknownCallOutput { .. }) - | Self::Storage(StorageError::ItemCursorNotFound { .. }) - ) { + let missing_response = matches!(self.client_visible_error(), + Self::Storage(StorageError::NotFound { resource_type, .. }) if resource_type == "Response"); + let code = if missing_response + || matches!( + self.client_visible_error(), + Self::Tool(ToolError::MissingOutput { .. } | ToolError::UnknownCallOutput { .. }) + | Self::Storage(StorageError::ItemCursorNotFound { .. }) + | Self::UnsupportedValue { code: None, .. } + ) { serde_json::Value::Null } else { serde_json::json!(self.error_code()) @@ -332,6 +342,8 @@ impl ExecutorError { error.insert("code".to_owned(), code); if let Some(param) = self.error_param() { error.insert("param".to_owned(), serde_json::json!(param)); + } else if missing_response { + error.insert("param".to_owned(), serde_json::Value::Null); } serde_json::Value::Object(error) } @@ -565,8 +577,9 @@ mod tests { #[test] fn unsupported_value_matches_openai_error_envelope() { let error = ExecutorError::UnsupportedValue { - param: "multi_agent", - message: "Async parallel tool calls are not yet supported in multi-agent mode.", + param: "multi_agent".into(), + code: Some("unsupported_value"), + message: "Async parallel tool calls are not yet supported in multi-agent mode.".into(), }; assert_eq!( envelope(error), diff --git a/crates/agentic-server-core/src/executor/modes/conversation.rs b/crates/agentic-server-core/src/executor/modes/conversation.rs index b759a9bf3..a897e1d47 100644 --- a/crates/agentic-server-core/src/executor/modes/conversation.rs +++ b/crates/agentic-server-core/src/executor/modes/conversation.rs @@ -3,9 +3,10 @@ use crate::storage::{ ConversationData, ConversationSnapshot, ConversationStore, ConversationVersion, ResponseMetadata, StorageError, }; -use crate::types::conversations::{ConversationMetadata, ItemPage, ItemResponse, ListItemsResponse}; +use crate::types::conversations::ConversationMetadata; use crate::types::io::InOutItem; use crate::types::io::OutputItem; +use crate::types::io::{ItemPage, ListItemsResponse}; use super::CommittedResponse; use crate::executor::error::{ExecutorError, ExecutorResult}; @@ -194,7 +195,7 @@ impl ConversationHandler { tenant_id: &str, conversation_id: &str, items: Vec, - ) -> ExecutorResult> { + ) -> ExecutorResult> { if items.is_empty() || items.len() > 20 { return Err(ExecutorError::InvalidRequest( "items must contain between 1 and 20 items".into(), @@ -211,7 +212,7 @@ impl ConversationHandler { let mut item_responses = Vec::with_capacity(created.len()); for db_item in created { let conversation_item = InOutItem::try_from(&db_item)?; - item_responses.push(ItemResponse::new(db_item.public_id().to_owned(), conversation_item)); + item_responses.push(conversation_item.into_public(db_item.public_id().to_owned())); } Ok(item_responses) @@ -240,7 +241,7 @@ impl ConversationHandler { let mut item_responses = Vec::with_capacity(items.len()); for db_item in items { let conversation_item = InOutItem::try_from(&db_item)?; - item_responses.push(ItemResponse::new(db_item.public_id().to_owned(), conversation_item)); + item_responses.push(conversation_item.into_public(db_item.public_id().to_owned())); } Ok(ListItemsResponse::new(item_responses, has_more)) @@ -255,7 +256,7 @@ impl ConversationHandler { tenant_id: &str, conversation_id: &str, item_id: &str, - ) -> ExecutorResult { + ) -> ExecutorResult { let db_item = self .store .retrieve_item(tenant_id, conversation_id, item_id) @@ -263,7 +264,7 @@ impl ConversationHandler { .map_err(ExecutorError::Storage)?; let conversation_item = InOutItem::try_from(&db_item)?; - Ok(ItemResponse::new(db_item.public_id().to_owned(), conversation_item)) + Ok(conversation_item.into_public(db_item.public_id().to_owned())) } /// Deletes an item by ID, returning the updated conversation. @@ -299,7 +300,6 @@ impl ConversationHandler { effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot: None, - output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/executor/modes/response.rs b/crates/agentic-server-core/src/executor/modes/response.rs index b7108343b..648e42b39 100644 --- a/crates/agentic-server-core/src/executor/modes/response.rs +++ b/crates/agentic-server-core/src/executor/modes/response.rs @@ -3,9 +3,9 @@ use std::collections::HashSet; use crate::storage::{ResponseData, ResponseMetadata, ResponseStore, StorageError}; -use crate::types::conversations::{ItemOrder, ItemPage, ItemResponse, ListItemsResponse}; -use crate::types::io::InOutItem; use crate::types::io::OutputItem; +use crate::types::io::{InOutItem, InputItem}; +use crate::types::io::{ItemOrder, ItemPage, ListItemsResponse}; use crate::types::request_response::ResponsePayload; use super::CommittedResponse; @@ -55,70 +55,76 @@ impl ResponseHandler { }) } - /// Deletes a response created with `store=true`. + /// Deletes a stored response. /// /// # Errors - /// Returns a storage error for missing IDs or unavailable storage. + /// Returns an error if the response is missing or storage is unavailable. pub async fn delete(&self, response_id: &str) -> ExecutorResult<()> { self.store.delete(response_id).await.map_err(ExecutorError::Storage) } - /// Lists the input items of a response created with `store=true`. - /// - /// Input items are the full context the response was generated from: items - /// inherited through `previous_response_id` or its conversation, including - /// earlier turns' outputs, followed by this turn's input. Only this response's - /// own output is excluded, identified by the output item IDs recorded when it - /// was stored (for older records, the IDs in its stored payload). - /// - /// Pagination follows conversation item listing: `after` is the public ID of - /// the last item on the previous page. + /// Resolve client references without overwriting the stored item with supplied content. + pub(crate) async fn resolve_input_items(&self, items: &mut [InOutItem]) -> ExecutorResult<()> { + let mut seen = HashSet::new(); + for (index, item) in items.iter_mut().enumerate() { + let Some(id) = item.id() else { continue }; + if !seen.insert(id.to_owned()) { + return Err(ExecutorError::UnsupportedValue { + param: "input".into(), + code: None, + message: format!( + "Duplicate item found with id {id}. Remove duplicate items from your input and try again." + ), + }); + } + // Validate message identities at admission, never while persisting upstream outputs. + if matches!( + item, + InOutItem::Input(InputItem::Message(_)) | InOutItem::Output(OutputItem::Message(_)) + ) { + item.validate_client_id(format!("input[{index}].id"))?; + } + if let Some(row) = self.store.get_input_item(id).await? { + *item = InOutItem::try_from(&row)?.into_public(row.public_id().to_owned()); + } + } + Ok(()) + } + + /// Lists stored context, excluding this response's own output. /// /// # Errors - /// Returns a storage error for a missing response, an unknown `after` cursor - /// or unavailable storage, and a conflict for older records without a - /// response snapshot, matching [`Self::retrieve`]. + /// Returns an error for invalid pagination, missing history, or a legacy response + /// without a retrievable snapshot. pub async fn list_input_items(&self, response_id: &str, page: ItemPage<'_>) -> ExecutorResult { - let stored = self.store.get(response_id).await?; - let snapshot = stored.metadata.response_snapshot.as_deref().ok_or_else(|| { - ExecutorError::Conflict( - "stored response has no retrievable input items; create a new response with store=true".into(), - ) - })?; - // Persistence records the row IDs of this response's own output, including rows - // for output kinds whose wire `id` is optional. Older records only have the - // snapshot's public IDs, matched against each row's public ID instead. - let recorded: Option> = stored - .metadata - .output_item_ids - .as_deref() - .map(|ids| ids.iter().map(String::as_str).collect()); - let snapshot_ids: HashSet<&str> = snapshot.output.iter().filter_map(OutputItem::id).collect(); - let rows = self.store.history_rows(&stored).await?; - - let mut items = Vec::with_capacity(rows.len()); - for row in &rows { - let item = InOutItem::try_from(row)?; - let is_own_output = matches!(item, InOutItem::Output(_)) - && match &recorded { - Some(ids) => ids.contains(row.id.as_str()), - None => snapshot_ids.contains(row.public_id()), - }; - if !is_own_output { - items.push(ItemResponse::new(row.public_id().to_owned(), item)); + let snapshot = self.retrieve(response_id).await?; + let rows = self.store.get_items(response_id).await?; + let mut items = rows + .into_iter() + .map(|row| { + let item = InOutItem::try_from(&row)?; + Ok(item.into_public(row.public_id().to_owned())) + }) + .collect::, StorageError>>()?; + // Both persistence paths append public outputs after the turn's inputs. + // Bound the suffix by this snapshot's output count so an inherited output + // remains input even when a turn supplies no new items. Session execution + // can already have recorded some outputs as canonical input history. + for _ in &snapshot.output { + if !items.last().is_some_and(|item| matches!(item, InOutItem::Output(_))) { + break; } + items.pop(); } if page.order == ItemOrder::Desc { items.reverse(); } - // A public ID can name several occurrences of an item in the history. Like - // conversation item listing, a cursor resumes after the furthest occurrence in - // this ordering, so paging always makes progress. - let start = match page.after { + let start = match page.after.filter(|cursor| !cursor.is_empty()) { Some(cursor) => { + // Match conversation pagination when a public ID occurs repeatedly. items .iter() - .rposition(|item| item.id == cursor) + .rposition(|item| item.id() == Some(cursor)) .ok_or_else(|| StorageError::ItemCursorNotFound { id: cursor.to_owned() })? + 1 } @@ -126,8 +132,10 @@ impl ResponseHandler { }; let limit = page.limit.get(); let has_more = items.len() - start > limit; - let data = items.into_iter().skip(start).take(limit).collect(); - Ok(ListItemsResponse::new(data, has_more)) + Ok(ListItemsResponse::new( + items.into_iter().skip(start).take(limit).collect(), + has_more, + )) } /// Retrieves the stored response for `previous_response_id`. @@ -143,7 +151,13 @@ impl ResponseHandler { .previous_response_id .as_deref() .ok_or_else(|| ExecutorError::InvalidRequest("previous_response_id is required for get".into()))?; - let mut stored = self.store.get(prev_id).await?; + let mut stored = self.store.get(prev_id).await.map_err(|error| { + if error.is_not_found() { + ExecutorError::PreviousResponseNotFound { id: prev_id.to_owned() } + } else { + ExecutorError::Storage(error) + } + })?; // Continuation checkpoints retain history and settings, not a duplicate payload. stored.metadata.response_snapshot = None; Ok(stored) @@ -192,7 +206,6 @@ impl ResponseHandler { effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot: None, - output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/executor/pending_calls.rs b/crates/agentic-server-core/src/executor/pending_calls.rs index 0db43de58..2ca65c7cb 100644 --- a/crates/agentic-server-core/src/executor/pending_calls.rs +++ b/crates/agentic-server-core/src/executor/pending_calls.rs @@ -344,6 +344,7 @@ mod tests { fn custom_tool_call_output(call_id: &str) -> InputItem { InputItem::CustomToolCallOutput(CustomToolCallOutputMessage { + id: None, call_id: call_id.to_owned(), name: None, output: ToolCallOutput::Text(String::new()), diff --git a/crates/agentic-server-core/src/executor/persist.rs b/crates/agentic-server-core/src/executor/persist.rs index 7f077b595..5387ed2ab 100644 --- a/crates/agentic-server-core/src/executor/persist.rs +++ b/crates/agentic-server-core/src/executor/persist.rs @@ -168,7 +168,6 @@ pub(crate) async fn persist_prepared_turn( effective_tools: ctx.enriched_request.tools.take(), tool_search_loaded_tools: None, response_snapshot, - output_item_ids: None, effective_tool_choice: ctx .original_request .tool_choice diff --git a/crates/agentic-server-core/src/executor/rehydrate.rs b/crates/agentic-server-core/src/executor/rehydrate.rs index 82302233b..7888b8a80 100644 --- a/crates/agentic-server-core/src/executor/rehydrate.rs +++ b/crates/agentic-server-core/src/executor/rehydrate.rs @@ -153,7 +153,8 @@ async fn rehydrate_request( let response_id = uuid7_str("resp_"); // Persistence keeps the public items. Tool lowering belongs to the enriched // inference copy, including when a later turn loads these items from storage. - let new_input_items = request.input.clone().into_canonical_items(); + let mut new_input_items = request.input.clone().into_canonical_items(); + exec_ctx.resp_handler.resolve_input_items(&mut new_input_items).await?; // One clone for the unmodified original; `request` is moved as enriched_request. let original_request = request.clone(); @@ -179,7 +180,7 @@ async fn rehydrate_request( } else if ctx.original_request.previous_response_id.is_some() { from_response(&mut ctx, exec_ctx).await?; } else { - ctx.enriched_request.input = ResponsesInput::from(Vec::from(&ctx.original_request.input)); + ctx.enriched_request.input = ResponsesInput::from(InOutItem::into_input_items(ctx.new_input_items.clone())); } exec_ctx @@ -213,8 +214,9 @@ fn validate_multi_agent_request(request: &RequestPayload) -> ExecutorResult<()> // `parallel_tool_calls` to false, so only an explicit `true` conflicts. if request.parallel_tool_calls == Some(true) && request.declares_async_tools() { return Err(ExecutorError::UnsupportedValue { - param: "multi_agent", - message: "Async parallel tool calls are not yet supported in multi-agent mode.", + param: "multi_agent".into(), + code: Some("unsupported_value"), + message: "Async parallel tool calls are not yet supported in multi-agent mode.".into(), }); } if request @@ -239,24 +241,15 @@ async fn from_response(ctx: &mut RequestContext, exec_ctx: &ExecutionContext) -> if ctx.continuation.is_some() { return from_session_response(ctx, exec_ctx).await; } - let previous_id = ctx.original_request.previous_response_id.clone().unwrap_or_default(); - let stored = exec_ctx - .resp_handler - .get(ctx) - .await - .map_err(|error| previous_response_missing(error, &previous_id))?; + let stored = exec_ctx.resp_handler.get(ctx).await?; if restore_agent_tree(ctx, &stored.metadata, exec_ctx.responses_config.max_retained_bytes)? { return Ok(()); } - let history = exec_ctx - .resp_handler - .rehydrate(ctx) - .await - .map_err(|error| previous_response_missing(error, &previous_id))?; + let history = exec_ctx.resp_handler.rehydrate(ctx).await?; let mut items = InOutItem::into_input_items(history); items.reserve(ctx.new_input_items.len()); - items.extend(Vec::from(&ctx.original_request.input)); + items.extend(InOutItem::into_input_items(ctx.new_input_items.clone())); if let Some(pending) = first_unanswered_sync_call(&items)? { return Err(ExecutorError::Tool(ToolError::MissingOutput { call_id: pending.call_id, @@ -317,7 +310,7 @@ async fn from_session_response(ctx: &mut RequestContext, exec_ctx: &ExecutionCon let mut items = parent.history.clone(); // Match durable rehydration: lower custom/shell call outputs only in the // inference copy. new_input_items retains their public wire types. - items.extend(Vec::from(&ctx.original_request.input)); + items.extend(InOutItem::into_input_items(ctx.new_input_items.clone())); let items = canonical_session_history(items); if let Some(pending) = first_unanswered_sync_call(&items)? { return Err(ExecutorError::Tool(ToolError::MissingOutput { @@ -371,7 +364,7 @@ async fn from_conversation(ctx: &mut RequestContext, exec_ctx: &ExecutionContext let mut items = InOutItem::into_input_items(snapshot.items); items.reserve(ctx.new_input_items.len()); - items.extend(Vec::from(&ctx.original_request.input)); + items.extend(InOutItem::into_input_items(ctx.new_input_items.clone())); if let Some(pending) = first_unanswered_sync_call(&items)? { return Err(ExecutorError::Tool(ToolError::MissingOutput { call_id: pending.call_id, @@ -773,9 +766,10 @@ mod tests { assert!(matches!( error, ExecutorError::UnsupportedValue { - param: "multi_agent", - message: "Async parallel tool calls are not yet supported in multi-agent mode." - } + param, + code, + message, + } if param == "multi_agent" && code == Some("unsupported_value") && message == "Async parallel tool calls are not yet supported in multi-agent mode." )); for (parallel, async_tool) in [ (serde_json::json!(false), true), diff --git a/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs b/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs index c84932d06..c4ec0cbf1 100644 --- a/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs +++ b/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs @@ -104,6 +104,7 @@ impl RetainedSize for ShellCallOutputMessage { impl RetainedSize for CustomToolCallOutputMessage { fn retained_bytes(&self) -> usize { RETAINED_CONTAINER_OVERHEAD_BYTES + + opt_len(self.id.as_ref()) + self.call_id.len() + opt_len(self.name.as_ref()) + self.output.retained_bytes() diff --git a/crates/agentic-server-core/src/storage/conversation.rs b/crates/agentic-server-core/src/storage/conversation.rs index 6e63ef4d3..3aa9524fe 100644 --- a/crates/agentic-server-core/src/storage/conversation.rs +++ b/crates/agentic-server-core/src/storage/conversation.rs @@ -10,8 +10,9 @@ use super::types::{ ConversationData, ConversationSnapshot, ConversationVersion, ResponseMetadata, StorageError, StoreResult, }; use crate::storage::{DbTransaction, Item}; -use crate::types::conversations::{ConversationMetadata, ItemOrder}; +use crate::types::conversations::ConversationMetadata; use crate::types::io::InOutItem; +use crate::types::io::ItemOrder; use crate::utils::common::{serialize_to_string, uuid7_str}; /// Conversation storage operations. @@ -205,7 +206,8 @@ impl ConversationStore { ) -> StoreResult<()> { let pool = self.pool()?; - let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; + let items_ = item::serialize_new_items(new_items)?; + let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; @@ -228,8 +230,7 @@ impl ConversationStore { }); } } - let written = item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; - let metadata_json = metadata.serialize_with_output_rows(&written)?; + item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; // A response branch must see the conversation as it existed at this turn, // including manually added items that may later be detached or deleted. let history_item_ids = item::ids_for_conversation_in_tx(&mut tx, conversation_id).await?; @@ -261,7 +262,10 @@ impl ConversationStore { metadata: Option, initial_items: Vec, ) -> StoreResult { - let items = item::serialize_new_items(initial_items, item::ItemSource::ConversationApi)?; + for (index, item) in initial_items.iter().enumerate() { + item.validate_client_id(format!("items[{index}].id"))?; + } + let items = item::serialize_new_items(initial_items)?; let metadata = metadata.map(|value| serialize_to_string(&value)).transpose()?; let id = uuid7_str("conv_"); let mut tx = self.pool()?.begin().await?; @@ -320,14 +324,13 @@ impl ConversationStore { /// # Errors /// Returns an error if the resource is missing or persistence fails. pub async fn create_items(&self, tenant_id: &str, id: &str, items: Vec) -> StoreResult> { - let items = item::serialize_new_items(items, item::ItemSource::ConversationApi)?; + for (index, item) in items.iter().enumerate() { + item.validate_client_id(format!("items[{index}].id"))?; + } + let items = item::serialize_new_items(items)?; let mut tx = self.pool()?.begin().await?; lock_owned(&mut tx, tenant_id, id).await?; - let rows: Vec = item::create_in_tx(&mut tx, items, Some(id)) - .await? - .into_iter() - .map(|written| written.row) - .collect(); + let rows = item::create_in_tx(&mut tx, items, Some(id)).await?; if !rows.is_empty() { conversation::bump_revision_in_tx(&mut tx, id).await?; } diff --git a/crates/agentic-server-core/src/storage/models/item.rs b/crates/agentic-server-core/src/storage/models/item.rs index 3f75ec0a4..d7e323131 100644 --- a/crates/agentic-server-core/src/storage/models/item.rs +++ b/crates/agentic-server-core/src/storage/models/item.rs @@ -1,21 +1,19 @@ //! Conversation history item stored in the database. use serde_json::Value; +use std::convert::TryFrom; use std::fmt::Write; use tracing::warn; use super::super::pool::{DbPool, DbResult, DbTransaction}; use super::super::types::item::{ItemKind, STORED_CODE_INTERPRETER_ORIGIN_KEY, STORED_ITEM_KIND_KEY}; use crate::storage::{StorageError, StoreResult}; -use crate::types::conversations::ItemOrder; use crate::types::io::InOutItem; +use crate::types::io::ItemOrder; use crate::types::io::code_interpreter::CodeInterpreterCallOrigin; use crate::types::io::{InputItem, OutputItem, ReasoningOutput}; use crate::utils::common::{deserialize_from_str_opt, utcnow_str, uuid7_str}; -pub use super::new_item::{ItemRole, NewItem, WrittenItem}; -pub(crate) use super::new_item::{ItemSource, serialize_new_items}; - const ITEM_COLUMN_COUNT: usize = 5; const SEQUENCE_COLUMN_INDEX: usize = 4; const MAX_BIND_PARAMETERS: usize = 999; @@ -166,6 +164,29 @@ fn item_values_clause(row_count: usize, first_bind_index: usize, sequence_from_c clause } +/// Serialize new history items, retaining public IDs already assigned to input or output items. +/// Items without IDs receive a storage-generated ID. +/// +/// # Errors +/// Returns an error if serialization fails or a supplied ID is empty. +pub(crate) fn serialize_new_items(items: Vec) -> StoreResult> { + items + .into_iter() + .map(|item| { + let (existing_id, prefix) = match &item { + InOutItem::Input(input) => (input.id(), input.id_prefix()), + InOutItem::Output(output) => (output.id(), output.id_prefix()), + }; + if existing_id == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = existing_id.map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok((id, data)) + }) + .collect() +} + /// Create items in a transaction with optional conversation context. /// /// If `conversation_id` is provided, the next sequence range is computed in the insert statement so @@ -179,14 +200,14 @@ fn item_values_clause(row_count: usize, first_bind_index: usize, sequence_from_c /// Returns an error if insertion fails or an item already belongs to the conversation. pub async fn create_in_tx( tx: &mut DbTransaction<'_>, - items: Vec, + items: Vec<(String, String)>, conversation_id: Option<&str>, -) -> StoreResult> { +) -> StoreResult> { if items.is_empty() { return Ok(Vec::new()); } - let mut created: Vec = Vec::with_capacity(items.len()); + let mut created: Vec = Vec::with_capacity(items.len()); for batch in items.chunks(MAX_ITEMS_PER_INSERT) { let mut rows = if let Some(conversation_id) = conversation_id { // Later SQL batches check history from before this call, excluding @@ -195,7 +216,7 @@ pub async fn create_in_tx( tx, batch, conversation_id, - created.iter().filter_map(|item| item.row.seq).min(), + created.iter().filter_map(|item| item.seq).min(), ) .await? } else { @@ -204,17 +225,15 @@ pub async fn create_in_tx( if conversation_id.is_some() { rows.sort_by_key(|row| row.seq); } - // Both inserts return exactly one row per item, in input order. - created.extend( - rows.into_iter() - .zip(batch) - .map(|(row, item)| WrittenItem { row, role: item.role }), - ); + created.append(&mut rows); } Ok(created) } -async fn create_in_tx_without_conversation(tx: &mut DbTransaction<'_>, items: &[NewItem]) -> DbResult> { +async fn create_in_tx_without_conversation( + tx: &mut DbTransaction<'_>, + items: &[(String, String)], +) -> DbResult> { // Each occurrence gets a fresh row ID; the supplied ID is public and may repeat. let now = utcnow_str(); let row_ids = items.iter().map(|_| uuid7_str("item_")).collect::>(); @@ -229,8 +248,8 @@ async fn create_in_tx_without_conversation(tx: &mut DbTransaction<'_>, items: &[ ); let mut query = sqlx::query_as::<_, Item>(&sql).bind(now); - for (item, row_id) in items.iter().zip(&row_ids) { - query = query.bind(row_id).bind(&item.id).bind(&item.data); + for ((public_id, data), row_id) in items.iter().zip(&row_ids) { + query = query.bind(row_id).bind(public_id).bind(data); } let mut rows = query.fetch_all(&mut **tx).await?; @@ -241,7 +260,7 @@ async fn create_in_tx_without_conversation(tx: &mut DbTransaction<'_>, items: &[ async fn create_in_tx_with_next_conversation_seq( tx: &mut DbTransaction<'_>, - items: &[NewItem], + items: &[(String, String)], conversation_id: &str, first_inserted_sequence: Option, ) -> StoreResult> { @@ -287,10 +306,10 @@ async fn create_in_tx_with_next_conversation_seq( .bind(conversation_id) .bind(first_inserted_sequence); #[allow(clippy::cast_possible_wrap)] - for (idx, item) in items.iter().enumerate() { + for (idx, (id, data)) in items.iter().enumerate() { query = query - .bind(&item.id) - .bind(&item.data) + .bind(id) + .bind(data) .bind(now) .bind(conversation_id) .bind(idx as i64) @@ -328,6 +347,19 @@ pub async fn get_items(pool: &DbPool, ids: &[String]) -> DbResult> { Ok(rows) } +/// Find the original stored item carrying a public identity. +/// +/// # Errors +/// Returns an error if the database query fails. +pub async fn get_by_public_id(pool: &DbPool, id: &str) -> DbResult> { + sqlx::query_as::<_, Item>( + "SELECT * FROM items WHERE COALESCE(public_id, reference_id, id) = $1 ORDER BY created_at, id LIMIT 1", + ) + .bind(id) + .fetch_optional(pool) + .await +} + /// Get items by conversation ID ordered by sequence. /// /// # Errors @@ -471,14 +503,6 @@ mod tests { InputItem, OutputItem, OutputMessage, ReasoningOutput, ReasoningTextContent, ResponsesInput, }; - fn history_item(id: &str) -> NewItem { - NewItem { - id: id.to_owned(), - data: "{}".to_owned(), - role: ItemRole::Input, - } - } - #[test] fn new_items_reuse_public_ids_and_generate_storage_ids() { let input: InputItem = serde_json::from_value(serde_json::json!({ @@ -495,20 +519,17 @@ mod tests { "name": "lookup", "arguments": "{}" })) .unwrap(); - let rows = serialize_new_items( - vec![ - InOutItem::Input(input), - InOutItem::Output(output), - InOutItem::Input(without_id), - InOutItem::Input(function_call), - ], - ItemSource::ConversationApi, - ) + let rows = serialize_new_items(vec![ + InOutItem::Input(input), + InOutItem::Output(output), + InOutItem::Input(without_id), + InOutItem::Input(function_call), + ]) .unwrap(); - assert_eq!(rows[0].id, "msg_supplied"); - assert_eq!(rows[1].id, "msg_generated"); - assert!(rows[2].id.starts_with("msg_")); - assert_eq!(rows[3].id, "fc_supplied"); + assert_eq!(rows[0].0, "msg_supplied"); + assert_eq!(rows[1].0, "msg_generated"); + assert!(rows[2].0.starts_with("msg_")); + assert_eq!(rows[3].0, "fc_supplied"); } #[test] @@ -522,11 +543,11 @@ mod tests { outputs: Some(vec![]), origin: CodeInterpreterCallOrigin::default(), }); - let rows = serialize_new_items(vec![InOutItem::Output(output)], ItemSource::ResponseHistory) - .expect("serialize code interpreter response history"); + let rows = + serialize_new_items(vec![InOutItem::Output(output)]).expect("serialize code interpreter response history"); - assert_eq!(rows[0].id, "ci_public"); - let data: Value = serde_json::from_str(&rows[0].data).expect("stored output JSON"); + assert_eq!(rows[0].0, "ci_public"); + let data: Value = serde_json::from_str(&rows[0].1).expect("stored output JSON"); assert_eq!(data["id"], "ci_public"); assert_eq!(data["type"], "code_interpreter_call"); } @@ -565,24 +586,21 @@ mod tests { call.container_id = "cntr_gateway".to_owned(); call.origin = CodeInterpreterCallOrigin::Gateway; - let stored = serialize_new_items( - vec![ - InOutItem::Input(gateway_call), - InOutItem::Input(gateway_result), - InOutItem::Output(gateway_projection), - InOutItem::Output(native_call), - ], - ItemSource::ResponseHistory, - ) + let stored = serialize_new_items(vec![ + InOutItem::Input(gateway_call), + InOutItem::Input(gateway_result), + InOutItem::Output(gateway_projection), + InOutItem::Output(native_call), + ]) .expect("serialize response history"); - let gateway_data: Value = serde_json::from_str(&stored[2].data).expect("stored gateway call"); - let native_data: Value = serde_json::from_str(&stored[3].data).expect("stored native call"); + let gateway_data: Value = serde_json::from_str(&stored[2].1).expect("stored gateway call"); + let native_data: Value = serde_json::from_str(&stored[3].1).expect("stored native call"); assert_eq!(gateway_data[STORED_CODE_INTERPRETER_ORIGIN_KEY], "gateway"); assert!(native_data.get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); let history = stored .into_iter() - .map(|NewItem { id, data, .. }| { + .map(|(id, data)| { Item { id, data, @@ -617,11 +635,11 @@ mod tests { "type": "message", "id": "", "role": "user", "content": "test" })) .unwrap(); - let result = serialize_new_items(vec![InOutItem::Input(input)], ItemSource::ConversationApi); + let result = serialize_new_items(vec![InOutItem::Input(input)]); assert!(result.is_err()); let err = result.unwrap_err(); assert!(err.is_validation()); - assert!(matches!(err, StorageError::InvalidItemId { param, .. } if param == "items[0].id")); + assert!(matches!(err, StorageError::Validation(_))); } #[test] @@ -630,11 +648,11 @@ mod tests { "type": "message", "role": "user", "content": "test" })) .unwrap(); - let result = serialize_new_items(vec![InOutItem::Input(input)], ItemSource::ConversationApi); + let result = serialize_new_items(vec![InOutItem::Input(input)]); assert!(result.is_ok()); let rows = result.unwrap(); assert_eq!(rows.len(), 1); - assert!(rows[0].id.starts_with("msg_")); + assert!(rows[0].0.starts_with("msg_")); } #[test] @@ -660,16 +678,13 @@ mod tests { .await .expect("create in-memory database"); let items = (0..=MAX_BIND_PARAMETERS) - .map(|index| history_item(&format!("item_{index}"))) + .map(|index| (format!("item_{index}"), "{}".to_owned())) .collect::>(); - let public_ids = items.iter().map(|item| item.id.clone()).collect::>(); + let public_ids = items.iter().map(|(id, _)| id.clone()).collect::>(); let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, None) .await - .expect("insert item batches") - .into_iter() - .map(|written| written.row) - .collect::>(); + .expect("insert item batches"); transaction.commit().await.expect("commit item batches"); let row_ids = created.iter().map(|item| item.id.clone()).collect::>(); let loaded = get_items(&pool, &row_ids).await.expect("load item batches"); @@ -688,14 +703,11 @@ mod tests { let pool = crate::storage::create_pool_with_schema(Some("sqlite://?mode=memory")) .await .expect("create in-memory database"); - let items = vec![history_item("item_repeated"), history_item("item_repeated")]; + let items = vec![("item_repeated".to_owned(), "{}".to_owned()); 2]; let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, None) .await - .expect("insert repeated public ids") - .into_iter() - .map(|written| written.row) - .collect::>(); + .expect("insert repeated public ids"); transaction.commit().await.expect("commit history rows"); assert_eq!(created.len(), 2); @@ -717,15 +729,12 @@ mod tests { .expect("create conversation"); let item_count = MAX_ITEMS_PER_INSERT + 1; let items = (0..item_count) - .map(|index| history_item(&format!("conversation_item_{index}"))) + .map(|index| (format!("conversation_item_{index}"), "{}".to_owned())) .collect::>(); let mut transaction = pool.begin().await.expect("begin transaction"); let created = create_in_tx(&mut transaction, items, Some(conversation_id)) .await - .expect("insert conversation item batches") - .into_iter() - .map(|written| written.row) - .collect::>(); + .expect("insert conversation item batches"); transaction.commit().await.expect("commit item batches"); let stored = get_items_by_conversation(&pool, conversation_id) .await @@ -1058,10 +1067,11 @@ mod item_id_tests { ]; for mut value in cases { let item = stored(value.clone()); - serialize_new_items(vec![item], ItemSource::ConversationApi).expect("matching public ID prefix"); + item.validate_client_id("items[0].id".into()) + .expect("matching public ID prefix"); value["id"] = json!("wrong_1"); let item = stored(value); - let error = serialize_new_items(vec![item], ItemSource::ConversationApi).unwrap_err(); + let error = item.validate_client_id("items[0].id".into()).unwrap_err(); assert!(error.is_validation()); assert!(matches!(error, StorageError::InvalidItemId { param, .. } if param == "items[0].id")); } @@ -1075,18 +1085,22 @@ mod item_id_tests { json!({"type":"function_call_output", "call_id":"call_1", "output":"done"}), json!({"type":"shell_call_output", "id":"output_1", "call_id":"call_1", "output":[]}), ] { - serialize_new_items(vec![stored(value)], ItemSource::ConversationApi).unwrap(); + stored(value).validate_client_id("items[0].id".into()).unwrap(); } } #[test] fn invalid_id_reports_its_batch_index_and_rejects_empty_ids() { for id in ["", "item_wrongprefix"] { - let items = vec![ + let items = [ stored(json!({"type":"message", "role":"user", "content":"valid"})), stored(json!({"type":"message", "id":id, "role":"user", "content":"invalid"})), ]; - let error = serialize_new_items(items, ItemSource::ConversationApi).unwrap_err(); + let error = items + .iter() + .enumerate() + .try_for_each(|(index, item)| item.validate_client_id(format!("items[{index}].id"))) + .unwrap_err(); assert!(matches!(&error, StorageError::InvalidItemId { param, .. } if param == "items[1].id")); assert_eq!( error.to_string(), diff --git a/crates/agentic-server-core/src/storage/models/mod.rs b/crates/agentic-server-core/src/storage/models/mod.rs index 80f19e026..e0f450859 100644 --- a/crates/agentic-server-core/src/storage/models/mod.rs +++ b/crates/agentic-server-core/src/storage/models/mod.rs @@ -2,7 +2,6 @@ pub mod conversation; pub mod item; -mod new_item; pub mod response; pub use conversation::Conversation; diff --git a/crates/agentic-server-core/src/storage/models/new_item.rs b/crates/agentic-server-core/src/storage/models/new_item.rs deleted file mode 100644 index a6c4abc6e..000000000 --- a/crates/agentic-server-core/src/storage/models/new_item.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Items prepared for insertion, and the rows written for them. - -use super::item::Item; -use crate::storage::{StorageError, StoreResult}; -use crate::types::io::InOutItem; -use crate::utils::common::uuid7_str; - -/// Distinguishes client-supplied conversation items from persisted model history. -#[derive(Clone, Copy, PartialEq, Eq)] -pub(crate) enum ItemSource { - ConversationApi, - ResponseHistory, -} - -/// Whether an item was supplied as input or produced as model output. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ItemRole { - Input, - Output, -} - -/// An item serialized for insertion. -#[derive(Debug)] -pub struct NewItem { - /// Public item ID: the supplied one, or one generated for the item's kind. - pub id: String, - pub data: String, - pub role: ItemRole, -} - -/// The row written for a [`NewItem`], keeping that item's role. -/// -/// The row ID identifies this stored occurrence; the public ID it carries may be -/// shared by other occurrences of the same item. -#[derive(Debug)] -pub struct WrittenItem { - pub row: Item, - pub role: ItemRole, -} - -impl WrittenItem { - /// Row IDs of the written output occurrences, in write order. - pub(crate) fn output_row_ids(written: &[Self]) -> Vec { - written - .iter() - .filter(|item| item.role == ItemRole::Output) - .map(|item| item.row.id.clone()) - .collect() - } -} - -/// Serialize new history items, retaining public IDs already assigned to input or output items. -/// Items without IDs receive a storage-generated ID. -/// -/// # Errors -/// Returns an error if serialization fails or a supplied ID is empty or has an invalid prefix. -pub(crate) fn serialize_new_items(items: Vec, source: ItemSource) -> StoreResult> { - items - .into_iter() - .enumerate() - .map(|(index, item)| { - let (existing_id, prefix, role) = match &item { - InOutItem::Input(input) => (input.id(), input.id_prefix(), ItemRole::Input), - InOutItem::Output(output) => (output.id(), output.id_prefix(), ItemRole::Output), - }; - // Upstream model IDs are preserved; some vLLM reasoning items use - // msg_ IDs. Prefix errors apply to supplied Conversation Items IDs. - if source == ItemSource::ConversationApi - && let (Some(id), Some(prefix)) = (existing_id, prefix) - && !id.starts_with(prefix) - { - return Err(StorageError::InvalidItemId { - param: format!("items[{index}].id"), - id: id.to_owned(), - prefix: prefix.trim_end_matches('_'), - }); - } - if existing_id == Some("") { - return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); - } - let id = existing_id.map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); - let data = String::try_from(&item)?; - Ok(NewItem { id, data, role }) - }) - .collect() -} diff --git a/crates/agentic-server-core/src/storage/models/response.rs b/crates/agentic-server-core/src/storage/models/response.rs index 185894711..d358f6ff6 100644 --- a/crates/agentic-server-core/src/storage/models/response.rs +++ b/crates/agentic-server-core/src/storage/models/response.rs @@ -1,7 +1,9 @@ //! LLM API response stored in the database. +use std::collections::HashSet; + use super::super::pool::{DbPool, DbResult, DbTransaction}; -use crate::utils::common::utcnow_str; +use crate::utils::common::{serialize_to_string, utcnow_str}; /// LLM API response stored in the database. /// @@ -67,13 +69,10 @@ pub async fn get(pool: &DbPool, id: &str) -> DbResult> { .await } -/// History items checked per query when looking for references that keep them alive. -const REFERENCE_CHECK_BATCH: usize = 64; - -/// Read a response row inside a transaction. +/// Read a response row inside the caller's transaction. /// /// # Errors -/// Returns `DbResult::Err` if the query fails. +/// Returns an error if the database query fails. pub async fn get_in_tx(tx: &mut DbTransaction<'_>, id: &str) -> DbResult> { sqlx::query_as::<_, Response>("SELECT * FROM responses WHERE id = $1") .bind(id) @@ -81,102 +80,61 @@ pub async fn get_in_tx(tx: &mut DbTransaction<'_>, id: &str) -> DbResult, id: &str) -> DbResult<()> { +/// Returns an error if history is malformed or the transaction fails. +pub async fn delete_in_tx(tx: &mut DbTransaction<'_>, id: &str) -> DbResult { + let Some(root) = get_in_tx(tx, id).await? else { + return Ok(false); + }; + let inherited: HashSet = root + .history_item_ids_vec() + .map_err(|error| sqlx::Error::Decode(Box::new(error)))? + .into_iter() + .collect(); + let mut parents = vec![root.id]; + while let Some(parent) = parents.pop() { + let children = + sqlx::query_as::<_, Response>("SELECT * FROM responses WHERE previous_response_id = $1 ORDER BY id") + .bind(parent) + .fetch_all(&mut **tx) + .await?; + for child in children { + if child.conversation_id.is_none() { + let mut ids = child + .history_item_ids_vec() + .map_err(|error| sqlx::Error::Decode(Box::new(error)))?; + ids.retain(|id| !inherited.contains(id)); + let json = serialize_to_string(&ids).map_err(|error| sqlx::Error::Encode(Box::new(error)))?; + sqlx::query("UPDATE responses SET history_item_ids = $1 WHERE id = $2") + .bind(json) + .bind(&child.id) + .execute(&mut **tx) + .await?; + } + parents.push(child.id); + } + } sqlx::query("DELETE FROM responses WHERE id = $1") .bind(id) .execute(&mut **tx) .await?; - Ok(()) + Ok(true) } -/// Point a conversation that named `deleted_id` as its latest response at its newest remaining response. -/// -/// The latest response carries the conversation's state (effective settings, tools loaded by -/// tool search, the multi-agent tree), so the conversation keeps the closest state it still has. -/// -/// # Errors -/// Returns `DbResult::Err` if a database statement fails. -pub async fn repoint_latest_in_tx(tx: &mut DbTransaction<'_>, conversation_id: &str, deleted_id: &str) -> DbResult<()> { - sqlx::query( - "UPDATE conversations SET latest_response_id = ( \ - SELECT id FROM responses WHERE conversation_id = $1 AND id <> $2 \ - ORDER BY created_at DESC, id DESC LIMIT 1 \ - ) WHERE id = $1 AND latest_response_id = $2", - ) - .bind(conversation_id) - .bind(deleted_id) - .execute(&mut **tx) - .await?; - Ok(()) -} - -/// Delete the history rows of a deleted response that nothing else keeps. -/// -/// Each batch scans the `responses` table once; deletion is rare enough to pay that. -/// -/// A row stays when it belongs to a conversation, when a conversation item references it, -/// or when another response's history lists it (descendants and siblings copy inherited -/// history). Call after the response row itself is deleted. Returns the number of rows removed. +/// Read the ordered history item IDs without loading response metadata. +/// A legacy NULL history is empty; a missing response returns `None`. /// /// # Errors -/// Returns `DbResult::Err` if a database statement fails. -pub async fn delete_unreferenced_items_in_tx(tx: &mut DbTransaction<'_>, history_item_ids: &[String]) -> DbResult { - let mut removed = 0; - for batch in history_item_ids.chunks(REFERENCE_CHECK_BATCH) { - let placeholders = (1..=batch.len()) - .map(|index| format!("${index}")) - .collect::>() - .join(", "); - let detached = format!( - "SELECT id FROM items WHERE id IN ({placeholders}) AND conversation_id IS NULL \ - AND NOT EXISTS (SELECT 1 FROM items referencing WHERE referencing.reference_id = items.id)" - ); - let mut query = sqlx::query_scalar::<_, String>(&detached); - for id in batch { - query = query.bind(id); - } - let mut candidates: Vec = query.fetch_all(&mut **tx).await?; - if candidates.is_empty() { - continue; - } - - // `LIKE` narrows the scan; parsing confirms an exact entry (`_` in IDs is a wildcard). - let patterns = (1..=candidates.len()) - .map(|index| format!("history_item_ids LIKE ${index}")) - .collect::>() - .join(" OR "); - let sql = format!("SELECT history_item_ids FROM responses WHERE {patterns}"); - let mut query = sqlx::query_scalar::<_, Option>(&sql); - for id in &candidates { - query = query.bind(format!("%\"{id}\"%")); - } - for history in query.fetch_all(&mut **tx).await?.into_iter().flatten() { - let listed: Vec = serde_json::from_str(&history).unwrap_or_default(); - candidates.retain(|id| !listed.contains(id)); - } - if candidates.is_empty() { - continue; - } - - let placeholders = (1..=candidates.len()) - .map(|index| format!("${index}")) - .collect::>() - .join(", "); - let delete = format!("DELETE FROM items WHERE id IN ({placeholders})"); - let mut query = sqlx::query(&delete); - for id in &candidates { - query = query.bind(id); - } - removed += query.execute(&mut **tx).await?.rows_affected(); - } - Ok(removed) +/// Returns an error if the database query fails. +pub async fn get_item_ids(pool: &DbPool, id: &str) -> DbResult> { + sqlx::query_scalar("SELECT COALESCE(history_item_ids, '[]') FROM responses WHERE id = $1") + .bind(id) + .fetch_optional(pool) + .await } /// Get the response that committed a conversation turn. diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index 242b22d40..1eedba2f7 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -4,11 +4,11 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::sync::Arc; -use super::models::{conversation, item, response}; +use super::models::{item, response}; use super::pool::DbPool; use super::types::{ResponseData, ResponseMetadata, StorageError, StoreResult}; use crate::types::io::InOutItem; -use crate::utils::common::serialize_to_string; +use crate::utils::common::{deserialize_from_str, serialize_to_string}; /// Response storage operations. #[derive(Clone, Debug)] @@ -58,85 +58,83 @@ impl ResponseStore { row.try_into() } - /// Deletes a stored response and the history rows nothing else keeps. - /// - /// Descendants created with `previous_response_id` stay readable because each - /// one stores its own complete history item list; their reference to the - /// deleted response is cleared by the foreign key, and the rows they list are kept. - /// Rows that belong to a conversation stay with it. When the response was its - /// conversation's latest, the conversation moves to its newest remaining response. - /// - /// Deletion does not reach WebSocket sessions. A session that already caches this - /// response as its latest checkpoint keeps continuing from it without a storage - /// read; those sessions live per connection, possibly on another replica, and a - /// storage check on that path would reorder automatic steering successors. + /// Deletes a response without deleting shared history items. /// /// # Errors - /// - /// Returns [`StorageError::NotFound`] if no response has this ID, or an error - /// if the database statement fails or the store is disabled. + /// Returns an error if the response is missing, storage is disabled, or deletion fails. pub async fn delete(&self, response_id: &str) -> StoreResult<()> { let mut tx = self.pool()?.begin().await?; - let Some(row) = response::get_in_tx(&mut tx, response_id).await? else { + if !response::delete_in_tx(&mut tx, response_id).await? { return Err(StorageError::not_found("Response", response_id)); - }; - // Same lock order as conversation persistence: the conversation, then its responses. - if let Some(conversation_id) = row.conversation_id.as_deref() { - match conversation::lock_in_tx(&mut tx, conversation_id).await { - Ok(_) | Err(sqlx::Error::RowNotFound) => {} - Err(error) => return Err(error.into()), - } } - let history_item_ids = row.history_item_ids_vec().map_err(StorageError::Serialization)?; - // Before the delete: its foreign key would clear the pointer this matches on. - if let Some(conversation_id) = row.conversation_id.as_deref() { - response::repoint_latest_in_tx(&mut tx, conversation_id, response_id).await?; - } - response::delete_in_tx(&mut tx, response_id).await?; - response::delete_unreferenced_items_in_tx(&mut tx, &history_item_ids).await?; tx.commit().await?; Ok(()) } - /// Rehydrates a response with full history. - /// - /// Fetches all history items referenced by a response. + /// Resolve a supplied public item ID when storage is configured. /// /// # Errors + /// Returns an error if the database query fails. + pub async fn get_input_item(&self, id: &str) -> StoreResult> { + let Some(pool) = self.pool.as_deref() else { + return Ok(None); + }; + Ok(item::get_by_public_id(pool, id).await?) + } + + /// Loads the response's existing item rows in history order. /// - /// Returns an error if a history item is missing or invalid, the database query - /// fails, or the store is disabled. - pub async fn rehydrate(&self, response_id: &str) -> StoreResult> { - let response = self.get(response_id).await?; - self.history_rows(&response) + /// # Errors + /// Returns an error for missing or invalid history, unavailable storage, or query failure. + pub async fn get_items(&self, response_id: &str) -> StoreResult> { + let pool = self.pool()?; + let json = response::get_item_ids(pool, response_id) .await? - .iter() - .map(InOutItem::try_from) + .ok_or_else(|| StorageError::not_found("Response", response_id))?; + let ids: Vec = deserialize_from_str(&json).map_err(|_| StorageError::InvalidResponseHistory { + response_id: response_id.to_owned(), + })?; + let mut rows: HashMap<_, _> = item::get_items(pool, &ids) + .await? + .into_iter() + .map(|row| (row.id.clone(), row)) + .collect(); + ids.into_iter() + .map(|id| rows.remove(&id).ok_or(StorageError::InvalidHistoryItem { item_id: id })) .collect() } - /// Loads the stored rows of a response's history, oldest first. + /// Rehydrates a response with full history. /// - /// The history is the full context recorded when the response was stored: - /// items inherited through `previous_response_id` or its conversation, this - /// turn's input, and this turn's output. + /// Fetches all history items referenced by a response. /// /// # Errors /// - /// Returns an error if a history item is missing, the database query fails, - /// or the store is disabled. - pub(crate) async fn history_rows(&self, response: &ResponseData) -> StoreResult> { - let rows = item::get_items(self.pool()?, &response.history_item_ids).await?; - let mut rows_by_id: HashMap = rows.into_iter().map(|row| (row.id.clone(), row)).collect(); - response + /// Returns an error if a history item is missing or invalid, the database query + /// fails, or the store is disabled. + pub async fn rehydrate(&self, response_id: &str) -> StoreResult> { + let pool = self.pool()?; + let response = self.get(response_id).await?; + let rows = item::get_items(pool, &response.history_item_ids).await?; + let mut items_by_id: HashMap = rows + .into_iter() + .map(|row| { + let item = InOutItem::try_from(&row)?; + Ok((row.id, item)) + }) + .collect::>()?; + + let ordered_items = response .history_item_ids .iter() .map(|id| { - rows_by_id + items_by_id .remove(id) .ok_or_else(|| StorageError::InvalidHistoryItem { item_id: id.clone() }) }) - .collect() + .collect::>()?; + + Ok(ordered_items) } /// Persists a response with its items and metadata. @@ -176,14 +174,14 @@ impl ResponseStore { Some(prev_id) => self.get(prev_id).await?.history_item_ids, None => Vec::new(), }; - let items_ = item::serialize_new_items(new_items, item::ItemSource::ResponseHistory)?; + let items_ = item::serialize_new_items(new_items)?; + let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; // History rows get their own row IDs, so repeated public IDs keep every position. - let written = item::create_in_tx(&mut tx, items_, None).await?; - let metadata_json = metadata.serialize_with_output_rows(&written)?; - item_ids.extend(written.into_iter().map(|item| item.row.id)); + let created = item::create_in_tx(&mut tx, items_, None).await?; + item_ids.extend(created.into_iter().map(|item| item.id)); let history_item_ids_json = serialize_to_string(&item_ids)?; response::create_in_tx( diff --git a/crates/agentic-server-core/src/storage/schema/readiness.rs b/crates/agentic-server-core/src/storage/schema/readiness.rs index b41a301ad..18aff5101 100644 --- a/crates/agentic-server-core/src/storage/schema/readiness.rs +++ b/crates/agentic-server-core/src/storage/schema/readiness.rs @@ -18,11 +18,7 @@ pub(crate) async fn verify_persistence_writable(pool: &DbPool) -> DbResult<()> { crate::storage::models::conversation::lock_in_tx(&mut transaction, &conversation_id).await?; crate::storage::models::item::create_in_tx( &mut transaction, - vec![crate::storage::models::item::NewItem { - id: item_id.clone(), - data: "{}".to_owned(), - role: crate::storage::models::item::ItemRole::Input, - }], + vec![(item_id.clone(), "{}".to_owned())], Some(&conversation_id), ) .await diff --git a/crates/agentic-server-core/src/storage/types/item.rs b/crates/agentic-server-core/src/storage/types/item.rs index cb9af84a7..a830b43dd 100644 --- a/crates/agentic-server-core/src/storage/types/item.rs +++ b/crates/agentic-server-core/src/storage/types/item.rs @@ -38,6 +38,27 @@ impl ItemKind { } } +impl InOutItem { + /// Validate a client-supplied public identity before item creation. + /// Persistence itself also accepts provider-assigned IDs with different prefixes. + pub(crate) fn validate_client_id(&self, param: String) -> Result<(), StorageError> { + let (id, prefix) = match self { + Self::Input(item) => (item.id(), item.id_prefix()), + Self::Output(item) => (item.id(), item.id_prefix()), + }; + if let (Some(id), Some(prefix)) = (id, prefix) + && !id.starts_with(prefix) + { + return Err(StorageError::InvalidItemId { + param, + id: id.to_owned(), + prefix: prefix.trim_end_matches('_'), + }); + } + Ok(()) + } +} + /// Decode retained history without silently omitting invalid records. /// /// Legacy unmarked items retain the existing input/output discrimination policy. diff --git a/crates/agentic-server-core/src/storage/types/response.rs b/crates/agentic-server-core/src/storage/types/response.rs index 1fcdba5a4..816645045 100644 --- a/crates/agentic-server-core/src/storage/types/response.rs +++ b/crates/agentic-server-core/src/storage/types/response.rs @@ -6,7 +6,6 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use super::super::models::Response as StorageDbResponse; -use super::super::models::item::WrittenItem; use super::errors::StorageError; use crate::types::agent_tree::StoredTreeSnapshot; use crate::types::io::ToolChoice; @@ -23,13 +22,6 @@ pub struct ResponseMetadata { /// Exact terminal Responses payload, absent for legacy and non-Responses records. #[serde(default, skip_serializing_if = "Option::is_none")] pub response_snapshot: Option>, - /// Item row IDs of this response's own output, in history order. - /// - /// Recorded by persistence from the rows it actually writes, so it includes IDs - /// generated for output kinds whose wire `id` is optional. Absent for records - /// written before it existed; readers then fall back to the snapshot's output IDs. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub output_item_ids: Option>, pub model: String, pub previous_response_id: Option, pub effective_tools: Option>, @@ -118,36 +110,11 @@ pub(in crate::storage) fn decode_metadata( }) } -impl ResponseMetadata { - /// Encode the metadata of a response whose items were just written, recording - /// the row IDs of its own output occurrences. - /// - /// # Errors - /// Returns [`StorageError::Serialization`] if the metadata cannot be encoded. - pub(in crate::storage) fn serialize_with_output_rows( - &self, - written: &[WrittenItem], - ) -> Result { - String::try_from(Self { - output_item_ids: Some(WrittenItem::output_row_ids(written)), - ..self.clone() - }) - } -} - impl TryFrom<&ResponseMetadata> for String { type Error = StorageError; fn try_from(metadata: &ResponseMetadata) -> Result { - Self::try_from(metadata.clone()) - } -} - -/// Sanitizes stored tool credentials in place, then encodes the metadata. -impl TryFrom for String { - type Error = StorageError; - - fn try_from(mut persisted: ResponseMetadata) -> Result { + let mut persisted = metadata.clone(); if let Some(tools) = persisted.effective_tools.as_mut() { for tool in tools { tool.sanitize_for_persistence(); @@ -255,7 +222,6 @@ mod tests { effective_tools: None, tool_search_loaded_tools: None, response_snapshot: None, - output_item_ids: None, effective_tool_choice: ToolChoice::Auto, effective_instructions: Some("be helpful".to_string()), }; diff --git a/crates/agentic-server-core/src/types/conversations.rs b/crates/agentic-server-core/src/types/conversations.rs index 9b0200da0..d320d7fff 100644 --- a/crates/agentic-server-core/src/types/conversations.rs +++ b/crates/agentic-server-core/src/types/conversations.rs @@ -1,16 +1,10 @@ //! Types for the OpenAI-compatible Conversations API. use std::collections::BTreeMap; -use std::str::FromStr; -use serde::de::IntoDeserializer as _; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; -use super::event::MessageStatus; -use super::io::{InputContent, InputMessageContent, InputTextContent}; -use serde_json::Value; - -use super::io::{InOutItem, InputItem}; +use super::io::InOutItem; /// String metadata attached to a conversation. pub type ConversationMetadata = BTreeMap; @@ -62,192 +56,6 @@ pub struct CreateItemRequest { pub items: Vec, } -/// Response for listing items with pagination. -#[derive(Debug, Clone, Serialize)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct ListItemsResponse { - /// Object type, always "list". - pub object: String, - - /// Array of conversation items. - pub data: Vec, - - /// Whether there are more items available. - pub has_more: bool, - - /// ID of the first item in this page. - pub first_id: Option, - - /// ID of the last item in this page. - pub last_id: Option, -} - -/// A conversation item with its storage identity and flattened public payload. -#[derive(Debug, Clone)] -pub struct ItemResponse { - pub id: String, - pub item: InOutItem, -} - -#[cfg(feature = "openapi")] -impl utoipa::PartialSchema for ItemResponse { - fn schema() -> utoipa::openapi::RefOr { - use utoipa::openapi::schema::{AllOfBuilder, Type}; - use utoipa::openapi::{ObjectBuilder, Ref}; - AllOfBuilder::new() - .item( - ObjectBuilder::new() - .property("id", ObjectBuilder::new().schema_type(Type::String)) - .required("id"), - ) - .item(Ref::from_schema_name("InOutItem")) - .into() - } -} - -#[cfg(feature = "openapi")] -impl utoipa::ToSchema for ItemResponse {} - -impl Serialize for ItemResponse { - fn serialize(&self, serializer: S) -> Result { - let mut value = serde_json::to_value(&self.item).map_err(serde::ser::Error::custom)?; - let object = value - .as_object_mut() - .ok_or_else(|| serde::ser::Error::custom("item must be an object"))?; - // Stored Responses output may retain an upstream ID. The public resource - // must use the same ID as its retrieval URL and pagination cursor, once. - object.insert("id".to_owned(), Value::String(self.id.clone())); - value.serialize(serializer) - } -} - -/// Ordering for conversation item pages. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub enum ItemOrder { - Asc, - #[default] - Desc, -} - -/// Page size for item listings, validated once to `1..=100`. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ItemLimit(u8); - -impl ItemLimit { - const MAX: u8 = 100; - - /// The number of items a page holds. - #[must_use] - pub fn get(self) -> usize { - usize::from(self.0) - } - - /// One more than the page size: fetching it tells whether another page follows. - #[must_use] - pub fn probe(self) -> i64 { - i64::from(self.0) + 1 - } -} - -impl Default for ItemLimit { - fn default() -> Self { - Self(20) - } -} - -impl TryFrom for ItemLimit { - type Error = InvalidItemLimit; - - fn try_from(value: i64) -> Result { - u8::try_from(value) - .ok() - .filter(|limit| (1..=Self::MAX).contains(limit)) - .map(Self) - .ok_or(InvalidItemLimit) - } -} - -/// A page size outside `1..=100`. -#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] -#[error("limit must be between 1 and 100")] -pub struct InvalidItemLimit; - -/// Validated pagination for item listings. -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct ItemPage<'a> { - pub limit: ItemLimit, - /// Public ID of the last item on the previous page. - pub after: Option<&'a str>, - pub order: ItemOrder, -} - -/// Extra item fields a listing can be asked to include, as `OpenAI` defines them. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub enum ItemInclude { - #[serde(rename = "file_search_call.results")] - FileSearchCallResults, - #[serde(rename = "web_search_call.results")] - WebSearchCallResults, - #[serde(rename = "web_search_call.action.sources")] - WebSearchCallActionSources, - #[serde(rename = "message.input_image.image_url")] - MessageInputImageUrl, - #[serde(rename = "computer_call_output.output.image_url")] - ComputerCallOutputImageUrl, - #[serde(rename = "code_interpreter_call.outputs")] - CodeInterpreterCallOutputs, - #[serde(rename = "reasoning.encrypted_content")] - ReasoningEncryptedContent, - #[serde(rename = "message.output_text.logprobs")] - MessageOutputTextLogprobs, -} - -impl FromStr for ItemInclude { - type Err = serde::de::value::Error; - - fn from_str(value: &str) -> Result { - Self::deserialize(value.into_deserializer()) - } -} - -impl ItemInclude { - /// Whether stored items already carry this field. - /// - /// Stored items keep every field their item had, so expansions that read the - /// item itself are served as stored. Hosted tool results come from data sources - /// the gateway does not keep, so those expansions cannot be served. - #[must_use] - pub const fn is_stored_with_items(self) -> bool { - match self { - Self::MessageInputImageUrl - | Self::ComputerCallOutputImageUrl - | Self::ReasoningEncryptedContent - | Self::MessageOutputTextLogprobs => true, - Self::FileSearchCallResults - | Self::WebSearchCallResults - | Self::WebSearchCallActionSources - | Self::CodeInterpreterCallOutputs => false, - } - } -} - -/// Response for successful deletion. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] -pub struct DeletedResponse { - /// ID of the deleted resource. - pub id: String, - - /// Object type. - pub object: String, - - /// Whether the deletion was successful. - pub deleted: bool, -} - impl ConversationResponse { /// Create a new conversation response. #[must_use] @@ -261,69 +69,5 @@ impl ConversationResponse { } } -impl ItemResponse { - /// Build the public resource, expanding shorthand message content. - #[must_use] - pub fn new(id: String, mut item: InOutItem) -> Self { - if let InOutItem::Input(InputItem::Message(message)) = &mut item { - message.status.get_or_insert(MessageStatus::Completed); - if let InputMessageContent::Text(text) = &mut message.content { - let content = InputTextContent::new(std::mem::take(text)); - message.content = InputMessageContent::Parts(vec![InputContent::InputText(content)]); - } - } - Self { id, item } - } -} - -impl ListItemsResponse { - /// Create a new list response. - #[must_use] - pub fn new(data: Vec, has_more: bool) -> Self { - let first_id = data.first().map(|item| item.id.clone()); - let last_id = data.last().map(|item| item.id.clone()); - - Self { - object: "list".to_string(), - data, - has_more, - first_id, - last_id, - } - } -} - -impl DeletedResponse { - /// Create a deleted conversation response. - #[must_use] - pub fn conversation(id: String) -> Self { - Self { - id, - object: "conversation.deleted".to_string(), - deleted: true, - } - } - - /// Create a deleted response object, matching the `DELETE /v1/responses/{id}` endpoint. - #[must_use] - pub fn response(id: String) -> Self { - Self { - id, - object: "response.deleted".to_string(), - deleted: true, - } - } - - /// Create a deleted item response. - #[must_use] - pub fn item(id: String) -> Self { - Self { - id, - object: "conversation.item.deleted".to_string(), - deleted: true, - } - } -} - #[cfg(test)] mod tests; diff --git a/crates/agentic-server-core/src/types/conversations/tests.rs b/crates/agentic-server-core/src/types/conversations/tests.rs index 59784fc60..62cb7333e 100644 --- a/crates/agentic-server-core/src/types/conversations/tests.rs +++ b/crates/agentic-server-core/src/types/conversations/tests.rs @@ -18,37 +18,9 @@ fn gateway_calls_keep_their_payload_and_unknown_types_are_rejected() { } #[test] -fn item_resource_has_one_id_and_flattened_content() { - let item = serde_json::from_value(json!({"type":"message", "id":"upstream_id", "role":"user", "content":"hello"})) - .unwrap(); - let resource = ItemResponse::new("item_stored".into(), item); - let text = serde_json::to_string(&resource).unwrap(); - assert_eq!(text.matches("\"id\"").count(), 1); - let actual: Value = serde_json::from_str(&text).unwrap(); - assert_eq!(actual["id"], "item_stored"); - assert_eq!(actual["type"], "message"); - assert_eq!(actual["status"], "completed"); - assert_eq!(actual["content"], json!([{"type":"input_text", "text":"hello"}])); - assert!(actual.get("item").is_none()); -} - -#[test] -fn batch_requests_and_empty_pages_match_wire_contract() { +fn batch_requests_match_wire_contract() { let request: CreateItemRequest = serde_json::from_value(json!({"items":[{"role":"user", "content":"hello"}]})).unwrap(); assert_eq!(request.items.len(), 1); assert!(serde_json::from_value::(json!({"item":{"role":"user", "content":"hello"}})).is_err()); - assert_eq!( - serde_json::to_value(ListItemsResponse::new(vec![], false)).unwrap(), - json!({"object":"list", "data":[], "has_more":false, "first_id":null, "last_id":null}) - ); -} - -#[test] -fn manually_added_assistant_shorthand_remains_input_text() { - let item: InOutItem = serde_json::from_value(json!({"type":"message", "role":"assistant", "content":"9"})).unwrap(); - let resource = ItemResponse::new("msg_manual".into(), item); - let actual = serde_json::to_value(resource).unwrap(); - assert_eq!(actual["role"], "assistant"); - assert_eq!(actual["content"], json!([{"type":"input_text", "text":"9"}])); } diff --git a/crates/agentic-server-core/src/types/io/input.rs b/crates/agentic-server-core/src/types/io/input.rs index ab4b297f6..765bc104f 100644 --- a/crates/agentic-server-core/src/types/io/input.rs +++ b/crates/agentic-server-core/src/types/io/input.rs @@ -271,6 +271,8 @@ impl TryFrom<&ToolSearchCall> for InputToolSearchCall { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct ToolSearchOutputMessage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, #[serde(deserialize_with = "deserialize_non_blank_string")] pub call_id: String, #[serde(default)] @@ -295,6 +297,8 @@ pub struct CompactionItem { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct CustomToolCallOutputMessage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, pub call_id: String, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, diff --git a/crates/agentic-server-core/src/types/io/item.rs b/crates/agentic-server-core/src/types/io/item.rs index 0426919f1..69fe374ac 100644 --- a/crates/agentic-server-core/src/types/io/item.rs +++ b/crates/agentic-server-core/src/types/io/item.rs @@ -3,7 +3,9 @@ mod item_id; use std::borrow::Cow; +use std::str::FromStr; +use serde::de::IntoDeserializer; use serde::{Deserialize, Serialize}; #[cfg(feature = "openapi")] use utoipa::{ @@ -11,7 +13,8 @@ use utoipa::{ openapi::{Ref, RefOr, schema::Schema}, }; -use super::{InputItem, OutputItem}; +use super::{InputContent, InputItem, InputMessageContent, InputTextContent, OutputItem}; +use crate::types::event::MessageStatus; use crate::utils::common::serialize_to_value; /// An input or output item retained in request, response, or conversation history. @@ -59,6 +62,22 @@ impl From for InOutItem { } impl InOutItem { + /// Assign the stored public identity and expand shorthand message content. + /// This consumes the returned copy; retained model history is not modified. + #[must_use] + pub(crate) fn into_public(mut self, id: String) -> Self { + self.set_id(id); + if let Self::Input(InputItem::Message(message)) = &mut self { + message.status.get_or_insert(MessageStatus::Completed); + if let InputMessageContent::Text(text) = &mut message.content { + message.content = InputMessageContent::Parts(vec![InputContent::InputText(InputTextContent::new( + std::mem::take(text), + ))]); + } + } + self + } + /// Borrow an input record or project a public output using the existing continuation conversion. pub(crate) fn as_input(&self) -> Option> { match self { @@ -99,5 +118,205 @@ impl InOutItem { } } +/// Response for listing items with pagination. +#[derive(Debug, Clone, Serialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct ListItemsResponse { + /// Object type, always "list". + pub object: String, + + /// Items in this page. + pub data: Vec, + + /// Whether there are more items available. + pub has_more: bool, + + /// ID of the first item in this page. + pub first_id: Option, + + /// ID of the last item in this page. + pub last_id: Option, +} + +/// Ordering for item pages. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum ItemOrder { + Asc, + #[default] + Desc, +} + +/// Page size for item listings, validated to `0..=100`. +/// Conversation listings additionally require a nonzero limit. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(try_from = "i64", into = "i64")] +pub struct ItemLimit(u8); + +impl ItemLimit { + /// The number of items a page holds. + #[must_use] + pub fn get(self) -> usize { + usize::from(self.0) + } + + /// One extra item determines whether another page follows. + #[must_use] + pub fn probe(self) -> i64 { + i64::from(self.0) + 1 + } +} + +impl Default for ItemLimit { + fn default() -> Self { + Self(20) + } +} + +impl TryFrom for ItemLimit { + type Error = InvalidItemLimit; + + fn try_from(value: i64) -> Result { + if value < 0 { + return Err(InvalidItemLimit::BelowMinimum); + } + if value > 100 { + return Err(InvalidItemLimit::AboveMaximum); + } + u8::try_from(value) + .map(Self) + .map_err(|_| InvalidItemLimit::AboveMaximum) + } +} + +impl From for i64 { + fn from(limit: ItemLimit) -> Self { + Self::from(limit.0) + } +} + +/// A page size outside `0..=100`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum InvalidItemLimit { + #[error("limit must be at least 0")] + BelowMinimum, + #[error("limit must be at most 100")] + AboveMaximum, +} + +impl InvalidItemLimit { + /// Machine-readable code for an invalid page size. + #[must_use] + pub fn code(&self) -> &'static str { + match self { + Self::BelowMinimum => "integer_below_min_value", + Self::AboveMaximum => "integer_above_max_value", + } + } +} + +/// Validated pagination for item listings. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct ItemPage<'a> { + pub limit: ItemLimit, + /// Public ID of the last item on the previous page. + pub after: Option<&'a str>, + pub order: ItemOrder, +} + +/// Extra item fields a listing can be asked to include, as `OpenAI` defines them. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub enum ItemInclude { + #[serde(rename = "file_search_call.results")] + FileSearchCallResults, + #[serde(rename = "web_search_call.results")] + WebSearchCallResults, + #[serde(rename = "web_search_call.action.sources")] + WebSearchCallActionSources, + #[serde(rename = "message.input_image.image_url")] + MessageInputImageUrl, + #[serde(rename = "computer_call_output.output.image_url")] + ComputerCallOutputImageUrl, + #[serde(rename = "code_interpreter_call.outputs")] + CodeInterpreterCallOutputs, + #[serde(rename = "reasoning.encrypted_content")] + ReasoningEncryptedContent, + #[serde(rename = "message.output_text.logprobs")] + MessageOutputTextLogprobs, +} + +impl FromStr for ItemInclude { + type Err = serde::de::value::Error; + + fn from_str(value: &str) -> Result { + Self::deserialize(value.into_deserializer()) + } +} + +/// Response for successful deletion. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct DeletedResponse { + /// ID of the deleted resource. + pub id: String, + + /// Object type. + pub object: String, + + /// Whether the deletion was successful. + pub deleted: bool, +} + +impl ListItemsResponse { + /// Create a new list response. + #[must_use] + pub fn new(data: Vec, has_more: bool) -> Self { + let first_id = data.first().and_then(InOutItem::id).map(str::to_owned); + let last_id = data.last().and_then(InOutItem::id).map(str::to_owned); + + Self { + object: "list".to_string(), + data, + has_more, + first_id, + last_id, + } + } +} + +impl DeletedResponse { + /// Create a deleted response resource. + #[must_use] + pub fn response(id: String) -> Self { + Self { + id, + object: "response.deleted".to_string(), + deleted: true, + } + } + + /// Create a deleted conversation response. + #[must_use] + pub fn conversation(id: String) -> Self { + Self { + id, + object: "conversation.deleted".to_string(), + deleted: true, + } + } + + /// Create a deleted item response. + #[must_use] + pub fn item(id: String) -> Self { + Self { + id, + object: "conversation.item.deleted".to_string(), + deleted: true, + } + } +} + #[cfg(test)] mod tests; diff --git a/crates/agentic-server-core/src/types/io/item/item_id.rs b/crates/agentic-server-core/src/types/io/item/item_id.rs index 1a9644ecc..b95f18bdd 100644 --- a/crates/agentic-server-core/src/types/io/item/item_id.rs +++ b/crates/agentic-server-core/src/types/io/item/item_id.rs @@ -1,6 +1,6 @@ //! Typed public item identities and their known type-specific prefixes. -use crate::types::io::{InputItem, OutputItem}; +use crate::types::io::{InOutItem, InputItem, OutputItem}; impl InputItem { /// Returns the public ID carried by this input item, when present. @@ -22,10 +22,9 @@ impl InputItem { Self::Compaction(item) => item.id.as_deref(), Self::WebSearchCall(item) => Some(&item.id), Self::McpCall(item) => Some(&item.id), - Self::FunctionCallOutput(_) - | Self::ToolSearchOutput(_) - | Self::CustomToolCallOutput(_) - | Self::CompactionTrigger => None, + Self::ToolSearchOutput(item) => item.id.as_deref(), + Self::CustomToolCallOutput(item) => item.id.as_deref(), + Self::FunctionCallOutput(_) | Self::CompactionTrigger => None, } } @@ -103,3 +102,57 @@ impl OutputItem { } } } + +impl InOutItem { + /// The item's public identity, when supplied or assigned by storage. + #[must_use] + pub fn id(&self) -> Option<&str> { + match self { + Self::Input(item) => item.id(), + Self::Output(item) => item.id(), + } + } + + /// Assign the public resource identity without changing the item kind or payload. + pub(crate) fn set_id(&mut self, id: String) { + match self { + Self::Input(item) => match item { + InputItem::MultiAgentCall(item) => item.id = Some(id), + InputItem::MultiAgentCallOutput(item) => item.id = Some(id), + InputItem::AgentMessage(item) => item.id = Some(id), + InputItem::Message(item) => item.id = Some(id), + InputItem::FunctionCall(item) => item.id = Some(id), + InputItem::ShellCall(item) => item.id = Some(id), + InputItem::ShellCallOutput(item) => item.id = Some(id), + InputItem::Compaction(item) => item.id = Some(id), + InputItem::ToolSearchOutput(item) => item.id = Some(id), + InputItem::CustomToolCallOutput(item) => item.id = Some(id), + InputItem::CodeInterpreterCall(item) => item.id = id, + InputItem::ToolSearchCall(item) => item.id = id, + InputItem::CustomToolCall(item) => item.id = id, + InputItem::Reasoning(item) => item.id = id, + InputItem::McpListTools(item) => item.id = id, + InputItem::WebSearchCall(item) => item.id = id, + InputItem::McpCall(item) => item.id = id, + InputItem::FunctionCallOutput(_) | InputItem::CompactionTrigger => {} + }, + Self::Output(item) => match item { + OutputItem::MultiAgentCall(item) => item.id = id, + OutputItem::MultiAgentCallOutput(item) => item.id = id, + OutputItem::AgentMessage(item) => item.id = id, + OutputItem::Message(item) => item.id = id, + OutputItem::FunctionCall(item) => item.id = id, + OutputItem::CodeInterpreterCall(item) => item.id = id, + OutputItem::ToolSearchCall(item) => item.id = id, + OutputItem::CustomToolCall(item) => item.id = id, + OutputItem::WebSearchCall(item) => item.id = id, + OutputItem::McpCall(item) => item.id = id, + OutputItem::McpListTools(item) => item.id = id, + OutputItem::Reasoning(item) => item.id = id, + OutputItem::ShellCall(item) => item.id = Some(id), + OutputItem::Compaction(item) => item.id = Some(id), + OutputItem::Unknown => {} + }, + } + } +} diff --git a/crates/agentic-server-core/src/types/io/item/tests.rs b/crates/agentic-server-core/src/types/io/item/tests.rs index 77db83ebc..927281d8b 100644 --- a/crates/agentic-server-core/src/types/io/item/tests.rs +++ b/crates/agentic-server-core/src/types/io/item/tests.rs @@ -210,3 +210,90 @@ fn preserves_gateway_call_input_and_rejects_unknown_types() { assert!(serde_json::from_value::(json!({"type":"future_item", "payload":42})).is_err()); assert!(serde_json::from_value::(json!({"type":"function_call_output"})).is_err()); } + +#[test] +fn item_resource_has_one_id_and_flattened_content() { + let item: InOutItem = + serde_json::from_value(json!({"type":"message", "id":"upstream_id", "role":"user", "content":"hello"})) + .unwrap(); + let resource = item.into_public("item_stored".into()); + let text = serde_json::to_string(&resource).unwrap(); + assert_eq!(text.matches("\"id\"").count(), 1); + let actual: serde_json::Value = serde_json::from_str(&text).unwrap(); + assert_eq!(actual["id"], "item_stored"); + assert_eq!(actual["type"], "message"); + assert_eq!(actual["status"], "completed"); + assert_eq!(actual["content"], json!([{"type":"input_text", "text":"hello"}])); + assert!(actual.get("item").is_none()); +} + +#[test] +fn function_call_history_status_is_preserved() { + for wire in [ + json!({"type":"function_call","call_id":"call_1","name":"lookup","arguments":"{}"}), + json!({"type":"function_call","call_id":"call_1","name":"lookup","arguments":"{}","status":"completed"}), + ] { + let item: InOutItem = serde_json::from_value(wire.clone()).unwrap(); + let public = serde_json::to_value(item.into_public("fc_history".into())).unwrap(); + assert_eq!(public.get("status"), wire.get("status")); + assert_eq!(public["call_id"], "call_1"); + } +} + +#[test] +fn manually_added_assistant_shorthand_remains_input_text() { + let item: InOutItem = serde_json::from_value(json!({"type":"message", "role":"assistant", "content":"9"})).unwrap(); + let resource = item.into_public("msg_manual".into()); + let actual = serde_json::to_value(resource).unwrap(); + assert_eq!(actual["role"], "assistant"); + assert_eq!(actual["content"], json!([{"type":"input_text", "text":"9"}])); +} + +#[test] +fn public_item_ids_are_typed_and_preserve_tool_and_orchestration_payloads() { + for wire in [ + json!({"type":"custom_tool_call_output", "id":"old", "call_id":"call_1", "output":"done"}), + json!({"type":"tool_search_output", "id":"old", "call_id":"call_1", "tools":[]}), + json!({"type":"web_search_call", "id":"old", "status":"completed", "action":{"type":"search", "query":"weather"}}), + json!({"type":"mcp_list_tools", "id":"old", "server_label":"weather", "tools":[]}), + json!({"type":"compaction", "id":"old", "encrypted_content":"opaque"}), + ] { + let item: InOutItem = serde_json::from_value(wire).unwrap(); + assert_eq!(item.id(), Some("old")); + let mut expected = serde_json::to_value(&item).unwrap(); + expected["id"] = json!("stored"); + let public = item.into_public("stored".into()); + assert_eq!(public.id(), Some("stored")); + assert_eq!(serde_json::to_value(public).unwrap(), expected); + } + + let item = InOutItem::Output(OutputItem::Message(OutputMessage::new( + "upstream", + MessageStatus::Completed, + ))); + let mut expected = serde_json::to_value(&item).unwrap(); + expected["id"] = json!("stored"); + let public = item.into_public("stored".into()); + assert!(matches!(public, InOutItem::Output(_))); + assert_eq!(serde_json::to_value(public).unwrap(), expected); +} + +#[test] +fn item_limit_serde_preserves_bounds() { + for value in [0_i64, 20, 100] { + let limit: ItemLimit = serde_json::from_value(json!(value)).unwrap(); + assert_eq!(i64::from(limit), value); + assert_eq!(serde_json::to_value(limit).unwrap(), value); + } + for value in [json!(-1), json!(101), json!(1.5)] { + assert!(serde_json::from_value::(value).is_err()); + } +} + +#[test] +fn empty_pages_match_wire_contract() { + assert_eq!( + serde_json::to_value(ListItemsResponse::new(vec![], false)).unwrap(), + json!({"object":"list", "data":[], "has_more":false, "first_id":null, "last_id":null}) + ); +} diff --git a/crates/agentic-server-core/src/types/io/mod.rs b/crates/agentic-server-core/src/types/io/mod.rs index b8be1f29d..d76e027c9 100644 --- a/crates/agentic-server-core/src/types/io/mod.rs +++ b/crates/agentic-server-core/src/types/io/mod.rs @@ -1,7 +1,9 @@ pub mod code_interpreter; pub mod input; mod item; -pub use item::InOutItem; +pub use item::{ + DeletedResponse, InOutItem, InvalidItemLimit, ItemInclude, ItemLimit, ItemOrder, ItemPage, ListItemsResponse, +}; pub mod multi_agent; pub mod output; pub mod reasoning; diff --git a/crates/agentic-server-core/src/types/mod.rs b/crates/agentic-server-core/src/types/mod.rs index 3872de462..3b14c7db0 100644 --- a/crates/agentic-server-core/src/types/mod.rs +++ b/crates/agentic-server-core/src/types/mod.rs @@ -8,9 +8,9 @@ pub mod request_response; pub mod tools; pub use conversations::{ - ConversationResponse, CreateConversationRequest, CreateItemRequest, DeletedResponse, ItemResponse, - ListItemsResponse, UpdateConversationRequest, + ConversationResponse, CreateConversationRequest, CreateItemRequest, UpdateConversationRequest, }; + pub use io::{ AllowedTool, AllowedToolsMode, CodeInterpreterCall, CodeInterpreterCallOutput, CodeInterpreterCallStatus, CodeInterpreterCallStreamEvent, CompactionItem, CustomToolCall, CustomToolCallOutputMessage, FunctionTool, @@ -25,6 +25,7 @@ pub use io::{ WebSearchActionFindInPage, WebSearchActionOpenPage, WebSearchActionSearch, WebSearchCall, WebSearchCallStatus, WebSearchSource, }; +pub use io::{DeletedResponse, ListItemsResponse}; pub use request_response::{ CompactRequest, CompactedResponse, ContextManagement, IncompleteDetails, ReasoningConfig, RequestPayload, ResponsePayload, ResponseTextConfig, ResponseTextFormat, UpstreamRequest, UpstreamTool, diff --git a/crates/agentic-server-core/tests/cassettes/README.md b/crates/agentic-server-core/tests/cassettes/README.md index 79eb78adc..d0d0e6db1 100644 --- a/crates/agentic-server-core/tests/cassettes/README.md +++ b/crates/agentic-server-core/tests/cassettes/README.md @@ -37,6 +37,113 @@ printf 'What did I just show you?\n' | python tests/cassettes/record_cassette.py The recorder scripts (`record_reasoning_cassettes.sh`, `record_tool_call_cassettes.sh`, etc.) use `printf` to feed fixed prompts per test so no manual input is needed. +`record_responses_api_cassettes.sh` extends the existing recorder's `items` mode. +Each provider gets one flat `turns` list per suite, using the same capture format as +`cassettes/conversations`. Individual edge probes record the observed status rather +than assuming that OpenAI and the gateway accept the same requests. + +| `RESPONSES_SCENARIO` | HTTP turns | Coverage | +| --- | --- | --- | +| `lifecycle` | 19 | Stored parent/child responses, input pages, retrieval, parent deletion, child visibility, continued inference after deletion, repeated deletion. | +| `pagination` | 16 | Ascending, descending and default order; one-item pages; limit 100; cursors at the end; a cursor from an unrelated response. | +| `edge-cases` | 23 | Unstored/missing IDs; missing parent; invalid, repeated and out-of-range query parameters; unknown and repeated includes; repeated deletion. | +| `conversation` | 19 | Responses attached to a conversation, manual item insertion/deletion, snapshots before and after mutations, response deletion, conversation deletion. | +| `replayed-items` | 14 | Generated-ID reuse, two copies of an ID with different content, wrong-prefix IDs, function call/output history without public item IDs, seed immutability after probes. | +| `tool-items` | 40 | Replay existing OpenAI web-search, MCP, code-interpreter, shell and custom-tool histories; inspect input items with original tool fields, omitted IDs, omitted statuses, and both omitted. | + +From the worktree root, with an inference server already running: + +```bash +# One-time recorder dependencies. +uv venv .venv +uv pip install --python .venv/bin/python -r crates/agentic-server-core/tests/cassettes/recorder-requirements.txt + +# Start the release gateway in a separate terminal; persistence must be enabled. +AGENTIC_API_HOME=/home/akk/maral/res/.agentic-api \ + ./target/release/agentic-server --llm-api-base http://127.0.0.1:5050 --gateway-port 9000 + +# Load credentials without printing them. Record each provider independently. +set -a; source /home/akk/maral/res/.env; set +a +RESPONSES_RECORD_SET=openai OPENAI_MODEL=gpt-4.1 \ + bash crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh +RESPONSES_RECORD_SET=gateway GATEWAY_MODEL=Qwen/Qwen3.8-27B-FP8 GATEWAY_URL=http://127.0.0.1:9000 \ + bash crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh + +# Replay both providers' recordings through the actual gateway handlers. +cargo build --locked -p agentic-server --bin agentic-server +cargo test --locked -p agentic-server-core --test responses_api_test +``` + +Use the exact model alias served by your inference engine for `GATEWAY_MODEL`. +`RESPONSES_SCENARIO=all` is the default; select any row above to replace only that suite. +`RESPONSES_STREAM_MODE=nonstreaming` is the default. Set `streaming` or `both` to +exercise streamed response creation as well; management calls still return JSON. +The `replayed-items` suite keeps admission probes non-streaming so rejected request +bodies remain inspectable, while its seed response follows the selected mode. +Files are named `responses/response--.yaml`, with `-stream` +appended to the scenario for streaming runs. Incomplete recordings remain `.yaml.tmp` +and do not replace a previously validated capture. + +The `tool-items` suite loads the existing non-streaming OpenAI references in +`web_search/`, `mcp/`, `code_interpreter/`, `shell/`, and `custom_tool/`. It keeps +the complete recorded history, including reasoning dependencies and call/output +pairs, and submits it in a new stored response without enabling tools. Only the +selected tool items' `id` and `status` fields are omitted in the variant probes. +Each creation is followed by `GET /responses/{id}/input_items`; rejected creations +are followed by a missing-response lookup, never another response's history. +These admission probes always use JSON and are recorded once even when both +transports are selected. To refresh just this reference: + +```bash +RESPONSES_RECORD_SET=openai RESPONSES_SCENARIO=tool-items OPENAI_MODEL=gpt-5.6 \ + bash crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh +``` + +Use the source recordings' reasoning-compatible model for this suite. The recorded +reference accepts 18 of 20 creations: code-interpreter items require an ID, but +OpenAI assigns them new IDs in these replays. All +accepted tool items have resource IDs in the input-items page. `mcp_list_tools` +has no status; the other selected items have statuses. Shell/custom calls without +IDs are listed as `in_progress`, including when a completed custom call was +submitted; with their original recorded IDs they are listed as `completed`. +Their output items are listed as `completed`. These observations distinguish +replaying existing item identities from submitting history without identities; +they do not justify a universal status default. + +`recorded_tool_history_ids_and_statuses` asserts this OpenAI reference directly. +The new suite does not yet establish live gateway parity; the five existing +management suites below continue to run through the gateway. + +`RESPONSES_CASSETTES_DIR` overrides the output/replay directory and +`RECORDER_PROXY_PORT` overrides the local proxy port (default 7070). +Repeated query values are retained as arrays in `request.query_params`; a single +value remains a string. No observed response body or status is rewritten. + +The Rust tests require the five original suites for both providers in JSON and streaming mode. +They assert the recordings directly, then replay every management request through +a local gateway process with only inference mocked. `RESPONSES_GATEWAY_BIN` can +select an explicitly built binary; otherwise the tests use `agentic-server` from +the Cargo profile directory. Error `type`, `code`, and `param` are checked against +the OpenAI reference. Log probabilities are excluded from item comparisons. +The lifecycle suite also checks that the continuation retains the surviving child's +history and new user message after parent deletion, and inspects the live gateway's +upstream request to ensure the deleted parent's user message is not sent to inference. +Default statuses for client-supplied function history are deferred to the item-type +refactor: OpenAI lists an omitted function-call status as `in_progress` and an omitted +function-result status as `completed`; the gateway currently leaves them absent. +Comparisons tolerate only absence versus these two recorded defaults. Other status +values remain checked, and the captured YAML is unchanged. `FunctionToolResultMessage` +retains only `call_id` and `output`; function-result resource IDs and supplied statuses +are also deferred to that refactor, so these replays do not establish parity for those +fields. Function results still retain their `call_id` association. +Stateless creation remains upstream pass-through on this branch, so its optional +`store` response field is not compared across providers; subsequent CRUD calls +verify that the response was not retained. Differences in model-generated output +are checked within each provider's pages, while client input must match across providers. + +Reference: [Responses input items](https://developers.openai.com/api/reference/resources/responses/subresources/input_items/methods/list) +and [Conversation items](https://developers.openai.com/api/reference/resources/conversations/subresources/items/methods/list). + ## Mid-turn steering exploration `record_multi_turn_steering_cassettes.sh` records OpenAI references or gateway observations of the WebSocket diff --git a/crates/agentic-server-core/tests/cassettes/record_cassette.py b/crates/agentic-server-core/tests/cassettes/record_cassette.py index a62fd31c5..5f5acecfc 100644 --- a/crates/agentic-server-core/tests/cassettes/record_cassette.py +++ b/crates/agentic-server-core/tests/cassettes/record_cassette.py @@ -196,12 +196,16 @@ async def proxy_request(request: Request, path: str) -> Response: raw_body = await request.body() parsed_body = json.loads(raw_body.decode("utf-8")) if raw_body else {} + query_params = {} + for key in request.query_params: + values = request.query_params.getlist(key) + query_params[key] = values[0] if len(values) == 1 else values turn: dict[str, Any] = { "filename": filename, "request": { "method": request.method, "path": f"/{path}", - "query_params": dict(request.query_params), + "query_params": query_params, "body": parsed_body, "headers": _filter_request_headers(request.headers), }, @@ -349,7 +353,7 @@ def _item_request( path: str, *, body: dict | None = None, - params: dict | None = None, + params: dict | list[tuple[str, str]] | None = None, expected_status: int | None = 200, ) -> dict: """Send management requests through the shared recording proxy.""" @@ -373,13 +377,14 @@ def _item_response( conversation: str | None = None, previous_response_id: str | None = None, stream: bool = False, + store: bool = True, ) -> dict: if conversation and previous_response_id: raise ValueError("conversation and previous_response_id are mutually exclusive") body: dict[str, Any] = { "model": model, "input": [{"role": "user", "content": prompt}], - "store": True, + "store": store, "stream": stream, } if conversation: @@ -419,7 +424,211 @@ def _record_duplicate_items( def run_items( client: httpx.Client, proxy_url: str, model: str, scenario: str, stream: bool ) -> None: - """Record each conversation or item API request as its own numbered step.""" + """Record each fixed Conversation Items or Responses API request as a numbered step.""" + if scenario == "response-tool-items": + # Reuse the complete captured history, including reasoning dependencies + # and client-side results. Do not execute the tools again. + references = ( + ("web_search/web-search-openai-reference-gpt-5.6-nonstreaming.yaml", + {"web_search_call"}), + ("mcp/mcp-openai-reference-counter-say-hello-gpt-4o-nonstreaming.yaml", + {"mcp_list_tools", "mcp_call"}), + ("code_interpreter/code-interpreter-openai-reference-gpt-5.6-nonstreaming.yaml", + {"code_interpreter_call"}), + ("shell/shell-openai-reference-success-gpt-5.6-nonstreaming.yaml", + {"shell_call", "shell_call_output"}), + ("custom_tool/custom-tool-openai-reference-gpt-5.6-nonstreaming.yaml", + {"custom_tool_call", "custom_tool_call_output"}), + ) + for filename, item_types in references: + reference = yaml_load((Path(__file__).parent / filename).read_text(encoding="utf-8")) + history = [] + for turn in reference["turns"]: + inputs = turn["request"]["body"].get("input", []) + if isinstance(inputs, str): + history.append({"role": "user", "content": inputs}) + else: + history.extend(inputs) + history.extend(turn["response"]["body"]["output"]) + if not item_types <= {item.get("type") for item in history}: + raise RuntimeError(f"{filename}: missing expected tool history") + for omitted in ((), ("id",), ("status",), ("id", "status")): + print(f"[tool history: {filename}; omitted fields: {omitted}]") + items = [{key: value for key, value in item.items() + if item.get("type") not in item_types or key not in omitted} + for item in history] + result = _item_request( + client, proxy_url, "POST", "/v1/responses", + body={"model": model, "store": True, "stream": False, + "input": [*items, {"role": "user", "content": + "This is completed tool history. Do not call tools. Reply only OK."}]}, + expected_status=None, + ) + # Rejections are evidence too. Never substitute another + # response's history when admission fails. + response_id = result.get("id", "resp_missing") + _item_request(client, proxy_url, "GET", f"/v1/responses/{response_id}/input_items", + params={"order": "asc", "limit": 100}, expected_status=None) + return + if scenario == "response-lifecycle": + first = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", stream=stream) + first_path = f"/v1/responses/{first['id']}" + _item_request(client, proxy_url, "GET", f"{first_path}/input_items") + child = _item_response( + client, proxy_url, model, "Reply with only ORCHID.", + previous_response_id=first["id"], stream=stream, + ) + child_path = f"/v1/responses/{child['id']}" + child_items_path = f"{child_path}/input_items" + _item_request(client, proxy_url, "GET", child_items_path, params={"order": "asc"}) + page = _item_request( + client, proxy_url, "GET", child_items_path, + params={"order": "asc", "limit": 1}, + ) + if not page.get("last_id") or not page.get("has_more"): + raise RuntimeError(f"expected a non-final one-item input page: {page}") + _item_request( + client, proxy_url, "GET", child_items_path, + params={"order": "asc", "limit": 1, "after": page["last_id"]}, + ) + _item_request(client, proxy_url, "GET", child_path) + _item_request(client, proxy_url, "DELETE", first_path) + _item_request(client, proxy_url, "GET", first_path, expected_status=None) + _item_request(client, proxy_url, "GET", f"{first_path}/input_items", expected_status=None) + _item_request(client, proxy_url, "GET", child_items_path, params={"order": "asc"}) + _item_request(client, proxy_url, "GET", child_path) + _item_request(client, proxy_url, "DELETE", first_path, expected_status=None) + # A listing's deletion visibility does not establish what inference sees. + # Continue the surviving child before deleting it and capture both the + # model's answer and the new response's input window. + continuation = _item_response( + client, proxy_url, model, + "What exact word did the first user message ask you to reply with? Reply with only that word.", + previous_response_id=child["id"], stream=stream, + ) + continuation_path = f"/v1/responses/{continuation['id']}" + _item_request(client, proxy_url, "GET", f"{continuation_path}/input_items", params={"order": "asc"}) + _item_request(client, proxy_url, "DELETE", continuation_path) + _item_request(client, proxy_url, "DELETE", child_path) + _item_request(client, proxy_url, "GET", child_items_path, expected_status=None) + _item_request(client, proxy_url, "GET", child_path, expected_status=None) + return + if scenario == "response-pagination": + first = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", stream=stream) + child = _item_response( + client, proxy_url, model, "Reply with only ORCHID.", + previous_response_id=first["id"], stream=stream, + ) + path = f"/v1/responses/{child['id']}/input_items" + _item_request(client, proxy_url, "GET", path, params={"order": "asc"}) + _item_request(client, proxy_url, "GET", path, params={"order": "desc"}) + for order in ("asc", "desc"): + page = _item_request(client, proxy_url, "GET", path, params={"order": order, "limit": 1}) + if not page.get("last_id") or not page.get("has_more"): + raise RuntimeError(f"expected a non-final {order} page: {page}") + _item_request( + client, proxy_url, "GET", path, + params={"order": order, "limit": 1, "after": page["last_id"]}, + ) + _item_request(client, proxy_url, "GET", path) + for order in ("asc", "desc"): + page = _item_request(client, proxy_url, "GET", path, params={"order": order, "limit": 100}) + if not page.get("last_id"): + raise RuntimeError(f"expected an input cursor: {page}") + _item_request(client, proxy_url, "GET", path, + params={"order": order, "after": page["last_id"]}, expected_status=None) + foreign = _item_response(client, proxy_url, model, "Reply with only VIOLET.", stream=stream) + foreign_page = _item_request(client, proxy_url, "GET", f"/v1/responses/{foreign['id']}/input_items") + _item_request(client, proxy_url, "GET", path, + params={"after": foreign_page["data"][0]["id"]}, expected_status=None) + return + if scenario == "response-edge-cases": + transient = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", stream=stream, store=False) + transient_path = f"/v1/responses/{transient['id']}" + for method, path in ( + ("GET", transient_path), ("GET", f"{transient_path}/input_items"), + ("DELETE", transient_path), ("GET", "/v1/responses/resp_missing"), + ("GET", "/v1/responses/resp_missing/input_items"), + ("DELETE", "/v1/responses/resp_missing"), + ): + _item_request(client, proxy_url, method, path, expected_status=None) + _item_request( + client, proxy_url, "POST", "/v1/responses", + body={"model": model, "input": [{"role": "user", "content": "Reply with only ORCHID."}], + "previous_response_id": "resp_missing", "store": True, "stream": False}, + expected_status=None, + ) + stored = _item_response(client, proxy_url, model, "Reply with only ORCHID.", stream=stream) + path = f"/v1/responses/{stored['id']}/input_items" + for params in ( + {"limit": "0"}, {"limit": "101"}, {"order": "sideways"}, + {"after": "msg_missing"}, {"include[]": "web_search_call.results"}, + {"include[]": "message.output_text.logprobs"}, + {"limit": "-1"}, {"limit": "ten"}, {"after": ""}, + {"include[]": "not.a.valid.include"}, + [("limit", "1"), ("limit", "2")], + [("include[]", "message.output_text.logprobs"), + ("include[]", "reasoning.encrypted_content")], + ): + _item_request(client, proxy_url, "GET", path, params=params, expected_status=None) + _item_request(client, proxy_url, "DELETE", f"/v1/responses/{stored['id']}") + _item_request(client, proxy_url, "DELETE", f"/v1/responses/{stored['id']}", expected_status=None) + return + if scenario == "response-conversation": + conversation = _create_conversation(client, proxy_url) + items_path = f"/v1/conversations/{conversation}/items" + _item_request(client, proxy_url, "POST", items_path, + body={"items": [{"role": "user", "content": "Remember SAPPHIRE."}]}) + first = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", + conversation=conversation, stream=stream) + first_path = f"/v1/responses/{first['id']}" + _item_request(client, proxy_url, "GET", f"{first_path}/input_items", params={"order": "asc"}) + added = _item_request(client, proxy_url, "POST", items_path, + body={"items": [{"role": "user", "content": "Now remember ORCHID."}]}) + _item_request(client, proxy_url, "GET", f"{first_path}/input_items", params={"order": "asc"}) + child = _item_response(client, proxy_url, model, "Reply with only ORCHID.", + conversation=conversation, stream=stream) + child_path = f"/v1/responses/{child['id']}" + _item_request(client, proxy_url, "GET", f"{child_path}/input_items", params={"order": "asc"}) + _item_request(client, proxy_url, "GET", items_path, params={"order": "asc"}) + _item_request(client, proxy_url, "DELETE", f"{items_path}/{added['data'][0]['id']}") + for path in (child_path, first_path): + _item_request(client, proxy_url, "GET", f"{path}/input_items", + params={"order": "asc"}, expected_status=None) + _item_request(client, proxy_url, "DELETE", first_path) + _item_request(client, proxy_url, "GET", f"{child_path}/input_items", + params={"order": "asc"}, expected_status=None) + _item_request(client, proxy_url, "GET", items_path, params={"order": "asc"}) + _item_request(client, proxy_url, "GET", child_path, expected_status=None) + _item_request(client, proxy_url, "DELETE", f"/v1/conversations/{conversation}") + _item_request(client, proxy_url, "GET", f"{child_path}/input_items", expected_status=None) + _item_request(client, proxy_url, "GET", child_path, expected_status=None) + return + if scenario == "response-replayed-items": + seed = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", stream=stream) + seed_items = f"/v1/responses/{seed['id']}/input_items" + page = _item_request(client, proxy_url, "GET", seed_items, params={"order": "asc"}) + reused = {"type": "message", "id": page["data"][0]["id"], + "role": "user", "content": "Reply with only ORCHID."} + for items in ( + [reused], + [reused, {**reused, "content": "Reply with only VIOLET."}], + [{**reused, "id": f"item_wrongprefix_{secrets.token_hex(8)}"}], + [{"type": "function_call", "call_id": "call_history_probe", "name": "lookup", + "arguments": "{}"}, + {"type": "function_call_output", "call_id": "call_history_probe", "output": "SAPPHIRE"}, + {"role": "user", "content": "Reply with only the lookup result."}], + ): + # Probe status is evidence. On rejection, still inspect the seed to + # establish that rejected input did not mutate its stored context. + result = _item_request(client, proxy_url, "POST", "/v1/responses", + body={"model": model, "input": items, "store": True, + "stream": False}, expected_status=None) + response_id = result.get("id", seed["id"]) + _item_request(client, proxy_url, "GET", f"/v1/responses/{response_id}/input_items", + params={"order": "asc"}, expected_status=None) + _item_request(client, proxy_url, "GET", seed_items, params={"order": "asc"}) + return if scenario == "edge-cases": for item in ( { @@ -1728,9 +1937,9 @@ def run_responses( ) @click.option( "--items-scenario", - type=click.Choice(["continuation", "branch", "deletion", "pagination", "edge-cases"]), + type=click.Choice(["continuation", "branch", "deletion", "pagination", "edge-cases", "response-lifecycle", "response-pagination", "response-edge-cases", "response-conversation", "response-replayed-items", "response-tool-items"]), default="continuation", - help="Conversation item recording scenario (for --mode items).", + help="Fixed Conversation Items or Responses API scenario (for --mode items).", ) @click.option( "--branch-from", @@ -1996,7 +2205,13 @@ def main( raise click.UsageError("--max-tool-calls requires linear --mode responses without branches.") if mode == "items": - expected_turns = (10 if items_scenario == "pagination" else + expected_turns = (19 if items_scenario == "response-lifecycle" else + 16 if items_scenario == "response-pagination" else + 23 if items_scenario == "response-edge-cases" else + 19 if items_scenario == "response-conversation" else + 14 if items_scenario == "response-replayed-items" else + 40 if items_scenario == "response-tool-items" else + 10 if items_scenario == "pagination" else 6 if items_scenario == "branch" else 19 if items_scenario == "edge-cases" else 5) if turns != expected_turns: @@ -2165,7 +2380,8 @@ def main( if gateway_url: target = gateway_url.rstrip("/") - headers = {} + gateway_api_key = os.environ.get("GATEWAY_API_KEY", "") + headers = {"Authorization": f"Bearer {gateway_api_key}"} if gateway_api_key else {} backend_label = f"Gateway: {target}" elif vllm_url: target = vllm_url.rstrip("/") diff --git a/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh b/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh new file mode 100755 index 000000000..8e2ffd4f8 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Record flat Responses API cassettes with the existing Python recorder. +# RESPONSES_RECORD_SET=all|openai|gateway; RESPONSES_SCENARIO=all|lifecycle|pagination|edge-cases|conversation|replayed-items|tool-items. +# RESPONSES_STREAM_MODE=nonstreaming|streaming|both (default: nonstreaming). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CASSETTES_DIR="${RESPONSES_CASSETTES_DIR:-$SCRIPT_DIR/responses}" +RECORD_SET="${RESPONSES_RECORD_SET:-all}" +SCENARIO="${RESPONSES_SCENARIO:-all}" +STREAM_MODE="${RESPONSES_STREAM_MODE:-nonstreaming}" +OPENAI_MODEL="${OPENAI_MODEL:-gpt-4.1}" +GATEWAY_MODEL="${GATEWAY_MODEL:-$OPENAI_MODEL}" +GATEWAY_URL="${GATEWAY_URL:-http://localhost:9000}" +WORKTREE_PYTHON="$SCRIPT_DIR/../../../../.venv/bin/python" +if [[ -x "$WORKTREE_PYTHON" ]]; then + PYTHON_BIN="${PYTHON_BIN:-$WORKTREE_PYTHON}" +else + PYTHON_BIN="${PYTHON_BIN:-python3}" +fi + +case "$RECORD_SET" in + all|openai|gateway) ;; + *) echo "RESPONSES_RECORD_SET must be all, openai, or gateway" >&2; exit 2 ;; +esac +case "$SCENARIO" in + all|lifecycle|pagination|edge-cases|conversation|replayed-items|tool-items) ;; + *) echo "RESPONSES_SCENARIO must be all, lifecycle, pagination, edge-cases, conversation, replayed-items, or tool-items" >&2; exit 2 ;; +esac +case "$STREAM_MODE" in + nonstreaming|streaming|both) ;; + *) echo "RESPONSES_STREAM_MODE must be nonstreaming, streaming, or both" >&2; exit 2 ;; +esac +if [[ "$RECORD_SET" != gateway && -z "${OPENAI_API_KEY:-}" ]]; then + echo "OPENAI_API_KEY is required for OpenAI recordings" >&2 + exit 2 +fi +if ! "$PYTHON_BIN" -c 'import click, fastapi, httpx, uvicorn, yaml' 2>/dev/null; then + echo "Recorder dependencies are missing for $PYTHON_BIN; install recorder-requirements.txt" >&2 + exit 2 +fi + +mkdir -p "$CASSETTES_DIR" +record() { + local provider="$1" scenario="$2" model="$3" transport="$4" + local turns mode destination temporary + case "$scenario" in + lifecycle) turns=19; mode=response-lifecycle ;; + pagination) turns=16; mode=response-pagination ;; + edge-cases) turns=23; mode=response-edge-cases ;; + conversation) turns=19; mode=response-conversation ;; + replayed-items) turns=14; mode=response-replayed-items ;; + tool-items) turns=40; mode=response-tool-items ;; + esac + local suffix="" stream_flag=--no-stream + if [[ "$transport" == streaming ]]; then suffix="-stream"; stream_flag=--stream; fi + destination="$CASSETTES_DIR/response-${scenario}${suffix}-${provider}.yaml" + temporary="${destination}.tmp" + local backend=() + if [[ "$provider" == openai ]]; then + backend=(--openai https://api.openai.com) + else + backend=(--gateway "$GATEWAY_URL") + export GATEWAY_API_KEY="${GATEWAY_API_KEY:-${OPENAI_API_KEY:-}}" + fi + echo "Recording $provider/$scenario -> $destination" + rm -f "$temporary" + "$PYTHON_BIN" "$SCRIPT_DIR/record_cassette.py" \ + --mode items --items-scenario "$mode" --turns "$turns" \ + --proxy-port "${RECORDER_PROXY_PORT:-7070}" --model "$model" "$stream_flag" "${backend[@]}" --output "$temporary" + "$PYTHON_BIN" - "$temporary" "$scenario" "$turns" <<'CASSETTE_CHECK' +import sys +from pathlib import Path +from yaml import safe_load + +path, scenario, count = Path(sys.argv[1]), sys.argv[2], int(sys.argv[3]) +cassette = safe_load(path.read_text()) +assert list(cassette) == ["turns"], "cassette must have one flat turns list" +turns = cassette["turns"] +assert len(turns) == count +assert [turn["filename"] for turn in turns] == [f"t{i}" for i in range(1, count + 1)] +for turn in turns: + assert set(turn) == {"filename", "request", "response"} + request, response = turn["request"], turn["response"] + assert {"method", "path", "query_params", "body", "headers"} <= set(request) + assert {"status_code", "headers"} <= set(response) + assert "body" in response or "sse" in response + assert request["path"].startswith(("/v1/responses", "/v1/conversations")), request["path"] + assert request["headers"].get("authorization", "Bearer ***") == "Bearer ***" + assert "transport_error" not in response, "upstream transport failed" + assert isinstance(response["status_code"], int) +first_path = "/v1/conversations" if scenario == "conversation" else "/v1/responses" +assert (turns[0]["request"]["method"], turns[0]["request"]["path"]) == ("POST", first_path) +if scenario == "lifecycle": + assert sum(turn["request"]["method"] == "DELETE" for turn in turns) == 4 + assert sum(turn["request"]["path"].endswith("/input_items") for turn in turns) == 8 +elif scenario == "pagination": + assert sum(turn["request"]["method"] == "POST" for turn in turns) == 3 + assert {"asc", "desc"} <= {turn["request"]["query_params"].get("order") for turn in turns[2:]} +elif scenario == "edge-cases": + assert turns[0]["request"]["body"]["store"] is False + assert turns[7]["request"]["body"]["previous_response_id"] == "resp_missing" + assert turns[8]["request"]["body"]["store"] is True + assert sum(turn["request"]["method"] == "DELETE" for turn in turns) == 4 +elif scenario == "conversation": + assert sum(turn["request"]["method"] == "DELETE" for turn in turns) == 3 + assert any(turn["request"]["body"].get("conversation") for turn in turns) +elif scenario == "replayed-items": + assert sum(turn["request"]["method"] == "POST" for turn in turns) == 5 + assert any(len(turn["request"]["body"].get("input", [])) == 3 for turn in turns) +elif scenario == "tool-items": + for create, listing in zip(turns[::2], turns[1::2]): + assert create["request"]["method"] == "POST" + assert create["request"]["body"]["stream"] is False + assert listing["request"]["method"] == "GET" + response_id = create["response"]["body"].get("id", "resp_missing") + assert listing["request"]["path"] == f"/v1/responses/{response_id}/input_items" + assert {"web_search_call", "mcp_list_tools", "mcp_call", "code_interpreter_call", + "shell_call", "shell_call_output", "custom_tool_call", "custom_tool_call_output"} <= { + item.get("type") for turn in turns[::2] for item in turn["request"]["body"]["input"]} +print(f"Validated {path.name}: {count} flat HTTP turns") +CASSETTE_CHECK + mv "$temporary" "$destination" +} + +for provider in openai gateway; do + if [[ "$RECORD_SET" != all && "$RECORD_SET" != "$provider" ]]; then continue; fi + if [[ "$provider" == openai ]]; then model="$OPENAI_MODEL"; else model="$GATEWAY_MODEL"; fi + for scenario in lifecycle pagination edge-cases conversation replayed-items tool-items; do + if [[ "$SCENARIO" != all && "$SCENARIO" != "$scenario" ]]; then continue; fi + # Admission probes stay JSON so failures remain inspectable. Capture + # tool-items once even when both transports are selected. + if [[ "$scenario" == tool-items ]]; then + record "$provider" "$scenario" "$model" nonstreaming + continue + fi + for transport in nonstreaming streaming; do + if [[ "$STREAM_MODE" == both || "$STREAM_MODE" == "$transport" ]]; then + record "$provider" "$scenario" "$model" "$transport" + fi + done + done +done diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-conversation-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-openai.yaml new file mode 100644 index 000000000..78937cf19 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-openai.yaml @@ -0,0 +1,977 @@ +turns: +- filename: t1 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations + query_params: {} + response: + body: + created_at: 1792738271 + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + items: + - content: Remember SAPPHIRE. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f/items + query_params: {} + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + conversation: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738273 + conversation: + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + created_at: 1792738273 + error: null + frequency_penalty: 0.0 + id: resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 25 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 6 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 31 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + items: + - content: Now remember ORCHID. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f/items + query_params: {} + response: + body: + data: + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + first_id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + has_more: false + last_id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + conversation: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738277 + conversation: + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + created_at: 1792738276 + error: null + frequency_penalty: 0.0 + id: resp_0186b7532502d89f006adb03e451988197846ca60742726a66 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 55 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 60 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + role: assistant + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f/items/msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + query_params: {} + response: + body: + created_at: 1792738271 + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c + query_params: {} + response: + body: + deleted: true + id: resp_0186b7532502d89f006adb03e0cd348197a454dbacff6b234c + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + role: assistant + status: completed + type: message + first_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + has_more: false + last_id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66 + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738277 + conversation: + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + created_at: 1792738276 + error: null + frequency_penalty: 0.0 + id: resp_0186b7532502d89f006adb03e451988197846ca60742726a66 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 55 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 60 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + query_params: {} + response: + body: + deleted: true + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + object: conversation.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + role: user + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03e33fa0819787cbdb0d1682bd790186b7532502d89f + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0186b7532502d89f006adb03e1b6608197a9104e4c499c3bb8 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0186b7532502d89f006adb03e178788197ab9cda68741a4f92 + role: user + status: completed + type: message + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + role: user + status: completed + type: message + first_id: msg_0186b7532502d89f006adb03e4899081978fb72810bff05971 + has_more: false + last_id: msg_6adb03dfbae48197a061baae3f401fe40186b7532502d89f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0186b7532502d89f006adb03e451988197846ca60742726a66 + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738277 + conversation: + id: conv_6adb03df14c08197b2bb094ed25cf7fd0186b7532502d89f + created_at: 1792738276 + error: null + frequency_penalty: 0.0 + id: resp_0186b7532502d89f006adb03e451988197846ca60742726a66 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0186b7532502d89f006adb03e4f3648197b1d3d884ff5c1cf8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 55 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 60 + user: null + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-openai.yaml new file mode 100644 index 000000000..1af4bbded --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-openai.yaml @@ -0,0 +1,1052 @@ +turns: +- filename: t1 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations + query_params: {} + response: + body: + created_at: 1792738289 + id: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + items: + - content: Remember SAPPHIRE. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa/items + query_params: {} + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + conversation: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39","object":"response","created_at":1792738293,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39","object":"response","created_at":1792738293,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"S","item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"obfuscation":"TsBVXJQWviGCDwx","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"APP","item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"obfuscation":"2fwzBmRdvpTQv","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"H","item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"obfuscation":"Tr5pB4XqwqZmH5z","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IRE","item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"obfuscation":"zV9kMk9ooz56Q","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"obfuscation":"Syk58FLNMeZZDyQ","output_index":0,"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","logprobs":[],"output_index":0,"sequence_number":9,"text":"SAPPHIRE."} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE."},"sequence_number":10} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE."}],"role":"assistant"},"output_index":0,"sequence_number":11} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39","object":"response","created_at":1792738293,"status":"completed","access_programs":null,"background":false,"completed_at":1792738294,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":25,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":6,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":31},"user":null,"metadata":{}},"sequence_number":12} + + ' + - ' + + ' + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + items: + - content: Now remember ORCHID. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa/items + query_params: {} + response: + body: + data: + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + first_id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + has_more: false + last_id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + conversation: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc","object":"response","created_at":1792738296,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc","object":"response","created_at":1792738296,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"OR","item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","logprobs":[],"obfuscation":"25ftE9TeQslu4q","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"CH","item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","logprobs":[],"obfuscation":"6jwmf7J5tUAsoA","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"ID","item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","logprobs":[],"obfuscation":"2jjjZUWynCM2Dr","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","logprobs":[],"obfuscation":"jlltGZ06BgFsw5j","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","logprobs":[],"output_index":0,"sequence_number":8,"text":"ORCHID."} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID."},"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID."}],"role":"assistant"},"output_index":0,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc","object":"response","created_at":1792738296,"status":"completed","access_programs":null,"background":false,"completed_at":1792738296,"conversation":{"id":"conv_6adb03f135508196bb37728642fa17e60f78024076088eaa"},"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":55,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":60},"user":null,"metadata":{}},"sequence_number":11} + + ' + - ' + + ' + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + role: assistant + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa/items/msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + query_params: {} + response: + body: + created_at: 1792738289 + id: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39 + query_params: {} + response: + body: + deleted: true + id: resp_0f78024076088eaa006adb03f3e7ac8196a6895584da7e1e39 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + role: assistant + status: completed + type: message + first_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + has_more: false + last_id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738296 + conversation: + id: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + created_at: 1792738296 + error: null + frequency_penalty: 0.0 + id: resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 55 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 60 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + query_params: {} + response: + body: + deleted: true + id: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + object: conversation.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + role: user + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_6adb03f7285c8196998034551c5b636c0f78024076088eaa + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0f78024076088eaa006adb03f61cdc8196b66272355833fc56 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f78024076088eaa006adb03f5d6108196907067f973abbba5 + role: user + status: completed + type: message + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + role: user + status: completed + type: message + first_id: msg_0f78024076088eaa006adb03f84bb8819681b38ebce384f460 + has_more: false + last_id: msg_6adb03f273008196a7793f98b07f32600f78024076088eaa + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738296 + conversation: + id: conv_6adb03f135508196bb37728642fa17e60f78024076088eaa + created_at: 1792738296 + error: null + frequency_penalty: 0.0 + id: resp_0f78024076088eaa006adb03f824048196a91ba8418fef53fc + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_0f78024076088eaa006adb03f887d08196873f5faaafb7a88b + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 55 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 60 + user: null + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-openai.yaml new file mode 100644 index 000000000..11054dca6 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-openai.yaml @@ -0,0 +1,666 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: false + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738245 + created_at: 1792738244 + error: null + frequency_penalty: 0.0 + id: resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0569f94ea43169bf016adb03c4fb7887d1bda49a6c865bc289 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: false + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 20 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0569f94ea43169bf016adb03c48e0087d186182eecca6f321c' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t8 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_missing + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: previous_response_not_found + message: Previous response with id 'resp_missing' not found. + param: previous_response_id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738249 + created_at: 1792738248 + error: null + frequency_penalty: 0.0 + id: resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_0865073d0b1881b3006adb03c8f9a487d1adc111a401afb68f + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 14 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 18 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + limit: '0' + response: + body: + data: [] + first_id: null + has_more: true + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + limit: '101' + response: + body: + error: + code: integer_above_max_value + message: 'Invalid ''limit'': integer above maximum value. Expected a value + <= 100, but got 101 instead.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + order: sideways + response: + body: + error: + code: invalid_value + message: 'Invalid value: ''sideways''. Supported values are: ''asc'' and ''desc''.' + param: order + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + after: msg_missing + response: + body: + error: + code: null + message: No item found with id 'msg_missing' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + include[]: web_search_call.results + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + role: user + status: completed + type: message + first_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + has_more: false + last_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + include[]: message.output_text.logprobs + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + role: user + status: completed + type: message + first_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + has_more: false + last_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + limit: '-1' + response: + body: + error: + code: integer_below_min_value + message: 'Invalid ''limit'': integer below minimum value. Expected a value + >= 0, but got -1 instead.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + limit: ten + response: + body: + error: + code: invalid_type + message: 'Invalid type for ''limit'': expected an integer, but got a string + value that could not be converted into an integer.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + after: '' + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + role: user + status: completed + type: message + first_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + has_more: false + last_id: msg_0865073d0b1881b3006adb03c8b52c87d1a5125622cced8484 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + include[]: not.a.valid.include + response: + body: + error: + code: invalid_value + message: 'Invalid value: ''not.a.valid.include''. Supported values are: ''file_search_call.results'', + ''web_search_call.results'', ''web_search_call.action.sources'', ''message.input_image.image_url'', + ''computer_call_output.output.image_url'', ''code_interpreter_call.outputs'', + ''reasoning.encrypted_content'', and ''message.output_text.logprobs''.' + param: include[0] + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + limit: + - '1' + - '2' + response: + body: + error: + code: duplicate_parameter + message: 'Duplicate parameter: ''limit''. You provided multiple values for + this parameter, whereas only one is allowed. If you are trying to provide + a list of values, use the array syntax instead e.g. ''limit[]=''.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t21 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc/input_items + query_params: + include[]: + - message.output_text.logprobs + - reasoning.encrypted_content + response: + body: + error: + code: null + message: Encrypted content cannot be requested for persisted responses. + param: include + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc + query_params: {} + response: + body: + deleted: true + id: resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0865073d0b1881b3006adb03c8ab5c87d1958ea23d891129bc' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-openai.yaml new file mode 100644 index 000000000..632d577ab --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-openai.yaml @@ -0,0 +1,727 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: false + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df","object":"response","created_at":1792738258,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df","object":"response","created_at":1792738258,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"S","item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","logprobs":[],"obfuscation":"ZqY080edNefV9Ap","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"APP","item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","logprobs":[],"obfuscation":"dASGF7TgR0MJx","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"H","item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","logprobs":[],"obfuscation":"gYcxcjmeYJQykQk","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IRE","item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","logprobs":[],"obfuscation":"H9IA6IdRXxi0A","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","logprobs":[],"output_index":0,"sequence_number":8,"text":"SAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"},"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"},"output_index":0,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df","object":"response","created_at":1792738258,"status":"completed","access_programs":null,"background":false,"completed_at":1792738258,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f47bfd0e0317e53016adb03d26a9487d18af503cbbdcc2be9","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":15,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":20},"user":null,"metadata":{}},"sequence_number":11} + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f47bfd0e0317e53016adb03d1f8a487d1b2315c640bd630df' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t8 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_missing + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: previous_response_not_found + message: Previous response with id 'resp_missing' not found. + param: previous_response_id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370","object":"response","created_at":1792738262,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370","object":"response","created_at":1792738262,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"OR","item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","logprobs":[],"obfuscation":"78iBqzP21ijgYY","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"CH","item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","logprobs":[],"obfuscation":"a8t1XHfBMermN9","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"ID","item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","logprobs":[],"obfuscation":"0qPssj6fzozoaX","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","logprobs":[],"output_index":0,"sequence_number":7,"text":"ORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"},"output_index":0,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370","object":"response","created_at":1792738262,"status":"completed","access_programs":null,"background":false,"completed_at":1792738262,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0a0ecb550dc1fa76006adb03d64a6c87d18b643ed9fbc42324","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":14,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":18},"user":null,"metadata":{}},"sequence_number":10} + + ' + - ' + + ' + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + limit: '0' + response: + body: + data: [] + first_id: null + has_more: true + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + limit: '101' + response: + body: + error: + code: integer_above_max_value + message: 'Invalid ''limit'': integer above maximum value. Expected a value + <= 100, but got 101 instead.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + order: sideways + response: + body: + error: + code: invalid_value + message: 'Invalid value: ''sideways''. Supported values are: ''asc'' and ''desc''.' + param: order + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + after: msg_missing + response: + body: + error: + code: null + message: No item found with id 'msg_missing' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + include[]: web_search_call.results + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + role: user + status: completed + type: message + first_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + has_more: false + last_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + include[]: message.output_text.logprobs + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + role: user + status: completed + type: message + first_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + has_more: false + last_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + limit: '-1' + response: + body: + error: + code: integer_below_min_value + message: 'Invalid ''limit'': integer below minimum value. Expected a value + >= 0, but got -1 instead.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + limit: ten + response: + body: + error: + code: invalid_type + message: 'Invalid type for ''limit'': expected an integer, but got a string + value that could not be converted into an integer.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + after: '' + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + role: user + status: completed + type: message + first_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + has_more: false + last_id: msg_0a0ecb550dc1fa76006adb03d6031487d1b0f57772ac44e6c9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + include[]: not.a.valid.include + response: + body: + error: + code: invalid_value + message: 'Invalid value: ''not.a.valid.include''. Supported values are: ''file_search_call.results'', + ''web_search_call.results'', ''web_search_call.action.sources'', ''message.input_image.image_url'', + ''computer_call_output.output.image_url'', ''code_interpreter_call.outputs'', + ''reasoning.encrypted_content'', and ''message.output_text.logprobs''.' + param: include[0] + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + limit: + - '1' + - '2' + response: + body: + error: + code: duplicate_parameter + message: 'Duplicate parameter: ''limit''. You provided multiple values for + this parameter, whereas only one is allowed. If you are trying to provide + a list of values, use the array syntax instead e.g. ''limit[]=''.' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t21 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370/input_items + query_params: + include[]: + - message.output_text.logprobs + - reasoning.encrypted_content + response: + body: + error: + code: null + message: Encrypted content cannot be requested for persisted responses. + param: include + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370 + query_params: {} + response: + body: + deleted: true + id: resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370 + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0a0ecb550dc1fa76006adb03d5f93087d19aea460bcbf00370' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-openai.yaml new file mode 100644 index 000000000..8b189136d --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-openai.yaml @@ -0,0 +1,829 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744697 + created_at: 1792744696 + error: null + frequency_penalty: 0.0 + id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cf8f88c87d0b4120e37112d37c1 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 6 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 21 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + role: user + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + has_more: false + last_id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744698 + created_at: 1792744698 + error: null + frequency_penalty: 0.0 + id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cfa945887d0954d2950bdf34b56 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 35 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 40 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cf8f88c87d0b4120e37112d37c1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + role: user + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + has_more: false + last_id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + role: user + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + has_more: true + last_id: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec/input_items + query_params: + after: msg_03ec669b52bd3e6f006adb1cf81f4487d0b148e69357f5467a + limit: '1' + order: asc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cf8f88c87d0b4120e37112d37c1 + role: assistant + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cf8f88c87d0b4120e37112d37c1 + has_more: true + last_id: msg_03ec669b52bd3e6f006adb1cf8f88c87d0b4120e37112d37c1 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744698 + created_at: 1792744698 + error: null + frequency_penalty: 0.0 + id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cfa945887d0954d2950bdf34b56 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 35 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 40 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + query_params: {} + response: + body: + deleted: true + id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + role: user + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + has_more: false + last_id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744698 + created_at: 1792744698 + error: null + frequency_penalty: 0.0 + id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cfa945887d0954d2950bdf34b56 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 35 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 40 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901 + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_03ec669b52bd3e6f006adb1cf7dbd487d0a9005513573ff901' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: + input: + - content: What exact word did the first user message ask you to reply with? + Reply with only that word. + role: user + model: gpt-4.1 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744703 + created_at: 1792744703 + error: null + frequency_penalty: 0.0 + id: resp_03ec669b52bd3e6f006adb1cff6ab487d0a6c520cdef13e27a + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_03ec669b52bd3e6f006adb1cffbf4487d0addfc60124ff76f6 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 46 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 50 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cff6ab487d0a6c520cdef13e27a/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID. + type: output_text + id: msg_03ec669b52bd3e6f006adb1cfa945887d0954d2950bdf34b56 + role: assistant + status: completed + type: message + - content: + - text: What exact word did the first user message ask you to reply with? + Reply with only that word. + type: input_text + id: msg_03ec669b52bd3e6f006adb1cff76bc87d0ab21d63dec836f8e + role: user + status: completed + type: message + first_id: msg_03ec669b52bd3e6f006adb1cfa095487d08e7f1d2d8717d6ac + has_more: false + last_id: msg_03ec669b52bd3e6f006adb1cff76bc87d0ab21d63dec836f8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cff6ab487d0a6c520cdef13e27a + query_params: {} + response: + body: + deleted: true + id: resp_03ec669b52bd3e6f006adb1cff6ab487d0a6c520cdef13e27a + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + query_params: {} + response: + body: + deleted: true + id: resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_03ec669b52bd3e6f006adb1cf9ff1087d08e8ec4c1d27292ec' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-openai.yaml new file mode 100644 index 000000000..f47d2a615 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-openai.yaml @@ -0,0 +1,916 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","object":"response","created_at":1792744708,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","object":"response","created_at":1792744708,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"S","item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","logprobs":[],"obfuscation":"byqqdV1zH6DQNnR","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"APP","item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","logprobs":[],"obfuscation":"fpKjXxn6OAeDZ","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"H","item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","logprobs":[],"obfuscation":"bm7vkTowiMR4i6a","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IRE","item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","logprobs":[],"obfuscation":"sA8lJo5PKn2vV","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","logprobs":[],"output_index":0,"sequence_number":8,"text":"SAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"},"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"},"output_index":0,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","object":"response","created_at":1792744708,"status":"completed","access_programs":null,"background":false,"completed_at":1792744708,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":15,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":20},"user":null,"metadata":{}},"sequence_number":11} + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + role: user + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + has_more: false + last_id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","object":"response","created_at":1792744709,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","object":"response","created_at":1792744709,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"OR","item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","logprobs":[],"obfuscation":"VIXvIHClXXxQoh","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"CH","item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","logprobs":[],"obfuscation":"4ICuUvySCXkL6G","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"ID","item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","logprobs":[],"obfuscation":"c20DjKACTcMP8i","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","logprobs":[],"output_index":0,"sequence_number":7,"text":"ORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"},"output_index":0,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","object":"response","created_at":1792744709,"status":"completed","access_programs":null,"background":false,"completed_at":1792744710,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":34,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":38},"user":null,"metadata":{}},"sequence_number":10} + + ' + - ' + + ' + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + role: user + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + has_more: false + last_id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + role: user + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + has_more: true + last_id: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890/input_items + query_params: + after: msg_0f7f14a9fb88d556006adb1d043eac87d093aaa8468c4e0d31 + limit: '1' + order: asc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b + role: assistant + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b + has_more: true + last_id: msg_0f7f14a9fb88d556006adb1d04ef1c87d0b30cc1c9e836ae1b + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744710 + created_at: 1792744709 + error: null + frequency_penalty: 0.0 + id: resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 34 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 38 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + query_params: {} + response: + body: + deleted: true + id: resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + role: user + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + has_more: false + last_id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792744710 + created_at: 1792744709 + error: null + frequency_penalty: 0.0 + id: resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 34 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 38 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f7f14a9fb88d556006adb1d0437f087d0a8522e2bac32031a' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: + input: + - content: What exact word did the first user message ask you to reply with? + Reply with only that word. + role: user + model: gpt-4.1 + previous_response_id: resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01","object":"response","created_at":1792744715,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01","object":"response","created_at":1792744715,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"OR","item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","logprobs":[],"obfuscation":"YWiKUTUJ6iyQ9U","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"CH","item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","logprobs":[],"obfuscation":"CTkgJw813ZOrUv","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"ID","item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","logprobs":[],"obfuscation":"zuHfBakyJbZLZ6","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","logprobs":[],"output_index":0,"sequence_number":7,"text":"ORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"},"output_index":0,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01","object":"response","created_at":1792744715,"status":"completed","access_programs":null,"background":false,"completed_at":1792744716,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0f7f14a9fb88d556006adb1d0c014c87d0986d7759e3e3726e","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":45,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":49},"user":null,"metadata":{}},"sequence_number":10} + + ' + - ' + + ' + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_0f7f14a9fb88d556006adb1d06c60887d09899a87dad211585 + role: assistant + status: completed + type: message + - content: + - text: What exact word did the first user message ask you to reply with? + Reply with only that word. + type: input_text + id: msg_0f7f14a9fb88d556006adb1d0ba41487d08486b01cc6dc73c0 + role: user + status: completed + type: message + first_id: msg_0f7f14a9fb88d556006adb1d05dc9487d0a9eb9c2fdc5163a2 + has_more: false + last_id: msg_0f7f14a9fb88d556006adb1d0ba41487d08486b01cc6dc73c0 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01 + query_params: {} + response: + body: + deleted: true + id: resp_0f7f14a9fb88d556006adb1d0b9c2087d084d208f6061a1f01 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + query_params: {} + response: + body: + deleted: true + id: resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890/input_items + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890 + query_params: {} + response: + body: + error: + code: null + message: Response with id 'resp_0f7f14a9fb88d556006adb1d05cddc87d0999cee73f7651890' + not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-pagination-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-openai.yaml new file mode 100644 index 000000000..daca9d673 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-openai.yaml @@ -0,0 +1,722 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738222 + created_at: 1792738221 + error: null + frequency_penalty: 0.0 + id: resp_0095165ba269388d006adb03ad4fdc87d1baca8e5a7332a895 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 20 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_0095165ba269388d006adb03ad4fdc87d1baca8e5a7332a895 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738223 + created_at: 1792738222 + error: null + frequency_penalty: 0.0 + id: resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: ORCHID + type: output_text + id: msg_0095165ba269388d006adb03af3f0487d1a39936ac86f05b2b + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_0095165ba269388d006adb03ad4fdc87d1baca8e5a7332a895 + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 34 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 38 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + has_more: false + last_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + has_more: false + last_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + has_more: true + last_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + after: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + limit: '1' + order: asc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + first_id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + has_more: true + last_id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + limit: '1' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + has_more: true + last_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + after: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + limit: '1' + order: desc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + first_id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + has_more: true + last_id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + has_more: false + last_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + has_more: false + last_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + after: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + order: asc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + limit: '100' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0095165ba269388d006adb03ae0d9887d19c099d280a40e1e1 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + role: user + status: completed + type: message + first_id: msg_0095165ba269388d006adb03aea00087d1913d32d66f0d9126 + has_more: false + last_id: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + after: msg_0095165ba269388d006adb03ad574487d1b0f7cef0c9d24564 + order: desc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: + input: + - content: Reply with only VIOLET. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738229 + created_at: 1792738229 + error: null + frequency_penalty: 0.0 + id: resp_0b2c1b8500100c1c006adb03b51fc087d1a7322a0675a93dcc + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: VIOLET + type: output_text + id: msg_0b2c1b8500100c1c006adb03b57f5087d19f594e7097d821e2 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 14 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 4 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 18 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0b2c1b8500100c1c006adb03b51fc087d1a7322a0675a93dcc/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only VIOLET. + type: input_text + id: msg_0b2c1b8500100c1c006adb03b52cfc87d1b1279cc883cd4075 + role: user + status: completed + type: message + first_id: msg_0b2c1b8500100c1c006adb03b52cfc87d1b1279cc883cd4075 + has_more: false + last_id: msg_0b2c1b8500100c1c006adb03b52cfc87d1b1279cc883cd4075 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0095165ba269388d006adb03ae8eb887d199b2f01d6ee3471f/input_items + query_params: + after: msg_0b2c1b8500100c1c006adb03b52cfc87d1b1279cc883cd4075 + response: + body: + error: + code: null + message: No item found with id 'msg_0b2c1b8500100c1c006adb03b52cfc87d1b1279cc883cd4075' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-openai.yaml new file mode 100644 index 000000000..c16574a18 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-openai.yaml @@ -0,0 +1,809 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","object":"response","created_at":1792738232,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","object":"response","created_at":1792738232,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"S","item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","logprobs":[],"obfuscation":"fL0BugJFlxfAM13","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"APP","item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","logprobs":[],"obfuscation":"MmDLzl28h65SA","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"H","item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","logprobs":[],"obfuscation":"stgyzm7cVzB2XAx","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IRE","item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","logprobs":[],"obfuscation":"InOUGNwxNNBBA","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","logprobs":[],"output_index":0,"sequence_number":8,"text":"SAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"},"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"},"output_index":0,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","object":"response","created_at":1792738232,"status":"completed","access_programs":null,"background":false,"completed_at":1792738233,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":15,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":20},"user":null,"metadata":{}},"sequence_number":11} + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: gpt-4.1 + previous_response_id: resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399","object":"response","created_at":1792738233,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399","object":"response","created_at":1792738233,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"OR","item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","logprobs":[],"obfuscation":"jZYteudj06HFKC","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"CH","item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","logprobs":[],"obfuscation":"3pVgiIeuCYIpZh","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"ID","item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","logprobs":[],"obfuscation":"g8Y0OfLVtyIxqI","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","logprobs":[],"output_index":0,"sequence_number":7,"text":"ORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"},"output_index":0,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399","object":"response","created_at":1792738233,"status":"completed","access_programs":null,"background":false,"completed_at":1792738234,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0a200e2f1f716972006adb03ba3a7487d18280bd61ee58fe77","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"ORCHID"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":"resp_0a200e2f1f716972006adb03b8839887d1af0aa02050a9343c","prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":34,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":38},"user":null,"metadata":{}},"sequence_number":10} + + ' + - ' + + ' + status_code: 200 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + has_more: false + last_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + has_more: false + last_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + has_more: true + last_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + after: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + limit: '1' + order: asc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + has_more: true + last_id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + limit: '1' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + has_more: true + last_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + after: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + limit: '1' + order: desc + response: + body: + data: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + has_more: true + last_id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + has_more: false + last_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + has_more: false + last_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + after: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + order: asc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + limit: '100' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0a200e2f1f716972006adb03b9066487d1bb41d02dbd938416 + role: assistant + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + role: user + status: completed + type: message + first_id: msg_0a200e2f1f716972006adb03b9b13887d1be38ba8a5e9f154f + has_more: false + last_id: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + after: msg_0a200e2f1f716972006adb03b88df887d1b2904e9da6c338a3 + order: desc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: + input: + - content: Reply with only VIOLET. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_071f39d6a547e429006adb03c0f8d487d1bf8a2e29a176c475","object":"response","created_at":1792738241,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_071f39d6a547e429006adb03c0f8d487d1bf8a2e29a176c475","object":"response","created_at":1792738241,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"V","item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","logprobs":[],"obfuscation":"Y4GBgztgbDks6O5","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IO","item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","logprobs":[],"obfuscation":"uIzugyqt1zVyWr","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"LET","item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","logprobs":[],"obfuscation":"X4WegZ1gLBTBO","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","logprobs":[],"output_index":0,"sequence_number":7,"text":"VIOLET"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"VIOLET"},"sequence_number":8} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"VIOLET"}],"role":"assistant"},"output_index":0,"sequence_number":9} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_071f39d6a547e429006adb03c0f8d487d1bf8a2e29a176c475","object":"response","created_at":1792738241,"status":"completed","access_programs":null,"background":false,"completed_at":1792738241,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_071f39d6a547e429006adb03c18fd087d18506739d3702d1aa","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"VIOLET"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":14,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":4,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":18},"user":null,"metadata":{}},"sequence_number":10} + + ' + - ' + + ' + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_071f39d6a547e429006adb03c0f8d487d1bf8a2e29a176c475/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only VIOLET. + type: input_text + id: msg_071f39d6a547e429006adb03c1010487d1a299303a6dc6ea55 + role: user + status: completed + type: message + first_id: msg_071f39d6a547e429006adb03c1010487d1a299303a6dc6ea55 + has_more: false + last_id: msg_071f39d6a547e429006adb03c1010487d1a299303a6dc6ea55 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0a200e2f1f716972006adb03b9a8c487d19acc32ff2238c399/input_items + query_params: + after: msg_071f39d6a547e429006adb03c1010487d1a299303a6dc6ea55 + response: + body: + error: + code: null + message: No item found with id 'msg_071f39d6a547e429006adb03c1010487d1a299303a6dc6ea55' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-openai.yaml new file mode 100644 index 000000000..ead901e85 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-openai.yaml @@ -0,0 +1,622 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738307 + created_at: 1792738306 + error: null + frequency_penalty: 0.0 + id: resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0d0ddf1849dc41d3006adb04032c1887d1ac3ab98ee608ec78 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 20 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738308 + created_at: 1792738308 + error: null + frequency_penalty: 0.0 + id: resp_0d0ddf1849dc41d3006adb04043eb487d1a24119a6ddfe941f + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE. + type: output_text + id: msg_0d0ddf1849dc41d3006adb0404e0d487d191e40d604fc5c9de + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 6 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 21 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb04043eb487d1a24119a6ddfe941f/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + type: message + - content: Reply with only VIOLET. + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: null + message: Duplicate item found with id msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c. + Remove duplicate items from your input and try again. + param: input + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + id: item_wrongprefix_1ee813b549f151e7 + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: invalid_value + message: 'Invalid ''input[0].id'': ''item_wrongprefix_1ee813b549f151e7''. + Expected an ID that begins with ''msg''.' + param: input[0].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: + input: + - arguments: '{}' + call_id: call_history_probe + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: Reply with only the lookup result. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738313 + created_at: 1792738313 + error: null + frequency_penalty: 0.0 + id: resp_03872222178b552a006adb04095b6c87d1bca5ff94e6a26f88 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_03872222178b552a006adb0409ae2887d1abd9cdfec30b3b7d + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 32 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 37 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_03872222178b552a006adb04095b6c87d1bca5ff94e6a26f88/input_items + query_params: + order: asc + response: + body: + data: + - arguments: '{}' + call_id: call_history_probe + id: fc_03872222178b552a006adb0409621087d1ad182bf8c2d3d7ec + name: lookup + status: in_progress + type: function_call + - call_id: call_history_probe + id: fc_03872222178b552a006adb0409622c87d1a56cb8e7d8ed31f7 + output: SAPPHIRE + status: completed + type: function_call_output + - content: + - text: Reply with only the lookup result. + type: input_text + id: msg_03872222178b552a006adb0409624487d1bcf6af6e89f7851d + role: user + status: completed + type: message + first_id: fc_03872222178b552a006adb0409621087d1ad182bf8c2d3d7ec + has_more: false + last_id: msg_03872222178b552a006adb0409624487d1bcf6af6e89f7851d + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d0ddf1849dc41d3006adb0402ae7c87d19358aa976725cab0/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + role: user + status: completed + type: message + first_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + has_more: false + last_id: msg_0d0ddf1849dc41d3006adb0402b89c87d18d5ce807b0ef4e7c + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-openai.yaml new file mode 100644 index 000000000..88549e2f8 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-openai.yaml @@ -0,0 +1,657 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: gpt-4.1 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","response":{"id":"resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065","object":"response","created_at":1792738317,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","response":{"id":"resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065","object":"response","created_at":1792738317,"status":"in_progress","access_programs":null,"background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","item":{"id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":0,"sequence_number":2} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"S","item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","logprobs":[],"obfuscation":"jDUVS0PoO3xRaqe","output_index":0,"sequence_number":4} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"APP","item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","logprobs":[],"obfuscation":"4AYenFhaKfbhZ","output_index":0,"sequence_number":5} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"H","item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","logprobs":[],"obfuscation":"kHedfu7aXYcdNzr","output_index":0,"sequence_number":6} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","content_index":0,"delta":"IRE","item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","logprobs":[],"obfuscation":"KpMbzksiWxTXX","output_index":0,"sequence_number":7} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","logprobs":[],"output_index":0,"sequence_number":8,"text":"SAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"},"sequence_number":9} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","item":{"id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"},"output_index":0,"sequence_number":10} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","response":{"id":"resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065","object":"response","created_at":1792738317,"status":"completed","access_programs":null,"background":false,"completed_at":1792738317,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"msg_0e216db7dd114c74006adb040d86bc87d185232eff15c99472","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"SAPPHIRE"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":true,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":15,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":20},"user":null,"metadata":{}},"sequence_number":11} + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738319 + created_at: 1792738319 + error: null + frequency_penalty: 0.0 + id: resp_0e216db7dd114c74006adb040efc3087d1b2dbe55b54238b63 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0e216db7dd114c74006adb040f7c8087d18261391f23edd41b + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 15 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 20 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040efc3087d1b2dbe55b54238b63/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + type: message + - content: Reply with only VIOLET. + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: null + message: Duplicate item found with id msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28. + Remove duplicate items from your input and try again. + param: input + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + id: item_wrongprefix_ac26da7b8747efee + role: user + type: message + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: invalid_value + message: 'Invalid ''input[0].id'': ''item_wrongprefix_ac26da7b8747efee''. + Expected an ID that begins with ''msg''.' + param: input[0].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: + input: + - arguments: '{}' + call_id: call_history_probe + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: Reply with only the lookup result. + role: user + model: gpt-4.1 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792738324 + created_at: 1792738323 + error: null + frequency_penalty: 0.0 + id: resp_0e8ee067a46e72d3006adb0413a50487d1ab0d47db75b969bd + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-2025-04-14 + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: SAPPHIRE + type: output_text + id: msg_0e8ee067a46e72d3006adb0413f50c87d194f7ff9dc9d709e7 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: in_memory + reasoning: + context: null + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 32 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 37 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e8ee067a46e72d3006adb0413a50487d1ab0d47db75b969bd/input_items + query_params: + order: asc + response: + body: + data: + - arguments: '{}' + call_id: call_history_probe + id: fc_0e8ee067a46e72d3006adb0413ab7487d185e4c6e198cb70a2 + name: lookup + status: in_progress + type: function_call + - call_id: call_history_probe + id: fc_0e8ee067a46e72d3006adb0413ab8c87d1a6c69ccff84848f3 + output: SAPPHIRE + status: completed + type: function_call_output + - content: + - text: Reply with only the lookup result. + type: input_text + id: msg_0e8ee067a46e72d3006adb0413aba087d19b2ecef0c4a8f7d8 + role: user + status: completed + type: message + first_id: fc_0e8ee067a46e72d3006adb0413ab7487d185e4c6e198cb70a2 + has_more: false + last_id: msg_0e8ee067a46e72d3006adb0413aba087d19b2ecef0c4a8f7d8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e216db7dd114c74006adb040d012887d191fcf7e9076c5065/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + role: user + status: completed + type: message + first_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + has_more: false + last_id: msg_0e216db7dd114c74006adb040d115087d1a56c09556fcd9c28 + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml new file mode 100644 index 000000000..0d61a5416 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml @@ -0,0 +1,3739 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + role: user + - content: [] + encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748959 + created_at: 1792748957 + error: null + frequency_penalty: 0.0 + id: resp_05b26e957aa7a7d7006adb2d9d970c81998accadbe8276534d + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_05b26e957aa7a7d7006adb2d9f6e908199a976fea1705f7adf + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 4236 + input_tokens_details: + cache_write_tokens: 4233 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 4241 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_05b26e957aa7a7d7006adb2d9d970c81998accadbe8276534d/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + type: input_text + id: msg_05b26e957aa7a7d7006adb2d9dacb48199a4d8ce6109eb9c66 + role: user + status: completed + type: message + - content: [] + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_05b26e957aa7a7d7006adb2d9dacf4819981704ba1d4fb0546 + role: user + status: completed + type: message + first_id: msg_05b26e957aa7a7d7006adb2d9dacb48199a4d8ce6109eb9c66 + has_more: false + last_id: msg_05b26e957aa7a7d7006adb2d9dacf4819981704ba1d4fb0546 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + role: user + - content: [] + encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748962 + created_at: 1792748960 + error: null + frequency_penalty: 0.0 + id: resp_05b26e957aa7a7d7006adb2da053808199b740ff50c3547bf6 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_05b26e957aa7a7d7006adb2da25bb08199b684516b0411186b + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 171 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 176 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_05b26e957aa7a7d7006adb2da053808199b740ff50c3547bf6/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da064748199869ee22f22949892 + role: user + status: completed + type: message + - content: [] + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006adb2da064b481998ca513fb543f498d + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da064d48199a2637188cc9a6538 + role: user + status: completed + type: message + first_id: msg_05b26e957aa7a7d7006adb2da064748199869ee22f22949892 + has_more: false + last_id: msg_05b26e957aa7a7d7006adb2da064d48199a2637188cc9a6538 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + input: + - content: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + role: user + - content: [] + encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748963 + created_at: 1792748963 + error: null + frequency_penalty: 0.0 + id: resp_05b26e957aa7a7d7006adb2da34de08199856a35d7aa499a1b + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_05b26e957aa7a7d7006adb2da3dd5c8199bb8b1add72e05e63 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 4236 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 4233 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 4241 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_05b26e957aa7a7d7006adb2da34de08199856a35d7aa499a1b/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da359588199a8eac6505d8c2b31 + role: user + status: completed + type: message + - content: [] + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da3598c8199a352ec754ed716e8 + role: user + status: completed + type: message + first_id: msg_05b26e957aa7a7d7006adb2da359588199a8eac6505d8c2b31 + has_more: false + last_id: msg_05b26e957aa7a7d7006adb2da3598c8199a352ec754ed716e8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + input: + - content: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + role: user + - content: [] + encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748966 + created_at: 1792748965 + error: null + frequency_penalty: 0.0 + id: resp_05b26e957aa7a7d7006adb2da4f3e08199b8b47b97e8b424f2 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_05b26e957aa7a7d7006adb2da69ec48199b82ee584a0e15613 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 171 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 176 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_05b26e957aa7a7d7006adb2da4f3e08199b8b47b97e8b424f2/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use web search to search for the exact query "potato", then summarize + the result in one sentence. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da514908199bb562e550dee91d4 + role: user + status: completed + type: message + - content: [] + id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + summary: [] + type: reasoning + - action: + queries: + - potato + query: potato + type: search + id: ws_05b26e957aa7a7d7006adb2da514c88199933a174038b40e25 + status: completed + type: web_search_call + - content: + - annotations: + - end_index: 265 + start_index: 173 + title: POTATO Definition & Meaning - Merriam-Webster + type: url_citation + url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + logprobs: [] + text: Search results for **“potato”** primarily define it as an edible, + starchy underground tuber, alongside cooking guides, recipes, agricultural + resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + type: output_text + id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_05b26e957aa7a7d7006adb2da514ec8199acfefda77727580e + role: user + status: completed + type: message + first_id: msg_05b26e957aa7a7d7006adb2da514908199bb562e550dee91d4 + has_more: false + last_id: msg_05b26e957aa7a7d7006adb2da514ec8199acfefda77727580e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: + - content: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + role: user + - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748968 + created_at: 1792748967 + error: null + frequency_penalty: 0.0 + id: resp_0e3fe24990ca0d3e006adb2da7df58819b990cf42bfab87be1 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0e3fe24990ca0d3e006adb2da8c144819b8360636febb90b2b + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 83 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 88 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e3fe24990ca0d3e006adb2da7df58819b990cf42bfab87be1/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2da7f954819ba2743cd71418c47d + role: user + status: completed + type: message + - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2da7f9a0819b9be58b3eacf1bc65 + role: user + status: completed + type: message + first_id: msg_0e3fe24990ca0d3e006adb2da7f954819ba2743cd71418c47d + has_more: false + last_id: msg_0e3fe24990ca0d3e006adb2da7f9a0819b9be58b3eacf1bc65 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: + input: + - content: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + role: user + - server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748970 + created_at: 1792748970 + error: null + frequency_penalty: 0.0 + id: resp_0e3fe24990ca0d3e006adb2da9de74819b9ef63ece99c3d3db + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0e3fe24990ca0d3e006adb2daa9ce4819b88d72e9aaab847b3 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 63 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 68 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e3fe24990ca0d3e006adb2da9de74819b9ef63ece99c3d3db/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2da9fcdc819bbb9f2cce171f5dfd + role: user + status: completed + type: message + - id: mcpl_0e3fe24990ca0d3e006adb2da9fcfc819b855f39c543a9783d + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006adb2da9fd14819bb77d3fa6f284c22d + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2daa007c819b9c0d4284e94e36bd + role: user + status: completed + type: message + first_id: msg_0e3fe24990ca0d3e006adb2da9fcdc819bbb9f2cce171f5dfd + has_more: false + last_id: msg_0e3fe24990ca0d3e006adb2daa007c819b9c0d4284e94e36bd + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: + input: + - content: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + role: user + - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a + name: say_hello + output: hello + server_label: counter + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748972 + created_at: 1792748971 + error: null + frequency_penalty: 0.0 + id: resp_0e3fe24990ca0d3e006adb2dabd1c8819bb1f382c542666508 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0e3fe24990ca0d3e006adb2dac9304819b8dffeddfac44ab36 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 83 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 88 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e3fe24990ca0d3e006adb2dabd1c8819bb1f382c542666508/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2dabe320819bb59a1cae672a60eb + role: user + status: completed + type: message + - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2dabe364819bb1fecc1c05711c88 + role: user + status: completed + type: message + first_id: msg_0e3fe24990ca0d3e006adb2dabe320819bb59a1cae672a60eb + has_more: false + last_id: msg_0e3fe24990ca0d3e006adb2dabe364819bb1fecc1c05711c88 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: + input: + - content: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + role: user + - server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + name: say_hello + output: hello + server_label: counter + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748974 + created_at: 1792748973 + error: null + frequency_penalty: 0.0 + id: resp_0e3fe24990ca0d3e006adb2dadbfa4819b858da14bdd89b375 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0e3fe24990ca0d3e006adb2daea174819bb33ffb88a04df474 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 63 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 68 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e3fe24990ca0d3e006adb2dadbfa4819b858da14bdd89b375/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Call counter__say_hello exactly once with {}. Do not call any other + tool. Return exactly MCP_SAYS=hello. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2dadd7f4819b8d15e120b8a6629e + role: user + status: completed + type: message + - id: mcpl_0e3fe24990ca0d3e006adb2dadd810819baffca9debc56b778 + server_label: counter + tools: + - annotations: + read_only: false + description: Say hello to the client + input_schema: + properties: {} + type: object + name: say_hello + type: mcp_list_tools + - approval_request_id: null + arguments: '{}' + error: null + id: mcp_0e3fe24990ca0d3e006adb2dadd854819b9340cdf6b97c764e + name: say_hello + output: hello + server_label: counter + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: MCP_SAYS=hello + type: output_text + id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0e3fe24990ca0d3e006adb2dadd87c819bab391e666aa99d02 + role: user + status: completed + type: message + first_id: msg_0e3fe24990ca0d3e006adb2dadd7f4819b8d15e120b8a6629e + has_more: false + last_id: msg_0e3fe24990ca0d3e006adb2dadd87c819bab391e666aa99d02 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: + input: + - content: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + role: user + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + id: ci_020ffaaa85f1ecf6016ab617b64c4c87d1b4d893d7936f5244 + outputs: null + status: completed + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748976 + created_at: 1792748975 + error: null + frequency_penalty: 0.0 + id: resp_020ffaaa85f1ecf6006adb2dafe05487d183b6c417597c17cd + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_020ffaaa85f1ecf6006adb2db09aa487d199d76c9b12a69006 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 126 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 131 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_020ffaaa85f1ecf6006adb2dafe05487d183b6c417597c17cd/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + type: input_text + id: msg_020ffaaa85f1ecf6006adb2dafea6487d18f06d0d363a8f9fe + role: user + status: completed + type: message + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + id: ci_020ffaaa85f1ecf6006adb2dafeaa487d19557cc928c7f8f26 + outputs: null + status: completed + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6006adb2dafeaf887d1bb8342a6268df5df + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_020ffaaa85f1ecf6006adb2dafead887d18a40330286c9b6aa + role: user + status: completed + type: message + first_id: msg_020ffaaa85f1ecf6006adb2dafea6487d18f06d0d363a8f9fe + has_more: false + last_id: msg_020ffaaa85f1ecf6006adb2dafead887d18a40330286c9b6aa + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: + input: + - content: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + role: user + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + outputs: null + status: completed + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: missing_required_parameter + message: 'Missing required parameter: ''input[1].id''.' + param: input[1].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: + limit: '100' + order: asc + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t21 + request: + body: + input: + - content: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + role: user + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + id: ci_020ffaaa85f1ecf6016ab617b64c4c87d1b4d893d7936f5244 + outputs: null + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748980 + created_at: 1792748979 + error: null + frequency_penalty: 0.0 + id: resp_020ffaaa85f1ecf6006adb2db3388887d1bc850fef29be776d + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_020ffaaa85f1ecf6006adb2db4149087d197835deda97e68b0 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 126 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 131 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_020ffaaa85f1ecf6006adb2db3388887d1bc850fef29be776d/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + type: input_text + id: msg_020ffaaa85f1ecf6006adb2db3421087d1a784151b5a023421 + role: user + status: completed + type: message + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + id: ci_020ffaaa85f1ecf6006adb2db3424887d1bbbbd8b6c686b4b2 + outputs: null + status: completed + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6006adb2db3429c87d1b55ceab0e354d62d + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_020ffaaa85f1ecf6006adb2db3428087d1b9c1c7cd003b1f28 + role: user + status: completed + type: message + first_id: msg_020ffaaa85f1ecf6006adb2db3421087d1a784151b5a023421 + has_more: false + last_id: msg_020ffaaa85f1ecf6006adb2db3428087d1b9c1c7cd003b1f28 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: + input: + - content: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + role: user + - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + + ' + container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 + outputs: null + type: code_interpreter_call + - content: + - annotations: [] + logprobs: [] + text: CODE_INTERPRETER_OK=385 + type: output_text + id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: missing_required_parameter + message: 'Missing required parameter: ''input[1].id''.' + param: input[1].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t24 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: + limit: '100' + order: asc + response: + body: + error: + code: null + message: Response with id 'resp_missing' not found. + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t25 + request: + body: + input: + - content: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + role: user + - content: [] + encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a + status: completed + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + type: shell_call_output + - content: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748983 + created_at: 1792748982 + error: null + frequency_penalty: 0.0 + id: resp_0d2fb655988faaf0006adb2db6bca087d084eb452458cb70fb + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0d2fb655988faaf0006adb2db7532487d0aa7bd5ed3557a630 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 345 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 350 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t26 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d2fb655988faaf0006adb2db6bca087d084eb452458cb70fb/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_0d2fb655988faaf0006adb2db6d17c87d0aa102ef98557cc7d + role: user + status: completed + type: message + - content: [] + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a + status: completed + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + id: sho_0d2fb655988faaf0006adb2db6d1b887d0b624f670294527d6 + max_output_length: null + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_0d2fb655988faaf0006adb2db6d1d087d0ac4c0057b273e0cb + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0d2fb655988faaf0006adb2db6d1f087d0a4593221d350eac5 + role: user + status: completed + type: message + first_id: msg_0d2fb655988faaf0006adb2db6d17c87d0aa102ef98557cc7d + has_more: false + last_id: msg_0d2fb655988faaf0006adb2db6d1f087d0a4593221d350eac5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t27 + request: + body: + input: + - content: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + role: user + - content: [] + encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + status: completed + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + type: shell_call_output + - content: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748985 + created_at: 1792748984 + error: null + frequency_penalty: 0.0 + id: resp_0d2fb655988faaf0006adb2db8ed6c87d0a5066123901cd60f + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0d2fb655988faaf0006adb2db9b01c87d0a3a70577e5c9291e + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 345 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 350 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t28 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d2fb655988faaf0006adb2db8ed6c87d0a5066123901cd60f/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_0d2fb655988faaf0006adb2db8f8c487d086435fe04f3ea615 + role: user + status: completed + type: message + - content: [] + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006adb2db8f90887d0a9f997dc4de5c96a + status: in_progress + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + id: sho_0d2fb655988faaf0006adb2db8f95087d0b2def1f37e948d07 + max_output_length: null + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_0d2fb655988faaf0006adb2db8f96487d0826bdc912180a83c + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0d2fb655988faaf0006adb2db8f97c87d0a5fe376f3ed09861 + role: user + status: completed + type: message + first_id: msg_0d2fb655988faaf0006adb2db8f8c487d086435fe04f3ea615 + has_more: false + last_id: msg_0d2fb655988faaf0006adb2db8f97c87d0a5fe376f3ed09861 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t29 + request: + body: + input: + - content: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + role: user + - content: [] + encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + type: shell_call_output + - content: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748988 + created_at: 1792748987 + error: null + frequency_penalty: 0.0 + id: resp_0d2fb655988faaf0006adb2dbb31cc87d09a6aaecad597ec64 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0d2fb655988faaf0006adb2dbc270c87d08f9bab7037ca8bb1 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 345 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 350 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t30 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d2fb655988faaf0006adb2dbb31cc87d09a6aaecad597ec64/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_0d2fb655988faaf0006adb2dbb4a8487d0a80c3f37865b09de + role: user + status: completed + type: message + - content: [] + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a + status: completed + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + id: sho_0d2fb655988faaf0006adb2dbb4ab887d0a44a88f0a35fabe5 + max_output_length: null + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_0d2fb655988faaf0006adb2dbb4ad087d08262acca50df3e00 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0d2fb655988faaf0006adb2dbb4aec87d085485737deeb5175 + role: user + status: completed + type: message + first_id: msg_0d2fb655988faaf0006adb2dbb4a8487d0a80c3f37865b09de + has_more: false + last_id: msg_0d2fb655988faaf0006adb2dbb4aec87d085485737deeb5175 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t31 + request: + body: + input: + - content: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + role: user + - content: [] + encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + type: shell_call_output + - content: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748990 + created_at: 1792748989 + error: null + frequency_penalty: 0.0 + id: resp_0d2fb655988faaf0006adb2dbdb97487d0b1812a2c7f6ef70e + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0d2fb655988faaf0006adb2dbe7a5c87d09f60c3302658b4dd + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 345 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 350 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t32 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0d2fb655988faaf0006adb2dbdb97487d0b1812a2c7f6ef70e/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_0d2fb655988faaf0006adb2dbdc90487d097906ecab451ffa9 + role: user + status: completed + type: message + - content: [] + id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + environment: null + id: sh_0d2fb655988faaf0006adb2dbdc93c87d08eefa2e05e9b9150 + status: in_progress + type: shell_call + - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + id: sho_0d2fb655988faaf0006adb2dbdc97c87d09985d9f947e1851b + max_output_length: null + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_0d2fb655988faaf0006adb2dbdc99087d081b9be311e6e5598 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" + type: output_text + id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_0d2fb655988faaf0006adb2dbdc9a887d0a5c926b453dafb8e + role: user + status: completed + type: message + first_id: msg_0d2fb655988faaf0006adb2dbdc90487d097906ecab451ffa9 + has_more: false + last_id: msg_0d2fb655988faaf0006adb2dbdc9a887d0a5c926b453dafb8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t33 + request: + body: + input: + - content: You must call the agentic_raw_echo custom tool exactly once with + exactly CUSTOM_CASSETTE_OK as its raw text input. + role: user + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: completed + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + output: CUSTOM_CASSETTE_OUTPUT_OK + type: custom_tool_call_output + - content: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748993 + created_at: 1792748992 + error: null + frequency_penalty: 0.0 + id: resp_01014eb0097f04f3006adb2dc09728819baac1adac0f700482 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_01014eb0097f04f3006adb2dc15744819bbe0cf7a7e091c54f + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 134 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 139 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t34 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01014eb0097f04f3006adb2dc09728819baac1adac0f700482/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: You must call the agentic_raw_echo custom tool exactly once with exactly + CUSTOM_CASSETTE_OK as its raw text input. + type: input_text + id: msg_01014eb0097f04f3006adb2dc0d030819ba646d33b6ee18db3 + role: user + status: completed + type: message + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: completed + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctco_01014eb0097f04f3006adb2dc0d06c819b9406a9592450c766 + output: CUSTOM_CASSETTE_OUTPUT_OK + status: completed + type: custom_tool_call_output + - content: + - text: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc0d088819b9b7ae022bcf4108b + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc0d0ac819bbd7ebfb1307fd2bf + role: user + status: completed + type: message + first_id: msg_01014eb0097f04f3006adb2dc0d030819ba646d33b6ee18db3 + has_more: false + last_id: msg_01014eb0097f04f3006adb2dc0d0ac819bbd7ebfb1307fd2bf + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t35 + request: + body: + input: + - content: You must call the agentic_raw_echo custom tool exactly once with + exactly CUSTOM_CASSETTE_OK as its raw text input. + role: user + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: completed + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + output: CUSTOM_CASSETTE_OUTPUT_OK + type: custom_tool_call_output + - content: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748996 + created_at: 1792748995 + error: null + frequency_penalty: 0.0 + id: resp_01014eb0097f04f3006adb2dc34dc0819b859bc81611f5b439 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_01014eb0097f04f3006adb2dc3f85c819b82e96d0897b24d3b + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 134 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 139 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t36 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01014eb0097f04f3006adb2dc34dc0819b859bc81611f5b439/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: You must call the agentic_raw_echo custom tool exactly once with exactly + CUSTOM_CASSETTE_OK as its raw text input. + type: input_text + id: msg_01014eb0097f04f3006adb2dc369f8819b85b55bffd078e5ca + role: user + status: completed + type: message + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006adb2dc36a14819bb9937b6b4d5ceb4b + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: in_progress + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctco_01014eb0097f04f3006adb2dc36a30819bac7740c6a0ef2c31 + output: CUSTOM_CASSETTE_OUTPUT_OK + status: completed + type: custom_tool_call_output + - content: + - text: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc36a44819bb382685a81d5d24a + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc36a64819bb9fca37be0224385 + role: user + status: completed + type: message + first_id: msg_01014eb0097f04f3006adb2dc369f8819b85b55bffd078e5ca + has_more: false + last_id: msg_01014eb0097f04f3006adb2dc36a64819bb9fca37be0224385 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t37 + request: + body: + input: + - content: You must call the agentic_raw_echo custom tool exactly once with + exactly CUSTOM_CASSETTE_OK as its raw text input. + role: user + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + output: CUSTOM_CASSETTE_OUTPUT_OK + type: custom_tool_call_output + - content: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792748998 + created_at: 1792748998 + error: null + frequency_penalty: 0.0 + id: resp_01014eb0097f04f3006adb2dc62acc819bbcfc572bf84f8e0b + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_01014eb0097f04f3006adb2dc6d8a4819bbfdcbd36d9ea6696 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 134 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 139 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t38 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01014eb0097f04f3006adb2dc62acc819bbcfc572bf84f8e0b/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: You must call the agentic_raw_echo custom tool exactly once with exactly + CUSTOM_CASSETTE_OK as its raw text input. + type: input_text + id: msg_01014eb0097f04f3006adb2dc63ff4819b91d239d7b351104f + role: user + status: completed + type: message + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: completed + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctco_01014eb0097f04f3006adb2dc64020819ba310f92062beec75 + output: CUSTOM_CASSETTE_OUTPUT_OK + status: completed + type: custom_tool_call_output + - content: + - text: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc64038819bbcab940a76439917 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc64060819b8b0f4e0b6122c6f1 + role: user + status: completed + type: message + first_id: msg_01014eb0097f04f3006adb2dc63ff4819b91d239d7b351104f + has_more: false + last_id: msg_01014eb0097f04f3006adb2dc64060819b8b0f4e0b6122c6f1 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t39 + request: + body: + input: + - content: You must call the agentic_raw_echo custom tool exactly once with + exactly CUSTOM_CASSETTE_OK as its raw text input. + role: user + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + output: CUSTOM_CASSETTE_OUTPUT_OK + type: custom_tool_call_output + - content: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + role: user + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: This is completed tool history. Do not call tools. Reply only OK. + role: user + model: gpt-5.6 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792749002 + created_at: 1792749000 + error: null + frequency_penalty: 0.0 + id: resp_01014eb0097f04f3006adb2dc8bda4819b88feec8f807aa201 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_01014eb0097f04f3006adb2dcae238819b83a09efc49b21b6c + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: [] + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 134 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 139 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t40 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01014eb0097f04f3006adb2dc8bda4819b88feec8f807aa201/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: You must call the agentic_raw_echo custom tool exactly once with exactly + CUSTOM_CASSETTE_OK as its raw text input. + type: input_text + id: msg_01014eb0097f04f3006adb2dc8c8c4819bbfcadcb9d1edbc3e + role: user + status: completed + type: message + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctc_01014eb0097f04f3006adb2dc8c8e0819b8f64300ac362aba4 + input: 'CUSTOM_CASSETTE_OK + + ' + name: agentic_raw_echo + status: in_progress + type: custom_tool_call + - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF + id: ctco_01014eb0097f04f3006adb2dc8c8f8819ba473098f6a4b24e9 + output: CUSTOM_CASSETTE_OUTPUT_OK + status: completed + type: custom_tool_call_output + - content: + - text: Use the custom tool output provided above. Do not call any tool again. + Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc8c90c819bae5146fe70453d7e + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: CUSTOM_CASSETTE_OUTPUT_OK + type: output_text + id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: This is completed tool history. Do not call tools. Reply only OK. + type: input_text + id: msg_01014eb0097f04f3006adb2dc8c92c819b82487bf31f975e67 + role: user + status: completed + type: message + first_id: msg_01014eb0097f04f3006adb2dc8c8c4819bbfcadcb9d1edbc3e + has_more: false + last_id: msg_01014eb0097f04f3006adb2dc8c92c819b82487bf31f975e67 + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/conversation_branch_response_test.rs b/crates/agentic-server-core/tests/conversation_branch_response_test.rs index ee3029dd6..fa007750c 100644 --- a/crates/agentic-server-core/tests/conversation_branch_response_test.rs +++ b/crates/agentic-server-core/tests/conversation_branch_response_test.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use agentic_core::executor::ExecuteRequest; use agentic_core::executor::session::ResponseSession; use agentic_core::types::RequestPayload; -use agentic_core::types::conversations::{ItemLimit, ItemOrder, ItemPage}; +use agentic_core::types::io::{ItemOrder, ItemPage}; use either::Either; use futures::StreamExt; use serde_json::Value; @@ -60,6 +60,11 @@ async fn assert_branch_is_detached(streaming: bool) { env!("CARGO_MANIFEST_DIR") )); let turns = responses_turns(&cassette); + let page = ItemPage { + limit: 100.try_into().unwrap(), + after: None, + order: ItemOrder::Asc, + }; // Exercise durable lookup directly and through a transient session. for use_session in [false, true] { for store in [false, true] { @@ -96,7 +101,7 @@ async fn assert_branch_is_detached(streaming: bool) { let before = fixture .exec_ctx .conv_handler - .list_items("default_tenant", &conversation_id, all_ascending()) + .list_items("default_tenant", &conversation_id, page) .await .unwrap(); let parent_id = parent["id"].as_str().unwrap(); @@ -119,7 +124,7 @@ async fn assert_branch_is_detached(streaming: bool) { let after = fixture .exec_ctx .conv_handler - .list_items("default_tenant", &conversation_id, all_ascending()) + .list_items("default_tenant", &conversation_id, page) .await .unwrap(); assert_eq!( @@ -151,11 +156,3 @@ async fn previous_response_branch_omits_conversation_in_json_and_retrieval() { async fn previous_response_branch_omits_conversation_in_every_stream_response() { assert_branch_is_detached(true).await; } - -fn all_ascending() -> ItemPage<'static> { - ItemPage { - limit: ItemLimit::try_from(100).unwrap(), - after: None, - order: ItemOrder::Asc, - } -} diff --git a/crates/agentic-server-core/tests/conversation_storage_regressions.rs b/crates/agentic-server-core/tests/conversation_storage_regressions.rs index 36ed5b99c..c4ea220de 100644 --- a/crates/agentic-server-core/tests/conversation_storage_regressions.rs +++ b/crates/agentic-server-core/tests/conversation_storage_regressions.rs @@ -5,8 +5,8 @@ use std::sync::atomic::{AtomicU64, Ordering}; use agentic_core::storage::models::item as item_model; use agentic_core::storage::{ConversationStore, ResponseMetadata, ResponseStore, create_pool_with_schema}; -use agentic_core::types::conversations::ItemOrder; use agentic_core::types::io::InOutItem; +use agentic_core::types::io::ItemOrder; use serde_json::json; static COUNTER: AtomicU64 = AtomicU64::new(0); @@ -186,14 +186,6 @@ async fn insert_with_ids( ) -> agentic_core::storage::StoreResult<()> { let mut tx = pool.begin().await?; agentic_core::storage::models::conversation::lock_in_tx(&mut tx, id).await?; - let items = items - .into_iter() - .map(|(id, data)| item_model::NewItem { - id, - data, - role: item_model::ItemRole::Input, - }) - .collect(); item_model::create_in_tx(&mut tx, items, Some(id)).await?; tx.commit().await?; Ok(()) diff --git a/crates/agentic-server-core/tests/conversations/edge_replay.rs b/crates/agentic-server-core/tests/conversations/edge_replay.rs index 4356ff012..9d5b24bd3 100644 --- a/crates/agentic-server-core/tests/conversations/edge_replay.rs +++ b/crates/agentic-server-core/tests/conversations/edge_replay.rs @@ -3,10 +3,8 @@ use super::{IdPairs, Turn, Value, cassette, compare_value, remap_error_message}; use agentic_core::executor::{ConversationHandler, ExecutorError}; use agentic_core::storage::{ConversationData, ConversationStore, create_pool_with_schema}; -use agentic_core::types::conversations::{ - ConversationResponse, CreateConversationRequest, CreateItemRequest, ItemLimit, ItemOrder, ItemPage, - ListItemsResponse, -}; +use agentic_core::types::conversations::{ConversationResponse, CreateConversationRequest, CreateItemRequest}; +use agentic_core::types::io::{ItemLimit, ItemOrder, ItemPage, ListItemsResponse}; pub(super) fn remap_ids(value: &Value, ids: &IdPairs) -> Value { match value { diff --git a/crates/agentic-server-core/tests/conversations/item_references.rs b/crates/agentic-server-core/tests/conversations/item_references.rs index 3d1c5d15b..9cff83b15 100644 --- a/crates/agentic-server-core/tests/conversations/item_references.rs +++ b/crates/agentic-server-core/tests/conversations/item_references.rs @@ -2,7 +2,7 @@ use super::{message_with_id, test_store}; use agentic_core::storage::{ResponseMetadata, ResponseStore, StorageError}; -use agentic_core::types::conversations::ItemOrder; +use agentic_core::types::io::ItemOrder; use std::sync::Arc; /// A response history row stores its content under a `item_…` row ID and keeps the public ID in diff --git a/crates/agentic-server-core/tests/conversations_api_cassette_test.rs b/crates/agentic-server-core/tests/conversations_api_cassette_test.rs index 0b21d1c47..6574530c6 100644 --- a/crates/agentic-server-core/tests/conversations_api_cassette_test.rs +++ b/crates/agentic-server-core/tests/conversations_api_cassette_test.rs @@ -927,8 +927,8 @@ async fn existing_conversation_item_error_matches_openai_recording() { async fn deleted_cursor_error_body_matches_openai_recording() { use agentic_core::executor::ExecutorError; use agentic_core::storage::{ConversationStore, create_pool_with_schema}; - use agentic_core::types::conversations::ItemOrder; use agentic_core::types::io::InOutItem; + use agentic_core::types::io::ItemOrder; let reference = cassette("edge-cases", "openai").expect("OpenAI edge-case recording"); let pool = create_pool_with_schema(Some("sqlite::memory:")).await.unwrap(); diff --git a/crates/agentic-server-core/tests/postgres_tenant_isolation_test.rs b/crates/agentic-server-core/tests/postgres_tenant_isolation_test.rs index 5a3947e30..e1fd3fbbc 100644 --- a/crates/agentic-server-core/tests/postgres_tenant_isolation_test.rs +++ b/crates/agentic-server-core/tests/postgres_tenant_isolation_test.rs @@ -218,7 +218,7 @@ async fn postgres_concurrent_tenant_isolation() { #[ignore = "requires TEST_POSTGRES_URL pointing to an isolated PostgreSQL database"] async fn postgres_item_crud_uses_turn_locking_and_preserves_history() { use agentic_core::storage::{ResponseMetadata, ResponseStore, StorageError}; - use agentic_core::types::conversations::ItemOrder; + use agentic_core::types::io::ItemOrder; let pool = setup_postgres_pool().await; let store = Arc::new(ConversationStore::new(Arc::clone(&pool))); diff --git a/crates/agentic-server-core/tests/relayed_stream_validation_test.rs b/crates/agentic-server-core/tests/relayed_stream_validation_test.rs index 06d280f37..eada15996 100644 --- a/crates/agentic-server-core/tests/relayed_stream_validation_test.rs +++ b/crates/agentic-server-core/tests/relayed_stream_validation_test.rs @@ -327,13 +327,22 @@ async fn strict_relay_decoder_accepts_compatible_recorded_responses_streams() { } } + // This stateless gateway capture is raw upstream pass-through. vLLM emits + // message content-part events for a reasoning item; strict ingestion must + // reject it. Keep this exact observed failure distinct from valid streams. + let invalid_passthrough = concat!( + "responses/response-edge-cases-stream-gateway.yaml turn 1: ", + "invalid request: upstream stream event does not match its active output item: ", + "inconsistent item ID, output_index, or kind" + ); + assert!(rejected.iter().any(|failure| failure == invalid_passthrough)); let unexpected: Vec<_> = rejected .iter() - .filter(|failure| !failure.contains("changes 'call_id'")) + .filter(|failure| !failure.contains("changes 'call_id'") && failure.as_str() != invalid_passthrough) .collect(); assert!( unexpected.is_empty(), - "recorded streams failed for reasons other than the call-ID stability rule:\n{}", + "recorded streams failed beyond the characterized upstream incompatibilities:\n{}", unexpected .iter() .map(|failure| failure.as_str()) @@ -342,8 +351,8 @@ async fn strict_relay_decoder_accepts_compatible_recorded_responses_streams() { ); assert_eq!( rejected.len(), - 10, - "the recorded call-ID incompatibility baseline changed:\n{}", + 11, + "the recorded upstream incompatibility baseline changed:\n{}", rejected.join("\n") ); assert!(decoded >= 50, "decoded only {decoded} recorded Responses SSE streams"); diff --git a/crates/agentic-server-core/tests/response_deletion_storage_test.rs b/crates/agentic-server-core/tests/response_deletion_storage_test.rs index c772aa18a..4689370a9 100644 --- a/crates/agentic-server-core/tests/response_deletion_storage_test.rs +++ b/crates/agentic-server-core/tests/response_deletion_storage_test.rs @@ -1,11 +1,12 @@ -//! Deleting a stored response erases the history rows nothing else keeps, and leaves a -//! conversation on its newest remaining response. +//! Response CRUD uses existing history rows and foreign-key behavior. use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; -use agentic_core::storage::models::conversation as conversation_model; -use agentic_core::storage::{ConversationStore, DbPool, ResponseMetadata, ResponseStore, create_pool_with_schema}; +use agentic_core::storage::models::{conversation as conversation_model, response as response_model}; +use agentic_core::storage::{ + ConversationStore, DbPool, ResponseMetadata, ResponseStore, StorageError, create_pool_with_schema, +}; use agentic_core::types::io::InOutItem; use serde_json::json; @@ -51,18 +52,48 @@ async fn turn(store: &ResponseStore, id: &str, previous: Option<&str>) { } #[tokio::test] -async fn deleting_a_response_erases_its_prompt_and_answer() { +async fn deleting_a_response_removes_the_resource_and_keeps_history_rows() { let pool = pool().await; let store = ResponseStore::new(Arc::clone(&pool)); turn(&store, "resp_only", None).await; assert_eq!(item_count(&pool).await, 2); store.delete("resp_only").await.unwrap(); - assert_eq!(item_count(&pool).await, 0); + assert_eq!(item_count(&pool).await, 2); + assert!(store.get("resp_only").await.unwrap_err().is_not_found()); + assert!(store.get_items("resp_only").await.unwrap_err().is_not_found()); + assert!(store.delete("resp_only").await.unwrap_err().is_not_found()); } #[tokio::test] -async fn rows_another_response_lists_survive_until_the_last_one_is_deleted() { +async fn response_deletion_uses_the_callers_transaction() { + let pool = pool().await; + let store = ResponseStore::new(Arc::clone(&pool)); + turn(&store, "resp_parent", None).await; + turn(&store, "resp_child", Some("resp_parent")).await; + let original = store.get("resp_child").await.unwrap(); + + let mut tx = pool.begin().await.unwrap(); + assert!(response_model::delete_in_tx(&mut tx, "resp_parent").await.unwrap()); + assert!( + response_model::get_in_tx(&mut tx, "resp_parent") + .await + .unwrap() + .is_none() + ); + let child = response_model::get_in_tx(&mut tx, "resp_child").await.unwrap().unwrap(); + assert!(child.previous_response_id.is_none()); + tx.rollback().await.unwrap(); + + assert!(store.get("resp_parent").await.is_ok()); + let restored = store.get("resp_child").await.unwrap(); + assert_eq!(restored.previous_response_id, original.previous_response_id); + assert_eq!(restored.history_item_ids, original.history_item_ids); + assert_eq!(item_count(&pool).await, 4); +} + +#[tokio::test] +async fn deleting_a_parent_removes_its_context_and_preserves_child_history_order() { let pool = pool().await; let store = ResponseStore::new(Arc::clone(&pool)); // `resp_a` and `resp_b` are siblings: both inherit the parent's two rows. @@ -71,19 +102,26 @@ async fn rows_another_response_lists_survive_until_the_last_one_is_deleted() { turn(&store, "resp_b", Some("resp_parent")).await; assert_eq!(item_count(&pool).await, 6); - // Children still list every parent row. + // OpenAI removes the deleted parent's context from descendant listings and continuations. store.delete("resp_parent").await.unwrap(); assert_eq!(item_count(&pool).await, 6); - // Only `resp_a`'s own rows go; `resp_b` still lists the inherited ones. + // Deletion only removes response records, preserving all shared item rows. store.delete("resp_a").await.unwrap(); - assert_eq!(item_count(&pool).await, 4); - assert_eq!(store.rehydrate("resp_b").await.unwrap().len(), 4); + assert_eq!(item_count(&pool).await, 6); + let rows = store.get_items("resp_b").await.unwrap(); + let ids = store.get("resp_b").await.unwrap().history_item_ids; + assert_eq!( + rows.iter().map(|row| &row.id).collect::>(), + ids.iter().collect::>() + ); + let history = store.rehydrate("resp_b").await.unwrap(); + assert_eq!(history, vec![message("resp_b"), answer("msg_resp_b")]); store.delete("resp_b").await.unwrap(); - assert_eq!(item_count(&pool).await, 0); + assert_eq!(item_count(&pool).await, 6); } #[tokio::test] -async fn deleting_a_conversation_latest_response_keeps_its_items_and_previous_state() { +async fn deleting_a_conversation_latest_response_clears_its_pointer_and_keeps_items() { let pool = pool().await; let conversations = ConversationStore::new(Arc::clone(&pool)); let responses = ResponseStore::new(Arc::clone(&pool)); @@ -116,8 +154,8 @@ async fn deleting_a_conversation_latest_response_keeps_its_items_and_previous_st responses.delete("resp_second").await.unwrap(); assert_eq!( latest(Arc::clone(&pool)).await.as_deref(), - Some("resp_first"), - "the conversation keeps the newest state it still has" + None, + "the existing foreign key clears the deleted pointer" ); assert_eq!( item_count(&pool).await, @@ -129,3 +167,39 @@ async fn deleting_a_conversation_latest_response_keeps_its_items_and_previous_st assert_eq!(latest(Arc::clone(&pool)).await, None); assert_eq!(item_count(&pool).await, 4); } + +#[tokio::test] +async fn item_lookup_distinguishes_missing_empty_and_invalid_history() { + let pool = pool().await; + let store = ResponseStore::new(Arc::clone(&pool)); + assert!(store.get_items("resp_missing").await.unwrap_err().is_not_found()); + store + .persist("resp_empty", None, vec![], &ResponseMetadata::default()) + .await + .unwrap(); + assert!(store.get_items("resp_empty").await.unwrap().is_empty()); + sqlx::query("UPDATE responses SET history_item_ids = NULL WHERE id = 'resp_empty'") + .execute(pool.as_ref()) + .await + .unwrap(); + assert!(store.get_items("resp_empty").await.unwrap().is_empty()); + for invalid in ["not-json", "null", "{}"] { + sqlx::query("UPDATE responses SET history_item_ids = $1 WHERE id = 'resp_empty'") + .bind(invalid) + .execute(pool.as_ref()) + .await + .unwrap(); + assert!(matches!( + store.get_items("resp_empty").await, + Err(StorageError::InvalidResponseHistory { .. }) + )); + } + sqlx::query("UPDATE responses SET history_item_ids = '[\"item_missing\"]' WHERE id = 'resp_empty'") + .execute(pool.as_ref()) + .await + .unwrap(); + assert!(matches!( + store.get_items("resp_empty").await, + Err(StorageError::InvalidHistoryItem { .. }) + )); +} diff --git a/crates/agentic-server-core/tests/responses_api/recordings.rs b/crates/agentic-server-core/tests/responses_api/recordings.rs new file mode 100644 index 000000000..949d23f94 --- /dev/null +++ b/crates/agentic-server-core/tests/responses_api/recordings.rs @@ -0,0 +1,362 @@ +//! Assertions over the unchanged recordings, independent of live replay. + +use std::collections::HashSet; + +use super::{cassette, error_metadata, item_signature, terminal}; +use serde_json::{Value, json}; + +const SCENARIOS: [&str; 5] = [ + "lifecycle", + "pagination", + "edge-cases", + "conversation", + "replayed-items", +]; + +fn items(turns: &[Value], turn: usize) -> Vec { + terminal(&turns[turn - 1]["response"])["data"] + .as_array() + .unwrap() + .clone() +} + +fn comparable_items(items: &[Value]) -> Vec { + items + .iter() + .map(|item| { + let mut value = item_signature(item); + value["id"] = item["id"].clone(); + value + }) + .collect() +} + +fn assert_pages(turns: &[Value], full_turn: usize, page_turns: &[usize], label: &str) { + let full = items(turns, full_turn); + for &turn in page_turns { + let query = &turns[turn - 1]["request"]["query_params"]; + let body = terminal(&turns[turn - 1]["response"]); + let mut ordered = full.clone(); + if query["order"].as_str().unwrap_or("desc") == "desc" { + ordered.reverse(); + } + let start = query["after"].as_str().filter(|id| !id.is_empty()).map_or(0, |id| { + ordered + .iter() + .rposition(|item| item["id"] == id) + .expect("cursor in full history") + + 1 + }); + let limit = query["limit"] + .as_str() + .map_or(20, |value| value.parse::().unwrap()); + let expected: Vec<_> = ordered.iter().skip(start).take(limit).cloned().collect(); + assert_eq!( + comparable_items(&items(turns, turn)), + comparable_items(&expected), + "{label} t{turn}: page content/order" + ); + assert_eq!( + body["has_more"], + ordered.len() - start > limit, + "{label} t{turn}: has_more" + ); + } +} + +fn assert_recording(scenario: &str, turns: &[Value], label: &str) { + for (index, turn) in turns.iter().enumerate() { + let body = terminal(&turn["response"]); + if let Some(data) = body["data"].as_array() { + for (field, expected) in [("first_id", data.first()), ("last_id", data.last())] { + assert_eq!( + body[field], + expected.map_or(Value::Null, |item| item["id"].clone()), + "{label} t{}: {field}", + index + 1 + ); + } + } + } + match scenario { + "pagination" => assert_pages(turns, 3, &[4, 5, 6, 7, 8, 9, 10, 11, 12, 13], label), + "lifecycle" => { + assert_pages(turns, 4, &[5, 6], label); + let before = items(turns, 4); + assert_eq!( + comparable_items(&items(turns, 11)), + comparable_items(&before[before.len() - 1..]), + "{label}: descendant input after deletion" + ); + assert_eq!( + terminal(&turns[6]["response"]), + terminal(&turns[11]["response"]), + "{label}: stored response changed after deleting parent" + ); + let child = terminal(&turns[6]["response"]); + let mut retained = items(turns, 11); + retained.extend(child["output"].as_array().unwrap().iter().cloned()); + let continued = items(turns, 15); + assert_eq!(continued.len(), retained.len() + 1, "{label}: continuation length"); + assert_eq!( + comparable_items(&continued[..retained.len()]), + comparable_items(&retained), + "{label}: continued history must contain only the surviving child" + ); + assert_eq!( + continued.last().unwrap()["content"][0]["text"], + turns[13]["request"]["body"]["input"][0]["content"], + "{label}: continuation must retain the new user message" + ); + } + "conversation" => { + for turn in [6, 12] { + assert_eq!( + comparable_items(&items(turns, 4)), + comparable_items(&items(turns, turn)), + "{label}: first response input snapshot changed" + ); + } + for turn in [11, 14] { + assert_eq!( + comparable_items(&items(turns, 8)), + comparable_items(&items(turns, turn)), + "{label}: child input snapshot changed" + ); + } + assert_pages(turns, 8, &[18], label); + let deleted_id = items(turns, 5)[0]["id"].clone(); + let expected: Vec<_> = items(turns, 9) + .into_iter() + .filter(|item| item["id"] != deleted_id) + .collect(); + assert_eq!( + comparable_items(&items(turns, 15)), + comparable_items(&expected), + "{label}: conversation deletion removed the wrong items" + ); + } + "replayed-items" => { + for turn in [4, 5, 7, 8, 10, 11, 14] { + assert_eq!( + comparable_items(&items(turns, 2)), + comparable_items(&items(turns, turn)), + "{label}: reused ID or rejected request changed seed content" + ); + } + } + "edge-cases" => { + assert!(items(turns, 10).is_empty(), "{label}: limit=0 returns no items"); + assert_eq!(terminal(&turns[9]["response"])["has_more"], true); + } + _ => unreachable!(), + } +} + +#[test] +fn recorded_histories_and_pages_are_consistent() { + for scenario in SCENARIOS { + for suffix in ["", "-stream"] { + for provider in ["openai", "gateway"] { + let name = format!("{scenario}{suffix}"); + let turns = cassette(&name, provider).expect("required recording"); + assert_recording(scenario, &turns, &format!("{name}/{provider}")); + } + } + } +} + +fn generated_ids(turns: &[Value]) -> HashSet { + turns + .iter() + .filter(|turn| turn["request"]["method"] == "POST" && turn["request"]["path"] == "/v1/responses") + .flat_map(|turn| { + terminal(&turn["response"])["output"] + .as_array() + .cloned() + .unwrap_or_default() + }) + .filter_map(|item| item["id"].as_str().map(str::to_owned)) + .collect() +} + +#[test] +fn recorded_management_contract_matches_openai() { + let mut differences = Vec::new(); + for scenario in SCENARIOS { + for suffix in ["", "-stream"] { + let name = format!("{scenario}{suffix}"); + let a = cassette(&name, "openai").expect("OpenAI recording"); + let b = cassette(&name, "gateway").expect("gateway recording"); + assert_eq!(a.len(), b.len(), "{name}: turn count"); + let a_generated = generated_ids(&a); + let b_generated = generated_ids(&b); + for (index, (left, right)) in a.iter().zip(&b).enumerate() { + let location = format!("{name} t{}", index + 1); + assert_eq!( + left["request"]["method"], right["request"]["method"], + "{location}: method" + ); + assert_eq!( + left["response"]["status_code"], right["response"]["status_code"], + "{location}: HTTP status" + ); + if left["response"]["status_code"] != 200 { + continue; + } + let l = terminal(&left["response"]); + let r = terminal(&right["response"]); + for field in ["object", "status", "deleted", "has_more"] { + assert_eq!(l[field], r[field], "{location}: {field}"); + } + // Stateless creation is passed through on this branch; its upstream payload + // may omit `store`. Storage behavior is checked by the subsequent CRUD calls. + if left["request"]["body"]["store"] != false { + assert_eq!(l["store"], r["store"], "{location}: store"); + } + // Different models can emit different numbers of reasoning/output items. + // Each recording's complete pages (including those items) are checked above; + // client-supplied items must still match across providers exactly. + let client_items = |body: &Value, generated: &HashSet| -> Vec { + body["data"] + .as_array() + .into_iter() + .flatten() + .filter(|item| !item["id"].as_str().is_some_and(|id| generated.contains(id))) + .map(item_signature) + .collect() + }; + let lc = client_items(&l, &a_generated); + let rc = client_items(&r, &b_generated); + if lc != rc { + differences.push(format!("{location}: client items\nOpenAI: {lc:?}\ngateway: {rc:?}")); + } + } + } + } + assert!(differences.is_empty(), "{}", differences.join("\n")); +} + +#[test] +fn recorded_error_metadata_matches_openai() { + let mut differences = Vec::new(); + for scenario in SCENARIOS { + for suffix in ["", "-stream"] { + let name = format!("{scenario}{suffix}"); + let a = cassette(&name, "openai").expect("OpenAI recording"); + let b = cassette(&name, "gateway").expect("gateway recording"); + assert_eq!(a.len(), b.len()); + for (index, (left, right)) in a.iter().zip(&b).enumerate() { + if left["response"]["status_code"].as_u64().unwrap() < 400 { + continue; + } + let expected = error_metadata(&terminal(&left["response"])); + let actual = error_metadata(&terminal(&right["response"])); + if expected != actual { + differences.push(format!("{name} t{}: OpenAI {expected}; gateway {actual}", index + 1)); + } + } + } + } + assert!( + differences.is_empty(), + "recordings need to be refreshed after fixing these errors:\n{}", + differences.join("\n") + ); +} + +#[test] +fn deferred_tool_status_defaults_do_not_hide_other_statuses() { + for (kind, default) in [("function_call", "in_progress"), ("function_call_output", "completed")] { + let absent = json!({"type": kind, "call_id": "call_1"}); + let mut recorded = absent.clone(); + recorded["status"] = json!(default); + assert_eq!(item_signature(&absent), item_signature(&recorded)); + recorded["status"] = json!("incomplete"); + assert_ne!(item_signature(&absent), item_signature(&recorded)); + } +} + +#[test] +fn error_metadata_comparison_detects_each_field() { + let body = json!({"error":{"type":"invalid_request_error","code":"invalid_value","param":"limit"}}); + for field in ["type", "code", "param"] { + let mut changed = body.clone(); + changed["error"][field] = Value::Null; + assert_ne!(error_metadata(&body), error_metadata(&changed)); + } +} + +#[test] +fn recorded_tool_history_ids_and_statuses() { + let turns = cassette("tool-items", "openai").expect("OpenAI tool-history recording"); + assert_eq!(turns.len(), 40); + let groups: &[&[(&str, &str)]] = &[ + &[("web_search_call", "ws_")], + &[("mcp_list_tools", "mcpl_"), ("mcp_call", "mcp_")], + &[("code_interpreter_call", "ci_")], + &[("shell_call", "sh_"), ("shell_call_output", "sho_")], + &[("custom_tool_call", "ctc_"), ("custom_tool_call_output", "ctco_")], + ]; + for (group, probes) in groups.iter().zip(turns.as_chunks::<8>().0) { + for (variant, pair) in probes.as_chunks::<2>().0.iter().enumerate() { + let request = &pair[0]["request"]["body"]; + let created = terminal(&pair[0]["response"]); + let page = terminal(&pair[1]["response"]); + let omitted_id = variant % 2 == 1; + let omitted_status = variant >= 2; + let submitted: Vec<_> = request["input"] + .as_array() + .unwrap() + .iter() + .filter(|item| group.iter().any(|(kind, _)| item["type"] == *kind)) + .collect(); + assert_eq!(submitted.len(), group.len()); + for item in &submitted { + if omitted_id { + assert!(item.get("id").is_none()); + } + if omitted_status { + assert!(item.get("status").is_none()); + } + } + if group[0].0 == "code_interpreter_call" && omitted_id { + assert_eq!(pair[0]["response"]["status_code"], 400); + assert_eq!(created["error"]["code"], "missing_required_parameter"); + assert_eq!(created["error"]["param"], "input[1].id"); + assert_eq!(pair[1]["response"]["status_code"], 404); + continue; + } + assert_eq!(pair[0]["response"]["status_code"], 200, "{created}"); + assert_eq!(pair[1]["response"]["status_code"], 200); + assert_eq!( + pair[1]["request"]["path"], + format!("/v1/responses/{}/input_items", created["id"].as_str().unwrap()) + ); + let data = page["data"].as_array().unwrap(); + assert_eq!(data.len(), request["input"].as_array().unwrap().len()); + for ((kind, prefix), input) in group.iter().zip(submitted) { + let item = data.iter().find(|item| item["type"] == *kind).unwrap(); + assert!(item["id"].as_str().unwrap().starts_with(prefix)); + if input.get("id").is_some() { + if *kind == "code_interpreter_call" { + assert_ne!(item["id"], input["id"], "OpenAI reassigns the recorded code-call ID"); + } else { + assert_eq!(item["id"], input["id"]); + } + } + assert_eq!(item["call_id"], input["call_id"]); + if *kind == "mcp_list_tools" { + assert!(item.get("status").is_none()); + } else { + let status = if omitted_id && matches!(*kind, "shell_call" | "custom_tool_call") { + "in_progress" + } else { + "completed" + }; + assert_eq!(item["status"], status, "{kind}: variant {variant}"); + } + } + } + } +} diff --git a/crates/agentic-server-core/tests/responses_api_test.rs b/crates/agentic-server-core/tests/responses_api_test.rs new file mode 100644 index 000000000..40b283c2c --- /dev/null +++ b/crates/agentic-server-core/tests/responses_api_test.rs @@ -0,0 +1,429 @@ +//! Replay recorded Responses management calls through the gateway. +//! Only model inference is mocked; storage and management endpoints run locally. +#[path = "responses_api/recordings.rs"] +mod recordings; + +use std::collections::HashMap; +use std::path::{Path, PathBuf}; +use std::process::Stdio; +use std::time::Duration; + +use std::slice; +use std::sync::Arc; +use tokio::process::{Child, Command}; + +use agentic_core::events::normalize_sse_line; +use axum::{Json, Router, response::IntoResponse, routing::post}; +use serde_json::{Value, json}; +use tokio::net::TcpListener; +use tokio::sync::Mutex; + +fn cassette(scenario: &str, provider: &str) -> Option> { + let directory = std::env::var_os("RESPONSES_CASSETTES_DIR").map_or_else( + || PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/cassettes/responses"), + PathBuf::from, + ); + let path = directory.join(format!("response-{scenario}-{provider}.yaml")); + if !path.exists() { + return None; + } + let document: Value = serde_yml::from_str(&std::fs::read_to_string(&path).expect("read cassette")) + .expect("parse recorder-generated cassette"); + assert_eq!( + document.as_object().unwrap().len(), + 1, + "only a flat turns list is allowed" + ); + let turns = document["turns"].as_array().unwrap().clone(); + assert!(!turns.is_empty(), "{}: empty cassette", path.display()); + for (index, turn) in turns.iter().enumerate() { + assert_eq!(turn["filename"], format!("t{}", index + 1)); + assert!(turn["request"]["method"].is_string()); + assert!(turn["request"]["query_params"].is_object()); + assert!(turn["response"]["status_code"].as_u64().is_some()); + } + Some(turns) +} + +fn terminal(response: &Value) -> Value { + if let Some(body) = response.get("body") { + return body.clone(); + } + let stream: String = response["sse"] + .as_array() + .unwrap() + .iter() + .map(|line| line.as_str().unwrap()) + .collect(); + stream + .lines() + .filter_map(normalize_sse_line) + .filter_map(|frame| frame.wire.rest.get("response").cloned()) + .find(|body| matches!(body["status"].as_str(), Some("completed" | "incomplete" | "failed"))) + .expect("stream must contain a terminal response") +} + +fn mapped(id: &str, ids: &HashMap) -> String { + ids.get(id).cloned().unwrap_or_else(|| id.to_owned()) +} + +fn map_request_ids(value: &mut Value, ids: &HashMap) { + match value { + Value::Array(items) => items.iter_mut().for_each(|item| map_request_ids(item, ids)), + Value::Object(object) => { + for (key, value) in object { + if matches!(key.as_str(), "id" | "previous_response_id" | "conversation" | "after") + && let Some(id) = value.as_str() + { + *value = mapped(id, ids).into(); + } else { + map_request_ids(value, ids); + } + } + } + _ => {} + } +} + +fn item_signature(item: &Value) -> Value { + let mut item = item.clone(); + item.as_object_mut().unwrap().remove("id"); + // Missing tool-history status defaults are deferred to the item-type refactor. + // Ignore only absence versus these recorded defaults; other statuses still differ. + if matches!( + (item["type"].as_str(), item["status"].as_str()), + (Some("function_call"), Some("in_progress")) | (Some("function_call_output"), Some("completed")) + ) { + item.as_object_mut().unwrap().remove("status"); + } + // Log probabilities are outside this API-management contract comparison. + if let Some(content) = item["content"].as_array_mut() { + for part in content { + if let Some(part) = part.as_object_mut() { + part.remove("logprobs"); + } + } + } + item +} + +fn bind_id(recorded: &str, live: &str, ids: &mut HashMap, location: &str) { + if let Some(previous) = ids.get(recorded) { + assert_eq!(previous, live, "{location}: an item changed identity"); + } else { + assert!( + !ids.values().any(|value| value == live), + "{location}: distinct IDs collapsed" + ); + ids.insert(recorded.to_owned(), live.to_owned()); + } +} + +fn error_metadata(body: &Value) -> Value { + json!({"type": body["error"]["type"], "code": body["error"]["code"], "param": body["error"]["param"]}) +} + +fn compare_items(expected: &[Value], actual: &[Value], ids: &mut HashMap, location: &str) { + assert_eq!(expected.len(), actual.len(), "{location}: item count"); + for (left, right) in expected.iter().zip(actual) { + assert_eq!(item_signature(left), item_signature(right), "{location}: item content"); + let recorded = left["id"].as_str(); + let live = right["id"].as_str(); + // Function-result resource IDs are deferred; all other item identities remain required. + if left["type"] != "function_call_output" { + assert_eq!(recorded.is_some(), live.is_some(), "{location}: item ID presence"); + } + if let (Some(recorded), Some(live)) = (recorded, live) { + bind_id(recorded, live, ids, location); + } + } +} + +fn assert_exchange(expected: &Value, actual: &Value, ids: &mut HashMap, location: &str) { + assert_eq!(actual["object"], expected["object"], "{location}: object"); + if let (Some(recorded), Some(live)) = (expected["id"].as_str(), actual["id"].as_str()) { + if let Some(mapped) = ids.get(recorded) { + assert_eq!(live, mapped, "{location}: identity"); + } else { + ids.insert(recorded.to_owned(), live.to_owned()); + } + } + if let Some(expected_items) = expected["data"].as_array() { + let actual_items = actual["data"].as_array().unwrap(); + assert_eq!(actual["has_more"], expected["has_more"], "{location}: has_more"); + compare_items(expected_items, actual_items, ids, location); + for field in ["first_id", "last_id"] { + if let Some(id) = expected[field].as_str() { + assert_eq!(actual[field], mapped(id, ids), "{location}: {field}"); + } else { + assert!(actual[field].is_null(), "{location}: {field}"); + } + } + } + if let Some(output) = expected["output"].as_array() { + compare_items(output, actual["output"].as_array().unwrap(), ids, location); + for field in ["status", "store"] { + assert_eq!(actual[field], expected[field], "{location}: {field}"); + } + } + if expected.get("deleted").is_some() { + assert_eq!(actual["deleted"], expected["deleted"], "{location}: deleted"); + } +} + +async fn replay_turn( + client: &reqwest::Client, + url: &str, + turn: &Value, + reference: &Value, + ids: &mut HashMap, + location: &str, +) { + let request = &turn["request"]; + let path = request["path"] + .as_str() + .unwrap() + .split('/') + .map(|part| mapped(part, ids)) + .collect::>() + .join("/"); + let mut body = request["body"].clone(); + map_request_ids(&mut body, ids); + let mut query = request["query_params"].clone(); + map_request_ids(&mut query, ids); + let query: Vec<(&str, &str)> = query + .as_object() + .unwrap() + .iter() + .flat_map(|(key, value)| { + value + .as_array() + .map_or_else(|| slice::from_ref(value), Vec::as_slice) + .iter() + .map(move |value| (key.as_str(), value.as_str().unwrap())) + }) + .collect(); + let method = request["method"].as_str().unwrap(); + let mut call = client + .request(method.parse().unwrap(), format!("{url}{path}")) + .bearer_auth("test-key") + .query(&query); + if method == "POST" { + call = call + .header("content-type", "application/json") + .body(serde_json::to_vec(&body).unwrap()); + } + let response = call.send().await.unwrap(); + let status = response.status().as_u16(); + let text = response.text().await.unwrap(); + let actual: Value = if status == 200 && body["stream"] == true && method == "POST" { + terminal(&json!({"sse":text.split_inclusive('\n').collect::>()})) + } else { + serde_json::from_str(&text).expect("JSON management response") + }; + assert_eq!( + u64::from(status), + reference["response"]["status_code"].as_u64().unwrap(), + "{location}: {actual}" + ); + if status != 200 { + assert_eq!( + error_metadata(&actual), + error_metadata(&terminal(&reference["response"])), + "{location}: OpenAI error metadata" + ); + return; + } + assert_exchange(&terminal(&turn["response"]), &actual, ids, location); +} + +async fn spawn_gateway(upstream_url: &str, home: &Path, client: &reqwest::Client) -> (String, Child) { + let port = std::net::TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port(); + let binary = std::env::var_os("RESPONSES_GATEWAY_BIN").map_or_else( + || { + std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .join(format!("agentic-server{}", std::env::consts::EXE_SUFFIX)) + }, + PathBuf::from, + ); + let log_path = home.join("gateway.log"); + let log = std::fs::File::create(&log_path).unwrap(); + let mut gateway = Command::new(binary) + .env_clear() + .env("AGENTIC_API_HOME", home) + .env("OPENAI_API_KEY", "test-key") + .env("RUST_LOG", "warn") + .args([ + "--llm-api-base", + upstream_url, + "--skip-llm-ready-check", + "--gateway-host", + "127.0.0.1", + "--gateway-port", + &port.to_string(), + "--db-url", + "sqlite://?mode=memory", + ]) + .stdin(Stdio::null()) + .stdout(log.try_clone().unwrap()) + .stderr(log) + .kill_on_drop(true) + .spawn() + .expect("build the gateway first: cargo build -p agentic-server --bin agentic-server"); + let url = format!("http://127.0.0.1:{port}"); + let ready = tokio::time::timeout(Duration::from_secs(15), async { + loop { + assert!( + gateway.try_wait().unwrap().is_none(), + "gateway exited: {}", + std::fs::read_to_string(&log_path).unwrap() + ); + if client + .get(format!("{url}/health")) + .send() + .await + .is_ok_and(|response| response.status().is_success()) + { + break; + } + tokio::time::sleep(Duration::from_millis(25)).await; + } + }) + .await; + assert!( + ready.is_ok(), + "gateway did not start: {}", + std::fs::read_to_string(&log_path).unwrap() + ); + (url, gateway) +} + +async fn replay(scenario: &str, provider: &str, turns: &[Value]) { + let model_responses: Vec = turns + .iter() + .filter(|turn| { + turn["request"]["method"] == "POST" + && turn["request"]["path"] == "/v1/responses" + && turn["response"]["status_code"] == 200 + }) + .map(|turn| turn["response"].clone()) + .collect(); + let outputs = Arc::new(Mutex::new(model_responses.into_iter())); + let upstream_outputs = Arc::clone(&outputs); + let last_model_input = Arc::new(Mutex::new(Value::Null)); + let upstream_input = Arc::clone(&last_model_input); + let upstream_router = Router::new().route( + "/v1/responses", + post(move |Json(request): Json| { + let outputs = Arc::clone(&upstream_outputs); + let input = Arc::clone(&upstream_input); + async move { + *input.lock().await = request["input"].clone(); + let response = outputs.lock().await.next().expect("recorded model response exhausted"); + if let Some(chunks) = response["sse"].as_array() { + let stream: String = chunks.iter().map(|chunk| chunk.as_str().unwrap()).collect(); + ([("content-type", "text/event-stream")], stream).into_response() + } else { + Json(response["body"].clone()).into_response() + } + } + }), + ); + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let upstream_url = format!("http://{}", listener.local_addr().unwrap()); + let upstream = tokio::spawn(async move { axum::serve(listener, upstream_router).await.unwrap() }); + let home = std::env::temp_dir().join(format!("responses-replay-{}", uuid::Uuid::now_v7())); + std::fs::create_dir(&home).unwrap(); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(10)) + .build() + .unwrap(); + let (url, mut gateway) = spawn_gateway(&upstream_url, &home, &client).await; + let reference = cassette(scenario, "openai").expect("OpenAI reference is required for error contracts"); + let mut ids = HashMap::::new(); + for (index, turn) in turns.iter().enumerate() { + replay_turn( + &client, + &url, + turn, + &reference[index], + &mut ids, + &format!("{scenario}/{provider} t{}", index + 1), + ) + .await; + } + assert!( + outputs.lock().await.next().is_none(), + "all recorded model calls must be consumed" + ); + if scenario == "lifecycle" || scenario == "lifecycle-stream" { + let input = last_model_input.lock().await; + let user_texts: Vec<&str> = input + .as_array() + .unwrap() + .iter() + .filter(|item| item["role"] == "user") + .flat_map(|item| { + let content = &item["content"]; + content + .as_array() + .map_or_else(|| slice::from_ref(content), Vec::as_slice) + }) + .map(|part| part.as_str().or_else(|| part["text"].as_str()).expect("user text")) + .collect(); + let expected: Vec<&str> = [2, 13] + .map(|index| turns[index]["request"]["body"]["input"][0]["content"].as_str().unwrap()) + .into(); + assert_eq!( + user_texts, expected, + "deleted parent must not reach continued inference" + ); + } + gateway.kill().await.unwrap(); + gateway.wait().await.unwrap(); + upstream.abort(); + let _ = upstream.await; + std::fs::remove_dir_all(&home).unwrap(); +} + +macro_rules! replay_tests { + ($(($name:ident, $scenario:literal, $provider:literal)),* $(,)?) => {$( + #[tokio::test] + async fn $name() { + let turns = cassette($scenario, $provider).expect("required Responses recording missing"); + replay($scenario, $provider, &turns).await; + } + )*}; +} + +replay_tests! { + (replay_lifecycle_openai, "lifecycle", "openai"), + (replay_lifecycle_gateway, "lifecycle", "gateway"), + (replay_lifecycle_stream_openai, "lifecycle-stream", "openai"), + (replay_lifecycle_stream_gateway, "lifecycle-stream", "gateway"), + (replay_pagination_openai, "pagination", "openai"), + (replay_pagination_gateway, "pagination", "gateway"), + (replay_pagination_stream_openai, "pagination-stream", "openai"), + (replay_pagination_stream_gateway, "pagination-stream", "gateway"), + (replay_edge_cases_openai, "edge-cases", "openai"), + (replay_edge_cases_gateway, "edge-cases", "gateway"), + (replay_edge_cases_stream_openai, "edge-cases-stream", "openai"), + (replay_edge_cases_stream_gateway, "edge-cases-stream", "gateway"), + (replay_conversation_openai, "conversation", "openai"), + (replay_conversation_gateway, "conversation", "gateway"), + (replay_conversation_stream_openai, "conversation-stream", "openai"), + (replay_conversation_stream_gateway, "conversation-stream", "gateway"), + (replay_replayed_items_openai, "replayed-items", "openai"), + (replay_replayed_items_gateway, "replayed-items", "gateway"), + (replay_replayed_items_stream_openai, "replayed-items-stream", "openai"), + (replay_replayed_items_stream_gateway, "replayed-items-stream", "gateway"), +} diff --git a/crates/agentic-server-core/tests/stateful_responses_integration.rs b/crates/agentic-server-core/tests/stateful_responses_integration.rs index 309ef6349..9ed439447 100644 --- a/crates/agentic-server-core/tests/stateful_responses_integration.rs +++ b/crates/agentic-server-core/tests/stateful_responses_integration.rs @@ -1001,7 +1001,7 @@ async fn tool_search_store_false_manual_replay_completes_without_reusable_respon .get(&lookup_ctx) .await .expect_err("store:false response ID must not be reusable"); - assert!(matches!(error, agentic_core::executor::ExecutorError::Storage(source) if source.is_not_found())); + assert!(matches!(error, ExecutorError::PreviousResponseNotFound { .. })); } #[tokio::test] diff --git a/crates/agentic-server/src/handler/http/conversation_items.rs b/crates/agentic-server/src/handler/http/conversation_items.rs index b93cc199f..0f8a0244f 100644 --- a/crates/agentic-server/src/handler/http/conversation_items.rs +++ b/crates/agentic-server/src/handler/http/conversation_items.rs @@ -1,106 +1,19 @@ //! HTTP transport for conversation item CRUD. -use std::borrow::Cow; - use axum::extract::{Path, RawQuery, Request, State}; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; +use agentic_core::types::io::ItemInclude; #[cfg(feature = "openapi")] -use agentic_core::types::ConversationResponse; -use agentic_core::types::conversations::{ItemInclude, ItemLimit, ItemOrder, ItemPage}; -use agentic_core::types::{CreateItemRequest, ItemResponse, ListItemsResponse}; +use agentic_core::types::{ConversationResponse, InOutItem}; +use agentic_core::types::{CreateItemRequest, ListItemsResponse}; use super::super::common::{error_response, executor_error_response, extract_json, read_bytes}; use super::conversations::{DEFAULT_TENANT_ID, conversation_response}; +use super::items::ListItemsQuery; use crate::app::AppState; -/// Query parameters for item listings. -/// -/// Parsed from the raw query string by [`ListItemsQuery::parse`]: `include` may -/// repeat, which form deserialization into a struct does not support. -#[derive(Debug, Default)] -#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))] -#[cfg_attr(feature = "openapi", into_params(parameter_in = Query))] -pub struct ListItemsQuery { - /// Maximum number of items to return (default: 20, max: 100). - #[cfg_attr(feature = "openapi", param(value_type = Option, minimum = 1, maximum = 100))] - pub limit: ItemLimit, - - /// Cursor for pagination (item ID to start after). - pub after: Option, - - /// Sort order for items (default: desc). Ascending order is oldest-first. - #[cfg_attr(feature = "openapi", param(value_type = Option))] - pub order: ItemOrder, - - /// Extra item fields to include, repeatable and also accepted as `include[]`. - #[cfg_attr(feature = "openapi", param(value_type = Option>))] - pub include: Vec, -} - -impl ListItemsQuery { - /// Parse and validate the parameters shared by every item listing. - /// - /// Unknown parameters are ignored, as typed query extraction does. - /// - /// # Errors - /// Returns a 400 response naming the offending parameter for a repeated - /// single-valued parameter, an out-of-range limit, an unknown order, or an - /// include value that is unknown or that stored items cannot serve. - pub(super) fn parse(raw: Option<&str>) -> Result> { - let invalid = |message: String| Box::new(error_response(StatusCode::BAD_REQUEST, "invalid_request", &message)); - let mut query = Self::default(); - let (mut limit, mut after, mut order) = (None, None, None); - for (key, value) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) { - let slot = match key.as_ref() { - "limit" => &mut limit, - "after" => &mut after, - "order" => &mut order, - "include" | "include[]" => { - let include = value - .parse::() - .map_err(|_| invalid(format!("unknown include value '{value}'")))?; - if !include.is_stored_with_items() { - return Err(invalid(format!("include value '{value}' is not supported"))); - } - query.include.push(include); - continue; - } - _ => continue, - }; - if slot.replace(value.clone()).is_some() { - return Err(invalid(format!("'{key}' must be given at most once"))); - } - } - if let Some(limit) = limit { - query.limit = limit - .parse::() - .ok() - .and_then(|limit| ItemLimit::try_from(limit).ok()) - .ok_or_else(|| invalid("limit must be between 1 and 100".to_owned()))?; - } - if let Some(order) = order { - query.order = match order.as_ref() { - "asc" => ItemOrder::Asc, - "desc" => ItemOrder::Desc, - _ => return Err(invalid("order must be 'asc' or 'desc'".to_owned())), - }; - } - query.after = after.map(Cow::into_owned); - Ok(query) - } - - /// The validated pagination, borrowing the cursor. - pub(super) fn page(&self) -> ItemPage<'_> { - ItemPage { - limit: self.limit, - after: self.after.as_deref(), - order: self.order, - } - } -} - #[cfg_attr(feature = "openapi", utoipa::path( post, path = "/v1/conversations/{conversation_id}/items", @@ -166,6 +79,22 @@ pub async fn list_items( Ok(query) => query, Err(error) => return *error, }; + // ItemLimit validates the upper bound; conversations also require a nonzero limit. + if query.limit.get() == 0 { + return error_response( + StatusCode::BAD_REQUEST, + "invalid_request", + "limit must be between 1 and 100", + ); + } + // Text output logprobs are already present in stored message items. Other + // include expansions need their own data sources before they can be served. + if query + .include + .is_some_and(|include| include != ItemInclude::MessageOutputTextLogprobs) + { + return error_response(StatusCode::BAD_REQUEST, "invalid_request", "unsupported include value"); + } match state .exec_ctx @@ -186,7 +115,7 @@ pub async fn list_items( ("item_id" = String, Path, description = "Item ID") ), responses( - (status = 200, description = "Item retrieved", body = ItemResponse), + (status = 200, description = "Item retrieved", body = InOutItem), (status = 404, description = "Item or conversation not found"), ), security(("bearer_auth" = [])), diff --git a/crates/agentic-server/src/handler/http/items.rs b/crates/agentic-server/src/handler/http/items.rs new file mode 100644 index 000000000..ac22f6a00 --- /dev/null +++ b/crates/agentic-server/src/handler/http/items.rs @@ -0,0 +1,106 @@ +//! Shared HTTP query handling for item listings. + +use agentic_core::types::io::{ItemInclude, ItemLimit, ItemOrder, ItemPage}; +use std::collections::HashSet; + +use agentic_core::executor::ExecutorError; +use axum::response::Response; +use serde::de::IntoDeserializer; +use serde::{Deserialize, Serialize}; + +use super::super::common::executor_error_response; + +/// Shared query parameters for conversation and response input item listings. +#[derive(Debug, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))] +pub struct ListItemsQuery { + /// Maximum number of items to return (default: 20, max: 100). + #[serde(default)] + #[cfg_attr(feature = "openapi", param(value_type = Option, minimum = 0, maximum = 100))] + pub limit: ItemLimit, + + /// Cursor for pagination (item ID to start after). + pub after: Option, + + /// Sort order for items (default: desc). Ascending order is oldest-first. + #[serde(default)] + pub order: ItemOrder, + + /// Optional output expansion, encoded as `include[]=value` by `OpenAI` clients. + #[serde(alias = "include[]")] + pub include: Option, +} + +impl ListItemsQuery { + /// Decode item listing parameters while retaining parameter-specific errors. + /// + /// # Errors + /// Returns an OpenAI-compatible 400 for invalid or duplicate query parameters. + pub(super) fn parse(raw: Option<&str>) -> Result> { + let invalid = |param: &str, code: Option<&'static str>, message: String| { + Box::new(executor_error_response(ExecutorError::UnsupportedValue { + param: param.to_owned(), + code, + message, + })) + }; + let mut query = Self::default(); + let mut seen = HashSet::new(); + for (key, value) in url::form_urlencoded::parse(raw.unwrap_or_default().as_bytes()) { + let key = match key.as_ref() { + "include" | "include[]" => "include", + "limit" => "limit", + "order" => "order", + "after" => "after", + _ => continue, + }; + // Check each expansion, including repeated includes, before rejecting duplicates. + if key == "include" { + let include = value + .parse::() + .map_err(|error| invalid("include[0]", Some("invalid_value"), error.to_string()))?; + if include == ItemInclude::ReasoningEncryptedContent { + return Err(invalid( + "include", + None, + "Encrypted content cannot be requested for persisted responses.".into(), + )); + } + query.include = Some(include); + } + if !seen.insert(key) { + return Err(invalid( + key, + Some("duplicate_parameter"), + format!("'{key}' must be given at most once"), + )); + } + match key { + "limit" => { + let limit = value + .parse::() + .map_err(|_| invalid(key, Some("invalid_type"), "limit must be an integer".into()))?; + query.limit = ItemLimit::try_from(limit) + .map_err(|error| invalid(key, Some(error.code()), error.to_string()))?; + } + "order" => { + let order: Result = + ItemOrder::deserialize(value.as_ref().into_deserializer()); + query.order = order.map_err(|error| invalid(key, Some("invalid_value"), error.to_string()))?; + } + "after" => query.after = Some(value.into_owned()), + _ => {} + } + } + Ok(query) + } + + /// Borrow validated pagination arguments for the executor API. + pub(super) fn page(&self) -> ItemPage<'_> { + ItemPage { + limit: self.limit, + after: self.after.as_deref(), + order: self.order, + } + } +} diff --git a/crates/agentic-server/src/handler/http/mod.rs b/crates/agentic-server/src/handler/http/mod.rs index cb64238bf..8ad4fed4d 100644 --- a/crates/agentic-server/src/handler/http/mod.rs +++ b/crates/agentic-server/src/handler/http/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod chat_completions; pub(crate) mod conversation_items; pub(crate) mod conversations; pub(crate) mod embeddings; +pub(crate) mod items; pub(crate) mod messages; pub(crate) mod models; pub(crate) mod responses; diff --git a/crates/agentic-server/src/handler/http/responses.rs b/crates/agentic-server/src/handler/http/responses.rs index 6a92ba7d1..98f227aaa 100644 --- a/crates/agentic-server/src/handler/http/responses.rs +++ b/crates/agentic-server/src/handler/http/responses.rs @@ -12,12 +12,13 @@ use agentic_core::executor::telemetry::Api; use agentic_core::executor::{ExecuteRequest, compact_response as execute_compaction}; use agentic_core::proxy::ProxyRequest; use agentic_core::tool::ToolSearchHandler; +use agentic_core::types::io::DeletedResponse; use agentic_core::types::request_response::{CompactRequest, PromptCacheOptions, RequestPayload, ResponseTextConfig}; use super::super::common::{ convert_response, executor_error_response, extract_bearer, read_bytes, read_json, sse_response, }; -use super::conversation_items::ListItemsQuery; +use super::items::ListItemsQuery; use crate::app::AppState; // Only executor requests interpret text and prompt-cache options. Proxy requests retain their raw body. @@ -165,7 +166,7 @@ pub async fn retrieve_response(State(state): State, Path(response_id): path = "/v1/responses/{response_id}", params(("response_id" = String, Path, description = "Stored response ID")), responses( - (status = 200, description = "Response deleted", body = agentic_core::types::conversations::DeletedResponse), + (status = 200, description = "Response deleted", body = agentic_core::types::io::DeletedResponse), (status = 401, description = "Missing or invalid bearer token", body = crate::openapi::ApiErrorResponse), (status = 404, description = "Response not found", body = crate::openapi::ApiErrorResponse), ), @@ -174,10 +175,7 @@ pub async fn retrieve_response(State(state): State, Path(response_id): ))] pub async fn delete_response(State(state): State, Path(response_id): Path) -> Response { match state.exec_ctx.resp_handler.delete(&response_id).await { - Ok(()) => axum::Json(agentic_core::types::conversations::DeletedResponse::response( - response_id, - )) - .into_response(), + Ok(()) => axum::Json(DeletedResponse::response(response_id)).into_response(), Err(error) => executor_error_response(error), } } @@ -207,9 +205,9 @@ pub async fn delete_response(State(state): State, Path(response_id): P pub async fn list_input_items( State(state): State, Path(response_id): Path, - RawQuery(raw): RawQuery, + RawQuery(query): RawQuery, ) -> Response { - let query = match ListItemsQuery::parse(raw.as_deref()) { + let query = match ListItemsQuery::parse(query.as_deref()) { Ok(query) => query, Err(error) => return *error, }; diff --git a/crates/agentic-server/src/openapi.rs b/crates/agentic-server/src/openapi.rs index e14cbd79c..0cf429d18 100644 --- a/crates/agentic-server/src/openapi.rs +++ b/crates/agentic-server/src/openapi.rs @@ -172,13 +172,12 @@ use utoipa::OpenApi; agentic_core::types::conversations::CreateConversationRequest, agentic_core::types::conversations::ConversationResponse, agentic_core::types::conversations::UpdateConversationRequest, - agentic_core::types::conversations::DeletedResponse, + agentic_core::types::io::DeletedResponse, agentic_core::types::conversations::CreateItemRequest, - agentic_core::types::conversations::ItemResponse, - agentic_core::types::conversations::ListItemsResponse, + agentic_core::types::io::ListItemsResponse, InOutItem, - agentic_core::types::conversations::ItemOrder, - agentic_core::types::conversations::ItemInclude, + agentic_core::types::io::ItemOrder, + agentic_core::types::io::ItemInclude, )), modifiers(&SecurityAddon), tags( diff --git a/crates/agentic-server/tests/conversations/regressions.rs b/crates/agentic-server/tests/conversations/regressions.rs index e808fda8d..1119ef328 100644 --- a/crates/agentic-server/tests/conversations/regressions.rs +++ b/crates/agentic-server/tests/conversations/regressions.rs @@ -1,5 +1,6 @@ use super::*; use agentic_core::types::InOutItem; +use serde_json::Value; async fn create_conversation_with_items(url: &str, items: Vec) -> String { let response = reqwest::Client::new() @@ -139,13 +140,27 @@ async fn rejects_oversized_batches_and_invalid_limits() { .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } - for query in ["limit=0", "limit=101", "order=invalid"] { + for (query, code, param) in [ + ("limit=0", None, None), + ("limit=101", Some("integer_above_max_value"), Some("limit")), + ("limit=-1", Some("integer_below_min_value"), Some("limit")), + ("order=invalid", Some("invalid_value"), Some("order")), + ("limit=ten", Some("invalid_type"), Some("limit")), + ("limit=1&limit=2", Some("duplicate_parameter"), Some("limit")), + ("include[]=unknown", Some("invalid_value"), Some("include[0]")), + ] { let response = client .get(format!("{url}/v1/conversations/{conv_id}/items?{query}")) .send() .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); + let body: Value = response.json().await.unwrap(); + if let Some(code) = code { + assert_eq!(body["error"]["type"], "invalid_request_error", "{query}: {body}"); + assert_eq!(body["error"]["code"], code, "{query}: {body}"); + } + assert_eq!(body["error"]["param"].as_str(), param, "{query}: {body}"); } handle.abort(); } diff --git a/crates/agentic-server/tests/response_input_items_test.rs b/crates/agentic-server/tests/response_input_items_test.rs index d33bcd6cb..d7899506d 100644 --- a/crates/agentic-server/tests/response_input_items_test.rs +++ b/crates/agentic-server/tests/response_input_items_test.rs @@ -166,6 +166,17 @@ async fn input_items_list_the_inherited_context_without_the_response_own_output( } assert_eq!(walked, ["first", "answer 0", "second"]); + // OpenAI removes the deleted parent's turn from the surviving child's input window. + let deleted = harness + .client + .delete(format!("{}/v1/responses/{first_id}", harness.url)) + .bearer_auth("test-key") + .send() + .await + .unwrap(); + assert_eq!(deleted.status(), StatusCode::OK); + assert_eq!(texts(&harness.page(second_id, "?order=asc").await), ["second"]); + harness.stop().await; } @@ -227,23 +238,31 @@ async fn input_items_reject_what_retrieval_rejects() { for missing in [transient["id"].as_str().unwrap(), "resp_missing"] { assert_eq!(harness.input_items(missing, "").await.status(), StatusCode::NOT_FOUND); } - for (query, message) in [ - ("?limit=0", "limit must be between 1 and 100"), - ("?limit=101", "limit must be between 1 and 100"), - ("?limit=ten", "limit must be between 1 and 100"), - ("?limit=1&limit=2", "'limit' must be given at most once"), - ("?order=sideways", "order must be 'asc' or 'desc'"), - ("?include=no.such.field", "unknown include value 'no.such.field'"), - // Hosted tool results come from data sources the gateway does not keep. - ( - "?include[]=web_search_call.results", - "include value 'web_search_call.results' is not supported", - ), + // Recorded OpenAI edge cases accept an empty page and known include fields. + let empty = harness.page(stored_id, "?limit=0").await; + assert_eq!(empty["data"], json!([])); + assert_eq!(empty["has_more"], true); + assert_eq!( + harness + .input_items(stored_id, "?include[]=web_search_call.results") + .await + .status(), + StatusCode::OK + ); + for (query, code, param) in [ + ("?limit=101", "integer_above_max_value", "limit"), + ("?limit=-1", "integer_below_min_value", "limit"), + ("?order=sideways", "invalid_value", "order"), + ("?include=no.such.field", "invalid_value", "include[0]"), + ("?limit=ten", "invalid_type", "limit"), + ("?limit=1&limit=2", "duplicate_parameter", "limit"), ] { let response = harness.input_items(stored_id, query).await; assert_eq!(response.status(), StatusCode::BAD_REQUEST, "{query}"); let body: Value = response.json().await.expect("a structured error body"); - assert_eq!(body["error"]["message"], message, "{query}"); + assert_eq!(body["error"]["type"], "invalid_request_error", "{query}"); + assert_eq!(body["error"]["code"], code, "{query}"); + assert_eq!(body["error"]["param"], param, "{query}"); } // Same contract as conversation item listing: an unknown cursor is a not-found naming `after`. let unknown_cursor = harness.input_items(stored_id, "?after=msg_unknown").await; @@ -306,6 +325,27 @@ async fn input_items_exclude_own_output_items_that_carry_no_wire_id() { .collect(); assert_eq!(types, ["message", "compaction", "message", "message"], "{next}"); assert!(next["data"][1]["id"].as_str().unwrap().starts_with("cmp_")); + + // Conversation turns exclude appended outputs without wire IDs too. + let conversation: Value = harness + .client + .post(format!("{}/v1/conversations", harness.url)) + .bearer_auth("test-key") + .json(&json!({})) + .send() + .await + .unwrap() + .json() + .await + .unwrap(); + let in_conversation = harness + .create(json!({"model":"test-model", "input":"conversation input", "store":true, + "conversation":conversation["id"]})) + .await; + let conversation_inputs = harness + .page(in_conversation["id"].as_str().unwrap(), "?order=asc") + .await; + assert_eq!(texts(&conversation_inputs), ["conversation input"]); harness.stop().await; } @@ -339,7 +379,7 @@ async fn input_items_keep_an_earlier_turn_output_that_shares_this_turn_output_id } #[tokio::test] -async fn input_items_accept_repeated_include_values_that_stored_items_carry() { +async fn input_items_reuse_conversation_query_parameters() { let harness = Harness::start().await; let stored = harness .create( @@ -351,8 +391,7 @@ async fn input_items_accept_repeated_include_values_that_stored_items_carry() { let page = harness .page( stored["id"].as_str().unwrap(), - "?include[]=message.input_image.image_url&include[]=message.output_text.logprobs\ - &include=reasoning.encrypted_content", + "?include[]=message.output_text.logprobs", ) .await; assert_eq!( @@ -408,3 +447,20 @@ async fn input_item_pages_make_progress_past_a_repeated_public_id() { } harness.stop().await; } + +#[tokio::test] +async fn an_empty_input_turn_keeps_the_previous_answer_in_its_input_list() { + let harness = Harness::start().await; + let first = harness + .create(json!({"model":"test-model", "input":"first", "store":true})) + .await; + let second = harness + .create(json!({"model":"test-model", "input":[], "store":true, + "previous_response_id":first["id"]})) + .await; + assert_eq!( + texts(&harness.page(second["id"].as_str().unwrap(), "?order=asc").await), + ["first", "answer 0"] + ); + harness.stop().await; +} diff --git a/crates/agentic-server/tests/response_retrieval_test.rs b/crates/agentic-server/tests/response_retrieval_test.rs index edff7ee76..620fa455c 100644 --- a/crates/agentic-server/tests/response_retrieval_test.rs +++ b/crates/agentic-server/tests/response_retrieval_test.rs @@ -210,9 +210,7 @@ async fn store_false_continuation_of_a_stored_response_is_not_retained() { "previous_response_id":child_id})) .await; assert_eq!(orphaned.status(), StatusCode::BAD_REQUEST); - let body = orphaned.json::().await.unwrap(); - assert_eq!(body["error"]["code"], "previous_response_not_found"); - assert_eq!(body["error"]["param"], "previous_response_id"); + assert!(orphaned.json::().await.unwrap().get("error").is_some()); assert_eq!( requests.lock().unwrap().len(), 2, @@ -332,14 +330,14 @@ async fn deleting_a_response_keeps_its_descendants_and_ends_its_own_continuation let orphaned = create(json!({"model":"test-model", "input":"third", "store":true, "previous_response_id":parent_id})) .await; - // As OpenAI reports it: a request error naming `previous_response_id`. + // OpenAI reports a missing continuation as an invalid previous_response_id. assert_eq!(orphaned.status(), StatusCode::BAD_REQUEST); let body = orphaned.json::().await.unwrap(); - assert_eq!(body["error"]["code"], "previous_response_not_found", "{body}"); + assert!(body["error"].is_object(), "{body}"); assert_eq!(requests.lock().unwrap().len(), 2); - // A descendant stores its own complete history, so it survives its parent: - // still retrievable unchanged, and still continuable with the parent's turn. + // OpenAI keeps the child's snapshot but excludes the deleted parent's turn + // from subsequent inference (recorded in the Responses lifecycle suite). let retrieved = retrieve(child_id).await; assert_eq!(retrieved.status(), StatusCode::OK); assert_eq!(retrieved.json::().await.unwrap(), child); @@ -347,10 +345,7 @@ async fn deleting_a_response_keeps_its_descendants_and_ends_its_own_continuation "previous_response_id":child_id})) .await; assert_eq!(grandchild.status(), StatusCode::OK); - assert_eq!( - input_texts(&requests.lock().unwrap()[2]), - ["first", "answer", "second", "answer", "third"] - ); + assert_eq!(input_texts(&requests.lock().unwrap()[2]), ["second", "answer", "third"]); upstream.abort(); let _ = upstream.await; diff --git a/docs/api/index.md b/docs/api/index.md index d97cbd430..0b59ddc7d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -439,45 +439,41 @@ must send that key as a bearer token. ### `DELETE /v1/responses/{response_id}` Deletes a stored response and returns -`{"id": "", "object": "response.deleted", "deleted": true}`. Later -retrieval returns `404`, and a new request naming the deleted ID as -`previous_response_id` returns `400` with code `previous_response_not_found` -and `param: "previous_response_id"`, as for any unknown previous response. -Responses created from it stay retrievable and can still be continued: each -stored response keeps its own complete history. The response's prompt and -output rows are erased unless something still uses them: another response's -history (descendants and siblings inherit history), a conversation, or a -conversation item that references them. Deleting a conversation's latest -response moves the conversation to its newest remaining response, so the next -turn keeps the closest stored state (effective settings, tools loaded by tool -search, the multi-agent tree). Unknown IDs return `404`. Authentication matches -retrieval. - -Deletion does not reach live WebSocket sessions: a connection whose latest -response is the deleted one can still continue from it, because the session -caches that checkpoint and does not read storage again. A `store: true` turn -from it then fails at persistence. New connections and HTTP requests are -rejected as described above. +`{"id": "", "object": "response.deleted", "deleted": true}`. +Unknown or already deleted IDs return `404`. The deleted response can no +longer be retrieved or used as a durable `previous_response_id`. + +History item rows are retained: descendant responses and conversations may +still reference them. Descendants remain retrievable and continuable, but +response-chain descendants exclude the deleted response's inherited context +from their input listings and subsequent inference. Conversation snapshots +retain their recorded context. These behaviors are covered by OpenAI and +gateway lifecycle recordings, including continuation after parent deletion. +Existing foreign keys clear pointers to the deleted response, including a +conversation's `latest_response_id`; deletion does not choose an older turn +as replacement state. Authentication matches response retrieval. + +Deletion does not invalidate a live WebSocket session's cached checkpoint. +Such a session may still continue without storage; persisting a child of the +deleted response fails. New connections resolve durable IDs through storage. ### `GET /v1/responses/{response_id}/input_items` -Lists the context a stored response was generated from: items inherited through -`previous_response_id` or its conversation (including earlier turns' outputs), -followed by that turn's input. The response's own output is not included. A -conversation turn lists the conversation as it was at that turn, not as it is -now. Pagination matches conversation item listing: `limit` (1–100, default 20), -`order` (`desc` by default), and `after` (the ID of the last item on the -previous page, `404` if unknown). An item can occur more than once in a -history; an `after` cursor naming it resumes after its last occurrence in the -requested order, so paging always advances. `include` (repeatable, also as -`include[]`) accepts `message.input_image.image_url`, -`computer_call_output.output.image_url`, `reasoning.encrypted_content`, and -`message.output_text.logprobs`, which stored items already carry; hosted tool -results (`web_search_call.*`, `file_search_call.results`, -`code_interpreter_call.outputs`) and unknown values return `400`, as do a -repeated `limit`, `order`, or `after`. Conversation item listing parses its -query the same way. Unknown IDs return `404`, and records without a stored -response snapshot return `409`. Authentication matches retrieval. +Lists the stored context for a response: inherited history, including earlier +outputs, followed by this turn's inputs. This response's appended outputs are +excluded. The endpoint uses the existing history IDs, item records, and saved +response snapshot; it does not add metadata or change persistence. + +Pagination uses the Conversation API query parameters: `limit` (1–100, +default 20), `order` (`asc` or `desc`, default `desc`), and `after` (a public +item ID; unknown cursors return `404`). A repeated public ID resumes after +its last occurrence in the requested order. `include` or `include[]` accepts +`message.output_text.logprobs`, which stored items already contain; other +expansions return `400`, as on conversation item listing. Malformed or repeated +scalar query parameters return `400`. + +Unknown or unstored responses return `404`. Legacy responses without a saved +payload return `409`, as on response retrieval. Authentication matches retrieval. ### `POST /v1/responses/compact` From bec9a7d99bbf660561bd14ac3a423a5023ba5c8b Mon Sep 17 00:00:00 2001 From: maral Date: Fri, 23 Oct 2026 22:12:08 +0800 Subject: [PATCH 7/7] fix: preserve tool result IDs and statuses in response history Signed-off-by: maral --- .../src/executor/compaction.rs | 7 + .../executor/engine/multi_agent/delivery.rs | 3 + .../src/executor/engine/multi_agent_tests.rs | 2 + .../engine/multi_agent_tests/live_control.rs | 2 + .../src/executor/multi_agent/control.rs | 2 + .../src/executor/multi_agent/pending_calls.rs | 4 + .../src/executor/pending_calls.rs | 3 + .../response_budget/client_outputs.rs | 5 +- .../src/storage/conversation.rs | 63 +- .../src/storage/models/item.rs | 188 +- .../src/storage/response.rs | 219 +- .../agentic-server-core/src/tool/normalize.rs | 4 + .../src/tool/tool_search.rs | 6 +- .../src/types/injection.rs | 2 + .../agentic-server-core/src/types/io/input.rs | 27 +- .../src/types/io/input/conversions.rs | 78 +- .../src/types/io/item/item_id.rs | 17 +- .../src/types/io/item/tests.rs | 24 +- .../tests/cassettes/README.md | 91 +- .../tests/cassettes/record_cassette.py | 55 +- .../record_responses_api_cassettes.sh | 50 +- .../tests/cassettes/replay_old_cassettes.py | 76 + .../response-conversation-gateway.yaml | 1016 ++ .../response-conversation-stream-gateway.yaml | 1489 +++ .../response-edge-cases-gateway.yaml | 640 ++ .../response-edge-cases-stream-gateway.yaml | 1110 ++ .../responses/response-lifecycle-gateway.yaml | 785 ++ .../response-lifecycle-stream-gateway.yaml | 1831 ++++ .../response-pagination-gateway.yaml | 737 ++ .../response-pagination-stream-gateway.yaml | 1582 +++ .../response-replayed-items-gateway.yaml | 584 ++ ...esponse-replayed-items-stream-gateway.yaml | 830 ++ .../response-tool-items-gateway.yaml | 9311 +++++++++++++++++ .../responses/response-tool-items-openai.yaml | 6549 +++++++++--- .../tests/compaction_cassette_test.rs | 5 +- .../tests/mcp_tool_test.rs | 36 +- .../tests/responses_api/recordings.rs | 146 +- .../tests/responses_api_test.rs | 54 +- .../tests/session_continuation_test.rs | 20 +- .../tests/shell_tool_test.rs | 1 + .../tests/stateful_responses_integration.rs | 6 + .../tests/web_search_tool_test.rs | 84 +- 42 files changed, 25794 insertions(+), 1950 deletions(-) create mode 100644 crates/agentic-server-core/tests/cassettes/replay_old_cassettes.py create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-conversation-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-pagination-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-gateway.yaml create mode 100644 crates/agentic-server-core/tests/cassettes/responses/response-tool-items-gateway.yaml diff --git a/crates/agentic-server-core/src/executor/compaction.rs b/crates/agentic-server-core/src/executor/compaction.rs index 678a0b55b..5f5bfb654 100644 --- a/crates/agentic-server-core/src/executor/compaction.rs +++ b/crates/agentic-server-core/src/executor/compaction.rs @@ -455,6 +455,8 @@ mod tests { fn function_image_output(encoded_bytes: usize) -> InputItem { InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_view_image".to_owned(), output: ToolCallOutput::Content(vec![ToolOutputContent::InputImage(inline_image(encoded_bytes))]), }) @@ -462,6 +464,7 @@ mod tests { fn custom_image_output(encoded_bytes: usize) -> InputItem { InputItem::CustomToolCallOutput(CustomToolCallOutputMessage { + status: None, id: None, call_id: "call_view_image".to_owned(), name: Some("view_image".to_owned()), @@ -586,6 +589,8 @@ mod tests { }), user_message("first"), InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_1".to_owned(), output: "tool output".into(), }), @@ -701,6 +706,8 @@ mod tests { let input = ResponsesInput::from(vec![ user_message("hello context"), InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_1".to_owned(), output: "substantial tool output".into(), }), diff --git a/crates/agentic-server-core/src/executor/engine/multi_agent/delivery.rs b/crates/agentic-server-core/src/executor/engine/multi_agent/delivery.rs index ff8fb9b1b..2f4a98ccb 100644 --- a/crates/agentic-server-core/src/executor/engine/multi_agent/delivery.rs +++ b/crates/agentic-server-core/src/executor/engine/multi_agent/delivery.rs @@ -10,6 +10,7 @@ use crate::executor::{ use crate::types::{ agent::{AgentCompletion, AgentIdentity, AgentTurnKey}, agent_commands::CollaborationResult, + event::MessageStatus, io::output::OutputTextContent, io::{ AgentMessage, AgentMessageContent, FunctionToolResultMessage, InputItem, MultiAgentAction, @@ -43,6 +44,8 @@ impl MultiAgentRun { .stored .history .push(InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: Some(uuid7_str("fc_")), + status: Some(MessageStatus::Completed), call_id: call_id.clone(), output: ToolCallOutput::Text(text.clone()), })); diff --git a/crates/agentic-server-core/src/executor/engine/multi_agent_tests.rs b/crates/agentic-server-core/src/executor/engine/multi_agent_tests.rs index a5d57ccc9..299998622 100644 --- a/crates/agentic-server-core/src/executor/engine/multi_agent_tests.rs +++ b/crates/agentic-server-core/src/executor/engine/multi_agent_tests.rs @@ -483,6 +483,8 @@ async fn concurrent_children_pause_and_resume_from_durable_tree_json_and_sse() { .iter() .map(|call| { InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: call.call_id.clone(), output: ToolCallOutput::Text("proposal details".into()), }) diff --git a/crates/agentic-server-core/src/executor/engine/multi_agent_tests/live_control.rs b/crates/agentic-server-core/src/executor/engine/multi_agent_tests/live_control.rs index 82a7ed8c0..d612a448a 100644 --- a/crates/agentic-server-core/src/executor/engine/multi_agent_tests/live_control.rs +++ b/crates/agentic-server-core/src/executor/engine/multi_agent_tests/live_control.rs @@ -163,6 +163,8 @@ fn batch(response: &str, ids: &[&str]) -> ClientToolOutputBatch { .iter() .map(|id| { ClientToolOutput::Function(FunctionToolResultMessage { + id: None, + status: None, call_id: (*id).into(), output: ToolCallOutput::Text(format!("output for {id}")), }) diff --git a/crates/agentic-server-core/src/executor/multi_agent/control.rs b/crates/agentic-server-core/src/executor/multi_agent/control.rs index bdcf03c31..07566e415 100644 --- a/crates/agentic-server-core/src/executor/multi_agent/control.rs +++ b/crates/agentic-server-core/src/executor/multi_agent/control.rs @@ -180,6 +180,8 @@ mod tests { ClientToolOutputBatch { response_id: "resp_test".into(), outputs: vec![ClientToolOutput::Function(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_test".into(), output: ToolCallOutput::Text(text.into()), })], diff --git a/crates/agentic-server-core/src/executor/multi_agent/pending_calls.rs b/crates/agentic-server-core/src/executor/multi_agent/pending_calls.rs index 565495079..22c70651d 100644 --- a/crates/agentic-server-core/src/executor/multi_agent/pending_calls.rs +++ b/crates/agentic-server-core/src/executor/multi_agent/pending_calls.rs @@ -402,6 +402,8 @@ mod tests { fn function(id: &str) -> ClientToolOutput { ClientToolOutput::Function(FunctionToolResultMessage { + id: None, + status: None, call_id: id.into(), output: ToolCallOutput::Text("same output".into()), }) @@ -673,6 +675,8 @@ mod tests { .into_iter() .map(|part| { ClientToolOutput::Function(FunctionToolResultMessage { + id: None, + status: None, call_id: "large".into(), output: ToolCallOutput::Content(vec![part]), }) diff --git a/crates/agentic-server-core/src/executor/pending_calls.rs b/crates/agentic-server-core/src/executor/pending_calls.rs index 2ca65c7cb..08d1db7fd 100644 --- a/crates/agentic-server-core/src/executor/pending_calls.rs +++ b/crates/agentic-server-core/src/executor/pending_calls.rs @@ -325,6 +325,8 @@ mod tests { fn function_call_output(call_id: &str) -> InputItem { InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: call_id.to_owned(), output: ToolCallOutput::Text(String::new()), }) @@ -344,6 +346,7 @@ mod tests { fn custom_tool_call_output(call_id: &str) -> InputItem { InputItem::CustomToolCallOutput(CustomToolCallOutputMessage { + status: None, id: None, call_id: call_id.to_owned(), name: None, diff --git a/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs b/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs index c4ec0cbf1..d3b42af39 100644 --- a/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs +++ b/crates/agentic-server-core/src/executor/response_budget/client_outputs.rs @@ -70,7 +70,10 @@ impl RetainedSize for ToolCallOutput { impl RetainedSize for FunctionToolResultMessage { fn retained_bytes(&self) -> usize { - RETAINED_CONTAINER_OVERHEAD_BYTES + self.call_id.len() + self.output.retained_bytes() + RETAINED_CONTAINER_OVERHEAD_BYTES + + opt_len(self.id.as_ref()) + + self.call_id.len() + + self.output.retained_bytes() } } diff --git a/crates/agentic-server-core/src/storage/conversation.rs b/crates/agentic-server-core/src/storage/conversation.rs index 3aa9524fe..5f8acf5fd 100644 --- a/crates/agentic-server-core/src/storage/conversation.rs +++ b/crates/agentic-server-core/src/storage/conversation.rs @@ -206,7 +206,24 @@ impl ConversationStore { ) -> StoreResult<()> { let pool = self.pool()?; - let items_ = item::serialize_new_items(new_items)?; + let new_items = new_items + .into_iter() + .map(|item| { + let prefix = match &item { + InOutItem::Input(input) => input.id_prefix(), + InOutItem::Output(output) => output.id_prefix(), + }; + if item.id() == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = item + .id() + .map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok((id, data)) + }) + .collect::>>()?; + let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; @@ -230,7 +247,7 @@ impl ConversationStore { }); } } - item::create_in_tx(&mut tx, items_, Some(conversation_id)).await?; + item::create_in_tx(&mut tx, new_items, Some(conversation_id)).await?; // A response branch must see the conversation as it existed at this turn, // including manually added items that may later be detached or deleted. let history_item_ids = item::ids_for_conversation_in_tx(&mut tx, conversation_id).await?; @@ -265,13 +282,30 @@ impl ConversationStore { for (index, item) in initial_items.iter().enumerate() { item.validate_client_id(format!("items[{index}].id"))?; } - let items = item::serialize_new_items(initial_items)?; + let initial_items = initial_items + .into_iter() + .map(|item| { + let prefix = match &item { + InOutItem::Input(input) => input.id_prefix(), + InOutItem::Output(output) => output.id_prefix(), + }; + if item.id() == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = item + .id() + .map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok((id, data)) + }) + .collect::>>()?; + let metadata = metadata.map(|value| serialize_to_string(&value)).transpose()?; let id = uuid7_str("conv_"); let mut tx = self.pool()?.begin().await?; let row = conversation::create_in_tx(&mut tx, &id, tenant_id, metadata.as_deref()).await?; - if !items.is_empty() { - item::create_in_tx(&mut tx, items, Some(&id)).await?; + if !initial_items.is_empty() { + item::create_in_tx(&mut tx, initial_items, Some(&id)).await?; conversation::bump_revision_in_tx(&mut tx, &id).await?; } tx.commit().await?; @@ -327,7 +361,24 @@ impl ConversationStore { for (index, item) in items.iter().enumerate() { item.validate_client_id(format!("items[{index}].id"))?; } - let items = item::serialize_new_items(items)?; + let items = items + .into_iter() + .map(|item| { + let prefix = match &item { + InOutItem::Input(input) => input.id_prefix(), + InOutItem::Output(output) => output.id_prefix(), + }; + if item.id() == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = item + .id() + .map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok((id, data)) + }) + .collect::>>()?; + let mut tx = self.pool()?.begin().await?; lock_owned(&mut tx, tenant_id, id).await?; let rows = item::create_in_tx(&mut tx, items, Some(id)).await?; diff --git a/crates/agentic-server-core/src/storage/models/item.rs b/crates/agentic-server-core/src/storage/models/item.rs index d7e323131..0ca9155f2 100644 --- a/crates/agentic-server-core/src/storage/models/item.rs +++ b/crates/agentic-server-core/src/storage/models/item.rs @@ -1,7 +1,6 @@ //! Conversation history item stored in the database. use serde_json::Value; -use std::convert::TryFrom; use std::fmt::Write; use tracing::warn; @@ -164,29 +163,6 @@ fn item_values_clause(row_count: usize, first_bind_index: usize, sequence_from_c clause } -/// Serialize new history items, retaining public IDs already assigned to input or output items. -/// Items without IDs receive a storage-generated ID. -/// -/// # Errors -/// Returns an error if serialization fails or a supplied ID is empty. -pub(crate) fn serialize_new_items(items: Vec) -> StoreResult> { - items - .into_iter() - .map(|item| { - let (existing_id, prefix) = match &item { - InOutItem::Input(input) => (input.id(), input.id_prefix()), - InOutItem::Output(output) => (output.id(), output.id_prefix()), - }; - if existing_id == Some("") { - return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); - } - let id = existing_id.map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); - let data = String::try_from(&item)?; - Ok((id, data)) - }) - .collect() -} - /// Create items in a transaction with optional conversation context. /// /// If `conversation_id` is provided, the next sequence range is computed in the insert statement so @@ -499,161 +475,7 @@ pub async fn detach_from_conversation_in_tx( mod tests { use super::*; use crate::types::event::MessageStatus; - use crate::types::io::{ - InputItem, OutputItem, OutputMessage, ReasoningOutput, ReasoningTextContent, ResponsesInput, - }; - - #[test] - fn new_items_reuse_public_ids_and_generate_storage_ids() { - let input: InputItem = serde_json::from_value(serde_json::json!({ - "type": "message", "id": "msg_supplied", "role": "user", "content": "hello" - })) - .unwrap(); - let output = OutputItem::Message(OutputMessage::new("msg_generated", MessageStatus::Completed)); - let without_id: InputItem = serde_json::from_value(serde_json::json!({ - "type": "message", "role": "user", "content": "next" - })) - .unwrap(); - let function_call: InputItem = serde_json::from_value(serde_json::json!({ - "type": "function_call", "id": "fc_supplied", "call_id": "call_1", - "name": "lookup", "arguments": "{}" - })) - .unwrap(); - let rows = serialize_new_items(vec![ - InOutItem::Input(input), - InOutItem::Output(output), - InOutItem::Input(without_id), - InOutItem::Input(function_call), - ]) - .unwrap(); - assert_eq!(rows[0].0, "msg_supplied"); - assert_eq!(rows[1].0, "msg_generated"); - assert!(rows[2].0.starts_with("msg_")); - assert_eq!(rows[3].0, "fc_supplied"); - } - - #[test] - fn response_history_preserves_code_interpreter_public_id() { - let output = OutputItem::CodeInterpreterCall(crate::types::io::CodeInterpreterCall { - agent: None, - id: "ci_public".to_owned(), - container_id: "cntr_public".to_owned(), - code: "print(42)".to_owned(), - status: crate::types::io::CodeInterpreterCallStatus::Completed, - outputs: Some(vec![]), - origin: CodeInterpreterCallOrigin::default(), - }); - let rows = - serialize_new_items(vec![InOutItem::Output(output)]).expect("serialize code interpreter response history"); - - assert_eq!(rows[0].0, "ci_public"); - let data: Value = serde_json::from_str(&rows[0].1).expect("stored output JSON"); - assert_eq!(data["id"], "ci_public"); - assert_eq!(data["type"], "code_interpreter_call"); - } - - #[test] - fn code_interpreter_history_replays_native_call_without_gateway_duplicate() { - let gateway_call: InputItem = serde_json::from_value(serde_json::json!({ - "type": "function_call", - "id": "fc_gateway", - "call_id": "call_gateway", - "name": "code_interpreter", - "arguments": "{\"code\":\"print(42)\"}", - "status": "completed" - })) - .expect("gateway model-facing call"); - let gateway_result: InputItem = serde_json::from_value(serde_json::json!({ - "type": "function_call_output", - "call_id": "call_gateway", - "output": "42" - })) - .expect("gateway model-facing output"); - let native_call: OutputItem = serde_json::from_value(serde_json::json!({ - "type": "code_interpreter_call", - "id": "ci_native", - "container_id": "cntr_native", - "code": "print(7)", - "status": "completed", - "outputs": [{"type": "logs", "logs": "7\n"}] - })) - .expect("native upstream call"); - let mut gateway_projection = native_call.clone(); - let OutputItem::CodeInterpreterCall(call) = &mut gateway_projection else { - panic!("code interpreter call"); - }; - call.id = "ci_gateway".to_owned(); - call.container_id = "cntr_gateway".to_owned(); - call.origin = CodeInterpreterCallOrigin::Gateway; - - let stored = serialize_new_items(vec![ - InOutItem::Input(gateway_call), - InOutItem::Input(gateway_result), - InOutItem::Output(gateway_projection), - InOutItem::Output(native_call), - ]) - .expect("serialize response history"); - let gateway_data: Value = serde_json::from_str(&stored[2].1).expect("stored gateway call"); - let native_data: Value = serde_json::from_str(&stored[3].1).expect("stored native call"); - assert_eq!(gateway_data[STORED_CODE_INTERPRETER_ORIGIN_KEY], "gateway"); - assert!(native_data.get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); - - let history = stored - .into_iter() - .map(|(id, data)| { - Item { - id, - data, - created_at: 0, - conversation_id: None, - seq: None, - tenant_id: None, - reference_id: None, - public_id: None, - } - .as_inout() - .expect("rehydrate stored item") - }) - .collect(); - let inputs = InOutItem::into_input_items(history); - assert_eq!(inputs.len(), 3); - assert!(matches!(&inputs[0], InputItem::FunctionCall(call) if call.call_id == "call_gateway")); - assert!(matches!(&inputs[1], InputItem::FunctionCallOutput(output) if output.call_id == "call_gateway")); - assert!( - matches!(&inputs[2], InputItem::CodeInterpreterCall(call) if call.id == "ci_native" - && call.code == "print(7)" && call.outputs.as_ref().is_some_and(|outputs| outputs.len() == 1)) - ); - let model_input = - serde_json::to_value(ResponsesInput::from(inputs).model_input()).expect("model input serializes"); - assert_eq!(model_input[2]["type"], "code_interpreter_call"); - assert!(model_input[2].get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); - } - - #[test] - fn serialize_new_items_rejects_empty_id() { - let input: InputItem = serde_json::from_value(serde_json::json!({ - "type": "message", "id": "", "role": "user", "content": "test" - })) - .unwrap(); - let result = serialize_new_items(vec![InOutItem::Input(input)]); - assert!(result.is_err()); - let err = result.unwrap_err(); - assert!(err.is_validation()); - assert!(matches!(err, StorageError::Validation(_))); - } - - #[test] - fn serialize_new_items_accepts_missing_id() { - let input: InputItem = serde_json::from_value(serde_json::json!({ - "type": "message", "role": "user", "content": "test" - })) - .unwrap(); - let result = serialize_new_items(vec![InOutItem::Input(input)]); - assert!(result.is_ok()); - let rows = result.unwrap(); - assert_eq!(rows.len(), 1); - assert!(rows[0].0.starts_with("msg_")); - } + use crate::types::io::{InputItem, OutputItem, ReasoningOutput, ReasoningTextContent}; #[test] fn item_values_clause_numbers_plain_rows() { @@ -1057,6 +879,10 @@ mod item_id_tests { json!({"type":"message", "id":"msg_1", "role":"user", "content":"hello"}), json!({"type":"function_call", "id":"fc_1", "call_id":"call_1", "name":"f", "arguments":"{}"}), json!({"type":"tool_search_call", "id":"tsc_1", "call_id":"call_1", "arguments":{}}), + json!({"type":"function_call_output", "id":"fc_1", "call_id":"call_1", "output":"done"}), + json!({"type":"custom_tool_call_output", "id":"ctco_1", "call_id":"call_1", "output":"done"}), + json!({"type":"shell_call_output", "id":"sho_1", "call_id":"call_1", "output":[]}), + json!({"type":"tool_search_output", "id":"tso_1", "call_id":"call_1", "tools":[]}), json!({"type":"custom_tool_call", "id":"ctc_1", "call_id":"call_1", "name":"f", "input":"x"}), json!({"type":"shell_call", "id":"sh_1", "call_id":"call_1", "action":{"commands":["pwd"]}}), json!({"type":"reasoning", "id":"rs_1", "summary":[]}), @@ -1078,12 +904,12 @@ mod item_id_tests { } #[test] - fn optional_ids_and_unverified_prefixes_are_not_restricted() { + fn optional_ids_are_not_required() { for value in [ json!({"type":"message", "role":"user", "content":"hello"}), json!({"type":"function_call", "call_id":"call_1", "name":"f", "arguments":"{}"}), json!({"type":"function_call_output", "call_id":"call_1", "output":"done"}), - json!({"type":"shell_call_output", "id":"output_1", "call_id":"call_1", "output":[]}), + json!({"type":"shell_call_output", "call_id":"call_1", "output":[]}), ] { stored(value).validate_client_id("items[0].id".into()).unwrap(); } diff --git a/crates/agentic-server-core/src/storage/response.rs b/crates/agentic-server-core/src/storage/response.rs index 1eedba2f7..760f73be4 100644 --- a/crates/agentic-server-core/src/storage/response.rs +++ b/crates/agentic-server-core/src/storage/response.rs @@ -8,7 +8,7 @@ use super::models::{item, response}; use super::pool::DbPool; use super::types::{ResponseData, ResponseMetadata, StorageError, StoreResult}; use crate::types::io::InOutItem; -use crate::utils::common::{deserialize_from_str, serialize_to_string}; +use crate::utils::common::{deserialize_from_str, serialize_to_string, uuid7_str}; /// Response storage operations. #[derive(Clone, Debug)] @@ -174,13 +174,30 @@ impl ResponseStore { Some(prev_id) => self.get(prev_id).await?.history_item_ids, None => Vec::new(), }; - let items_ = item::serialize_new_items(new_items)?; + let new_items = new_items + .into_iter() + .map(|item| { + let prefix = match &item { + InOutItem::Input(input) => input.id_prefix(), + InOutItem::Output(output) => output.id_prefix(), + }; + if item.id() == Some("") { + return Err(StorageError::Validation("item id cannot be an empty string".to_owned())); + } + let id = item + .id() + .map_or_else(|| uuid7_str(prefix.unwrap_or("item_")), str::to_owned); + let data = String::try_from(&item)?; + Ok((id, data)) + }) + .collect::>>()?; + let metadata_json = String::try_from(metadata)?; let mut tx = pool.begin().await?; // History rows get their own row IDs, so repeated public IDs keep every position. - let created = item::create_in_tx(&mut tx, items_, None).await?; + let created = item::create_in_tx(&mut tx, new_items, None).await?; item_ids.extend(created.into_iter().map(|item| item.id)); let history_item_ids_json = serialize_to_string(&item_ids)?; @@ -203,6 +220,202 @@ impl ResponseStore { mod tests { use super::super::types::ResponseMetadata; use super::*; + use crate::storage::create_pool_with_schema; + use crate::storage::types::item::STORED_CODE_INTERPRETER_ORIGIN_KEY; + use crate::types::event::MessageStatus; + use crate::types::io::code_interpreter::CodeInterpreterCallOrigin; + use crate::types::io::{ + CodeInterpreterCall, CodeInterpreterCallStatus, InputItem, OutputItem, OutputMessage, ResponsesInput, + }; + use serde_json::Value; + + #[tokio::test] + async fn new_items_reuse_public_ids_and_generate_storage_ids() { + let pool = create_pool_with_schema(Some("sqlite://?mode=memory")).await.unwrap(); + let store = ResponseStore::new(pool); + let input: InputItem = serde_json::from_value(serde_json::json!({ + "type": "message", "id": "msg_supplied", "role": "user", "content": "hello" + })) + .unwrap(); + let output = OutputItem::Message(OutputMessage::new("msg_generated", MessageStatus::Completed)); + let without_id: InputItem = serde_json::from_value(serde_json::json!({ + "type": "message", "role": "user", "content": "next" + })) + .unwrap(); + let function_call: InputItem = serde_json::from_value(serde_json::json!({ + "type": "function_call", "id": "fc_supplied", "call_id": "call_1", + "name": "lookup", "arguments": "{}" + })) + .unwrap(); + store + .persist( + "resp_items", + None, + vec![ + InOutItem::Input(input), + InOutItem::Output(output), + InOutItem::Input(without_id), + InOutItem::Input(function_call), + ], + &ResponseMetadata::default(), + ) + .await + .unwrap(); + let rows = store.get_items("resp_items").await.unwrap(); + assert_eq!(rows[0].public_id(), "msg_supplied"); + assert_eq!(rows[1].public_id(), "msg_generated"); + assert!(rows[2].public_id().starts_with("msg_")); + assert_eq!(rows[3].public_id(), "fc_supplied"); + } + + #[tokio::test] + async fn response_history_preserves_code_interpreter_public_id() { + let pool = create_pool_with_schema(Some("sqlite://?mode=memory")).await.unwrap(); + let store = ResponseStore::new(pool); + let output = OutputItem::CodeInterpreterCall(CodeInterpreterCall { + agent: None, + id: "ci_public".to_owned(), + container_id: "cntr_public".to_owned(), + code: "print(42)".to_owned(), + status: CodeInterpreterCallStatus::Completed, + outputs: Some(vec![]), + origin: CodeInterpreterCallOrigin::default(), + }); + store + .persist( + "resp_items", + None, + vec![InOutItem::Output(output)], + &ResponseMetadata::default(), + ) + .await + .expect("serialize code interpreter response history"); + + let rows = store.get_items("resp_items").await.unwrap(); + assert_eq!(rows[0].public_id(), "ci_public"); + let data: Value = serde_json::from_str(&rows[0].data).expect("stored output JSON"); + assert_eq!(data["id"], "ci_public"); + assert_eq!(data["type"], "code_interpreter_call"); + } + + #[tokio::test] + async fn code_interpreter_history_replays_native_call_without_gateway_duplicate() { + let pool = create_pool_with_schema(Some("sqlite://?mode=memory")).await.unwrap(); + let store = ResponseStore::new(pool); + let gateway_call: InputItem = serde_json::from_value(serde_json::json!({ + "type": "function_call", + "id": "fc_gateway", + "call_id": "call_gateway", + "name": "code_interpreter", + "arguments": "{\"code\":\"print(42)\"}", + "status": "completed" + })) + .expect("gateway model-facing call"); + let gateway_result: InputItem = serde_json::from_value(serde_json::json!({ + "type": "function_call_output", + "call_id": "call_gateway", + "output": "42" + })) + .expect("gateway model-facing output"); + let native_call: OutputItem = serde_json::from_value(serde_json::json!({ + "type": "code_interpreter_call", + "id": "ci_native", + "container_id": "cntr_native", + "code": "print(7)", + "status": "completed", + "outputs": [{"type": "logs", "logs": "7\n"}] + })) + .expect("native upstream call"); + let mut gateway_projection = native_call.clone(); + let OutputItem::CodeInterpreterCall(call) = &mut gateway_projection else { + panic!("code interpreter call"); + }; + call.id = "ci_gateway".to_owned(); + call.container_id = "cntr_gateway".to_owned(); + call.origin = CodeInterpreterCallOrigin::Gateway; + + store + .persist( + "resp_items", + None, + vec![ + InOutItem::Input(gateway_call), + InOutItem::Input(gateway_result), + InOutItem::Output(gateway_projection), + InOutItem::Output(native_call), + ], + &ResponseMetadata::default(), + ) + .await + .expect("serialize response history"); + let stored = store.get_items("resp_items").await.unwrap(); + let gateway_data: Value = serde_json::from_str(&stored[2].data).expect("stored gateway call"); + let native_data: Value = serde_json::from_str(&stored[3].data).expect("stored native call"); + assert_eq!(gateway_data[STORED_CODE_INTERPRETER_ORIGIN_KEY], "gateway"); + assert!(native_data.get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); + + let history = stored + .into_iter() + .map(|row| row.as_inout().expect("rehydrate stored item")) + .collect(); + let inputs = InOutItem::into_input_items(history); + assert_eq!(inputs.len(), 3); + assert!(matches!(&inputs[0], InputItem::FunctionCall(call) if call.call_id == "call_gateway")); + assert!(matches!(&inputs[1], InputItem::FunctionCallOutput(output) if output.call_id == "call_gateway")); + assert!( + matches!(&inputs[2], InputItem::CodeInterpreterCall(call) if call.id == "ci_native" + && call.code == "print(7)" && call.outputs.as_ref().is_some_and(|outputs| outputs.len() == 1)) + ); + let model_input = + serde_json::to_value(ResponsesInput::from(inputs).model_input()).expect("model input serializes"); + assert_eq!(model_input[2]["type"], "code_interpreter_call"); + assert!(model_input[2].get(STORED_CODE_INTERPRETER_ORIGIN_KEY).is_none()); + } + + #[tokio::test] + async fn create_items_rejects_empty_id() { + let pool = create_pool_with_schema(Some("sqlite://?mode=memory")).await.unwrap(); + let store = ResponseStore::new(pool); + let input: InputItem = serde_json::from_value(serde_json::json!({ + "type": "message", "id": "", "role": "user", "content": "test" + })) + .unwrap(); + let result = store + .persist( + "resp_items", + None, + vec![InOutItem::Input(input)], + &ResponseMetadata::default(), + ) + .await; + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!(err.is_validation()); + assert!(matches!(err, StorageError::Validation(_))); + } + + #[tokio::test] + async fn create_items_accepts_missing_id() { + let pool = create_pool_with_schema(Some("sqlite://?mode=memory")).await.unwrap(); + let store = ResponseStore::new(pool); + let input: InputItem = serde_json::from_value(serde_json::json!({ + "type": "message", "role": "user", "content": "test" + })) + .unwrap(); + let result = store + .persist( + "resp_items", + None, + vec![InOutItem::Input(input)], + &ResponseMetadata::default(), + ) + .await; + assert!(result.is_ok()); + result.unwrap(); + let rows = store.get_items("resp_items").await.unwrap(); + assert_eq!(rows.len(), 1); + assert!(rows[0].public_id().starts_with("msg_")); + } #[test] fn test_response_store_disabled() { diff --git a/crates/agentic-server-core/src/tool/normalize.rs b/crates/agentic-server-core/src/tool/normalize.rs index 6e7020af7..3ef85c3a2 100644 --- a/crates/agentic-server-core/src/tool/normalize.rs +++ b/crates/agentic-server-core/src/tool/normalize.rs @@ -1,5 +1,7 @@ +use crate::types::event::MessageStatus; use crate::types::io::FunctionTool; use crate::types::io::input::FunctionToolResultMessage; +use crate::utils::uuid7_str; use super::code_interpreter::CodeInterpreterHandler; use super::codex::CodexNamespaceHandler; @@ -146,6 +148,8 @@ impl ToolDeclaration { impl From for FunctionToolResultMessage { fn from(o: ToolOutput) -> Self { Self { + id: Some(uuid7_str("fc_")), + status: Some(MessageStatus::Completed), call_id: o.call_id, output: o.output.into(), } diff --git a/crates/agentic-server-core/src/tool/tool_search.rs b/crates/agentic-server-core/src/tool/tool_search.rs index ccb44d343..602d26cb4 100644 --- a/crates/agentic-server-core/src/tool/tool_search.rs +++ b/crates/agentic-server-core/src/tool/tool_search.rs @@ -1157,14 +1157,14 @@ fn prepare_search_output( load_definition(tool, definition_accumulator)?; } let projected_tools = model_visible_output_tools(&output.tools)?; - let canonical_value = serialize_to_value(&CanonicalToolSearchOutput { + let canonical_output = serialize_to_string(&CanonicalToolSearchOutput { tools: &projected_tools, }) .map_err(|_| ToolError::Config("tool_search_output could not be canonicalized safely".to_owned()))?; - let canonical_output = serialize_to_string(&canonical_value) - .map_err(|_| ToolError::Config("tool_search_output could not be canonicalized safely".to_owned()))?; completed_call_ids.insert(output.call_id.clone()); Ok(InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: output.call_id.clone(), output: ToolCallOutput::Text(canonical_output), })) diff --git a/crates/agentic-server-core/src/types/injection.rs b/crates/agentic-server-core/src/types/injection.rs index 9a8043dda..5dc91ca3e 100644 --- a/crates/agentic-server-core/src/types/injection.rs +++ b/crates/agentic-server-core/src/types/injection.rs @@ -58,6 +58,8 @@ pub struct InjectionFunctionOutput { impl From for FunctionToolResultMessage { fn from(output: InjectionFunctionOutput) -> Self { Self { + id: None, + status: None, call_id: output.call_id, output: output.output, } diff --git a/crates/agentic-server-core/src/types/io/input.rs b/crates/agentic-server-core/src/types/io/input.rs index 765bc104f..4afe6d7c2 100644 --- a/crates/agentic-server-core/src/types/io/input.rs +++ b/crates/agentic-server-core/src/types/io/input.rs @@ -77,6 +77,10 @@ impl Default for InputMessageContent { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct FunctionToolResultMessage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub status: Option, pub call_id: String, pub output: ToolCallOutput, } @@ -209,18 +213,6 @@ impl From for InputFunctionToolCall { } } -impl From for FunctionToolResultMessage { - fn from(output: ShellCallOutputMessage) -> Self { - Self { - call_id: output.call_id, - // Command outputs contain only JSON-compatible values and string map keys. - output: serde_json::to_string(&output.output) - .expect("shell outputs serialize to JSON") - .into(), - } - } -} - pub(super) fn deserialize_non_blank_string<'de, D>(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -297,6 +289,8 @@ pub struct CompactionItem { #[derive(Debug, Clone, Serialize, Deserialize)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] pub struct CustomToolCallOutputMessage { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub status: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option, pub call_id: String, @@ -305,15 +299,6 @@ pub struct CustomToolCallOutputMessage { pub output: ToolCallOutput, } -impl From for FunctionToolResultMessage { - fn from(output: CustomToolCallOutputMessage) -> Self { - Self { - call_id: output.call_id, - output: output.output, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type")] pub enum InputItem { diff --git a/crates/agentic-server-core/src/types/io/input/conversions.rs b/crates/agentic-server-core/src/types/io/input/conversions.rs index fa8fc96c6..e20f82636 100644 --- a/crates/agentic-server-core/src/types/io/input/conversions.rs +++ b/crates/agentic-server-core/src/types/io/input/conversions.rs @@ -1,12 +1,42 @@ -use crate::types::io::{InOutItem, OutputMessage, OutputMessageContent}; +use crate::types::event::MessageStatus; +use crate::types::io::{InOutItem, OutputMessage, OutputMessageContent, ShellCallOutputMessage, ShellCallStatus}; use serde_json::Value; use super::{ - InputContent, InputItem, InputMessage, InputMessageContent, InputMessageType, InputTextContent, - InputValidationError, ResponsesInput, + CustomToolCallOutputMessage, FunctionToolResultMessage, InputContent, InputItem, InputMessage, InputMessageContent, + InputMessageType, InputTextContent, InputValidationError, ResponsesInput, }; use std::borrow::Cow; +impl From for FunctionToolResultMessage { + fn from(output: ShellCallOutputMessage) -> Self { + Self { + id: output.id, + status: match output.status { + Some(ShellCallStatus::Completed) => Some(MessageStatus::Completed), + Some(ShellCallStatus::InProgress) => Some(MessageStatus::InProgress), + Some(ShellCallStatus::Incomplete) | None => None, + }, + call_id: output.call_id, + // Command outputs contain only JSON-compatible values and string map keys. + output: serde_json::to_string(&output.output) + .expect("shell outputs serialize to JSON") + .into(), + } + } +} + +impl From for FunctionToolResultMessage { + fn from(output: CustomToolCallOutputMessage) -> Self { + Self { + id: output.id, + status: output.status, + call_id: output.call_id, + output: output.output, + } + } +} + impl From for InputContent { fn from(part: OutputMessageContent) -> Self { match part { @@ -60,10 +90,29 @@ impl ResponsesInput { /// Move request input into canonical items without normalizing tool types, /// removing items, or applying model-specific content defaults. /// Text becomes one user message, including when the text is empty. + /// Submitted tool results default to completed before persistence and model + /// lowering. This describes delivery of the result, even if its payload + /// reports an error; explicit statuses and call execution states are retained. #[must_use] pub fn into_canonical_items(self) -> Vec { match self { - Self::Items(items) => items, + Self::Items(mut items) => { + for item in &mut items { + match item { + InOutItem::Input(InputItem::FunctionCallOutput(output)) => { + output.status.get_or_insert(MessageStatus::Completed); + } + InOutItem::Input(InputItem::CustomToolCallOutput(output)) => { + output.status.get_or_insert(MessageStatus::Completed); + } + InOutItem::Input(InputItem::ShellCallOutput(output)) => { + output.status.get_or_insert(ShellCallStatus::Completed); + } + _ => {} + } + } + items + } // Reuse the existing user-message conversion only for text. Its // item-array branch also lowers custom/shell items for inference. text @ Self::Text(_) => Vec::::from(text).into_iter().map(InOutItem::Input).collect(), @@ -167,15 +216,30 @@ mod tests { use crate::utils::common::{deserialize_from_value, serialize_to_value}; use serde_json::json; + #[test] + fn canonical_results_default_to_completed_even_when_their_payload_reports_failure() { + for wire in [ + json!({"type":"function_call_output", "call_id":"call_1", "output":"error: lookup failed"}), + json!({"type":"custom_tool_call_output", "call_id":"call_1", "output":"error: lookup failed"}), + json!({"type":"shell_call_output", "call_id":"call_1", "output":[{"stdout":"", "stderr":"failed", "outcome":{"type":"exit", "exit_code":1}}]}), + ] { + let item: InOutItem = serde_json::from_value(wire.clone()).unwrap(); + let items = ResponsesInput::Items(vec![item]).into_canonical_items(); + let public = serde_json::to_value(&items[0]).unwrap(); + assert_eq!(public["status"], "completed"); + assert_eq!(public["output"], wire["output"]); + } + } + #[test] fn canonical_items_preserve_custom_shell_and_unknown_items() { let mut items: Vec = deserialize_from_value(json!([ {"type":"custom_tool_call", "id":"ctc_notes", "call_id":"notes", "name":"record_notes", "input":"note"}, - {"type":"custom_tool_call_output", "call_id":"notes", "output":"recorded"}, + {"type":"custom_tool_call_output", "call_id":"notes", "output":"recorded", "status":"in_progress"}, {"type":"shell_call", "id":"sh_test", "call_id":"shell", "action":{ "commands":["pwd"], "timeout_ms":1000, "max_output_length":1024 }}, - {"type":"shell_call_output", "call_id":"shell", "output":[{ + {"type":"shell_call_output", "call_id":"shell", "status":"incomplete", "output":[{ "stdout":"/tmp", "stderr":"", "outcome":{"type":"exit", "exit_code":0} }]} ])) @@ -211,7 +275,7 @@ mod tests { #[test] fn canonical_items_keep_media_defaults_separate_from_model_normalization() { let input: ResponsesInput = deserialize_from_value(json!([ - {"type":"custom_tool_call_output", "call_id":"notes", "output":"recorded"}, + {"type":"custom_tool_call_output", "call_id":"notes", "output":"recorded", "status":"in_progress"}, {"role":"user", "content":[{"type":"input_image", "image_url":"data:image/png;base64,test"}]} ])) .unwrap(); diff --git a/crates/agentic-server-core/src/types/io/item/item_id.rs b/crates/agentic-server-core/src/types/io/item/item_id.rs index b95f18bdd..426733759 100644 --- a/crates/agentic-server-core/src/types/io/item/item_id.rs +++ b/crates/agentic-server-core/src/types/io/item/item_id.rs @@ -24,7 +24,8 @@ impl InputItem { Self::McpCall(item) => Some(&item.id), Self::ToolSearchOutput(item) => item.id.as_deref(), Self::CustomToolCallOutput(item) => item.id.as_deref(), - Self::FunctionCallOutput(_) | Self::CompactionTrigger => None, + Self::FunctionCallOutput(item) => item.id.as_deref(), + Self::CompactionTrigger => None, } } @@ -37,7 +38,7 @@ impl InputItem { Self::MultiAgentCallOutput(_) => Some("maco_"), Self::AgentMessage(_) => Some("amsg_"), Self::Message(_) => Some("msg_"), - Self::FunctionCall(_) => Some("fc_"), + Self::FunctionCall(_) | Self::FunctionCallOutput(_) => Some("fc_"), Self::CodeInterpreterCall(_) => Some("ci_"), Self::ToolSearchCall(_) => Some("tsc_"), Self::CustomToolCall(_) => Some("ctc_"), @@ -47,11 +48,10 @@ impl InputItem { Self::Compaction(_) => Some("cmp_"), Self::WebSearchCall(_) => Some("ws_"), Self::McpCall(_) => Some("mcp_"), - Self::ShellCallOutput(_) - | Self::FunctionCallOutput(_) - | Self::ToolSearchOutput(_) - | Self::CustomToolCallOutput(_) - | Self::CompactionTrigger => None, + Self::ShellCallOutput(_) => Some("sho_"), + Self::CustomToolCallOutput(_) => Some("ctco_"), + Self::ToolSearchOutput(_) => Some("tso_"), + Self::CompactionTrigger => None, } } } @@ -134,7 +134,8 @@ impl InOutItem { InputItem::McpListTools(item) => item.id = id, InputItem::WebSearchCall(item) => item.id = id, InputItem::McpCall(item) => item.id = id, - InputItem::FunctionCallOutput(_) | InputItem::CompactionTrigger => {} + InputItem::FunctionCallOutput(item) => item.id = Some(id), + InputItem::CompactionTrigger => {} }, Self::Output(item) => match item { OutputItem::MultiAgentCall(item) => item.id = id, diff --git a/crates/agentic-server-core/src/types/io/item/tests.rs b/crates/agentic-server-core/src/types/io/item/tests.rs index 927281d8b..e1f14e55d 100644 --- a/crates/agentic-server-core/src/types/io/item/tests.rs +++ b/crates/agentic-server-core/src/types/io/item/tests.rs @@ -252,7 +252,7 @@ fn manually_added_assistant_shorthand_remains_input_text() { #[test] fn public_item_ids_are_typed_and_preserve_tool_and_orchestration_payloads() { for wire in [ - json!({"type":"custom_tool_call_output", "id":"old", "call_id":"call_1", "output":"done"}), + json!({"type":"custom_tool_call_output", "id":"old", "call_id":"call_1", "output":"done", "status":"in_progress"}), json!({"type":"tool_search_output", "id":"old", "call_id":"call_1", "tools":[]}), json!({"type":"web_search_call", "id":"old", "status":"completed", "action":{"type":"search", "query":"weather"}}), json!({"type":"mcp_list_tools", "id":"old", "server_label":"weather", "tools":[]}), @@ -297,3 +297,25 @@ fn empty_pages_match_wire_contract() { json!({"object":"list", "data":[], "has_more":false, "first_id":null, "last_id":null}) ); } + +#[test] +fn public_items_preserve_failed_and_incomplete_tool_states() { + for wire in [ + json!({"type":"code_interpreter_call", "id":"ci_1", "container_id":"cntr_1", "code":"1/0", "outputs":[], "status":"failed"}), + json!({"type":"web_search_call", "id":"ws_1", "action":{"type":"search", "query":"weather"}, "status":"failed"}), + json!({"type":"tool_search_call", "id":"tsc_1", "call_id":"call_1", "arguments":{}, "status":"incomplete"}), + json!({"type":"tool_search_output", "id":"tso_1", "call_id":"call_1", "tools":[], "status":"incomplete"}), + json!({"type":"mcp_call", "id":"mcp_1", "server_label":"weather", "name":"forecast", "arguments":"{}", "error":"unavailable", "status":"failed"}), + json!({"type":"shell_call_output", "id":"sho_1", "call_id":"call_1", "output":[], "status":"incomplete"}), + json!({"type":"function_call_output", "id":"fc_1", "call_id":"call_1", "output":"partial", "status":"in_progress"}), + json!({"type":"custom_tool_call_output", "id":"ctco_1", "call_id":"call_1", "output":"partial", "status":"in_progress"}), + ] { + let item: InOutItem = serde_json::from_value(wire).unwrap(); + let mut expected = serde_json::to_value(&item).unwrap(); + expected["id"] = json!("stored"); + assert_eq!( + serde_json::to_value(item.into_public("stored".into())).unwrap(), + expected + ); + } +} diff --git a/crates/agentic-server-core/tests/cassettes/README.md b/crates/agentic-server-core/tests/cassettes/README.md index d0d0e6db1..9e49482a0 100644 --- a/crates/agentic-server-core/tests/cassettes/README.md +++ b/crates/agentic-server-core/tests/cassettes/README.md @@ -49,7 +49,7 @@ than assuming that OpenAI and the gateway accept the same requests. | `edge-cases` | 23 | Unstored/missing IDs; missing parent; invalid, repeated and out-of-range query parameters; unknown and repeated includes; repeated deletion. | | `conversation` | 19 | Responses attached to a conversation, manual item insertion/deletion, snapshots before and after mutations, response deletion, conversation deletion. | | `replayed-items` | 14 | Generated-ID reuse, two copies of an ID with different content, wrong-prefix IDs, function call/output history without public item IDs, seed immutability after probes. | -| `tool-items` | 40 | Replay existing OpenAI web-search, MCP, code-interpreter, shell and custom-tool histories; inspect input items with original tool fields, omitted IDs, omitted statuses, and both omitted. | +| `tool-items` | 48 plus additional pages | Replay existing parallel builtin/client/mixed tools, shell, code interpreter, tool search, and the multi-agent review task; inspect complete input pages after each response and a final continuation. | From the worktree root, with an inference server already running: @@ -84,35 +84,57 @@ Files are named `responses/response--.yaml`, with `-stream` appended to the scenario for streaming runs. Incomplete recordings remain `.yaml.tmp` and do not replace a previously validated capture. -The `tool-items` suite loads the existing non-streaming OpenAI references in -`web_search/`, `mcp/`, `code_interpreter/`, `shell/`, and `custom_tool/`. It keeps -the complete recorded history, including reasoning dependencies and call/output -pairs, and submits it in a new stored response without enabling tools. Only the -selected tool items' `id` and `status` fields are omitted in the variant probes. -Each creation is followed by `GET /responses/{id}/input_items`; rejected creations -are followed by a missing-response lookup, never another response's history. -These admission probes always use JSON and are recorded once even when both -transports are selected. To refresh just this reference: +The `tool-items` suite uses `replay_old_cassettes.py` through the shared recorder. +It replays the original tool-enabled requests from the non-streaming OpenAI +`parallel_tool_calls/` builtin-only, client-only and mixed suites, plus `shell/` +and `code_interpreter/`, `tool_search/`, and only the `multi_agent/` review case. MCP uses the public documentation-search server declared +in the parallel fixture. Client outputs use the existing simulated tool callbacks +with the live response's calls and IDs; no shell commands are executed. + +Each successful creation is followed by `GET /responses/{id}/input_items`, +following `has_more` and `last_id` until the complete input history is captured. +A final continuation resolves pending client calls and makes the preceding output +visible in the next input-items page. Rejected creations are recorded and end that +scenario, so configuration failures can produce fewer than 48 HTTP turns. Pagination +can produce additional GET requests even with `limit=100`. These +captures use JSON even when both transports are selected. To refresh them: ```bash -RESPONSES_RECORD_SET=openai RESPONSES_SCENARIO=tool-items OPENAI_MODEL=gpt-5.6 \ +RESPONSES_RECORD_SET=all RESPONSES_SCENARIO=tool-items OPENAI_MODEL=gpt-5.6 \ + GATEWAY_MODEL=Qwen/Qwen3.8-27B-FP8 \ bash crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh ``` -Use the source recordings' reasoning-compatible model for this suite. The recorded -reference accepts 18 of 20 creations: code-interpreter items require an ID, but -OpenAI assigns them new IDs in these replays. All -accepted tool items have resource IDs in the input-items page. `mcp_list_tools` -has no status; the other selected items have statuses. Shell/custom calls without -IDs are listed as `in_progress`, including when a completed custom call was -submitted; with their original recorded IDs they are listed as `completed`. -Their output items are listed as `completed`. These observations distinguish -replaying existing item identities from submitting history without identities; -they do not justify a universal status default. - -`recorded_tool_history_ids_and_statuses` asserts this OpenAI reference directly. -The new suite does not yet establish live gateway parity; the five existing -management suites below continue to run through the gateway. +For the review case, `OPENAI_MULTI_AGENT_MODEL` defaults to `gpt-5.6-sol`; the +recorder preserves the fixture's multi-agent beta header. Gateway cases always use +`GATEWAY_MODEL`. Tool-search continuations reuse the fixture's discovered definitions +and client output callbacks through the shared recorder. + +The gateway needs MCP access to the fixture's server and an enabled code-interpreter +runtime to complete those cases. Recorded failures do not establish tool parity. + +`recorded_tool_execution_ids_and_statuses` checks both provider captures, including +complete pagination, tool item ID prefixes and statuses, and parent output identity +in continuation input pages. The live gateway replays cover the five +management suites plus client-tool and tool-search input pages from the gateway execution capture. +Those tool replays assert result IDs and completed statuses against the OpenAI +contract even when the older gateway capture lacks those fields. OpenAI's opaque +reasoning cannot serve as vLLM inference history, so the live client-tool replay +uses the gateway's own recorded reasoning without dropping it. Separate web-search +and MCP execution regressions verify that stored tool call/result pairs remain in +both inference history and input-item listings. + +The refreshed gateway capture contains 48 successful HTTP exchanges, including code +interpreter, tool search, and the multi-agent review case. All fifteen tool and +orchestration item types match the OpenAI reference's ID-prefix and status contracts; +parent output identities are preserved in continuation input pages. The Rust replay +executes the client function/custom, shell, and tool-search flows (13 Responses +requests plus their input pages) through a local gateway with inference mocked. +This validates the recorded successful flows, not every possible tool failure. + +`HTTP_READ_TIMEOUT` overrides the recorder's upstream read timeout. Tool-item +recordings default to 900 seconds, matching the existing multi-agent recorder; +other management scenarios default to 300 seconds. `RESPONSES_CASSETTES_DIR` overrides the output/replay directory and `RECORDER_PROXY_PORT` overrides the local proxy port (default 7070). @@ -128,14 +150,17 @@ the OpenAI reference. Log probabilities are excluded from item comparisons. The lifecycle suite also checks that the continuation retains the surviving child's history and new user message after parent deletion, and inspects the live gateway's upstream request to ensure the deleted parent's user message is not sent to inference. -Default statuses for client-supplied function history are deferred to the item-type -refactor: OpenAI lists an omitted function-call status as `in_progress` and an omitted -function-result status as `completed`; the gateway currently leaves them absent. -Comparisons tolerate only absence versus these two recorded defaults. Other status -values remain checked, and the captured YAML is unchanged. `FunctionToolResultMessage` -retains only `call_id` and `output`; function-result resource IDs and supplied statuses -are also deferred to that refactor, so these replays do not establish parity for those -fields. Function results still retain their `call_id` association. +Client-supplied function-call status defaults remain deferred: OpenAI lists an +omitted call status as `in_progress`; these comparisons tolerate its absence while +checking other values. Function, custom, and shell result types retain their IDs +and statuses. Stored results use `fc_`, `ctco_`, and `sho_` IDs respectively. +Request input conversion fills omitted result statuses as `completed` before +persistence, without changing explicit statuses or call execution states. +Gateway-created function results receive their identity and completed status at +creation. Input-item listings return stored items without classifying gateway call/result +pairs by their projected IDs. The gateway tool capture predates removal of that +filter, so its built-in-tool pages must be re-recorded before claiming parity for +this listing behavior. The captured YAML is retained unchanged. Stateless creation remains upstream pass-through on this branch, so its optional `store` response field is not compared across providers; subsequent CRUD calls verify that the response was not retained. Differences in model-generated output diff --git a/crates/agentic-server-core/tests/cassettes/record_cassette.py b/crates/agentic-server-core/tests/cassettes/record_cassette.py index 5f5acecfc..8c9d6aa00 100644 --- a/crates/agentic-server-core/tests/cassettes/record_cassette.py +++ b/crates/agentic-server-core/tests/cassettes/record_cassette.py @@ -63,6 +63,8 @@ from yaml import dump as yaml_dump from yaml import safe_load as yaml_load +from replay_old_cassettes import run_tool_items + logging.basicConfig(level=logging.WARNING) logger = logging.getLogger("cassette_proxy") @@ -354,11 +356,13 @@ def _item_request( *, body: dict | None = None, params: dict | list[tuple[str, str]] | None = None, + headers: dict[str, str] | None = None, expected_status: int | None = 200, ) -> dict: """Send management requests through the shared recording proxy.""" response = client.request( - method, f"{proxy_url}{path}", json=body, params=params, timeout=TIMEOUT + method, f"{proxy_url}{path}", json=body, params=params, headers=headers, + timeout=_http_timeout(proxy_client=True), ) if expected_status is not None and response.status_code != expected_status: raise RuntimeError( @@ -426,49 +430,10 @@ def run_items( ) -> None: """Record each fixed Conversation Items or Responses API request as a numbered step.""" if scenario == "response-tool-items": - # Reuse the complete captured history, including reasoning dependencies - # and client-side results. Do not execute the tools again. - references = ( - ("web_search/web-search-openai-reference-gpt-5.6-nonstreaming.yaml", - {"web_search_call"}), - ("mcp/mcp-openai-reference-counter-say-hello-gpt-4o-nonstreaming.yaml", - {"mcp_list_tools", "mcp_call"}), - ("code_interpreter/code-interpreter-openai-reference-gpt-5.6-nonstreaming.yaml", - {"code_interpreter_call"}), - ("shell/shell-openai-reference-success-gpt-5.6-nonstreaming.yaml", - {"shell_call", "shell_call_output"}), - ("custom_tool/custom-tool-openai-reference-gpt-5.6-nonstreaming.yaml", - {"custom_tool_call", "custom_tool_call_output"}), - ) - for filename, item_types in references: - reference = yaml_load((Path(__file__).parent / filename).read_text(encoding="utf-8")) - history = [] - for turn in reference["turns"]: - inputs = turn["request"]["body"].get("input", []) - if isinstance(inputs, str): - history.append({"role": "user", "content": inputs}) - else: - history.extend(inputs) - history.extend(turn["response"]["body"]["output"]) - if not item_types <= {item.get("type") for item in history}: - raise RuntimeError(f"{filename}: missing expected tool history") - for omitted in ((), ("id",), ("status",), ("id", "status")): - print(f"[tool history: {filename}; omitted fields: {omitted}]") - items = [{key: value for key, value in item.items() - if item.get("type") not in item_types or key not in omitted} - for item in history] - result = _item_request( - client, proxy_url, "POST", "/v1/responses", - body={"model": model, "store": True, "stream": False, - "input": [*items, {"role": "user", "content": - "This is completed tool history. Do not call tools. Reply only OK."}]}, - expected_status=None, - ) - # Rejections are evidence too. Never substitute another - # response's history when admission fails. - response_id = result.get("id", "resp_missing") - _item_request(client, proxy_url, "GET", f"/v1/responses/{response_id}/input_items", - params={"order": "asc", "limit": 100}, expected_status=None) + run_tool_items(client, proxy_url, model, send_request=_item_request, + extract_tool_calls=_extract_tool_calls, + load_tool_outputs=_load_tool_outputs_file, + build_tool_output_input=_build_tool_output_input) return if scenario == "response-lifecycle": first = _item_response(client, proxy_url, model, "Reply with only SAPPHIRE.", stream=stream) @@ -2210,7 +2175,7 @@ def main( 23 if items_scenario == "response-edge-cases" else 19 if items_scenario == "response-conversation" else 14 if items_scenario == "response-replayed-items" else - 40 if items_scenario == "response-tool-items" else + 48 if items_scenario == "response-tool-items" else 10 if items_scenario == "pagination" else 6 if items_scenario == "branch" else 19 if items_scenario == "edge-cases" else 5) diff --git a/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh b/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh index 8e2ffd4f8..c229beb9f 100755 --- a/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh +++ b/crates/agentic-server-core/tests/cassettes/record_responses_api_cassettes.sh @@ -43,14 +43,15 @@ fi mkdir -p "$CASSETTES_DIR" record() { local provider="$1" scenario="$2" model="$3" transport="$4" - local turns mode destination temporary + local turns mode destination temporary read_timeout + read_timeout="${HTTP_READ_TIMEOUT:-300}" case "$scenario" in lifecycle) turns=19; mode=response-lifecycle ;; pagination) turns=16; mode=response-pagination ;; edge-cases) turns=23; mode=response-edge-cases ;; conversation) turns=19; mode=response-conversation ;; replayed-items) turns=14; mode=response-replayed-items ;; - tool-items) turns=40; mode=response-tool-items ;; + tool-items) turns=48; mode=response-tool-items; read_timeout="${HTTP_READ_TIMEOUT:-900}" ;; esac local suffix="" stream_flag=--no-stream if [[ "$transport" == streaming ]]; then suffix="-stream"; stream_flag=--stream; fi @@ -65,8 +66,11 @@ record() { fi echo "Recording $provider/$scenario -> $destination" rm -f "$temporary" - "$PYTHON_BIN" "$SCRIPT_DIR/record_cassette.py" \ + local multi_agent_model="$model" + if [[ "$provider" == openai ]]; then multi_agent_model="${OPENAI_MULTI_AGENT_MODEL:-gpt-5.6-sol}"; fi + RESPONSES_MULTI_AGENT_MODEL="$multi_agent_model" "$PYTHON_BIN" "$SCRIPT_DIR/record_cassette.py" \ --mode items --items-scenario "$mode" --turns "$turns" \ + --http-read-timeout "$read_timeout" \ --proxy-port "${RECORDER_PROXY_PORT:-7070}" --model "$model" "$stream_flag" "${backend[@]}" --output "$temporary" "$PYTHON_BIN" - "$temporary" "$scenario" "$turns" <<'CASSETTE_CHECK' import sys @@ -77,8 +81,8 @@ path, scenario, count = Path(sys.argv[1]), sys.argv[2], int(sys.argv[3]) cassette = safe_load(path.read_text()) assert list(cassette) == ["turns"], "cassette must have one flat turns list" turns = cassette["turns"] -assert len(turns) == count -assert [turn["filename"] for turn in turns] == [f"t{i}" for i in range(1, count + 1)] +assert len(turns) >= 5 if scenario == "tool-items" else len(turns) == count +assert [turn["filename"] for turn in turns] == [f"t{i}" for i in range(1, len(turns) + 1)] for turn in turns: assert set(turn) == {"filename", "request", "response"} request, response = turn["request"], turn["response"] @@ -109,16 +113,28 @@ elif scenario == "replayed-items": assert sum(turn["request"]["method"] == "POST" for turn in turns) == 5 assert any(len(turn["request"]["body"].get("input", [])) == 3 for turn in turns) elif scenario == "tool-items": - for create, listing in zip(turns[::2], turns[1::2]): - assert create["request"]["method"] == "POST" - assert create["request"]["body"]["stream"] is False - assert listing["request"]["method"] == "GET" - response_id = create["response"]["body"].get("id", "resp_missing") - assert listing["request"]["path"] == f"/v1/responses/{response_id}/input_items" - assert {"web_search_call", "mcp_list_tools", "mcp_call", "code_interpreter_call", - "shell_call", "shell_call_output", "custom_tool_call", "custom_tool_call_output"} <= { - item.get("type") for turn in turns[::2] for item in turn["request"]["body"]["input"]} -print(f"Validated {path.name}: {count} flat HTTP turns") + created_ids = set() + first_tools = set() + for index, turn in enumerate(turns): + request, response = turn["request"], turn["response"] + if request["method"] != "POST": + continue + body = request["body"] + if "previous_response_id" not in body and not body.get("multi_agent", {}).get("enabled"): + assert body["tools"], "tool-enabled requests must preserve declarations" + assert body["stream"] is False + if "previous_response_id" in body: + assert body["previous_response_id"] in created_ids + else: + first_tools.update(tool["type"] for tool in body.get("tools", [])) + if response["status_code"] == 200: + response_id = response["body"]["id"] + created_ids.add(response_id) + listing = turns[index + 1]["request"] + assert listing["method"] == "GET" + assert listing["path"] == f"/v1/responses/{response_id}/input_items" + assert first_tools == {"web_search_preview", "mcp", "function", "code_interpreter", "shell", "custom", "tool_search", "namespace"} +print(f"Validated {path.name}: {len(turns)} flat HTTP turns") CASSETTE_CHECK mv "$temporary" "$destination" } @@ -128,8 +144,8 @@ for provider in openai gateway; do if [[ "$provider" == openai ]]; then model="$OPENAI_MODEL"; else model="$GATEWAY_MODEL"; fi for scenario in lifecycle pagination edge-cases conversation replayed-items tool-items; do if [[ "$SCENARIO" != all && "$SCENARIO" != "$scenario" ]]; then continue; fi - # Admission probes stay JSON so failures remain inspectable. Capture - # tool-items once even when both transports are selected. + # Source tool scenarios use JSON. Capture tool-items once even + # when both transports are selected. if [[ "$scenario" == tool-items ]]; then record "$provider" "$scenario" "$model" nonstreaming continue diff --git a/crates/agentic-server-core/tests/cassettes/replay_old_cassettes.py b/crates/agentic-server-core/tests/cassettes/replay_old_cassettes.py new file mode 100644 index 000000000..670615ac1 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/replay_old_cassettes.py @@ -0,0 +1,76 @@ +"""Replay existing tool scenarios through the shared recorder, then list inputs.""" + +import copy +import json +import os +from pathlib import Path + +from yaml import safe_load as yaml_load + + +def run_tool_items(client, proxy_url, model, *, send_request, extract_tool_calls, + load_tool_outputs, build_tool_output_input): + root = Path(__file__).parent + references = ( + "parallel_tool_calls/parallel-builtin-only-openai-reference-gpt-5.6-nonstreaming.yaml", + "parallel_tool_calls/parallel-client-openai-reference-gpt-5.6-nonstreaming.yaml", + "parallel_tool_calls/parallel-mixed-openai-reference-gpt-5.6-nonstreaming.yaml", + "shell/shell-openai-reference-success-gpt-5.6-nonstreaming.yaml", + "code_interpreter/code-interpreter-openai-reference-gpt-5.6-nonstreaming.yaml", + "tool_search/tool-search-openai-reference-gpt-5.6-nonstreaming.yaml", + "multi_agent/multi-agent-openai-reference-review-gpt-5.6-sol-nonstreaming.yaml", + ) + for filename in references: + turns = yaml_load((root / filename).read_text(encoding="utf-8"))["turns"] + if filename.startswith("tool_search/"): + fixtures = load_tool_outputs(root / "tool_search/function_outputs.json") + search_tools = json.loads((root / "tool_search/returned_tools.json").read_text()) + else: + fixtures = load_tool_outputs(root / ("shell/scenarios.py" if filename.startswith("shell/") + else "parallel_tool_calls/tool_outputs.py")) + search_tools = None + headers = {key: value for key, value in turns[0]["request"]["headers"].items() + if key.lower() == "openai-beta"} + scenario_model = os.environ.get("RESPONSES_MULTI_AGENT_MODEL", model) if filename.startswith("multi_agent/") else model + # The last response's output appears in the following response's input + # page. Resolve any pending client calls using the existing fake tools. + followup = copy.deepcopy(turns[-1]["request"]["body"]) + followup.update(previous_response_id=turns[-1]["response"]["body"]["id"], + input="Do not call tools again. Reply only OK.", tool_choice="none") + responses = {} + for turn in [*turns, {"request": {"body": followup}}]: + body = copy.deepcopy(turn["request"]["body"]) + body.update(model=scenario_model, store=True, stream=False) + previous = body.get("previous_response_id") + if previous: + parent = responses[previous] + body["previous_response_id"] = parent["id"] + # Preserve recorded user messages, but answer the live calls; + # models can choose different batches and ordering of calls. + user_input = body["input"] + if isinstance(user_input, str): + user_input = [{"type": "message", "role": "user", "content": user_input}] + else: + user_input = [item for item in user_input if item.get("type") == "message"] + body["input"] = build_tool_output_input( + extract_tool_calls(parent), fixtures, None, search_tools + ) + user_input + print(f"[tool execution: {filename}; continuation: {bool(previous)}]", flush=True) + result = send_request(client, proxy_url, "POST", "/v1/responses", + body=body, headers=headers, expected_status=None) + if not result.get("id"): + print(f"[tool execution failed: {result.get('error')}]", flush=True) + break + params = {"order": "asc", "limit": 100} + while True: + page = send_request(client, proxy_url, "GET", f"/v1/responses/{result['id']}/input_items", + params=params, expected_status=None) + if not page.get("has_more"): + break + cursor = page.get("last_id") + if not cursor or cursor == params.get("after"): + raise RuntimeError("Input-items pagination made no progress") + params["after"] = cursor + recorded = turn.get("response", {}).get("body") + if recorded is not None: + responses[recorded["id"]] = result diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-conversation-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-gateway.yaml new file mode 100644 index 000000000..1d3086a70 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-gateway.yaml @@ -0,0 +1,1016 @@ +turns: +- filename: t1 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations + query_params: {} + response: + body: + created_at: 1792748297 + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + items: + - content: Remember SAPPHIRE. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d/items + query_params: {} + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + conversation: conv_01a16820-1c5c-76e1-b321-93d02754171d + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748298 + conversation: + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + created_at: 1792748298 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-1c75-7f40-9579-ee0308b75eb5 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 57 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 30 + output_tokens_details: + reasoning_tokens: 23 + total_tokens: 87 + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1c75-7f40-9579-ee0308b75eb5/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + items: + - content: Now remember ORCHID. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d/items + query_params: {} + response: + body: + data: + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + first_id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + has_more: false + last_id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1c75-7f40-9579-ee0308b75eb5/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + conversation: conv_01a16820-1c5c-76e1-b321-93d02754171d + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748298 + conversation: + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + created_at: 1792748298 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to remember "ORCHID" and reply with only that word. + + ' + type: reasoning_text + id: rs_850b00fd9650bc59 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_967d1f143e45818e + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 116 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 25 + output_tokens_details: + reasoning_tokens: 19 + total_tokens: 141 + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-220a-7ea2-a264-af426a40c9be + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember "ORCHID" and reply with only that word. + + ' + type: reasoning_text + id: rs_850b00fd9650bc59 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_967d1f143e45818e + role: assistant + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_967d1f143e45818e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d/items/msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + query_params: {} + response: + body: + created_at: 1792748297 + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-220a-7ea2-a264-af426a40c9be + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1c75-7f40-9579-ee0308b75eb5/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-1c75-7f40-9579-ee0308b75eb5 + query_params: {} + response: + body: + deleted: true + id: resp_01a16820-1c75-7f40-9579-ee0308b75eb5 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_01a16820-220a-7ea2-a264-af426a40c9be + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + - content: + - text: 'The user wants me to remember "ORCHID" and reply with only that word. + + ' + type: reasoning_text + id: rs_850b00fd9650bc59 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_967d1f143e45818e + role: assistant + status: completed + type: message + first_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + has_more: false + last_id: msg_967d1f143e45818e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a + query_params: {} + response: + body: + background: false + completed_at: 1792748298 + conversation: + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + created_at: 1792748298 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to remember "ORCHID" and reply with only that word. + + ' + type: reasoning_text + id: rs_850b00fd9650bc59 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_967d1f143e45818e + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 116 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 25 + output_tokens_details: + reasoning_tokens: 19 + total_tokens: 141 + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_01a16820-1c5c-76e1-b321-93d02754171d + query_params: {} + response: + body: + deleted: true + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + object: conversation.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-220a-7ea2-a264-af426a40c9be + role: user + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-1f6c-7d51-be19-3d1d69b74858 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b472515258629203 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to remember the word "SAPPHIRE" and then reply + with only that word. + + ' + type: reasoning_text + id: rs_80555bf008476001 + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-1f3a-7c03-b205-a86401a8a875 + role: user + status: completed + type: message + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + role: user + status: completed + type: message + first_id: msg_01a16820-220a-7ea2-a264-af426a40c9be + has_more: false + last_id: msg_01a16820-1c68-7513-b9de-5efef8bfc898 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a + query_params: {} + response: + body: + background: false + completed_at: 1792748298 + conversation: + id: conv_01a16820-1c5c-76e1-b321-93d02754171d + created_at: 1792748298 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-1fab-7cd0-9e0a-08eb28e5769a + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to remember "ORCHID" and reply with only that word. + + ' + type: reasoning_text + id: rs_850b00fd9650bc59 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_967d1f143e45818e + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 116 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 25 + output_tokens_details: + reasoning_tokens: 19 + total_tokens: 141 + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-gateway.yaml new file mode 100644 index 000000000..1dcba3d62 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-conversation-stream-gateway.yaml @@ -0,0 +1,1489 @@ +turns: +- filename: t1 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations + query_params: {} + response: + body: + created_at: 1792748301 + id: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + items: + - content: Remember SAPPHIRE. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4/items + query_params: {} + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + conversation: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c","created_at":1792748301,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"}}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c","created_at":1792748301,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"}}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_9c49380ef26d9800","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"S","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"AP","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"PH","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"IRE","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_9c49380ef26d9800"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":20,"output_index":0,"content_index":0,"item_id":"rs_9c49380ef26d9800","text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":21,"output_index":0,"item":{"id":"rs_9c49380ef26d9800","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":22,"output_index":1,"item":{"id":"msg_bbe8b713e5898ffa","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":23,"output_index":1,"content_index":0,"item_id":"msg_bbe8b713e5898ffa","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"\n\nS","item_id":"msg_bbe8b713e5898ffa","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"AP","item_id":"msg_bbe8b713e5898ffa","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":26,"output_index":1,"content_index":0,"delta":"PH","item_id":"msg_bbe8b713e5898ffa","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":27,"output_index":1,"content_index":0,"delta":"IRE","item_id":"msg_bbe8b713e5898ffa","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":28,"output_index":1,"content_index":0,"item_id":"msg_bbe8b713e5898ffa","logprobs":[],"text":"\n\nSAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":29,"output_index":1,"content_index":0,"item_id":"msg_bbe8b713e5898ffa","part":{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":30,"output_index":1,"item":{"id":"msg_bbe8b713e5898ffa","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":31,"response":{"id":"resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c","object":"response","created_at":1792748301,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_9c49380ef26d9800","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_bbe8b713e5898ffa","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nSAPPHIRE","annotations":[]}]}],"usage":{"input_tokens":57,"output_tokens":24,"total_tokens":81,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":17}},"incomplete_details":null,"error":null,"previous_response_id":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"},"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748301,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + items: + - content: Now remember ORCHID. + role: user + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4/items + query_params: {} + response: + body: + data: + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + first_id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + has_more: false + last_id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + conversation: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a16820-2e87-7210-8e58-c1e51d100a74","created_at":1792748301,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"}}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a16820-2e87-7210-8e58-c1e51d100a74","created_at":1792748301,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"}}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_b74e1e323184327f","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"OR","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_b74e1e323184327f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":19,"output_index":0,"content_index":0,"item_id":"rs_b74e1e323184327f","text":"The + user wants me to reply with only the word \"ORCHID\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":20,"output_index":0,"item":{"id":"rs_b74e1e323184327f","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"ORCHID\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":21,"output_index":1,"item":{"id":"msg_b65d8e89158b45ee","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":22,"output_index":1,"content_index":0,"item_id":"msg_b65d8e89158b45ee","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":23,"output_index":1,"content_index":0,"delta":"\n\nOR","item_id":"msg_b65d8e89158b45ee","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"CH","item_id":"msg_b65d8e89158b45ee","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"ID","item_id":"msg_b65d8e89158b45ee","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":26,"output_index":1,"content_index":0,"item_id":"msg_b65d8e89158b45ee","logprobs":[],"text":"\n\nORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":27,"output_index":1,"content_index":0,"item_id":"msg_b65d8e89158b45ee","part":{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":28,"output_index":1,"item":{"id":"msg_b65d8e89158b45ee","content":[{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":29,"response":{"id":"resp_01a16820-2e87-7210-8e58-c1e51d100a74","object":"response","created_at":1792748302,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_b74e1e323184327f","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"ORCHID\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_b65d8e89158b45ee","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nORCHID","annotations":[]}]}],"usage":{"input_tokens":110,"output_tokens":22,"total_tokens":132,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":16}},"incomplete_details":null,"error":null,"previous_response_id":null,"conversation":{"id":"conv_01a16820-2b87-7c53-9bb1-676804d87fd4"},"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748302,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b74e1e323184327f + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_b65d8e89158b45ee + role: assistant + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_b65d8e89158b45ee + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4/items/msg_01a16820-2e29-7993-80a9-0eddbfed3890 + query_params: {} + response: + body: + created_at: 1792748301 + id: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + metadata: {} + object: conversation + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c + query_params: {} + response: + body: + deleted: true + id: resp_01a16820-2b9b-7410-a0cb-696c5bb1db8c + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4/items + query_params: + order: asc + response: + body: + data: + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b74e1e323184327f + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_b65d8e89158b45ee + role: assistant + status: completed + type: message + first_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + has_more: false + last_id: msg_b65d8e89158b45ee + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74 + query_params: {} + response: + body: + background: false + completed_at: 1792748302 + conversation: + id: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + created_at: 1792748302 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-2e87-7210-8e58-c1e51d100a74 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b74e1e323184327f + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_b65d8e89158b45ee + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 110 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 132 + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/conversations/conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + query_params: {} + response: + body: + deleted: true + id: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + object: conversation.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + role: user + status: completed + type: message + - content: + - text: Now remember ORCHID. + type: input_text + id: msg_01a16820-2e29-7993-80a9-0eddbfed3890 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_bbe8b713e5898ffa + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_9c49380ef26d9800 + status: completed + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-2de1-7c51-9f2f-d64870812414 + role: user + status: completed + type: message + - content: + - text: Remember SAPPHIRE. + type: input_text + id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + role: user + status: completed + type: message + first_id: msg_01a16820-30a6-7831-9ba0-b47b94f85227 + has_more: false + last_id: msg_01a16820-2b91-7bf0-8ac3-76392132d0d8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-2e87-7210-8e58-c1e51d100a74 + query_params: {} + response: + body: + background: false + completed_at: 1792748302 + conversation: + id: conv_01a16820-2b87-7c53-9bb1-676804d87fd4 + created_at: 1792748302 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-2e87-7210-8e58-c1e51d100a74 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b74e1e323184327f + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_b65d8e89158b45ee + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 110 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 132 + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-gateway.yaml new file mode 100644 index 000000000..3235c57d9 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-gateway.yaml @@ -0,0 +1,640 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: false + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + created_at: 1792748289 + ec_transfer_params: null + frequency_penalty: 0.0 + id: resp_afb7c16e4290a0d2 + incomplete_details: null + input_messages: null + instructions: null + kv_transfer_params: null + max_output_tokens: 65489 + max_tool_calls: null + metadata: null + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + encrypted_content: null + id: rs_8895056f462f79ad + status: null + summary: [] + type: reasoning + - content: + - annotations: [] + logprobs: null + text: ' + + + SAPPHIRE' + type: output_text + id: msg_86bf767304965ea4 + phase: null + role: assistant + status: completed + type: message + output_messages: null + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt: null + reasoning: null + service_tier: auto + status: completed + temperature: 1.0 + text: null + tool_choice: none + tools: [] + top_logprobs: null + top_p: 0.95 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + cached_tokens_per_turn: [] + input_tokens_per_turn: [] + output_tokens: 24 + output_tokens_details: + output_tokens_per_turn: [] + reasoning_tokens: 17 + tool_output_tokens: 0 + tool_output_tokens_per_turn: [] + total_tokens: 71 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_afb7c16e4290a0d2 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_afb7c16e4290a0d2''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_afb7c16e4290a0d2/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_afb7c16e4290a0d2''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_afb7c16e4290a0d2 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_afb7c16e4290a0d2''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t8 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_missing + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: previous_response_not_found + message: Previous response with id 'resp_missing' not found. + param: previous_response_id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748290 + created_at: 1792748290 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-0157-7fb2-9533-3e564b318881 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_a24b3a74045cbd54 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_be10834c08f1f3e4 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 69 + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + limit: '0' + response: + body: + data: [] + first_id: null + has_more: true + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + limit: '101' + response: + body: + error: + code: integer_above_max_value + message: limit must be at most 100 + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + order: sideways + response: + body: + error: + code: invalid_value + message: unknown variant `sideways`, expected `asc` or `desc` + param: order + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + after: msg_missing + response: + body: + error: + code: null + message: No item found with id 'msg_missing' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + include[]: web_search_call.results + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + role: user + status: completed + type: message + first_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + has_more: false + last_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + include[]: message.output_text.logprobs + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + role: user + status: completed + type: message + first_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + has_more: false + last_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + limit: '-1' + response: + body: + error: + code: integer_below_min_value + message: limit must be at least 0 + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + limit: ten + response: + body: + error: + code: invalid_type + message: limit must be an integer + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + after: '' + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + role: user + status: completed + type: message + first_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + has_more: false + last_id: msg_01a16820-0376-7952-9c78-b48a50a7e4c5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + include[]: not.a.valid.include + response: + body: + error: + code: invalid_value + message: unknown variant `not.a.valid.include`, expected one of `file_search_call.results`, + `web_search_call.results`, `web_search_call.action.sources`, `message.input_image.image_url`, + `computer_call_output.output.image_url`, `code_interpreter_call.outputs`, + `reasoning.encrypted_content`, `message.output_text.logprobs` + param: include[0] + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + limit: + - '1' + - '2' + response: + body: + error: + code: duplicate_parameter + message: '''limit'' must be given at most once' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t21 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881/input_items + query_params: + include[]: + - message.output_text.logprobs + - reasoning.encrypted_content + response: + body: + error: + code: null + message: Encrypted content cannot be requested for persisted responses. + param: include + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881 + query_params: {} + response: + body: + deleted: true + id: resp_01a16820-0157-7fb2-9533-3e564b318881 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-0157-7fb2-9533-3e564b318881 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a16820-0157-7fb2-9533-3e564b318881''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-gateway.yaml new file mode 100644 index 000000000..a101ead84 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-edge-cases-stream-gateway.yaml @@ -0,0 +1,1110 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: false + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"response":{"id":"resp_873a4b1797aa4849","created_at":1792748293,"incomplete_details":null,"instructions":null,"metadata":null,"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":true,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":0.95,"background":false,"max_output_tokens":65489,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":null,"top_logprobs":null,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null},"sequence_number":0,"type":"response.created"} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"response":{"id":"resp_873a4b1797aa4849","created_at":1792748293,"incomplete_details":null,"instructions":null,"metadata":null,"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":true,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":0.95,"background":false,"max_output_tokens":65489,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":null,"top_logprobs":null,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null},"sequence_number":1,"type":"response.in_progress"} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"item":{"id":"rs_86cbe401d979ff9e","summary":[],"type":"reasoning","content":null,"encrypted_content":null,"status":"in_progress"},"output_index":0,"sequence_number":2,"type":"response.output_item.added"} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"content_index":0,"item_id":"rs_86cbe401d979ff9e","output_index":0,"part":{"text":"","type":"reasoning_text"},"sequence_number":3,"type":"response.content_part.added"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"The","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":4,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" user","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":5,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" wants","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":6,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" me","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":7,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" to","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":8,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" reply","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":9,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" with","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":10,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" only","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":11,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" the","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":12,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" word","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":13,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":" \"","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":14,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"S","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":15,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"AP","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":16,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"PH","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":17,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"IRE","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":18,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"\".","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":19,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"content_index":0,"delta":"\n","item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":20,"type":"response.reasoning_text.delta"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"content_index":0,"item_id":"rs_86cbe401d979ff9e","output_index":0,"sequence_number":21,"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"response.reasoning_text.done"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"content_index":0,"item_id":"rs_86cbe401d979ff9e","output_index":0,"part":{"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"reasoning_text"},"sequence_number":22,"type":"response.content_part.done"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"item":{"id":"rs_86cbe401d979ff9e","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"reasoning_text"}],"encrypted_content":null,"status":"completed"},"output_index":0,"sequence_number":23,"type":"response.output_item.done"} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"item":{"id":"msg_8ca715d6204854f4","content":[],"role":"assistant","status":"in_progress","type":"message","phase":null},"output_index":1,"sequence_number":24,"type":"response.output_item.added"} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"content_index":0,"item_id":"msg_8ca715d6204854f4","output_index":1,"part":{"annotations":[],"text":"","type":"output_text","logprobs":[]},"sequence_number":25,"type":"response.content_part.added"} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"content_index":0,"delta":"\n\nS","item_id":"msg_8ca715d6204854f4","logprobs":[],"output_index":1,"sequence_number":26,"type":"response.output_text.delta"} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"content_index":0,"delta":"AP","item_id":"msg_8ca715d6204854f4","logprobs":[],"output_index":1,"sequence_number":27,"type":"response.output_text.delta"} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"content_index":0,"delta":"PH","item_id":"msg_8ca715d6204854f4","logprobs":[],"output_index":1,"sequence_number":28,"type":"response.output_text.delta"} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"content_index":0,"delta":"IRE","item_id":"msg_8ca715d6204854f4","logprobs":[],"output_index":1,"sequence_number":29,"type":"response.output_text.delta"} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"content_index":0,"item_id":"msg_8ca715d6204854f4","logprobs":[],"output_index":1,"sequence_number":30,"text":"\n\nSAPPHIRE","type":"response.output_text.done"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"content_index":0,"item_id":"msg_8ca715d6204854f4","output_index":1,"part":{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":null},"sequence_number":31,"type":"response.content_part.done"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"item":{"id":"msg_8ca715d6204854f4","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":null}],"role":"assistant","status":"completed","type":"message","phase":null},"output_index":1,"sequence_number":32,"type":"response.output_item.done"} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"response":{"id":"resp_873a4b1797aa4849","created_at":1792748293,"incomplete_details":null,"instructions":null,"metadata":null,"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[{"id":"rs_86cbe401d979ff9e","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"reasoning_text"}],"encrypted_content":null,"status":"completed"},{"id":"msg_8ca715d6204854f4","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":null}],"role":"assistant","status":"completed","type":"message","phase":null}],"parallel_tool_calls":true,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":0.95,"background":false,"max_output_tokens":65489,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"completed","text":null,"top_logprobs":null,"truncation":"disabled","usage":{"input_tokens":47,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0,"input_tokens_per_turn":[],"cached_tokens_per_turn":[]},"output_tokens":24,"output_tokens_details":{"reasoning_tokens":17,"tool_output_tokens":0,"output_tokens_per_turn":[],"tool_output_tokens_per_turn":[]},"total_tokens":71},"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null},"sequence_number":33,"type":"response.completed"} + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_873a4b1797aa4849 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_873a4b1797aa4849''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_873a4b1797aa4849/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_873a4b1797aa4849''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_873a4b1797aa4849 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_873a4b1797aa4849''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_missing/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_missing + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_missing''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t8 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_missing + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: previous_response_not_found + message: Previous response with id 'resp_missing' not found. + param: previous_response_id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a16820-0fd3-7242-84a7-af23630e3345","created_at":1792748294,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a16820-0fd3-7242-84a7-af23630e3345","created_at":1792748294,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_8de5e7f820ccd569","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"OR","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_8de5e7f820ccd569"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":19,"output_index":0,"content_index":0,"item_id":"rs_8de5e7f820ccd569","text":"The + user wants me to reply with only the word \"ORCHID\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":20,"output_index":0,"item":{"id":"rs_8de5e7f820ccd569","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"ORCHID\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":21,"output_index":1,"item":{"id":"msg_83abae8265795305","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":22,"output_index":1,"content_index":0,"item_id":"msg_83abae8265795305","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":23,"output_index":1,"content_index":0,"delta":"\n\nOR","item_id":"msg_83abae8265795305","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"CH","item_id":"msg_83abae8265795305","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"ID","item_id":"msg_83abae8265795305","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":26,"output_index":1,"content_index":0,"item_id":"msg_83abae8265795305","logprobs":[],"text":"\n\nORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":27,"output_index":1,"content_index":0,"item_id":"msg_83abae8265795305","part":{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":28,"output_index":1,"item":{"id":"msg_83abae8265795305","content":[{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":29,"response":{"id":"resp_01a16820-0fd3-7242-84a7-af23630e3345","object":"response","created_at":1792748294,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_8de5e7f820ccd569","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"ORCHID\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_83abae8265795305","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nORCHID","annotations":[]}]}],"usage":{"input_tokens":47,"output_tokens":22,"total_tokens":69,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":16}},"incomplete_details":null,"error":null,"previous_response_id":null,"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748294,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + limit: '0' + response: + body: + data: [] + first_id: null + has_more: true + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + limit: '101' + response: + body: + error: + code: integer_above_max_value + message: limit must be at most 100 + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + order: sideways + response: + body: + error: + code: invalid_value + message: unknown variant `sideways`, expected `asc` or `desc` + param: order + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + after: msg_missing + response: + body: + error: + code: null + message: No item found with id 'msg_missing' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + include[]: web_search_call.results + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + role: user + status: completed + type: message + first_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + has_more: false + last_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + include[]: message.output_text.logprobs + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + role: user + status: completed + type: message + first_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + has_more: false + last_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + limit: '-1' + response: + body: + error: + code: integer_below_min_value + message: limit must be at least 0 + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + limit: ten + response: + body: + error: + code: invalid_type + message: limit must be an integer + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + after: '' + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + role: user + status: completed + type: message + first_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + has_more: false + last_id: msg_01a16820-11f5-7733-9248-1b0c81f85b9e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + include[]: not.a.valid.include + response: + body: + error: + code: invalid_value + message: unknown variant `not.a.valid.include`, expected one of `file_search_call.results`, + `web_search_call.results`, `web_search_call.action.sources`, `message.input_image.image_url`, + `computer_call_output.output.image_url`, `code_interpreter_call.outputs`, + `reasoning.encrypted_content`, `message.output_text.logprobs` + param: include[0] + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + limit: + - '1' + - '2' + response: + body: + error: + code: duplicate_parameter + message: '''limit'' must be given at most once' + param: limit + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t21 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345/input_items + query_params: + include[]: + - message.output_text.logprobs + - reasoning.encrypted_content + response: + body: + error: + code: null + message: Encrypted content cannot be requested for persisted responses. + param: include + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345 + query_params: {} + response: + body: + deleted: true + id: resp_01a16820-0fd3-7242-84a7-af23630e3345 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a16820-0fd3-7242-84a7-af23630e3345 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a16820-0fd3-7242-84a7-af23630e3345''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-gateway.yaml new file mode 100644 index 000000000..34f1121f8 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-gateway.yaml @@ -0,0 +1,785 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748270 + created_at: 1792748270 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE." + + ' + type: reasoning_text + id: rs_8d45a4c7a23ddd16 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_93d50f85aaccf87a + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 24 + output_tokens_details: + reasoning_tokens: 17 + total_tokens: 71 + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + role: user + status: completed + type: message + first_id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + has_more: false + last_id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748270 + created_at: 1792748270 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-b326-7743-affc-287260336b36 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID." + + ' + type: reasoning_text + id: rs_80e43f4959d5c2f6 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_8da328936b6052e9 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE." + + ' + type: reasoning_text + id: rs_8d45a4c7a23ddd16 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_93d50f85aaccf87a + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + role: user + status: completed + type: message + first_id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + has_more: false + last_id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + role: user + status: completed + type: message + first_id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + has_more: true + last_id: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36/input_items + query_params: + after: msg_01a1681f-b2fb-7de2-b190-8cfd7bf663ea + limit: '1' + order: asc + response: + body: + data: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE." + + ' + type: reasoning_text + id: rs_8d45a4c7a23ddd16 + summary: [] + type: reasoning + first_id: rs_8d45a4c7a23ddd16 + has_more: true + last_id: rs_8d45a4c7a23ddd16 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36 + query_params: {} + response: + body: + background: false + completed_at: 1792748270 + created_at: 1792748270 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-b326-7743-affc-287260336b36 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID." + + ' + type: reasoning_text + id: rs_80e43f4959d5c2f6 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_8da328936b6052e9 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + role: user + status: completed + type: message + first_id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + has_more: false + last_id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36 + query_params: {} + response: + body: + background: false + completed_at: 1792748270 + created_at: 1792748270 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-b326-7743-affc-287260336b36 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID." + + ' + type: reasoning_text + id: rs_80e43f4959d5c2f6 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_8da328936b6052e9 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-b0a6-7ed1-87d9-dcf12cf72e63''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: + input: + - content: What exact word did the first user message ask you to reply with? + Reply with only that word. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-b326-7743-affc-287260336b36 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748272 + created_at: 1792748272 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-b783-7862-862d-fa186c5efad1 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user is asking what exact word the first user message asked me + to reply with. The first user message said "Reply with only ORCHID." So + the word is ORCHID. They want me to reply with only that word. + + ' + type: reasoning_text + id: rs_ae1b45109884f964 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_a1bfde998fcfe613 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-b326-7743-affc-287260336b36 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 100 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 55 + output_tokens_details: + reasoning_tokens: 49 + total_tokens: 155 + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b783-7862-862d-fa186c5efad1/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "ORCHID." + + ' + type: reasoning_text + id: rs_80e43f4959d5c2f6 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_8da328936b6052e9 + role: assistant + status: completed + type: message + - content: + - text: What exact word did the first user message ask you to reply with? + Reply with only that word. + type: input_text + id: msg_01a1681f-bc44-7452-8012-112e41706260 + role: user + status: completed + type: message + first_id: msg_01a1681f-b549-77f2-9c84-54ff09e5e399 + has_more: false + last_id: msg_01a1681f-bc44-7452-8012-112e41706260 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-b783-7862-862d-fa186c5efad1 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-b783-7862-862d-fa186c5efad1 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-b326-7743-affc-287260336b36 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-b326-7743-affc-287260336b36''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-b326-7743-affc-287260336b36 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-b326-7743-affc-287260336b36''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-gateway.yaml new file mode 100644 index 000000000..796986a80 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-lifecycle-stream-gateway.yaml @@ -0,0 +1,1831 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","created_at":1792748274,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","created_at":1792748274,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_8af01c08c9063798","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"S","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"AP","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"PH","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"IRE","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_8af01c08c9063798"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":20,"output_index":0,"content_index":0,"item_id":"rs_8af01c08c9063798","text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":21,"output_index":0,"item":{"id":"rs_8af01c08c9063798","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":22,"output_index":1,"item":{"id":"msg_8da27e2bda189a09","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":23,"output_index":1,"content_index":0,"item_id":"msg_8da27e2bda189a09","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"\n\nS","item_id":"msg_8da27e2bda189a09","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"AP","item_id":"msg_8da27e2bda189a09","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":26,"output_index":1,"content_index":0,"delta":"PH","item_id":"msg_8da27e2bda189a09","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":27,"output_index":1,"content_index":0,"delta":"IRE","item_id":"msg_8da27e2bda189a09","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":28,"output_index":1,"content_index":0,"item_id":"msg_8da27e2bda189a09","logprobs":[],"text":"\n\nSAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":29,"output_index":1,"content_index":0,"item_id":"msg_8da27e2bda189a09","part":{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":30,"output_index":1,"item":{"id":"msg_8da27e2bda189a09","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":31,"response":{"id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","object":"response","created_at":1792748275,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_8af01c08c9063798","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"SAPPHIRE\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_8da27e2bda189a09","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nSAPPHIRE","annotations":[]}]}],"usage":{"input_tokens":47,"output_tokens":24,"total_tokens":71,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":17}},"incomplete_details":null,"error":null,"previous_response_id":null,"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748275,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + role: user + status: completed + type: message + first_id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + has_more: false + last_id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","created_at":1792748275,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","created_at":1792748275,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_b200b8226f4c0bfc","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"OR","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_b200b8226f4c0bfc"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":19,"output_index":0,"content_index":0,"item_id":"rs_b200b8226f4c0bfc","text":"The + user wants me to reply with only the word \"ORCHID\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":20,"output_index":0,"item":{"id":"rs_b200b8226f4c0bfc","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"ORCHID\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":21,"output_index":1,"item":{"id":"msg_85767da589130597","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":22,"output_index":1,"content_index":0,"item_id":"msg_85767da589130597","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":23,"output_index":1,"content_index":0,"delta":"\n\nOR","item_id":"msg_85767da589130597","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"CH","item_id":"msg_85767da589130597","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"ID","item_id":"msg_85767da589130597","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":26,"output_index":1,"content_index":0,"item_id":"msg_85767da589130597","logprobs":[],"text":"\n\nORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":27,"output_index":1,"content_index":0,"item_id":"msg_85767da589130597","part":{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":28,"output_index":1,"item":{"id":"msg_85767da589130597","content":[{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":29,"response":{"id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","object":"response","created_at":1792748275,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_b200b8226f4c0bfc","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"ORCHID\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_85767da589130597","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nORCHID","annotations":[]}]}],"usage":{"input_tokens":89,"output_tokens":22,"total_tokens":111,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":16}},"incomplete_details":null,"error":null,"previous_response_id":"resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7","instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748275,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_8af01c08c9063798 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_8da27e2bda189a09 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + role: user + status: completed + type: message + first_id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + has_more: false + last_id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + role: user + status: completed + type: message + first_id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + has_more: true + last_id: msg_01a1681f-c610-71b3-aae5-c823b78c016b + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89/input_items + query_params: + after: msg_01a1681f-c610-71b3-aae5-c823b78c016b + limit: '1' + order: asc + response: + body: + data: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_8af01c08c9063798 + status: completed + summary: [] + type: reasoning + first_id: rs_8af01c08c9063798 + has_more: true + last_id: rs_8af01c08c9063798 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + query_params: {} + response: + body: + background: false + completed_at: 1792748275 + created_at: 1792748275 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b200b8226f4c0bfc + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_85767da589130597 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + role: user + status: completed + type: message + first_id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + has_more: false + last_id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + query_params: {} + response: + body: + background: false + completed_at: 1792748275 + created_at: 1792748275 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b200b8226f4c0bfc + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_85767da589130597 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-c3c7-77a2-80e5-2f5a0e82f3d7''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t14 + request: + body: + input: + - content: What exact word did the first user message ask you to reply with? + Reply with only that word. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-cba2-7081-b883-9947138e73d6","created_at":1792748276,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-cba2-7081-b883-9947138e73d6","created_at":1792748276,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_84709161e21da2c9","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + is","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + asking","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + what","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + exact","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + first","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":" + message","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":" + asked","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":20,"output_index":0,"content_index":0,"delta":".","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":21,"output_index":0,"content_index":0,"delta":" + The","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":22,"output_index":0,"content_index":0,"delta":" + first","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":23,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":24,"output_index":0,"content_index":0,"delta":" + message","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":25,"output_index":0,"content_index":0,"delta":" + said","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":26,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":27,"output_index":0,"content_index":0,"delta":"Reply","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":28,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":29,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":30,"output_index":0,"content_index":0,"delta":" + OR","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":31,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":32,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":33,"output_index":0,"content_index":0,"delta":".\"","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":34,"output_index":0,"content_index":0,"delta":" + So","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":35,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":36,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":37,"output_index":0,"content_index":0,"delta":" + is","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":38,"output_index":0,"content_index":0,"delta":" + OR","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":39,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":40,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":41,"output_index":0,"content_index":0,"delta":".","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":42,"output_index":0,"content_index":0,"delta":" + They","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":43,"output_index":0,"content_index":0,"delta":" + want","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":44,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":45,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":46,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":47,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":48,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":49,"output_index":0,"content_index":0,"delta":" + that","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":50,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":51,"output_index":0,"content_index":0,"delta":".","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":52,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_84709161e21da2c9"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":53,"output_index":0,"content_index":0,"item_id":"rs_84709161e21da2c9","text":"The + user is asking me what exact word the first user message asked me to reply with. + The first user message said \"Reply with only ORCHID.\" So the word is ORCHID. + They want me to reply with only that word.\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":54,"output_index":0,"item":{"id":"rs_84709161e21da2c9","summary":[],"type":"reasoning","content":[{"text":"The + user is asking me what exact word the first user message asked me to reply with. + The first user message said \"Reply with only ORCHID.\" So the word is ORCHID. + They want me to reply with only that word.\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":55,"output_index":1,"item":{"id":"msg_9dded234346ad83a","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":56,"output_index":1,"content_index":0,"item_id":"msg_9dded234346ad83a","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":57,"output_index":1,"content_index":0,"delta":"\n\nOR","item_id":"msg_9dded234346ad83a","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":58,"output_index":1,"content_index":0,"delta":"CH","item_id":"msg_9dded234346ad83a","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":59,"output_index":1,"content_index":0,"delta":"ID","item_id":"msg_9dded234346ad83a","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":60,"output_index":1,"content_index":0,"item_id":"msg_9dded234346ad83a","logprobs":[],"text":"\n\nORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":61,"output_index":1,"content_index":0,"item_id":"msg_9dded234346ad83a","part":{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":62,"output_index":1,"item":{"id":"msg_9dded234346ad83a","content":[{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":63,"response":{"id":"resp_01a1681f-cba2-7081-b883-9947138e73d6","object":"response","created_at":1792748277,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_84709161e21da2c9","content":[{"type":"reasoning_text","text":"The + user is asking me what exact word the first user message asked me to reply with. + The first user message said \"Reply with only ORCHID.\" So the word is ORCHID. + They want me to reply with only that word.\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_9dded234346ad83a","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nORCHID","annotations":[]}]}],"usage":{"input_tokens":100,"output_tokens":56,"total_tokens":156,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":50}},"incomplete_details":null,"error":null,"previous_response_id":"resp_01a1681f-c648-74d1-a2fa-98a0612dbb89","instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748277,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-cba2-7081-b883-9947138e73d6/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_b200b8226f4c0bfc + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_85767da589130597 + role: assistant + status: completed + type: message + - content: + - text: What exact word did the first user message ask you to reply with? + Reply with only that word. + type: input_text + id: msg_01a1681f-d076-7202-84df-99c102a6e6c9 + role: user + status: completed + type: message + first_id: msg_01a1681f-c86b-71f1-a429-939cdc0277e8 + has_more: false + last_id: msg_01a1681f-d076-7202-84df-99c102a6e6c9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-cba2-7081-b883-9947138e73d6 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-cba2-7081-b883-9947138e73d6 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: DELETE + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + query_params: {} + response: + body: + deleted: true + id: resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + object: response.deleted + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89/input_items + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-c648-74d1-a2fa-98a0612dbb89''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 +- filename: t19 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-c648-74d1-a2fa-98a0612dbb89 + query_params: {} + response: + body: + error: + code: null + message: 'storage error: not found: Response with id ''resp_01a1681f-c648-74d1-a2fa-98a0612dbb89''' + param: null + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-pagination-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-gateway.yaml new file mode 100644 index 000000000..95831e8d6 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-gateway.yaml @@ -0,0 +1,737 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748280 + created_at: 1792748280 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-d907-7202-86b7-552d54678efd + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 24 + output_tokens_details: + reasoning_tokens: 17 + total_tokens: 71 + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-d907-7202-86b7-552d54678efd + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748281 + created_at: 1792748281 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-db5e-7c62-9a8b-1164306878bb + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "ORCHID". + + ' + type: reasoning_text + id: rs_843dbb18167c5c52 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + ORCHID' + type: output_text + id: msg_8096b62420788c7d + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1681f-d907-7202-86b7-552d54678efd + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 89 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 111 + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + first_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + has_more: false + last_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + first_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + has_more: false + last_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + first_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + has_more: true + last_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + after: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + limit: '1' + order: asc + response: + body: + data: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + first_id: rs_83623ec2e77c29e2 + has_more: true + last_id: rs_83623ec2e77c29e2 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + limit: '1' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + first_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + has_more: true + last_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + after: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + limit: '1' + order: desc + response: + body: + data: + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + first_id: msg_9a636063e7beff44 + has_more: true + last_id: msg_9a636063e7beff44 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + first_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + has_more: false + last_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + first_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + has_more: false + last_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + after: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + order: asc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + limit: '100' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9a636063e7beff44 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83623ec2e77c29e2 + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + role: user + status: completed + type: message + first_id: msg_01a1681f-dd7c-76a2-b880-e86c8fc606a6 + has_more: false + last_id: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + after: msg_01a1681f-db4e-71e1-b7e1-91ff4f94de8e + order: desc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: + input: + - content: Reply with only VIOLET. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748283 + created_at: 1792748283 + error: null + frequency_penalty: 0.0 + id: resp_01a1681f-e3c7-7832-94bf-689225129b05 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "VIOLET". + + ' + type: reasoning_text + id: rs_ab97ab7ec598edfa + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + VIOLET' + type: output_text + id: msg_958708764e616442 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 22 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 69 + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-e3c7-7832-94bf-689225129b05/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only VIOLET. + type: input_text + id: msg_01a1681f-e5e8-76c2-b2af-87f6f9272b13 + role: user + status: completed + type: message + first_id: msg_01a1681f-e5e8-76c2-b2af-87f6f9272b13 + has_more: false + last_id: msg_01a1681f-e5e8-76c2-b2af-87f6f9272b13 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-db5e-7c62-9a8b-1164306878bb/input_items + query_params: + after: msg_01a1681f-e5e8-76c2-b2af-87f6f9272b13 + response: + body: + error: + code: null + message: No item found with id 'msg_01a1681f-e5e8-76c2-b2af-87f6f9272b13' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-gateway.yaml new file mode 100644 index 000000000..6c0ae06f9 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-pagination-stream-gateway.yaml @@ -0,0 +1,1582 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","created_at":1792748284,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","created_at":1792748284,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_aefea1220c1acaf2","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"S","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"AP","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"PH","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"IRE","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":" + This","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":20,"output_index":0,"content_index":0,"delta":" + is","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":21,"output_index":0,"content_index":0,"delta":" + a","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":22,"output_index":0,"content_index":0,"delta":" + simple","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":23,"output_index":0,"content_index":0,"delta":" + and","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":24,"output_index":0,"content_index":0,"delta":" + direct","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":25,"output_index":0,"content_index":0,"delta":" + request","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":26,"output_index":0,"content_index":0,"delta":".","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":27,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_aefea1220c1acaf2"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":28,"output_index":0,"content_index":0,"item_id":"rs_aefea1220c1acaf2","text":"The + user wants me to reply with only the word \"SAPPHIRE\". This is a simple and + direct request.\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":29,"output_index":0,"item":{"id":"rs_aefea1220c1acaf2","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE\". This is a simple and + direct request.\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":30,"output_index":1,"item":{"id":"msg_b41da4ad9a54c8c5","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":31,"output_index":1,"content_index":0,"item_id":"msg_b41da4ad9a54c8c5","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":32,"output_index":1,"content_index":0,"delta":"\n\nS","item_id":"msg_b41da4ad9a54c8c5","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":33,"output_index":1,"content_index":0,"delta":"AP","item_id":"msg_b41da4ad9a54c8c5","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":34,"output_index":1,"content_index":0,"delta":"PH","item_id":"msg_b41da4ad9a54c8c5","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":35,"output_index":1,"content_index":0,"delta":"IRE","item_id":"msg_b41da4ad9a54c8c5","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":36,"output_index":1,"content_index":0,"item_id":"msg_b41da4ad9a54c8c5","logprobs":[],"text":"\n\nSAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":37,"output_index":1,"content_index":0,"item_id":"msg_b41da4ad9a54c8c5","part":{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":38,"output_index":1,"item":{"id":"msg_b41da4ad9a54c8c5","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":39,"response":{"id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","object":"response","created_at":1792748285,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_aefea1220c1acaf2","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"SAPPHIRE\". This is a simple and + direct request.\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_b41da4ad9a54c8c5","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nSAPPHIRE","annotations":[]}]}],"usage":{"input_tokens":47,"output_tokens":32,"total_tokens":79,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":25}},"incomplete_details":null,"error":null,"previous_response_id":null,"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748285,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: + input: + - content: Reply with only ORCHID. + role: user + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a1681f-ec13-7ee1-b141-101d5f4946e0 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-ef15-7b91-a366-b9658ae97c12","created_at":1792748285,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-ef15-7b91-a366-b9658ae97c12","created_at":1792748285,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_b5addb64a5a713d0","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"OR","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"CH","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"ID","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":" + Simple","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":" + and","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":20,"output_index":0,"content_index":0,"delta":" + direct","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":21,"output_index":0,"content_index":0,"delta":" + request","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":22,"output_index":0,"content_index":0,"delta":".","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":23,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_b5addb64a5a713d0"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":24,"output_index":0,"content_index":0,"item_id":"rs_b5addb64a5a713d0","text":"The + user wants me to reply with only the word \"ORCHID\". Simple and direct request.\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":25,"output_index":0,"item":{"id":"rs_b5addb64a5a713d0","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"ORCHID\". Simple and direct request.\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":26,"output_index":1,"item":{"id":"msg_8c26bd66c2d95ded","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":27,"output_index":1,"content_index":0,"item_id":"msg_8c26bd66c2d95ded","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":28,"output_index":1,"content_index":0,"delta":"\n\nOR","item_id":"msg_8c26bd66c2d95ded","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":29,"output_index":1,"content_index":0,"delta":"CH","item_id":"msg_8c26bd66c2d95ded","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":30,"output_index":1,"content_index":0,"delta":"ID","item_id":"msg_8c26bd66c2d95ded","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":31,"output_index":1,"content_index":0,"item_id":"msg_8c26bd66c2d95ded","logprobs":[],"text":"\n\nORCHID"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":32,"output_index":1,"content_index":0,"item_id":"msg_8c26bd66c2d95ded","part":{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":33,"output_index":1,"item":{"id":"msg_8c26bd66c2d95ded","content":[{"annotations":[],"text":"\n\nORCHID","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":34,"response":{"id":"resp_01a1681f-ef15-7b91-a366-b9658ae97c12","object":"response","created_at":1792748286,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_b5addb64a5a713d0","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"ORCHID\". Simple and direct request.\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_8c26bd66c2d95ded","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nORCHID","annotations":[]}]}],"usage":{"input_tokens":97,"output_tokens":27,"total_tokens":124,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":21}},"incomplete_details":null,"error":null,"previous_response_id":"resp_01a1681f-ec13-7ee1-b141-101d5f4946e0","instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748286,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t3 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + first_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + has_more: false + last_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + first_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + has_more: false + last_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + limit: '1' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + first_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + has_more: true + last_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + after: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + limit: '1' + order: asc + response: + body: + data: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + first_id: rs_aefea1220c1acaf2 + has_more: true + last_id: rs_aefea1220c1acaf2 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + limit: '1' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + first_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + has_more: true + last_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + after: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + limit: '1' + order: desc + response: + body: + data: + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + first_id: msg_b41da4ad9a54c8c5 + has_more: true + last_id: msg_b41da4ad9a54c8c5 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + first_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + has_more: false + last_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + first_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + has_more: false + last_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + after: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + order: asc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + limit: '100' + order: desc + response: + body: + data: + - content: + - text: Reply with only ORCHID. + type: input_text + id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + role: user + status: completed + type: message + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b41da4ad9a54c8c5 + role: assistant + status: completed + type: message + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". This is + a simple and direct request. + + ' + type: reasoning_text + id: rs_aefea1220c1acaf2 + status: completed + summary: [] + type: reasoning + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + role: user + status: completed + type: message + first_id: msg_01a1681f-f198-7493-b44d-54c00ca93a94 + has_more: false + last_id: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + after: msg_01a1681f-eefa-7952-bfca-800cf0bee98c + order: desc + response: + body: + data: [] + first_id: null + has_more: false + last_id: null + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: + input: + - content: Reply with only VIOLET. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a1681f-f595-7982-859e-51bccb69f1e3","created_at":1792748287,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a1681f-f595-7982-859e-51bccb69f1e3","created_at":1792748287,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_9f11d7be9b86827b","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"V","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"IO","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"LET","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"\".","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_9f11d7be9b86827b"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":19,"output_index":0,"content_index":0,"item_id":"rs_9f11d7be9b86827b","text":"The + user wants me to reply with only the word \"VIOLET\".\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":20,"output_index":0,"item":{"id":"rs_9f11d7be9b86827b","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"VIOLET\".\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":21,"output_index":1,"item":{"id":"msg_a8ab98d299a26512","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":22,"output_index":1,"content_index":0,"item_id":"msg_a8ab98d299a26512","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":23,"output_index":1,"content_index":0,"delta":"\n\nV","item_id":"msg_a8ab98d299a26512","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"IO","item_id":"msg_a8ab98d299a26512","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"LET","item_id":"msg_a8ab98d299a26512","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":26,"output_index":1,"content_index":0,"item_id":"msg_a8ab98d299a26512","logprobs":[],"text":"\n\nVIOLET"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":27,"output_index":1,"content_index":0,"item_id":"msg_a8ab98d299a26512","part":{"annotations":[],"text":"\n\nVIOLET","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":28,"output_index":1,"item":{"id":"msg_a8ab98d299a26512","content":[{"annotations":[],"text":"\n\nVIOLET","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":29,"response":{"id":"resp_01a1681f-f595-7982-859e-51bccb69f1e3","object":"response","created_at":1792748287,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_9f11d7be9b86827b","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"VIOLET\".\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_a8ab98d299a26512","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nVIOLET","annotations":[]}]}],"usage":{"input_tokens":47,"output_tokens":22,"total_tokens":69,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":16}},"incomplete_details":null,"error":null,"previous_response_id":null,"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748287,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t15 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-f595-7982-859e-51bccb69f1e3/input_items + query_params: {} + response: + body: + data: + - content: + - text: Reply with only VIOLET. + type: input_text + id: msg_01a1681f-f7ad-7b73-8fe2-fdf22cff3f79 + role: user + status: completed + type: message + first_id: msg_01a1681f-f7ad-7b73-8fe2-fdf22cff3f79 + has_more: false + last_id: msg_01a1681f-f7ad-7b73-8fe2-fdf22cff3f79 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1681f-ef15-7b91-a366-b9658ae97c12/input_items + query_params: + after: msg_01a1681f-f7ad-7b73-8fe2-fdf22cff3f79 + response: + body: + error: + code: null + message: No item found with id 'msg_01a1681f-f7ad-7b73-8fe2-fdf22cff3f79' + param: after + type: invalid_request_error + headers: + content-type: application/json + status_code: 404 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-gateway.yaml new file mode 100644 index 000000000..19324b4f8 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-gateway.yaml @@ -0,0 +1,584 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748305 + created_at: 1792748305 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-3b48-7372-bd98-214486197122 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_a593f79b2097fe2d + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_9548042b171a269a + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 24 + output_tokens_details: + reasoning_tokens: 17 + total_tokens: 71 + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748306 + created_at: 1792748306 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-3dae-7192-985f-b0b8cc948278 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user is asking me to reply with only the word "SAPPHIRE." + + ' + type: reasoning_text + id: rs_a3eb5300385d8c5a + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_97f8cea357370a3f + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 25 + output_tokens_details: + reasoning_tokens: 18 + total_tokens: 72 + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3dae-7192-985f-b0b8cc948278/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + type: message + - content: Reply with only VIOLET. + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: null + message: Duplicate item found with id msg_01a16820-3d8b-7a11-b157-3c11de042d72. + Remove duplicate items from your input and try again. + param: input + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + id: item_wrongprefix_d71598b4bf20d34f + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: invalid_value + message: 'Invalid ''input[0].id'': ''item_wrongprefix_d71598b4bf20d34f''. + Expected an ID that begins with ''msg''.' + param: input[0].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: + input: + - arguments: '{}' + call_id: call_history_probe + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: Reply with only the lookup result. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748307 + created_at: 1792748307 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-4190-7cf3-aed2-e40d5f1b45da + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the lookup result from the previous + turn. The lookup result was "SAPPHIRE". + + ' + type: reasoning_text + id: rs_982d339e9f98aca5 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_8082f76f0614422c + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 82 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 34 + output_tokens_details: + reasoning_tokens: 27 + total_tokens: 116 + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4190-7cf3-aed2-e40d5f1b45da/input_items + query_params: + order: asc + response: + body: + data: + - arguments: '{}' + call_id: call_history_probe + id: fc_01a16820-44a1-7dd1-8914-80ffa744f5a0 + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: + - text: Reply with only the lookup result. + type: input_text + id: msg_01a16820-44a1-7dd1-8914-8112e1445097 + role: user + status: completed + type: message + first_id: fc_01a16820-44a1-7dd1-8914-80ffa744f5a0 + has_more: false + last_id: msg_01a16820-44a1-7dd1-8914-8112e1445097 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-3b48-7372-bd98-214486197122/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + role: user + status: completed + type: message + first_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + has_more: false + last_id: msg_01a16820-3d8b-7a11-b157-3c11de042d72 + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-gateway.yaml new file mode 100644 index 000000000..52eb2cd89 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-replayed-items-stream-gateway.yaml @@ -0,0 +1,830 @@ +turns: +- filename: t1 + request: + body: + input: + - content: Reply with only SAPPHIRE. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: true + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + headers: + content-type: text/event-stream; charset=utf-8 + sse: + - 'event: response.created + + ' + - 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_01a16820-4a84-7ac2-86de-a8608839d611","created_at":1792748309,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.in_progress + + ' + - 'data: {"type":"response.in_progress","sequence_number":1,"response":{"id":"resp_01a16820-4a84-7ac2-86de-a8608839d611","created_at":1792748309,"incomplete_details":null,"instructions":null,"metadata":{},"model":"Qwen/Qwen3.8-27B-FP8","object":"response","output":[],"parallel_tool_calls":false,"temperature":1.0,"tool_choice":"none","tools":[],"top_p":1.0,"background":false,"max_output_tokens":null,"max_tool_calls":null,"previous_response_id":null,"prompt":null,"reasoning":null,"service_tier":"auto","status":"in_progress","text":{"format":{"type":"text"}},"top_logprobs":0,"truncation":"disabled","usage":null,"user":null,"presence_penalty":0.0,"frequency_penalty":0.0,"kv_transfer_params":null,"ec_transfer_params":null,"input_messages":null,"output_messages":null,"completed_at":null,"store":true,"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null,"error":null}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":2,"output_index":0,"item":{"id":"rs_a4cd688dbf81fe1f","summary":[],"type":"reasoning","content":[],"status":"in_progress"}} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":3,"output_index":0,"content_index":0,"delta":"The","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":4,"output_index":0,"content_index":0,"delta":" + user","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":5,"output_index":0,"content_index":0,"delta":" + wants","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":6,"output_index":0,"content_index":0,"delta":" + me","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":7,"output_index":0,"content_index":0,"delta":" + to","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":8,"output_index":0,"content_index":0,"delta":" + reply","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":9,"output_index":0,"content_index":0,"delta":" + with","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":10,"output_index":0,"content_index":0,"delta":" + only","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":11,"output_index":0,"content_index":0,"delta":" + the","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":12,"output_index":0,"content_index":0,"delta":" + word","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":13,"output_index":0,"content_index":0,"delta":" + \"","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":14,"output_index":0,"content_index":0,"delta":"S","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":15,"output_index":0,"content_index":0,"delta":"AP","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":16,"output_index":0,"content_index":0,"delta":"PH","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":17,"output_index":0,"content_index":0,"delta":"IRE","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":18,"output_index":0,"content_index":0,"delta":".\"","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.delta + + ' + - 'data: {"type":"response.reasoning_text.delta","sequence_number":19,"output_index":0,"content_index":0,"delta":"\n","item_id":"rs_a4cd688dbf81fe1f"} + + ' + - ' + + ' + - 'event: response.reasoning_text.done + + ' + - 'data: {"type":"response.reasoning_text.done","sequence_number":20,"output_index":0,"content_index":0,"item_id":"rs_a4cd688dbf81fe1f","text":"The + user wants me to reply with only the word \"SAPPHIRE.\"\n"} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":21,"output_index":0,"item":{"id":"rs_a4cd688dbf81fe1f","summary":[],"type":"reasoning","content":[{"text":"The + user wants me to reply with only the word \"SAPPHIRE.\"\n","type":"reasoning_text"}],"status":"completed"}} + + ' + - ' + + ' + - 'event: response.output_item.added + + ' + - 'data: {"type":"response.output_item.added","sequence_number":22,"output_index":1,"item":{"id":"msg_baaee0fee3e10a0d","content":[],"role":"assistant","status":"in_progress","type":"message"}} + + ' + - ' + + ' + - 'event: response.content_part.added + + ' + - 'data: {"type":"response.content_part.added","sequence_number":23,"output_index":1,"content_index":0,"item_id":"msg_baaee0fee3e10a0d","part":{"annotations":[],"text":"","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":24,"output_index":1,"content_index":0,"delta":"\n\nS","item_id":"msg_baaee0fee3e10a0d","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":25,"output_index":1,"content_index":0,"delta":"AP","item_id":"msg_baaee0fee3e10a0d","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":26,"output_index":1,"content_index":0,"delta":"PH","item_id":"msg_baaee0fee3e10a0d","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.delta + + ' + - 'data: {"type":"response.output_text.delta","sequence_number":27,"output_index":1,"content_index":0,"delta":"IRE","item_id":"msg_baaee0fee3e10a0d","logprobs":[]} + + ' + - ' + + ' + - 'event: response.output_text.done + + ' + - 'data: {"type":"response.output_text.done","sequence_number":28,"output_index":1,"content_index":0,"item_id":"msg_baaee0fee3e10a0d","logprobs":[],"text":"\n\nSAPPHIRE"} + + ' + - ' + + ' + - 'event: response.content_part.done + + ' + - 'data: {"type":"response.content_part.done","sequence_number":29,"output_index":1,"content_index":0,"item_id":"msg_baaee0fee3e10a0d","part":{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}} + + ' + - ' + + ' + - 'event: response.output_item.done + + ' + - 'data: {"type":"response.output_item.done","sequence_number":30,"output_index":1,"item":{"id":"msg_baaee0fee3e10a0d","content":[{"annotations":[],"text":"\n\nSAPPHIRE","type":"output_text","logprobs":[]}],"role":"assistant","status":"completed","type":"message"}} + + ' + - ' + + ' + - 'event: response.completed + + ' + - 'data: {"type":"response.completed","sequence_number":31,"response":{"id":"resp_01a16820-4a84-7ac2-86de-a8608839d611","object":"response","created_at":1792748309,"model":"Qwen/Qwen3.8-27B-FP8","status":"completed","output":[{"type":"reasoning","id":"rs_a4cd688dbf81fe1f","content":[{"type":"reasoning_text","text":"The + user wants me to reply with only the word \"SAPPHIRE.\"\n"}],"summary":[],"status":"completed"},{"type":"message","id":"msg_baaee0fee3e10a0d","role":"assistant","status":"completed","content":[{"type":"output_text","text":"\n\nSAPPHIRE","annotations":[]}]}],"usage":{"input_tokens":47,"output_tokens":24,"total_tokens":71,"input_tokens_details":{"cached_tokens":0,"cache_write_tokens":0},"output_tokens_details":{"reasoning_tokens":17}},"incomplete_details":null,"error":null,"previous_response_id":null,"instructions":null,"max_tool_calls":null,"service_tier":"auto","tools":[],"tool_choice":"auto","completed_at":1792748309,"truncation":"disabled","parallel_tool_calls":false,"text":{"format":{"type":"text"}},"top_p":1.0,"presence_penalty":0.0,"frequency_penalty":0.0,"top_logprobs":0,"temperature":1.0,"reasoning":null,"max_output_tokens":null,"store":true,"background":false,"metadata":{},"safety_identifier":null,"prompt_cache_key":null,"prompt_cache_options":null}} + + ' + - ' + + ' + - 'data: [DONE] + + ' + - ' + + ' + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748310 + created_at: 1792748310 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-4d06-72e2-98b8-4426fea978c9 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the word "SAPPHIRE". + + ' + type: reasoning_text + id: rs_bfa9f6bd06e01a21 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b1f359add9782112 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 47 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 24 + output_tokens_details: + reasoning_tokens: 17 + total_tokens: 71 + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4d06-72e2-98b8-4426fea978c9/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: + input: + - content: Reply with only ORCHID. + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + type: message + - content: Reply with only VIOLET. + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: null + message: Duplicate item found with id msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4. + Remove duplicate items from your input and try again. + param: input + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t7 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: + - content: Reply with only ORCHID. + id: item_wrongprefix_89b8b67d44667ef6 + role: user + type: message + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + error: + code: invalid_value + message: 'Invalid ''input[0].id'': ''item_wrongprefix_89b8b67d44667ef6''. + Expected an ID that begins with ''msg''.' + param: input[0].id + type: invalid_request_error + headers: + content-type: application/json + status_code: 400 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: + input: + - arguments: '{}' + call_id: call_history_probe + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: Reply with only the lookup result. + role: user + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792748311 + created_at: 1792748311 + error: null + frequency_penalty: 0.0 + id: resp_01a16820-5138-7cc1-9558-14011b3410b4 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with only the lookup result, which was + "SAPPHIRE". + + ' + type: reasoning_text + id: rs_83b3dbf08db452a7 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + SAPPHIRE' + type: output_text + id: msg_b3e0297184259a99 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 82 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 28 + output_tokens_details: + reasoning_tokens: 21 + total_tokens: 110 + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-5138-7cc1-9558-14011b3410b4/input_items + query_params: + order: asc + response: + body: + data: + - arguments: '{}' + call_id: call_history_probe + id: fc_01a16820-53d1-7583-851b-b15b6d9fd689 + name: lookup + type: function_call + - call_id: call_history_probe + output: SAPPHIRE + type: function_call_output + - content: + - text: Reply with only the lookup result. + type: input_text + id: msg_01a16820-53d1-7583-851b-b17055be1005 + role: user + status: completed + type: message + first_id: fc_01a16820-53d1-7583-851b-b15b6d9fd689 + has_more: false + last_id: msg_01a16820-53d1-7583-851b-b17055be1005 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16820-4a84-7ac2-86de-a8608839d611/input_items + query_params: + order: asc + response: + body: + data: + - content: + - text: Reply with only SAPPHIRE. + type: input_text + id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + role: user + status: completed + type: message + first_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + has_more: false + last_id: msg_01a16820-4cc9-7541-b7d9-55e4add4cbe4 + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-gateway.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-gateway.yaml new file mode 100644 index 000000000..a57227f72 --- /dev/null +++ b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-gateway.yaml @@ -0,0 +1,9311 @@ +turns: +- filename: t1 + request: + body: + input: 'Do two separate web_search calls in parallel, in this single turn -- + do not wait for one to finish before starting the other. The first web_search + call must batch two exact queries together: "potato nutrition facts" and "tomato + nutrition facts". The second web_search call must batch two different exact + queries together: "cucumber nutrition facts" and "carrot nutrition facts". + Issue both calls now, then summarize each of the four results in one sentence.' + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753055 + created_at: 1792753055 + error: null + frequency_penalty: 0.0 + id: resp_01a16868-644c-78d3-8018-5ba2236e2c29 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - id: mcpl_01a16868-68c8-7483-a47b-626ba509ed37 + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools + - content: + - text: 'The user wants me to make two web_search calls in parallel in this + single turn. The first call should batch "potato nutrition facts" and + "tomato nutrition facts" together using the `queries` parameter. The second + call should batch "cucumber nutrition facts" and "carrot nutrition facts" + together using the `queries` parameter. Both calls should be made in the + same function_calls block to execute in parallel. + + ' + type: reasoning_text + id: rs_8694582545bb175b + summary: [] + type: reasoning + - action: + queries: + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + sources: + - title: Potato Nutrition Facts | Nutrients, Calories, Benefits of a Potato + url: https://potatogoodness.com/nutrition/ + - title: Potato + url: https://www.fda.gov/media/76882/download + - title: FACTS | Modern Solutions for K-12 Schools and More + url: https://factsmgt.com/ + - title: 'Potatoes 101: Nutrition Facts, Health Benefits, and Types' + url: https://www.healthline.com/nutrition/foods/potatoes + - title: 'FACTS SIS: Accounts' + url: https://accounts.renweb.com/Account/Login + - title: Nutrition - Potatoes International + url: https://usapotatoes.com/potato-nutrition/ + - title: Food Search | USDA FoodData Central + url: https://fdc.nal.usda.gov/food-search?query=potato + - title: Potato Nutrition - Cooking with Potatoes - Alsum + url: https://alsum.com/products/potato-nutrition-facts/ + - title: Idaho® Potato Nutrition Facts | Idaho Potato Commission + url: https://idahopotato.com/nutrition + - title: The EDFacts Initiative | U.S. Department of Education + url: https://www.ed.gov/data/edfacts-initiative + - title: 'Tomatoes: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/tomatoes + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellhealth.com/tomato-nutrition-12012681 + - title: Nutrition Facts for Tomatoes + url: https://tools.myfooddata.com/nutrition-facts/170457/wt6 + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/tomatoes-nutrition-facts-calories-and-health-benefits-4119981 + - title: Tomatoes - SNAP-Ed - USDA + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/tomatoes + - title: Tomatoes Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/tomatoes-2226 + - title: 'Tomatoes 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/tomatoes + - title: 'Tomatoes, raw: calories and nutrition facts' + url: https://www.nutritionvalue.org/Tomatoes%2C_raw_74101000_nutritional_value.html + - title: Top 10 Tomato Nutrition facts and Health benefits + url: https://www.nutrition-and-you.com/tomato.html + - title: 'Tomatoes: A Nutritional Powerhouse and Culinary Favorite - UF/IFAS + ...' + url: https://blogs.ifas.ufl.edu/brevardco/2025/04/07/tomatoes-a-nutritional-powerhouse-and-culinary-favorite/ + type: search + id: ws_90d3592d82410588 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + sources: + - title: Cucumber Nutrition Facts and Benefits - Franklin County Center + ... + url: https://franklin.ces.ncsu.edu/news/cucumber-nutrition-facts-and-benefits/ + - title: Health Benefits of Cucumber + url: https://www.webmd.com/food-recipes/cucumber-health-benefits + - title: Cucumbers | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/cucumbers + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11206-3 + - title: Nutrition Facts for Cucumber + url: https://tools.myfooddata.com/nutrition-facts/168409/wt9 + - title: 'Cucumber: Calories, Nutrition and Health Benefits | NutriScan + App' + url: https://nutriscan.app/calories-nutrition/sliced-cucumber + - title: ¼ Cucumber Of Cucumber Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/cucumber-1971?a=0.25%3A2 + - title: Cucumber, peeled, raw, 1 medium | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/cucumber-peeled-raw-1-medium + - title: Are Cucumbers Good for You? + url: https://health.clevelandclinic.org/benefits-of-cucumbers + - title: 'Cucumber: nutritional values, nutrients and calories | FreshMAG + ...' + url: https://www.liebherr.com/en-gb/fridges-freezers/freshmag/food-a-z/cucumber-8279000 + - title: 'Carrots 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/carrots + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11124-3 + - title: Carrots | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/carrots + - title: Nutrition Facts for Raw Carrots + url: https://tools.myfooddata.com/nutrition-facts/170393/wt4 + - title: 'Carrots: Health Benefits, Nutrition Facts, and Risks' + url: https://www.webmd.com/food-recipes/benefits-carrots + - title: Carrot Nutrition Facts 0g Fat 25 calories 0.5g Protein 1.5g Fiber + ... + url: https://cdn.agclassroom.org/media/uploads/LP875/Carrot_nutrition_facts.pdf + - title: Carrots - Nutritional information | Wilcox + url: https://www.wilcoxgoodness.co.nz/helpful-info/nutritional-information/carrots + - title: Carrots, raw, 1 large (7-1/4" to 8-1/2" long) | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/carrots-raw-1-large-7-14-to-8-12-long + - title: Baby Carrots Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/baby-carrots-2569 + - title: Baby Carrots Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/baby-carrot-nutrition-facts-and-health-benefits-5071650 + type: search + id: ws_8ee6b8b91369059f + status: completed + type: web_search_call + - content: + - text: 'Both searches returned results. Let me summarize each of the four + queries in one sentence: + + + 1. **Potato nutrition facts**: A medium potato (148g) has 110 calories, + 0g fat, 26g carbs, 2g fiber, and is a good source of vitamin C (30% DV), + potassium (15% DV), and vitamin B6 (10% DV). + + + 2. **Tomato nutrition facts**: A small tomato (91g) provides about 16-22 + calories and is an excellent source of vitamin C, fiber, and vitamin K, + plus the cancer-fighting antioxidant lycopene. + + + 3. **Cucumber nutrition facts**: Cucumbers are about 95-96% water with + only 15-16 calories per 100g, and provide vitamin K (20% DV), potassium, + and antioxidants like beta-carotene. + + + 4. **Carrot nutrition facts**: One medium carrot (61g) contains 25 calories, + 6g carbs, 1.5g fiber, 0g fat, and is especially rich in vitamin A (beta-carotene), + vitamin K, and potassium. + + ' + type: reasoning_text + id: rs_bb8c0e8e4b68beed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each of the four searches: + + + 1. **Potato:** A medium potato (148g) provides 110 calories, 26g of carbohydrates, + 2g of fiber, and is a good source of vitamin C (30% DV), potassium (15% + DV), and vitamin B6 (10% DV), while being naturally fat- and cholesterol-free. + + + 2. **Tomato:** A small raw tomato (91g) delivers about 16–22 calories + and is an excellent source of vitamin C, dietary fiber, and vitamin K, + and is the primary dietary source of the antioxidant lycopene, which is + linked to reduced risks of heart disease and certain cancers. + + + 3. **Cucumber:** With only about 15–16 calories per 100g and roughly 95–96% + water, cucumbers provide vitamin K (~20% DV), potassium, and antioxidants + like beta-carotene, making them a hydrating, low-calorie addition to a + healthy diet. + + + 4. **Carrot:** One medium raw carrot (61g) contains 25 calories, 6g of + carbohydrates, 1.5g of fiber, and 0g of fat, and is especially rich in + beta-carotene (vitamin A), vitamin K, and potassium, with diets high in + carotenoids potentially protective against several types of cancer.' + type: output_text + id: msg_862b73745b32c760 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 11196 + input_tokens_details: + cache_write_tokens: 9408 + cached_tokens: 784 + output_tokens: 730 + output_tokens_details: + reasoning_tokens: 344 + total_tokens: 11926 + headers: + content-type: application/json + status_code: 200 +- filename: t2 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16868-644c-78d3-8018-5ba2236e2c29/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' + type: input_text + id: msg_01a16868-b5eb-7231-8c69-837e933982da + role: user + status: completed + type: message + first_id: msg_01a16868-b5eb-7231-8c69-837e933982da + has_more: false + last_id: msg_01a16868-b5eb-7231-8c69-837e933982da + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t3 + request: + body: + input: + - content: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel + for two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one sentence.' + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16868-644c-78d3-8018-5ba2236e2c29 + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753072 + created_at: 1792753072 + error: null + frequency_penalty: 0.0 + id: resp_01a16868-b8bf-73b2-a734-2c38a1d79be7 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to call `mcp__gitmcp_tiktoken__search_tiktoken_documentation` + twice in parallel - once with query "encoding" and once with query "tokenizer". + I''ll issue both calls in the same function_calls block. + + ' + type: reasoning_text + id: rs_8a8a17763b04a832 + summary: [] + type: reasoning + - approval_request_id: null + arguments: '{"query": "encoding"}' + error: null + id: mcp_a4bef669c02bfcf5 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query": "tokenizer"}' + error: null + id: mcp_8db02699fcd1210b + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - text: 'Both searches returned the same fallback README content since no + specific documentation was indexed for either query. Let me summarize + each in one sentence. + + ' + type: reasoning_text + id: rs_9dca2a42406fd775 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each query: + + + 1. **"encoding" search:** No specific documentation was indexed for "encoding," + so the tool returned a fallback of tiktoken''s README, which explains + that tiktoken is a fast BPE tokeniser for OpenAI''s models, that encodings + can be retrieved via `tiktoken.get_encoding("o200k_base")` or `tiktoken.encoding_for_model("gpt-4o")`, + and that custom encodings can be extended by either directly constructing + `tiktoken.Encoding` objects or registering them through the `tiktoken_ext` + plugin mechanism. + + + 2. **"tokenizer" search:** Similarly, no specific documentation was indexed + for "tokenizer," and the same fallback README was returned, noting that + tiktoken is 3–6× faster than comparable open-source tokenisers (benchmarked + against `GPT2TokenizerFast` from HuggingFace `tokenizers`), that the core + tokenizer API is documented in `tiktoken/core.py`, and that BPE works + by converting text into reversible, lossless token sequences where each + token corresponds to roughly 4 bytes on average.' + type: output_text + id: msg_9625fc5a78cfcb95 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16868-644c-78d3-8018-5ba2236e2c29 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 24974 + input_tokens_details: + cache_write_tokens: 13328 + cached_tokens: 10976 + output_tokens: 393 + output_tokens_details: + reasoning_tokens: 81 + total_tokens: 25367 + headers: + content-type: application/json + status_code: 200 +- filename: t4 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16868-b8bf-73b2-a734-2c38a1d79be7/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' + type: input_text + id: msg_01a16868-b5eb-7231-8c69-837e933982da + role: user + status: completed + type: message + - id: mcpl_01a16868-68c8-7483-a47b-626ba509ed37 + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools + - content: + - text: 'The user wants me to make two web_search calls in parallel in this + single turn. The first call should batch "potato nutrition facts" and + "tomato nutrition facts" together using the `queries` parameter. The second + call should batch "cucumber nutrition facts" and "carrot nutrition facts" + together using the `queries` parameter. Both calls should be made in the + same function_calls block to execute in parallel. + + ' + type: reasoning_text + id: rs_8694582545bb175b + summary: [] + type: reasoning + - action: + queries: + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + sources: + - title: Potato Nutrition Facts | Nutrients, Calories, Benefits of a Potato + url: https://potatogoodness.com/nutrition/ + - title: Potato + url: https://www.fda.gov/media/76882/download + - title: FACTS | Modern Solutions for K-12 Schools and More + url: https://factsmgt.com/ + - title: 'Potatoes 101: Nutrition Facts, Health Benefits, and Types' + url: https://www.healthline.com/nutrition/foods/potatoes + - title: 'FACTS SIS: Accounts' + url: https://accounts.renweb.com/Account/Login + - title: Nutrition - Potatoes International + url: https://usapotatoes.com/potato-nutrition/ + - title: Food Search | USDA FoodData Central + url: https://fdc.nal.usda.gov/food-search?query=potato + - title: Potato Nutrition - Cooking with Potatoes - Alsum + url: https://alsum.com/products/potato-nutrition-facts/ + - title: Idaho® Potato Nutrition Facts | Idaho Potato Commission + url: https://idahopotato.com/nutrition + - title: The EDFacts Initiative | U.S. Department of Education + url: https://www.ed.gov/data/edfacts-initiative + - title: 'Tomatoes: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/tomatoes + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellhealth.com/tomato-nutrition-12012681 + - title: Nutrition Facts for Tomatoes + url: https://tools.myfooddata.com/nutrition-facts/170457/wt6 + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/tomatoes-nutrition-facts-calories-and-health-benefits-4119981 + - title: Tomatoes - SNAP-Ed - USDA + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/tomatoes + - title: Tomatoes Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/tomatoes-2226 + - title: 'Tomatoes 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/tomatoes + - title: 'Tomatoes, raw: calories and nutrition facts' + url: https://www.nutritionvalue.org/Tomatoes%2C_raw_74101000_nutritional_value.html + - title: Top 10 Tomato Nutrition facts and Health benefits + url: https://www.nutrition-and-you.com/tomato.html + - title: 'Tomatoes: A Nutritional Powerhouse and Culinary Favorite - UF/IFAS + ...' + url: https://blogs.ifas.ufl.edu/brevardco/2025/04/07/tomatoes-a-nutritional-powerhouse-and-culinary-favorite/ + type: search + id: ws_90d3592d82410588 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + sources: + - title: Cucumber Nutrition Facts and Benefits - Franklin County Center + ... + url: https://franklin.ces.ncsu.edu/news/cucumber-nutrition-facts-and-benefits/ + - title: Health Benefits of Cucumber + url: https://www.webmd.com/food-recipes/cucumber-health-benefits + - title: Cucumbers | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/cucumbers + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11206-3 + - title: Nutrition Facts for Cucumber + url: https://tools.myfooddata.com/nutrition-facts/168409/wt9 + - title: 'Cucumber: Calories, Nutrition and Health Benefits | NutriScan + App' + url: https://nutriscan.app/calories-nutrition/sliced-cucumber + - title: ¼ Cucumber Of Cucumber Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/cucumber-1971?a=0.25%3A2 + - title: Cucumber, peeled, raw, 1 medium | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/cucumber-peeled-raw-1-medium + - title: Are Cucumbers Good for You? + url: https://health.clevelandclinic.org/benefits-of-cucumbers + - title: 'Cucumber: nutritional values, nutrients and calories | FreshMAG + ...' + url: https://www.liebherr.com/en-gb/fridges-freezers/freshmag/food-a-z/cucumber-8279000 + - title: 'Carrots 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/carrots + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11124-3 + - title: Carrots | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/carrots + - title: Nutrition Facts for Raw Carrots + url: https://tools.myfooddata.com/nutrition-facts/170393/wt4 + - title: 'Carrots: Health Benefits, Nutrition Facts, and Risks' + url: https://www.webmd.com/food-recipes/benefits-carrots + - title: Carrot Nutrition Facts 0g Fat 25 calories 0.5g Protein 1.5g Fiber + ... + url: https://cdn.agclassroom.org/media/uploads/LP875/Carrot_nutrition_facts.pdf + - title: Carrots - Nutritional information | Wilcox + url: https://www.wilcoxgoodness.co.nz/helpful-info/nutritional-information/carrots + - title: Carrots, raw, 1 large (7-1/4" to 8-1/2" long) | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/carrots-raw-1-large-7-14-to-8-12-long + - title: Baby Carrots Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/baby-carrots-2569 + - title: Baby Carrots Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/baby-carrot-nutrition-facts-and-health-benefits-5071650 + type: search + id: ws_8ee6b8b91369059f + status: completed + type: web_search_call + - content: + - text: 'Both searches returned results. Let me summarize each of the four + queries in one sentence: + + + 1. **Potato nutrition facts**: A medium potato (148g) has 110 calories, + 0g fat, 26g carbs, 2g fiber, and is a good source of vitamin C (30% DV), + potassium (15% DV), and vitamin B6 (10% DV). + + + 2. **Tomato nutrition facts**: A small tomato (91g) provides about 16-22 + calories and is an excellent source of vitamin C, fiber, and vitamin K, + plus the cancer-fighting antioxidant lycopene. + + + 3. **Cucumber nutrition facts**: Cucumbers are about 95-96% water with + only 15-16 calories per 100g, and provide vitamin K (20% DV), potassium, + and antioxidants like beta-carotene. + + + 4. **Carrot nutrition facts**: One medium carrot (61g) contains 25 calories, + 6g carbs, 1.5g fiber, 0g fat, and is especially rich in vitamin A (beta-carotene), + vitamin K, and potassium. + + ' + type: reasoning_text + id: rs_bb8c0e8e4b68beed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each of the four searches: + + + 1. **Potato:** A medium potato (148g) provides 110 calories, 26g of carbohydrates, + 2g of fiber, and is a good source of vitamin C (30% DV), potassium (15% + DV), and vitamin B6 (10% DV), while being naturally fat- and cholesterol-free. + + + 2. **Tomato:** A small raw tomato (91g) delivers about 16–22 calories + and is an excellent source of vitamin C, dietary fiber, and vitamin K, + and is the primary dietary source of the antioxidant lycopene, which is + linked to reduced risks of heart disease and certain cancers. + + + 3. **Cucumber:** With only about 15–16 calories per 100g and roughly 95–96% + water, cucumbers provide vitamin K (~20% DV), potassium, and antioxidants + like beta-carotene, making them a hydrating, low-calorie addition to a + healthy diet. + + + 4. **Carrot:** One medium raw carrot (61g) contains 25 calories, 6g of + carbohydrates, 1.5g of fiber, and 0g of fat, and is especially rich in + beta-carotene (vitamin A), vitamin K, and potassium, with diets high in + carotenoids potentially protective against several types of cancer.' + type: output_text + id: msg_862b73745b32c760 + role: assistant + status: completed + type: message + - content: + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' + type: input_text + id: msg_01a16868-f8e7-7121-a3e5-71db08a39cb0 + role: user + status: completed + type: message + first_id: msg_01a16868-b5eb-7231-8c69-837e933982da + has_more: false + last_id: msg_01a16868-f8e7-7121-a3e5-71db08a39cb0 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t5 + request: + body: + input: + - content: 'Do both of these right now, in parallel, in this single turn -- + do not wait for one to finish before starting the other: (1) search the + web for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16868-b8bf-73b2-a734-2c38a1d79be7 + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753090 + created_at: 1792753090 + error: null + frequency_penalty: 0.0 + id: resp_01a16868-f9b7-7551-ae08-2017135c85ba + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to make two tool calls in parallel: + + 1. A web_search call with the query "latest vLLM release notes" + + 2. A gitmcp_tiktoken__search_tiktoken_documentation call with the query + "encoding" + + + These are independent, so I''ll issue both in the same block. + + ' + type: reasoning_text + id: rs_b118aa524415e1c8 + summary: [] + type: reasoning + - action: + queries: + - latest vLLM release notes + query: latest vLLM release notes + sources: + - title: Releases · vllm-project/vllm + url: https://github.com/vllm-project/vllm/releases + - title: vLLM Release Notes - NVIDIA Docs + url: https://docs.nvidia.com/deeplearning/frameworks/vllm-release-notes/index.html + - title: Release Notes - vLLM Ascend + url: https://docs.vllm.ai/projects/ascend/en/latest/user_guide/release_notes.html + - title: Previous vLLM Releases | vLLM + url: https://vllm.ai/releases + - title: Release Notes — vllm-ascend + url: https://docs.vllm.ai/projects/ascend/en/main/user_guide/release_notes.html + - title: Releases · vllm-project/vllm-ascend + url: https://github.com/vllm-project/vllm-ascend/releases + - title: Blog | vLLM + url: https://vllm.ai/blog + - title: vLLM release history | Freedom.Tech + url: https://freedom.tech/project/vllm/ + - title: Release Notes - vLLM Hardware Plugin for Intel® Gaudi® + url: https://docs.vllm.ai/projects/gaudi/en/latest/release_notes.html + - title: Release note — vllm-ascend + url: https://docs.vllm.ai/projects/ascend/en/v0.10.0rc1/user_guide/release_notes.html + type: search + id: ws_b02f4144d93a3ba4 + status: completed + type: web_search_call + - approval_request_id: null + arguments: '{"query": "encoding"}' + error: null + id: mcp_8ccd9b6e8f62355f + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - text: 'Both calls were made in parallel as requested. Let me summarize the + results. + + + 1. Web search for "latest vLLM release notes" - found the GitHub releases + page, which shows the latest release (v0.26.0) with 411 commits from 212 + contributors, featuring improvements like faster startup, bi-directional + KV transfer, and various performance optimizations. + + + 2. tiktoken documentation search for "encoding" - returned the README/fallback + docs explaining that tiktoken is a fast BPE tokeniser, how to use encodings, + and how to extend with custom encodings. + + + The user just asked me to do both in parallel and didn''t ask for a summary + this time - they said "Do not call any other tool." So I should just confirm + both were done and briefly note the results. + + ' + type: reasoning_text + id: rs_be9503832d80f838 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Both calls were issued in parallel in this single turn. Here''s a brief + recap of each: + + + 1. **Web search ("latest vLLM release notes"):** The top result is the + vLLM GitHub releases page, showing the latest release **v0.26.0** (411 + commits from 212 contributors), which adds features like faster startup + via precompiled FlashInfer kernels, bi-directional KV transfer for reduced + TTFT (up to 3.3× on long multi-turn prompts), a shared-memory tensor arena + for multimodal tensors, and least-loaded API worker routing for 43–85% + more req/s on short requests. + + + 2. **tiktoken doc search ("encoding"):** No specific documentation was + indexed, so the tool returned the repo README as a fallback, which explains + that tiktoken is a fast BPE tokeniser for OpenAI models, that you can + retrieve encodings via `tiktoken.get_encoding("o200k_base")` or `tiktoken.encoding_for_model("gpt-4o")`, + and that custom encodings can be created by directly constructing `tiktoken.Encoding` + objects or registering them through the `tiktoken_ext` plugin mechanism.' + type: output_text + id: msg_86c8e81126d72e24 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16868-b8bf-73b2-a734-2c38a1d79be7 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 34388 + input_tokens_details: + cache_write_tokens: 8624 + cached_tokens: 25088 + output_tokens: 567 + output_tokens_details: + reasoning_tokens: 241 + total_tokens: 34955 + headers: + content-type: application/json + status_code: 200 +- filename: t6 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16868-f9b7-7551-ae08-2017135c85ba/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' + type: input_text + id: msg_01a16868-b5eb-7231-8c69-837e933982da + role: user + status: completed + type: message + - id: mcpl_01a16868-68c8-7483-a47b-626ba509ed37 + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools + - content: + - text: 'The user wants me to make two web_search calls in parallel in this + single turn. The first call should batch "potato nutrition facts" and + "tomato nutrition facts" together using the `queries` parameter. The second + call should batch "cucumber nutrition facts" and "carrot nutrition facts" + together using the `queries` parameter. Both calls should be made in the + same function_calls block to execute in parallel. + + ' + type: reasoning_text + id: rs_8694582545bb175b + summary: [] + type: reasoning + - action: + queries: + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + sources: + - title: Potato Nutrition Facts | Nutrients, Calories, Benefits of a Potato + url: https://potatogoodness.com/nutrition/ + - title: Potato + url: https://www.fda.gov/media/76882/download + - title: FACTS | Modern Solutions for K-12 Schools and More + url: https://factsmgt.com/ + - title: 'Potatoes 101: Nutrition Facts, Health Benefits, and Types' + url: https://www.healthline.com/nutrition/foods/potatoes + - title: 'FACTS SIS: Accounts' + url: https://accounts.renweb.com/Account/Login + - title: Nutrition - Potatoes International + url: https://usapotatoes.com/potato-nutrition/ + - title: Food Search | USDA FoodData Central + url: https://fdc.nal.usda.gov/food-search?query=potato + - title: Potato Nutrition - Cooking with Potatoes - Alsum + url: https://alsum.com/products/potato-nutrition-facts/ + - title: Idaho® Potato Nutrition Facts | Idaho Potato Commission + url: https://idahopotato.com/nutrition + - title: The EDFacts Initiative | U.S. Department of Education + url: https://www.ed.gov/data/edfacts-initiative + - title: 'Tomatoes: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/tomatoes + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellhealth.com/tomato-nutrition-12012681 + - title: Nutrition Facts for Tomatoes + url: https://tools.myfooddata.com/nutrition-facts/170457/wt6 + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/tomatoes-nutrition-facts-calories-and-health-benefits-4119981 + - title: Tomatoes - SNAP-Ed - USDA + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/tomatoes + - title: Tomatoes Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/tomatoes-2226 + - title: 'Tomatoes 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/tomatoes + - title: 'Tomatoes, raw: calories and nutrition facts' + url: https://www.nutritionvalue.org/Tomatoes%2C_raw_74101000_nutritional_value.html + - title: Top 10 Tomato Nutrition facts and Health benefits + url: https://www.nutrition-and-you.com/tomato.html + - title: 'Tomatoes: A Nutritional Powerhouse and Culinary Favorite - UF/IFAS + ...' + url: https://blogs.ifas.ufl.edu/brevardco/2025/04/07/tomatoes-a-nutritional-powerhouse-and-culinary-favorite/ + type: search + id: ws_90d3592d82410588 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + sources: + - title: Cucumber Nutrition Facts and Benefits - Franklin County Center + ... + url: https://franklin.ces.ncsu.edu/news/cucumber-nutrition-facts-and-benefits/ + - title: Health Benefits of Cucumber + url: https://www.webmd.com/food-recipes/cucumber-health-benefits + - title: Cucumbers | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/cucumbers + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11206-3 + - title: Nutrition Facts for Cucumber + url: https://tools.myfooddata.com/nutrition-facts/168409/wt9 + - title: 'Cucumber: Calories, Nutrition and Health Benefits | NutriScan + App' + url: https://nutriscan.app/calories-nutrition/sliced-cucumber + - title: ¼ Cucumber Of Cucumber Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/cucumber-1971?a=0.25%3A2 + - title: Cucumber, peeled, raw, 1 medium | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/cucumber-peeled-raw-1-medium + - title: Are Cucumbers Good for You? + url: https://health.clevelandclinic.org/benefits-of-cucumbers + - title: 'Cucumber: nutritional values, nutrients and calories | FreshMAG + ...' + url: https://www.liebherr.com/en-gb/fridges-freezers/freshmag/food-a-z/cucumber-8279000 + - title: 'Carrots 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/carrots + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11124-3 + - title: Carrots | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/carrots + - title: Nutrition Facts for Raw Carrots + url: https://tools.myfooddata.com/nutrition-facts/170393/wt4 + - title: 'Carrots: Health Benefits, Nutrition Facts, and Risks' + url: https://www.webmd.com/food-recipes/benefits-carrots + - title: Carrot Nutrition Facts 0g Fat 25 calories 0.5g Protein 1.5g Fiber + ... + url: https://cdn.agclassroom.org/media/uploads/LP875/Carrot_nutrition_facts.pdf + - title: Carrots - Nutritional information | Wilcox + url: https://www.wilcoxgoodness.co.nz/helpful-info/nutritional-information/carrots + - title: Carrots, raw, 1 large (7-1/4" to 8-1/2" long) | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/carrots-raw-1-large-7-14-to-8-12-long + - title: Baby Carrots Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/baby-carrots-2569 + - title: Baby Carrots Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/baby-carrot-nutrition-facts-and-health-benefits-5071650 + type: search + id: ws_8ee6b8b91369059f + status: completed + type: web_search_call + - content: + - text: 'Both searches returned results. Let me summarize each of the four + queries in one sentence: + + + 1. **Potato nutrition facts**: A medium potato (148g) has 110 calories, + 0g fat, 26g carbs, 2g fiber, and is a good source of vitamin C (30% DV), + potassium (15% DV), and vitamin B6 (10% DV). + + + 2. **Tomato nutrition facts**: A small tomato (91g) provides about 16-22 + calories and is an excellent source of vitamin C, fiber, and vitamin K, + plus the cancer-fighting antioxidant lycopene. + + + 3. **Cucumber nutrition facts**: Cucumbers are about 95-96% water with + only 15-16 calories per 100g, and provide vitamin K (20% DV), potassium, + and antioxidants like beta-carotene. + + + 4. **Carrot nutrition facts**: One medium carrot (61g) contains 25 calories, + 6g carbs, 1.5g fiber, 0g fat, and is especially rich in vitamin A (beta-carotene), + vitamin K, and potassium. + + ' + type: reasoning_text + id: rs_bb8c0e8e4b68beed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each of the four searches: + + + 1. **Potato:** A medium potato (148g) provides 110 calories, 26g of carbohydrates, + 2g of fiber, and is a good source of vitamin C (30% DV), potassium (15% + DV), and vitamin B6 (10% DV), while being naturally fat- and cholesterol-free. + + + 2. **Tomato:** A small raw tomato (91g) delivers about 16–22 calories + and is an excellent source of vitamin C, dietary fiber, and vitamin K, + and is the primary dietary source of the antioxidant lycopene, which is + linked to reduced risks of heart disease and certain cancers. + + + 3. **Cucumber:** With only about 15–16 calories per 100g and roughly 95–96% + water, cucumbers provide vitamin K (~20% DV), potassium, and antioxidants + like beta-carotene, making them a hydrating, low-calorie addition to a + healthy diet. + + + 4. **Carrot:** One medium raw carrot (61g) contains 25 calories, 6g of + carbohydrates, 1.5g of fiber, and 0g of fat, and is especially rich in + beta-carotene (vitamin A), vitamin K, and potassium, with diets high in + carotenoids potentially protective against several types of cancer.' + type: output_text + id: msg_862b73745b32c760 + role: assistant + status: completed + type: message + - content: + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' + type: input_text + id: msg_01a16868-f8e7-7121-a3e5-71db08a39cb0 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call `mcp__gitmcp_tiktoken__search_tiktoken_documentation` + twice in parallel - once with query "encoding" and once with query "tokenizer". + I''ll issue both calls in the same function_calls block. + + ' + type: reasoning_text + id: rs_8a8a17763b04a832 + summary: [] + type: reasoning + - approval_request_id: null + arguments: '{"query": "encoding"}' + error: null + id: mcp_a4bef669c02bfcf5 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query": "tokenizer"}' + error: null + id: mcp_8db02699fcd1210b + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - text: 'Both searches returned the same fallback README content since no + specific documentation was indexed for either query. Let me summarize + each in one sentence. + + ' + type: reasoning_text + id: rs_9dca2a42406fd775 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each query: + + + 1. **"encoding" search:** No specific documentation was indexed for "encoding," + so the tool returned a fallback of tiktoken''s README, which explains + that tiktoken is a fast BPE tokeniser for OpenAI''s models, that encodings + can be retrieved via `tiktoken.get_encoding("o200k_base")` or `tiktoken.encoding_for_model("gpt-4o")`, + and that custom encodings can be extended by either directly constructing + `tiktoken.Encoding` objects or registering them through the `tiktoken_ext` + plugin mechanism. + + + 2. **"tokenizer" search:** Similarly, no specific documentation was indexed + for "tokenizer," and the same fallback README was returned, noting that + tiktoken is 3–6× faster than comparable open-source tokenisers (benchmarked + against `GPT2TokenizerFast` from HuggingFace `tokenizers`), that the core + tokenizer API is documented in `tiktoken/core.py`, and that BPE works + by converting text into reversible, lossless token sequences where each + token corresponds to roughly 4 bytes on average.' + type: output_text + id: msg_9625fc5a78cfcb95 + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one to finish before starting the other: (1) search the web + for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' + type: input_text + id: msg_01a16869-4085-7ea3-b457-aaed0b24407a + role: user + status: completed + type: message + first_id: msg_01a16868-b5eb-7231-8c69-837e933982da + has_more: false + last_id: msg_01a16869-4085-7ea3-b457-aaed0b24407a + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16868-f9b7-7551-ae08-2017135c85ba + store: true + stream: false + tool_choice: none + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753096 + created_at: 1792753096 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-41f8-7d30-8f49-92e87599a7e3 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply only "OK" and not call any tools. + + ' + type: reasoning_text + id: rs_b7341380f1ecc47f + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_a4185254fd908c9e + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16868-f9b7-7551-ae08-2017135c85ba + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 20587 + input_tokens_details: + cache_write_tokens: 6272 + cached_tokens: 14112 + output_tokens: 21 + output_tokens_details: + reasoning_tokens: 17 + total_tokens: 20608 + headers: + content-type: application/json + status_code: 200 +- filename: t8 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-41f8-7d30-8f49-92e87599a7e3/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' + type: input_text + id: msg_01a16868-b5eb-7231-8c69-837e933982da + role: user + status: completed + type: message + - id: mcpl_01a16868-68c8-7483-a47b-626ba509ed37 + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools + - content: + - text: 'The user wants me to make two web_search calls in parallel in this + single turn. The first call should batch "potato nutrition facts" and + "tomato nutrition facts" together using the `queries` parameter. The second + call should batch "cucumber nutrition facts" and "carrot nutrition facts" + together using the `queries` parameter. Both calls should be made in the + same function_calls block to execute in parallel. + + ' + type: reasoning_text + id: rs_8694582545bb175b + summary: [] + type: reasoning + - action: + queries: + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + sources: + - title: Potato Nutrition Facts | Nutrients, Calories, Benefits of a Potato + url: https://potatogoodness.com/nutrition/ + - title: Potato + url: https://www.fda.gov/media/76882/download + - title: FACTS | Modern Solutions for K-12 Schools and More + url: https://factsmgt.com/ + - title: 'Potatoes 101: Nutrition Facts, Health Benefits, and Types' + url: https://www.healthline.com/nutrition/foods/potatoes + - title: 'FACTS SIS: Accounts' + url: https://accounts.renweb.com/Account/Login + - title: Nutrition - Potatoes International + url: https://usapotatoes.com/potato-nutrition/ + - title: Food Search | USDA FoodData Central + url: https://fdc.nal.usda.gov/food-search?query=potato + - title: Potato Nutrition - Cooking with Potatoes - Alsum + url: https://alsum.com/products/potato-nutrition-facts/ + - title: Idaho® Potato Nutrition Facts | Idaho Potato Commission + url: https://idahopotato.com/nutrition + - title: The EDFacts Initiative | U.S. Department of Education + url: https://www.ed.gov/data/edfacts-initiative + - title: 'Tomatoes: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/tomatoes + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellhealth.com/tomato-nutrition-12012681 + - title: Nutrition Facts for Tomatoes + url: https://tools.myfooddata.com/nutrition-facts/170457/wt6 + - title: Tomato Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/tomatoes-nutrition-facts-calories-and-health-benefits-4119981 + - title: Tomatoes - SNAP-Ed - USDA + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/tomatoes + - title: Tomatoes Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/tomatoes-2226 + - title: 'Tomatoes 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/tomatoes + - title: 'Tomatoes, raw: calories and nutrition facts' + url: https://www.nutritionvalue.org/Tomatoes%2C_raw_74101000_nutritional_value.html + - title: Top 10 Tomato Nutrition facts and Health benefits + url: https://www.nutrition-and-you.com/tomato.html + - title: 'Tomatoes: A Nutritional Powerhouse and Culinary Favorite - UF/IFAS + ...' + url: https://blogs.ifas.ufl.edu/brevardco/2025/04/07/tomatoes-a-nutritional-powerhouse-and-culinary-favorite/ + type: search + id: ws_90d3592d82410588 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + sources: + - title: Cucumber Nutrition Facts and Benefits - Franklin County Center + ... + url: https://franklin.ces.ncsu.edu/news/cucumber-nutrition-facts-and-benefits/ + - title: Health Benefits of Cucumber + url: https://www.webmd.com/food-recipes/cucumber-health-benefits + - title: Cucumbers | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/cucumbers + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11206-3 + - title: Nutrition Facts for Cucumber + url: https://tools.myfooddata.com/nutrition-facts/168409/wt9 + - title: 'Cucumber: Calories, Nutrition and Health Benefits | NutriScan + App' + url: https://nutriscan.app/calories-nutrition/sliced-cucumber + - title: ¼ Cucumber Of Cucumber Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/cucumber-1971?a=0.25%3A2 + - title: Cucumber, peeled, raw, 1 medium | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/cucumber-peeled-raw-1-medium + - title: Are Cucumbers Good for You? + url: https://health.clevelandclinic.org/benefits-of-cucumbers + - title: 'Cucumber: nutritional values, nutrients and calories | FreshMAG + ...' + url: https://www.liebherr.com/en-gb/fridges-freezers/freshmag/food-a-z/cucumber-8279000 + - title: 'Carrots 101: Nutrition Facts and Health Benefits' + url: https://www.healthline.com/nutrition/foods/carrots + - title: Nutrition Facts - URMC.Rochester.edu + url: https://www.urmc.rochester.edu/encyclopedia/content?contenttypeid=76&contentid=11124-3 + - title: Carrots | SNAP-Ed + url: https://snaped.fna.usda.gov/resources/nutrition-education-materials/seasonal-produce-guide/carrots + - title: Nutrition Facts for Raw Carrots + url: https://tools.myfooddata.com/nutrition-facts/170393/wt4 + - title: 'Carrots: Health Benefits, Nutrition Facts, and Risks' + url: https://www.webmd.com/food-recipes/benefits-carrots + - title: Carrot Nutrition Facts 0g Fat 25 calories 0.5g Protein 1.5g Fiber + ... + url: https://cdn.agclassroom.org/media/uploads/LP875/Carrot_nutrition_facts.pdf + - title: Carrots - Nutritional information | Wilcox + url: https://www.wilcoxgoodness.co.nz/helpful-info/nutritional-information/carrots + - title: Carrots, raw, 1 large (7-1/4" to 8-1/2" long) | University Hospitals + url: https://www.uhhospitals.org/health-information/health-and-wellness-library/article/nutritionfacts-v1/carrots-raw-1-large-7-14-to-8-12-long + - title: Baby Carrots Nutrition Facts - Eat This Much + url: https://www.eatthismuch.com/calories/baby-carrots-2569 + - title: Baby Carrots Nutrition Facts and Health Benefits + url: https://www.verywellfit.com/baby-carrot-nutrition-facts-and-health-benefits-5071650 + type: search + id: ws_8ee6b8b91369059f + status: completed + type: web_search_call + - content: + - text: 'Both searches returned results. Let me summarize each of the four + queries in one sentence: + + + 1. **Potato nutrition facts**: A medium potato (148g) has 110 calories, + 0g fat, 26g carbs, 2g fiber, and is a good source of vitamin C (30% DV), + potassium (15% DV), and vitamin B6 (10% DV). + + + 2. **Tomato nutrition facts**: A small tomato (91g) provides about 16-22 + calories and is an excellent source of vitamin C, fiber, and vitamin K, + plus the cancer-fighting antioxidant lycopene. + + + 3. **Cucumber nutrition facts**: Cucumbers are about 95-96% water with + only 15-16 calories per 100g, and provide vitamin K (20% DV), potassium, + and antioxidants like beta-carotene. + + + 4. **Carrot nutrition facts**: One medium carrot (61g) contains 25 calories, + 6g carbs, 1.5g fiber, 0g fat, and is especially rich in vitamin A (beta-carotene), + vitamin K, and potassium. + + ' + type: reasoning_text + id: rs_bb8c0e8e4b68beed + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each of the four searches: + + + 1. **Potato:** A medium potato (148g) provides 110 calories, 26g of carbohydrates, + 2g of fiber, and is a good source of vitamin C (30% DV), potassium (15% + DV), and vitamin B6 (10% DV), while being naturally fat- and cholesterol-free. + + + 2. **Tomato:** A small raw tomato (91g) delivers about 16–22 calories + and is an excellent source of vitamin C, dietary fiber, and vitamin K, + and is the primary dietary source of the antioxidant lycopene, which is + linked to reduced risks of heart disease and certain cancers. + + + 3. **Cucumber:** With only about 15–16 calories per 100g and roughly 95–96% + water, cucumbers provide vitamin K (~20% DV), potassium, and antioxidants + like beta-carotene, making them a hydrating, low-calorie addition to a + healthy diet. + + + 4. **Carrot:** One medium raw carrot (61g) contains 25 calories, 6g of + carbohydrates, 1.5g of fiber, and 0g of fat, and is especially rich in + beta-carotene (vitamin A), vitamin K, and potassium, with diets high in + carotenoids potentially protective against several types of cancer.' + type: output_text + id: msg_862b73745b32c760 + role: assistant + status: completed + type: message + - content: + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' + type: input_text + id: msg_01a16868-f8e7-7121-a3e5-71db08a39cb0 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call `mcp__gitmcp_tiktoken__search_tiktoken_documentation` + twice in parallel - once with query "encoding" and once with query "tokenizer". + I''ll issue both calls in the same function_calls block. + + ' + type: reasoning_text + id: rs_8a8a17763b04a832 + summary: [] + type: reasoning + - approval_request_id: null + arguments: '{"query": "encoding"}' + error: null + id: mcp_a4bef669c02bfcf5 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query": "tokenizer"}' + error: null + id: mcp_8db02699fcd1210b + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - text: 'Both searches returned the same fallback README content since no + specific documentation was indexed for either query. Let me summarize + each in one sentence. + + ' + type: reasoning_text + id: rs_9dca2a42406fd775 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here are one-sentence summaries for each query: + + + 1. **"encoding" search:** No specific documentation was indexed for "encoding," + so the tool returned a fallback of tiktoken''s README, which explains + that tiktoken is a fast BPE tokeniser for OpenAI''s models, that encodings + can be retrieved via `tiktoken.get_encoding("o200k_base")` or `tiktoken.encoding_for_model("gpt-4o")`, + and that custom encodings can be extended by either directly constructing + `tiktoken.Encoding` objects or registering them through the `tiktoken_ext` + plugin mechanism. + + + 2. **"tokenizer" search:** Similarly, no specific documentation was indexed + for "tokenizer," and the same fallback README was returned, noting that + tiktoken is 3–6× faster than comparable open-source tokenisers (benchmarked + against `GPT2TokenizerFast` from HuggingFace `tokenizers`), that the core + tokenizer API is documented in `tiktoken/core.py`, and that BPE works + by converting text into reversible, lossless token sequences where each + token corresponds to roughly 4 bytes on average.' + type: output_text + id: msg_9625fc5a78cfcb95 + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one to finish before starting the other: (1) search the web + for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' + type: input_text + id: msg_01a16869-4085-7ea3-b457-aaed0b24407a + role: user + status: completed + type: message + - content: + - text: 'The user wants me to make two tool calls in parallel: + + 1. A web_search call with the query "latest vLLM release notes" + + 2. A gitmcp_tiktoken__search_tiktoken_documentation call with the query + "encoding" + + + These are independent, so I''ll issue both in the same block. + + ' + type: reasoning_text + id: rs_b118aa524415e1c8 + summary: [] + type: reasoning + - action: + queries: + - latest vLLM release notes + query: latest vLLM release notes + sources: + - title: Releases · vllm-project/vllm + url: https://github.com/vllm-project/vllm/releases + - title: vLLM Release Notes - NVIDIA Docs + url: https://docs.nvidia.com/deeplearning/frameworks/vllm-release-notes/index.html + - title: Release Notes - vLLM Ascend + url: https://docs.vllm.ai/projects/ascend/en/latest/user_guide/release_notes.html + - title: Previous vLLM Releases | vLLM + url: https://vllm.ai/releases + - title: Release Notes — vllm-ascend + url: https://docs.vllm.ai/projects/ascend/en/main/user_guide/release_notes.html + - title: Releases · vllm-project/vllm-ascend + url: https://github.com/vllm-project/vllm-ascend/releases + - title: Blog | vLLM + url: https://vllm.ai/blog + - title: vLLM release history | Freedom.Tech + url: https://freedom.tech/project/vllm/ + - title: Release Notes - vLLM Hardware Plugin for Intel® Gaudi® + url: https://docs.vllm.ai/projects/gaudi/en/latest/release_notes.html + - title: Release note — vllm-ascend + url: https://docs.vllm.ai/projects/ascend/en/v0.10.0rc1/user_guide/release_notes.html + type: search + id: ws_b02f4144d93a3ba4 + status: completed + type: web_search_call + - approval_request_id: null + arguments: '{"query": "encoding"}' + error: null + id: mcp_8ccd9b6e8f62355f + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - text: 'Both calls were made in parallel as requested. Let me summarize the + results. + + + 1. Web search for "latest vLLM release notes" - found the GitHub releases + page, which shows the latest release (v0.26.0) with 411 commits from 212 + contributors, featuring improvements like faster startup, bi-directional + KV transfer, and various performance optimizations. + + + 2. tiktoken documentation search for "encoding" - returned the README/fallback + docs explaining that tiktoken is a fast BPE tokeniser, how to use encodings, + and how to extend with custom encodings. + + + The user just asked me to do both in parallel and didn''t ask for a summary + this time - they said "Do not call any other tool." So I should just confirm + both were done and briefly note the results. + + ' + type: reasoning_text + id: rs_be9503832d80f838 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Both calls were issued in parallel in this single turn. Here''s a brief + recap of each: + + + 1. **Web search ("latest vLLM release notes"):** The top result is the + vLLM GitHub releases page, showing the latest release **v0.26.0** (411 + commits from 212 contributors), which adds features like faster startup + via precompiled FlashInfer kernels, bi-directional KV transfer for reduced + TTFT (up to 3.3× on long multi-turn prompts), a shared-memory tensor arena + for multimodal tensors, and least-loaded API worker routing for 43–85% + more req/s on short requests. + + + 2. **tiktoken doc search ("encoding"):** No specific documentation was + indexed, so the tool returned the repo README as a fallback, which explains + that tiktoken is a fast BPE tokeniser for OpenAI models, that you can + retrieve encodings via `tiktoken.get_encoding("o200k_base")` or `tiktoken.encoding_for_model("gpt-4o")`, + and that custom encodings can be created by directly constructing `tiktoken.Encoding` + objects or registering them through the `tiktoken_ext` plugin mechanism.' + type: output_text + id: msg_86c8e81126d72e24 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a16869-5631-74d0-9543-76b9c7d2b740 + role: user + status: completed + type: message + first_id: msg_01a16868-b5eb-7231-8c69-837e933982da + has_more: false + last_id: msg_01a16869-5631-74d0-9543-76b9c7d2b740 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t9 + request: + body: + input: 'Do all three of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit to + set my preferred unit to "fahrenheit" for the rest of this conversation.' + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + store: true + stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753101 + created_at: 1792753101 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-5882-76a1-a153-61199d2d278c + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to make three independent function calls in parallel: + + 1. get_weather for "Tokyo" + + 2. get_stock_price for "AAPL" + + 3. set_temperature_unit to "fahrenheit" + + + These are all independent of each other, so I can call them all in the + same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the `input` field + should be just the string "fahrenheit". + + ' + type: reasoning_text + id: rs_921e660698a6a519 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_8d0577669244161c + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker": "AAPL"}' + call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_a30f8440cf89804e + name: get_stock_price + status: completed + type: function_call + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctc_ad92e0c07fb5a297 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 612 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 194 + output_tokens_details: + reasoning_tokens: 110 + total_tokens: 806 + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-5882-76a1-a153-61199d2d278c/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' + type: input_text + id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + role: user + status: completed + type: message + first_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + has_more: false + last_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t11 + request: + body: + input: + - call_id: chatcmpl-tool-a0fbc5371a13c949 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - call_id: chatcmpl-tool-b6d0f4de6bac48e1 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + type: function_call_output + - call_id: chatcmpl-tool-9a7bee702487b0bd + output: '{"status": "ok", "unit": "fahrenheit"}' + type: custom_tool_call_output + - content: What did you find for the weather and the stock price, and what temperature + unit did you just set? + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-5882-76a1-a153-61199d2d278c + store: true + stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753106 + created_at: 1792753106 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-6bd5-7ad1-af74-c0e3522cbe31 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user is asking me to summarize the results from the three function + calls. Let me recall: + + + 1. Weather for Tokyo: 22°C, Clear + + 2. Stock price for AAPL: $231.45 USD + + 3. Temperature unit set to: fahrenheit + + + Since the user''s preferred unit is now fahrenheit, I should convert 22°C + to Fahrenheit. 22 × 9/5 + 32 = 39.6 + 32 = 71.6°F. + + + I''ll report the weather in Fahrenheit since that''s their preferred unit + now. + + ' + type: reasoning_text + id: rs_9f7159cd728397cc + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s a summary of all three results: + + + 1. **Weather in Tokyo:** Clear skies, 72°F (22°C). + + 2. **AAPL Stock Price:** $231.45 USD. + + 3. **Temperature Unit:** Set to **Fahrenheit** — I''ll use that for all + temperature readings going forward in this conversation.' + type: output_text + id: msg_a2f0ca45d3849d86 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-5882-76a1-a153-61199d2d278c + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 915 + input_tokens_details: + cache_write_tokens: 784 + cached_tokens: 0 + output_tokens: 206 + output_tokens_details: + reasoning_tokens: 128 + total_tokens: 1121 + headers: + content-type: application/json + status_code: 200 +- filename: t12 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-6bd5-7ad1-af74-c0e3522cbe31/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' + type: input_text + id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to make three independent function calls in parallel: + + 1. get_weather for "Tokyo" + + 2. get_stock_price for "AAPL" + + 3. set_temperature_unit to "fahrenheit" + + + These are all independent of each other, so I can call them all in the + same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the `input` field + should be just the string "fahrenheit". + + ' + type: reasoning_text + id: rs_921e660698a6a519 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_8d0577669244161c + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker": "AAPL"}' + call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_a30f8440cf89804e + name: get_stock_price + status: completed + type: function_call + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctc_ad92e0c07fb5a297 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_01a16869-7dde-74a0-818e-5fb75604b5df + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_01a16869-7dde-74a0-818e-5fca9c798427 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctco_01a16869-7dde-74a0-818e-5fddcdc44b4b + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_01a16869-7dde-74a0-818e-5fedd0bae4f3 + role: user + status: completed + type: message + first_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + has_more: false + last_id: msg_01a16869-7dde-74a0-818e-5fedd0bae4f3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t13 + request: + body: + input: + - content: 'Do both of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-6bd5-7ad1-af74-c0e3522cbe31 + store: true + stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753109 + created_at: 1792753109 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-80ea-7dc1-9cf2-1c6bf6a8de3e + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants two independent weather calls made in parallel. Both + are independent, so I can call them in the same block. + + ' + type: reasoning_text + id: rs_939826702f1303fe + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-bae30d743412e2ce + id: fc_b8e5ffef362603c3 + name: get_weather + status: completed + type: function_call + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-935da776948aeef6 + id: fc_bd4b1764aef9e65a + name: get_weather + status: completed + type: function_call + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-6bd5-7ad1-af74-c0e3522cbe31 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 1178 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 81 + output_tokens_details: + reasoning_tokens: 26 + total_tokens: 1259 + headers: + content-type: application/json + status_code: 200 +- filename: t14 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-80ea-7dc1-9cf2-1c6bf6a8de3e/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' + type: input_text + id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to make three independent function calls in parallel: + + 1. get_weather for "Tokyo" + + 2. get_stock_price for "AAPL" + + 3. set_temperature_unit to "fahrenheit" + + + These are all independent of each other, so I can call them all in the + same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the `input` field + should be just the string "fahrenheit". + + ' + type: reasoning_text + id: rs_921e660698a6a519 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_8d0577669244161c + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker": "AAPL"}' + call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_a30f8440cf89804e + name: get_stock_price + status: completed + type: function_call + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctc_ad92e0c07fb5a297 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_01a16869-7dde-74a0-818e-5fb75604b5df + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_01a16869-7dde-74a0-818e-5fca9c798427 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctco_01a16869-7dde-74a0-818e-5fddcdc44b4b + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_01a16869-7dde-74a0-818e-5fedd0bae4f3 + role: user + status: completed + type: message + - content: + - text: 'The user is asking me to summarize the results from the three function + calls. Let me recall: + + + 1. Weather for Tokyo: 22°C, Clear + + 2. Stock price for AAPL: $231.45 USD + + 3. Temperature unit set to: fahrenheit + + + Since the user''s preferred unit is now fahrenheit, I should convert 22°C + to Fahrenheit. 22 × 9/5 + 32 = 39.6 + 32 = 71.6°F. + + + I''ll report the weather in Fahrenheit since that''s their preferred unit + now. + + ' + type: reasoning_text + id: rs_9f7159cd728397cc + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s a summary of all three results: + + + 1. **Weather in Tokyo:** Clear skies, 72°F (22°C). + + 2. **AAPL Stock Price:** $231.45 USD. + + 3. **Temperature Unit:** Set to **Fahrenheit** — I''ll use that for all + temperature readings going forward in this conversation.' + type: output_text + id: msg_a2f0ca45d3849d86 + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' + type: input_text + id: msg_01a16869-8825-7042-92bc-07043634ac23 + role: user + status: completed + type: message + first_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + has_more: false + last_id: msg_01a16869-8825-7042-92bc-07043634ac23 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t15 + request: + body: + input: + - call_id: chatcmpl-tool-bae30d743412e2ce + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - call_id: chatcmpl-tool-935da776948aeef6 + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - content: What's the weather in each of those two cities? + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-80ea-7dc1-9cf2-1c6bf6a8de3e + store: true + stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753112 + created_at: 1792753112 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-8b97-7523-8bec-470ad071c33d + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants the weather results. Their preferred unit is Fahrenheit + (set earlier in the conversation), so I should convert 22°C to °F: 22 + × 9/5 + 32 = 71.6°F ≈ 72°F. + + ' + type: reasoning_text + id: rs_9fd78d65dbbacfda + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s the weather for both cities: + + + - **Tokyo:** Clear, 72°F + + - **Paris:** Clear, 72°F + + + Both cities are sitting at the same temperature with clear skies!' + type: output_text + id: msg_b78f1a007e37f170 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-80ea-7dc1-9cf2-1c6bf6a8de3e + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 1336 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 104 + output_tokens_details: + reasoning_tokens: 57 + total_tokens: 1440 + headers: + content-type: application/json + status_code: 200 +- filename: t16 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-8b97-7523-8bec-470ad071c33d/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' + type: input_text + id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to make three independent function calls in parallel: + + 1. get_weather for "Tokyo" + + 2. get_stock_price for "AAPL" + + 3. set_temperature_unit to "fahrenheit" + + + These are all independent of each other, so I can call them all in the + same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the `input` field + should be just the string "fahrenheit". + + ' + type: reasoning_text + id: rs_921e660698a6a519 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_8d0577669244161c + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker": "AAPL"}' + call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_a30f8440cf89804e + name: get_stock_price + status: completed + type: function_call + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctc_ad92e0c07fb5a297 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_01a16869-7dde-74a0-818e-5fb75604b5df + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_01a16869-7dde-74a0-818e-5fca9c798427 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctco_01a16869-7dde-74a0-818e-5fddcdc44b4b + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_01a16869-7dde-74a0-818e-5fedd0bae4f3 + role: user + status: completed + type: message + - content: + - text: 'The user is asking me to summarize the results from the three function + calls. Let me recall: + + + 1. Weather for Tokyo: 22°C, Clear + + 2. Stock price for AAPL: $231.45 USD + + 3. Temperature unit set to: fahrenheit + + + Since the user''s preferred unit is now fahrenheit, I should convert 22°C + to Fahrenheit. 22 × 9/5 + 32 = 39.6 + 32 = 71.6°F. + + + I''ll report the weather in Fahrenheit since that''s their preferred unit + now. + + ' + type: reasoning_text + id: rs_9f7159cd728397cc + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s a summary of all three results: + + + 1. **Weather in Tokyo:** Clear skies, 72°F (22°C). + + 2. **AAPL Stock Price:** $231.45 USD. + + 3. **Temperature Unit:** Set to **Fahrenheit** — I''ll use that for all + temperature readings going forward in this conversation.' + type: output_text + id: msg_a2f0ca45d3849d86 + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' + type: input_text + id: msg_01a16869-8825-7042-92bc-07043634ac23 + role: user + status: completed + type: message + - content: + - text: 'The user wants two independent weather calls made in parallel. Both + are independent, so I can call them in the same block. + + ' + type: reasoning_text + id: rs_939826702f1303fe + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-bae30d743412e2ce + id: fc_b8e5ffef362603c3 + name: get_weather + status: completed + type: function_call + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-935da776948aeef6 + id: fc_bd4b1764aef9e65a + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-bae30d743412e2ce + id: fc_01a16869-94ce-72b2-b037-abc4d326a524 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-935da776948aeef6 + id: fc_01a16869-94ce-72b2-b037-abd079d15512 + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - content: + - text: What's the weather in each of those two cities? + type: input_text + id: msg_01a16869-94ce-72b2-b037-abe7953f3f19 + role: user + status: completed + type: message + first_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + has_more: false + last_id: msg_01a16869-94ce-72b2-b037-abe7953f3f19 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t17 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-8b97-7523-8bec-470ad071c33d + store: true + stream: false + tool_choice: none + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753113 + created_at: 1792753113 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-986d-78b1-bcdd-69646184c2c9 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to simply reply "OK" without calling any tools. + + ' + type: reasoning_text + id: rs_a4a679e8d5b878c8 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_8f34b38a5c7b17d2 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-8b97-7523-8bec-470ad071c33d + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 1461 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 20 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 1481 + headers: + content-type: application/json + status_code: 200 +- filename: t18 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-986d-78b1-bcdd-69646184c2c9/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' + type: input_text + id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to make three independent function calls in parallel: + + 1. get_weather for "Tokyo" + + 2. get_stock_price for "AAPL" + + 3. set_temperature_unit to "fahrenheit" + + + These are all independent of each other, so I can call them all in the + same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the `input` field + should be just the string "fahrenheit". + + ' + type: reasoning_text + id: rs_921e660698a6a519 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_8d0577669244161c + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker": "AAPL"}' + call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_a30f8440cf89804e + name: get_stock_price + status: completed + type: function_call + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctc_ad92e0c07fb5a297 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - call_id: chatcmpl-tool-a0fbc5371a13c949 + id: fc_01a16869-7dde-74a0-818e-5fb75604b5df + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b6d0f4de6bac48e1 + id: fc_01a16869-7dde-74a0-818e-5fca9c798427 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-9a7bee702487b0bd + id: ctco_01a16869-7dde-74a0-818e-5fddcdc44b4b + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_01a16869-7dde-74a0-818e-5fedd0bae4f3 + role: user + status: completed + type: message + - content: + - text: 'The user is asking me to summarize the results from the three function + calls. Let me recall: + + + 1. Weather for Tokyo: 22°C, Clear + + 2. Stock price for AAPL: $231.45 USD + + 3. Temperature unit set to: fahrenheit + + + Since the user''s preferred unit is now fahrenheit, I should convert 22°C + to Fahrenheit. 22 × 9/5 + 32 = 39.6 + 32 = 71.6°F. + + + I''ll report the weather in Fahrenheit since that''s their preferred unit + now. + + ' + type: reasoning_text + id: rs_9f7159cd728397cc + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s a summary of all three results: + + + 1. **Weather in Tokyo:** Clear skies, 72°F (22°C). + + 2. **AAPL Stock Price:** $231.45 USD. + + 3. **Temperature Unit:** Set to **Fahrenheit** — I''ll use that for all + temperature readings going forward in this conversation.' + type: output_text + id: msg_a2f0ca45d3849d86 + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' + type: input_text + id: msg_01a16869-8825-7042-92bc-07043634ac23 + role: user + status: completed + type: message + - content: + - text: 'The user wants two independent weather calls made in parallel. Both + are independent, so I can call them in the same block. + + ' + type: reasoning_text + id: rs_939826702f1303fe + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-bae30d743412e2ce + id: fc_b8e5ffef362603c3 + name: get_weather + status: completed + type: function_call + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-935da776948aeef6 + id: fc_bd4b1764aef9e65a + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-bae30d743412e2ce + id: fc_01a16869-94ce-72b2-b037-abc4d326a524 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-935da776948aeef6 + id: fc_01a16869-94ce-72b2-b037-abd079d15512 + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - content: + - text: What's the weather in each of those two cities? + type: input_text + id: msg_01a16869-94ce-72b2-b037-abe7953f3f19 + role: user + status: completed + type: message + - content: + - text: 'The user wants the weather results. Their preferred unit is Fahrenheit + (set earlier in the conversation), so I should convert 22°C to °F: 22 + × 9/5 + 32 = 71.6°F ≈ 72°F. + + ' + type: reasoning_text + id: rs_9fd78d65dbbacfda + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Here''s the weather for both cities: + + + - **Tokyo:** Clear, 72°F + + - **Paris:** Clear, 72°F + + + Both cities are sitting at the same temperature with clear skies!' + type: output_text + id: msg_b78f1a007e37f170 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a16869-9b18-7052-89f2-d9a50b80da5c + role: user + status: completed + type: message + first_id: msg_01a16869-6902-7422-bf79-d8d2089fe1d4 + has_more: false + last_id: msg_01a16869-9b18-7052-89f2-d9a50b80da5c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t19 + request: + body: + input: 'Do all three of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + (2) call set_temperature_unit to set my preferred unit to "fahrenheit" for + the rest of this conversation, and (3) search the web for the exact query + "Tokyo weather today".' + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753119 + created_at: 1792753118 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-9f3c-7120-8d07-b70fa46609ac + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to do three things in parallel in a single turn: + + 1. get_weather for "Tokyo" + + 2. set_temperature_unit to "fahrenheit" + + 3. web_search for "Tokyo weather today" + + + All three are independent, so I can call them all in the same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the input field + should be just "fahrenheit". + + ' + type: reasoning_text + id: rs_84d5e247696aa267 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-8a9b8fc5981ed96d + id: fc_bc130e3fbc03517a + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + id: ctc_9931a1dea54b7e74 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - action: + queries: + - Tokyo weather today + query: Tokyo weather today + sources: + - title: Tokyo, Tokyo, Japan Weather Forecast | AccuWeather + url: https://www.accuweather.com/en/jp/tokyo/226396/weather-forecast/226396 + - title: 10-Day Weather Forecast for Tokyo, Tokyo Prefecture, Japan - The + ... + url: https://weather.com/jp/tokyo-prefecture/city/tokyo/tenday + - title: Tokyo, Japan Hourly Weather Forecast | Weather Underground + url: https://www.wunderground.com/hourly/jp/tokyo + - title: Weather & Geography | Japan's Weather | Travel Japan | JNTO + url: https://www.japan.travel/en/weather/ + - title: Japan Meteorological Agency | Weather forecast + url: https://www.data.jma.go.jp/multi/yoho/yoho_detail.html?code=130010&lang=en + - title: Tokyo - BBC Weather + url: https://www.bbc.com/weather/1850147 + - title: Tokyo, Japan Weather Conditions | Weather Underground + url: https://www.wunderground.com/weather/jp/tokyo + - title: Tokyo, JP Weather Forecast, Conditions, and Maps – Yahoo Weather + url: https://weather.yahoo.com/jp/tokyo-prefecture/tokyo/ + - title: Tokyo, Japan 14 day weather forecast + url: https://www.timeanddate.com/weather/japan/tokyo/ext + - title: Tokyo (Japan) weather - Met Office + url: https://weather.metoffice.gov.uk/forecast/xn76urzn2 + type: search + id: ws_ac201549a7531927 + status: completed + type: web_search_call + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 852 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 191 + output_tokens_details: + reasoning_tokens: 106 + total_tokens: 1043 + headers: + content-type: application/json + status_code: 200 +- filename: t20 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-9f3c-7120-8d07-b70fa46609ac/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' + type: input_text + id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + role: user + status: completed + type: message + first_id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + has_more: false + last_id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t21 + request: + body: + input: + - call_id: chatcmpl-tool-8a9b8fc5981ed96d + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + output: '{"status": "ok", "unit": "fahrenheit"}' + type: custom_tool_call_output + - content: Now report Tokyo's current temperature using the unit I just told + you to prefer. + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-9f3c-7120-8d07-b70fa46609ac + store: true + stream: false + tool_choice: auto + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753122 + created_at: 1792753122 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-b46d-7d93-8dc3-b1c275be5bf3 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to report Tokyo''s current temperature using their + preferred unit, which they set to fahrenheit. + + + From get_weather, I got: 22°C, Clear. + + + Converting 22°C to Fahrenheit: (22 × 9/5) + 32 = 39.6 + 32 = 71.6°F ≈ + 72°F. + + + I''ll report it in Fahrenheit as the user prefers. + + ' + type: reasoning_text + id: rs_b9c19400f4e77621 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Tokyo''s current temperature is **72°F** (Clear).' + type: output_text + id: msg_950ce41c85bf9c84 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-9f3c-7120-8d07-b70fa46609ac + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 2831 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 2352 + output_tokens: 113 + output_tokens_details: + reasoning_tokens: 96 + total_tokens: 2944 + headers: + content-type: application/json + status_code: 200 +- filename: t22 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-b46d-7d93-8dc3-b1c275be5bf3/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' + type: input_text + id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to do three things in parallel in a single turn: + + 1. get_weather for "Tokyo" + + 2. set_temperature_unit to "fahrenheit" + + 3. web_search for "Tokyo weather today" + + + All three are independent, so I can call them all in the same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the input field + should be just "fahrenheit". + + ' + type: reasoning_text + id: rs_84d5e247696aa267 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-8a9b8fc5981ed96d + id: fc_bc130e3fbc03517a + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + id: ctc_9931a1dea54b7e74 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - action: + queries: + - Tokyo weather today + query: Tokyo weather today + sources: + - title: Tokyo, Tokyo, Japan Weather Forecast | AccuWeather + url: https://www.accuweather.com/en/jp/tokyo/226396/weather-forecast/226396 + - title: 10-Day Weather Forecast for Tokyo, Tokyo Prefecture, Japan - The + ... + url: https://weather.com/jp/tokyo-prefecture/city/tokyo/tenday + - title: Tokyo, Japan Hourly Weather Forecast | Weather Underground + url: https://www.wunderground.com/hourly/jp/tokyo + - title: Weather & Geography | Japan's Weather | Travel Japan | JNTO + url: https://www.japan.travel/en/weather/ + - title: Japan Meteorological Agency | Weather forecast + url: https://www.data.jma.go.jp/multi/yoho/yoho_detail.html?code=130010&lang=en + - title: Tokyo - BBC Weather + url: https://www.bbc.com/weather/1850147 + - title: Tokyo, Japan Weather Conditions | Weather Underground + url: https://www.wunderground.com/weather/jp/tokyo + - title: Tokyo, JP Weather Forecast, Conditions, and Maps – Yahoo Weather + url: https://weather.yahoo.com/jp/tokyo-prefecture/tokyo/ + - title: Tokyo, Japan 14 day weather forecast + url: https://www.timeanddate.com/weather/japan/tokyo/ext + - title: Tokyo (Japan) weather - Met Office + url: https://weather.metoffice.gov.uk/forecast/xn76urzn2 + type: search + id: ws_ac201549a7531927 + status: completed + type: web_search_call + - call_id: chatcmpl-tool-8a9b8fc5981ed96d + id: fc_01a16869-be65-7980-ba27-7a6e783763c2 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + id: ctco_01a16869-be65-7980-ba27-7a79ed8c35a5 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: Now report Tokyo's current temperature using the unit I just told + you to prefer. + type: input_text + id: msg_01a16869-be65-7980-ba27-7a81d3316261 + role: user + status: completed + type: message + first_id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + has_more: false + last_id: msg_01a16869-be65-7980-ba27-7a81d3316261 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t23 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: true + previous_response_id: resp_01a16869-b46d-7d93-8dc3-b1c275be5bf3 + store: true + stream: false + tool_choice: none + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753124 + created_at: 1792753124 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-c2f5-7f71-84f9-9542ce5ef688 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply only "OK" without calling any tools. + + ' + type: reasoning_text + id: rs_998d81c904825b35 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_b8048966a882a4b5 + role: assistant + status: completed + type: message + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_01a16869-b46d-7d93-8dc3-b1c275be5bf3 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - filters: null + search_context_size: null + type: web_search_preview + user_location: null + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 2965 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 2352 + output_tokens: 20 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 2985 + headers: + content-type: application/json + status_code: 200 +- filename: t24 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-c2f5-7f71-84f9-9542ce5ef688/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' + type: input_text + id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to do three things in parallel in a single turn: + + 1. get_weather for "Tokyo" + + 2. set_temperature_unit to "fahrenheit" + + 3. web_search for "Tokyo weather today" + + + All three are independent, so I can call them all in the same block. + + + For set_temperature_unit, the description says "Call with the raw unit + as plain text input, e.g. fahrenheit -- not JSON." So the input field + should be just "fahrenheit". + + ' + type: reasoning_text + id: rs_84d5e247696aa267 + summary: [] + type: reasoning + - arguments: '{"city": "Tokyo"}' + call_id: chatcmpl-tool-8a9b8fc5981ed96d + id: fc_bc130e3fbc03517a + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + id: ctc_9931a1dea54b7e74 + input: fahrenheit + name: set_temperature_unit + status: completed + type: custom_tool_call + - action: + queries: + - Tokyo weather today + query: Tokyo weather today + sources: + - title: Tokyo, Tokyo, Japan Weather Forecast | AccuWeather + url: https://www.accuweather.com/en/jp/tokyo/226396/weather-forecast/226396 + - title: 10-Day Weather Forecast for Tokyo, Tokyo Prefecture, Japan - The + ... + url: https://weather.com/jp/tokyo-prefecture/city/tokyo/tenday + - title: Tokyo, Japan Hourly Weather Forecast | Weather Underground + url: https://www.wunderground.com/hourly/jp/tokyo + - title: Weather & Geography | Japan's Weather | Travel Japan | JNTO + url: https://www.japan.travel/en/weather/ + - title: Japan Meteorological Agency | Weather forecast + url: https://www.data.jma.go.jp/multi/yoho/yoho_detail.html?code=130010&lang=en + - title: Tokyo - BBC Weather + url: https://www.bbc.com/weather/1850147 + - title: Tokyo, Japan Weather Conditions | Weather Underground + url: https://www.wunderground.com/weather/jp/tokyo + - title: Tokyo, JP Weather Forecast, Conditions, and Maps – Yahoo Weather + url: https://weather.yahoo.com/jp/tokyo-prefecture/tokyo/ + - title: Tokyo, Japan 14 day weather forecast + url: https://www.timeanddate.com/weather/japan/tokyo/ext + - title: Tokyo (Japan) weather - Met Office + url: https://weather.metoffice.gov.uk/forecast/xn76urzn2 + type: search + id: ws_ac201549a7531927 + status: completed + type: web_search_call + - call_id: chatcmpl-tool-8a9b8fc5981ed96d + id: fc_01a16869-be65-7980-ba27-7a6e783763c2 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: chatcmpl-tool-b8e2d3e8496c45a7 + id: ctco_01a16869-be65-7980-ba27-7a79ed8c35a5 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output + - content: + - text: Now report Tokyo's current temperature using the unit I just told + you to prefer. + type: input_text + id: msg_01a16869-be65-7980-ba27-7a81d3316261 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to report Tokyo''s current temperature using their + preferred unit, which they set to fahrenheit. + + + From get_weather, I got: 22°C, Clear. + + + Converting 22°C to Fahrenheit: (22 × 9/5) + 32 = 39.6 + 32 = 71.6°F ≈ + 72°F. + + + I''ll report it in Fahrenheit as the user prefers. + + ' + type: reasoning_text + id: rs_b9c19400f4e77621 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + Tokyo''s current temperature is **72°F** (Clear).' + type: output_text + id: msg_950ce41c85bf9c84 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a16869-c591-7bc1-8338-5073f04822e9 + role: user + status: completed + type: message + first_id: msg_01a16869-afd4-7481-8c94-117fe35cb79c + has_more: false + last_id: msg_01a16869-c591-7bc1-8338-5073f04822e9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t25 + request: + body: + input: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + store: true + stream: false + tool_choice: auto + tools: + - environment: + type: local + type: shell + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753128 + created_at: 1792753128 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-ca67-73f1-849c-3a69af4f2bc9 + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to use the shell tool exactly once with the exact + action specified. Let me make that call. + + ' + type: reasoning_text + id: rs_9b4a99d65b51c7c3 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: chatcmpl-tool-9df3ae801df889cc + id: sh_a4cee5de9d328412 + status: completed + type: shell_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - environment: + type: local + type: shell + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 475 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 93 + output_tokens_details: + reasoning_tokens: 24 + total_tokens: 568 + headers: + content-type: application/json + status_code: 200 +- filename: t26 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-ca67-73f1-849c-3a69af4f2bc9/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + role: user + status: completed + type: message + first_id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + has_more: false + last_id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t27 + request: + body: + input: + - call_id: chatcmpl-tool-9df3ae801df889cc + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + type: shell_call_output + - content: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a16869-ca67-73f1-849c-3a69af4f2bc9 + store: true + stream: false + tool_choice: auto + tools: + - environment: + type: local + type: shell + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753132 + created_at: 1792753132 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-d7cc-7512-9697-448ea0a8d17b + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to report the results from the shell tool call + I made earlier. Let me parse the output: + + + - Command: `printf ''SHELL_OK\\n''` + + - stdout: `SHELL_OK\n` + + - stderr: (empty) + + - outcome: exit with exit code 0 + + ' + type: reasoning_text + id: rs_ac87e0489a7e4328 + summary: [] + type: reasoning + - content: + - annotations: [] + text: "\n\nHere are the results for each command in order:\n\n1. **Command:**\ + \ `printf 'SHELL_OK\\\\n'`\n - **stdout:** `SHELL_OK\\n`\n - **stderr:**\ + \ *(empty)*\n - **Outcome:** exit with exit code **0**" + type: output_text + id: msg_854e47652cab83e5 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a16869-ca67-73f1-849c-3a69af4f2bc9 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - environment: + type: local + type: shell + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 641 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 128 + output_tokens_details: + reasoning_tokens: 65 + total_tokens: 769 + headers: + content-type: application/json + status_code: 200 +- filename: t28 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-d7cc-7512-9697-448ea0a8d17b/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to use the shell tool exactly once with the exact + action specified. Let me make that call. + + ' + type: reasoning_text + id: rs_9b4a99d65b51c7c3 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: chatcmpl-tool-9df3ae801df889cc + id: sh_a4cee5de9d328412 + status: completed + type: shell_call + - call_id: chatcmpl-tool-9df3ae801df889cc + id: sho_01a16869-e315-7f11-8d40-d07c53a987f2 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_01a16869-e315-7f11-8d40-d084d91c94dd + role: user + status: completed + type: message + first_id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + has_more: false + last_id: msg_01a16869-e315-7f11-8d40-d084d91c94dd + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t29 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + previous_response_id: resp_01a16869-d7cc-7512-9697-448ea0a8d17b + store: true + stream: false + tool_choice: none + tools: + - environment: + type: local + type: shell + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753134 + created_at: 1792753134 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-e836-7723-8114-49922d32651b + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply only "OK" without calling any tools. + + ' + type: reasoning_text + id: rs_938da32298040366 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_b9e98535c6705462 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a16869-d7cc-7512-9697-448ea0a8d17b + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - environment: + type: local + type: shell + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 790 + input_tokens_details: + cache_write_tokens: 784 + cached_tokens: 0 + output_tokens: 20 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 810 + headers: + content-type: application/json + status_code: 200 +- filename: t30 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-e836-7723-8114-49922d32651b/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + type: input_text + id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + role: user + status: completed + type: message + - content: + - text: 'The user wants me to use the shell tool exactly once with the exact + action specified. Let me make that call. + + ' + type: reasoning_text + id: rs_9b4a99d65b51c7c3 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: chatcmpl-tool-9df3ae801df889cc + id: sh_a4cee5de9d328412 + status: completed + type: shell_call + - call_id: chatcmpl-tool-9df3ae801df889cc + id: sho_01a16869-e315-7f11-8d40-d07c53a987f2 + max_output_length: 4096 + output: + - outcome: + exit_code: 0 + type: exit + stderr: '' + stdout: 'SHELL_OK + + ' + status: completed + type: shell_call_output + - content: + - text: Use the shell output above without calling any more tools. For each + command, report its stdout, stderr, and exit code or timeout outcome in + order. + type: input_text + id: msg_01a16869-e315-7f11-8d40-d084d91c94dd + role: user + status: completed + type: message + - content: + - text: 'The user wants me to report the results from the shell tool call + I made earlier. Let me parse the output: + + + - Command: `printf ''SHELL_OK\\n''` + + - stdout: `SHELL_OK\n` + + - stderr: (empty) + + - outcome: exit with exit code 0 + + ' + type: reasoning_text + id: rs_ac87e0489a7e4328 + summary: [] + type: reasoning + - content: + - annotations: [] + text: "\n\nHere are the results for each command in order:\n\n1. **Command:**\ + \ `printf 'SHELL_OK\\\\n'`\n - **stdout:** `SHELL_OK\\n`\n - **stderr:**\ + \ *(empty)*\n - **Outcome:** exit with exit code **0**" + type: output_text + id: msg_854e47652cab83e5 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a16869-eb22-7c40-b803-6d71ae5df2d9 + role: user + status: completed + type: message + first_id: msg_01a16869-d2a2-7823-a02f-227257f40c2c + has_more: false + last_id: msg_01a16869-eb22-7c40-b803-6d71ae5df2d9 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t31 + request: + body: + input: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. After + it runs, answer exactly CODE_INTERPRETER_OK=385. + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + store: true + stream: false + tool_choice: required + tools: + - container: + type: auto + type: code_interpreter + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753143 + created_at: 1792753143 + error: null + frequency_penalty: 0.0 + id: resp_01a16869-f09f-7132-a3cf-29606be195f5 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to: + + 1. Use the Python tool to calculate the sum of squares of integers from + 1 through 10 + + 2. The code must print exactly "CODE_INTERPRETER_OK=385" + + 3. After it runs, answer exactly "CODE_INTERPRETER_OK=385" + + + Let me verify: 1² + 2² + 3² + 4² + 5² + 6² + 7² + 8² + 9² + 10² = 1 + + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385. Yes, that''s correct. + + + I need to write code that calculates this and prints "CODE_INTERPRETER_OK=385". + + ' + type: reasoning_text + id: rs_a05ae683ab165f1b + summary: [] + type: reasoning + - code: 'result = sum(i**2 for i in range(1, 11)) + + print(f"CODE_INTERPRETER_OK={result}")' + container_id: cntr_89e9d6945d831686 + id: ci_89e9d6945d831686 + outputs: + - logs: 'CODE_INTERPRETER_OK=385 + + ' + type: logs + status: completed + type: code_interpreter_call + - content: + - text: 'Perfect! The code executed successfully and printed exactly "CODE_INTERPRETER_OK=385". + Now I need to answer exactly "CODE_INTERPRETER_OK=385" as requested. + + ' + type: reasoning_text + id: rs_a15033a3b8ab3a55 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + CODE_INTERPRETER_OK=385' + type: output_text + id: msg_808b6e5437c72017 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: required + tools: + - container: + type: auto + type: code_interpreter + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 1051 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 295 + output_tokens_details: + reasoning_tokens: 226 + total_tokens: 1346 + headers: + content-type: application/json + status_code: 200 +- filename: t32 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a16869-f09f-7132-a3cf-29606be195f5/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + type: input_text + id: msg_01a1686a-0fd1-72c1-831e-64435a35d0e3 + role: user + status: completed + type: message + first_id: msg_01a1686a-0fd1-72c1-831e-64435a35d0e3 + has_more: false + last_id: msg_01a1686a-0fd1-72c1-831e-64435a35d0e3 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t33 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 2048 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + previous_response_id: resp_01a16869-f09f-7132-a3cf-29606be195f5 + store: true + stream: false + tool_choice: none + tools: + - container: + type: auto + type: code_interpreter + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753145 + created_at: 1792753145 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-155a-7113-ae2c-1ad8dc9e80c0 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply only "OK" without calling any tools. + + ' + type: reasoning_text + id: rs_be60bae0e7adbc4c + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_9df20571b8c82757 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a16869-f09f-7132-a3cf-29606be195f5 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - container: + type: auto + type: code_interpreter + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 748 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 20 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 768 + headers: + content-type: application/json + status_code: 200 +- filename: t34 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-155a-7113-ae2c-1ad8dc9e80c0/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. + type: input_text + id: msg_01a1686a-0fd1-72c1-831e-64435a35d0e3 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to: + + 1. Use the Python tool to calculate the sum of squares of integers from + 1 through 10 + + 2. The code must print exactly "CODE_INTERPRETER_OK=385" + + 3. After it runs, answer exactly "CODE_INTERPRETER_OK=385" + + + Let me verify: 1² + 2² + 3² + 4² + 5² + 6² + 7² + 8² + 9² + 10² = 1 + + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385. Yes, that''s correct. + + + I need to write code that calculates this and prints "CODE_INTERPRETER_OK=385". + + ' + type: reasoning_text + id: rs_a05ae683ab165f1b + summary: [] + type: reasoning + - code: 'result = sum(i**2 for i in range(1, 11)) + + print(f"CODE_INTERPRETER_OK={result}")' + container_id: cntr_89e9d6945d831686 + id: ci_89e9d6945d831686 + outputs: + - logs: 'CODE_INTERPRETER_OK=385 + + ' + type: logs + status: completed + type: code_interpreter_call + - content: + - text: 'Perfect! The code executed successfully and printed exactly "CODE_INTERPRETER_OK=385". + Now I need to answer exactly "CODE_INTERPRETER_OK=385" as requested. + + ' + type: reasoning_text + id: rs_a15033a3b8ab3a55 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + CODE_INTERPRETER_OK=385' + type: output_text + id: msg_808b6e5437c72017 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a1686a-1816-7bc2-aab5-57e072e46d19 + role: user + status: completed + type: message + first_id: msg_01a1686a-0fd1-72c1-831e-64435a35d0e3 + has_more: false + last_id: msg_01a1686a-1816-7bc2-aab5-57e072e46d19 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t35 + request: + body: + input: First call tool_search exactly once to find both a current-weather function + and a travel time-zone function. Do not call any other tool yet. + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + store: true + stream: false + tool_choice: required + tools: + - description: Search the client tool catalog for tools that can satisfy the + request. + execution: client + parameters: + additionalProperties: false + properties: + query: + description: A concise description of the needed capabilities. + type: string + required: + - query + type: object + type: tool_search + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get the exchange rate between two currencies + name: get_exchange_rate + parameters: + additionalProperties: false + properties: + base: + type: string + quote: + type: string + required: + - base + - quote + type: object + strict: true + type: function + - defer_loading: true + description: Search for hotels in a city + name: search_hotels + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get latitude and longitude for a city + name: get_coordinates + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Calculate the distance between two cities + name: calculate_distance + parameters: + additionalProperties: false + properties: + destination: + type: string + origin: + type: string + required: + - origin + - destination + type: object + strict: true + type: function + type: namespace + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753149 + created_at: 1792753149 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-1e09-7c71-ab58-bd9f8ee8290b + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to call tool_search exactly once, searching for + both a current-weather function and a travel time-zone function. Let me + craft a query that covers both needs. + + ' + type: reasoning_text + id: rs_882196426dacde62 + summary: [] + type: reasoning + - arguments: + query: current weather for a city and travel time-zone conversion + call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tsc_a8223c016a4424d3 + status: completed + type: tool_search_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: required + tools: + - description: Search the client tool catalog for tools that can satisfy the + request. + execution: client + parameters: + additionalProperties: false + properties: + query: + description: A concise description of the needed capabilities. + type: string + required: + - query + type: object + type: tool_search + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get the exchange rate between two currencies + name: get_exchange_rate + parameters: + additionalProperties: false + properties: + base: + type: string + quote: + type: string + required: + - base + - quote + type: object + strict: true + type: function + - defer_loading: true + description: Search for hotels in a city + name: search_hotels + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get latitude and longitude for a city + name: get_coordinates + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Calculate the distance between two cities + name: calculate_distance + parameters: + additionalProperties: false + properties: + destination: + type: string + origin: + type: string + required: + - origin + - destination + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 395 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 73 + output_tokens_details: + reasoning_tokens: 37 + total_tokens: 468 + headers: + content-type: application/json + status_code: 200 +- filename: t36 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-1e09-7c71-ab58-bd9f8ee8290b/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + role: user + status: completed + type: message + first_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + has_more: false + last_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t37 + request: + body: + input: + - call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + previous_response_id: resp_01a1686a-1e09-7c71-ab58-bd9f8ee8290b + store: true + stream: false + tool_choice: + name: get_weather + type: function + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753151 + created_at: 1792753151 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-2add-76c2-9ec0-3098b8f46a6d + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to call get_weather exactly once with the city + "Paris" and nothing else. + + ' + type: reasoning_text + id: rs_8e2d51599a92e78e + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_b5a2ae9c32e51507 + name: get_weather + status: completed + type: function_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1686a-1e09-7c71-ab58-bd9f8ee8290b + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: + name: get_weather + type: function + tools: + - description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 712 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 48 + output_tokens_details: + reasoning_tokens: 21 + total_tokens: 760 + headers: + content-type: application/json + status_code: 200 +- filename: t38 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-2add-76c2-9ec0-3098b8f46a6d/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call tool_search exactly once, searching for + both a current-weather function and a travel time-zone function. Let me + craft a query that covers both needs. + + ' + type: reasoning_text + id: rs_882196426dacde62 + summary: [] + type: reasoning + - arguments: + query: current weather for a city and travel time-zone conversion + call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tsc_a8223c016a4424d3 + status: completed + type: tool_search_call + - call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tso_01a1686a-2fdf-72a1-8bde-381cc55a4ed8 + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_01a1686a-2fdf-72a1-8bde-3820d2049b99 + role: user + status: completed + type: message + first_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + has_more: false + last_id: msg_01a1686a-2fdf-72a1-8bde-3820d2049b99 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t39 + request: + body: + input: + - call_id: chatcmpl-tool-9e4783529d80aacb + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + type: function_call_output + - content: Now call the loaded travel namespace member get_timezone exactly + once with {"city":"Paris"}. Do not call any other tool. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + previous_response_id: resp_01a1686a-2add-76c2-9ec0-3098b8f46a6d + store: true + stream: false + tool_choice: auto + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753155 + created_at: 1792753155 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-3637-7d51-8cde-b035bd3a4b15 + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to call the travel namespace member `get_timezone` + with `{"city":"Paris"}`. + + ' + type: reasoning_text + id: rs_845c8e9c9b4e459f + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-b64d6e7c111b8a9a + id: fc_8064e5123e854c49 + name: get_timezone + namespace: travel + status: completed + type: function_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1686a-2add-76c2-9ec0-3098b8f46a6d + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: + - description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 821 + input_tokens_details: + cache_write_tokens: 784 + cached_tokens: 0 + output_tokens: 59 + output_tokens_details: + reasoning_tokens: 24 + total_tokens: 880 + headers: + content-type: application/json + status_code: 200 +- filename: t40 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-3637-7d51-8cde-b035bd3a4b15/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call tool_search exactly once, searching for + both a current-weather function and a travel time-zone function. Let me + craft a query that covers both needs. + + ' + type: reasoning_text + id: rs_882196426dacde62 + summary: [] + type: reasoning + - arguments: + query: current weather for a city and travel time-zone conversion + call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tsc_a8223c016a4424d3 + status: completed + type: tool_search_call + - call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tso_01a1686a-2fdf-72a1-8bde-381cc55a4ed8 + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_01a1686a-2fdf-72a1-8bde-3820d2049b99 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call get_weather exactly once with the city + "Paris" and nothing else. + + ' + type: reasoning_text + id: rs_8e2d51599a92e78e + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_b5a2ae9c32e51507 + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_01a1686a-3ca5-74d2-942c-ac60295f134c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_01a1686a-3ca5-74d2-942c-ac713b58a696 + role: user + status: completed + type: message + first_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + has_more: false + last_id: msg_01a1686a-3ca5-74d2-942c-ac713b58a696 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t41 + request: + body: + input: + - call_id: chatcmpl-tool-b64d6e7c111b8a9a + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + type: function_call_output + - content: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + previous_response_id: resp_01a1686a-3637-7d51-8cde-b035bd3a4b15 + store: true + stream: false + tool_choice: none + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753157 + created_at: 1792753157 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-4318-7f53-a2ec-ebf64c22d901 + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply with exactly "PARIS_MIXED_TOOLS_OK" and + call no more tools. + + ' + type: reasoning_text + id: rs_803b14c3efacf014 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + PARIS_MIXED_TOOLS_OK' + type: output_text + id: msg_b85656b29fd28fe6 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1686a-3637-7d51-8cde-b035bd3a4b15 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 932 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 34 + output_tokens_details: + reasoning_tokens: 24 + total_tokens: 966 + headers: + content-type: application/json + status_code: 200 +- filename: t42 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-4318-7f53-a2ec-ebf64c22d901/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call tool_search exactly once, searching for + both a current-weather function and a travel time-zone function. Let me + craft a query that covers both needs. + + ' + type: reasoning_text + id: rs_882196426dacde62 + summary: [] + type: reasoning + - arguments: + query: current weather for a city and travel time-zone conversion + call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tsc_a8223c016a4424d3 + status: completed + type: tool_search_call + - call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tso_01a1686a-2fdf-72a1-8bde-381cc55a4ed8 + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_01a1686a-2fdf-72a1-8bde-3820d2049b99 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call get_weather exactly once with the city + "Paris" and nothing else. + + ' + type: reasoning_text + id: rs_8e2d51599a92e78e + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_b5a2ae9c32e51507 + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_01a1686a-3ca5-74d2-942c-ac60295f134c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_01a1686a-3ca5-74d2-942c-ac713b58a696 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call the travel namespace member `get_timezone` + with `{"city":"Paris"}`. + + ' + type: reasoning_text + id: rs_845c8e9c9b4e459f + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-b64d6e7c111b8a9a + id: fc_8064e5123e854c49 + name: get_timezone + namespace: travel + status: completed + type: function_call + - call_id: chatcmpl-tool-b64d6e7c111b8a9a + id: fc_01a1686a-4638-7dc0-a7c4-731a6e236565 + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + status: completed + type: function_call_output + - content: + - text: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + type: input_text + id: msg_01a1686a-4638-7dc0-a7c4-732af44c3f56 + role: user + status: completed + type: message + first_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + has_more: false + last_id: msg_01a1686a-4638-7dc0-a7c4-732af44c3f56 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t43 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 4096 + model: Qwen/Qwen3.8-27B-FP8 + parallel_tool_calls: false + previous_response_id: resp_01a1686a-4318-7f53-a2ec-ebf64c22d901 + store: true + stream: false + tool_choice: none + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753159 + created_at: 1792753159 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-4cf5-7223-917f-cdf38247e6bb + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - content: + - text: 'The user wants me to reply only "OK" without calling any tools. + + ' + type: reasoning_text + id: rs_a98ef4703170f351 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_ab9477b32b79e026 + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1686a-4318-7f53-a2ec-ebf64c22d901 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: + - description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 987 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 784 + output_tokens: 20 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 1007 + headers: + content-type: application/json + status_code: 200 +- filename: t44 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-4cf5-7223-917f-cdf38247e6bb/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call tool_search exactly once, searching for + both a current-weather function and a travel time-zone function. Let me + craft a query that covers both needs. + + ' + type: reasoning_text + id: rs_882196426dacde62 + summary: [] + type: reasoning + - arguments: + query: current weather for a city and travel time-zone conversion + call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tsc_a8223c016a4424d3 + status: completed + type: tool_search_call + - call_id: chatcmpl-tool-9bfcb0395132f225 + execution: client + id: tso_01a1686a-2fdf-72a1-8bde-381cc55a4ed8 + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_01a1686a-2fdf-72a1-8bde-3820d2049b99 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call get_weather exactly once with the city + "Paris" and nothing else. + + ' + type: reasoning_text + id: rs_8e2d51599a92e78e + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_b5a2ae9c32e51507 + name: get_weather + status: completed + type: function_call + - call_id: chatcmpl-tool-9e4783529d80aacb + id: fc_01a1686a-3ca5-74d2-942c-ac60295f134c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_01a1686a-3ca5-74d2-942c-ac713b58a696 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to call the travel namespace member `get_timezone` + with `{"city":"Paris"}`. + + ' + type: reasoning_text + id: rs_845c8e9c9b4e459f + summary: [] + type: reasoning + - arguments: '{"city": "Paris"}' + call_id: chatcmpl-tool-b64d6e7c111b8a9a + id: fc_8064e5123e854c49 + name: get_timezone + namespace: travel + status: completed + type: function_call + - call_id: chatcmpl-tool-b64d6e7c111b8a9a + id: fc_01a1686a-4638-7dc0-a7c4-731a6e236565 + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + status: completed + type: function_call_output + - content: + - text: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + type: input_text + id: msg_01a1686a-4638-7dc0-a7c4-732af44c3f56 + role: user + status: completed + type: message + - content: + - text: 'The user wants me to reply with exactly "PARIS_MIXED_TOOLS_OK" and + call no more tools. + + ' + type: reasoning_text + id: rs_803b14c3efacf014 + summary: [] + type: reasoning + - content: + - annotations: [] + text: ' + + + PARIS_MIXED_TOOLS_OK' + type: output_text + id: msg_b85656b29fd28fe6 + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a1686a-4efc-7960-8cbe-f2aa5f05f279 + role: user + status: completed + type: message + first_id: msg_01a1686a-24e0-7e72-a093-4fb9ca871fd6 + has_more: false + last_id: msg_01a1686a-4efc-7960-8cbe-f2aa5f05f279 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t45 + request: + body: + input: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains these + lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, payload):; + 4: if user_id not in CACHE:; 5: CACHE[user_id] = pickle.loads(payload); + 6: return CACHE[user_id]. The function accepts payload bytes from an unauthenticated + HTTP request, caches profiles without expiration, and returns the cached mutable + dictionary directly. The only existing test loads one valid payload for one + user.' + max_output_tokens: 16384 + model: Qwen/Qwen3.8-27B-FP8 + multi_agent: + enabled: true + max_concurrent_subagents: 3 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + openai-beta: responses_multi_agent=v1 + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753299 + created_at: 1792753161 + error: null + frequency_penalty: 0.0 + id: resp_01a1686a-5682-7bd1-8c95-f08ef8429203 + incomplete_details: null + instructions: null + max_output_tokens: 16384 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - agent: + agent_name: /root + content: + - text: "The user wants me to use three subagents to review a Python change\ + \ in cache.py:\n1. One for correctness\n2. One for security\n3. One for\ + \ missing tests\n\nThe file cache.py contains:\n```python\nimport pickle\n\ + CACHE = {}\ndef load_profile(user_id, payload):\n if user_id not in\ + \ CACHE:\n CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\nContext: The function accepts payload bytes from an unauthenticated\ + \ HTTP request, caches profiles without expiration, and returns the cached\ + \ mutable dictionary directly. The only existing test loads one valid\ + \ payload for one user.\n\nLet me spawn three agents with distinct, bounded\ + \ assignments.\n" + type: reasoning_text + id: rs_b2d84a4596d72ce3 + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"correctness_review","message":"enc_arPr9zESu3iaF8wAhYeCmHJN2kC0HtkHASNRkc8p6iCBGYi-st0JNxGxBD_QJSQpNtlXdZIxOUX3l22u2iSIYc_FB4LrwTFlSyBsj18IX3xWmaAmypL9hpESdbqe-YqACaEBmASqeLqASkckhiarCxJd2b33BpP0zZEHlm8EDm-Xsr0h1fF2nR0-MhUkQ23RA0W82hnOpE-uOd0zjeEMkt1H1JosYwwJC7j5k0Wk-7hSSiPeRR26Kjbzqp2udbD2f7UdyxaYJfDgFrxiBV9oBQ35jJScVVZvIJhPuv6Ya4poW88Rr0C8KLk6wpE4ZFHk9e2Z_YbWVnr64nvc_ye1cCw_rGE7XHImQyVZATfkRmx0qO0DOhSmRlTk0oGO3hCUK9SdzPf6sm3CsLCXrejUHhr0QKsdqssQGsarHeOxmdgBQXUhfG_-ouNuSGlIMxZnwTFbCWmN-5ApShjDNA7PlYdQ0djLkdDhODSDc4Twm-2vWT4BqZkpPfqivlO0qQrgG1Fwbyllk8ZSCMEaBrMJDhhdcBIHbbvlSjIGBNnspu6t774dRszmHG045eSerE4aT5zcgyjVw_HRxoh07g1s5PFlLBNthqikj__Cfjcz8xoUSnZy15Sbom7TbSibmpDLoKhnoJXJXPnBubwgK-M85pqjLRm6aD6RH-zSQ8NIi87k8qkdcCKEMoOLtXn9wUHJ7S9JrCKh0QBobPdvLat8YlY6Z16yYl-SSkeX_4TPqpJbbOLHtq-zJEtHgB5WEqzFD_2phnOzoVBpp-QQHTxNui2UbDnonK2gpRjXbcmishOUZkK217pGsiqNpwRiIToMMuSKyvUoP5cy0Ysq_PDaCW0IJ6D6SQPEn4hFk_M0ascguBWtZ3jKC7kfiwGkH21wgWobPT6jLmVzqGHhuXCBzGPkioL8ns6mktgvL8sGOp7ZddjiNSBm5rhzdtba4bl8V6EiUUtj9TQ99vFOMK3aNTOTbW1lVutsdAH3OyecxpYfvazdU0OQw7sYyq5XUBKX2wmykEVxmSIoEaz0e1EM_1gvVwPVzlJCQsdrKiAn8eLMdzTryOR1jxI7bcMhZaBDyIkR7ulhnup-kPmnY9KqRQP4_EC8fCjEj_zSxLntgXSEGgVAabq7akLHoDNFG5qgTbrM3KT-9y-6_8T3gV4_R_Y1ori5730ADUxdqz0tP7SMfJvJTjDGzYX-xwAqGh38mIYZneHBPBAahFGuSudUaeTdUQQcYZMQpkQX56IBtwSZZxcq8iBjKCKzhuyHJXb9Frn_ZC8xXD6VAWrVdJTn2nxy65oT5yaJtcbuujiwcOWVNvunwKNSISTYMhcgqwNi1rFQwjDvfdlM4mhtYNU","fork_turns":"all"}' + call_id: chatcmpl-tool-ba1410025d43a186 + id: mac_01a1686a-7bbc-7a60-8630-532a18705f97 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + author: /root + content: + - encrypted_content: enc_hP4HMi3DIigVa2OOrMwxJ3Ka4-R_oXXm85lNFcfqHExygU1CjKZKf8oZpZmY1oOkFtD88IW7ah9jeI8j9CipyQMKH-XgqbLgJb1dnJYqEQOYKfoPWnwwvpfKGCK18VR5P_3KGepcnhneAaJubgSqmN0cIJ89GOK2L3iPWlLDTHINXLZxodqRDWIXLUWmercr5j7u78qmFH1xd_yCsyiPnqP2CmWNiCxJF8vwGrcc3A5uzIvIZ21INiBMYQKRPLEaRBohV1Gn-oWpIgBkskatVqI2iLcArEdfPTOQcbdTQ1J6IzDdT2wMp4Yooo9CGZ_H59rE5_H4l_NGbbOjRFrmgRPOlamVLD8vnABHWSWYG6cdfOeUV-6AGiBA3t3rF-sgbJFYkmfCXlpyqE_T304SyRZ5m7OEceslO-5IXuj9_oLAcULapBi-m_CgxiPuAmFP5Fi_Mj5W62ZVoQ6GBqBgP93uPE482fQBU36eJLW9qtzYouiimF8tOdVE0wbZKylXJmXqU-0hiH0AoAjInlbjwK5b56ieoZx3vgCaxpKlmB1fQKSNqa8Z3hVjt2_sM1qe4AkRqbEvbs70j48Y4mrnck0z8n0W9jDKkZGCNOYgXMSMz8cm_wEKbXE9LBEOA-ibAyZjwuPlYLHQeC9mkVvCXTmZP8XhcXuoT9ANysZx4KYRbuYOXoNsiWq_BVSvnqm5556HfJF4Rr8KFEd5bp-0usBdnvPpyiKdl4NiLRt711lnymHb1jfX50Opjq15_11PiBQvsXi6xy13fic_Yr94OKOmcBalyMbivrKlO0J2NjsuIa6M9KInnqCDN-oKy4Un7z7MxE0O_kZZElrgKzloHHR6Gp6WkIksMel4RC-Sviid1b53Fw5sgysmUbSulDwYo9icsWnR6CT-92-mAP25j2ivEjQ-JEqg1-hhPgHJjR7ObW9Gwhe_pA0pHjaMX2dp2S8wvU9WmPo4LtGDaNvHY5gQxFyLmAQbfdWJpV9jeB1aPH5E_gpotgF4rn95K9m1U4lqM1HPGF4SoxiudCNveHy5BZbDFw1LvI4d1-i-Q3UbnTM-yo3CYoSQCTN-1YA7uA3yw0xilC0yuR_wD-Mu7pi972A0P_BYj8Fog7ubZ6TGR5g3ByMmW_tqeiFEI9kSuBfvLH44PK5aPCBCCkg6hCXt-O6C5ooC832oLYvXFRfpRoST0BhXu-rHSlJmGWuvF0JMAvA3TWiwAJGw5JnwLkm7KgaW1D_1Vh107Nwh390PxRo4G92noptbdeYWb2MJp9W46rCllRVNJI36Bbu-tLU6SLTh9vLirGcwif2tEmTG6TAR4sRywD2mo0ox20yUOvP1GR1LVqNYcCTh_Pw + type: encrypted_content + id: amsg_01a1686a-7bbd-7d32-9a0c-3fc4a9c2ce6d + recipient: /root/correctness_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-ba1410025d43a186 + id: maco_01a1686a-7bbd-7d32-9a0c-3fe134e05c9c + output: + - annotations: [] + text: '{"task_name":"/root/correctness_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The first agent is spawned. Let me spawn the other two now. + + ' + type: reasoning_text + id: rs_9402c5bbd1e8db9f + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"security_review","message":"enc_EA87eB2Aidg3zZR2xPlAgyV600V5x5usELCIcCCV0nMdjtKldD0Q7TWHVjnUtESGXC40hpTpG2yWMiC6zSbPX1twhG524szMHDkHSCy3a-06IYxlDnte2kGa0IlZS2sJxV1iPfn08rBWTWzY_EjxQga7P0lVgtMv6YJaNGK9VGzjvc0cQXAdFyZbYRTQUiMWEno6e0VXbjua1FhfN02C4-2yHBu0qo95yXiPIAfyyEzsQHl-qVj4HpMmpkVuhhNoEfwgmZfEMnAywT1m3TtGcwq9lJqzfiNacpTSdzZclsNQnD6GkPy13mB5WpQiranCM36ZaW7BoeejUQ1G_D3RpQVSlcakb7VzPmae2_nAxctKkBm3Tskjo7aWRQTKkb40Xpir5RWR4rsAJEhc2U7UEgVxxD9RaQV--g8Xw5ZEr3f58RAzpPQBLG3V-Eo-Wv1TF19G2ESV98_kCf0cTUIpZbXr7GT_opbpo6ln6gKLn7JxphTaGPandYi4dOrVA-H69jKYaKW7i4qnM-tiOUKI7XM96V0hwb1XxxWe6lK95oq9MUrfrVZ5p4GbP9nytezqjBOuDR3Kq3iycOCVD6cOZOCSupfvxHbSBe8wn5LzULhRyoQ0VHFAd6FI9-AZ7_1_JRXef42VLuLKt26k82RnDx2wFiTn9mSTPu6NKjhzvZed7-uDrIjcVc-kTnGqI0hEcJDiRBL_DOV82mHiDvcENnG4j0WHtgxDUlrE_4Ogn0wK_hMg_bYC030a-AlROfIIAl4p5suP_SzBA4vYIWiXciniOYCejdO-gVnnX9_oeu7ExA_bQ8PTacb3KuSpKS9RK0j-sc25ow40Q7-VBONwvML-DHooMZXNZLfmHtvroEoAepOKkOOUnbKS_hALtnoxxzb7FjQGZppRPFsmiuehzJwUJlJQhSDMU0QFldxT1sBczHyO7KZsjkxAKC5MEJFI6Lf-BCMmi8EIVLikVcfjkk6Hjc5ZpoqJ4USqR8coMruyp2K19FXoeXHhV-XhGTMFSckvxm0RTSKDSV7q4rWcC7EzENLbKqH4eZY9aY_0NT1v-G3CSuP0WL6vvZyFlnHEY_IMXOteaE6eczs68qN-N8RDcuY2RTSVMx44FaW9Rl5uKIYaAMYZxnk7IETz2cy7b4vOOwnLFCKVSnMlGKhjofteE3cjWmEtTbQI3Ic5VRu_elv5On3ABXjNBdK_qgUpqdAPd013RQkzlNlKXScxOZeY6T3JYLW2pMr2o4CmB785K45ySMhB_3DV1ByJvHVfCLCckgiDvyosIwjRmCEvQJ-nIBQnuQiS5cb4pf57Yd3BTbDnqhpq-ptPh2PPmI49JWeXmqeDWeNjNhE67mFseqm_cZdh1GhHwcicbKhYypS_B5PR4Jht7wDYJz5SJUBVpNvV7FqRLs6jiL0W6MolBbMKDvAjeaXelhufJrDj_-bpDOYUrlg8XemoLtT5XvGe","fork_turns":"all"}' + call_id: chatcmpl-tool-b4e4d375f9b6642d + id: mac_01a1686a-9943-7720-a427-c50a9b89944a + type: multi_agent_call + - agent: + agent_name: /root/security_review + author: /root + content: + - encrypted_content: enc_pJF4Hzl4gf97q5vMVkTm_M-X3QGox8QJWyRGrxurEhMaG1EP4_CkrbDZGDNpOdYquKiIvckkUcFnlsWYkO05kBGcPwOXIE8ZSISziJzsMOUxeMnc_v33Wy72aixQuQddEwsMO24ECE9m4aYMwV7Z7VqMycXcNeOjywzvFDoeF8I7vkxBrWa7kyEptVl3frDhHnHX9eRPPnuGh4QozErxK3nFLv4qjzrr1d0GXT-m3yqXQckkZB_UUTmJeeWB3uFIWdnw_cwYsgomEemynnQP7af9KahRUo475qB4c74pTYiVDYEiA727Eoe0QLskY-J-dYFEIsJXzVuHWD1QfknDVG4GhCv7djTxPvtnVPKyyOPm9x2axcKF03Omi8NuM-ZTEJUkqEKQ45F3THq5LyU_DEsjHtwBWP1A2fsRuIQrWaJS9FkV3dkY-lc3vhO3HJeeM0JP62t_fb3BZxhPZzciqXrsLtT71tQaADY1zVlnSiAVHcuf9ZeG8H2lcjQ5YOgOQ4l3YKuEWrHXcbt7kiH9WPWWqR0t7dWbRThvNb9gPvLIT8bKhXkOZgXY2WMTQoQX0G44WEJezaIK87zEGHMVuUZvSeoqMm2mnFRFOdcuGJdqa4lAifNm_YXJtTBqJHEWfR4v2g2cVR5E_AfGqyqc82LyKexekWTsZdzsSkIbTMdF4fmnMhZP9WQMl1pJBcqq1Z1OSgU3rc3rfGA0HWkMcKbNrJmPI6HrK6nuoaVp5W7jsxoQNuseib_qcxxM9duIFSbI9uFcvR5f1xtfFiE6KOJLr_E-PO2sbjETUxA8ckVMYDGMneqx0VafJPcCU89YwnQZnHkBfH7yHhojONm9qUvEKZL0xUe1JS2TYD4DwFzSs7bUl9ZtMrFLHuTPznfLPbuJMJhZUw60VJqznv4mfoN8Hb3cO3WufzmTMa31be6FI--FfgvdABjlQ78wRtaFeODLv-dRhaMvwklPeLwlQP7F9GefOLShOcW0tM26NUBnVqxFP88iyBDhf3NFu5QLwg_ngi7EdcZbdFqtRXWU5w4SJW3Jzqu41PGVuK7CWqYKQ9pg-xqGOi8kS30bsJYCOYMsku7bYj3ne8EXDeTK5QWhftzjI91jy3jvD7_z9ZSCW0L2AL_ZmpCFeghPRCpFnp0xhX8Q2B2ZEGo_HkhI43wlMxwzM_wKzxyaa1a9NwnA2H0NkGxmRK8eiq50VGgpr7a8w5WhzDykZLc77mlLxcVWO8Xp6agRyhe559AR9MuEEZphXGb0tX9BbowLM5lJAYE-R1k6n-5rBRMAkNyaRZwmebQhADKlT44vAR_iD3_35Xq6zY6ie5I9h7xdRTf_PRpaes6T4JbNyvPzw54XqGueeD5KQOC7zHH7gJJ46waA7crLwrtpM7AaxZ3wfUFONDQAdnHcVFnLMqNOZdqm-U0z_wCTboek81cwg3oCzix9Ib9WaLSrsPe37VJKo2T6 + type: encrypted_content + id: amsg_01a1686a-9943-7720-a427-c52e5ad9a500 + recipient: /root/security_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-b4e4d375f9b6642d + id: maco_01a1686a-9943-7720-a427-c546658a5646 + output: + - annotations: [] + text: '{"task_name":"/root/security_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'Now I need to spawn the third agent for missing tests. + + ' + type: reasoning_text + id: rs_a4f8b08deb9728ad + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"missing_tests","message":"enc_o_seguRKsKQ1RCWPj1Sk26Y6IfY_KAddIexL-gSIIA_FY8XV-5_VEsjrL_Byy4P9cpbjcgCip9Mgh9BMTQ0Fxa-9x5hReTadtHK9yoqXTLVdCixQU1XSZcvMF0GLqlYvMizKZ9QCdXhYEQmcHQjYjySKp-zoRYwmvn9_IOGOmNwTMu5cgE-O5dcLVIDsaadq2CdivY7qCv7pF5RpMHl4YoFdP_88RXFQv8aTCyMGOOAkYfTYVOD_wzo7at1t043IzzYV-XSXbeMn0TzHucEvves6D7HDGZy39Nt9lugBsnxgopEZlE6-1OhiYO7kZZozIWyZlgw5wxkZNJ668d1wr8zxLkWeTf14VcnV353f5nCj3nT93ZvSbMo0fAaVIKOgN7Qg0NA5BnH-2sUrn2fhXedPuG3j2dt3c6Yo5sFKvgJouMfqi-U_4xpY9XJhuzgsLi17lSdjbvgBD1NjAFelyTVC9aM1p0UN-X-O0Z9mgv7i6kEDqbcWyNFKNNU6Az1fgCJLDoyOvg0JBKkbKhri4AU32FUCsTcCBzLG1SbgHW0Kq3s7bvzRLhx3PGKKsPX2Pfcc7NfZ-i_U49CXq2hJaaOkWuxbizdKJrydJRVNJCxcqzOqJdls4gsnIz2JA1u9AH-e0ZObbHoY-iYkfgQKRhJgCiZG4dbFrj5s4glbmBkopPNt8k5RsUPHTf7f0NlgE0YfjrA6tXUbdhoWV2_7GP61TaSGECYCZxKtwiUO6MYd4G_x9LccJCQ09cbpF6XHKBKEg9WXImigKc34-Xje1gjMsdzIv3BzRo3wv5JLo8bOcZAplppaH8nbmWeFCNlJlqeaHpxFxYHW4dQqZfRBO4gzLhdY9tfn90AI87ziot2tBsHwYf3z1Fr584BD-cxGb7nM6FRS16lwekXHs4MRhZFp6kJ0lDxk5yv9iW1v21ugk8JkeUq7mUTjvbSq-99dw03h_a7QNLGfdwEiH6ERguwWG12azXL8hBypGo4cmRYx8L4db6cSUnFNdYSb6Dc--au1W0-66cw1rr5lFNwDlH5cChWYdyeahRsj-Or3epQIBy-8a625843_wiK9GIwcVUaX6o3Yo1PuWRaTWOFainEIi8sKotV-65gAo8dTzZzxEggEbzLzUj8uV2upW6Ze5UT0kpkcvp19HA9mOuGl8aJIpbiOA4EGis4O3MJnuod4vmH8Zxs2-d_yz0HSVwEvo5f3IIHyzE12gHo6629PgAd4NniJsSsFRsSrzu7UmNpKHDlU24bDriyQbIvxF75Ya5t0uTX0QoYaAMVs0g2oy62vt3waoJYH2k07C-v-21iYW0yE_7ES9t5EUc8-M67jdqoj2xHbdpNzm5ArcMn6_sHS6g2FSP3b18gmHuDwKbmn8DpoCd_M2f08Z4RgH-cOV45qI_0TxmJbDO-EkYctXZXauPUJEVYDz8mEfJJgWAquPPo1a7fll5hf1rxfqyE78ZY3-rckjYejcoX7T6eUZdszKYk63rJHtk2Pj0oYnDHa_KXZ2VP5Cw","fork_turns":"all"}' + call_id: chatcmpl-tool-82d8e28a739efcd7 + id: mac_01a1686a-b7fe-73b0-a4a0-c57f7b509b26 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + author: /root + content: + - encrypted_content: enc_L4HqwAlVvY2TgvtD7HRK1NbwNShm7a5xYcZaBpJYZqxXop-C8VGhZTvbP6kJkdd4B6ynnB83DX97YrpMeLXw1DBQZvnIbwqNPAGnl8UD_XIpirWgJxtSAD2hCrqUMnKaX7EvgYUCOUpQkJQcKIF2oiOaXJswW5dse5Z6JKBRX6iPY87iijF_AdviypcH-MnTuX_0paLfDmeaQNUKbqKjLyLAq3YqpRmU3jAMEqGIlBS1sxmmoC0f_RYVJZaSBXu0W0gHgS2QD7DsAQRlnliyuo5KGwJriuVXVCLIjjgd13w2Cc6j5UM6p9vpH_Eh-UV_dy71XYmFDoVJltpRv2oqtzWM94R8dfqdxOw0c8zFjEva2KbfKaz7R-uQ1BvPiCp41PFWSp2iIAX19fGVEV4EOLYCKlNjOeBxotRmKK4V-6fp7lJPgPDSLQ7OdtFatP8ypqVdx_0a2weGzvKXBIPmLmT696VRAZllWC_HPRboN-G3G5efD-IhjLsejxAD_ECSFj2NpWC2XjsIdahdaEPFvf0Xo5Hx6mP67dvM011SESyLUQ0DlOyiBf4iVNf5itIgcfdvCzNMABl5UqRCzcNZ-5RvkzcqMTGyKh1Ho_FD5wU22MWn9okYpg7ZaFl5eivty4jBp6OKvWibxha8q5rnrWzFmLuDqV0Hi3A7FoFFDiMah6LW9vg4QHcp4eLs63uj9cWLxGXSLr6D2iokiB5LujiqOoguA0pbUCaVhR4ltB1U9czdj9A6W8-tlqZB4_iJgC658m8S7l_LT6wVgkGUXZ_wElPe5QyR8qfEmzKRqJTwdR600satDua9quEDkucsKhahqkKXGAyvtsCNc9s-fjNK9GPvImVZtmNsgpd97LibPcrtmLnnmr52pUBA1APbAx5CckOYYM_1cGqY4orKRR8rK16RQQ6-D4oExyCaCWaPQNTkFUdl8j7zsm-FrLPt9F3I67PAo73TDbzQS9dl3grC4N-LscgpDZOlV_qYtxcyM2gw5ORHzrn024zQDd8iAhaHgVuQhdN5uLbwUehTgz3PpQhs9Le7QwC7iix2IQwTk9P16KtM0KABfjcGMRskbqJ5E8MhldMNJyJnk-mwhgAwVavnu1OKr_6dLv-wFDb2DRe3AjGIK-NQXPzgSL8u-QyogmO4DcHrKHQJBV1l1KlSIF2qPqugQjDxTXuNAfX1tkVSE-Xw8NoQ5npYsRGAtmVAVfoHAQ_9xYOrSiYnxVinW2qDxkaG5bvIUd2j756lfcufqqAOn6UyyPI_mSeFzr_kfXdD2ZiM5OSUOFLGYijLivmjPUAvcvFe8fXWcFKdrVv3R4rXEDyakdvN8p8yz3O-Dmw8PBbMYdpZiuwZlbPqFRw6AAFdxIQlVQgNZjK9VPjO7MREcG5XzsFeGT4ONHfThWF0qJdXSCW0tvtt8Gf8MicEAfBXSn1E8oee__8i_zk_IA-0c8qqbLItZC_hVvaUDK58u1ecNkESHIsmondY8QkAakE3WXdPcpplZbe58qrC6VMDlA + type: encrypted_content + id: amsg_01a1686a-b7fe-73b0-a4a0-c59f62294d20 + recipient: /root/missing_tests + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-82d8e28a739efcd7 + id: maco_01a1686a-b7fe-73b0-a4a0-c5b1e5241864 + output: + - annotations: [] + text: '{"task_name":"/root/missing_tests"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'All three agents are now spawned. I need to wait for their results. + Let me wait for the first one to complete. + + ' + type: reasoning_text + id: rs_890c73a079af7523 + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: All three review agents are running. Let me collect their results. + type: output_text + id: msg_a8000a4038f8c579 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-a9a3f9406d5895cb + id: mac_01a1686a-c466-7ec3-9817-b33adeb90186 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + content: + - text: "My assignment is to review the Python code for MISSING TESTS. Let\ + \ me analyze the code carefully and identify all the test scenarios that\ + \ are missing.\n\nThe code:\n```python\nimport pickle\nCACHE = {}\ndef\ + \ load_profile(user_id, payload):\n if user_id not in CACHE:\n \ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\nContext:\n- `payload` is bytes from an unauthenticated HTTP request\n\ + - `user_id` identifies the profile\n- The result is a mutable dict returned\ + \ directly\n- The cache is a module-level global with no expiration or\ + \ eviction\n\nExisting test: One test that loads a single valid payload\ + \ for a single user and asserts the returned dict is correct.\n\nLet me\ + \ think through all the missing test scenarios systematically:\n\n**Caching\ + \ Semantics:**\n1. Second call with same user_id should return cached\ + \ data (not re-deserialize)\n2. Second call with same user_id but DIFFERENT\ + \ payload should still return the original cached data (first-write-wins)\n\ + 3. Different user_ids should get independent cache entries\n4. The cache\ + \ should return the same object (identity) for the same user_id across\ + \ calls\n\n**Edge Cases:**\n5. Empty user_id string\n6. Very long user_id\n\ + 7. Non-string user_id (e.g., integer, bytes)\n8. Empty payload bytes (b\"\ + \")\n9. Payload that deserializes to None\n10. Payload that deserializes\ + \ to a non-dict (e.g., a string, a list, an integer)\n11. Payload that\ + \ deserializes to an empty dict\n\n**Error Handling:**\n12. Malformed/invalid\ + \ pickle data (raises pickle.UnpicklingError or similar)\n13. Payload\ + \ that raises an exception during unpickling\n14. What happens if pickle.loads\ + \ returns something that has no dict-like behavior\n\n**Concurrency:**\n\ + 15. Two threads calling load_profile with the same user_id simultaneously\ + \ — should only deserialize once, both get same object\n16. Two threads\ + \ calling with different user_ids — both should succeed independently\n\ + 17. Thread-safety of the CACHE dict itself (check-then-act race)\n\n**Cache\ + \ Mutation / Shared State:**\n18. Mutating the returned dict should affect\ + \ subsequent calls (since it's the same object) — this documents the current\ + \ behavior (or the bug)\n19. Mutating the returned dict should NOT affect\ + \ other users' cached data\n\n**Unbounded Growth:**\n20. Many unique user_ids\ + \ cause CACHE to grow without bound (documents the leak)\n\n**Type/Contract\ + \ Issues:**\n21. payload is None instead of bytes\n22. payload is a string\ + \ instead of bytes\n23. user_id is None\n\nLet me organize these into\ + \ a clear, structured report. I should be specific about inputs and expected\ + \ outputs.\n\nLet me also think about what \"first-write-wins\" means\ + \ — if user \"alice\" first sends payload A, then later sends payload\ + \ B, the function should still return the deserialized A. This is a critical\ + \ caching semantic that's untested.\n\nAlso, the check-then-act pattern\ + \ (if user_id not in CACHE: CACHE[user_id] = ...) is not atomic. Under\ + \ concurrency, two threads could both see user_id not in CACHE, both call\ + \ pickle.loads, and one overwrites the other. The final value depends\ + \ on which thread's assignment executes last. This is a real race condition.\n\ + \nLet me write up the full report now.\n" + type: reasoning_text + id: rs_ac7b4b15cd0776ac + summary: [] + type: reasoning + - action: list_agents + agent: + agent_name: /root/missing_tests + arguments: '{}' + call_id: chatcmpl-tool-80af08d7ee382b50 + id: mac_01a1686b-0032-7e60-85df-241ed3ee0676 + type: multi_agent_call + - action: list_agents + agent: + agent_name: /root/missing_tests + call_id: chatcmpl-tool-80af08d7ee382b50 + id: maco_01a1686b-0032-7e60-85df-24334e05366f + output: + - annotations: [] + text: '{"agents":[{"agent_name":"/root","agent_status":"running"},{"agent_name":"/root/correctness_review","agent_status":"running"},{"agent_name":"/root/security_review","agent_status":"running"},{"agent_name":"/root/missing_tests","agent_status":"running"}]}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/missing_tests + content: + - text: 'Good, I can see my siblings are running. My assignment is clear — + I need to produce my own findings on missing tests. Let me write a comprehensive + report now. I don''t need to wait for the other agents since my task is + independent. + + + Let me structure my findings carefully. + + ' + type: reasoning_text + id: rs_abd432a548919a85 + summary: [] + type: reasoning + - agent: + agent_name: /root/missing_tests + content: + - annotations: [] + text: I have a clear, independent assignment. Let me produce my findings + directly. + type: output_text + id: msg_b5a39e4b8a98e219 + role: assistant + status: completed + type: message + - action: spawn_agent + agent: + agent_name: /root/missing_tests + arguments: '{"task_name":"missing_tests","message":"enc_crV_LNJ2itOXlJp5Jm5jsDg9LPvKi2I-jJE_94dGv3Dl3xr4mIAGBW2gpFMPYEXUpiYwJtX9R5fT9rek4KQD8ok7PrLOiXRKpT7RTztHUfX8ZXCKhrWYFSvw5EuFaH-jqPJX9ilyOE44RcKmGnczCsbxMopO5hFv6I4L1FbEl872OYiWSBTxroOXqJ3VMBqppVamPDh9MPTACIFIZMQNsrJXTYaI96DOagfVuqdMqQ5lXn6NIaX338Eov9akSDO36ZYgOps_zOJBF6Od-54LFtK_IKdBQXOfZ3K7_Ia-6MeG_FCdgu1Dqgd2oTP5L0dKLoHCMDbzRatGnAAyXtXza64Jltv_4xidBHV3KD86F2C7KZ4fgBfoWBnvNF94S7AGzbtCEBUCcWNSLvsgvGOlGGOpQGgeqvJoP5eh7ztf6a33lNQRw4hQqovIa0yu2PdmWBi4IgObuE2BgYppz2_aDUpNce4fptBcZ5EUM3OrLC3kRcJW0CTHeYqwPGNq54fXQE-dJz2unYwUxOyALXb16j2jh01N0dXxNlDAwyvmMg8mRaKFaXPn3JTbJPMDw0zLkfYoKjnnU3NdtHMLjrtFEZNfysbYl3ehQYbCrFrTLGaK2U-2aJBJDMyD9YZmud2DzILEgHkMn6LRS9SyYhdlmW-BlR8J0i8o3hNT_ZTNFiYHofi4TDgyJCyBt7mrMQFdj0JKvPo1gAs_zsyWS66K7UOjT9pNvec7SS5QPrJOl-euesZ-pLhRLoQXZcmM2XLThxml557AyZvnWS0s0_xt55vhmBtD_yuzeb47LZ5bVuih3BQ-W6K7kYNMV9TZi5pzjfblGQxq-0QHh8d1gepMDMa1IqMd-x8ucVwd0iRx5GMIHoPje52vr-UeflTIRI5itTqTLmzL7o5Fv9-pOzBVuRcGMQ3UqNw59CiKdiI2czPz47aIJv9H6ST8EnSBDKe4wFBuCHQefd4flXSmikiocP6y_gQB10AQymOsgqHvR-qFi3yRtxGqA510JxAnBSE4_16JmaytwABos5B7Y8hlGoQRKOfaHk4Px_crzZ-5ISOwTxAEmovffamKBWELCMvpCgqiEFm94ij1aT0sD4adNS-pCz2pd84rlYKy1ClqgkDwUytsR2UCXE9iBhLWsU1rUwusPisrNMOlA37m_e2cOI1bapXPagkFCaVUd1Wv9hFqaIPIntw2N5tApCEV6Kfe22KJ6k1SPcRYL-NA68sgtLhiV9ayOAJAyhythhyXnwC5fTGfcJi3ukuXnN8OfKJpblWAbVMSf5vPyl0oXJbVWDaAHkFbsWfCknpstel94q61AP_gn3PKF-gJtvftTR3CvoqYkI-TzSgVbGBfjLlC14cErU7ZS_NESKhbVbGExdxIAHruSESyM0oasGMFswUIqUv1DRsi8V3NS3kZTgti0TtF3Z8sNzBQhLuHruPafCpP4wRHk0E6C75qBGQOWqKafVZUkMpEyLP1aXbMd06Gn3czT8NV8ICZamJbopScVb3ZB744btNeznfjF6dtfOaww2ySbsbPMA7EVXPLfEB4UMqg9nc15qgt_fHw","fork_turns":"all"}' + call_id: chatcmpl-tool-8dc9ece48552be04 + id: mac_01a1686b-222d-71c3-9209-f5773e2997ef + type: multi_agent_call + - action: spawn_agent + agent: + agent_name: /root/missing_tests + call_id: chatcmpl-tool-8dc9ece48552be04 + id: maco_01a1686b-222d-71c3-9209-f5901356dd29 + output: + - annotations: [] + text: '{"error":"maximum concurrent subagent turns reached"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/security_review + content: + - text: 'I''m the security_review agent. I have a clear, bounded assignment: + review the cache.py code for security vulnerabilities only. Let me analyze + the code directly and return my findings. I don''t need to spawn any subagents + for this — it''s a small code snippet and I can complete it myself. + + + Let me analyze the security issues: + + + 1. **Pickle deserialization of untrusted input (Critical)** - `pickle.loads(payload)` + on line 5 where payload comes from an unauthenticated HTTP request. This + is a classic RCE vector. An attacker can craft a malicious pickle payload + that executes arbitrary code when deserialized. + + + 2. **No authentication/authorization (Critical)** - The function is called + with data from an unauthenticated HTTP request. There''s no verification + that the caller is who they claim to be. Any unauthenticated user can + load any profile. + + + 3. **Cache poisoning (High)** - Line 4-5: The first request for a given + `user_id` wins. Since there''s no authentication, an attacker can poison + the cache for any user_id by being the first to call with that user_id. + Subsequent legitimate requests for that user_id will get the attacker''s + poisoned data. + + + 4. **Information disclosure via shared mutable state (High)** - Line 6: + The function returns the cached mutable dict directly. If the caller (or + downstream code) modifies this dict, it modifies the shared CACHE entry, + affecting all future callers for that user_id. Combined with the lack + of authentication, this means any attacker who calls first can modify + the data that all subsequent callers see. + + + 5. **Unbounded memory growth / DoS (Medium)** - Lines 2, 4-5: CACHE is + a global dict with no eviction policy, no size limit, and no expiration. + An attacker can send requests with many unique user_ids to exhaust server + memory (DoS). + + + 6. **User enumeration (Low/Medium)** - If the application returns different + responses for cached vs uncached user_ids (e.g., timing differences or + different error messages), an attacker could enumerate valid user_ids. + + + Let me structure these findings clearly. + + ' + type: reasoning_text + id: rs_9818f9bb42a2249c + summary: [] + type: reasoning + - agent: + agent_name: /root/security_review + content: + - annotations: [] + text: ' + + + ## Security Review: `cache.py` + + + ### 1. Remote Code Execution via Unsafe Deserialization + + - **Severity:** Critical + + - **Lines:** 5 + + - **Problem:** `pickle.loads(payload)` deserializes attacker-controlled + bytes from an unauthenticated HTTP request. Pickle is not a safe serialization + format — a crafted payload can instantiate arbitrary objects and execute + arbitrary code during `__reduce__` / `__setstate__`. This is a well-known + RCE vector (OWASP A03:2021 – Injection). + + - **Mitigation:** Never use `pickle.loads` on untrusted input. Replace + with a safe format such as JSON (`json.loads`), MessagePack, or Protocol + Buffers. If pickle must be retained internally, restrict it to server-to-server + channels with mutual TLS and integrity verification. + + + --- + + + ### 2. Missing Authentication / Authorization + + - **Severity:** Critical + + - **Lines:** 3 (function signature), entire file + + - **Problem:** The function is invoked with `payload` and `user_id` from + an **unauthenticated** HTTP request. No identity verification or access-control + check exists. Any anonymous caller can (a) trigger deserialization of + arbitrary bytes, (b) read any user''s profile, and (c) write arbitrary + data into the cache under any `user_id`. This violates OWASP A01:2021 + – Broken Access Control. + + - **Mitigation:** Authenticate the caller (session token, OAuth, API key) + and enforce an authorization check that the authenticated principal is + allowed to load the profile for the requested `user_id` (typically `caller + == user_id` or an explicit ACL). + + + --- + + + ### 3. Cache Poisoning (Cross-User) + + - **Severity:** High + + - **Lines:** 4–5 + + - **Problem:** The first request that supplies a given `user_id` populates + the cache, and subsequent requests for the same `user_id` return the cached + value without re-validation. Because the input is unauthenticated, an + attacker can send the first request for a victim''s `user_id` with attacker-chosen + `payload` bytes. All later legitimate requests for that victim then receive + the attacker''s poisoned data. This is a classic cache-poisoning / confusion + attack. + + - **Mitigation:** Derive the cache key from a **trusted** identity (e.g., + a signed session token) rather than a client-supplied `user_id`. Alternatively, + tag each cache entry with the authenticated principal who created it and + validate ownership on read. Invalidate or re-fetch if the authenticated + principal does not match. + + + --- + + + ### 4. Information Disclosure & Tampering via Shared Mutable State + + - **Severity:** High + + - **Lines:** 2, 6 + + - **Problem:** `CACHE` is a process-global mutable dict. Line 6 returns + the **same dict object** to the caller. Any downstream mutation of the + returned dict (e.g., `profile["role"] = "admin"`) silently rewrites the + cached entry for all future callers. Combined with the lack of authentication, + an attacker who calls `load_profile` first can mutate the shared dict + and thereby alter data seen by other users'' requests. + + - **Mitigation:** Return a **deep copy** (`copy.deepcopy(CACHE[user_id])`) + or a frozen/immutable structure. Alternatively, serialize to a read-only + view before returning. Ensure the cache stores canonical, server-validated + data only. + + + --- + + + ### 5. Unbounded Memory Growth (Denial of Service) + + - **Severity:** Medium + + - **Lines:** 2, 4–5 + + - **Problem:** `CACHE` has no size limit, no TTL, and no eviction policy. + An unauthenticated attacker can issue requests with millions of distinct + `user_id` values, each storing an arbitrarily large deserialized object, + exhausting process memory and crashing the server (OWASP A05:2021 – Security + Misconfiguration / A04:2021 – Insecure Design). + + - **Mitigation:** Use a bounded cache (e.g., `functools.lru_cache` with + `maxsize`, `cachetools.TTLCache`, or a Redis/Memcached backend with `MAXMEMORY` + and an eviction policy). Enforce a maximum payload size at the HTTP layer + before deserialization. + + + --- + + + ### 6. User Enumeration + + - **Severity:** Low + + - **Lines:** 4, 6 + + - **Problem:** If the application surface exposes observable differences + between a cache hit and a cache miss (timing, error messages, response + size), an unauthenticated attacker can probe `user_id` values to enumerate + which identifiers are valid. + + - **Mitigation:** Normalize response timing and error messages regardless + of cache state. Ideally, the access-control fix in Finding 2 eliminates + this entirely. + + + --- + + + ### Summary Table + + + | # | Finding | Severity | Primary Line(s) | + + |---|---------|----------|-----------------| + + | 1 | RCE via `pickle.loads` on untrusted input | Critical | 5 | + + | 2 | No authentication / authorization | Critical | 3 (whole file) | + + | 3 | Cross-user cache poisoning | High | 4–5 | + + | 4 | Shared mutable state → tampering & info leak | High | 2, 6 | + + | 5 | Unbounded cache → memory DoS | Medium | 2, 4–5 | + + | 6 | User enumeration via cache state | Low | 4, 6 |' + type: output_text + id: msg_b068d7e90b024f84 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/security_review + content: + - encrypted_content: enc_xdqHKtKU03YmCjc7bQxN1aKKKOlI91CLE4TLr8NvJDYryiz4GH2QfUmRLovHAKy6djCIbl3SRtuR-6wpp99EPSY8_cBmgY-7OVqLRGFkjpSeiY5pmHScsIYTTt4hvY-aTdzbGMRQf2tXfnE3jce3r0To4HtcSSGwSjmvzfn_PpjpgAn-i1q6tVK2haHsX8OaV82cOc9ZQ4_qZVl39HnvRoqgWlN3PPmyL50VZGsTnEsra3xRpwsdtu8FGc_UJpPlQOiGJvd_-4cQX8Ov6Zd2AFoMhz7UXkvxsE2Vm3R8g6ulz0iufAh4ceJOl-r8uUGV5BqA_PdAQABl74Om68ulhPE_HVQsmtRlY27GwAxDVSREZd173ozun81Xi0qkeMNljFy0FzquRBMgUpMQQKLtWJA9qI9MW6iXDFbShC9twKvDMjp6oAcDAjCkjc8YN9kekz2kpNri9w6DBsjcsFtgGD5V6FWvLEJr1XDYoLrgu3OahRnecyolcxCGkFASSPDPHHFqgujVvAWwMMdYCHBE2QdtUBA6rbEk9kiTspjTQqrn8v1TCZ6veT3TUg2t0sgX-4hR0RceMNcipYATQspcXFGJg45lqlsAbeb-J9indPtNpSxV2JVjNS7Yz7ArsulMbqlvudogD1cPg469upGhr3gPO_I5DEDHoANBDC_VnROQR2xp2EidueGLwA-Lp9n4rVVe79fX5IZ80xmBepYVj35tlwXXGjSsV62XeGuE51b8Cuz7e1XP1SdFoRVPSwtb3mBoSz6VejPZgsheNFeiF29N3vklJm-TYdTHCxJIzAs257gEbThW_GJH1c14jb1e7YCrloMXujIao5OV6VYM7JSkuKUd4b6Q3jTI1FsURQMYNZs56m4c_ZFAEgn67sHcl9HyHC2rvOpC8JZhQVua2iPIXxI_P81P9bvlE78SmP_nE2TeS-k0uCGJgagQ3-smx21_zPWBgkJOdSRdTer17d7qeA05iSWonWHEpU0ux6T4B_V3jkbg157LICoixAie0TTtn8rrsZczgk2GBbfvG1kHbRhhsc9jAj53NhdMfS0yW4lxqMAkLWH0djhw4T0O1fAAa21EosY05-rSR_GuNisAtsVic8t_U4nLJHwSF8i6zdrD8n9N2g43avy8NghJvat5XTxFWWOZR16I9G-veRs5F5i_7WEqK_X2BUR8CB7DywtDfXgYF2vXwSy4-vgLvyB2LkAyD7SQp_WyEoDVx6VS8Awpifg3aecbXnKMBxRjQpbwOFWPVcSmRTbqanHjK8CENjo3SiImVO8U1B3zcTlSus9RedaqH25nE07ozfWFo-vUG6-kK9Uxs-YsP53GAvTpZzZB98ih2utVFMcYvarsFSFEobnT0eRKlOctD2VMAK0bATzMhfSTcWoK0Uy1DhuyI5yJLaeQi_Gr22LgODMtebuSOkNVlebph3d0IesSk7GOASEUYeO1DzGitwqRhIbq_6iSYvbH0tmEVXQvWaCIaATmrQM659xbKRszoovHG2dFMalkyA0kMV3kXCHrORIutpf_RAAlfvK3SK-82ocOwMNZBvdUQTg_qqoJ-HmtkCo83jdK5DqR4tqPBnd_bAE0oNYLdt4TFlEZkOfxHRsH7nb1e9IhLEUpzU5e2rJlImGXNVjCrUKRG9sXJJxhEZisOaUIJH-EVbA9OyoRGeOH_aItYWEGjvouIi8WWp2oRfy6CwdPUE873HGr9o_BA05Jhkv9-WPuoJWAGVfjwlf3YVHC9713Gb6g61nMky2WNIkFJU0fPHKK1cnazc-lRCsxCTiWxG7oRQ3C-A-xTKwa8Gp-cFJpD_w2I_4dCMwfWruWNvL_-wch5xGoYip02htUJrMZv0E5KPvtYY9IJts8vpSOLfHzbm_-OTuXjIS5UxatNxNUPCkJCLvF1lB7OuoeaDqdKGkdqRjbTK-hpCljEw8_Z6DhCBoLZ2k7dpax2ODKd9jRNHHDrwr5ZQikDzWeA0Z5bF3lDO-w6zfUALSkkdkAMYy77qdMysK1XLAwfyanJ6JrCixmQa0kutJwN8HtkK-roWZ5cUR-Z1Q8ztMuxrlMpuZ2fHBAw5E_QS4us3Dzju3KZauY8p6RKgrPIiprxxGoSF5gjW8lAyFGitx62dIxZCquAHTITDVpfjU-J5FmH5qPjl0Ff4Sjtg_bp3xlQb3sD9oEcIsSm9_t-s9EIIhWP8lCvS6CkPf4YRQHDbCV-uE9x7ZIdsVgVBo8U066CfHngVqFrHMrgiDlCGe83-mnzCKOViJylzrHDc_exP3HIauY0aB7_g8P77Xiuns4tPDA1J59dIHTtCWh2Mw1tJpaG-cjbAvRyO8M_hsu0YtY_2S-OxnryDOvzO6uEnZ-uu-1-Inl-CaO3xUrJ3pD2wuiHY5F1smQASZCq55S6xAhMNpsLbEDigwWnTr8MMrcaDJRiUPI0GJykcxQrTd5lUUQS2b7_GexafNqCdlCScNSy-MsEWloY1FYfaVVfEhmjLBljiYde06fVJ7gQB2_ZjM2BhmvMcF-t0iH2ZxI_pzG_HJwchrV-fX4wcD9KfypwOgRppF5jIKku0pP4u7YKUtIYUhBVxuzt72SlzjVfNe8TVW522lQoHcin9jajwY5kRfH-gGPzRmWKVT1Mvjpla3Vi3fPEdzcm1R81rvp0K2wtLganzmVjwn4hCWf01y1Ta5oVLBMuLGb2B-wzFnBnYPUbjESvkAp480ZHG6eJXnittaicloKJXq6UamM4k5-XZ4PLjKpbmy6BAOHvwVIFuQEbUE1C4j3pbnpvk9jaAmAYizGMrdqIFV67p7GLTXHQe9JAWN47k3-S9HoVLUo4PHC54TP9621pxUbK86F-4Ye3F5ufY-MBA-QUo_smrugsMkFqO7uktKfrd0eNtjFTw8XFX3t5jF1tLvg8Fxkp_m_F2bKLcVJVBlsO_TV1NqDWkF5h2QzwlgnGg4ITm_pGG0_RJIezY9dV2cfses-br9EItp8DgoVErLMLFdp7IjfF3TvrBAo7KV_inErP3WFjKeaII-F5rV9ySZrIn2bpSfFIou-a-z2Mhoud1Rk4pJNJJc0cAsrIE38-fkjUPpSSLue7ozc3YuLoU-41wgBBQ6r3f6WEcErK32RdgJVQKlpqCAX3PQtXwU33j4v51EmNzQqOin1Kq2VVvlBebrxRyWHlbxDlsY9orCJ-__NVvddwCzuNDrVwnk2COvD5pi6gO1LBpf7nuufyqvJNAT7AKZ_mBpGr5jxzzQgEXkJ3d8znpcKrw0300PloyoFrC01a27yVzqGzfxUijaK9Pqmy3L_cjDUYtw9Z0C5ZfA5GqK90PvzDn_hvTMCxEhz8crYvO3AneNxiYMjeUE25QCPMTv3yAhUsAruVWQwwsy3PCbAOx7gpcuyEtuFsWuQzkBaYi7TzINoNXLIX6hVZip4mH8tTSV9qHH4joP1KTmw8tB1nR2Wv73NmNpku7GVojzqSPWNAA-vD6BTbakAhJDdhYiVlaTFxX8GX4U1mlr0dQ-_YIYidkAp0_Sf0U-ybsYTYhTXj8Xl8lX-LQu-zsmp3BhIMlPcMLV3cBmMOxOs3XTlximmL8FUd-tIy80HJ_ycGrnLJeKpdxCA2YIPoRMLDTOjP7au4QvtGwimvHaHWHjVIOweAfWg14HXMIw47SATKZ_tpxZ42HDNPNWOgdzhxKBqvLVuz5Z0tyVohO_AstzBp9hEpgrzwSAWYtbcHkJV3fzpCeM7kq_uCXLVz_JsRusp-c14BQGUa339uomv2wtrI7e3riXlfXJB1QVKktuunOf8QLKG-c-WmiVTjx4RqkdcwWmqdKM_FgNYDEA1YgFEVUcuH5eSbynJWonp9O3AMfpExZ3h-JeYoplB5mnI2t_Xdu23Uf3AtHeSVen8iE1-wrpvZVrjr6n7ZTiQWFR--9bHuIYNnoj_g1PzLc0y4Tnroadgri2ar8GptD5BRMU27HN48f4NIdZCa9YaigY2voTBT3TLtU5KfwF6bLQkWDw__SajgeG8KHuusPOaFUIv9Oz1W1yiH5zNm0ccuXN2_MXbhSEE1ECqOS4bWaxldyS6RbsEyVXVrq4grAGZGgw0WIOLcnz6RWOH-YMQir84h_VKKWbfnI_9EL8YiYm4398eHLF9IvF38dR-YzGqLeVHOJMtmR3fhkPFjQy9SMpTnt5Yqg39DfmwxcuV8ncV0hjLM7EMDcHqY9hxaqYf-meqwUJNncYd2sQ1SHPpoi_PLasyUQTGcvHMtEWJkrnQW-fpmM1x48tTzySEoXVQKxbJzsnsaNtqrBMtJTWT67R4ZTKiN0x-cLw66mqPOv6J-JCE7Vhs26IVRaK7FBCAAJphyxW4VRxewKqIdqFCNvD3mcKfp0F2moQlNrIC-TaqU-yZbbbnHDaAchWDQZRUiwzvKByZmFgj7GvVx_4XPgpDddWTaraS0s6X2GMk5Wpqy2iWO2CZlmNHWgbaH5Pa64CzB69-ReGEqw0ixbpW7rYKSTza3oXppzll4W9gj7g9vTkZzd6UhO0hYb2e9ypHznnMvesvJdKEji93T3aJqMBFO_VUOmLvgUxgwyzNFvvNQdsLq--jym8AQkrZWWluggxHPmrgqK-HTv9RVhmXSUFLy7xiFOvZiyLash6QfCIu3HHFRmmGHJ0acK_oInmlQkG89h3NvsPB_VTbBokXYMdJkLzX93j4Ly8JzTB1k_y8wvVr3tTuH9I_-FbsjqYqNhjOKolOQJINb3TBNg6PMYYOPXeCXFIaFUdTEbHlfE4enTPQAFiJSBfKUOYgxM1Zdti0phT6ky4cVx-_mZukjGCERfSoN9WgvhsvS8oMUwW9XdWx9L0qAvpPYuvjeNhE9fGcCOI7QOPRhTsW25MPP-96rk1p1spNjzvqrwfjaejhfn8JgVpNFNWSa4OD1s_U5FjeFyamHut0MgfEGD02znr9GlVW7_A5wgAOoquJ9YB8adjaMbaTl9CPdMDdLkkB1-Q0xx3pj3c7Vzeuu3v3nG_thPl8DW2Yl7KAbPY6TrNbxLdR5oVgE2V9KA80yUXIuSsh7281oz8o8PX39qgl9HAGm5tL0I5ouSZtyHokcKdZrkAT1iWmY6aG9LNx3mU3wrDgVVKj8lsHKpy2z3RlZxknpNld5gBBN7TeGbnZq5k0FlnCJ1RCeMqG32n10CfQPSSNEJ56onwpWXI0Xi0GxLHlisSsmQYGkhmCjuXFDlrsLq-JXyzdEK99kHGFh4R4U71BixWkdTrGTCxw1GbViTIfhIKH8YeRpJ2JD2UNq1_3yKZ5Kv5pXkKPFoCYKwY1RcxejBP8cgoovZe2SlnSBOaQF4qjyn68smoFcfuW3oO9peC_rnM_YcL_wz5J5r2SH0KPCm_l-cZyt5KwSsx5DWwo8-_DJRUYnGUVlXH28iZ7zkiMEP7BT63iXmEr4abj7NfQrCU1wYNQ7p-00zlLInIrVQyf0kYDuHdHy_uiVbCMaj-NpuhcYV3NdUzINOd65_68BibEf0-iz-_YgnUYcS25ZyQZxlzrCQLJSFCFp2d47JILC2lZekpGPRZhlczYVwwI8_NsSCAy_cdNEFk15pPfVAKYY4zjjLy5SSPQJjzh4zB7UXfQjUVydbaOkEiA9QzA0zMhhXrnBlgELOIRuFmE_1GqtBFVxLz-VtxsSVelQhVqP3hh8AfR7cfzkRWw2dHE-DS3gsEmtVupl_B04cBbsTDZTUMhcuEW_rZkqZII9Gp07HuUXRE-Vkk7-JQuKiln0keMlizCUB4XGbH0t_l_IZUgoBCeBal0fEAU5DTAPPOy8Vs3lemncVqKP9fEbH7vWlYDuFMdKHBROWBC87AWsjVsl-gMKPCg45fz84O8TeptqiYRLIKHukukAaf53H8LK5YnfjbAr_qIqEoe94sTMZPuvR00bpJxezeEisD2IB1j2ZfeBHu6l4VD2QN6Vuu74p1Ud8CTEB1WZ9W1qEwbMbejryG4wDaMbSpgCuowmauzva4y__HtlvuCciVOt2ipH5h-WUN5KTb_FzEptBIJlwzigbPRNGqPzcAdBY4Klz66G1AK_n6ZptFr5qvQWzk2lpbp-w9HbKesDQgDo94htVvNlfybGPWrgv5F-TkVwHMtdh__Fl5AnKA8s_W5sYDMfXrv9XICdF-Szw_hp1QE_C9sgf3Aeubtr6mHSDZd2zwZ7-q8lrvEtlsglc6hPAjvpH1FvVfdCzAKXL6DKAYksKwR5-xRYtOOEjQwjwgVZTsVDC3mn0lSlCeMaj4Rg_dB1MkLXmoZGnkpX51z4YlWPcrvGRachBa3HIMMcspXP8urhwCvXbmqf5IT2o8hwlgUpPsVxDsY8Awb7odqVAs9svAil_l-dzgQU8vKMQbr93BlvSVAplgD_ovFL6GW8vQdJ704W7deqVKlmWJVpQO1GpcQw1BtXmZuVl5emODZVA7rei7Ccg + type: encrypted_content + id: amsg_01a1686b-2e93-7943-8ad3-9794a6eaedc2 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-a9a3f9406d5895cb + id: maco_01a1686b-2e93-7943-8ad3-97b16d5b741f + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The security review is complete. Now I need to wait for the other + two agents (correctness_review and missing_tests) to finish. + + ' + type: reasoning_text + id: rs_beb22deb778120fb + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: Security review is in. Waiting for the other two agents to complete. + type: output_text + id: msg_a619e1b0bc4a3674 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-ab98420abeedd69c + id: mac_01a1686b-3990-7ca1-bba3-1303431948c2 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + content: + - text: "The user wants me to review the Python code for correctness issues\ + \ only. Let me analyze the code carefully:\n\n```python\n1: import pickle\n\ + 2: CACHE = {}\n3: def load_profile(user_id, payload):\n4: if user_id\ + \ not in CACHE:\n5: CACHE[user_id] = pickle.loads(payload)\n6:\ + \ return CACHE[user_id]\n```\n\nContext: Called with `payload` bytes\ + \ from an HTTP request. `user_id` identifies which profile to cache.\n\ + \nLet me think through each focus area:\n\n1. **Logic bugs (race conditions,\ + \ TOCTOU, incorrect caching semantics)**\n\n - **TOCTOU / Race condition\ + \ on lines 4-5**: The check `if user_id not in CACHE` and the assignment\ + \ `CACHE[user_id] = pickle.loads(payload)` are not atomic. In a multithreaded\ + \ environment, two threads could both see `user_id not in CACHE` as True,\ + \ both call `pickle.loads(payload)`, and both assign to `CACHE[user_id]`.\ + \ This is a classic check-then-act race. While the final value might be\ + \ consistent (both write the same or different values), it means `pickle.loads`\ + \ could be called redundantly, and if the payloads differ (which they\ + \ can since they come from different HTTP requests), the result is non-deterministic.\n\ + \ \n - **Stale cache / incorrect caching semantics (lines 4-5)**:\ + \ The cache is \"set-and-never-update.\" Once a `user_id` is in `CACHE`,\ + \ subsequent calls with a different (updated) `payload` for the same `user_id`\ + \ will silently ignore the new payload and return the stale cached value.\ + \ This means if a user's profile is updated, the cache will serve the\ + \ old profile forever (until process restart). This is a behavioral correctness\ + \ issue — the function's contract is ambiguous but the behavior is clearly\ + \ \"first write wins, forever.\"\n\n2. **Thread-safety issues with the\ + \ global CACHE dict**\n\n - **Unprotected read-modify-write on the global\ + \ dict**: Python's GIL makes individual dict operations (like `__setitem__`\ + \ or `__contains__`) atomic at the bytecode level, but the compound operation\ + \ on lines 4-5 (check + set) is not atomic. In CPython, `if user_id not\ + \ in CACHE` compiles to a `CONTAINS_OP` followed by a conditional jump,\ + \ and `CACHE[user_id] = ...` is a separate `STORE_SUBSCR`. Between these,\ + \ another thread can interleave.\n \n - Also, `return CACHE[user_id]`\ + \ on line 6 — if another thread deletes the key between the check on line\ + \ 4 and the return on line 6, you'd get a `KeyError`. Though in this code\ + \ there's no deletion, it's worth noting the pattern is fragile.\n\n \ + \ - Actually, since there's no deletion in this code, the `KeyError`\ + \ on line 6 is not an immediate issue. But the race on lines 4-5 is real.\n\ + \n3. **Behavioral correctness (stale data)**\n\n - As noted above, once\ + \ cached, the value is never invalidated or updated. If an HTTP client\ + \ sends an updated profile for the same `user_id`, the new payload is\ + \ silently discarded. This is a correctness issue if the intent is to\ + \ reflect the latest profile.\n\n4. **Error handling gaps**\n\n - **Malformed\ + \ pickle data (line 5)**: `pickle.loads(payload)` can raise `pickle.UnpicklingError`,\ + \ `EOFError`, `ValueError`, or other exceptions if `payload` is malformed.\ + \ There's no try/except, so the function will propagate the exception\ + \ to the caller. Depending on the HTTP framework, this could result in\ + \ a 500 error. More subtly, if the unpickling partially succeeds before\ + \ failing, no state is corrupted (the CACHE assignment hasn't happened\ + \ yet), but the lack of handling means the caller must be prepared for\ + \ arbitrary exceptions.\n \n - **Unexpected types (line 5)**: `pickle.loads`\ + \ can return any Python object, not just a dict. If a malicious or buggy\ + \ client sends a payload that unpickles to a string, integer, or `None`,\ + \ then `CACHE[user_id]` will hold that type. Subsequent code that expects\ + \ a dict (e.g., `profile['name']`) will fail with a `TypeError`. There's\ + \ no type validation.\n \n - **`payload` could be `None` or non-bytes**:\ + \ If `payload` is `None` (e.g., an empty request body not properly handled\ + \ upstream), `pickle.loads(None)` raises `TypeError`. No validation of\ + \ input type.\n\n5. **Resource/leak issues that affect correctness**\n\ + \n - **Unbounded growth of CACHE (line 2)**: The global `CACHE` dict\ + \ grows without bound. Every unique `user_id` seen adds an entry that\ + \ is never evicted. In a long-running server, this is a memory leak. More\ + \ than a resource issue, this can affect correctness indirectly: if the\ + \ system is serving profiles for many users, memory pressure could cause\ + \ OOM kills, which is a liveness issue. But strictly as a correctness\ + \ issue in the caching semantics: the cache never expires, so it's both\ + \ a leak and a staleness problem.\n\nLet me structure these findings clearly.\n" + type: reasoning_text + id: rs_a510afee9ec596ea + summary: [] + type: reasoning + - agent: + agent_name: /root/correctness_review + content: + - annotations: [] + text: "\n\n## Correctness Review: `cache.py`\n\n### 1. TOCTOU / Check-Then-Act\ + \ Race (Lines 4–5)\n\n**Problem:** The membership test on line 4 and the\ + \ assignment on line 5 are not atomic. Under concurrent requests for the\ + \ same `user_id`, two threads can both observe `user_id not in CACHE`\ + \ as `True`, both deserialize, and both write. The final cached value\ + \ is non-deterministic if the two payloads differ.\n\n**Fix:** Use a lock,\ + \ or a thread-safe cache primitive (e.g., `threading.Lock` around the\ + \ check-and-set, or a `collections.defaultdict` + `Lock`). At minimum:\n\ + \n```python\nimport threading\n_CACHE_LOCK = threading.Lock()\n\ndef load_profile(user_id,\ + \ payload):\n with _CACHE_LOCK:\n if user_id not in CACHE:\n\ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\n---\n\n### 2. Stale / Never-Invalidated Cache (Lines 4–5)\n\n**Problem:**\ + \ The cache is \"first write wins, forever.\" Once a `user_id` is present,\ + \ any subsequent call with an updated `payload` is silently ignored and\ + \ the original (stale) value is returned. There is no TTL, no version\ + \ check, and no invalidation path. If the intent is to reflect the user's\ + \ current profile, this is incorrect.\n\n**Fix:** Either (a) add a TTL\ + \ / max-age and evict expired entries before the lookup, or (b) accept\ + \ an explicit `version` or `updated_at` field and re-cache when it changes,\ + \ or (c) rename the function and document that it intentionally returns\ + \ the *first* payload seen per key.\n\n---\n\n### 3. No Validation of\ + \ Deserialized Type (Line 5)\n\n**Problem:** `pickle.loads(payload)` can\ + \ return *any* Python object (int, str, list, `None`, …). The cache will\ + \ store whatever type is produced. Downstream code that assumes a dict\ + \ (e.g., `profile[\"name\"]`) will raise `TypeError`. The function gives\ + \ no signal that the cached value is not the expected shape.\n\n**Fix:**\ + \ Validate after deserialization:\n\n```python\nobj = pickle.loads(payload)\n\ + if not isinstance(obj, dict):\n raise ValueError(f\"Expected dict payload,\ + \ got {type(obj).__name__}\")\nCACHE[user_id] = obj\n```\n\n---\n\n###\ + \ 4. Unhandled Exceptions on Malformed / Wrong-Type Input (Line 5)\n\n\ + **Problem:** If `payload` is not valid pickle bytes, `pickle.loads` raises\ + \ `pickle.UnpicklingError`, `EOFError`, or `ValueError`. If `payload`\ + \ is `None` or a `str`, it raises `TypeError`. None of these are caught,\ + \ so a single bad request can produce an unhandled 500 (or worse, depending\ + \ on the framework). There is also no guard that `payload` is actually\ + \ `bytes`.\n\n**Fix:** Add input-type validation and a narrow `try/except`\ + \ around the deserialization:\n\n```python\nif not isinstance(payload,\ + \ (bytes, bytearray, memoryview)):\n raise TypeError(\"payload must\ + \ be bytes\")\ntry:\n obj = pickle.loads(payload)\nexcept (pickle.UnpicklingError,\ + \ EOFError, ValueError) as e:\n raise ValueError(f\"Malformed payload:\ + \ {e}\")\n```\n\n---\n\n### 5. Unbounded Cache Growth (Line 2)\n\n**Problem:**\ + \ `CACHE` is a plain dict with no eviction policy. Every distinct `user_id`\ + \ observed in any request adds an entry that persists for the lifetime\ + \ of the process. In a long-running server handling many users, this is\ + \ an unbounded memory leak that will eventually cause OOM, killing the\ + \ process and losing all in-flight state.\n\n**Fix:** Use a bounded cache\ + \ such as `functools.lru_cache` (if the function can be made cacheable)\ + \ or a third-party `LRUCache` / `cachetools.TTLCache` with a max size.\ + \ Alternatively, add a background task that evicts entries older than\ + \ a threshold.\n\n---\n\n### 6. Mutable Cache Value Returned by Reference\ + \ (Line 6)\n\n**Problem:** The function returns the actual dict object\ + \ stored in `CACHE`. Any caller that mutates the returned object (e.g.,\ + \ `profile[\"bio\"] = \"...\"`) silently corrupts the cached state for\ + \ all subsequent callers. This is a data-integrity / correctness bug in\ + \ any multi-caller context.\n\n**Fix:** Return a copy:\n\n```python\n\ + import copy\nreturn copy.deepcopy(CACHE[user_id])\n```\n\nor document\ + \ that the returned object is read-only and enforce that at the API boundary.\n\ + \n---\n\n**Summary table**\n\n| # | Title | Lines | Severity |\n|---|-------|-------|----------|\n\ + | 1 | TOCTOU race on check-then-set | 4–5 | High (concurrency) |\n| 2\ + \ | Stale / never-invalidated cache | 4–5 | High (correctness) |\n| 3\ + \ | No type validation on deserialized object | 5 | Medium |\n| 4 | Unhandled\ + \ exceptions on bad input | 5 | Medium |\n| 5 | Unbounded cache growth\ + \ | 2 | Medium (liveness) |\n| 6 | Mutable value returned by reference\ + \ | 6 | Medium (data integrity) |" + type: output_text + id: msg_a5eeb577ad29b303 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/correctness_review + content: + - encrypted_content: enc_rLcUyEe-lvNU9G2eM7l4oVbuz4mJI6IWIS_tcmMwToXOw7NMk11heuLhlVv7Ejh5Ksb1EBpClg1kuy7FPO3EufcN_Kg7JCutNcInp5_iAmsBXtUN8e3ykSi2cRsl7U-1TT23Wpg4wUz8PWdFnFxF-kL0ofr_FvFo_6_C1dQqptl7bR_TWz6DrIFzGj6UY97Z81va7VCpH_oCB4h-FPpgFtbFoWboy-M4qMqnsaDQijl8jpQ-Y1Jg4P9WX5nhym_7wCUOf02UywKJDGe4AVRAFqe-FpqrvaLC5tpf5X2zmlml-9S2FGszfDtVG9fKFb30OFXx0U6rEa96bVVvF8fdFLjCyU0c7k9kwrxACSKFXGcfG4QdmnOyi8EQ4lj4OIETrPbcNuH-w0RAxGBygR9yhRdWZbJ9ADj8l-w8LF8xqcqoPOfCV09rA02b4lEScqRbtuP9POZllUAUbB9ifbHIJ1jF2WJZFm1uGiGfYhKHdAQfvdtOy6OUiga3PefLHo6SXBoFeKsvWIdCqLyxpjmTucPLkovB5N4NL2K1ynwLJEiQfFL2AM7GzNZ1-wqmSr4NaOUs6qUb8JGfSFA3c88fe226Q29kx9tR6W3MW7FDCXrIZBWSl7XFhAyzK0zBANKfeyHCV4hKCVn8SiI6Ld_oJI9H_gZrJELJMPHBAj4vljwsvmo6cGWZx1x9_nIJwRG8Rd2STnwdC-qnevrvOn1e-GY1_UvjGOfv7HVDo_Re5JzWFVfr4PnknPqZhdCROnp_PLtXIurcn8F5XVM5l78cU1psZYLxhsO-2v2ukIbQiux0hQU8a1tYf0ZJoQ-rbTgRx_elLahg7fL1GRgkQwA2mox6Gb2ZsxuWF5d8Qwp_OKekEWlDt9UBbH5euiwuXB1lVSQ1HGy5KexIPG_4zvXFxB2selqRKmz6huoFoMywLqkGDyJtA109y2Y1ZnwXwXzDL7asOKzNsK2hBaRSS0CX_WlEcBgsepsL0J-ebqkFv6M6YNnvWBWh7wj5d5u3D_dUHuKwwA-c9Ge96tcUXO-Hgo2XzSHP4TJLrO250GFPdQsC-P4u09OybPihgChvrhCyDhnh22cTmAV5Cfu9Nc4hXXFIqkGMp6SAZRnse9bHsHNTyC3QJ2v88sADC7Pbkf-p9e3CGtz-5SvyoQ8ZrGnTdeA0FsRO9-kz_7Z459O3V1okhIbffqKQ0uOzQiHTCyoONyTYdf1gPBC4bcIBFBs0tWTwPajlqmT7tSVx0diNTLnifAeeroj3JnSk1PRlM3YkmgkAs2HlJAsS18E8PU5eaZ0T6GmQQZZyAUNyKAYWjDc5mstMuDEGbCiOYK1EqY2NkUHA3hqZo0ehHOCS7_LuBP9_OjKJCCH0zsicOlvuR32FJetFjCJcgenBVYfMszs2SPqYg1d0kl8gy0SSYHFUQITQla3mwAjjjckDxh2v2wNwPdPcaHqlj7SWyW7IKaCG7iysfj46TCb_Pb03eih7Zvj9s6lPHOKJZ195I-aSYqCfg58WwpwvhzPz6QaObUswt4JLVlNhOKYxAMqhHh-DGaYLxHvGYBrgVnjQtrA9OLlZVkradHq1exqZZXJtZELfjlqnuJo16RhuYu0K5dqZ47cF0il-TSdYzpBRJLwOEahwSmyfJOABneDaTbzZDKyd1EgJ2jUg-t_sl2bPQa5RMteqpat4Lq9eD5hegUF2xQzPGS0iQBfqgOZGBxB_N6yxYlw_HVhi4VCYdhm3QdvX_3d5YaXmsxHTVg-fHvSE3qfXYgMyS8w6Q45OnfFIOFHDXASe5n5JD8HkuL6bU05FxqIUH937TS_WiqCabtGiyhuEIWPyMmg5pX3DhvCmcthglQM3jOp1kDhfUOebgeUt0dSbR0pKNiZ6ntDc-dP2nnMuUdCzGk_kAqrQmOHXk-jlpaVAZoMylM7XQN3IosGzxcIuKrurkqeOWRTWzntBfQbVl4AE7muiP6gQLzuU24pvq32onCuQkR7QKnE7_kKkowycGcU30GR0kDgPQExhiVMMIxWQkjBqnuPHj_Jx_scMY_v5m8bTfUGUzF6tj48lKR05QPKaTKXAFEk0tvFaxOWNcIv56DAi9KTuG5qfsFJPrDbrb5fKQcKqJ6eQXTIgx7VNd-y9fKg0T--rEzbxtrJ-88CPWz_6EI3Qmjlb697_nL1v1A2sBxXPkN_RF4-2wcm4leovLW0HYrD3_2uDFjH_n0Xi3UiymnOoiQpIPqQSXK922_0NfpEr5bFuNOkfOywT5_igVXWOiOXk7OZ3x4GQVbH2WRLg1mjqu7YmqbnyAIUaGiWhYnIHJNOWL348DhkkiTvg_i1gOBIbTu1MflPmaQbPYDWDWMm8YRA29ehzfM15VSoU99FrScogSwlwTJxEq3B4jYUnmQ8V3C6PS48m-_HLwOGr2K3TFk9Yuje6nTetotL8nIzxmkoD0KVwZvgCzQCh3NJNuxt-Cz9HLy21CBouMoFbqRIEe2z8gk5rH_DqdnP0mHtw-FT5lfJ2zBRwEjoGcuSRySdZUg-sqQoPwug9_3vIaIEczCKZ4G8WavYmPP8ZQMvuRCL9TIVzsfeTR1JG0zjtxokqCeQ4arV_e3tw0q9qDBtFjjHk4nCYprMyIPcwDV0MVr5OUPQrR3xnk1wPyOkOl7wxyJz-fYBku_JDnDM6D5619MOBo6EnxxJuGOOnsVijgS-rLkt299ep5cQrXuqGRhcbtQnZBB_Ad6QYIpV-zvsZaxBRyEzO94thOFsEbI73ISs-iWr1SkedVJ4Y6yLa9KaVLKDAHyQlCU8vA9xnKcHKEmg_3EhNfzXhKEE2gyGfx_uslhSJO7peYPeCForDQhFA-IYuIg6XzygAwxtIKEoi5FpIr9Sn91Z9O1IShqGSd0-7LbCF6T5Nu1cfqA_sPfcPm9sVb7YHmRF4YvWbdedbU5HpPFwpNZlIYo6oAD3wd5VochrhFe6IPLi-6FVFqXgOHcs5s0M57dB-7RGNrMWP5iBGpkatDbg6PC9nfnX-5vCUsEnc5CPb2ndv5YR9KbSPwRTJ4rXjtrGRsson_vDyFyEq76QRxWg-VOS03D_FQCjK9N15B-PkctR5FlKqm1UiRV_ErrMJUtXgDqF7hkzIy6nXDm5jsxEG9IJ4RqmqmwywUyiwatzaLGsthFoHRwslNmnZaouVNee83ou5qKJjZ4Yz5uD_S66vNaBl7Foj3CPDtLLpy4Copm78uYvUAUrBTc-LkBw-MlZDeXxcc3fDiLXWPIFu9Cn0ND2XrG6EPc1oOsCSbLCOUD_mD8aSDQ9qSEsKCZRyGvYzf0d3G36B8CFpAaUT3bBIeuzsDXv94lcCAUfMtHSvjFxEHw5CFEYIvU4mGrFvhq69bTAEo9pqiCdP0E1I-by26T09ZKiGSR34TN5I4yyYpUvvhzq7u03vofWLXHl0-VYTNpUBipfkjfj5FNkF50rUWPSZZUzYhvhiCi1QZXBz82c95TaaSrD8b6vbUkSSdaJXdZTI6KzUmwRkHMqZEvz_X5n0C0uaLHpIjQONXxcSzMMwdUZ192eOuAu0oGD8KIczEItmXSr8mbiAlbF_58QTNm725J3QAk7VP4sfw6BGLVGkilGJdWyCLIZEL4xuR8v3aMO6JWU1t-YjQFbMtmiHpW2tQ2R6oaFIM3Wv-ouO5p5b_0OWcsL0ewGeUbj92HBYpxUcBwCRx62Y2JlIeHxHQOQAXIokxNaN6vHCZdRCRfuutrwRKMdsZt9ze_hJUPztUhz0Mn9l-XnK7sTDil0zS5ZzbM1kPG4Nmvm1gPgxI0mvFBE1C3snsMjZish6fIUBgXANA3t1dVy6v4WI6Obi_wx9g8Xys9lHy_Mf-jNCR7bFbojWjzrsmLvhnzVfnITWUAB1W2R5FpGBfd7_NnixwL4x6IBu5dNA2a0sVzTlt2vMmWD-oN1jpWgYDvcednNnCsg8zjpZLMtQjDpkamw_CMk3LIXhHMyloYIrGF1HwRVnQtcZ249WaRn6y2a0usr_z6HxwOwHhZEDMIHOKvrxJ_PreLyQZAR9Zfw2JiqHMyhqJhCT3AlGxZkGbAhO7kO_jdGiayyXU5nipJI20yjgvcxRH-Akn-rpMqZrkFVPTX9Bcu-GTH9OwYB-piFXieKmKdWy8-gG-kBjb0CteR4quph6pCDhhLqz2Km12XyHM1huiwtDSxarJlGQCandl4t-K_Sr7gO_LUPeBkl-Iw8Yme4YBiXAAg8H11bZi2LOpbwjoqgnGx7If-TE5ooWe5rVgkN39M2wbhE-0vQXJuFPe6O9yOC3iu50ZsDgsIPb0rqzhGSVKqihgBQ8xFRt22a4QP5NsxnNESlzIJqRhEXZtmQM6O1xotL8SlyVEPNOJDSGO7FHGdeiDaFjGe1aGB4WovDYGPUvDKlxA4fmB6n9AygbmTcByOtC4PDJKWYpW6x9R9u04Z240IJhxcqzRb61i1Sh0eQnbPYYNnvaqRUz6fnGn0FL_1wE7dDTxlgtUinavPOItYtT07lF7QHDqAP5XIIF0iWu2V6WrnRU3RbV4Z1ze9VkKB2okzfzB66sJF8Fm56KV5cK6vpHThVUt6F7VGBU6zAJQ9KwYMyug3qCRaVB62FYIl7RqTyhVPnJnJ6oF5PribB_evSYwzJgTc1Cv0yIz05Cts7KuftHijcxnz61z9AT8GuWHoX46rv3wSvVQFaQpJN2DIeaEHk-gxiDPMxjO-dff1204G-DScNprr2w8ygysObaR4yuZmvHXPgPPRIdyC0DuHgInD4-ubL35lnPkFQg0vhojqFXjvT1fsPGyG232JbhxU8YoW5B_sK52DeVhwBKooCIY1-pCzgnmXXa5xqHoBB9acz8SouvgfuDDmzBzh3df7Y19XBB_OrLET49LFTQnRHAbH2mnzQCQGY97stP2Y2Boj6wQuiV0QqaFYZg04agsUHWoP6p0vn0JFrejYZ7UxpLFHiGXUI0_-0DBWBbpn_mq3PpeNniDKrtXUo1RqjYfYUXV0DesgU2H7sbk8kKiZpss3NsDe6JCG2Kp8hdoSqFHaqKKYfLrGZyGkmYlNvVcUNNgSCIof8YCSsigfCKpE6CKRUgEMCcuVbX0jF4eNmuxpVgPzi4BJMmKdrNCIwTeUNUwC2COHcnj5tx306WeSkMtaDurEtflqCQ-zmML2E3vPZyy2bxFNdMdduHvj4DSJ4IRhX3MBpGiPHlmnfrvLXc6me3LVcJGtd35XKe9SHs8CLScS_cTaZweOolqgxPJsHRga8Bzp2oCyTuLQiWMV4Hoqt2eeUpA3etq7IqNJRyvRiI6oV1pmKzQqMGYw19srueo0oPoAvgsrqwRtQESXbjJ1NOnuCt7cxvSICyvy-xDLAS89CE8lVkqqEfvKb0VAsZUiRH_PpJSaRQJbAye4JrVo5OzOWaoTnVBOntR3NndoupF8IZGUZt6h44ibNtl7fQ3yz0o7a_qf6hGepSvN98ehmBCZPU8y6N0Xmu7AygkEUsD_MpyaxVyRgqcryDRsiRStPnDZ4_fVTUPZIk7YU3fzocr6B51DSuotWORYc9hsdzg4vN-XKgxqCO9IjlBxQw58rLYlRqr4X0i2u2Z0LU_hyQRUl27S7ieU_ca5Xr53odTqSf1-mVDPojATfmuUG2PJIwim4JrDaNopTWGt_zVjd2NCnJfXVZUXErl_DSVGYXtunCRTbioJKycFhKjelKcUrc7WG_W2_8KblMKVOqsdgtGMFd89hdYV9uD1q7aPMDTKLWghYgBLm83zDH3k1242mhqTDZLXrFZps-HWSgphVxeMdDcGqtMZGgCa7A4_o9_OZ235Z6BmjR1yTqKVpHzxpQ950epPTfLhnjeMj2bvh7PQ7-wWN7fFJ3TilB9jeG4Tx7gtONz0yEob2Okq4HMr6DeIOrFtX0WFEWVl12ly_em74c5xoOIB2Zgo2_CxYaSdYBkwahgLEmd-edTsO-PF_ukPMOqUMYWASfdg + type: encrypted_content + id: amsg_01a1686b-5282-7a32-9b23-c75d789a643a + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-ab98420abeedd69c + id: maco_01a1686b-5282-7a32-9b23-c7760c17d458 + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'Two of the three reviews are in. I''m still waiting for the missing_tests + agent to complete. Let me wait for it. + + ' + type: reasoning_text + id: rs_aad46c74173698ab + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: Two reviews complete. Waiting for the missing tests review. + type: output_text + id: msg_969d0d12e7a47035 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-afc49a7b2cfc2b4c + id: mac_01a1686b-5ef2-7421-9cb8-d9284a360863 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + content: + - text: 'I can''t spawn another agent because the slot is full. But my assignment + IS to do the missing tests review myself — I''m `/root/missing_tests`. + Let me just complete the work directly and return my findings. + + ' + type: reasoning_text + id: rs_abe99740748e3d46 + summary: [] + type: reasoning + - agent: + agent_name: /root/missing_tests + content: + - annotations: [] + text: ' + + + I''m `/root/missing_tests` myself — I don''t need to spawn another agent. + Let me complete this assignment directly and return my findings. + + + Here is my full review of missing tests for `cache.py`: + + + --- + + + ## Missing Test Report — `cache.py` + + + ### 1. Caching Semantics + + + **1a. `test_second_call_returns_cached_value_not_reloaded`** + + - **Input:** Call `load_profile("alice", pickle.dumps({"name": "Alice", + "age": 30}))` twice. + + - **Verify:** The second call returns the same object (identity check: + `assert result1 is result2`). Also verify that if `pickle.loads` were + instrumented (e.g., via a spy or by passing a payload that would raise + on a second decode), it is not re-invoked. + + - **Why it matters:** Confirms the cache actually caches. A regression + that accidentally re-deserializes on every call (e.g., a logic inversion) + would be caught. + + + **1b. `test_first_write_wins_subsequent_payload_ignored`** + + - **Input:** Call `load_profile("bob", pickle.dumps({"role": "admin"}))`, + then call `load_profile("bob", pickle.dumps({"role": "viewer"}))`. + + - **Verify:** The second call returns `{"role": "admin"}`, not `{"role": + "viewer"}`. + + - **Why it matters:** Documents and locks in the "first writer wins" caching + contract. A regression that overwrites existing cache entries (e.g., switching + to an unconditional assignment) would silently allow one request to overwrite + another user''s cached profile. + + + **1c. `test_different_users_get_independent_entries`** + + - **Input:** `load_profile("alice", pickle.dumps({"a": 1}))` and `load_profile("bob", + pickle.dumps({"b": 2}))`. + + - **Verify:** `CACHE["alice"] == {"a": 1}` and `CACHE["bob"] == {"b": + 2}`; mutating one does not affect the other. + + - **Why it matters:** Catches a regression where the cache key is accidentally + ignored (e.g., a single global slot) or where entries are aliased. + + + **1d. `test_cache_grows_without_bound`** + + - **Input:** Call `load_profile` with N unique user_ids (e.g., 10,000). + + - **Verify:** `len(CACHE) == N` after all calls. (This is a "documents + current behavior" test — it would fail if a TTL or eviction mechanism + were added.) + + - **Why it matters:** Pins the memory-leak behavior so that a future regression + or intentional fix to add eviction is a conscious, visible change rather + than an accidental side-effect of a refactor. + + + --- + + + ### 2. Edge Cases / Input Validation + + + **2a. `test_empty_user_id`** + + - **Input:** `load_profile("", pickle.dumps({"x": 1}))`. + + - **Verify:** Returns `{"x": 1}` and `CACHE[""]` exists. + + - **Why it matters:** An empty string is a valid dict key in Python but + may indicate a missing or malformed user identifier upstream. Catches + an accidental `if not user_id` guard that would break valid-but-empty + keys, or conversely documents that no such guard exists. + + + **2b. `test_non_string_user_id`** + + - **Input:** `load_profile(12345, pickle.dumps({"x": 1}))` (integer key). + + - **Verify:** Returns `{"x": 1}` and `CACHE[12345]` exists. A subsequent + call with `load_profile("12345", ...)` should create a *separate* entry. + + - **Why it matters:** If the upstream layer ever coerces user IDs to strings, + an int-vs-str mismatch would silently create duplicate cache entries and + break the "one profile per user" invariant. + + + **2c. `test_empty_payload`** + + - **Input:** `load_profile("carol", b"")`. + + - **Verify:** Raises `pickle.UnpicklingError` (or `EOFError`). The cache + entry for "carol" should NOT be created (i.e., `"carol" not in CACHE` + after the exception). + + - **Why it matters:** Confirms that a failed deserialization does not + pollute the cache. A regression that assigns `CACHE[user_id]` before the + `pickle.loads` call succeeds (e.g., refactoring to pre-allocate) would + leave a `None` or partial entry that poisons all future requests for that + user. + + + **2d. `test_payload_deserializes_to_none`** + + - **Input:** `load_profile("dave", pickle.dumps(None))`. + + - **Verify:** Returns `None`. `CACHE["dave"] is None`. + + - **Why it matters:** If downstream code assumes the result is always + a dict (e.g., calls `result["key"]`), a `None` entry causes an `AttributeError`. + This test documents that the function does not validate the deserialized + type. + + + **2e. `test_payload_deserializes_to_non_dict`** + + - **Input:** `load_profile("eve", pickle.dumps([1, 2, 3]))` (a list, not + a dict). + + - **Verify:** Returns `[1, 2, 3]`. + + - **Why it matters:** The docstring/context says the result is a "mutable + dict," but the function enforces no type. This test documents the absence + of a type contract and would catch a regression that adds one (or the + lack thereof, if downstream code relies on dict behavior). + + + **2f. `test_payload_deserializes_to_empty_dict`** + + - **Input:** `load_profile("frank", pickle.dumps({}))`. + + - **Verify:** Returns `{}`. Subsequent call with same user_id returns + the same empty dict object (identity). + + - **Why it matters:** An empty dict is falsy in Python. A regression that + uses `if not CACHE.get(user_id)` instead of `if user_id not in CACHE` + would re-deserialize on every call for empty profiles, breaking the cache + contract. + + + **2g. `test_none_payload`** + + - **Input:** `load_profile("grace", None)`. + + - **Verify:** Raises `TypeError` (or similar). No cache entry created. + + - **Why it matters:** Documents that the function does not guard against + `None` input. A regression that adds a truthiness check (`if payload:`) + would silently skip caching for `None` without raising — a different (and + possibly worse) failure mode. + + + **2h. `test_string_payload_instead_of_bytes`** + + - **Input:** `load_profile("heidi", "some_string")`. + + - **Verify:** Raises `TypeError` (pickle expects bytes). No cache entry + created. + + - **Why it matters:** In Python 3, passing a `str` to `pickle.loads` is + a type error. This test pins the expected exception type so a regression + that silently encodes the string would be caught. + + + --- + + + ### 3. Error Handling + + + **3a. `test_malformed_pickle_raises`** + + - **Input:** `load_profile("ivan", b"\xff\xff\xff not real pickle")`. + + - **Verify:** Raises `pickle.UnpicklingError` (or a subclass). `"ivan" + not in CACHE` after the exception. + + - **Why it matters:** The most critical error-path test. Confirms that + a failed deserialization does NOT leave a partial or poison entry in the + cache. A TOCTOU-style refactor (e.g., setting `CACHE[user_id] = None` + before calling `pickle.loads`) would pass the happy path but fail this + test. + + + **3b. `test_pickle_raises_during_unpickling_of_nested_object`** + + - **Input:** A payload that is structurally valid pickle but whose `__reduce__` + or `__setstate__` raises during reconstruction. + + - **Verify:** The exception propagates. No cache entry is created. + + - **Why it matters:** Distinguishes "malformed bytes" from "valid pickle, + failing object." Both must leave the cache clean. + + + **3c. `test_exception_does_not_create_cache_entry`** + + - **Input:** Any payload that causes `pickle.loads` to raise (use a `mock.patch` + on `pickle.loads` to raise a custom exception). + + - **Verify:** After the exception, `user_id not in CACHE`. + + - **Why it matters:** This is the key invariant: the cache must be all-or-nothing + per entry. A refactor that wraps the assignment in a try/except and still + assigns would violate this. + + + --- + + + ### 4. Concurrency / Thread Safety + + + **4a. `test_concurrent_same_user_id_single_deserialization`** + + - **Input:** Launch N threads (e.g., 50) that all call `load_profile("shared", + payload)` simultaneously (use a barrier to synchronize the start). + + - **Verify:** All threads receive the same object (`all(r is results[0] + for r in results)`). `pickle.loads` is called exactly once (instrument + with a counter or spy). + + - **Why it matters:** The check-then-act pattern (`if user_id not in CACHE: + CACHE[user_id] = ...`) is a classic race. Without a lock, multiple threads + can pass the `not in` check, each call `pickle.loads`, and the last writer + wins. While the final value may be correct (same payload), the test catches + unintended redundant deserialization and, more importantly, would fail + if different payloads were in play (see 4b). + + + **4b. `test_con' + type: output_text + id: msg_a78a40af81c14eca + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/missing_tests + content: + - encrypted_content: enc_nxrnjVqdicjf0g9oDCODZMR1IPQ1vZcAu4veAL9hcZTaVmzkiB61dHneGx6WjHHpFwKGs1X7uw7w-w3Y-Tbe-PRyYZXsqNfojlr78vcpx76l7OzOvkBqnZzNmy7OfmNk_5pB6oUQsQRocZzMzjN4wGuASj_qIM3FU7rGWaI-gvmE6fJ166hXTP2WqJeaDMyZRu0SWA5Snj5Jg_2nmOGGKAl-iT1RfFUBN_NUuZmslamLpt8ElFDxRbyF0v7zTFQOIY1M2OGq7zRGu4B_7DblQYzfzLwTKMIuLQBOKH5Jjrb7iQcet-SwchgZqTtq9CoyG1SZqYOy_F1zHghK8I8Dy3-4qcXYSUCFwsEIAeTYX1YBbK5Zwsv19zD4ZZl38ELdy4PDvMxUtl-IjbRhv_eaEdKH8SryLABZVevr9jl4dHVi24mmEOqtONuwp9e8tpNbtZSWxa99IA-HhrdreDdHONjomKvrKAzHCvWaoDBLhVwVoV2NQCNQrhQ0AqV2hmqsguM76hQiqhzHZlYq059tefu6uypA5kViPJEJU5HOV0GRJ2280UfnPeosrgSKIA3XKk8vOoxI3qo9GxxbGva5-WTkazzz6s9V4L1NfNMRXlbRWwtbGROvb1L6sMqGzAsAb64_PdgrkpBRMlYvKIcDRkCDtIrkBQqrFw7RV1V_DHR1juHXZLXgpdW8V206tfYOPKBoWkP_sZ0rsvTCNBNAxtzROt3dKpeWq-7yTD_WB-7cIIim5isL3RN58M-jGu6BnRF0LrD_C6KhCIo17tQZYjURpKTmtOB__kJgcSbMoT28Sb4q9YVNpMDP87sGUWFP73xUFMKfZZ4JY5Cht3I-QPPPwuO82GiyiHnkg5-ORngEkKuDVaLFJZwsB98LGNgNv1j2bk7VQ447ChTW6k0YDh3tPYDj1fm4oc2KFcMaSbRJGXjkgzKMTtcEpWsY0nJULTkx4YXLDZognqw58ibQywbH-ROQ9dyPJujRrBcEufZqHUQgs3VBg9Ij4q6KY-MWuHWVqhc3tHO82pxKWotFLYLLm63bcsoZE9HUmq0ZdrU30PoCFiNO7hrjpqOjsEJuxxFfUxFqZH5X59GZYrbup-R4uDFjmvoRwD-FznQXJrpqEVkVTHkeT54lz3X6v5Z4BsTwLTZJJBSXc2aiecTvtuSUdiiPlVWWzaDzgdEki3kbMlu5ZDriCg2RQ2XoznzsjR7pIPKgx5cZU_4U9iJ-5sPpHVu5JrQSPqdle0z-xnFLnIl47PX7H-heRKMQG-hxp-pXFrcFtMPIQFINR4Ls5oiC-4skMPj2ilQTSaNFNVcRxehsPlau-jiW6R562uawkwCgv1YCxjUR9TmMqdjFGam9sij1aPf7qJZ54jKWrxpCLD4CzcbS8FYra0kGHSee08eiiMWCQFLWBgO6eGsGVpTnvmZu-GeS5R2H_uk4QsQW_S6t9PzoBNboUmIZzeG0cjdLZts2_UqA31eyhja0C5OUmtr_b60dTxDCzgmXPTmEYY7mL_FtLbh3A0rzcpQjs3r718qCKPmNY_iC_1f4kPWpkSEUxaGl_gzMFGHPIYDUdhdzPRlvLH7AYlL6qmisRQ8Pzew0af1ecDH9PTf2nCA0IUPEQs4rQ3CBdvNWNNYR_v57E7swkyd_TPVklIOfDLJTqorYuxm5sdyGIfGoak_IzG0ycTwhoFyBmr9C4whsAVQzrg6MOrmw52vqJnea34K9SPkzIKEYG2VvsKRezG6qaY587ceiiwYy1HpJHsVj6Lrtm3IClZrfQ4mx7rJX53IxyjRXCWC1b2alL8PcXc_XHVfhOgFO7lzvxv_T1sRJ28FCGhh5TAasS9-1627HJVE_fESGtORC-KF8HQar5oFmbDyycizfyNDuKrdGK-jPoxJ1wk7OvQvGYiSNDOqO6kPqfrBo0qknwlXaIWWZlBZJf_Tz2-TCrpmyI2QgtmCsncoHq-a3Y1wWV2DWXQm7YUWtW509VdlWq2ltb64DMC9C5fdRcJl9fWXeeFXQ_OxLugLl79DYwE1xudsX4IrRlD4pgMm_3Bpo75eJ2HZ6OPY0eHhWK0FuM0nPKwX7ctcoW2NICjqbbVvbgk3za7b2ywyHAPQAj6V2dtJ6-SqA2nTxup0TXpJUhJdTOAx1FBqb69WNMMrMtPpoNH6MwiY-E1pGzkJUzUchub-QsuPqTSRaa4mBgC1G4y9WnkHkzTMutn1SANgyBEGj-pe-RS4p6LiHYb7RgQFa3nKVEFGH7uRmZRUwRY0OF1Ee-KSx-76xpRYMWWkt3nbjZycYsjHDQr5KihA_qDZy8Rcb0XXniXlbeDaHxTs8MLsD_WhdaOLNPL4YL5ugaevUEo9UwSqo2xNQM4gdu5sNeP6Ou5V-7tvYTBNiAyrAleB0dG_zztkIIN98-vtWYHhPPROHo94XMm3fjdl5KxSyERmVkFfy6wfCNe3snQKHZ7LEI93YhBe5Xbqtnq_9YFrggO_6zhuirvDiAd3No3s-XEJ_fADJrWOvcZV1Ke56cs1cHENXrnba65Gr03EcpIFKGiAvw3j56-hZpUfEiWLgU9wj06jzdFZvgXlbgs3tsc3RdbacU-bMlIg3pJV03Taf_FFLIMn2-sJwAvnSfFvkhkMd-2OFWOmI1okQWRJpY58CtDzCzeZ6JPRLmVrtNYdozqjNAPRNdQuyeLUg4KxkjsT8B0pq8ZZzDgWW0-aWBNY6C83rDSz7fsopSsMDkhm6kZkwBSWaTE4Z2Nxwj58PwaiW4xHF6Bc2U8LZy3xyLcP_Wkmp_cCJvWNq2DP7_MM5aTqReYMXvFZg9Zt_2mP2lGThiHJREmI0Z6_hcjX_KYlDLjCGGfsugevV9NocjWme0uYmNC8WsqfG_81cOvvtF21qycy4omK0AdnG1FN7KV64wR0KW8i59x679K2AY8NLWXmJhpxLdxqjQKJ5N9NPtk9n3QWpdVse8fKGjGd1Ifz8jT1-DIT8pEaCC8mEdC6-Upju8oko1pLu8a6PC1L3hesPLIRIu3sTxZmaqeWeeG3Q83ns4ApFitI9bR5aWWGOlBfKCmL5NFAADhI85PE3l3cJUW8Pv--jJigVAS2RLeO81U4uQxhz-oIWsoQJnknKFCSIJUw3abcTPhgGJCEup26ce3QcF0IobaP9nb0K60mhVlYAkii266aoUVt017qZnz8QUcUbhIBfOxtDrcTeuNJ6Vcr2J5N1u2McMKb3k5mS1s8VLOT-EcKzwb-jQeQN-6PRlsGhOwdcAFOnYOfMr_Mpu-dfSXvh-14s6Y0VB3oGdPGeeFul1ircUMeu3vUP5XlmRw6LZK_oPqkgqDBHBqVWYCnwido8ZBWywd9ibybH0gu6u0aVb3YsmYlM-8eBTee2Z_dT8_WHx3urbDJILLTLUXIh4pfI7TWegimSVf-qZuOI1E0vC8hF2zgRWEY8G1HF4ADnXFrbeubOmkt2nm94TlAOTQ1P4Jw5EbYa47XnHt5mV7LpXC5t9YBuTnjX4fVKsmUPG5VosTi7yQjEGJGaNZ72MFrH_Q4zNQ7VW5AGIjOElc3CsHO1OzH-aK5ULE_yaUZyuqXNc2M9Q-nANHdNVUAAwHDrXGPseFgEDbVVO1yyj6rZSrLMOSuNPhk6RJXjzbuh_4rBagsnXPJhAWRv9TiCnSx-h9KpL9J78LUlO-QkmaXTdsUoby9v88AhdUreFVlXfn9y5X11gN6Ma7DnCPr4mftVf31ZTJP06vdGexVwU-D2_0yqLXPAp0EYnhg-Eo7eouROrR8wyqtlJof1hai1hMJh9BCuYB9S9r-_GBoK9wHkJRFB9-WWSHBs86bQGwK091VsJPmPpFUWAIuS6FlAzvbZDqpxWjY9eMoD09CL0blfJB_62S8_0y2vq_dgjlo3l7DLhTL2TRF4K7MdUOOUopt-A7JTDsBIOjs2rqQf02YD4m3ONaGjhTQ85PEHBkTgBhmAgAiXz1mLznJEi1ijh82JKDg0EVVe4MCo21XU9DG2tdZetGW6DKyn-d7BhHvtLWPSJrfsVS1p24OQBmpaTTXiWDCKSfpvoKZqhgh8KLtS2PEpE0nzKeadxBPXzC8bEV9rF3ng0r3MfoYit3lM6tivqdBe_oxalbN3mKZ_MBfpoN_w0H2WfXTBu144bmUDhsmuDyOdLUExS4CCrJva4H0napyBzsg6si8r2DfdpvF-jMj8Fg9rTL7HMn9d-G5q-LH1qB6vgUKnvpA0kHs7u7Au7iAEKL9tCKWP38YA27H4IEKim2kvJG4e81gCl2yzDmLJl78kiB1CwMqp1zzAIr2Zs9fot2ezH0HT3d40sdE6CrI9gO46Rfee-7Vw0rBrgvcmFL1GjH5eXXIQXB4npVGN-Hfe4jMARdWiJo_eHUMsopZovl7Fcbnr4ZKLseHEjm80nap6l-x-lFkkKl7Go5iVDmHd1DzL_yLL1iRjhE6T37jTtYTDgw2PNROGvr5968UV8-b09Pj97e4ksQauo1uqxYOp5GpMkBmGWVMQ59aRyZeKO4pmzu1U5ZsdjDh6jy_T1S1umqaPAIrWrZoLdOtVpqE78JlYgkmPpvFj8L6Dn82DuggJ8jT8ZOYzaYuNEG3gz_PHCha5X4QPllM1L58g12um4575_BcMAyd0oIhPeDXj_f9v0Fe14okIYLqo3Q7FtqUaCQaqsef5nzQ4y852SHimOGI4BNRd0wUQ2_DDB1bB7pYaQ7lKhh_kteKi0qhh603mttuH6Vt8Bedxj6e03VB7p10Gd43m9qoF4V6Fdjiz9vEX4CYfG1BOzBjHdvTWBnXGkfHQqpEF6-qi5p5cEpRR23DXpQnZvVMnNI0TCfEINbdfGJ2PsmQf8MJh0mReQJxdshZXYshi2cS7gYzF99JkWLXJMtwzulk8hfhKZsuAfnUT8Oi68mGXNo5mhFWmJdhzg8XV3tpZxXR9ISV7C5lUBP3RRCp-SFX9OIv60TJLZrta24eKwf932aa1vvGSIB4k_hX03eDEpc1TJTH-lmflNC3AVHb_XdTr_VjTBVqQI3J4jG--jBhtBfgwJM8G0BDuPp0eu5PoxFuEr-UaZoo0g5Bdf5LwECuCeaovtfMN3TbJk75x1X-e9a0_uCu9QzL0y7TyBC-G3yBMulKcjTO4o_DpkWcSRAyfOfA9-E4dh77Hld-JLs0wInrGtiB4Z-QNBLpzazDurSJIxlHdjYlb4t6ZT5e19iT-VsKOsKnfmC9sT8srg6dlawemfimqNdH8U78BusvEhqln4vns0QSW6-3LGugCFKFoV8QKjSMijU8ZS_Z-ZDmoJdQjm4MVi3NG_gza_ewdAAJ_XzfWfLbL3P2w_IvPWPLYJ3CKwCfblWoo-4dByAL-alWUH1yfJny3-DDz_OmPPy5dmd7GqBmlsSA97liafzBC_tJl55-3z48Nr0vwtxCqPWl3mxwb15NhqwN3BfIWKAG0wNZDgTpSm6uaUmSRBbwbCrJDBeI2Cif3lBOPXZKr7ydkpw1EaeEyZaaVIvzU1tkvBnWhtPHj_AwQo_aapmGE75MbIdR7HbnO8zDPsvL-3PMq30HoTNdfNJYKZRpJDkn8t17KZZepMmxnHfkUJPgQOGdZ7a8cvJCtllxG80lSLrMxO_C1X-uzqD9ZYPzxgorhiE9XUiOyfrZoytIYn316tHJXXEbFPSc8MThB2hkqpAbOq5426fYPvsURz12zUR8qlAwmD843ipcm_4_A32drW__e3V9r7iTnrmvaATVLApbDfC_7XDMSFzoK6PJWcMqjRBwikyxwml4h2oq5IVcq8FgNdXuxAW4w98BuDwfQK6X5N4Qh-CK70iB1aGCbRCYRM2mlaQU6mai7OC8rII5kznWBelB044qSByeJnSEbxH6GiHLk5zmVgTy-jMwkKDJjmZ9B70C57leY3O90IVsf4OxUYChVBQI3Ns7W9QPb0F_W96D9Y7LrKJ9bVRrY1INqn_iaLSvh95X3GpZzcP90XAt06pNLnMekjPHbLL9xpl42nRyZ8vrlw84Fg1RnUITYWIWHLvf2MR64Q5MTRdQuVoN5gGCwpjiv3iI1oDAKFNzuqs1qksrfXip6atC5jTVk6ktF3O2Cs047xRrO-LkdvCth6TcEc0wkRrDbDKuV589vKM6qgdKyIZslTEW_DPyZ1vYvm4qWQvx6XxQ409opd_CXucTINcPftKpgPl5x5_10ZJPHy33nkuMbD3oQLoLHJ96wuEt22KYTTpH79ZhulQaXJ-6CNpMKCBALM97nEWwCzQavpv_thD78YjwrNwbhzS-KNtKHegqBpocl-bG1vEaLfaZnIbsX2s7xMWIbD1EWNY2gj_xHDaBpBqXayEmsOiJAP9RRbFd0H0G4jWF2WicztJzfex9bXF0AHvtQt5rLCuYXVLP9gqEqUroHebu6J1rnbyuVFPY6Rn4vkF2EGOm2lKf4QNLZJ5hPrGxoH3ktWtwPayt5dG3Xrle6ofUFIy5vCJvafz_mo7AElyl7pojrZW2HUtSCAzcaHIpFkNy5m0Rpm6337u6707lPa3CbtEAxa4iYJoXyQchcMxfnkwLouPUjZal5Tnn1Sx3jqA8b1S_u_pu7Ov2XiJjTdwjKPlln-WcGMPxjUsXVfYHF5jQ9RvkDZiUhNZw2dBIaTHy1DdqtTBhN-i6EWhZDR6OIWn-PDH0XDC1EcdXws-j8wyscY2hMbgofQX2O349bXC9TbhWqQ-qKdalBGberwH4lrbHaSUzQhgMGlVNiBwtnvDiwcLpz-qXn8-xIqXG9z4GgF2PfBXTNVYYuvWVzVzUMNMXiQ56AENfZRYrbgqXMsic1sgRdLvZYy-8iRQGQ7YC-NOy8nWgo4kNgWYtqkuL4hb2Q5-LmUUELq45EBd9XdFp3ZiVon23eMhBF-oIgHAo-EA1TogkKwl2avbn00Po6sgrTliW09mzbzy4xSwjZ5nu7FcMa6dHRw_iUe9fz50FqT-19k5ms9hc1hzI00GO6Sj8Sv0ybtI5vNZuaNV4u2ezuUFnzPOq3Xg__i39aGPENsQUQweYkhS2DspkLvI7zkw31tLDsPj0UV_XWwx5ZR2zOsrFd6rgtHlzVGBetIZ3GmuZoTwMjXq2wkl6UPsNw-Up2XOFP8cVXWtjpGWe55cSxzgtvFIew8rKxyXzJiK8uW-nxkowy_yeWGBfYl1W7S5BrHFbWXGCq6LmJWfdsRUKPTQJ-fZgxnvBX0hSVH4rr-UrFTgiCf6LWkFGlj0eS5KErFW3mYNV0Lv-Wtib7cjCaTBtsLeX2R3X0bSDF1WJe90DKkcijoXmmoTdmKliYIbUJUmweOG1I1iCmeoGYeukujxqxg8Lbc2NYAQkqHZH2MPGJe3Q3Fc9N3JkVUaHpyDX-EPh6pG_D3zMCAaWor05cIaGi4CoBoOzcW-Sr62A99EGam7hIZDxROxQts0uUQWFNS_esSUfYmlqwVXIPz6A3MXzK0VHisXE8TAiYP0leiRU80O0MfKFzAwqkoiztG24wYL6GPH5TWZU_GKk5f4agZ6GoXxCvbWvxB09nMhSgt3JFavTymh1dSIk2-m5MnGDDMfYyWV_UBYYR7U-wK--zmXd5Af4kS5GlWfOGtFA64OFCMccnGNnNKxb5DO7a3RPjcNzNNCYNLXLVm1J6nZ2aknyeI-rgI95QVIFBgeaxweMHYC1QAGfqvegBn46SkfnU-DzTKBRXFWAbl8znLmsAJMXtXxGjjQyGUN3JoYcxOgwvvqSsbA1DNuE4WpJj1910kW-grhiV43ug-mX1KBeCkCHGsnLU2ZZKCvJ0PdOAJE53GzjrwBe0JcnP-UBM8gawoVg5BmMR5xiVahjmbhKLe30gDl8puFoNMbEeDJ3SNAcpukzzZ1-W3PwBnc5fQ6vDb8ho84SU2Yg4jwpWJaSXHYjKfF1mXiG1neU5zbtFVS5x3JrslJlEfQbSFNeZqrwWGAIOcfS5C5VjqeHFM9dvc5K9Snen7AuIaxdir16pwlQvFl6h3PuhPL3LUYdraBqr6amn2y9CSrQwN7XgwJHdNtr4JZSe870eblLgbShflCzUSMaYs_bkyLNuuOb1BaKcH1CfpMqGE6-FbCPguhooJnzaoYG7xsoJII09LZueAgGhCw4cvQgetj0vEHgb_CHL1tFztz-srEr3104chhDRo0X6a0yj3MRd1RH0AWvJQxkWugsrOCT6165h_Q-isURFvL8dcnd0ffvznD9PRfz2tVBUTm2WskIWeunu3zjgwy2QB70LzfCYl4PMLHHItKP7mKUOFPo3yZVN_lTC3zuE-EQRfU4Rzihi1dUFH2F60ba8K9RjlNziudHc8vD9TJzmvUwB-C7l2YSCRDckrNz2Wp5HLHu-TYUxw7AkAAv_uJL9rj54Vd_f3dF1njGdRcibaLGSJGikm7qcnyamL2ZKCv6xJoTrykOwXjoJuEiD-rSv4lBbbEWllC9nFOSUrQGBm5WmcA4971CEtLsUfTG7gQOKPLWiTH8DNCwCvmFUKwBdV7wwCKL-x30j5axDciK0KeVR4sL9TYCQ4L_QhPGvVKUC_8onx3KNBKT3GpRdu70Vs086HAN27UyRba2dc_7O4zTlscusjwhpn0WD40K5bj0vaOXPHUJPa_Z8Jm6kYNA4qUQTbuBZdyECLFCdoH0_n2UHPHZuAzOofQWzZ6QrG7ZgUMk_mukA4RRGYZvhsqWBbw89dnHzIudUw6npCADc6EOuav34QWHmubxJOZUdsRC60pTHwYOlAWEX2XbHStiB0B6AzPQldQjdAwkIXNGUAAhvs5MBFX8eLydh0B7xNI2v2aAQTLkMoOFnbo192M_B_pEYLkuy91bPyanqxucP7wi4DWh1XFugsuWNr_KJBAO8eWQFQDkyKWDIlFY7zZE6K8GuaflzXTG9IrLQRJRVQjCCIMD-3r5-zCoMha6u6LQd8GjuqsHsHRbrkZ4U-5A1htJkjVoQ41vk2_opHmmk0mlpe86MjMIfcEeeLxpALYJ3JzhXuyPXWgGL80cu260OXGiJmB439rnNzdOG3oO2_G_Rfg4-k4dx-1_jRQU2gJctrCVlc_Jn_Gs9U0e00DxBU4txBzuZF2aKT93iwb7q-u1pmwTQlFGPeT6Paf2hRSPDZA1CDZPSr1BvtxKfHVmZZDeGeMbqFpt9PEtWzlmEMPulnG6YNT8-apiXQm-tHcMpWp1JvCUGlp2vUWnFwyfI9osc0vGG6X1zCDudT85yoNvJBxjZfR1oOhmpDwN27GHMao2PwP4swHDSRJ3BCqyMMhwQ6V12hI2jo3x69B9B4FFbvVf5uoZhqzvP18eqgAL4kS6IDEtPNTavbywiJ6_emk3whvr8lMQ7EVkMtNLBUPxASlWbZVTFKEbJdGJ6D7x6bNgzzLUsCfcK_HgB66wKYI3LfNTegpXvJe2RWWqxo0rv4aE-HxcUaeYI0ZHzGaHIV6h8MEADTPKDxhxTJoywZnQV6PRS-UivyZM9Ak_-s-EpdcnjhCb1RTP6USe9XXkxYJYbTI7J0dhJBgAdF8-QDgF1L6ajkMuUIVsDg4RBzqE5LgycW3y4DO0L8RozwsFXWDvlQpS1Gj6SGNUU_ACcSHWnUpyNeX7sV0eDWYrR6QPIB4b7pZB6vKEnkkcqqlxqvfT7wjw48ss4wJ4jnLtXYsJBq7Jv7sfCCj8ml0KaIb6gpAcX3BbtCNYCZrZt0sAjjP6Js4xEogcdGF4fOTaJG59fIEVMl0OLWKfEZ4F0J06LWlrg5o5WRgTEe4ToKZM6yQb1gfaLC8dWgc9SnsHgZAggCeHpA0ONhEPNZVETHn_OqAYuMd-Lj9IZfLyopvjKevL2I0-q81RBqPLQulSwVukAaEYrTtElykvsjIWVsOQUNWAFVK-XPL5h1qP6MQ6-I-D8PUQGxT6myI4177NWyXP_uiEQ6ZhSK3MIrZ-HU7GF2do_Xa3Xi5Vo-xfvi-Ju7TwOlPDDm0dwNhJUT9I0CRjVrKs3ddwhQlES-HAgAC8GwdD1avimwQeENHEzk4vCwjseNptvehroZEu6Bf58Djb8vwMQ1dSHLIj_u9V3pj4_DmpT0zC1jlC0SCVxzvOXw0VJojE3v_NuyJ7tlb6kYN6qlARyhrnSyqJsvcS38ArXexc6k6BzNXtvSuIGJMzItwZtSd9vmM6p4BMcYPA4ckBSSiw2L-qwqk9JCa2Xf6qx51OFnYJiO8l9shPER3JpMD3eSFeVQeLM29FfBrHesPvPWdcFuEMXEwfBKseagt3EvdrlgQrl7PNDkon3Og45y9ehVsCKpR10OIMGyZA61OTm4LC2sW6Lqhp-0g39HHuyofoF18mrWf5zpAxxf3pYWyhqXGbI-uEkeREr1ygZLxmsMUxNdjzjpkix0QNO8i_cjgo_Z8tk1hBf4P3UsW2IXceSn909EDBmdGc9fvUjJcD6qe7V3q3ehRN9ICmCa17C3ZoanAX7b110XUgx5dEpm5aXv94NKKkRSNSyNe51MVHchcS9b1pW_NnQT7aqAHkIeY7arSA4yuo4DxsCY4Y5AwN0uguQmo2i4UBOsSJ-R7t4YDdVuSpdjd0653SkCKbIH3fFOp1rUPT0EWm52cz-lfJcUIxzUHx6EsO08mC6NDNklKea0wjgOid6fSSUkQEXf6dNX_TW4L8 + type: encrypted_content + id: amsg_01a1686b-dc2a-78b2-8e60-9ce94668c444 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-afc49a7b2cfc2b4c + id: maco_01a1686b-dc2a-78b2-8e60-9d0340d8f459 + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The missing tests review appears to have been cut off at the end. + Let me check what I have and whether I need to follow up with that agent. + The response was cut off at "4b. `test_con" — it seems like the agent''s + response was truncated. Let me check if the agent is still running or + if this is the final answer that was just truncated in display. + + + Looking at the message, it says "Message Type: FINAL_ANSWER" so this is + the agent''s final answer, but it appears to have been cut off in the + display. I have enough content to synthesize a comprehensive review. Let + me compile the results from all three agents and present them to the user. + + + Actually, let me check - the response seems to be cut off mid-sentence + at "4b. `test_con". This might just be a display truncation. I have substantial + content from all three agents. Let me compile the final review. + + + Let me synthesize all three reviews into a coherent final answer. + + ' + type: reasoning_text + id: rs_a89b80782feb1644 + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: "\n\nAll three reviews are complete. Here's the consolidated report:\n\ + \n---\n\n# Code Review: `cache.py`\n\n```python\nimport pickle\nCACHE\ + \ = {}\ndef load_profile(user_id, payload):\n if user_id not in CACHE:\n\ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\n---\n\n## \U0001F534 Security Findings\n\n| # | Finding | Severity\ + \ | Lines |\n|---|---------|----------|-------|\n| S1 | **RCE via `pickle.loads`\ + \ on untrusted input** — Attacker-controlled bytes from an unauthenticated\ + \ request are deserialized by pickle, which can execute arbitrary code\ + \ during `__reduce__`/`__setstate__`. | **Critical** | 5 |\n| S2 | **No\ + \ authentication/authorization** — No identity check; any anonymous caller\ + \ can trigger deserialization, read any profile, or write arbitrary cache\ + \ entries. | **Critical** | 3 (whole file) |\n| S3 | **Cross-user cache\ + \ poisoning** — First request for a `user_id` populates the cache permanently;\ + \ an attacker can pre-seed a victim's `user_id` with malicious data. |\ + \ **High** | 4–5 |\n| S4 | **Shared mutable state → tampering & info leak**\ + \ — Returns the cached dict object by reference; downstream mutation silently\ + \ rewrites state for all callers. | **High** | 2, 6 |\n| S5 | **Unbounded\ + \ cache → memory DoS** — No size limit, TTL, or eviction; attacker can\ + \ exhaust process memory with millions of unique keys. | **Medium** |\ + \ 2, 4–5 |\n| S6 | **User enumeration** — Observable differences between\ + \ cache hit/miss (timing, errors) allow probing valid `user_id`s. | **Low**\ + \ | 4, 6 |\n\n**Top mitigations:**\n- Replace `pickle` with a safe format\ + \ (JSON, Protocol Buffers) or restrict to authenticated/mutual-TLS channels.\n\ + - Authenticate callers and enforce `caller == user_id` or explicit ACL.\n\ + - Return `copy.deepcopy(...)` or an immutable view.\n- Use a bounded,\ + \ TTL-backed cache (`cachetools.TTLCache`, Redis with `maxmemory`).\n\n\ + ---\n\n## \U0001F7E0 Correctness Findings\n\n| # | Finding | Severity\ + \ | Lines |\n|---|---------|----------|-------|\n| C1 | **TOCTOU race\ + \ (check-then-act)** — Two threads can both see `user_id not in CACHE`\ + \ as `True`, both deserialize, and the last writer wins non-deterministically.\ + \ | **High** (concurrency) | 4–5 |\n| C2 | **Stale / never-invalidated\ + \ cache** — First write wins forever; subsequent calls with updated payloads\ + \ are silently ignored. No TTL, version, or invalidation path. | **High**\ + \ (correctness) | 4–5 |\n| C3 | **No type validation on deserialized object**\ + \ — `pickle.loads` can return any Python object; downstream code assuming\ + \ a dict will crash. | **Medium** | 5 |\n| C4 | **Unhandled exceptions\ + \ on bad input** — Malformed bytes, `None`, or `str` payloads raise unhandled\ + \ exceptions (500s); no input-type guard. | **Medium** | 5 |\n| C5 | **Unbounded\ + \ cache growth** — No eviction; long-running process will eventually OOM.\ + \ | **Medium** (liveness) | 2 |\n| C6 | **Mutable value returned by reference**\ + \ — Caller mutation corrupts cached state for all future callers. | **Medium**\ + \ (data integrity) | 6 |\n\n**Top fixes:**\n```python\nimport threading,\ + \ copy\n_CACHE_LOCK = threading.Lock()\n\ndef load_profile(user_id, payload):\n\ + \ if not isinstance(payload, (bytes, bytearray, memoryview)):\n \ + \ raise TypeError(\"payload must be bytes\")\n with _CACHE_LOCK:\n\ + \ if user_id not in CACHE:\n obj = pickle.loads(payload)\ + \ # replace with safe format\n if not isinstance(obj, dict):\n\ + \ raise ValueError(f\"Expected dict, got {type(obj).__name__}\"\ + )\n CACHE[user_id] = obj\n return copy.deepcopy(CACHE[user_id])\n\ + ```\n\n---\n\n## \U0001F7E1 Missing Tests\n\nThe single existing test\ + \ (one valid payload, one user) leaves the following untested:\n\n###\ + \ Caching Semantics\n| Test | What it verifies |\n|------|-----------------|\n\ + | `test_second_call_returns_cached_value_not_reloaded` | Cache actually\ + \ caches (identity check + spy on `pickle.loads`) |\n| `test_first_write_wins_subsequent_payload_ignored`\ + \ | Documents the \"first writer wins\" contract |\n| `test_different_users_get_independent_entries`\ + \ | No cross-contamination between keys |\n| `test_empty_dict_profile_is_cached`\ + \ | Guards against a truthiness-based check (`if not CACHE.get(...)`)\ + \ |\n\n### Input Validation / Edge Cases\n| Test | What it verifies |\n\ + |------|-----------------|\n| `test_empty_user_id` | Behavior with `\"\ + \"` as key |\n| `test_non_string_user_id` | int vs. str key mismatch creates\ + \ separate entries |\n| `test_empty_payload` | `b\"\"` raises; **no cache\ + \ entry created** |\n| `test_payload_deserializes_to_none` | Documents\ + \ lack of type contract |\n| `test_payload_deserializes_to_non_dict` |\ + \ List/int payload stored as-is |\n| `test_none_payload` | `None` input\ + \ raises `TypeError`; no entry created |\n| `test_string_payload_instead_of_bytes`\ + \ | `str` input raises `TypeError` |\n\n### Error Handling\n| Test | What\ + \ it verifies |\n|------|-----------------|\n| `test_malformed_pickle_raises`\ + \ | Garbage bytes raise `UnpicklingError`; cache stays clean |\n| `test_exception_does_not_create_cache_entry`\ + \ | **Key invariant:** failed deserialization never poisons the cache\ + \ |\n| `test_pickle_raises_during_object_reconstruction` | Valid pickle,\ + \ failing `__reduce__` — cache stays clean |\n\n### Concurrency\n| Test\ + \ | What it verifies |\n|------|-----------------|\n| `test_concurrent_same_user_id_single_deserialization`\ + \ | N threads, same key → `pickle.loads` called exactly once |\n| `test_concurrent_different_user_ids_no_interference`\ + \ | Parallel distinct keys don't corrupt each other |\n\n---\n\n## Summary\n\ + \nThe code has **two critical security vulnerabilities** (remote code\ + \ execution and missing authentication), a high-severity cache-poisoning\ + \ vector, and multiple correctness issues around concurrency, staleness,\ + \ and data integrity. The test suite is essentially empty — it covers\ + \ only the happy path for a single user and provides no protection against\ + \ any of the failure modes identified above. The most urgent fixes are:\ + \ (1) replace pickle with a safe deserializer, (2) add authentication,\ + \ and (3) add a lock around the check-and-set." + type: output_text + id: msg_9876395e7e41277c + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 44435 + input_tokens_details: + cache_write_tokens: 21952 + cached_tokens: 18032 + output_tokens: 10109 + output_tokens_details: + reasoning_tokens: 2866 + total_tokens: 54544 + headers: + content-type: application/json + status_code: 200 +- filename: t46 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686a-5682-7bd1-8c95-f08ef8429203/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_01a1686c-71e5-7a92-bb27-7ccd32a8b1cb + role: user + status: completed + type: message + first_id: msg_01a1686c-71e5-7a92-bb27-7ccd32a8b1cb + has_more: false + last_id: msg_01a1686c-71e5-7a92-bb27-7ccd32a8b1cb + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t47 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 16384 + model: Qwen/Qwen3.8-27B-FP8 + multi_agent: + enabled: true + max_concurrent_subagents: 3 + previous_response_id: resp_01a1686a-5682-7bd1-8c95-f08ef8429203 + store: true + stream: false + tool_choice: none + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + openai-beta: responses_multi_agent=v1 + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + background: false + completed_at: 1792753303 + created_at: 1792753302 + error: null + frequency_penalty: 0.0 + id: resp_01a1686c-7a7c-7cb1-8d4d-dc60efce66b3 + incomplete_details: null + instructions: null + max_output_tokens: 16384 + max_tool_calls: null + metadata: {} + model: Qwen/Qwen3.8-27B-FP8 + object: response + output: + - agent: + agent_name: /root + content: + - text: 'The user is asking me to reply only "OK" without calling any tools. + This is a simple acknowledgment request. + + ' + type: reasoning_text + id: rs_a0a616f511ff23bf + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: ' + + + OK' + type: output_text + id: msg_a501402cc01fcbd5 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_01a1686a-5682-7bd1-8c95-f08ef8429203 + prompt_cache_key: null + prompt_cache_options: null + reasoning: null + safety_identifier: null + service_tier: auto + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + tool_choice: none + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 9464 + input_tokens_details: + cache_write_tokens: 2352 + cached_tokens: 7056 + output_tokens: 28 + output_tokens_details: + reasoning_tokens: 24 + total_tokens: 9492 + headers: + content-type: application/json + status_code: 200 +- filename: t48 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_01a1686c-7a7c-7cb1-8d4d-dc60efce66b3/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_01a1686c-71e5-7a92-bb27-7ccd32a8b1cb + role: user + status: completed + type: message + - agent: + agent_name: /root + content: + - text: "The user wants me to use three subagents to review a Python change\ + \ in cache.py:\n1. One for correctness\n2. One for security\n3. One for\ + \ missing tests\n\nThe file cache.py contains:\n```python\nimport pickle\n\ + CACHE = {}\ndef load_profile(user_id, payload):\n if user_id not in\ + \ CACHE:\n CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\nContext: The function accepts payload bytes from an unauthenticated\ + \ HTTP request, caches profiles without expiration, and returns the cached\ + \ mutable dictionary directly. The only existing test loads one valid\ + \ payload for one user.\n\nLet me spawn three agents with distinct, bounded\ + \ assignments.\n" + type: reasoning_text + id: rs_b2d84a4596d72ce3 + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"correctness_review","message":"enc_arPr9zESu3iaF8wAhYeCmHJN2kC0HtkHASNRkc8p6iCBGYi-st0JNxGxBD_QJSQpNtlXdZIxOUX3l22u2iSIYc_FB4LrwTFlSyBsj18IX3xWmaAmypL9hpESdbqe-YqACaEBmASqeLqASkckhiarCxJd2b33BpP0zZEHlm8EDm-Xsr0h1fF2nR0-MhUkQ23RA0W82hnOpE-uOd0zjeEMkt1H1JosYwwJC7j5k0Wk-7hSSiPeRR26Kjbzqp2udbD2f7UdyxaYJfDgFrxiBV9oBQ35jJScVVZvIJhPuv6Ya4poW88Rr0C8KLk6wpE4ZFHk9e2Z_YbWVnr64nvc_ye1cCw_rGE7XHImQyVZATfkRmx0qO0DOhSmRlTk0oGO3hCUK9SdzPf6sm3CsLCXrejUHhr0QKsdqssQGsarHeOxmdgBQXUhfG_-ouNuSGlIMxZnwTFbCWmN-5ApShjDNA7PlYdQ0djLkdDhODSDc4Twm-2vWT4BqZkpPfqivlO0qQrgG1Fwbyllk8ZSCMEaBrMJDhhdcBIHbbvlSjIGBNnspu6t774dRszmHG045eSerE4aT5zcgyjVw_HRxoh07g1s5PFlLBNthqikj__Cfjcz8xoUSnZy15Sbom7TbSibmpDLoKhnoJXJXPnBubwgK-M85pqjLRm6aD6RH-zSQ8NIi87k8qkdcCKEMoOLtXn9wUHJ7S9JrCKh0QBobPdvLat8YlY6Z16yYl-SSkeX_4TPqpJbbOLHtq-zJEtHgB5WEqzFD_2phnOzoVBpp-QQHTxNui2UbDnonK2gpRjXbcmishOUZkK217pGsiqNpwRiIToMMuSKyvUoP5cy0Ysq_PDaCW0IJ6D6SQPEn4hFk_M0ascguBWtZ3jKC7kfiwGkH21wgWobPT6jLmVzqGHhuXCBzGPkioL8ns6mktgvL8sGOp7ZddjiNSBm5rhzdtba4bl8V6EiUUtj9TQ99vFOMK3aNTOTbW1lVutsdAH3OyecxpYfvazdU0OQw7sYyq5XUBKX2wmykEVxmSIoEaz0e1EM_1gvVwPVzlJCQsdrKiAn8eLMdzTryOR1jxI7bcMhZaBDyIkR7ulhnup-kPmnY9KqRQP4_EC8fCjEj_zSxLntgXSEGgVAabq7akLHoDNFG5qgTbrM3KT-9y-6_8T3gV4_R_Y1ori5730ADUxdqz0tP7SMfJvJTjDGzYX-xwAqGh38mIYZneHBPBAahFGuSudUaeTdUQQcYZMQpkQX56IBtwSZZxcq8iBjKCKzhuyHJXb9Frn_ZC8xXD6VAWrVdJTn2nxy65oT5yaJtcbuujiwcOWVNvunwKNSISTYMhcgqwNi1rFQwjDvfdlM4mhtYNU","fork_turns":"all"}' + call_id: chatcmpl-tool-ba1410025d43a186 + id: mac_01a1686a-7bbc-7a60-8630-532a18705f97 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + author: /root + content: + - encrypted_content: enc_hP4HMi3DIigVa2OOrMwxJ3Ka4-R_oXXm85lNFcfqHExygU1CjKZKf8oZpZmY1oOkFtD88IW7ah9jeI8j9CipyQMKH-XgqbLgJb1dnJYqEQOYKfoPWnwwvpfKGCK18VR5P_3KGepcnhneAaJubgSqmN0cIJ89GOK2L3iPWlLDTHINXLZxodqRDWIXLUWmercr5j7u78qmFH1xd_yCsyiPnqP2CmWNiCxJF8vwGrcc3A5uzIvIZ21INiBMYQKRPLEaRBohV1Gn-oWpIgBkskatVqI2iLcArEdfPTOQcbdTQ1J6IzDdT2wMp4Yooo9CGZ_H59rE5_H4l_NGbbOjRFrmgRPOlamVLD8vnABHWSWYG6cdfOeUV-6AGiBA3t3rF-sgbJFYkmfCXlpyqE_T304SyRZ5m7OEceslO-5IXuj9_oLAcULapBi-m_CgxiPuAmFP5Fi_Mj5W62ZVoQ6GBqBgP93uPE482fQBU36eJLW9qtzYouiimF8tOdVE0wbZKylXJmXqU-0hiH0AoAjInlbjwK5b56ieoZx3vgCaxpKlmB1fQKSNqa8Z3hVjt2_sM1qe4AkRqbEvbs70j48Y4mrnck0z8n0W9jDKkZGCNOYgXMSMz8cm_wEKbXE9LBEOA-ibAyZjwuPlYLHQeC9mkVvCXTmZP8XhcXuoT9ANysZx4KYRbuYOXoNsiWq_BVSvnqm5556HfJF4Rr8KFEd5bp-0usBdnvPpyiKdl4NiLRt711lnymHb1jfX50Opjq15_11PiBQvsXi6xy13fic_Yr94OKOmcBalyMbivrKlO0J2NjsuIa6M9KInnqCDN-oKy4Un7z7MxE0O_kZZElrgKzloHHR6Gp6WkIksMel4RC-Sviid1b53Fw5sgysmUbSulDwYo9icsWnR6CT-92-mAP25j2ivEjQ-JEqg1-hhPgHJjR7ObW9Gwhe_pA0pHjaMX2dp2S8wvU9WmPo4LtGDaNvHY5gQxFyLmAQbfdWJpV9jeB1aPH5E_gpotgF4rn95K9m1U4lqM1HPGF4SoxiudCNveHy5BZbDFw1LvI4d1-i-Q3UbnTM-yo3CYoSQCTN-1YA7uA3yw0xilC0yuR_wD-Mu7pi972A0P_BYj8Fog7ubZ6TGR5g3ByMmW_tqeiFEI9kSuBfvLH44PK5aPCBCCkg6hCXt-O6C5ooC832oLYvXFRfpRoST0BhXu-rHSlJmGWuvF0JMAvA3TWiwAJGw5JnwLkm7KgaW1D_1Vh107Nwh390PxRo4G92noptbdeYWb2MJp9W46rCllRVNJI36Bbu-tLU6SLTh9vLirGcwif2tEmTG6TAR4sRywD2mo0ox20yUOvP1GR1LVqNYcCTh_Pw + type: encrypted_content + id: amsg_01a1686a-7bbd-7d32-9a0c-3fc4a9c2ce6d + recipient: /root/correctness_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-ba1410025d43a186 + id: maco_01a1686a-7bbd-7d32-9a0c-3fe134e05c9c + output: + - annotations: [] + text: '{"task_name":"/root/correctness_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The first agent is spawned. Let me spawn the other two now. + + ' + type: reasoning_text + id: rs_9402c5bbd1e8db9f + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"security_review","message":"enc_EA87eB2Aidg3zZR2xPlAgyV600V5x5usELCIcCCV0nMdjtKldD0Q7TWHVjnUtESGXC40hpTpG2yWMiC6zSbPX1twhG524szMHDkHSCy3a-06IYxlDnte2kGa0IlZS2sJxV1iPfn08rBWTWzY_EjxQga7P0lVgtMv6YJaNGK9VGzjvc0cQXAdFyZbYRTQUiMWEno6e0VXbjua1FhfN02C4-2yHBu0qo95yXiPIAfyyEzsQHl-qVj4HpMmpkVuhhNoEfwgmZfEMnAywT1m3TtGcwq9lJqzfiNacpTSdzZclsNQnD6GkPy13mB5WpQiranCM36ZaW7BoeejUQ1G_D3RpQVSlcakb7VzPmae2_nAxctKkBm3Tskjo7aWRQTKkb40Xpir5RWR4rsAJEhc2U7UEgVxxD9RaQV--g8Xw5ZEr3f58RAzpPQBLG3V-Eo-Wv1TF19G2ESV98_kCf0cTUIpZbXr7GT_opbpo6ln6gKLn7JxphTaGPandYi4dOrVA-H69jKYaKW7i4qnM-tiOUKI7XM96V0hwb1XxxWe6lK95oq9MUrfrVZ5p4GbP9nytezqjBOuDR3Kq3iycOCVD6cOZOCSupfvxHbSBe8wn5LzULhRyoQ0VHFAd6FI9-AZ7_1_JRXef42VLuLKt26k82RnDx2wFiTn9mSTPu6NKjhzvZed7-uDrIjcVc-kTnGqI0hEcJDiRBL_DOV82mHiDvcENnG4j0WHtgxDUlrE_4Ogn0wK_hMg_bYC030a-AlROfIIAl4p5suP_SzBA4vYIWiXciniOYCejdO-gVnnX9_oeu7ExA_bQ8PTacb3KuSpKS9RK0j-sc25ow40Q7-VBONwvML-DHooMZXNZLfmHtvroEoAepOKkOOUnbKS_hALtnoxxzb7FjQGZppRPFsmiuehzJwUJlJQhSDMU0QFldxT1sBczHyO7KZsjkxAKC5MEJFI6Lf-BCMmi8EIVLikVcfjkk6Hjc5ZpoqJ4USqR8coMruyp2K19FXoeXHhV-XhGTMFSckvxm0RTSKDSV7q4rWcC7EzENLbKqH4eZY9aY_0NT1v-G3CSuP0WL6vvZyFlnHEY_IMXOteaE6eczs68qN-N8RDcuY2RTSVMx44FaW9Rl5uKIYaAMYZxnk7IETz2cy7b4vOOwnLFCKVSnMlGKhjofteE3cjWmEtTbQI3Ic5VRu_elv5On3ABXjNBdK_qgUpqdAPd013RQkzlNlKXScxOZeY6T3JYLW2pMr2o4CmB785K45ySMhB_3DV1ByJvHVfCLCckgiDvyosIwjRmCEvQJ-nIBQnuQiS5cb4pf57Yd3BTbDnqhpq-ptPh2PPmI49JWeXmqeDWeNjNhE67mFseqm_cZdh1GhHwcicbKhYypS_B5PR4Jht7wDYJz5SJUBVpNvV7FqRLs6jiL0W6MolBbMKDvAjeaXelhufJrDj_-bpDOYUrlg8XemoLtT5XvGe","fork_turns":"all"}' + call_id: chatcmpl-tool-b4e4d375f9b6642d + id: mac_01a1686a-9943-7720-a427-c50a9b89944a + type: multi_agent_call + - agent: + agent_name: /root/security_review + author: /root + content: + - encrypted_content: enc_pJF4Hzl4gf97q5vMVkTm_M-X3QGox8QJWyRGrxurEhMaG1EP4_CkrbDZGDNpOdYquKiIvckkUcFnlsWYkO05kBGcPwOXIE8ZSISziJzsMOUxeMnc_v33Wy72aixQuQddEwsMO24ECE9m4aYMwV7Z7VqMycXcNeOjywzvFDoeF8I7vkxBrWa7kyEptVl3frDhHnHX9eRPPnuGh4QozErxK3nFLv4qjzrr1d0GXT-m3yqXQckkZB_UUTmJeeWB3uFIWdnw_cwYsgomEemynnQP7af9KahRUo475qB4c74pTYiVDYEiA727Eoe0QLskY-J-dYFEIsJXzVuHWD1QfknDVG4GhCv7djTxPvtnVPKyyOPm9x2axcKF03Omi8NuM-ZTEJUkqEKQ45F3THq5LyU_DEsjHtwBWP1A2fsRuIQrWaJS9FkV3dkY-lc3vhO3HJeeM0JP62t_fb3BZxhPZzciqXrsLtT71tQaADY1zVlnSiAVHcuf9ZeG8H2lcjQ5YOgOQ4l3YKuEWrHXcbt7kiH9WPWWqR0t7dWbRThvNb9gPvLIT8bKhXkOZgXY2WMTQoQX0G44WEJezaIK87zEGHMVuUZvSeoqMm2mnFRFOdcuGJdqa4lAifNm_YXJtTBqJHEWfR4v2g2cVR5E_AfGqyqc82LyKexekWTsZdzsSkIbTMdF4fmnMhZP9WQMl1pJBcqq1Z1OSgU3rc3rfGA0HWkMcKbNrJmPI6HrK6nuoaVp5W7jsxoQNuseib_qcxxM9duIFSbI9uFcvR5f1xtfFiE6KOJLr_E-PO2sbjETUxA8ckVMYDGMneqx0VafJPcCU89YwnQZnHkBfH7yHhojONm9qUvEKZL0xUe1JS2TYD4DwFzSs7bUl9ZtMrFLHuTPznfLPbuJMJhZUw60VJqznv4mfoN8Hb3cO3WufzmTMa31be6FI--FfgvdABjlQ78wRtaFeODLv-dRhaMvwklPeLwlQP7F9GefOLShOcW0tM26NUBnVqxFP88iyBDhf3NFu5QLwg_ngi7EdcZbdFqtRXWU5w4SJW3Jzqu41PGVuK7CWqYKQ9pg-xqGOi8kS30bsJYCOYMsku7bYj3ne8EXDeTK5QWhftzjI91jy3jvD7_z9ZSCW0L2AL_ZmpCFeghPRCpFnp0xhX8Q2B2ZEGo_HkhI43wlMxwzM_wKzxyaa1a9NwnA2H0NkGxmRK8eiq50VGgpr7a8w5WhzDykZLc77mlLxcVWO8Xp6agRyhe559AR9MuEEZphXGb0tX9BbowLM5lJAYE-R1k6n-5rBRMAkNyaRZwmebQhADKlT44vAR_iD3_35Xq6zY6ie5I9h7xdRTf_PRpaes6T4JbNyvPzw54XqGueeD5KQOC7zHH7gJJ46waA7crLwrtpM7AaxZ3wfUFONDQAdnHcVFnLMqNOZdqm-U0z_wCTboek81cwg3oCzix9Ib9WaLSrsPe37VJKo2T6 + type: encrypted_content + id: amsg_01a1686a-9943-7720-a427-c52e5ad9a500 + recipient: /root/security_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-b4e4d375f9b6642d + id: maco_01a1686a-9943-7720-a427-c546658a5646 + output: + - annotations: [] + text: '{"task_name":"/root/security_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'Now I need to spawn the third agent for missing tests. + + ' + type: reasoning_text + id: rs_a4f8b08deb9728ad + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"missing_tests","message":"enc_o_seguRKsKQ1RCWPj1Sk26Y6IfY_KAddIexL-gSIIA_FY8XV-5_VEsjrL_Byy4P9cpbjcgCip9Mgh9BMTQ0Fxa-9x5hReTadtHK9yoqXTLVdCixQU1XSZcvMF0GLqlYvMizKZ9QCdXhYEQmcHQjYjySKp-zoRYwmvn9_IOGOmNwTMu5cgE-O5dcLVIDsaadq2CdivY7qCv7pF5RpMHl4YoFdP_88RXFQv8aTCyMGOOAkYfTYVOD_wzo7at1t043IzzYV-XSXbeMn0TzHucEvves6D7HDGZy39Nt9lugBsnxgopEZlE6-1OhiYO7kZZozIWyZlgw5wxkZNJ668d1wr8zxLkWeTf14VcnV353f5nCj3nT93ZvSbMo0fAaVIKOgN7Qg0NA5BnH-2sUrn2fhXedPuG3j2dt3c6Yo5sFKvgJouMfqi-U_4xpY9XJhuzgsLi17lSdjbvgBD1NjAFelyTVC9aM1p0UN-X-O0Z9mgv7i6kEDqbcWyNFKNNU6Az1fgCJLDoyOvg0JBKkbKhri4AU32FUCsTcCBzLG1SbgHW0Kq3s7bvzRLhx3PGKKsPX2Pfcc7NfZ-i_U49CXq2hJaaOkWuxbizdKJrydJRVNJCxcqzOqJdls4gsnIz2JA1u9AH-e0ZObbHoY-iYkfgQKRhJgCiZG4dbFrj5s4glbmBkopPNt8k5RsUPHTf7f0NlgE0YfjrA6tXUbdhoWV2_7GP61TaSGECYCZxKtwiUO6MYd4G_x9LccJCQ09cbpF6XHKBKEg9WXImigKc34-Xje1gjMsdzIv3BzRo3wv5JLo8bOcZAplppaH8nbmWeFCNlJlqeaHpxFxYHW4dQqZfRBO4gzLhdY9tfn90AI87ziot2tBsHwYf3z1Fr584BD-cxGb7nM6FRS16lwekXHs4MRhZFp6kJ0lDxk5yv9iW1v21ugk8JkeUq7mUTjvbSq-99dw03h_a7QNLGfdwEiH6ERguwWG12azXL8hBypGo4cmRYx8L4db6cSUnFNdYSb6Dc--au1W0-66cw1rr5lFNwDlH5cChWYdyeahRsj-Or3epQIBy-8a625843_wiK9GIwcVUaX6o3Yo1PuWRaTWOFainEIi8sKotV-65gAo8dTzZzxEggEbzLzUj8uV2upW6Ze5UT0kpkcvp19HA9mOuGl8aJIpbiOA4EGis4O3MJnuod4vmH8Zxs2-d_yz0HSVwEvo5f3IIHyzE12gHo6629PgAd4NniJsSsFRsSrzu7UmNpKHDlU24bDriyQbIvxF75Ya5t0uTX0QoYaAMVs0g2oy62vt3waoJYH2k07C-v-21iYW0yE_7ES9t5EUc8-M67jdqoj2xHbdpNzm5ArcMn6_sHS6g2FSP3b18gmHuDwKbmn8DpoCd_M2f08Z4RgH-cOV45qI_0TxmJbDO-EkYctXZXauPUJEVYDz8mEfJJgWAquPPo1a7fll5hf1rxfqyE78ZY3-rckjYejcoX7T6eUZdszKYk63rJHtk2Pj0oYnDHa_KXZ2VP5Cw","fork_turns":"all"}' + call_id: chatcmpl-tool-82d8e28a739efcd7 + id: mac_01a1686a-b7fe-73b0-a4a0-c57f7b509b26 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + author: /root + content: + - encrypted_content: enc_L4HqwAlVvY2TgvtD7HRK1NbwNShm7a5xYcZaBpJYZqxXop-C8VGhZTvbP6kJkdd4B6ynnB83DX97YrpMeLXw1DBQZvnIbwqNPAGnl8UD_XIpirWgJxtSAD2hCrqUMnKaX7EvgYUCOUpQkJQcKIF2oiOaXJswW5dse5Z6JKBRX6iPY87iijF_AdviypcH-MnTuX_0paLfDmeaQNUKbqKjLyLAq3YqpRmU3jAMEqGIlBS1sxmmoC0f_RYVJZaSBXu0W0gHgS2QD7DsAQRlnliyuo5KGwJriuVXVCLIjjgd13w2Cc6j5UM6p9vpH_Eh-UV_dy71XYmFDoVJltpRv2oqtzWM94R8dfqdxOw0c8zFjEva2KbfKaz7R-uQ1BvPiCp41PFWSp2iIAX19fGVEV4EOLYCKlNjOeBxotRmKK4V-6fp7lJPgPDSLQ7OdtFatP8ypqVdx_0a2weGzvKXBIPmLmT696VRAZllWC_HPRboN-G3G5efD-IhjLsejxAD_ECSFj2NpWC2XjsIdahdaEPFvf0Xo5Hx6mP67dvM011SESyLUQ0DlOyiBf4iVNf5itIgcfdvCzNMABl5UqRCzcNZ-5RvkzcqMTGyKh1Ho_FD5wU22MWn9okYpg7ZaFl5eivty4jBp6OKvWibxha8q5rnrWzFmLuDqV0Hi3A7FoFFDiMah6LW9vg4QHcp4eLs63uj9cWLxGXSLr6D2iokiB5LujiqOoguA0pbUCaVhR4ltB1U9czdj9A6W8-tlqZB4_iJgC658m8S7l_LT6wVgkGUXZ_wElPe5QyR8qfEmzKRqJTwdR600satDua9quEDkucsKhahqkKXGAyvtsCNc9s-fjNK9GPvImVZtmNsgpd97LibPcrtmLnnmr52pUBA1APbAx5CckOYYM_1cGqY4orKRR8rK16RQQ6-D4oExyCaCWaPQNTkFUdl8j7zsm-FrLPt9F3I67PAo73TDbzQS9dl3grC4N-LscgpDZOlV_qYtxcyM2gw5ORHzrn024zQDd8iAhaHgVuQhdN5uLbwUehTgz3PpQhs9Le7QwC7iix2IQwTk9P16KtM0KABfjcGMRskbqJ5E8MhldMNJyJnk-mwhgAwVavnu1OKr_6dLv-wFDb2DRe3AjGIK-NQXPzgSL8u-QyogmO4DcHrKHQJBV1l1KlSIF2qPqugQjDxTXuNAfX1tkVSE-Xw8NoQ5npYsRGAtmVAVfoHAQ_9xYOrSiYnxVinW2qDxkaG5bvIUd2j756lfcufqqAOn6UyyPI_mSeFzr_kfXdD2ZiM5OSUOFLGYijLivmjPUAvcvFe8fXWcFKdrVv3R4rXEDyakdvN8p8yz3O-Dmw8PBbMYdpZiuwZlbPqFRw6AAFdxIQlVQgNZjK9VPjO7MREcG5XzsFeGT4ONHfThWF0qJdXSCW0tvtt8Gf8MicEAfBXSn1E8oee__8i_zk_IA-0c8qqbLItZC_hVvaUDK58u1ecNkESHIsmondY8QkAakE3WXdPcpplZbe58qrC6VMDlA + type: encrypted_content + id: amsg_01a1686a-b7fe-73b0-a4a0-c59f62294d20 + recipient: /root/missing_tests + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-82d8e28a739efcd7 + id: maco_01a1686a-b7fe-73b0-a4a0-c5b1e5241864 + output: + - annotations: [] + text: '{"task_name":"/root/missing_tests"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'All three agents are now spawned. I need to wait for their results. + Let me wait for the first one to complete. + + ' + type: reasoning_text + id: rs_890c73a079af7523 + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: All three review agents are running. Let me collect their results. + type: output_text + id: msg_a8000a4038f8c579 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-a9a3f9406d5895cb + id: mac_01a1686a-c466-7ec3-9817-b33adeb90186 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + content: + - text: "My assignment is to review the Python code for MISSING TESTS. Let\ + \ me analyze the code carefully and identify all the test scenarios that\ + \ are missing.\n\nThe code:\n```python\nimport pickle\nCACHE = {}\ndef\ + \ load_profile(user_id, payload):\n if user_id not in CACHE:\n \ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\nContext:\n- `payload` is bytes from an unauthenticated HTTP request\n\ + - `user_id` identifies the profile\n- The result is a mutable dict returned\ + \ directly\n- The cache is a module-level global with no expiration or\ + \ eviction\n\nExisting test: One test that loads a single valid payload\ + \ for a single user and asserts the returned dict is correct.\n\nLet me\ + \ think through all the missing test scenarios systematically:\n\n**Caching\ + \ Semantics:**\n1. Second call with same user_id should return cached\ + \ data (not re-deserialize)\n2. Second call with same user_id but DIFFERENT\ + \ payload should still return the original cached data (first-write-wins)\n\ + 3. Different user_ids should get independent cache entries\n4. The cache\ + \ should return the same object (identity) for the same user_id across\ + \ calls\n\n**Edge Cases:**\n5. Empty user_id string\n6. Very long user_id\n\ + 7. Non-string user_id (e.g., integer, bytes)\n8. Empty payload bytes (b\"\ + \")\n9. Payload that deserializes to None\n10. Payload that deserializes\ + \ to a non-dict (e.g., a string, a list, an integer)\n11. Payload that\ + \ deserializes to an empty dict\n\n**Error Handling:**\n12. Malformed/invalid\ + \ pickle data (raises pickle.UnpicklingError or similar)\n13. Payload\ + \ that raises an exception during unpickling\n14. What happens if pickle.loads\ + \ returns something that has no dict-like behavior\n\n**Concurrency:**\n\ + 15. Two threads calling load_profile with the same user_id simultaneously\ + \ — should only deserialize once, both get same object\n16. Two threads\ + \ calling with different user_ids — both should succeed independently\n\ + 17. Thread-safety of the CACHE dict itself (check-then-act race)\n\n**Cache\ + \ Mutation / Shared State:**\n18. Mutating the returned dict should affect\ + \ subsequent calls (since it's the same object) — this documents the current\ + \ behavior (or the bug)\n19. Mutating the returned dict should NOT affect\ + \ other users' cached data\n\n**Unbounded Growth:**\n20. Many unique user_ids\ + \ cause CACHE to grow without bound (documents the leak)\n\n**Type/Contract\ + \ Issues:**\n21. payload is None instead of bytes\n22. payload is a string\ + \ instead of bytes\n23. user_id is None\n\nLet me organize these into\ + \ a clear, structured report. I should be specific about inputs and expected\ + \ outputs.\n\nLet me also think about what \"first-write-wins\" means\ + \ — if user \"alice\" first sends payload A, then later sends payload\ + \ B, the function should still return the deserialized A. This is a critical\ + \ caching semantic that's untested.\n\nAlso, the check-then-act pattern\ + \ (if user_id not in CACHE: CACHE[user_id] = ...) is not atomic. Under\ + \ concurrency, two threads could both see user_id not in CACHE, both call\ + \ pickle.loads, and one overwrites the other. The final value depends\ + \ on which thread's assignment executes last. This is a real race condition.\n\ + \nLet me write up the full report now.\n" + type: reasoning_text + id: rs_ac7b4b15cd0776ac + summary: [] + type: reasoning + - action: list_agents + agent: + agent_name: /root/missing_tests + arguments: '{}' + call_id: chatcmpl-tool-80af08d7ee382b50 + id: mac_01a1686b-0032-7e60-85df-241ed3ee0676 + type: multi_agent_call + - action: list_agents + agent: + agent_name: /root/missing_tests + call_id: chatcmpl-tool-80af08d7ee382b50 + id: maco_01a1686b-0032-7e60-85df-24334e05366f + output: + - annotations: [] + text: '{"agents":[{"agent_name":"/root","agent_status":"running"},{"agent_name":"/root/correctness_review","agent_status":"running"},{"agent_name":"/root/security_review","agent_status":"running"},{"agent_name":"/root/missing_tests","agent_status":"running"}]}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/missing_tests + content: + - text: 'Good, I can see my siblings are running. My assignment is clear — + I need to produce my own findings on missing tests. Let me write a comprehensive + report now. I don''t need to wait for the other agents since my task is + independent. + + + Let me structure my findings carefully. + + ' + type: reasoning_text + id: rs_abd432a548919a85 + summary: [] + type: reasoning + - agent: + agent_name: /root/missing_tests + content: + - annotations: [] + text: I have a clear, independent assignment. Let me produce my findings + directly. + type: output_text + id: msg_b5a39e4b8a98e219 + role: assistant + status: completed + type: message + - action: spawn_agent + agent: + agent_name: /root/missing_tests + arguments: '{"task_name":"missing_tests","message":"enc_crV_LNJ2itOXlJp5Jm5jsDg9LPvKi2I-jJE_94dGv3Dl3xr4mIAGBW2gpFMPYEXUpiYwJtX9R5fT9rek4KQD8ok7PrLOiXRKpT7RTztHUfX8ZXCKhrWYFSvw5EuFaH-jqPJX9ilyOE44RcKmGnczCsbxMopO5hFv6I4L1FbEl872OYiWSBTxroOXqJ3VMBqppVamPDh9MPTACIFIZMQNsrJXTYaI96DOagfVuqdMqQ5lXn6NIaX338Eov9akSDO36ZYgOps_zOJBF6Od-54LFtK_IKdBQXOfZ3K7_Ia-6MeG_FCdgu1Dqgd2oTP5L0dKLoHCMDbzRatGnAAyXtXza64Jltv_4xidBHV3KD86F2C7KZ4fgBfoWBnvNF94S7AGzbtCEBUCcWNSLvsgvGOlGGOpQGgeqvJoP5eh7ztf6a33lNQRw4hQqovIa0yu2PdmWBi4IgObuE2BgYppz2_aDUpNce4fptBcZ5EUM3OrLC3kRcJW0CTHeYqwPGNq54fXQE-dJz2unYwUxOyALXb16j2jh01N0dXxNlDAwyvmMg8mRaKFaXPn3JTbJPMDw0zLkfYoKjnnU3NdtHMLjrtFEZNfysbYl3ehQYbCrFrTLGaK2U-2aJBJDMyD9YZmud2DzILEgHkMn6LRS9SyYhdlmW-BlR8J0i8o3hNT_ZTNFiYHofi4TDgyJCyBt7mrMQFdj0JKvPo1gAs_zsyWS66K7UOjT9pNvec7SS5QPrJOl-euesZ-pLhRLoQXZcmM2XLThxml557AyZvnWS0s0_xt55vhmBtD_yuzeb47LZ5bVuih3BQ-W6K7kYNMV9TZi5pzjfblGQxq-0QHh8d1gepMDMa1IqMd-x8ucVwd0iRx5GMIHoPje52vr-UeflTIRI5itTqTLmzL7o5Fv9-pOzBVuRcGMQ3UqNw59CiKdiI2czPz47aIJv9H6ST8EnSBDKe4wFBuCHQefd4flXSmikiocP6y_gQB10AQymOsgqHvR-qFi3yRtxGqA510JxAnBSE4_16JmaytwABos5B7Y8hlGoQRKOfaHk4Px_crzZ-5ISOwTxAEmovffamKBWELCMvpCgqiEFm94ij1aT0sD4adNS-pCz2pd84rlYKy1ClqgkDwUytsR2UCXE9iBhLWsU1rUwusPisrNMOlA37m_e2cOI1bapXPagkFCaVUd1Wv9hFqaIPIntw2N5tApCEV6Kfe22KJ6k1SPcRYL-NA68sgtLhiV9ayOAJAyhythhyXnwC5fTGfcJi3ukuXnN8OfKJpblWAbVMSf5vPyl0oXJbVWDaAHkFbsWfCknpstel94q61AP_gn3PKF-gJtvftTR3CvoqYkI-TzSgVbGBfjLlC14cErU7ZS_NESKhbVbGExdxIAHruSESyM0oasGMFswUIqUv1DRsi8V3NS3kZTgti0TtF3Z8sNzBQhLuHruPafCpP4wRHk0E6C75qBGQOWqKafVZUkMpEyLP1aXbMd06Gn3czT8NV8ICZamJbopScVb3ZB744btNeznfjF6dtfOaww2ySbsbPMA7EVXPLfEB4UMqg9nc15qgt_fHw","fork_turns":"all"}' + call_id: chatcmpl-tool-8dc9ece48552be04 + id: mac_01a1686b-222d-71c3-9209-f5773e2997ef + type: multi_agent_call + - action: spawn_agent + agent: + agent_name: /root/missing_tests + call_id: chatcmpl-tool-8dc9ece48552be04 + id: maco_01a1686b-222d-71c3-9209-f5901356dd29 + output: + - annotations: [] + text: '{"error":"maximum concurrent subagent turns reached"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/security_review + content: + - text: 'I''m the security_review agent. I have a clear, bounded assignment: + review the cache.py code for security vulnerabilities only. Let me analyze + the code directly and return my findings. I don''t need to spawn any subagents + for this — it''s a small code snippet and I can complete it myself. + + + Let me analyze the security issues: + + + 1. **Pickle deserialization of untrusted input (Critical)** - `pickle.loads(payload)` + on line 5 where payload comes from an unauthenticated HTTP request. This + is a classic RCE vector. An attacker can craft a malicious pickle payload + that executes arbitrary code when deserialized. + + + 2. **No authentication/authorization (Critical)** - The function is called + with data from an unauthenticated HTTP request. There''s no verification + that the caller is who they claim to be. Any unauthenticated user can + load any profile. + + + 3. **Cache poisoning (High)** - Line 4-5: The first request for a given + `user_id` wins. Since there''s no authentication, an attacker can poison + the cache for any user_id by being the first to call with that user_id. + Subsequent legitimate requests for that user_id will get the attacker''s + poisoned data. + + + 4. **Information disclosure via shared mutable state (High)** - Line 6: + The function returns the cached mutable dict directly. If the caller (or + downstream code) modifies this dict, it modifies the shared CACHE entry, + affecting all future callers for that user_id. Combined with the lack + of authentication, this means any attacker who calls first can modify + the data that all subsequent callers see. + + + 5. **Unbounded memory growth / DoS (Medium)** - Lines 2, 4-5: CACHE is + a global dict with no eviction policy, no size limit, and no expiration. + An attacker can send requests with many unique user_ids to exhaust server + memory (DoS). + + + 6. **User enumeration (Low/Medium)** - If the application returns different + responses for cached vs uncached user_ids (e.g., timing differences or + different error messages), an attacker could enumerate valid user_ids. + + + Let me structure these findings clearly. + + ' + type: reasoning_text + id: rs_9818f9bb42a2249c + summary: [] + type: reasoning + - agent: + agent_name: /root/security_review + content: + - annotations: [] + text: ' + + + ## Security Review: `cache.py` + + + ### 1. Remote Code Execution via Unsafe Deserialization + + - **Severity:** Critical + + - **Lines:** 5 + + - **Problem:** `pickle.loads(payload)` deserializes attacker-controlled + bytes from an unauthenticated HTTP request. Pickle is not a safe serialization + format — a crafted payload can instantiate arbitrary objects and execute + arbitrary code during `__reduce__` / `__setstate__`. This is a well-known + RCE vector (OWASP A03:2021 – Injection). + + - **Mitigation:** Never use `pickle.loads` on untrusted input. Replace + with a safe format such as JSON (`json.loads`), MessagePack, or Protocol + Buffers. If pickle must be retained internally, restrict it to server-to-server + channels with mutual TLS and integrity verification. + + + --- + + + ### 2. Missing Authentication / Authorization + + - **Severity:** Critical + + - **Lines:** 3 (function signature), entire file + + - **Problem:** The function is invoked with `payload` and `user_id` from + an **unauthenticated** HTTP request. No identity verification or access-control + check exists. Any anonymous caller can (a) trigger deserialization of + arbitrary bytes, (b) read any user''s profile, and (c) write arbitrary + data into the cache under any `user_id`. This violates OWASP A01:2021 + – Broken Access Control. + + - **Mitigation:** Authenticate the caller (session token, OAuth, API key) + and enforce an authorization check that the authenticated principal is + allowed to load the profile for the requested `user_id` (typically `caller + == user_id` or an explicit ACL). + + + --- + + + ### 3. Cache Poisoning (Cross-User) + + - **Severity:** High + + - **Lines:** 4–5 + + - **Problem:** The first request that supplies a given `user_id` populates + the cache, and subsequent requests for the same `user_id` return the cached + value without re-validation. Because the input is unauthenticated, an + attacker can send the first request for a victim''s `user_id` with attacker-chosen + `payload` bytes. All later legitimate requests for that victim then receive + the attacker''s poisoned data. This is a classic cache-poisoning / confusion + attack. + + - **Mitigation:** Derive the cache key from a **trusted** identity (e.g., + a signed session token) rather than a client-supplied `user_id`. Alternatively, + tag each cache entry with the authenticated principal who created it and + validate ownership on read. Invalidate or re-fetch if the authenticated + principal does not match. + + + --- + + + ### 4. Information Disclosure & Tampering via Shared Mutable State + + - **Severity:** High + + - **Lines:** 2, 6 + + - **Problem:** `CACHE` is a process-global mutable dict. Line 6 returns + the **same dict object** to the caller. Any downstream mutation of the + returned dict (e.g., `profile["role"] = "admin"`) silently rewrites the + cached entry for all future callers. Combined with the lack of authentication, + an attacker who calls `load_profile` first can mutate the shared dict + and thereby alter data seen by other users'' requests. + + - **Mitigation:** Return a **deep copy** (`copy.deepcopy(CACHE[user_id])`) + or a frozen/immutable structure. Alternatively, serialize to a read-only + view before returning. Ensure the cache stores canonical, server-validated + data only. + + + --- + + + ### 5. Unbounded Memory Growth (Denial of Service) + + - **Severity:** Medium + + - **Lines:** 2, 4–5 + + - **Problem:** `CACHE` has no size limit, no TTL, and no eviction policy. + An unauthenticated attacker can issue requests with millions of distinct + `user_id` values, each storing an arbitrarily large deserialized object, + exhausting process memory and crashing the server (OWASP A05:2021 – Security + Misconfiguration / A04:2021 – Insecure Design). + + - **Mitigation:** Use a bounded cache (e.g., `functools.lru_cache` with + `maxsize`, `cachetools.TTLCache`, or a Redis/Memcached backend with `MAXMEMORY` + and an eviction policy). Enforce a maximum payload size at the HTTP layer + before deserialization. + + + --- + + + ### 6. User Enumeration + + - **Severity:** Low + + - **Lines:** 4, 6 + + - **Problem:** If the application surface exposes observable differences + between a cache hit and a cache miss (timing, error messages, response + size), an unauthenticated attacker can probe `user_id` values to enumerate + which identifiers are valid. + + - **Mitigation:** Normalize response timing and error messages regardless + of cache state. Ideally, the access-control fix in Finding 2 eliminates + this entirely. + + + --- + + + ### Summary Table + + + | # | Finding | Severity | Primary Line(s) | + + |---|---------|----------|-----------------| + + | 1 | RCE via `pickle.loads` on untrusted input | Critical | 5 | + + | 2 | No authentication / authorization | Critical | 3 (whole file) | + + | 3 | Cross-user cache poisoning | High | 4–5 | + + | 4 | Shared mutable state → tampering & info leak | High | 2, 6 | + + | 5 | Unbounded cache → memory DoS | Medium | 2, 4–5 | + + | 6 | User enumeration via cache state | Low | 4, 6 |' + type: output_text + id: msg_b068d7e90b024f84 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/security_review + content: + - encrypted_content: enc_xdqHKtKU03YmCjc7bQxN1aKKKOlI91CLE4TLr8NvJDYryiz4GH2QfUmRLovHAKy6djCIbl3SRtuR-6wpp99EPSY8_cBmgY-7OVqLRGFkjpSeiY5pmHScsIYTTt4hvY-aTdzbGMRQf2tXfnE3jce3r0To4HtcSSGwSjmvzfn_PpjpgAn-i1q6tVK2haHsX8OaV82cOc9ZQ4_qZVl39HnvRoqgWlN3PPmyL50VZGsTnEsra3xRpwsdtu8FGc_UJpPlQOiGJvd_-4cQX8Ov6Zd2AFoMhz7UXkvxsE2Vm3R8g6ulz0iufAh4ceJOl-r8uUGV5BqA_PdAQABl74Om68ulhPE_HVQsmtRlY27GwAxDVSREZd173ozun81Xi0qkeMNljFy0FzquRBMgUpMQQKLtWJA9qI9MW6iXDFbShC9twKvDMjp6oAcDAjCkjc8YN9kekz2kpNri9w6DBsjcsFtgGD5V6FWvLEJr1XDYoLrgu3OahRnecyolcxCGkFASSPDPHHFqgujVvAWwMMdYCHBE2QdtUBA6rbEk9kiTspjTQqrn8v1TCZ6veT3TUg2t0sgX-4hR0RceMNcipYATQspcXFGJg45lqlsAbeb-J9indPtNpSxV2JVjNS7Yz7ArsulMbqlvudogD1cPg469upGhr3gPO_I5DEDHoANBDC_VnROQR2xp2EidueGLwA-Lp9n4rVVe79fX5IZ80xmBepYVj35tlwXXGjSsV62XeGuE51b8Cuz7e1XP1SdFoRVPSwtb3mBoSz6VejPZgsheNFeiF29N3vklJm-TYdTHCxJIzAs257gEbThW_GJH1c14jb1e7YCrloMXujIao5OV6VYM7JSkuKUd4b6Q3jTI1FsURQMYNZs56m4c_ZFAEgn67sHcl9HyHC2rvOpC8JZhQVua2iPIXxI_P81P9bvlE78SmP_nE2TeS-k0uCGJgagQ3-smx21_zPWBgkJOdSRdTer17d7qeA05iSWonWHEpU0ux6T4B_V3jkbg157LICoixAie0TTtn8rrsZczgk2GBbfvG1kHbRhhsc9jAj53NhdMfS0yW4lxqMAkLWH0djhw4T0O1fAAa21EosY05-rSR_GuNisAtsVic8t_U4nLJHwSF8i6zdrD8n9N2g43avy8NghJvat5XTxFWWOZR16I9G-veRs5F5i_7WEqK_X2BUR8CB7DywtDfXgYF2vXwSy4-vgLvyB2LkAyD7SQp_WyEoDVx6VS8Awpifg3aecbXnKMBxRjQpbwOFWPVcSmRTbqanHjK8CENjo3SiImVO8U1B3zcTlSus9RedaqH25nE07ozfWFo-vUG6-kK9Uxs-YsP53GAvTpZzZB98ih2utVFMcYvarsFSFEobnT0eRKlOctD2VMAK0bATzMhfSTcWoK0Uy1DhuyI5yJLaeQi_Gr22LgODMtebuSOkNVlebph3d0IesSk7GOASEUYeO1DzGitwqRhIbq_6iSYvbH0tmEVXQvWaCIaATmrQM659xbKRszoovHG2dFMalkyA0kMV3kXCHrORIutpf_RAAlfvK3SK-82ocOwMNZBvdUQTg_qqoJ-HmtkCo83jdK5DqR4tqPBnd_bAE0oNYLdt4TFlEZkOfxHRsH7nb1e9IhLEUpzU5e2rJlImGXNVjCrUKRG9sXJJxhEZisOaUIJH-EVbA9OyoRGeOH_aItYWEGjvouIi8WWp2oRfy6CwdPUE873HGr9o_BA05Jhkv9-WPuoJWAGVfjwlf3YVHC9713Gb6g61nMky2WNIkFJU0fPHKK1cnazc-lRCsxCTiWxG7oRQ3C-A-xTKwa8Gp-cFJpD_w2I_4dCMwfWruWNvL_-wch5xGoYip02htUJrMZv0E5KPvtYY9IJts8vpSOLfHzbm_-OTuXjIS5UxatNxNUPCkJCLvF1lB7OuoeaDqdKGkdqRjbTK-hpCljEw8_Z6DhCBoLZ2k7dpax2ODKd9jRNHHDrwr5ZQikDzWeA0Z5bF3lDO-w6zfUALSkkdkAMYy77qdMysK1XLAwfyanJ6JrCixmQa0kutJwN8HtkK-roWZ5cUR-Z1Q8ztMuxrlMpuZ2fHBAw5E_QS4us3Dzju3KZauY8p6RKgrPIiprxxGoSF5gjW8lAyFGitx62dIxZCquAHTITDVpfjU-J5FmH5qPjl0Ff4Sjtg_bp3xlQb3sD9oEcIsSm9_t-s9EIIhWP8lCvS6CkPf4YRQHDbCV-uE9x7ZIdsVgVBo8U066CfHngVqFrHMrgiDlCGe83-mnzCKOViJylzrHDc_exP3HIauY0aB7_g8P77Xiuns4tPDA1J59dIHTtCWh2Mw1tJpaG-cjbAvRyO8M_hsu0YtY_2S-OxnryDOvzO6uEnZ-uu-1-Inl-CaO3xUrJ3pD2wuiHY5F1smQASZCq55S6xAhMNpsLbEDigwWnTr8MMrcaDJRiUPI0GJykcxQrTd5lUUQS2b7_GexafNqCdlCScNSy-MsEWloY1FYfaVVfEhmjLBljiYde06fVJ7gQB2_ZjM2BhmvMcF-t0iH2ZxI_pzG_HJwchrV-fX4wcD9KfypwOgRppF5jIKku0pP4u7YKUtIYUhBVxuzt72SlzjVfNe8TVW522lQoHcin9jajwY5kRfH-gGPzRmWKVT1Mvjpla3Vi3fPEdzcm1R81rvp0K2wtLganzmVjwn4hCWf01y1Ta5oVLBMuLGb2B-wzFnBnYPUbjESvkAp480ZHG6eJXnittaicloKJXq6UamM4k5-XZ4PLjKpbmy6BAOHvwVIFuQEbUE1C4j3pbnpvk9jaAmAYizGMrdqIFV67p7GLTXHQe9JAWN47k3-S9HoVLUo4PHC54TP9621pxUbK86F-4Ye3F5ufY-MBA-QUo_smrugsMkFqO7uktKfrd0eNtjFTw8XFX3t5jF1tLvg8Fxkp_m_F2bKLcVJVBlsO_TV1NqDWkF5h2QzwlgnGg4ITm_pGG0_RJIezY9dV2cfses-br9EItp8DgoVErLMLFdp7IjfF3TvrBAo7KV_inErP3WFjKeaII-F5rV9ySZrIn2bpSfFIou-a-z2Mhoud1Rk4pJNJJc0cAsrIE38-fkjUPpSSLue7ozc3YuLoU-41wgBBQ6r3f6WEcErK32RdgJVQKlpqCAX3PQtXwU33j4v51EmNzQqOin1Kq2VVvlBebrxRyWHlbxDlsY9orCJ-__NVvddwCzuNDrVwnk2COvD5pi6gO1LBpf7nuufyqvJNAT7AKZ_mBpGr5jxzzQgEXkJ3d8znpcKrw0300PloyoFrC01a27yVzqGzfxUijaK9Pqmy3L_cjDUYtw9Z0C5ZfA5GqK90PvzDn_hvTMCxEhz8crYvO3AneNxiYMjeUE25QCPMTv3yAhUsAruVWQwwsy3PCbAOx7gpcuyEtuFsWuQzkBaYi7TzINoNXLIX6hVZip4mH8tTSV9qHH4joP1KTmw8tB1nR2Wv73NmNpku7GVojzqSPWNAA-vD6BTbakAhJDdhYiVlaTFxX8GX4U1mlr0dQ-_YIYidkAp0_Sf0U-ybsYTYhTXj8Xl8lX-LQu-zsmp3BhIMlPcMLV3cBmMOxOs3XTlximmL8FUd-tIy80HJ_ycGrnLJeKpdxCA2YIPoRMLDTOjP7au4QvtGwimvHaHWHjVIOweAfWg14HXMIw47SATKZ_tpxZ42HDNPNWOgdzhxKBqvLVuz5Z0tyVohO_AstzBp9hEpgrzwSAWYtbcHkJV3fzpCeM7kq_uCXLVz_JsRusp-c14BQGUa339uomv2wtrI7e3riXlfXJB1QVKktuunOf8QLKG-c-WmiVTjx4RqkdcwWmqdKM_FgNYDEA1YgFEVUcuH5eSbynJWonp9O3AMfpExZ3h-JeYoplB5mnI2t_Xdu23Uf3AtHeSVen8iE1-wrpvZVrjr6n7ZTiQWFR--9bHuIYNnoj_g1PzLc0y4Tnroadgri2ar8GptD5BRMU27HN48f4NIdZCa9YaigY2voTBT3TLtU5KfwF6bLQkWDw__SajgeG8KHuusPOaFUIv9Oz1W1yiH5zNm0ccuXN2_MXbhSEE1ECqOS4bWaxldyS6RbsEyVXVrq4grAGZGgw0WIOLcnz6RWOH-YMQir84h_VKKWbfnI_9EL8YiYm4398eHLF9IvF38dR-YzGqLeVHOJMtmR3fhkPFjQy9SMpTnt5Yqg39DfmwxcuV8ncV0hjLM7EMDcHqY9hxaqYf-meqwUJNncYd2sQ1SHPpoi_PLasyUQTGcvHMtEWJkrnQW-fpmM1x48tTzySEoXVQKxbJzsnsaNtqrBMtJTWT67R4ZTKiN0x-cLw66mqPOv6J-JCE7Vhs26IVRaK7FBCAAJphyxW4VRxewKqIdqFCNvD3mcKfp0F2moQlNrIC-TaqU-yZbbbnHDaAchWDQZRUiwzvKByZmFgj7GvVx_4XPgpDddWTaraS0s6X2GMk5Wpqy2iWO2CZlmNHWgbaH5Pa64CzB69-ReGEqw0ixbpW7rYKSTza3oXppzll4W9gj7g9vTkZzd6UhO0hYb2e9ypHznnMvesvJdKEji93T3aJqMBFO_VUOmLvgUxgwyzNFvvNQdsLq--jym8AQkrZWWluggxHPmrgqK-HTv9RVhmXSUFLy7xiFOvZiyLash6QfCIu3HHFRmmGHJ0acK_oInmlQkG89h3NvsPB_VTbBokXYMdJkLzX93j4Ly8JzTB1k_y8wvVr3tTuH9I_-FbsjqYqNhjOKolOQJINb3TBNg6PMYYOPXeCXFIaFUdTEbHlfE4enTPQAFiJSBfKUOYgxM1Zdti0phT6ky4cVx-_mZukjGCERfSoN9WgvhsvS8oMUwW9XdWx9L0qAvpPYuvjeNhE9fGcCOI7QOPRhTsW25MPP-96rk1p1spNjzvqrwfjaejhfn8JgVpNFNWSa4OD1s_U5FjeFyamHut0MgfEGD02znr9GlVW7_A5wgAOoquJ9YB8adjaMbaTl9CPdMDdLkkB1-Q0xx3pj3c7Vzeuu3v3nG_thPl8DW2Yl7KAbPY6TrNbxLdR5oVgE2V9KA80yUXIuSsh7281oz8o8PX39qgl9HAGm5tL0I5ouSZtyHokcKdZrkAT1iWmY6aG9LNx3mU3wrDgVVKj8lsHKpy2z3RlZxknpNld5gBBN7TeGbnZq5k0FlnCJ1RCeMqG32n10CfQPSSNEJ56onwpWXI0Xi0GxLHlisSsmQYGkhmCjuXFDlrsLq-JXyzdEK99kHGFh4R4U71BixWkdTrGTCxw1GbViTIfhIKH8YeRpJ2JD2UNq1_3yKZ5Kv5pXkKPFoCYKwY1RcxejBP8cgoovZe2SlnSBOaQF4qjyn68smoFcfuW3oO9peC_rnM_YcL_wz5J5r2SH0KPCm_l-cZyt5KwSsx5DWwo8-_DJRUYnGUVlXH28iZ7zkiMEP7BT63iXmEr4abj7NfQrCU1wYNQ7p-00zlLInIrVQyf0kYDuHdHy_uiVbCMaj-NpuhcYV3NdUzINOd65_68BibEf0-iz-_YgnUYcS25ZyQZxlzrCQLJSFCFp2d47JILC2lZekpGPRZhlczYVwwI8_NsSCAy_cdNEFk15pPfVAKYY4zjjLy5SSPQJjzh4zB7UXfQjUVydbaOkEiA9QzA0zMhhXrnBlgELOIRuFmE_1GqtBFVxLz-VtxsSVelQhVqP3hh8AfR7cfzkRWw2dHE-DS3gsEmtVupl_B04cBbsTDZTUMhcuEW_rZkqZII9Gp07HuUXRE-Vkk7-JQuKiln0keMlizCUB4XGbH0t_l_IZUgoBCeBal0fEAU5DTAPPOy8Vs3lemncVqKP9fEbH7vWlYDuFMdKHBROWBC87AWsjVsl-gMKPCg45fz84O8TeptqiYRLIKHukukAaf53H8LK5YnfjbAr_qIqEoe94sTMZPuvR00bpJxezeEisD2IB1j2ZfeBHu6l4VD2QN6Vuu74p1Ud8CTEB1WZ9W1qEwbMbejryG4wDaMbSpgCuowmauzva4y__HtlvuCciVOt2ipH5h-WUN5KTb_FzEptBIJlwzigbPRNGqPzcAdBY4Klz66G1AK_n6ZptFr5qvQWzk2lpbp-w9HbKesDQgDo94htVvNlfybGPWrgv5F-TkVwHMtdh__Fl5AnKA8s_W5sYDMfXrv9XICdF-Szw_hp1QE_C9sgf3Aeubtr6mHSDZd2zwZ7-q8lrvEtlsglc6hPAjvpH1FvVfdCzAKXL6DKAYksKwR5-xRYtOOEjQwjwgVZTsVDC3mn0lSlCeMaj4Rg_dB1MkLXmoZGnkpX51z4YlWPcrvGRachBa3HIMMcspXP8urhwCvXbmqf5IT2o8hwlgUpPsVxDsY8Awb7odqVAs9svAil_l-dzgQU8vKMQbr93BlvSVAplgD_ovFL6GW8vQdJ704W7deqVKlmWJVpQO1GpcQw1BtXmZuVl5emODZVA7rei7Ccg + type: encrypted_content + id: amsg_01a1686b-2e93-7943-8ad3-9794a6eaedc2 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-a9a3f9406d5895cb + id: maco_01a1686b-2e93-7943-8ad3-97b16d5b741f + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The security review is complete. Now I need to wait for the other + two agents (correctness_review and missing_tests) to finish. + + ' + type: reasoning_text + id: rs_beb22deb778120fb + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: Security review is in. Waiting for the other two agents to complete. + type: output_text + id: msg_a619e1b0bc4a3674 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-ab98420abeedd69c + id: mac_01a1686b-3990-7ca1-bba3-1303431948c2 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + content: + - text: "The user wants me to review the Python code for correctness issues\ + \ only. Let me analyze the code carefully:\n\n```python\n1: import pickle\n\ + 2: CACHE = {}\n3: def load_profile(user_id, payload):\n4: if user_id\ + \ not in CACHE:\n5: CACHE[user_id] = pickle.loads(payload)\n6:\ + \ return CACHE[user_id]\n```\n\nContext: Called with `payload` bytes\ + \ from an HTTP request. `user_id` identifies which profile to cache.\n\ + \nLet me think through each focus area:\n\n1. **Logic bugs (race conditions,\ + \ TOCTOU, incorrect caching semantics)**\n\n - **TOCTOU / Race condition\ + \ on lines 4-5**: The check `if user_id not in CACHE` and the assignment\ + \ `CACHE[user_id] = pickle.loads(payload)` are not atomic. In a multithreaded\ + \ environment, two threads could both see `user_id not in CACHE` as True,\ + \ both call `pickle.loads(payload)`, and both assign to `CACHE[user_id]`.\ + \ This is a classic check-then-act race. While the final value might be\ + \ consistent (both write the same or different values), it means `pickle.loads`\ + \ could be called redundantly, and if the payloads differ (which they\ + \ can since they come from different HTTP requests), the result is non-deterministic.\n\ + \ \n - **Stale cache / incorrect caching semantics (lines 4-5)**:\ + \ The cache is \"set-and-never-update.\" Once a `user_id` is in `CACHE`,\ + \ subsequent calls with a different (updated) `payload` for the same `user_id`\ + \ will silently ignore the new payload and return the stale cached value.\ + \ This means if a user's profile is updated, the cache will serve the\ + \ old profile forever (until process restart). This is a behavioral correctness\ + \ issue — the function's contract is ambiguous but the behavior is clearly\ + \ \"first write wins, forever.\"\n\n2. **Thread-safety issues with the\ + \ global CACHE dict**\n\n - **Unprotected read-modify-write on the global\ + \ dict**: Python's GIL makes individual dict operations (like `__setitem__`\ + \ or `__contains__`) atomic at the bytecode level, but the compound operation\ + \ on lines 4-5 (check + set) is not atomic. In CPython, `if user_id not\ + \ in CACHE` compiles to a `CONTAINS_OP` followed by a conditional jump,\ + \ and `CACHE[user_id] = ...` is a separate `STORE_SUBSCR`. Between these,\ + \ another thread can interleave.\n \n - Also, `return CACHE[user_id]`\ + \ on line 6 — if another thread deletes the key between the check on line\ + \ 4 and the return on line 6, you'd get a `KeyError`. Though in this code\ + \ there's no deletion, it's worth noting the pattern is fragile.\n\n \ + \ - Actually, since there's no deletion in this code, the `KeyError`\ + \ on line 6 is not an immediate issue. But the race on lines 4-5 is real.\n\ + \n3. **Behavioral correctness (stale data)**\n\n - As noted above, once\ + \ cached, the value is never invalidated or updated. If an HTTP client\ + \ sends an updated profile for the same `user_id`, the new payload is\ + \ silently discarded. This is a correctness issue if the intent is to\ + \ reflect the latest profile.\n\n4. **Error handling gaps**\n\n - **Malformed\ + \ pickle data (line 5)**: `pickle.loads(payload)` can raise `pickle.UnpicklingError`,\ + \ `EOFError`, `ValueError`, or other exceptions if `payload` is malformed.\ + \ There's no try/except, so the function will propagate the exception\ + \ to the caller. Depending on the HTTP framework, this could result in\ + \ a 500 error. More subtly, if the unpickling partially succeeds before\ + \ failing, no state is corrupted (the CACHE assignment hasn't happened\ + \ yet), but the lack of handling means the caller must be prepared for\ + \ arbitrary exceptions.\n \n - **Unexpected types (line 5)**: `pickle.loads`\ + \ can return any Python object, not just a dict. If a malicious or buggy\ + \ client sends a payload that unpickles to a string, integer, or `None`,\ + \ then `CACHE[user_id]` will hold that type. Subsequent code that expects\ + \ a dict (e.g., `profile['name']`) will fail with a `TypeError`. There's\ + \ no type validation.\n \n - **`payload` could be `None` or non-bytes**:\ + \ If `payload` is `None` (e.g., an empty request body not properly handled\ + \ upstream), `pickle.loads(None)` raises `TypeError`. No validation of\ + \ input type.\n\n5. **Resource/leak issues that affect correctness**\n\ + \n - **Unbounded growth of CACHE (line 2)**: The global `CACHE` dict\ + \ grows without bound. Every unique `user_id` seen adds an entry that\ + \ is never evicted. In a long-running server, this is a memory leak. More\ + \ than a resource issue, this can affect correctness indirectly: if the\ + \ system is serving profiles for many users, memory pressure could cause\ + \ OOM kills, which is a liveness issue. But strictly as a correctness\ + \ issue in the caching semantics: the cache never expires, so it's both\ + \ a leak and a staleness problem.\n\nLet me structure these findings clearly.\n" + type: reasoning_text + id: rs_a510afee9ec596ea + summary: [] + type: reasoning + - agent: + agent_name: /root/correctness_review + content: + - annotations: [] + text: "\n\n## Correctness Review: `cache.py`\n\n### 1. TOCTOU / Check-Then-Act\ + \ Race (Lines 4–5)\n\n**Problem:** The membership test on line 4 and the\ + \ assignment on line 5 are not atomic. Under concurrent requests for the\ + \ same `user_id`, two threads can both observe `user_id not in CACHE`\ + \ as `True`, both deserialize, and both write. The final cached value\ + \ is non-deterministic if the two payloads differ.\n\n**Fix:** Use a lock,\ + \ or a thread-safe cache primitive (e.g., `threading.Lock` around the\ + \ check-and-set, or a `collections.defaultdict` + `Lock`). At minimum:\n\ + \n```python\nimport threading\n_CACHE_LOCK = threading.Lock()\n\ndef load_profile(user_id,\ + \ payload):\n with _CACHE_LOCK:\n if user_id not in CACHE:\n\ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\n---\n\n### 2. Stale / Never-Invalidated Cache (Lines 4–5)\n\n**Problem:**\ + \ The cache is \"first write wins, forever.\" Once a `user_id` is present,\ + \ any subsequent call with an updated `payload` is silently ignored and\ + \ the original (stale) value is returned. There is no TTL, no version\ + \ check, and no invalidation path. If the intent is to reflect the user's\ + \ current profile, this is incorrect.\n\n**Fix:** Either (a) add a TTL\ + \ / max-age and evict expired entries before the lookup, or (b) accept\ + \ an explicit `version` or `updated_at` field and re-cache when it changes,\ + \ or (c) rename the function and document that it intentionally returns\ + \ the *first* payload seen per key.\n\n---\n\n### 3. No Validation of\ + \ Deserialized Type (Line 5)\n\n**Problem:** `pickle.loads(payload)` can\ + \ return *any* Python object (int, str, list, `None`, …). The cache will\ + \ store whatever type is produced. Downstream code that assumes a dict\ + \ (e.g., `profile[\"name\"]`) will raise `TypeError`. The function gives\ + \ no signal that the cached value is not the expected shape.\n\n**Fix:**\ + \ Validate after deserialization:\n\n```python\nobj = pickle.loads(payload)\n\ + if not isinstance(obj, dict):\n raise ValueError(f\"Expected dict payload,\ + \ got {type(obj).__name__}\")\nCACHE[user_id] = obj\n```\n\n---\n\n###\ + \ 4. Unhandled Exceptions on Malformed / Wrong-Type Input (Line 5)\n\n\ + **Problem:** If `payload` is not valid pickle bytes, `pickle.loads` raises\ + \ `pickle.UnpicklingError`, `EOFError`, or `ValueError`. If `payload`\ + \ is `None` or a `str`, it raises `TypeError`. None of these are caught,\ + \ so a single bad request can produce an unhandled 500 (or worse, depending\ + \ on the framework). There is also no guard that `payload` is actually\ + \ `bytes`.\n\n**Fix:** Add input-type validation and a narrow `try/except`\ + \ around the deserialization:\n\n```python\nif not isinstance(payload,\ + \ (bytes, bytearray, memoryview)):\n raise TypeError(\"payload must\ + \ be bytes\")\ntry:\n obj = pickle.loads(payload)\nexcept (pickle.UnpicklingError,\ + \ EOFError, ValueError) as e:\n raise ValueError(f\"Malformed payload:\ + \ {e}\")\n```\n\n---\n\n### 5. Unbounded Cache Growth (Line 2)\n\n**Problem:**\ + \ `CACHE` is a plain dict with no eviction policy. Every distinct `user_id`\ + \ observed in any request adds an entry that persists for the lifetime\ + \ of the process. In a long-running server handling many users, this is\ + \ an unbounded memory leak that will eventually cause OOM, killing the\ + \ process and losing all in-flight state.\n\n**Fix:** Use a bounded cache\ + \ such as `functools.lru_cache` (if the function can be made cacheable)\ + \ or a third-party `LRUCache` / `cachetools.TTLCache` with a max size.\ + \ Alternatively, add a background task that evicts entries older than\ + \ a threshold.\n\n---\n\n### 6. Mutable Cache Value Returned by Reference\ + \ (Line 6)\n\n**Problem:** The function returns the actual dict object\ + \ stored in `CACHE`. Any caller that mutates the returned object (e.g.,\ + \ `profile[\"bio\"] = \"...\"`) silently corrupts the cached state for\ + \ all subsequent callers. This is a data-integrity / correctness bug in\ + \ any multi-caller context.\n\n**Fix:** Return a copy:\n\n```python\n\ + import copy\nreturn copy.deepcopy(CACHE[user_id])\n```\n\nor document\ + \ that the returned object is read-only and enforce that at the API boundary.\n\ + \n---\n\n**Summary table**\n\n| # | Title | Lines | Severity |\n|---|-------|-------|----------|\n\ + | 1 | TOCTOU race on check-then-set | 4–5 | High (concurrency) |\n| 2\ + \ | Stale / never-invalidated cache | 4–5 | High (correctness) |\n| 3\ + \ | No type validation on deserialized object | 5 | Medium |\n| 4 | Unhandled\ + \ exceptions on bad input | 5 | Medium |\n| 5 | Unbounded cache growth\ + \ | 2 | Medium (liveness) |\n| 6 | Mutable value returned by reference\ + \ | 6 | Medium (data integrity) |" + type: output_text + id: msg_a5eeb577ad29b303 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/correctness_review + content: + - encrypted_content: enc_rLcUyEe-lvNU9G2eM7l4oVbuz4mJI6IWIS_tcmMwToXOw7NMk11heuLhlVv7Ejh5Ksb1EBpClg1kuy7FPO3EufcN_Kg7JCutNcInp5_iAmsBXtUN8e3ykSi2cRsl7U-1TT23Wpg4wUz8PWdFnFxF-kL0ofr_FvFo_6_C1dQqptl7bR_TWz6DrIFzGj6UY97Z81va7VCpH_oCB4h-FPpgFtbFoWboy-M4qMqnsaDQijl8jpQ-Y1Jg4P9WX5nhym_7wCUOf02UywKJDGe4AVRAFqe-FpqrvaLC5tpf5X2zmlml-9S2FGszfDtVG9fKFb30OFXx0U6rEa96bVVvF8fdFLjCyU0c7k9kwrxACSKFXGcfG4QdmnOyi8EQ4lj4OIETrPbcNuH-w0RAxGBygR9yhRdWZbJ9ADj8l-w8LF8xqcqoPOfCV09rA02b4lEScqRbtuP9POZllUAUbB9ifbHIJ1jF2WJZFm1uGiGfYhKHdAQfvdtOy6OUiga3PefLHo6SXBoFeKsvWIdCqLyxpjmTucPLkovB5N4NL2K1ynwLJEiQfFL2AM7GzNZ1-wqmSr4NaOUs6qUb8JGfSFA3c88fe226Q29kx9tR6W3MW7FDCXrIZBWSl7XFhAyzK0zBANKfeyHCV4hKCVn8SiI6Ld_oJI9H_gZrJELJMPHBAj4vljwsvmo6cGWZx1x9_nIJwRG8Rd2STnwdC-qnevrvOn1e-GY1_UvjGOfv7HVDo_Re5JzWFVfr4PnknPqZhdCROnp_PLtXIurcn8F5XVM5l78cU1psZYLxhsO-2v2ukIbQiux0hQU8a1tYf0ZJoQ-rbTgRx_elLahg7fL1GRgkQwA2mox6Gb2ZsxuWF5d8Qwp_OKekEWlDt9UBbH5euiwuXB1lVSQ1HGy5KexIPG_4zvXFxB2selqRKmz6huoFoMywLqkGDyJtA109y2Y1ZnwXwXzDL7asOKzNsK2hBaRSS0CX_WlEcBgsepsL0J-ebqkFv6M6YNnvWBWh7wj5d5u3D_dUHuKwwA-c9Ge96tcUXO-Hgo2XzSHP4TJLrO250GFPdQsC-P4u09OybPihgChvrhCyDhnh22cTmAV5Cfu9Nc4hXXFIqkGMp6SAZRnse9bHsHNTyC3QJ2v88sADC7Pbkf-p9e3CGtz-5SvyoQ8ZrGnTdeA0FsRO9-kz_7Z459O3V1okhIbffqKQ0uOzQiHTCyoONyTYdf1gPBC4bcIBFBs0tWTwPajlqmT7tSVx0diNTLnifAeeroj3JnSk1PRlM3YkmgkAs2HlJAsS18E8PU5eaZ0T6GmQQZZyAUNyKAYWjDc5mstMuDEGbCiOYK1EqY2NkUHA3hqZo0ehHOCS7_LuBP9_OjKJCCH0zsicOlvuR32FJetFjCJcgenBVYfMszs2SPqYg1d0kl8gy0SSYHFUQITQla3mwAjjjckDxh2v2wNwPdPcaHqlj7SWyW7IKaCG7iysfj46TCb_Pb03eih7Zvj9s6lPHOKJZ195I-aSYqCfg58WwpwvhzPz6QaObUswt4JLVlNhOKYxAMqhHh-DGaYLxHvGYBrgVnjQtrA9OLlZVkradHq1exqZZXJtZELfjlqnuJo16RhuYu0K5dqZ47cF0il-TSdYzpBRJLwOEahwSmyfJOABneDaTbzZDKyd1EgJ2jUg-t_sl2bPQa5RMteqpat4Lq9eD5hegUF2xQzPGS0iQBfqgOZGBxB_N6yxYlw_HVhi4VCYdhm3QdvX_3d5YaXmsxHTVg-fHvSE3qfXYgMyS8w6Q45OnfFIOFHDXASe5n5JD8HkuL6bU05FxqIUH937TS_WiqCabtGiyhuEIWPyMmg5pX3DhvCmcthglQM3jOp1kDhfUOebgeUt0dSbR0pKNiZ6ntDc-dP2nnMuUdCzGk_kAqrQmOHXk-jlpaVAZoMylM7XQN3IosGzxcIuKrurkqeOWRTWzntBfQbVl4AE7muiP6gQLzuU24pvq32onCuQkR7QKnE7_kKkowycGcU30GR0kDgPQExhiVMMIxWQkjBqnuPHj_Jx_scMY_v5m8bTfUGUzF6tj48lKR05QPKaTKXAFEk0tvFaxOWNcIv56DAi9KTuG5qfsFJPrDbrb5fKQcKqJ6eQXTIgx7VNd-y9fKg0T--rEzbxtrJ-88CPWz_6EI3Qmjlb697_nL1v1A2sBxXPkN_RF4-2wcm4leovLW0HYrD3_2uDFjH_n0Xi3UiymnOoiQpIPqQSXK922_0NfpEr5bFuNOkfOywT5_igVXWOiOXk7OZ3x4GQVbH2WRLg1mjqu7YmqbnyAIUaGiWhYnIHJNOWL348DhkkiTvg_i1gOBIbTu1MflPmaQbPYDWDWMm8YRA29ehzfM15VSoU99FrScogSwlwTJxEq3B4jYUnmQ8V3C6PS48m-_HLwOGr2K3TFk9Yuje6nTetotL8nIzxmkoD0KVwZvgCzQCh3NJNuxt-Cz9HLy21CBouMoFbqRIEe2z8gk5rH_DqdnP0mHtw-FT5lfJ2zBRwEjoGcuSRySdZUg-sqQoPwug9_3vIaIEczCKZ4G8WavYmPP8ZQMvuRCL9TIVzsfeTR1JG0zjtxokqCeQ4arV_e3tw0q9qDBtFjjHk4nCYprMyIPcwDV0MVr5OUPQrR3xnk1wPyOkOl7wxyJz-fYBku_JDnDM6D5619MOBo6EnxxJuGOOnsVijgS-rLkt299ep5cQrXuqGRhcbtQnZBB_Ad6QYIpV-zvsZaxBRyEzO94thOFsEbI73ISs-iWr1SkedVJ4Y6yLa9KaVLKDAHyQlCU8vA9xnKcHKEmg_3EhNfzXhKEE2gyGfx_uslhSJO7peYPeCForDQhFA-IYuIg6XzygAwxtIKEoi5FpIr9Sn91Z9O1IShqGSd0-7LbCF6T5Nu1cfqA_sPfcPm9sVb7YHmRF4YvWbdedbU5HpPFwpNZlIYo6oAD3wd5VochrhFe6IPLi-6FVFqXgOHcs5s0M57dB-7RGNrMWP5iBGpkatDbg6PC9nfnX-5vCUsEnc5CPb2ndv5YR9KbSPwRTJ4rXjtrGRsson_vDyFyEq76QRxWg-VOS03D_FQCjK9N15B-PkctR5FlKqm1UiRV_ErrMJUtXgDqF7hkzIy6nXDm5jsxEG9IJ4RqmqmwywUyiwatzaLGsthFoHRwslNmnZaouVNee83ou5qKJjZ4Yz5uD_S66vNaBl7Foj3CPDtLLpy4Copm78uYvUAUrBTc-LkBw-MlZDeXxcc3fDiLXWPIFu9Cn0ND2XrG6EPc1oOsCSbLCOUD_mD8aSDQ9qSEsKCZRyGvYzf0d3G36B8CFpAaUT3bBIeuzsDXv94lcCAUfMtHSvjFxEHw5CFEYIvU4mGrFvhq69bTAEo9pqiCdP0E1I-by26T09ZKiGSR34TN5I4yyYpUvvhzq7u03vofWLXHl0-VYTNpUBipfkjfj5FNkF50rUWPSZZUzYhvhiCi1QZXBz82c95TaaSrD8b6vbUkSSdaJXdZTI6KzUmwRkHMqZEvz_X5n0C0uaLHpIjQONXxcSzMMwdUZ192eOuAu0oGD8KIczEItmXSr8mbiAlbF_58QTNm725J3QAk7VP4sfw6BGLVGkilGJdWyCLIZEL4xuR8v3aMO6JWU1t-YjQFbMtmiHpW2tQ2R6oaFIM3Wv-ouO5p5b_0OWcsL0ewGeUbj92HBYpxUcBwCRx62Y2JlIeHxHQOQAXIokxNaN6vHCZdRCRfuutrwRKMdsZt9ze_hJUPztUhz0Mn9l-XnK7sTDil0zS5ZzbM1kPG4Nmvm1gPgxI0mvFBE1C3snsMjZish6fIUBgXANA3t1dVy6v4WI6Obi_wx9g8Xys9lHy_Mf-jNCR7bFbojWjzrsmLvhnzVfnITWUAB1W2R5FpGBfd7_NnixwL4x6IBu5dNA2a0sVzTlt2vMmWD-oN1jpWgYDvcednNnCsg8zjpZLMtQjDpkamw_CMk3LIXhHMyloYIrGF1HwRVnQtcZ249WaRn6y2a0usr_z6HxwOwHhZEDMIHOKvrxJ_PreLyQZAR9Zfw2JiqHMyhqJhCT3AlGxZkGbAhO7kO_jdGiayyXU5nipJI20yjgvcxRH-Akn-rpMqZrkFVPTX9Bcu-GTH9OwYB-piFXieKmKdWy8-gG-kBjb0CteR4quph6pCDhhLqz2Km12XyHM1huiwtDSxarJlGQCandl4t-K_Sr7gO_LUPeBkl-Iw8Yme4YBiXAAg8H11bZi2LOpbwjoqgnGx7If-TE5ooWe5rVgkN39M2wbhE-0vQXJuFPe6O9yOC3iu50ZsDgsIPb0rqzhGSVKqihgBQ8xFRt22a4QP5NsxnNESlzIJqRhEXZtmQM6O1xotL8SlyVEPNOJDSGO7FHGdeiDaFjGe1aGB4WovDYGPUvDKlxA4fmB6n9AygbmTcByOtC4PDJKWYpW6x9R9u04Z240IJhxcqzRb61i1Sh0eQnbPYYNnvaqRUz6fnGn0FL_1wE7dDTxlgtUinavPOItYtT07lF7QHDqAP5XIIF0iWu2V6WrnRU3RbV4Z1ze9VkKB2okzfzB66sJF8Fm56KV5cK6vpHThVUt6F7VGBU6zAJQ9KwYMyug3qCRaVB62FYIl7RqTyhVPnJnJ6oF5PribB_evSYwzJgTc1Cv0yIz05Cts7KuftHijcxnz61z9AT8GuWHoX46rv3wSvVQFaQpJN2DIeaEHk-gxiDPMxjO-dff1204G-DScNprr2w8ygysObaR4yuZmvHXPgPPRIdyC0DuHgInD4-ubL35lnPkFQg0vhojqFXjvT1fsPGyG232JbhxU8YoW5B_sK52DeVhwBKooCIY1-pCzgnmXXa5xqHoBB9acz8SouvgfuDDmzBzh3df7Y19XBB_OrLET49LFTQnRHAbH2mnzQCQGY97stP2Y2Boj6wQuiV0QqaFYZg04agsUHWoP6p0vn0JFrejYZ7UxpLFHiGXUI0_-0DBWBbpn_mq3PpeNniDKrtXUo1RqjYfYUXV0DesgU2H7sbk8kKiZpss3NsDe6JCG2Kp8hdoSqFHaqKKYfLrGZyGkmYlNvVcUNNgSCIof8YCSsigfCKpE6CKRUgEMCcuVbX0jF4eNmuxpVgPzi4BJMmKdrNCIwTeUNUwC2COHcnj5tx306WeSkMtaDurEtflqCQ-zmML2E3vPZyy2bxFNdMdduHvj4DSJ4IRhX3MBpGiPHlmnfrvLXc6me3LVcJGtd35XKe9SHs8CLScS_cTaZweOolqgxPJsHRga8Bzp2oCyTuLQiWMV4Hoqt2eeUpA3etq7IqNJRyvRiI6oV1pmKzQqMGYw19srueo0oPoAvgsrqwRtQESXbjJ1NOnuCt7cxvSICyvy-xDLAS89CE8lVkqqEfvKb0VAsZUiRH_PpJSaRQJbAye4JrVo5OzOWaoTnVBOntR3NndoupF8IZGUZt6h44ibNtl7fQ3yz0o7a_qf6hGepSvN98ehmBCZPU8y6N0Xmu7AygkEUsD_MpyaxVyRgqcryDRsiRStPnDZ4_fVTUPZIk7YU3fzocr6B51DSuotWORYc9hsdzg4vN-XKgxqCO9IjlBxQw58rLYlRqr4X0i2u2Z0LU_hyQRUl27S7ieU_ca5Xr53odTqSf1-mVDPojATfmuUG2PJIwim4JrDaNopTWGt_zVjd2NCnJfXVZUXErl_DSVGYXtunCRTbioJKycFhKjelKcUrc7WG_W2_8KblMKVOqsdgtGMFd89hdYV9uD1q7aPMDTKLWghYgBLm83zDH3k1242mhqTDZLXrFZps-HWSgphVxeMdDcGqtMZGgCa7A4_o9_OZ235Z6BmjR1yTqKVpHzxpQ950epPTfLhnjeMj2bvh7PQ7-wWN7fFJ3TilB9jeG4Tx7gtONz0yEob2Okq4HMr6DeIOrFtX0WFEWVl12ly_em74c5xoOIB2Zgo2_CxYaSdYBkwahgLEmd-edTsO-PF_ukPMOqUMYWASfdg + type: encrypted_content + id: amsg_01a1686b-5282-7a32-9b23-c75d789a643a + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-ab98420abeedd69c + id: maco_01a1686b-5282-7a32-9b23-c7760c17d458 + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'Two of the three reviews are in. I''m still waiting for the missing_tests + agent to complete. Let me wait for it. + + ' + type: reasoning_text + id: rs_aad46c74173698ab + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: Two reviews complete. Waiting for the missing tests review. + type: output_text + id: msg_969d0d12e7a47035 + role: assistant + status: completed + type: message + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":120000}' + call_id: chatcmpl-tool-afc49a7b2cfc2b4c + id: mac_01a1686b-5ef2-7421-9cb8-d9284a360863 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests + content: + - text: 'I can''t spawn another agent because the slot is full. But my assignment + IS to do the missing tests review myself — I''m `/root/missing_tests`. + Let me just complete the work directly and return my findings. + + ' + type: reasoning_text + id: rs_abe99740748e3d46 + summary: [] + type: reasoning + - agent: + agent_name: /root/missing_tests + content: + - annotations: [] + text: ' + + + I''m `/root/missing_tests` myself — I don''t need to spawn another agent. + Let me complete this assignment directly and return my findings. + + + Here is my full review of missing tests for `cache.py`: + + + --- + + + ## Missing Test Report — `cache.py` + + + ### 1. Caching Semantics + + + **1a. `test_second_call_returns_cached_value_not_reloaded`** + + - **Input:** Call `load_profile("alice", pickle.dumps({"name": "Alice", + "age": 30}))` twice. + + - **Verify:** The second call returns the same object (identity check: + `assert result1 is result2`). Also verify that if `pickle.loads` were + instrumented (e.g., via a spy or by passing a payload that would raise + on a second decode), it is not re-invoked. + + - **Why it matters:** Confirms the cache actually caches. A regression + that accidentally re-deserializes on every call (e.g., a logic inversion) + would be caught. + + + **1b. `test_first_write_wins_subsequent_payload_ignored`** + + - **Input:** Call `load_profile("bob", pickle.dumps({"role": "admin"}))`, + then call `load_profile("bob", pickle.dumps({"role": "viewer"}))`. + + - **Verify:** The second call returns `{"role": "admin"}`, not `{"role": + "viewer"}`. + + - **Why it matters:** Documents and locks in the "first writer wins" caching + contract. A regression that overwrites existing cache entries (e.g., switching + to an unconditional assignment) would silently allow one request to overwrite + another user''s cached profile. + + + **1c. `test_different_users_get_independent_entries`** + + - **Input:** `load_profile("alice", pickle.dumps({"a": 1}))` and `load_profile("bob", + pickle.dumps({"b": 2}))`. + + - **Verify:** `CACHE["alice"] == {"a": 1}` and `CACHE["bob"] == {"b": + 2}`; mutating one does not affect the other. + + - **Why it matters:** Catches a regression where the cache key is accidentally + ignored (e.g., a single global slot) or where entries are aliased. + + + **1d. `test_cache_grows_without_bound`** + + - **Input:** Call `load_profile` with N unique user_ids (e.g., 10,000). + + - **Verify:** `len(CACHE) == N` after all calls. (This is a "documents + current behavior" test — it would fail if a TTL or eviction mechanism + were added.) + + - **Why it matters:** Pins the memory-leak behavior so that a future regression + or intentional fix to add eviction is a conscious, visible change rather + than an accidental side-effect of a refactor. + + + --- + + + ### 2. Edge Cases / Input Validation + + + **2a. `test_empty_user_id`** + + - **Input:** `load_profile("", pickle.dumps({"x": 1}))`. + + - **Verify:** Returns `{"x": 1}` and `CACHE[""]` exists. + + - **Why it matters:** An empty string is a valid dict key in Python but + may indicate a missing or malformed user identifier upstream. Catches + an accidental `if not user_id` guard that would break valid-but-empty + keys, or conversely documents that no such guard exists. + + + **2b. `test_non_string_user_id`** + + - **Input:** `load_profile(12345, pickle.dumps({"x": 1}))` (integer key). + + - **Verify:** Returns `{"x": 1}` and `CACHE[12345]` exists. A subsequent + call with `load_profile("12345", ...)` should create a *separate* entry. + + - **Why it matters:** If the upstream layer ever coerces user IDs to strings, + an int-vs-str mismatch would silently create duplicate cache entries and + break the "one profile per user" invariant. + + + **2c. `test_empty_payload`** + + - **Input:** `load_profile("carol", b"")`. + + - **Verify:** Raises `pickle.UnpicklingError` (or `EOFError`). The cache + entry for "carol" should NOT be created (i.e., `"carol" not in CACHE` + after the exception). + + - **Why it matters:** Confirms that a failed deserialization does not + pollute the cache. A regression that assigns `CACHE[user_id]` before the + `pickle.loads` call succeeds (e.g., refactoring to pre-allocate) would + leave a `None` or partial entry that poisons all future requests for that + user. + + + **2d. `test_payload_deserializes_to_none`** + + - **Input:** `load_profile("dave", pickle.dumps(None))`. + + - **Verify:** Returns `None`. `CACHE["dave"] is None`. + + - **Why it matters:** If downstream code assumes the result is always + a dict (e.g., calls `result["key"]`), a `None` entry causes an `AttributeError`. + This test documents that the function does not validate the deserialized + type. + + + **2e. `test_payload_deserializes_to_non_dict`** + + - **Input:** `load_profile("eve", pickle.dumps([1, 2, 3]))` (a list, not + a dict). + + - **Verify:** Returns `[1, 2, 3]`. + + - **Why it matters:** The docstring/context says the result is a "mutable + dict," but the function enforces no type. This test documents the absence + of a type contract and would catch a regression that adds one (or the + lack thereof, if downstream code relies on dict behavior). + + + **2f. `test_payload_deserializes_to_empty_dict`** + + - **Input:** `load_profile("frank", pickle.dumps({}))`. + + - **Verify:** Returns `{}`. Subsequent call with same user_id returns + the same empty dict object (identity). + + - **Why it matters:** An empty dict is falsy in Python. A regression that + uses `if not CACHE.get(user_id)` instead of `if user_id not in CACHE` + would re-deserialize on every call for empty profiles, breaking the cache + contract. + + + **2g. `test_none_payload`** + + - **Input:** `load_profile("grace", None)`. + + - **Verify:** Raises `TypeError` (or similar). No cache entry created. + + - **Why it matters:** Documents that the function does not guard against + `None` input. A regression that adds a truthiness check (`if payload:`) + would silently skip caching for `None` without raising — a different (and + possibly worse) failure mode. + + + **2h. `test_string_payload_instead_of_bytes`** + + - **Input:** `load_profile("heidi", "some_string")`. + + - **Verify:** Raises `TypeError` (pickle expects bytes). No cache entry + created. + + - **Why it matters:** In Python 3, passing a `str` to `pickle.loads` is + a type error. This test pins the expected exception type so a regression + that silently encodes the string would be caught. + + + --- + + + ### 3. Error Handling + + + **3a. `test_malformed_pickle_raises`** + + - **Input:** `load_profile("ivan", b"\xff\xff\xff not real pickle")`. + + - **Verify:** Raises `pickle.UnpicklingError` (or a subclass). `"ivan" + not in CACHE` after the exception. + + - **Why it matters:** The most critical error-path test. Confirms that + a failed deserialization does NOT leave a partial or poison entry in the + cache. A TOCTOU-style refactor (e.g., setting `CACHE[user_id] = None` + before calling `pickle.loads`) would pass the happy path but fail this + test. + + + **3b. `test_pickle_raises_during_unpickling_of_nested_object`** + + - **Input:** A payload that is structurally valid pickle but whose `__reduce__` + or `__setstate__` raises during reconstruction. + + - **Verify:** The exception propagates. No cache entry is created. + + - **Why it matters:** Distinguishes "malformed bytes" from "valid pickle, + failing object." Both must leave the cache clean. + + + **3c. `test_exception_does_not_create_cache_entry`** + + - **Input:** Any payload that causes `pickle.loads` to raise (use a `mock.patch` + on `pickle.loads` to raise a custom exception). + + - **Verify:** After the exception, `user_id not in CACHE`. + + - **Why it matters:** This is the key invariant: the cache must be all-or-nothing + per entry. A refactor that wraps the assignment in a try/except and still + assigns would violate this. + + + --- + + + ### 4. Concurrency / Thread Safety + + + **4a. `test_concurrent_same_user_id_single_deserialization`** + + - **Input:** Launch N threads (e.g., 50) that all call `load_profile("shared", + payload)` simultaneously (use a barrier to synchronize the start). + + - **Verify:** All threads receive the same object (`all(r is results[0] + for r in results)`). `pickle.loads` is called exactly once (instrument + with a counter or spy). + + - **Why it matters:** The check-then-act pattern (`if user_id not in CACHE: + CACHE[user_id] = ...`) is a classic race. Without a lock, multiple threads + can pass the `not in` check, each call `pickle.loads`, and the last writer + wins. While the final value may be correct (same payload), the test catches + unintended redundant deserialization and, more importantly, would fail + if different payloads were in play (see 4b). + + + **4b. `test_con' + type: output_text + id: msg_a78a40af81c14eca + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/missing_tests + content: + - encrypted_content: enc_nxrnjVqdicjf0g9oDCODZMR1IPQ1vZcAu4veAL9hcZTaVmzkiB61dHneGx6WjHHpFwKGs1X7uw7w-w3Y-Tbe-PRyYZXsqNfojlr78vcpx76l7OzOvkBqnZzNmy7OfmNk_5pB6oUQsQRocZzMzjN4wGuASj_qIM3FU7rGWaI-gvmE6fJ166hXTP2WqJeaDMyZRu0SWA5Snj5Jg_2nmOGGKAl-iT1RfFUBN_NUuZmslamLpt8ElFDxRbyF0v7zTFQOIY1M2OGq7zRGu4B_7DblQYzfzLwTKMIuLQBOKH5Jjrb7iQcet-SwchgZqTtq9CoyG1SZqYOy_F1zHghK8I8Dy3-4qcXYSUCFwsEIAeTYX1YBbK5Zwsv19zD4ZZl38ELdy4PDvMxUtl-IjbRhv_eaEdKH8SryLABZVevr9jl4dHVi24mmEOqtONuwp9e8tpNbtZSWxa99IA-HhrdreDdHONjomKvrKAzHCvWaoDBLhVwVoV2NQCNQrhQ0AqV2hmqsguM76hQiqhzHZlYq059tefu6uypA5kViPJEJU5HOV0GRJ2280UfnPeosrgSKIA3XKk8vOoxI3qo9GxxbGva5-WTkazzz6s9V4L1NfNMRXlbRWwtbGROvb1L6sMqGzAsAb64_PdgrkpBRMlYvKIcDRkCDtIrkBQqrFw7RV1V_DHR1juHXZLXgpdW8V206tfYOPKBoWkP_sZ0rsvTCNBNAxtzROt3dKpeWq-7yTD_WB-7cIIim5isL3RN58M-jGu6BnRF0LrD_C6KhCIo17tQZYjURpKTmtOB__kJgcSbMoT28Sb4q9YVNpMDP87sGUWFP73xUFMKfZZ4JY5Cht3I-QPPPwuO82GiyiHnkg5-ORngEkKuDVaLFJZwsB98LGNgNv1j2bk7VQ447ChTW6k0YDh3tPYDj1fm4oc2KFcMaSbRJGXjkgzKMTtcEpWsY0nJULTkx4YXLDZognqw58ibQywbH-ROQ9dyPJujRrBcEufZqHUQgs3VBg9Ij4q6KY-MWuHWVqhc3tHO82pxKWotFLYLLm63bcsoZE9HUmq0ZdrU30PoCFiNO7hrjpqOjsEJuxxFfUxFqZH5X59GZYrbup-R4uDFjmvoRwD-FznQXJrpqEVkVTHkeT54lz3X6v5Z4BsTwLTZJJBSXc2aiecTvtuSUdiiPlVWWzaDzgdEki3kbMlu5ZDriCg2RQ2XoznzsjR7pIPKgx5cZU_4U9iJ-5sPpHVu5JrQSPqdle0z-xnFLnIl47PX7H-heRKMQG-hxp-pXFrcFtMPIQFINR4Ls5oiC-4skMPj2ilQTSaNFNVcRxehsPlau-jiW6R562uawkwCgv1YCxjUR9TmMqdjFGam9sij1aPf7qJZ54jKWrxpCLD4CzcbS8FYra0kGHSee08eiiMWCQFLWBgO6eGsGVpTnvmZu-GeS5R2H_uk4QsQW_S6t9PzoBNboUmIZzeG0cjdLZts2_UqA31eyhja0C5OUmtr_b60dTxDCzgmXPTmEYY7mL_FtLbh3A0rzcpQjs3r718qCKPmNY_iC_1f4kPWpkSEUxaGl_gzMFGHPIYDUdhdzPRlvLH7AYlL6qmisRQ8Pzew0af1ecDH9PTf2nCA0IUPEQs4rQ3CBdvNWNNYR_v57E7swkyd_TPVklIOfDLJTqorYuxm5sdyGIfGoak_IzG0ycTwhoFyBmr9C4whsAVQzrg6MOrmw52vqJnea34K9SPkzIKEYG2VvsKRezG6qaY587ceiiwYy1HpJHsVj6Lrtm3IClZrfQ4mx7rJX53IxyjRXCWC1b2alL8PcXc_XHVfhOgFO7lzvxv_T1sRJ28FCGhh5TAasS9-1627HJVE_fESGtORC-KF8HQar5oFmbDyycizfyNDuKrdGK-jPoxJ1wk7OvQvGYiSNDOqO6kPqfrBo0qknwlXaIWWZlBZJf_Tz2-TCrpmyI2QgtmCsncoHq-a3Y1wWV2DWXQm7YUWtW509VdlWq2ltb64DMC9C5fdRcJl9fWXeeFXQ_OxLugLl79DYwE1xudsX4IrRlD4pgMm_3Bpo75eJ2HZ6OPY0eHhWK0FuM0nPKwX7ctcoW2NICjqbbVvbgk3za7b2ywyHAPQAj6V2dtJ6-SqA2nTxup0TXpJUhJdTOAx1FBqb69WNMMrMtPpoNH6MwiY-E1pGzkJUzUchub-QsuPqTSRaa4mBgC1G4y9WnkHkzTMutn1SANgyBEGj-pe-RS4p6LiHYb7RgQFa3nKVEFGH7uRmZRUwRY0OF1Ee-KSx-76xpRYMWWkt3nbjZycYsjHDQr5KihA_qDZy8Rcb0XXniXlbeDaHxTs8MLsD_WhdaOLNPL4YL5ugaevUEo9UwSqo2xNQM4gdu5sNeP6Ou5V-7tvYTBNiAyrAleB0dG_zztkIIN98-vtWYHhPPROHo94XMm3fjdl5KxSyERmVkFfy6wfCNe3snQKHZ7LEI93YhBe5Xbqtnq_9YFrggO_6zhuirvDiAd3No3s-XEJ_fADJrWOvcZV1Ke56cs1cHENXrnba65Gr03EcpIFKGiAvw3j56-hZpUfEiWLgU9wj06jzdFZvgXlbgs3tsc3RdbacU-bMlIg3pJV03Taf_FFLIMn2-sJwAvnSfFvkhkMd-2OFWOmI1okQWRJpY58CtDzCzeZ6JPRLmVrtNYdozqjNAPRNdQuyeLUg4KxkjsT8B0pq8ZZzDgWW0-aWBNY6C83rDSz7fsopSsMDkhm6kZkwBSWaTE4Z2Nxwj58PwaiW4xHF6Bc2U8LZy3xyLcP_Wkmp_cCJvWNq2DP7_MM5aTqReYMXvFZg9Zt_2mP2lGThiHJREmI0Z6_hcjX_KYlDLjCGGfsugevV9NocjWme0uYmNC8WsqfG_81cOvvtF21qycy4omK0AdnG1FN7KV64wR0KW8i59x679K2AY8NLWXmJhpxLdxqjQKJ5N9NPtk9n3QWpdVse8fKGjGd1Ifz8jT1-DIT8pEaCC8mEdC6-Upju8oko1pLu8a6PC1L3hesPLIRIu3sTxZmaqeWeeG3Q83ns4ApFitI9bR5aWWGOlBfKCmL5NFAADhI85PE3l3cJUW8Pv--jJigVAS2RLeO81U4uQxhz-oIWsoQJnknKFCSIJUw3abcTPhgGJCEup26ce3QcF0IobaP9nb0K60mhVlYAkii266aoUVt017qZnz8QUcUbhIBfOxtDrcTeuNJ6Vcr2J5N1u2McMKb3k5mS1s8VLOT-EcKzwb-jQeQN-6PRlsGhOwdcAFOnYOfMr_Mpu-dfSXvh-14s6Y0VB3oGdPGeeFul1ircUMeu3vUP5XlmRw6LZK_oPqkgqDBHBqVWYCnwido8ZBWywd9ibybH0gu6u0aVb3YsmYlM-8eBTee2Z_dT8_WHx3urbDJILLTLUXIh4pfI7TWegimSVf-qZuOI1E0vC8hF2zgRWEY8G1HF4ADnXFrbeubOmkt2nm94TlAOTQ1P4Jw5EbYa47XnHt5mV7LpXC5t9YBuTnjX4fVKsmUPG5VosTi7yQjEGJGaNZ72MFrH_Q4zNQ7VW5AGIjOElc3CsHO1OzH-aK5ULE_yaUZyuqXNc2M9Q-nANHdNVUAAwHDrXGPseFgEDbVVO1yyj6rZSrLMOSuNPhk6RJXjzbuh_4rBagsnXPJhAWRv9TiCnSx-h9KpL9J78LUlO-QkmaXTdsUoby9v88AhdUreFVlXfn9y5X11gN6Ma7DnCPr4mftVf31ZTJP06vdGexVwU-D2_0yqLXPAp0EYnhg-Eo7eouROrR8wyqtlJof1hai1hMJh9BCuYB9S9r-_GBoK9wHkJRFB9-WWSHBs86bQGwK091VsJPmPpFUWAIuS6FlAzvbZDqpxWjY9eMoD09CL0blfJB_62S8_0y2vq_dgjlo3l7DLhTL2TRF4K7MdUOOUopt-A7JTDsBIOjs2rqQf02YD4m3ONaGjhTQ85PEHBkTgBhmAgAiXz1mLznJEi1ijh82JKDg0EVVe4MCo21XU9DG2tdZetGW6DKyn-d7BhHvtLWPSJrfsVS1p24OQBmpaTTXiWDCKSfpvoKZqhgh8KLtS2PEpE0nzKeadxBPXzC8bEV9rF3ng0r3MfoYit3lM6tivqdBe_oxalbN3mKZ_MBfpoN_w0H2WfXTBu144bmUDhsmuDyOdLUExS4CCrJva4H0napyBzsg6si8r2DfdpvF-jMj8Fg9rTL7HMn9d-G5q-LH1qB6vgUKnvpA0kHs7u7Au7iAEKL9tCKWP38YA27H4IEKim2kvJG4e81gCl2yzDmLJl78kiB1CwMqp1zzAIr2Zs9fot2ezH0HT3d40sdE6CrI9gO46Rfee-7Vw0rBrgvcmFL1GjH5eXXIQXB4npVGN-Hfe4jMARdWiJo_eHUMsopZovl7Fcbnr4ZKLseHEjm80nap6l-x-lFkkKl7Go5iVDmHd1DzL_yLL1iRjhE6T37jTtYTDgw2PNROGvr5968UV8-b09Pj97e4ksQauo1uqxYOp5GpMkBmGWVMQ59aRyZeKO4pmzu1U5ZsdjDh6jy_T1S1umqaPAIrWrZoLdOtVpqE78JlYgkmPpvFj8L6Dn82DuggJ8jT8ZOYzaYuNEG3gz_PHCha5X4QPllM1L58g12um4575_BcMAyd0oIhPeDXj_f9v0Fe14okIYLqo3Q7FtqUaCQaqsef5nzQ4y852SHimOGI4BNRd0wUQ2_DDB1bB7pYaQ7lKhh_kteKi0qhh603mttuH6Vt8Bedxj6e03VB7p10Gd43m9qoF4V6Fdjiz9vEX4CYfG1BOzBjHdvTWBnXGkfHQqpEF6-qi5p5cEpRR23DXpQnZvVMnNI0TCfEINbdfGJ2PsmQf8MJh0mReQJxdshZXYshi2cS7gYzF99JkWLXJMtwzulk8hfhKZsuAfnUT8Oi68mGXNo5mhFWmJdhzg8XV3tpZxXR9ISV7C5lUBP3RRCp-SFX9OIv60TJLZrta24eKwf932aa1vvGSIB4k_hX03eDEpc1TJTH-lmflNC3AVHb_XdTr_VjTBVqQI3J4jG--jBhtBfgwJM8G0BDuPp0eu5PoxFuEr-UaZoo0g5Bdf5LwECuCeaovtfMN3TbJk75x1X-e9a0_uCu9QzL0y7TyBC-G3yBMulKcjTO4o_DpkWcSRAyfOfA9-E4dh77Hld-JLs0wInrGtiB4Z-QNBLpzazDurSJIxlHdjYlb4t6ZT5e19iT-VsKOsKnfmC9sT8srg6dlawemfimqNdH8U78BusvEhqln4vns0QSW6-3LGugCFKFoV8QKjSMijU8ZS_Z-ZDmoJdQjm4MVi3NG_gza_ewdAAJ_XzfWfLbL3P2w_IvPWPLYJ3CKwCfblWoo-4dByAL-alWUH1yfJny3-DDz_OmPPy5dmd7GqBmlsSA97liafzBC_tJl55-3z48Nr0vwtxCqPWl3mxwb15NhqwN3BfIWKAG0wNZDgTpSm6uaUmSRBbwbCrJDBeI2Cif3lBOPXZKr7ydkpw1EaeEyZaaVIvzU1tkvBnWhtPHj_AwQo_aapmGE75MbIdR7HbnO8zDPsvL-3PMq30HoTNdfNJYKZRpJDkn8t17KZZepMmxnHfkUJPgQOGdZ7a8cvJCtllxG80lSLrMxO_C1X-uzqD9ZYPzxgorhiE9XUiOyfrZoytIYn316tHJXXEbFPSc8MThB2hkqpAbOq5426fYPvsURz12zUR8qlAwmD843ipcm_4_A32drW__e3V9r7iTnrmvaATVLApbDfC_7XDMSFzoK6PJWcMqjRBwikyxwml4h2oq5IVcq8FgNdXuxAW4w98BuDwfQK6X5N4Qh-CK70iB1aGCbRCYRM2mlaQU6mai7OC8rII5kznWBelB044qSByeJnSEbxH6GiHLk5zmVgTy-jMwkKDJjmZ9B70C57leY3O90IVsf4OxUYChVBQI3Ns7W9QPb0F_W96D9Y7LrKJ9bVRrY1INqn_iaLSvh95X3GpZzcP90XAt06pNLnMekjPHbLL9xpl42nRyZ8vrlw84Fg1RnUITYWIWHLvf2MR64Q5MTRdQuVoN5gGCwpjiv3iI1oDAKFNzuqs1qksrfXip6atC5jTVk6ktF3O2Cs047xRrO-LkdvCth6TcEc0wkRrDbDKuV589vKM6qgdKyIZslTEW_DPyZ1vYvm4qWQvx6XxQ409opd_CXucTINcPftKpgPl5x5_10ZJPHy33nkuMbD3oQLoLHJ96wuEt22KYTTpH79ZhulQaXJ-6CNpMKCBALM97nEWwCzQavpv_thD78YjwrNwbhzS-KNtKHegqBpocl-bG1vEaLfaZnIbsX2s7xMWIbD1EWNY2gj_xHDaBpBqXayEmsOiJAP9RRbFd0H0G4jWF2WicztJzfex9bXF0AHvtQt5rLCuYXVLP9gqEqUroHebu6J1rnbyuVFPY6Rn4vkF2EGOm2lKf4QNLZJ5hPrGxoH3ktWtwPayt5dG3Xrle6ofUFIy5vCJvafz_mo7AElyl7pojrZW2HUtSCAzcaHIpFkNy5m0Rpm6337u6707lPa3CbtEAxa4iYJoXyQchcMxfnkwLouPUjZal5Tnn1Sx3jqA8b1S_u_pu7Ov2XiJjTdwjKPlln-WcGMPxjUsXVfYHF5jQ9RvkDZiUhNZw2dBIaTHy1DdqtTBhN-i6EWhZDR6OIWn-PDH0XDC1EcdXws-j8wyscY2hMbgofQX2O349bXC9TbhWqQ-qKdalBGberwH4lrbHaSUzQhgMGlVNiBwtnvDiwcLpz-qXn8-xIqXG9z4GgF2PfBXTNVYYuvWVzVzUMNMXiQ56AENfZRYrbgqXMsic1sgRdLvZYy-8iRQGQ7YC-NOy8nWgo4kNgWYtqkuL4hb2Q5-LmUUELq45EBd9XdFp3ZiVon23eMhBF-oIgHAo-EA1TogkKwl2avbn00Po6sgrTliW09mzbzy4xSwjZ5nu7FcMa6dHRw_iUe9fz50FqT-19k5ms9hc1hzI00GO6Sj8Sv0ybtI5vNZuaNV4u2ezuUFnzPOq3Xg__i39aGPENsQUQweYkhS2DspkLvI7zkw31tLDsPj0UV_XWwx5ZR2zOsrFd6rgtHlzVGBetIZ3GmuZoTwMjXq2wkl6UPsNw-Up2XOFP8cVXWtjpGWe55cSxzgtvFIew8rKxyXzJiK8uW-nxkowy_yeWGBfYl1W7S5BrHFbWXGCq6LmJWfdsRUKPTQJ-fZgxnvBX0hSVH4rr-UrFTgiCf6LWkFGlj0eS5KErFW3mYNV0Lv-Wtib7cjCaTBtsLeX2R3X0bSDF1WJe90DKkcijoXmmoTdmKliYIbUJUmweOG1I1iCmeoGYeukujxqxg8Lbc2NYAQkqHZH2MPGJe3Q3Fc9N3JkVUaHpyDX-EPh6pG_D3zMCAaWor05cIaGi4CoBoOzcW-Sr62A99EGam7hIZDxROxQts0uUQWFNS_esSUfYmlqwVXIPz6A3MXzK0VHisXE8TAiYP0leiRU80O0MfKFzAwqkoiztG24wYL6GPH5TWZU_GKk5f4agZ6GoXxCvbWvxB09nMhSgt3JFavTymh1dSIk2-m5MnGDDMfYyWV_UBYYR7U-wK--zmXd5Af4kS5GlWfOGtFA64OFCMccnGNnNKxb5DO7a3RPjcNzNNCYNLXLVm1J6nZ2aknyeI-rgI95QVIFBgeaxweMHYC1QAGfqvegBn46SkfnU-DzTKBRXFWAbl8znLmsAJMXtXxGjjQyGUN3JoYcxOgwvvqSsbA1DNuE4WpJj1910kW-grhiV43ug-mX1KBeCkCHGsnLU2ZZKCvJ0PdOAJE53GzjrwBe0JcnP-UBM8gawoVg5BmMR5xiVahjmbhKLe30gDl8puFoNMbEeDJ3SNAcpukzzZ1-W3PwBnc5fQ6vDb8ho84SU2Yg4jwpWJaSXHYjKfF1mXiG1neU5zbtFVS5x3JrslJlEfQbSFNeZqrwWGAIOcfS5C5VjqeHFM9dvc5K9Snen7AuIaxdir16pwlQvFl6h3PuhPL3LUYdraBqr6amn2y9CSrQwN7XgwJHdNtr4JZSe870eblLgbShflCzUSMaYs_bkyLNuuOb1BaKcH1CfpMqGE6-FbCPguhooJnzaoYG7xsoJII09LZueAgGhCw4cvQgetj0vEHgb_CHL1tFztz-srEr3104chhDRo0X6a0yj3MRd1RH0AWvJQxkWugsrOCT6165h_Q-isURFvL8dcnd0ffvznD9PRfz2tVBUTm2WskIWeunu3zjgwy2QB70LzfCYl4PMLHHItKP7mKUOFPo3yZVN_lTC3zuE-EQRfU4Rzihi1dUFH2F60ba8K9RjlNziudHc8vD9TJzmvUwB-C7l2YSCRDckrNz2Wp5HLHu-TYUxw7AkAAv_uJL9rj54Vd_f3dF1njGdRcibaLGSJGikm7qcnyamL2ZKCv6xJoTrykOwXjoJuEiD-rSv4lBbbEWllC9nFOSUrQGBm5WmcA4971CEtLsUfTG7gQOKPLWiTH8DNCwCvmFUKwBdV7wwCKL-x30j5axDciK0KeVR4sL9TYCQ4L_QhPGvVKUC_8onx3KNBKT3GpRdu70Vs086HAN27UyRba2dc_7O4zTlscusjwhpn0WD40K5bj0vaOXPHUJPa_Z8Jm6kYNA4qUQTbuBZdyECLFCdoH0_n2UHPHZuAzOofQWzZ6QrG7ZgUMk_mukA4RRGYZvhsqWBbw89dnHzIudUw6npCADc6EOuav34QWHmubxJOZUdsRC60pTHwYOlAWEX2XbHStiB0B6AzPQldQjdAwkIXNGUAAhvs5MBFX8eLydh0B7xNI2v2aAQTLkMoOFnbo192M_B_pEYLkuy91bPyanqxucP7wi4DWh1XFugsuWNr_KJBAO8eWQFQDkyKWDIlFY7zZE6K8GuaflzXTG9IrLQRJRVQjCCIMD-3r5-zCoMha6u6LQd8GjuqsHsHRbrkZ4U-5A1htJkjVoQ41vk2_opHmmk0mlpe86MjMIfcEeeLxpALYJ3JzhXuyPXWgGL80cu260OXGiJmB439rnNzdOG3oO2_G_Rfg4-k4dx-1_jRQU2gJctrCVlc_Jn_Gs9U0e00DxBU4txBzuZF2aKT93iwb7q-u1pmwTQlFGPeT6Paf2hRSPDZA1CDZPSr1BvtxKfHVmZZDeGeMbqFpt9PEtWzlmEMPulnG6YNT8-apiXQm-tHcMpWp1JvCUGlp2vUWnFwyfI9osc0vGG6X1zCDudT85yoNvJBxjZfR1oOhmpDwN27GHMao2PwP4swHDSRJ3BCqyMMhwQ6V12hI2jo3x69B9B4FFbvVf5uoZhqzvP18eqgAL4kS6IDEtPNTavbywiJ6_emk3whvr8lMQ7EVkMtNLBUPxASlWbZVTFKEbJdGJ6D7x6bNgzzLUsCfcK_HgB66wKYI3LfNTegpXvJe2RWWqxo0rv4aE-HxcUaeYI0ZHzGaHIV6h8MEADTPKDxhxTJoywZnQV6PRS-UivyZM9Ak_-s-EpdcnjhCb1RTP6USe9XXkxYJYbTI7J0dhJBgAdF8-QDgF1L6ajkMuUIVsDg4RBzqE5LgycW3y4DO0L8RozwsFXWDvlQpS1Gj6SGNUU_ACcSHWnUpyNeX7sV0eDWYrR6QPIB4b7pZB6vKEnkkcqqlxqvfT7wjw48ss4wJ4jnLtXYsJBq7Jv7sfCCj8ml0KaIb6gpAcX3BbtCNYCZrZt0sAjjP6Js4xEogcdGF4fOTaJG59fIEVMl0OLWKfEZ4F0J06LWlrg5o5WRgTEe4ToKZM6yQb1gfaLC8dWgc9SnsHgZAggCeHpA0ONhEPNZVETHn_OqAYuMd-Lj9IZfLyopvjKevL2I0-q81RBqPLQulSwVukAaEYrTtElykvsjIWVsOQUNWAFVK-XPL5h1qP6MQ6-I-D8PUQGxT6myI4177NWyXP_uiEQ6ZhSK3MIrZ-HU7GF2do_Xa3Xi5Vo-xfvi-Ju7TwOlPDDm0dwNhJUT9I0CRjVrKs3ddwhQlES-HAgAC8GwdD1avimwQeENHEzk4vCwjseNptvehroZEu6Bf58Djb8vwMQ1dSHLIj_u9V3pj4_DmpT0zC1jlC0SCVxzvOXw0VJojE3v_NuyJ7tlb6kYN6qlARyhrnSyqJsvcS38ArXexc6k6BzNXtvSuIGJMzItwZtSd9vmM6p4BMcYPA4ckBSSiw2L-qwqk9JCa2Xf6qx51OFnYJiO8l9shPER3JpMD3eSFeVQeLM29FfBrHesPvPWdcFuEMXEwfBKseagt3EvdrlgQrl7PNDkon3Og45y9ehVsCKpR10OIMGyZA61OTm4LC2sW6Lqhp-0g39HHuyofoF18mrWf5zpAxxf3pYWyhqXGbI-uEkeREr1ygZLxmsMUxNdjzjpkix0QNO8i_cjgo_Z8tk1hBf4P3UsW2IXceSn909EDBmdGc9fvUjJcD6qe7V3q3ehRN9ICmCa17C3ZoanAX7b110XUgx5dEpm5aXv94NKKkRSNSyNe51MVHchcS9b1pW_NnQT7aqAHkIeY7arSA4yuo4DxsCY4Y5AwN0uguQmo2i4UBOsSJ-R7t4YDdVuSpdjd0653SkCKbIH3fFOp1rUPT0EWm52cz-lfJcUIxzUHx6EsO08mC6NDNklKea0wjgOid6fSSUkQEXf6dNX_TW4L8 + type: encrypted_content + id: amsg_01a1686b-dc2a-78b2-8e60-9ce94668c444 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: chatcmpl-tool-afc49a7b2cfc2b4c + id: maco_01a1686b-dc2a-78b2-8e60-9d0340d8f459 + output: + - annotations: [] + text: '{"message":"Agent mailbox updated.","timed_out":false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + content: + - text: 'The missing tests review appears to have been cut off at the end. + Let me check what I have and whether I need to follow up with that agent. + The response was cut off at "4b. `test_con" — it seems like the agent''s + response was truncated. Let me check if the agent is still running or + if this is the final answer that was just truncated in display. + + + Looking at the message, it says "Message Type: FINAL_ANSWER" so this is + the agent''s final answer, but it appears to have been cut off in the + display. I have enough content to synthesize a comprehensive review. Let + me compile the results from all three agents and present them to the user. + + + Actually, let me check - the response seems to be cut off mid-sentence + at "4b. `test_con". This might just be a display truncation. I have substantial + content from all three agents. Let me compile the final review. + + + Let me synthesize all three reviews into a coherent final answer. + + ' + type: reasoning_text + id: rs_a89b80782feb1644 + summary: [] + type: reasoning + - agent: + agent_name: /root + content: + - annotations: [] + text: "\n\nAll three reviews are complete. Here's the consolidated report:\n\ + \n---\n\n# Code Review: `cache.py`\n\n```python\nimport pickle\nCACHE\ + \ = {}\ndef load_profile(user_id, payload):\n if user_id not in CACHE:\n\ + \ CACHE[user_id] = pickle.loads(payload)\n return CACHE[user_id]\n\ + ```\n\n---\n\n## \U0001F534 Security Findings\n\n| # | Finding | Severity\ + \ | Lines |\n|---|---------|----------|-------|\n| S1 | **RCE via `pickle.loads`\ + \ on untrusted input** — Attacker-controlled bytes from an unauthenticated\ + \ request are deserialized by pickle, which can execute arbitrary code\ + \ during `__reduce__`/`__setstate__`. | **Critical** | 5 |\n| S2 | **No\ + \ authentication/authorization** — No identity check; any anonymous caller\ + \ can trigger deserialization, read any profile, or write arbitrary cache\ + \ entries. | **Critical** | 3 (whole file) |\n| S3 | **Cross-user cache\ + \ poisoning** — First request for a `user_id` populates the cache permanently;\ + \ an attacker can pre-seed a victim's `user_id` with malicious data. |\ + \ **High** | 4–5 |\n| S4 | **Shared mutable state → tampering & info leak**\ + \ — Returns the cached dict object by reference; downstream mutation silently\ + \ rewrites state for all callers. | **High** | 2, 6 |\n| S5 | **Unbounded\ + \ cache → memory DoS** — No size limit, TTL, or eviction; attacker can\ + \ exhaust process memory with millions of unique keys. | **Medium** |\ + \ 2, 4–5 |\n| S6 | **User enumeration** — Observable differences between\ + \ cache hit/miss (timing, errors) allow probing valid `user_id`s. | **Low**\ + \ | 4, 6 |\n\n**Top mitigations:**\n- Replace `pickle` with a safe format\ + \ (JSON, Protocol Buffers) or restrict to authenticated/mutual-TLS channels.\n\ + - Authenticate callers and enforce `caller == user_id` or explicit ACL.\n\ + - Return `copy.deepcopy(...)` or an immutable view.\n- Use a bounded,\ + \ TTL-backed cache (`cachetools.TTLCache`, Redis with `maxmemory`).\n\n\ + ---\n\n## \U0001F7E0 Correctness Findings\n\n| # | Finding | Severity\ + \ | Lines |\n|---|---------|----------|-------|\n| C1 | **TOCTOU race\ + \ (check-then-act)** — Two threads can both see `user_id not in CACHE`\ + \ as `True`, both deserialize, and the last writer wins non-deterministically.\ + \ | **High** (concurrency) | 4–5 |\n| C2 | **Stale / never-invalidated\ + \ cache** — First write wins forever; subsequent calls with updated payloads\ + \ are silently ignored. No TTL, version, or invalidation path. | **High**\ + \ (correctness) | 4–5 |\n| C3 | **No type validation on deserialized object**\ + \ — `pickle.loads` can return any Python object; downstream code assuming\ + \ a dict will crash. | **Medium** | 5 |\n| C4 | **Unhandled exceptions\ + \ on bad input** — Malformed bytes, `None`, or `str` payloads raise unhandled\ + \ exceptions (500s); no input-type guard. | **Medium** | 5 |\n| C5 | **Unbounded\ + \ cache growth** — No eviction; long-running process will eventually OOM.\ + \ | **Medium** (liveness) | 2 |\n| C6 | **Mutable value returned by reference**\ + \ — Caller mutation corrupts cached state for all future callers. | **Medium**\ + \ (data integrity) | 6 |\n\n**Top fixes:**\n```python\nimport threading,\ + \ copy\n_CACHE_LOCK = threading.Lock()\n\ndef load_profile(user_id, payload):\n\ + \ if not isinstance(payload, (bytes, bytearray, memoryview)):\n \ + \ raise TypeError(\"payload must be bytes\")\n with _CACHE_LOCK:\n\ + \ if user_id not in CACHE:\n obj = pickle.loads(payload)\ + \ # replace with safe format\n if not isinstance(obj, dict):\n\ + \ raise ValueError(f\"Expected dict, got {type(obj).__name__}\"\ + )\n CACHE[user_id] = obj\n return copy.deepcopy(CACHE[user_id])\n\ + ```\n\n---\n\n## \U0001F7E1 Missing Tests\n\nThe single existing test\ + \ (one valid payload, one user) leaves the following untested:\n\n###\ + \ Caching Semantics\n| Test | What it verifies |\n|------|-----------------|\n\ + | `test_second_call_returns_cached_value_not_reloaded` | Cache actually\ + \ caches (identity check + spy on `pickle.loads`) |\n| `test_first_write_wins_subsequent_payload_ignored`\ + \ | Documents the \"first writer wins\" contract |\n| `test_different_users_get_independent_entries`\ + \ | No cross-contamination between keys |\n| `test_empty_dict_profile_is_cached`\ + \ | Guards against a truthiness-based check (`if not CACHE.get(...)`)\ + \ |\n\n### Input Validation / Edge Cases\n| Test | What it verifies |\n\ + |------|-----------------|\n| `test_empty_user_id` | Behavior with `\"\ + \"` as key |\n| `test_non_string_user_id` | int vs. str key mismatch creates\ + \ separate entries |\n| `test_empty_payload` | `b\"\"` raises; **no cache\ + \ entry created** |\n| `test_payload_deserializes_to_none` | Documents\ + \ lack of type contract |\n| `test_payload_deserializes_to_non_dict` |\ + \ List/int payload stored as-is |\n| `test_none_payload` | `None` input\ + \ raises `TypeError`; no entry created |\n| `test_string_payload_instead_of_bytes`\ + \ | `str` input raises `TypeError` |\n\n### Error Handling\n| Test | What\ + \ it verifies |\n|------|-----------------|\n| `test_malformed_pickle_raises`\ + \ | Garbage bytes raise `UnpicklingError`; cache stays clean |\n| `test_exception_does_not_create_cache_entry`\ + \ | **Key invariant:** failed deserialization never poisons the cache\ + \ |\n| `test_pickle_raises_during_object_reconstruction` | Valid pickle,\ + \ failing `__reduce__` — cache stays clean |\n\n### Concurrency\n| Test\ + \ | What it verifies |\n|------|-----------------|\n| `test_concurrent_same_user_id_single_deserialization`\ + \ | N threads, same key → `pickle.loads` called exactly once |\n| `test_concurrent_different_user_ids_no_interference`\ + \ | Parallel distinct keys don't corrupt each other |\n\n---\n\n## Summary\n\ + \nThe code has **two critical security vulnerabilities** (remote code\ + \ execution and missing authentication), a high-severity cache-poisoning\ + \ vector, and multiple correctness issues around concurrency, staleness,\ + \ and data integrity. The test suite is essentially empty — it covers\ + \ only the happy path for a single user and provides no protection against\ + \ any of the failure modes identified above. The most urgent fixes are:\ + \ (1) replace pickle with a safe deserializer, (2) add authentication,\ + \ and (3) add a lock around the check-and-set." + type: output_text + id: msg_9876395e7e41277c + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_01a1686c-80cc-74d0-95bd-912128a8645c + role: user + status: completed + type: message + first_id: msg_01a1686c-71e5-7a92-bb27-7ccd32a8b1cb + has_more: false + last_id: msg_01a1686c-80cc-74d0-95bd-912128a8645c + object: list + headers: + content-type: application/json + status_code: 200 diff --git a/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml index 0d61a5416..246c859fa 100644 --- a/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml +++ b/crates/agentic-server-core/tests/cassettes/responses/response-tool-items-openai.yaml @@ -2,45 +2,26 @@ turns: - filename: t1 request: body: - input: - - content: Use web search to search for the exact query "potato", then summarize - the result in one sentence. - role: user - - content: [] - encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b - summary: [] - type: reasoning - - action: - queries: - - potato - query: potato - type: search - id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 - status: completed - type: web_search_call - - content: - - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster - type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai - logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) - type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + input: 'Do two separate web_search calls in parallel, in this single turn -- + do not wait for one to finish before starting the other. The first web_search + call must batch two exact queries together: "potato nutrition facts" and "tomato + nutrition facts". The second web_search call must batch two different exact + queries together: "cucumber nutrition facts" and "carrot nutrition facts". + Issue both calls now, then summarize each of the four results in one sentence.' + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true store: true stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp headers: accept: '*/*' authorization: Bearer *** @@ -55,26 +36,110 @@ turns: background: false billing: payer: developer - completed_at: 1792748959 - created_at: 1792748957 + completed_at: 1792751797 + created_at: 1792751782 error: null frequency_penalty: 0.0 - id: resp_05b26e957aa7a7d7006adb2d9d970c81998accadbe8276534d + id: resp_028582086f036f90006adb38a6029887d09022c2dc4c9549ba incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: + - id: mcpl_028582086f036f90006adb38a6333c87d0a7527c0158e5d25b + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools + - content: [] + encrypted_content: gAAAAABq2zi1UOdNvGlQQsUdKkWtmqQGXlIxlzVVZiJ60m4D0CBY3P17daqHTFAAIXoYtJqYBFP-a7W66O2e-qlBK086tM4qUHjvS9SJtcOyvbMMv1Vb9Ey_IKVsPa_FE8UTX1oYVd8hLVTmQ0YBqlZgTyO8aE5zjjNH4YIEN6A4tpqWSTsp2orShheC7AxV3ZXJkreRRpS5PiTMz-kBLD4Xdyo_DNhUoFPb3R7O61g-pXC_bKtOlRBaSSvoW9Dh6Kn-jdAn_RqgyI_GO0QlRSWwpT0L3f1nHgBsfxKe8nPcVf7mAsKyZhaJshsmpijl9jLhF6vVf0pOYuftf62ds00_IuDkvaOf5D_c7lnTQSqisZlcJCpmBxYhS8ruuOMoq_43ZwIznEbkDigZl2-mBOSgKG6KeDzvH1UtBa2PH4U5KHSzgR7kJ_v4-AH9pDcT8Cz_jEiM_xvKsFbk3y2SYE2UWHtQCJtxr2_VxuH5fYdtpDQ4zcBERcl2h_xLd72WUVRMXC7UnOn4GlDn1lFRe05xDCp_men2crXxwQSP-AnDajRvRlc4uoTSOtqdg1tILegk7a-loExpEo9JKUKDzfdUBcCDI-SiW2kcIpT7NLQXNo001bDDA9TvR7R4tNRXReulam94DmQGUwBMQ6ie6-XvS-k8-pcXZgU7ix2flM10jEgTMZrdZTFLANUHCSm85w96EjSYtMd1_0LisT6tFCUcjZAVsHPCuWL21nan1QcrABgW3r2ND61Kplr9Vb7JVRfvJsYMpKyv44CzOeJyX36HJWX0c45859NWBHWmINcdvDEUmDgKW4OZ2jHpWx_TEcQ6jX9no1RqhJRos7nBP17_BRbHMGdTQ9dRbZwWjeqOCrHcCKRzrXcKYOY0qw9jNbhBZ3kUPh0kThyehm9q9QGDs6igxRTwHfAIIqJvBDb2ZDUbFkCS85G5I3Q7geqp-RmaWFS08iYqJ8WboTBOH_MeKmLn5z5kc1qZ2wyNFqhiWR2kwfqb8layUdG-VF050dE8Vl4FYukbKfj6LBemcrclAevYN3kAAAgucl8QVvoMmF6MquUA19mejnlGONCdKYnCpwP8Lg6jJfxBfkxiA8-jhOCUnkJc00r6AuVRzhDYabcoO308xTZtvI1Emf9smQfvPHE_-TdzjPrO03IUhdsinuJlnLzwWSZ098M_dL1aGEeRWHrWfZrpX7j4fBKC9tmnmJBKeOjXZjPIDhlnoYqG9gS3kU69fY8SFJ2sGaRSyAh5cptnfWHbfgUj-Upsu9f97a4sjTY6_sRl2wzLSuM_OYyfUSGyLtmconx29Q5p2VKi172Ri1Gb6eEGxIXFN9F5-bW5U7eSQCvtCW41W7fAuRJ0bUxZoit_wf7BN3iTfmzK0cOaRCXG_MMDiGfbjSe5MlWRO15Z-JcbrdyIuPb17r13bTv_A28RPt3bcC9E0fsn4l97qbMktQl4XcnjGWLlLyo_LWwb1TGwG4BsoYNIbueuqO7zAo2zGLNpPblviqtgZbqqF-9vKER-qHtPg6QK8nr-daSawv28UUVuC2PmElLCVLHpTZUXxtSWpJD9VW5_YPRRmvonIdoHRN1f86kJ8QpZCuWn2vt-ZCBl62oWc8-ZNuRjX0XnOPF05pl4scRyaGgglFBhiKJMmHshurSTYFvCCjXj_EmGb1C0BfPONd33XO_y5K_C3njbpmDa3OATXJ2Gztb-46YLQMFwYPyYtbErm2NX8vfemG80GWFG8mm-uShdByW55fOgAH49kekz65iyqB9Drg1MYm8ZIQ68LFeKRCRVgMLTBN0thNNjmmMTygKgtNIzn1KUMsYvWeL2QuVWEXkH3z76vVxJ79Db80TFJDje2tT6bT_WyJz3OEEQa5EkXsD5mIz1L3w2vMpDaXlyUpM8Vn_IZ7JetwUAdBTtRPGw8S6J3LMXumc9OD1D6P4HkUdEvvc8ppGtE6ZzgHSAN3s= + id: rs_028582086f036f90006adb38a8933c87d09fb12987127552e3 + summary: [] + type: reasoning + - action: + queries: + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + type: search + id: ws_028582086f036f90006adb38a91ca087d0bdb5f35f1d6bf3e9 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + type: search + id: ws_028582086f036f90006adb38ac260887d09b71c0ad11d3f615 + status: completed + type: web_search_call + - content: [] + encrypted_content: gAAAAABq2zi14HsARCbIKMxz0-ISOPlbzzhmHJ3YbzUaaduCSJ11gVOfVKPK8Wzn9jKJFkgCEicX8RvZGJlFUOX-xKi2cumMGoD5pa9gExybYg9F2Hr00LO5Xbjsh7kSBB3cMiBnvwhRfueizw26MVjkK5IFWep36dNgBHMZf_0rYeMX_5umhfqsEGenpE2xRVbm6BndUae0_LtoM95Gxu_jXNRzZqtqRFWu-XklZ20PmOrKgoT6HBLTTDGnrmQcrazdNQ3BpyMzrhil56mDcNJPqnrJwXy7WoO2NAMkVJuV87wqK3Ci1guvbXLKZaUZwRr7ILF_8eQFGzV23Q3mLVbR3H-eqEkvfsqLdeisSzKpCnHLDE6KuXsFK4uQgZ5iy_rTTTe0znuE5rVF3aMTUFpx25Auv871xoPkPaaissBdArfcxjVEiiXwZaPC4Y4xZlAUICNx5mWzG_ak5kE3MX3JpFoEU027frmMjTbpVnuC4cbpm0tR_oxW_VFm4SvDPfwk-ex_RT_R69JAKd91_k69p59PrDzSjmC7n_XggVud6Ct5-rMrV86j9us4MBJH-f-wedx4p_x6SdKCPk6GaGYsCc1j2tr1duoP7Lmx1tdRNi-l7VA6hLjQbZuR4FJV2V6adSmt50tbKahYqBJMs69kzzaaszKN-heee-QLFJ2MWHUHsahJKruLgPVlHZAPxGSnGieCMcq2AW_eFsA8Ur20Sq2ELdFXQeSdK_ZTQnG4xIn52H7qXZy6nI3eAkx4jNglSdkECpoWdd8Bm6-ZE4Lu5_YqZ739cZobWi95B_1fpMv0hLd6DYkpUIUv4sdcjbV4WpmFlF4ZCJYTeByzU2PIlIDTI_4wEHgtD6PK1Xlemm17uR9PRLLuBRan0PpzCjng1ZCXpvL61j1Vrc8R2SJ2nU0ZeHn3G6iqBuX2WeQNymkX1I8FydAd-WBY0ei3cLXtZ4lILxLS9Ykv2X-fDsx3rw4roRAsoTgj_xlr-zfBw0UO23xDm1DeJnYAmoJV_1Rzrw7wHzVNcJ1Qy6WoicNOZ2JgsUM5-ro1FY4pQMTDU6pLZK6rszDHpRVb0o9Fe86revz3xvOs4ioX6BEyLp7a0n1NsBi499JIfYNf6sDH5mTqRfoeVIAdq3Xb1o95oNldEvPaNKb0EDoS4JquL-XzMR6PpMQLyh_vFDGkrZGVGCFRnpMIwDqgXX02cycPIFmsqnHdzGv95iRSUWQPyFxraop1DubIbAJYFQoAbcPY9fPc4OagNdGdToSFC2jbMa5prI0rTdUkFqTOcuBgOutUoVbbWtvfLO0N6b8WtRozwSc6TpJMN7XpxYRZ6E25uYvJ6Jp-dzJZg_l_oxYQJ8bqx0EnfOhagm36GwAYYUGHMxRaG8evVrHXs1TTNRjOWmN7jVR9McMNbmVDiy0TGzHCzTKYLqaICEI1E7MBoVRgayw7dLQyZWJ4yVcqadq1UJv1tnZNTeojXK3RUGQ54Vu-UXifsOLwWpv8sVzIuI43nu-4HgWKGpnlxHc5Hzo-pKLBfWz-BD71noEweLGxMx8rqbaosMwUg1DITgvwJ30wXJnyc1KkFNVjBE28lwmlIM7g400Wrgo3moIE_mmbmPqPCV66nFw46nVSvUP9lCcxXx-9aQ11xzoUd3J5oAIE9j-_EpHksawErZHeH2MbZTkyjqDaxssVX-yoA4joz9jNr6Wl482Rz-Ev4GVDGoFHtsbi9LYyBs3rULXwo7gUlJ1aEfzAMSY7LNbzVOFSrDoVTxp0SuUFHW3l0bbFjDP7ocCNsWAUPINMy7teCTFJnjhgzTKw0OPMY4toBcO3MN6KQNBNtSIfw5R9hpFzxbiZKzuxbcjJKWFFL7j8jU6Cymhkl9REmhn7kA6OnWLp3iT3mjrZ5HQnwcM5bHpHIXVRMd6R21aO2Zv1aQ4C-ihatQZyUz6FStgbYxilthsgCdx1N0quxHofD-8c7LUC7YadQPBCnrXGdZ91sQheimHMAoRt6EgyrJ5WpH8cu_hK58S5gf0S5-COImaE2RJn8VZ3NAKkhtSDXs6wvcuQO6IB-1IcvDn3rj3HwoTcHa8S3ENSJHHz0YBBzNvbiUFSvucQWVezkPZoycS9JKzGJmBTOedgxT6MPLSNX5FNZ8AEGy1iLykSC9lNnYwEwst_z9Fvu6YywnRG-oaQkVFF4eGNJ7eXx7ZNXZMgVK-L98k8oq3slIiD9a_OlpGMG1PVTOeNz-bi2vX8N8lU2qYGJHse5GG5qAVOLmHUkg0U7qAna6dOBWC2Vp3tfvjDXLoFsCngkO5-_aRocXXmNY0ae-aupyqs-ttNqnFkooESKVcDxCv7dkPG-F4XDDwAlAnEbbmJdRTPUdrG45gCB2nGG6CbmpwlPwOc2aVqVYcQJpsSOzb4SKLn2TN8CjdjkULVvkVykFeKpwrO3A8p2CZkotmwUr9oQja7Vth5TI124dqimJA5KDigxaqCG94zkB1VFJmUORr55mbAVKYCpn1XXi75nnj8NZKupggCzucFf9KQWf16TOrL058mcBIWV0KGLD9l-26sfwToMr1NWPTRbHEiICln6J__mLrWbbOWqhpMUkbElL0yE7RJrRtihms2wD5sLGhPseCmrTxVrry6RnzVJ53nEmGh1FBUf26P_R81Og2lYhko7jJtJDZQ5q0CBfXN2FCpcGbbHQkEcuqLZOkOLh8PBceS0VVflYrlYwMovH4M4-IZmM7zy-nRfVJ-unuixwic74dpFG-QaaN4FLiy2UoNWNRYDSYbs9KbfHILtLAJq1hx2Rz_escMxVlW-HsL3ciGvSAnwBu63VZ5zdy38yj2BvDCf5rbrg== + id: rs_028582086f036f90006adb38b00b5087d0b3420318146f3aa4 + summary: [] + type: reasoning + - action: + type: open_page + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + id: ws_028582086f036f90006adb38b192c087d0acac3b7f7a3a0fa3 + status: completed + type: web_search_call - content: - - annotations: [] + - annotations: + - end_index: 262 + start_index: 134 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 528 + start_index: 400 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 808 + start_index: 680 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 1073 + start_index: 945 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo logprobs: [] - text: OK + text: '- **Potato:** One medium raw potato (148 g) has about 110 calories, + 26 g carbohydrates, 2 g fiber, 3 g protein, and 620 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Tomato:** One medium raw tomato (148 g) provides about 25 calories, + 5 g carbohydrates, 1 g fiber, 1 g protein, and 340 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Cucumber:** One-third of a medium raw cucumber (99 g) contains about + 10 calories, 2 g carbohydrates, 1 g fiber, 1 g protein, and 140 mg potassium. + ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Carrot:** One raw 7-inch carrot (78 g) supplies about 30 calories, + 7 g carbohydrates, 2 g fiber, 1 g protein, and 250 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo))' type: output_text - id: msg_05b26e957aa7a7d7006adb2d9f6e908199a976fea1705f7adf + id: msg_028582086f036f90006adb38b32cd487d0b059e5f3d6a1898e phase: final_answer role: assistant status: completed @@ -111,20 +176,38 @@ turns: text_tokens: 0 total_tokens: 0 web_search: - num_requests: 0 - tools: [] + num_requests: 2 + tools: + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - allowed_tools: + - search_tiktoken_documentation + headers: null + require_approval: never + server_description: null + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 4236 + input_tokens: 17132 input_tokens_details: - cache_write_tokens: 4233 - cached_tokens: 0 - output_tokens: 5 + cache_write_tokens: 0 + cached_tokens: 4683 + output_tokens: 488 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 4241 + reasoning_tokens: 260 + total_tokens: 17620 user: null headers: content-type: application/json @@ -137,7 +220,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_05b26e957aa7a7d7006adb2d9d970c81998accadbe8276534d/input_items + path: /v1/responses/resp_028582086f036f90006adb38a6029887d09022c2dc4c9549ba/input_items query_params: limit: '100' order: asc @@ -145,52 +228,21 @@ turns: body: data: - content: - - text: Use web search to search for the exact query "potato", then summarize - the result in one sentence. - type: input_text - id: msg_05b26e957aa7a7d7006adb2d9dacb48199a4d8ce6109eb9c66 - role: user - status: completed - type: message - - content: [] - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b - summary: [] - type: reasoning - - action: - queries: - - potato - query: potato - type: search - id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 - status: completed - type: web_search_call - - content: - - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster - type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai - logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) - type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2d9dacf4819981704ba1d4fb0546 + id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb role: user status: completed type: message - first_id: msg_05b26e957aa7a7d7006adb2d9dacb48199a4d8ce6109eb9c66 + first_id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb has_more: false - last_id: msg_05b26e957aa7a7d7006adb2d9dacf4819981704ba1d4fb0546 + last_id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb object: list headers: content-type: application/json @@ -199,43 +251,27 @@ turns: request: body: input: - - content: Use web search to search for the exact query "potato", then summarize - the result in one sentence. + - content: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel + for two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one sentence.' role: user - - content: [] - encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b - summary: [] - type: reasoning - - action: - queries: - - potato - query: potato - type: search - status: completed - type: web_search_call - - content: - - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster - type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai - logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) - type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 - phase: final_answer - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_028582086f036f90006adb38a6029887d09022c2dc4c9549ba store: true stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp headers: accept: '*/*' authorization: Bearer *** @@ -250,33 +286,196 @@ turns: background: false billing: payer: developer - completed_at: 1792748962 - created_at: 1792748960 + completed_at: 1792751810 + created_at: 1792751798 error: null frequency_penalty: 0.0 - id: resp_05b26e957aa7a7d7006adb2da053808199b740ff50c3547bf6 + id: resp_028582086f036f90006adb38b6924887d09b92d1e117ec7697 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: + - content: [] + encrypted_content: gAAAAABq2zjCFvgMCpkjUaESR9j4pAi9oWEdAgv-ZLG1ElVddrJUdTLgQwzYJT4pflxN9zb6Xc-DmlxplToAF5VzS0xWEvubDC5LjrK1pdIU_bDP0nfpB1DdTweJFdgHtTjE4EXjMz0iTF4vWPfsxRS34MXgI6IFeFtiadA511eV8RCsxPO_6Dzn78bQGVj9yc9ugsgeye7-Mjpuw9bBVLnVKVrPtZG5c0c01v_5Hl8ZIQWt1YFmu3JxjQFEYindHButXXrxqlawCfIooiv5Ux4P-STLxYB-DivGCfO27WVn9ydMFlcywjcNQ4OeT0F3FCrfMk2uFz9NCMLr-mzKrAMl2U0qdJ2OmbmtOMGxz3DEPLF_1VSoBYR5zEuOj-oOI3eIFeM7M1LGY8mTVyIAR1zFyFzx9z8k6m8Rt9UY3x1AzDWLt6TbHmfJ7tuH0zAGmCiYQaq_NTC0B_IusSAl3lOZ7OeX4MaJQTob8Dwnjj513fxRtSuLTVq47h_VhShJeO0nobmH5bDAiWjNjP43kvyHqpGNbp2G7lrp5RFHdDrbGtA2fxFY3KRGMmuua7_wlxN_AORZXkbLPKNPwXR4iV_Yzmeb6GDliXAUFD4EFPVFTBQdFwZDkquyQ4jXmc2AEaDcJTglRCjJeIz44AGxabjaHeV4W1LkNpV2VGMJJyJasaQ6xTX1EMKNyq0biTvXi2FMMXlpfNA-pY1vU10g82M3WFMaLMguee01kwqsdDIre4jRjN_JecsuakaChluoVCWU9fVnIulfqbasH-F4vGCaj0HOwMer4Y5vjHhzzPzzaDfxQxLrWV8lstXTO2O58UN_7YafxP0fd1FG3PBxs9d9dUs53FQh0h2ZWiFR6OnLcegNUsABGBVy-GWuORsSgoNRD3XCKjjPU0DKiUcRnCIFooopSgk_0Rie22fRwPiwb-SEFfxBg46QPxY8EWzlFzsSEVJzKdTx5J7RAMat9Vk7j9LhDAqF1V_mbZlr0gR-R7UrGeF7ucGXkw-XsBRpHtetPNU92hHj2pi7v5ppTPtrfxDwhRG-HydO4vy-cDIlsVm04560ZyHaGpVLUyBsN2Dsf7EGdsM-7J2bprpD9WESmp_w0-hNwqVPMVfMdqPAhp9tSnXG4kH9wg0LhHhN0XErYDd5l13S4cKFnRuRbTqduTATGn-_J3WegYQQz2QZ03XyfGRws9f6R4yUUgwyc2Xc6QsVcU92FqiYEP3zVIvMhd6uWq14L60D8VO8PjXPc1lKXOGoE4O2uykwmEugC1tWK8srH8_uLWADSvTc9AwNkWVUFS-AR8u2zUjNhHY5A2NVbn_erV3wunNdoyzhhgH1WNYiScmL7P4Nd9kJrezlw-VL3tRqbgfvXU1xBB3F9gOdmyobdxcKW3-xLwyRsmKrZv-wWDZqYa1CfdJhWyy45_3wOfSOPpQGF7FmbkZo_F_OieHjtxjj2ABHclbkCpWKumFLwCHha4jNKoicTzXqKTG45X6yEhOdp-XdD_DYxYdi6RjVtYzn8LBHBWTO2b52ddPg95nKoIq5mbihvO0lJs-nEatfUBaIRGQvPkIKDGh1bRMU0Wo= + id: rs_028582086f036f90006adb38b9043c87d0a251db279fc9b317 + summary: [] + type: reasoning + - approval_request_id: null + arguments: '{"query":"encoding"}' + error: null + id: mcp_028582086f036f90006adb38b95aa487d09c3039d3a5c93662 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query":"tokenizer"}' + error: null + id: mcp_028582086f036f90006adb38bddf1887d09e788e30fc1cdc50 + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call - content: - annotations: [] logprobs: [] - text: OK + text: '- **“encoding”:** No direct match was found, but the fallback documentation + explains how to retrieve, use, customize, and register tiktoken `Encoding` + objects. + + - **“tokenizer”:** No direct match was found, but the fallback describes + tiktoken as a fast, reversible BPE tokenizer for OpenAI models, with model-specific + selection via `encoding_for_model()`.' type: output_text - id: msg_05b26e957aa7a7d7006adb2da25bb08199b684516b0411186b + id: msg_028582086f036f90006adb38c1ba9887d082bf4912bcf37cc9 phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_028582086f036f90006adb38a6029887d09022c2dc4c9549ba prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -307,19 +506,37 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - allowed_tools: + - search_tiktoken_documentation + headers: null + require_approval: never + server_description: null + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 171 + input_tokens: 20221 input_tokens_details: cache_write_tokens: 0 - cached_tokens: 0 - output_tokens: 5 + cached_tokens: 17024 + output_tokens: 156 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 176 + reasoning_tokens: 14 + total_tokens: 20377 user: null headers: content-type: application/json @@ -332,7 +549,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_05b26e957aa7a7d7006adb2da053808199b740ff50c3547bf6/input_items + path: /v1/responses/resp_028582086f036f90006adb38b6924887d09b92d1e117ec7697/input_items query_params: limit: '100' order: asc @@ -340,52 +557,124 @@ turns: body: data: - content: - - text: Use web search to search for the exact query "potato", then summarize - the result in one sentence. + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da064748199869ee22f22949892 + id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb role: user status: completed type: message + - id: mcpl_028582086f036f90006adb38a6333c87d0a7527c0158e5d25b + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools - content: [] - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + id: rs_028582086f036f90006adb38a8933c87d09fb12987127552e3 summary: [] type: reasoning - action: queries: - - potato - query: potato + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts type: search - id: ws_05b26e957aa7a7d7006adb2da064b481998ca513fb543f498d + id: ws_028582086f036f90006adb38a91ca087d0bdb5f35f1d6bf3e9 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts + type: search + id: ws_028582086f036f90006adb38ac260887d09b71c0ad11d3f615 + status: completed + type: web_search_call + - content: [] + id: rs_028582086f036f90006adb38b00b5087d0b3420318146f3aa4 + summary: [] + type: reasoning + - action: + type: open_page + url: https://www.fda.gov/media/76886/download + id: ws_028582086f036f90006adb38b192c087d0acac3b7f7a3a0fa3 status: completed type: web_search_call - content: - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster + - end_index: 262 + start_index: 134 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 528 + start_index: 400 + title: Nutrition Information for Raw Vegetables | FDA type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 808 + start_index: 680 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 1073 + start_index: 945 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + text: '- **Potato:** One medium raw potato (148 g) has about 110 calories, + 26 g carbohydrates, 2 g fiber, 3 g protein, and 620 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Tomato:** One medium raw tomato (148 g) provides about 25 calories, + 5 g carbohydrates, 1 g fiber, 1 g protein, and 340 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Cucumber:** One-third of a medium raw cucumber (99 g) contains about + 10 calories, 2 g carbohydrates, 1 g fiber, 1 g protein, and 140 mg potassium. + ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Carrot:** One raw 7-inch carrot (78 g) supplies about 30 calories, + 7 g carbohydrates, 2 g fiber, 1 g protein, and 250 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo))' type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + id: msg_028582086f036f90006adb38b32cd487d0b059e5f3d6a1898e phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da064d48199a2637188cc9a6538 + id: msg_028582086f036f90006adb38b6b01c87d0b98150cd87c47ffd role: user status: completed type: message - first_id: msg_05b26e957aa7a7d7006adb2da064748199869ee22f22949892 + first_id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb has_more: false - last_id: msg_05b26e957aa7a7d7006adb2da064d48199a2637188cc9a6538 + last_id: msg_028582086f036f90006adb38b6b01c87d0b98150cd87c47ffd object: list headers: content-type: application/json @@ -394,43 +683,27 @@ turns: request: body: input: - - content: Use web search to search for the exact query "potato", then summarize - the result in one sentence. + - content: 'Do both of these right now, in parallel, in this single turn -- + do not wait for one to finish before starting the other: (1) search the + web for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' role: user - - content: [] - encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b - summary: [] - type: reasoning - - action: - queries: - - potato - query: potato - type: search - id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 - type: web_search_call - - content: - - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster - type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai - logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) - type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 - phase: final_answer - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_028582086f036f90006adb38b6924887d09b92d1e117ec7697 store: true stream: false + tool_choice: auto + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp headers: accept: '*/*' authorization: Bearer *** @@ -445,33 +718,139 @@ turns: background: false billing: payer: developer - completed_at: 1792748963 - created_at: 1792748963 + completed_at: 1792751822 + created_at: 1792751811 error: null frequency_penalty: 0.0 - id: resp_05b26e957aa7a7d7006adb2da34de08199856a35d7aa499a1b + id: resp_028582086f036f90006adb38c3b06487d0963b8de31aa5af01 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: + - content: [] + encrypted_content: gAAAAABq2zjOicIf4pfVUsJmWJqHw3d-dth6S9BihBUZFKzfIePUtzb1ytMKsD0phdmeNgUTJss1zts-ug6nJhiUiJUzfHkU6T9fFpjCuF5J_Uk0YKwZBg55PJGON90W2wuXhRD4SViTnzY58lGOqPNoDjJBpivTzca4goGa4K3xRI-aZsw5I0l3CLyTOLiia40g8oRho9wxYiszQCt6N4McPV0GWMg0fz2dkspPXS_DFU-8xwEhY2oZRpkk2owq7eA6wYcfvLOCdUEncsBrfRLA3JfdrbfUvi_j9H7fd-0CCxpKY-L0RYsiOhI6iJhlAcfo_Fm231BVB0FtBUNDGh9K7HcbJPEzGElCED_Stey8AyYygpdWWJ8c0lWz2nFhnOf7YCtgL_9NfisWvLP_AlQwS6frBnzxb3Jreflsg9nRK8JuIdtv28h0XYeKk9dUY3FmOplRVAZqXV5A6H88BsMSsBK_8YQL7bu9lEPkCaGT0f25zQcUzpX4xCPYyK6aRS67Gk0NPEY9RLPXgdVeNOH7mu6ZJfpn-YH7VBuXYLeNqsu8HN3gO0E-v7QqgM42MHGonsgvzfEdNIWRjXaLSkKdKLLfE_IoK9covjs0SBcxUb4MoPU5HNLKxp36marHxj0J-kdQ6p8HxwC4aYOFcU3uTY6kb4TNIDxpC-mGD8xYCYewbFXkejZ3AbjqxlLB0-EI7QUgU2x2CRMDGYBlQxVFfJGZWnSsBC7b3nFIkcOYKjJL9aNmKute_Yg734JG1WYDo_GKESkkJJ9EaOkYPVsjwsFbk_6Faxwke_hZGydGlOiuaOl5mNz6XMflt-g-eTpk20--x0ue_Fgv5gGP95v00kLzHVoU5K8tdjKsA2BOOQeULKLWp1DpSZWeveyrLfAYGrAreExPAv07MQ7ARTnDesMoMpwjn6T0GJzRZ1ZbuxN8akuyRmy2XQiTi8CGF96ZsHiATE3oNJE2-VK2q3aYYTxwxgaWJrc9XD_5ZCotEGM3ZhTu3tzS_BQhtU6c_MGOMAMgI2mWjCujUvG-7gYgMCDU6m3hxVPCRxS_cwUtwCmkvGMsZUWVIfIOhbQCIpHzu2zMIGV-SC-fLqDmlbfeDGc3ybmxpz61mz9iasOv0KDmpA8yznLw5wE1lLaEsWaNIIcwKevmDQMr228qUBo_KyT1-6oIWUwIeey66zlJcSS8m5VR4w5YqqG4OavpFoKMGnxMAFlaNlM1p_lW1JUjLfKlH_qHukxuAn-9ICBmWbJs-bB0y-2Z0Yk6OxaQPABexMCyhcPI8uVVj8tx5D3X22xwnPLQwjYesSE2Iig2Fcg-EmrNbIHijoq0pTi20qp-Ya4j_0weVIoW-tkkSe3f8HqQuUDDmIsIWnKWPghIK84Sy-qEbXh6zIhTE5turnGCM8fxblpjM0-m7kpx2m-ON12rznAPbqELNpr3E7aW1TKnBBixUIDgDl9lDi2k05p273f36kgpbQqKv4-WZZtFq_ygQwyFa3XV-oK6zGs60DDo6ypYk5hgDWnTrKipkHI2JHUcBZH7L-MrxLOxU2uteckGdwuSa4wms1imG9tvp29E1f_xRPF7f5NQYDuuhrZcdYtzYPzM0hx8wLDhTC95gcf3p1o5H2gnPWYd9pMQQRJXTfOc0BGLFm_4Ca4s8p-qxfIU0h_7Nh9YkkZ064TN5tVUPhIjYg== + id: rs_028582086f036f90006adb38c48bd087d09cf2d6b9d00c3424 + summary: [] + type: reasoning + - action: + queries: + - latest vLLM release notes + query: latest vLLM release notes + type: search + id: ws_028582086f036f90006adb38c4ffcc87d08f2761f66559b187 + status: completed + type: web_search_call + - approval_request_id: null + arguments: '{"query":"encoding"}' + error: null + id: mcp_028582086f036f90006adb38c9505c87d09f63f61480673f82 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: [] + encrypted_content: gAAAAABq2zjOa8-gQ4LtbZh0cpdwR00mc1Z_kIbTag0rkY_SdozVpozjBK1SkI7Rma55Q1CJYM-PYpjR7a-mAtb3uthASyEFf1Y0lInm7w90EXt44H5xUcp483XqP8KZOnJKGPpLSblPvxE8lVJVBHKsY6syXjH1hVolr3eGRW7WXkBuWuPC4x0r3bbS8WkfGXDqc3s4dYqr3-3dNFxk97xrXLkEc80upAC8SAbV5CixRD-RdysHvI85wDZMFhJTLCT3J6ocOtA8m5B68N21VgnlGsJx2QSk5KGaDXiJx9Ew2gzWgQ8jV3RjvT7eqxjaKEqQJ4wfLi8jre0HQKbV_YlI9bsARo9B_t_s__iqNMXh-m42EVqC_94TIOOEk09HKKmYRQNnDl5BqEAUQZCxzSQDU_L35gV4xUascQe8rbfUcw5LepgOwvtfDAmBewpa8BS1-pxmSNQLplcrBqT9HclM2TkfPsWYgBmetxZz9OdbnseHR-92cHxYXtroCs4qOEhorXMImGDlK_xMy8brFCsU_oocPBw9n3rBmUm3o-SlJJfYAzZPFLZaUsYdkAfoDgkLZ29GsBgJOCqQisnWABQmjB-VsOXf9r36PFoQEWNOGUqTMV8VGSfogNiClelCrGwMoxn2HYrNjbIth49jmO_I6JK-g3prgc8A7RWxO_WhzGmfkCq8EW74R6eyagc4l6dEUGJFsViXLSkEd1hotOOK40jlS65OOOzo_LEFPSMewm_5a0W-ZWPVmtNbq7YYW5j2m5uvzsCFPuqzMx1xTYgueLG3ZpnwGyQshXTJvj_1x4_Ta8dO-L-YOEIh0KHTkQ11czU-1I8as4rwYeQEw4hlKOoTy50np9QstooR4lNgwD9e2rwYXMl_F5MIKbLJpS8osEBg3273rlKqEoBqSCiYqETOBQc63i9iHtdh1PrGDcn7y4fy8cnEBoZquz2Xiovke6j2iOFbuA1w0g4WpkfISFdhPKjZZjKDAEetQ0gkL8shTSD-HkBF2hf3e68_nVOos0heQhdvs-ZCrkyHJOd5P0cBJYkrNx7RfjeRefXl0tj-BgfcEWqcdCzjmLFHvwl1NJ_bVfqwPQxhoIj7XYHNWYwZS642_Qe6e-GaQy3ykkiv6vxsHUeOdKq2yGtCmozA9d7Er_DVuGHcnKRomp6oh_puKMgnqGWP0-Eq9ckwQeBMj4Z0Fy4VbGTkZaH0_DNhQTCNLVnONWbZLQ2QXkmYiCjWvysTtDROXHmE8ZLVLcg9YtTjhsn0D5nLGXVQFXsRbfGDCkgzYat1UWiuUNdC7M5FxFCAQ5CNfnUhnTK5an_3BZR_znvT5qymKNIPFP7Jsfg5B3F17t7JrCatsWlfK4jNM63Nzqv-8a4amBiyYk_haCY6GzvmLHK6pMHPyy-FKZYZtWg03dDELpZtDgkQd6kngfZ_F9ok6TgchsIPxtGIra_ZvROgpfdblFBTFsJurrr_ubfV9EEO3TxhXWFAnrLwElx1NovjPCaI8WWkm89wevqMQew8_xPh5ZkriY464qfnGfNNbQJFMKmfnoYBziRP4BgmX7gcrCR45zbrXBaozVhJEBKuJE8nAUfwymBOwYZG3qgsCio3A1cU1VU2jTH5gqNUEgsMzSCmSqSOLY6oVlVXgK3cAmeBlam2ncvqJ4d_AXgw5kOVWKTXkcVjLckcVLp7ddlInVatrCPzhD1QQpma56yzgFWTd0aCUPkm488zw5RdoRMxECGVQYoDNYEfIJrzCjRDtnhvDWZEMFWPTFX_6uubrJUvvMp5-bhTPaWInqHh95yUEVYF1bYsitadz42fY8Pjv9V4lSxFlqh7WgEA6RZA0AFYIAbaiQLppE0F--M_F4P5itDeaGRUFuh5IT4SYsWnIRxWJdzt-ODDFVRKpWKoeraJQlPsc3lgE6NKkNu0anB01gS5Sm6Qlwwk_n8bZGw6lok8mDdNDJUNVqHB1VmDMozp1tr9D_TBUbh0a_1YPWhu-3NFMc7UYppE60lDEZ_MVU2Yn09pV73cILIZ48gSPn58bMJ3yAOmvb4nkKSkwayv7_51tzIV6fY-OmuiUjcf73wxxSGAN0L75Idufuh9bTeQj7Ohxn7khMbFE4mOvzfD5nJ7JQUDvCAvTaathmbZiLbJzY_1IpRc8Q14kdSFBr56Y_ZwlH4yl-J35YPYyU2tsd-5IWIPI5EVdhglbsGHKGtB3wWWKD9JSdiJfYx7nUOgtKMOOpOvcRsetiVWtSgyRZiUixm26MDkceXS8nCsYY3kB8QEiJ7zAtTkDOfuMdTg1FbryuMvqNuMPM72Vp6hs14JTNfcfazptA3G9YElcdV2XpQ8xEf3m9-2NNU9PvXqte2hv6B4p_9RmudnO39SYOwbSb8BApafAWic467prfzo8-x77_jCnUvPkfcmaj0DPYYvnkWhV7lz6AQ7EuMTdf_cP3x2f6sBsESekvXC3MQe5sfsqjmNFh1laHJxWU7PkohoFn44xD3EyKpd2GSs5uOSvhUrWjzluzufSX8mRiQFPxa6z-lGqwaC7QTq8fd875Js-8WUYsOCa7CGzKOzjjl3T0sZ734qI3No6VL105P2Wr0AaRXD43IWEG6tgHNyrjimOVM_n-zCEtD55dw0grhvE7B5NqKHlYoqljk4YFsFrnC308YhazZfNlw3LhhuyjFBOHFgPlJUF0serad13HcNNutE2R2w1otj4czo0-TQIWhD4qBSB6usoj5_jUKxgu8cbiq6MZi1dic_Uo4b8hdEY1c-1anwu3pbao-U4REeFK3A678E5QNeb0cbD_bdB_f09NmrVNZQLhSI5x2wvMnnvvumLyvvBNUrYOfY40TFnFA5iXFU_nFSb4XmqE4wUKyP_9h8rGJ6FMqt0rXM8Iuk1OLHQvvvWVBjCt3hmNAE-qEhkyg-HtxFemNgRJFr31IPeJpo_hPrLChDi9Q6asn4H2x5IsB4fOMz2rKk3ps6xoen9tYIxOLMK46vyeikivixtKGGdGb4FOlRgS1XQY1LhZwT-Rmo5FiqCVJ59Ohqg6EWBBxnzlpdCOVz0DYPuZVy6UwRloPv9iheoSxSk9Jw3r6H_x00JGRMuZew1bBNLzPtL7Gf91Z4sYX7HnTl7eWh2TtV26tDweUwo_sc9Ym4DHMsLMRpzSSrXjSr0jfEWhsFCW5moqxmyJUZYwQOsiLPHsD7nePwWXDqVHoXELiHwALPxspI8eKGKv5ZGeEgCg131cJMyvz9LtOB56RLZgDKsNkN5ilW-20LHDvoSnKJxZhlHbMyFRlC5k7n60AaiFFU8GXXfWgV5QmTJuVCV9H7ix8rlW7dVRilOfJ-dQ0feWWvzKECaggbly4i_XbbMzJcHe5TUuazvEo= + id: rs_028582086f036f90006adb38caf05087d0b82880a44827ea47 + summary: [] + type: reasoning - content: - - annotations: [] + - annotations: + - end_index: 261 + start_index: 182 + title: Releases · vllm-project/vllm · GitHub + type: url_citation + url: https://github.com/vllm-project/vllm/releases?utm_source=openai logprobs: [] - text: OK + text: '- **Web search:** Results surfaced vLLM’s official GitHub releases + page, while a secondary release tracker identifies **vLLM 0.32.0**, dated + October 18, 2026, as the latest release. ([github.com](https://github.com/vllm-project/vllm/releases?utm_source=openai)) + + - **tiktoken documentation:** No direct match for “encoding” was found, + but fallback documentation covered retrieving standard encodings, selecting + model-specific encodings, and creating or registering custom `Encoding` + objects.' type: output_text - id: msg_05b26e957aa7a7d7006adb2da3dd5c8199bb8b1add72e05e63 + id: msg_028582086f036f90006adb38cca54487d09c437c871528144a phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_028582086f036f90006adb38b6924887d09b92d1e117ec7697 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -501,20 +880,38 @@ turns: text_tokens: 0 total_tokens: 0 web_search: - num_requests: 0 - tools: [] + num_requests: 1 + tools: + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - allowed_tools: + - search_tiktoken_documentation + headers: null + require_approval: never + server_description: null + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 4236 + input_tokens: 25809 input_tokens_details: cache_write_tokens: 0 - cached_tokens: 4233 - output_tokens: 5 + cached_tokens: 17024 + output_tokens: 357 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 4241 + reasoning_tokens: 211 + total_tokens: 26166 user: null headers: content-type: application/json @@ -527,7 +924,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_05b26e957aa7a7d7006adb2da34de08199856a35d7aa499a1b/input_items + path: /v1/responses/resp_028582086f036f90006adb38c3b06487d0963b8de31aa5af01/input_items query_params: limit: '100' order: asc @@ -535,96 +932,332 @@ turns: body: data: - content: - - text: Use web search to search for the exact query "potato", then summarize - the result in one sentence. + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da359588199a8eac6505d8c2b31 + id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb role: user status: completed type: message + - id: mcpl_028582086f036f90006adb38a6333c87d0a7527c0158e5d25b + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools - content: [] - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + id: rs_028582086f036f90006adb38a8933c87d09fb12987127552e3 summary: [] type: reasoning - action: queries: - - potato - query: potato + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + type: search + id: ws_028582086f036f90006adb38a91ca087d0bdb5f35f1d6bf3e9 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts type: search - id: ws_05b26e957aa7a7d7006a6c9a8d934c8199b6e86ca1372c4621 + id: ws_028582086f036f90006adb38ac260887d09b71c0ad11d3f615 + status: completed + type: web_search_call + - content: [] + id: rs_028582086f036f90006adb38b00b5087d0b3420318146f3aa4 + summary: [] + type: reasoning + - action: + type: open_page + url: https://www.fda.gov/media/76886/download + id: ws_028582086f036f90006adb38b192c087d0acac3b7f7a3a0fa3 status: completed type: web_search_call - content: - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster + - end_index: 262 + start_index: 134 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 528 + start_index: 400 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 808 + start_index: 680 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 1073 + start_index: 945 + title: Nutrition Information for Raw Vegetables | FDA type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + text: '- **Potato:** One medium raw potato (148 g) has about 110 calories, + 26 g carbohydrates, 2 g fiber, 3 g protein, and 620 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Tomato:** One medium raw tomato (148 g) provides about 25 calories, + 5 g carbohydrates, 1 g fiber, 1 g protein, and 340 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Cucumber:** One-third of a medium raw cucumber (99 g) contains about + 10 calories, 2 g carbohydrates, 1 g fiber, 1 g protein, and 140 mg potassium. + ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Carrot:** One raw 7-inch carrot (78 g) supplies about 30 calories, + 7 g carbohydrates, 2 g fiber, 1 g protein, and 250 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo))' type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + id: msg_028582086f036f90006adb38b32cd487d0b059e5f3d6a1898e phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da3598c8199a352ec754ed716e8 + id: msg_028582086f036f90006adb38b6b01c87d0b98150cd87c47ffd role: user status: completed type: message - first_id: msg_05b26e957aa7a7d7006adb2da359588199a8eac6505d8c2b31 - has_more: false - last_id: msg_05b26e957aa7a7d7006adb2da3598c8199a352ec754ed716e8 - object: list - headers: - content-type: application/json - status_code: 200 -- filename: t7 - request: - body: - input: - - content: Use web search to search for the exact query "potato", then summarize - the result in one sentence. - role: user - content: [] - encrypted_content: gAAAAABqbJqT3jzQZaHOg1Wsz1ch-PsZdMKOBETTJuBoEWxvKF4Y1V6NzmWHE7zovHk_Xdevg5u1JDQszrvMKmvJMnlTREBqxR4bt9roowxwJj9M3TM1SJ__-ZzpzHtbk-d6_io_xscOxBtrzx8tvVxyCMBU-VvdBJC5CG4IWoEPdIUDAoyczGfRTSlZKDpVZLKJRxI_aw0G1UxBMl_W8UvGtCjfzs9XxiNp2CWA65YPZX2rGTmRN9_8AIJyEXMkm3d1Pvp45gYGZLYC-tRSrAiM9rD5F2Xej6LG_2rkcLRFLgWZBz5typRRWKAw9Yi0D3iuAn-Z4jx3EkK-gJWKKWy6_EWJw1pJbOsSLTu2wgItX5RGASQOpfgOtTpOcfoC1ci6yzM0DQ6ltEcoO8Uy66J3YXT83xTOMPxsvV7fmhGItVyr8SLqn9j8WzjQ7V1XHDNU3tOVMkqrEuyhiDbVIHri9bk--aaGE2E7evhMHfmZilx3GQ-2vX1zlkGCL4ooT7Udefqx8Ot7BmEC8uInPGo0AbmwXdKoucX_5sqEH6foLPSmr1yj6dDxUDzxhlsPkp_EohI1EBWv44YNUxv0Tz0NFgZp9BDQvLg6RBPOl1cjJNR7tw8VyBoHCBQ7bRbn8DPhgjnxWzsUmx0cdDkXmtI8umiaH7CcE688ns9MJBnRzEMeWSsoYszLGb1BTdT5m24eDD5wmNf27NFE_srdQUtQWQk7ZTrLUQAG9WnsNBTf29hGv478sT8fcW5DM9n1WQuf84quOc8HqOe6s2Mux3QVfJwHKFP6WEzqOFPKwTsX-5j_M-G4x0RWB4U_jjh2Smd-A6JoQf7pp1cTF-2I0pVSNl_WginPo1iSNE8NVQ2PAkl4rbiO-ee-jCW0BL46WNmZyNRDd9GxnVitwH2I1eQ3BldI5adaNHTIIxS5fH_KjRSp80T1wzJNSDARtrJ7wcAe9OpRwXbb0FFBYVeUJN0UeivYX5L6pcHE7nj0OPTGkRrWYxqiccSMETmiiLT0RSfPf0lkNZGg26cn724_W4ZtRBCEl8EpDg== - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + id: rs_028582086f036f90006adb38b9043c87d0a251db279fc9b317 summary: [] type: reasoning - - action: - queries: - - potato - query: potato - type: search - type: web_search_call + - approval_request_id: null + arguments: '{"query":"encoding"}' + error: null + id: mcp_028582086f036f90006adb38b95aa487d09c3039d3a5c93662 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query":"tokenizer"}' + error: null + id: mcp_028582086f036f90006adb38bddf1887d09e788e30fc1cdc50 + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call - content: - - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster - type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + - annotations: [] logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + text: '- **“encoding”:** No direct match was found, but the fallback documentation + explains how to retrieve, use, customize, and register tiktoken `Encoding` + objects. + + - **“tokenizer”:** No direct match was found, but the fallback describes + tiktoken as a fast, reversible BPE tokenizer for OpenAI models, with model-specific + selection via `encoding_for_model()`.' type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + id: msg_028582086f036f90006adb38c1ba9887d082bf4912bcf37cc9 phase: final_answer role: assistant status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one to finish before starting the other: (1) search the web + for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' + type: input_text + id: msg_028582086f036f90006adb38c3bfd487d09a64dac3753227b8 + role: user + status: completed + type: message + first_id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb + has_more: false + last_id: msg_028582086f036f90006adb38c3bfd487d09a64dac3753227b8 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t7 + request: + body: + input: + - content: Do not call tools again. Reply only OK. role: user + type: message + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_028582086f036f90006adb38c3b06487d0963b8de31aa5af01 store: true stream: false + tool_choice: none + tools: + - type: web_search_preview + - allowed_tools: + - search_tiktoken_documentation + require_approval: never + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp headers: accept: '*/*' authorization: Bearer *** @@ -639,14 +1272,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748966 - created_at: 1792748965 + completed_at: 1792751825 + created_at: 1792751824 error: null frequency_penalty: 0.0 - id: resp_05b26e957aa7a7d7006adb2da4f3e08199b8b47b97e8b424f2 + id: resp_028582086f036f90006adb38cff3c487d0919c6ee4e9c0fb06 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -658,14 +1291,14 @@ turns: logprobs: [] text: OK type: output_text - id: msg_05b26e957aa7a7d7006adb2da69ec48199b82ee584a0e15613 + id: msg_028582086f036f90006adb38d0d3a087d0b8ef41aedd71f9b4 phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_028582086f036f90006adb38c3b06487d0963b8de31aa5af01 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -682,7 +1315,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -696,19 +1329,37 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - allowed_tools: + - search_tiktoken_documentation + headers: null + require_approval: never + server_description: null + server_label: gitmcp_tiktoken + server_url: https://gitmcp.io/openai/tiktoken + type: mcp top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 171 + input_tokens: 26091 input_tokens_details: cache_write_tokens: 0 - cached_tokens: 0 + cached_tokens: 24064 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 176 + total_tokens: 26096 user: null headers: content-type: application/json @@ -721,7 +1372,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_05b26e957aa7a7d7006adb2da4f3e08199b8b47b97e8b424f2/input_items + path: /v1/responses/resp_028582086f036f90006adb38cff3c487d0919c6ee4e9c0fb06/input_items query_params: limit: '100' order: asc @@ -729,97 +1380,499 @@ turns: body: data: - content: - - text: Use web search to search for the exact query "potato", then summarize - the result in one sentence. + - text: 'Do two separate web_search calls in parallel, in this single turn + -- do not wait for one to finish before starting the other. The first + web_search call must batch two exact queries together: "potato nutrition + facts" and "tomato nutrition facts". The second web_search call must batch + two different exact queries together: "cucumber nutrition facts" and "carrot + nutrition facts". Issue both calls now, then summarize each of the four + results in one sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da514908199bb562e550dee91d4 + id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb role: user status: completed type: message + - id: mcpl_028582086f036f90006adb38a6333c87d0a7527c0158e5d25b + server_label: gitmcp_tiktoken + tools: + - annotations: + read_only: false + description: 'Semantically search within the fetched documentation from + GitHub repository: openai/tiktoken. Useful for specific queries.' + input_schema: + $schema: http://json-schema.org/draft-07/schema# + additionalProperties: false + properties: + query: + description: The search query to find relevant documentation + type: string + required: + - query + type: object + name: search_tiktoken_documentation + type: mcp_list_tools - content: [] - id: rs_05b26e957aa7a7d7006a6c9a8d1ad4819995cfa8e832d7f52b + id: rs_028582086f036f90006adb38a8933c87d09fb12987127552e3 summary: [] type: reasoning - action: queries: - - potato - query: potato + - potato nutrition facts + - tomato nutrition facts + query: potato nutrition facts + type: search + id: ws_028582086f036f90006adb38a91ca087d0bdb5f35f1d6bf3e9 + status: completed + type: web_search_call + - action: + queries: + - cucumber nutrition facts + - carrot nutrition facts + query: cucumber nutrition facts type: search - id: ws_05b26e957aa7a7d7006adb2da514c88199933a174038b40e25 + id: ws_028582086f036f90006adb38ac260887d09b71c0ad11d3f615 + status: completed + type: web_search_call + - content: [] + id: rs_028582086f036f90006adb38b00b5087d0b3420318146f3aa4 + summary: [] + type: reasoning + - action: + type: open_page + url: https://www.fda.gov/media/76886/download + id: ws_028582086f036f90006adb38b192c087d0acac3b7f7a3a0fa3 status: completed type: web_search_call - content: - annotations: - - end_index: 265 - start_index: 173 - title: POTATO Definition & Meaning - Merriam-Webster + - end_index: 262 + start_index: 134 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 528 + start_index: 400 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 808 + start_index: 680 + title: Nutrition Information for Raw Vegetables | FDA type: url_citation - url: https://www.merriam-webster.com/dictionary/potato?utm_source=openai + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo + - end_index: 1073 + start_index: 945 + title: Nutrition Information for Raw Vegetables | FDA + type: url_citation + url: https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo logprobs: [] - text: Search results for **“potato”** primarily define it as an edible, - starchy underground tuber, alongside cooking guides, recipes, agricultural - resources, and related content. ([merriam-webster.com](https://www.merriam-webster.com/dictionary/potato?utm_source=openai)) + text: '- **Potato:** One medium raw potato (148 g) has about 110 calories, + 26 g carbohydrates, 2 g fiber, 3 g protein, and 620 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Tomato:** One medium raw tomato (148 g) provides about 25 calories, + 5 g carbohydrates, 1 g fiber, 1 g protein, and 340 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Cucumber:** One-third of a medium raw cucumber (99 g) contains about + 10 calories, 2 g carbohydrates, 1 g fiber, 1 g protein, and 140 mg potassium. + ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo)) + + - **Carrot:** One raw 7-inch carrot (78 g) supplies about 30 calories, + 7 g carbohydrates, 2 g fiber, 1 g protein, and 250 mg potassium. ([fda.gov](https://www.fda.gov/food/nutrition-food-labeling-and-critical-foods/nutrition-information-raw-vegetables?source=foo))' type: output_text - id: msg_05b26e957aa7a7d7006a6c9a92bcc081998faad7bf98c9b051 + id: msg_028582086f036f90006adb38b32cd487d0b059e5f3d6a1898e phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Call gitmcp_tiktoken__search_tiktoken_documentation in parallel for + two separate exact queries -- do not wait for one to finish before starting + the other: (1) {"query":"encoding"} and (2) {"query":"tokenizer"}. Issue + both calls now in this single turn, then summarize each result in one + sentence.' type: input_text - id: msg_05b26e957aa7a7d7006adb2da514ec8199acfefda77727580e + id: msg_028582086f036f90006adb38b6b01c87d0b98150cd87c47ffd role: user status: completed type: message - first_id: msg_05b26e957aa7a7d7006adb2da514908199bb562e550dee91d4 - has_more: false - last_id: msg_05b26e957aa7a7d7006adb2da514ec8199acfefda77727580e + - content: [] + id: rs_028582086f036f90006adb38b9043c87d0a251db279fc9b317 + summary: [] + type: reasoning + - approval_request_id: null + arguments: '{"query":"encoding"}' + error: null + id: mcp_028582086f036f90006adb38b95aa487d09c3039d3a5c93662 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - approval_request_id: null + arguments: '{"query":"tokenizer"}' + error: null + id: mcp_028582086f036f90006adb38bddf1887d09e788e30fc1cdc50 + name: search_tiktoken_documentation + output: "### Search Results for: \"tokenizer\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken + status: completed + type: mcp_call + - content: + - annotations: [] + logprobs: [] + text: '- **“encoding”:** No direct match was found, but the fallback documentation + explains how to retrieve, use, customize, and register tiktoken `Encoding` + objects. + + - **“tokenizer”:** No direct match was found, but the fallback describes + tiktoken as a fast, reversible BPE tokenizer for OpenAI models, with model-specific + selection via `encoding_for_model()`.' + type: output_text + id: msg_028582086f036f90006adb38c1ba9887d082bf4912bcf37cc9 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one to finish before starting the other: (1) search the web + for the exact query "latest vLLM release notes", and (2) call gitmcp_tiktoken__search_tiktoken_documentation + with {"query":"encoding"}. Do not call any other tool.' + type: input_text + id: msg_028582086f036f90006adb38c3bfd487d09a64dac3753227b8 + role: user + status: completed + type: message + - content: [] + id: rs_028582086f036f90006adb38c48bd087d09cf2d6b9d00c3424 + summary: [] + type: reasoning + - action: + queries: + - latest vLLM release notes + query: latest vLLM release notes + type: search + id: ws_028582086f036f90006adb38c4ffcc87d08f2761f66559b187 + status: completed + type: web_search_call + first_id: msg_028582086f036f90006adb38a62ee087d0b021d27a63342ecb + has_more: true + last_id: ws_028582086f036f90006adb38c4ffcc87d08f2761f66559b187 object: list headers: content-type: application/json status_code: 200 - filename: t9 request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_028582086f036f90006adb38cff3c487d0919c6ee4e9c0fb06/input_items + query_params: + after: ws_028582086f036f90006adb38c4ffcc87d08f2761f66559b187 + limit: '100' + order: asc + response: body: - input: - - content: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. - role: user - - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools + data: - approval_request_id: null - arguments: '{}' + arguments: '{"query":"encoding"}' error: null - id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a - name: say_hello - output: hello - server_label: counter + id: mcp_028582086f036f90006adb38c9505c87d09f63f61480673f82 + name: search_tiktoken_documentation + output: "### Search Results for: \"encoding\"\n\nNo relevant documentation\ + \ found for your query. It's either being indexed or the search query did\ + \ not match any documentation.\n\nAs a fallback, this is the documentation\ + \ for openai/tiktoken:\n\n# ⏳ tiktoken\n\ntiktoken is a fast [BPE](https://en.wikipedia.org/wiki/Byte_pair_encoding)\ + \ tokeniser for use with\nOpenAI's models.\n\n```python\nimport tiktoken\n\ + enc = tiktoken.get_encoding(\"o200k_base\")\nassert enc.decode(enc.encode(\"\ + hello world\")) == \"hello world\"\n\n# To get the tokeniser corresponding\ + \ to a specific model in the OpenAI API:\nenc = tiktoken.encoding_for_model(\"\ + gpt-4o\")\n```\n\nThe open source version of `tiktoken` can be installed\ + \ from [PyPI](https://pypi.org/project/tiktoken):\n```\npip install tiktoken\n\ + ```\n\nThe tokeniser API is documented in `tiktoken/core.py`.\n\nExample\ + \ code using `tiktoken` can be found in the\n[OpenAI Cookbook](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb).\n\ + \n\n## Performance\n\n`tiktoken` is between 3-6x faster than a comparable\ + \ open source tokeniser:\n\n![image](https://raw.githubusercontent.com/openai/tiktoken/main/perf.svg)\n\ + \nPerformance measured on 1GB of text using the GPT-2 tokeniser, using `GPT2TokenizerFast`\ + \ from\n`tokenizers==0.13.2`, `transformers==4.24.0` and `tiktoken==0.2.0`.\n\ + \n\n## Getting help\n\nPlease post questions in the [issue tracker](https://github.com/openai/tiktoken/issues).\n\ + \nIf you work at OpenAI, make sure to check the internal documentation or\ + \ feel free to contact\n@shantanu.\n\n## What is BPE anyway?\n\nLanguage\ + \ models don't see text like you and I, instead they see a sequence of numbers\ + \ (known as tokens).\nByte pair encoding (BPE) is a way of converting text\ + \ into tokens. It has a couple desirable\nproperties:\n1) It's reversible\ + \ and lossless, so you can convert tokens back into the original text\n\ + 2) It works on arbitrary text, even text that is not in the tokeniser's\ + \ training data\n3) It compresses the text: the token sequence is shorter\ + \ than the bytes corresponding to the\n original text. On average, in\ + \ practice, each token corresponds to about 4 bytes.\n4) It attempts to\ + \ let the model see common subwords. For instance, \"ing\" is a common subword\ + \ in\n English, so BPE encodings will often split \"encoding\" into tokens\ + \ like \"encod\" and \"ing\"\n (instead of e.g. \"enc\" and \"oding\"\ + ). Because the model will then see the \"ing\" token again and\n again\ + \ in different contexts, it helps models generalise and better understand\ + \ grammar.\n\n`tiktoken` contains an educational submodule that is friendlier\ + \ if you want to learn more about\nthe details of BPE, including code that\ + \ helps visualise the BPE procedure:\n```python\nfrom tiktoken._educational\ + \ import *\n\n# Train a BPE tokeniser on a small amount of text\nenc = train_simple_encoding()\n\ + \n# Visualise how the GPT-4 encoder encodes text\nenc = SimpleBytePairEncoding.from_tiktoken(\"\ + cl100k_base\")\nenc.encode(\"hello world aaaaaaaaaaaa\")\n```\n\n\n## Extending\ + \ tiktoken\n\nYou may wish to extend `tiktoken` to support new encodings.\ + \ There are two ways to do this.\n\n\n**Create your `Encoding` object exactly\ + \ the way you want and simply pass it around.**\n\n```python\ncl100k_base\ + \ = tiktoken.get_encoding(\"cl100k_base\")\n\n# In production, load the\ + \ arguments directly instead of accessing private attributes\n# See openai_public.py\ + \ for examples of arguments for specific encodings\nenc = tiktoken.Encoding(\n\ + \ # If you're changing the set of special tokens, make sure to use a\ + \ different name\n # It should be clear from the name what behaviour\ + \ to expect.\n name=\"cl100k_im\",\n pat_str=cl100k_base._pat_str,\n\ + \ mergeable_ranks=cl100k_base._mergeable_ranks,\n special_tokens={\n\ + \ **cl100k_base._special_tokens,\n \"<|im_start|>\": 100264,\n\ + \ \"<|im_end|>\": 100265,\n }\n)\n```\n\n**Use the `tiktoken_ext`\ + \ plugin mechanism to register your `Encoding` objects with `tiktoken`.**\n\ + \nThis is only useful if you need `tiktoken.get_encoding` to find your encoding,\ + \ otherwise prefer\noption 1.\n\nTo do this, you'll need to create a namespace\ + \ package under `tiktoken_ext`.\n\nLayout your project like this, making\ + \ sure to omit the `tiktoken_ext/__init__.py` file:\n```\nmy_tiktoken_extension\n\ + ├── tiktoken_ext\n│   └── my_encodings.py\n└── setup.py\n```\n\n`my_encodings.py`\ + \ should be a module that contains a variable named `ENCODING_CONSTRUCTORS`.\n\ + This is a dictionary from an encoding name to a function that takes no arguments\ + \ and returns\narguments that can be passed to `tiktoken.Encoding` to construct\ + \ that encoding. For an example, see\n`tiktoken_ext/openai_public.py`. For\ + \ precise details, see `tiktoken/registry.py`.\n\nYour `setup.py` should\ + \ look something like this:\n```python\nfrom setuptools import setup, find_namespace_packages\n\ + \nsetup(\n name=\"my_tiktoken_extension\",\n packages=find_namespace_packages(include=['tiktoken_ext*']),\n\ + \ install_requires=[\"tiktoken\"],\n ...\n)\n```\n\nThen simply `pip\ + \ install ./my_tiktoken_extension` and you should be able to use your\n\ + custom encodings! Make sure **not** to use an editable install.\n\n\n\n\ + If you'd like to retry the search, try changing the query to increase the\ + \ likelihood of a match." + server_label: gitmcp_tiktoken status: completed type: mcp_call + - content: [] + id: rs_028582086f036f90006adb38caf05087d0b82880a44827ea47 + summary: [] + type: reasoning - content: - - annotations: [] + - annotations: + - end_index: 261 + start_index: 182 + title: Releases · vllm-project/vllm · GitHub + type: url_citation + url: https://github.com/vllm-project/vllm/releases?utm_source=openai logprobs: [] - text: MCP_SAYS=hello + text: '- **Web search:** Results surfaced vLLM’s official GitHub releases + page, while a secondary release tracker identifies **vLLM 0.32.0**, dated + October 18, 2026, as the latest release. ([github.com](https://github.com/vllm-project/vllm/releases?utm_source=openai)) + + - **tiktoken documentation:** No direct match for “encoding” was found, + but fallback documentation covered retrieving standard encodings, selecting + model-specific encodings, and creating or registering custom `Encoding` + objects.' type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 + id: msg_028582086f036f90006adb38cca54487d09c437c871528144a + phase: final_answer role: assistant status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_028582086f036f90006adb38d00b1c87d08abeadd6f06bfe82 role: user + status: completed + type: message + first_id: mcp_028582086f036f90006adb38c9505c87d09f63f61480673f82 + has_more: false + last_id: msg_028582086f036f90006adb38d00b1c87d08abeadd6f06bfe82 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t10 + request: + body: + input: 'Do all three of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit to + set my preferred unit to "fahrenheit" for the rest of this conversation.' + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true store: true stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -834,30 +1887,33 @@ turns: background: false billing: payer: developer - completed_at: 1792748968 - created_at: 1792748967 + completed_at: 1792751829 + created_at: 1792751827 error: null frequency_penalty: 0.0 - id: resp_0e3fe24990ca0d3e006adb2da7df58819b990cf42bfab87be1 + id: resp_04129cd8130df26a006adb38d3b9c087d08af4c0a6518b95ce incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] - logprobs: [] - text: OK - type: output_text - id: msg_0e3fe24990ca0d3e006adb2da8c144819b8360636febb90b2b - phase: final_answer - role: assistant + - content: [] + encrypted_content: gAAAAABq2zjVKHZ_Yvy6nOqXgvtgg10Y_zM3rTM8pdRRoeymLBSqOYPAKw3EWHJIGTpG5pffgUuT6Jtzgnk1dUurqouCyO5DDFBMLtwcJT66M0qqxj9D593KmxDE2oLmBUx3IFa-sWhz0E_iqQLZ7rQZUd41GqefsRjdu9C0WPlOAYt0JVSXhdR79n3vbAZEdaHeR863dKa8wcSp7FBpXs-U3KHpUHe4k8yPSzQ3fCw-IQpCwpGzgDNBUIO9rwOFQKyExziLb5_uhUI9E4ef9YCKgBGQs3lMAvcFhVVxjBaCC7a7lBftHnzbV7cg8uBMyoCZe5rmFxsBR_kalJAV43EB-aaERJkWG017jA_A4sGFzkWvqVGRXwsb2qQeru50yV7IgqJrF0gZLItwzXu6NAHq7Kw8c9ppZDYRv_cv8RtU9t7wmTepDrKHuanaALLWZ0NyUb3WekgLsXGj9kzfWxDRI5o-hH354AV8YMk64s6xXK65r0GS8kDpU9ecTqa4QpACuw_l64nvXXfRMWSaE5Nizrcr_wmS681ll4PzJjGFQ7Wf3wzoVQDDfIu-TbQsAiyBjiF_Ikj5ycBedqjXdorw4nI-Z8C9nOpPm1Sups0dNQ8WMCFTAymoBiEG3PT11qAwOfuGTNibPuzbepqZOv4dth02UA_KoKEnQ8foa6sErbFmvBOgAqcCq3FmnmqJEk33fssBVjGw8JK9C48xfvZucs9OtpSUi-eQW8Zc-soinDR8AfgvWz3Cf-cvJxjtTSfAeQsq0E-eCHQ6eCBgKcPx0q3CJAeIDK3LvV-pBSKEwobZyl6T1TQZCgXioJ0aclu2DkYFxgaK4zuUuqunE0Y1T6QE635SbJugQ6wg12jhnk8KCHKkgZMGXz_B1B-ei561IqaY8_5-pyzG9sQIn-v3BKzMYhU5BXr1f69I3I1hhx7AwYaZOgZFgi6y2nEFNjvAXWqH8kShSrU2TKuKG2pw-4dd8493Ae6zXzIIdMjpljtK1zSyMR_zgJwAJKh_scuqSUIzHqb_YmbD32oqCs7IhDarVP4qSQ_t5JpRYC_3M7f2PTZTRtT8odJyLiCnVf3TGyp2sG6VWMsg1R9wsNymM8ysiizOlw54YQ3gnkUDqBzwioDYIqEROhP8nE-V9_3nRTPRhKbRkcU6YXqjc9DKQF2o6_GFrqRNy4e2FKalSCaljUkWdsqJ5weY6P9aP4VVzLAS8bwdIjfec9LHZivHfpJPRo0Tg2rqh00lcn8WG1UCZkCMShG6Yi9bu7tCcYt_x7EV1anADrFr0XxN9OfIE8RWsLv2STj9YMyr0NB8Pbpq7L7Pgg9O7UnR0-f9WfeXuXkGXglWLljTsrxIgbbbcaF1FeNNfAdLibCRv7t7aEdtm30ZLC59Cz8v6djQUNRybl0fSXcHcpk85Y-RTdj6rlz8lUJil4s6v5o9fSd5yy3xdj62VXb9kf5t4OKpQMiH_MvRDaILV3NKnG_LCXtVP-8I2zyvuUDA3ilLdkaFJCfcunL68iTAnVVGOAeakW6UDqbmKWO2n0oESe33x1VVIsMabGlhCSwG1caS57ApPDDfjdUJUpo0xzwiBteXH0A8feBm_aL7IUwaTMDz6diiJrPdsRdp8P7agmkxKZMvqCEAzjag-KonDI6kgZOUR7CjfE7vjwJS8RTPolyoYEVXBn4fftnVzD79L1J5QQHrB0cePTSldFT-TDki0vGfgmece89iddGRCL6gRP8xXx_iv6eo7ID6OzgaaiM9ey4sfFQQHi_asYQLyrVJhFhO32NS_ZnIImmflHfmSP2OpTwMTdmXyy1HDoQ9lp66mdbqr5mBQaBimzy_bgHj_hgREJPL_5aHWC4FC0_Lh8ufJOchAsLGh6lrSYwejWTJtMnT8P4UFd01IsYcZ8LbKYIcnd7g_sXjOwqDQ6U7-2ElTXzMKhJU1GVDk-Q9kPfHTOAJ4yqQTljox3xnQhjxGVwU3XX1u2g4MrkZk0zm15ztBBBT0OYfDL6U3aZbX7f83_FDXEEM_NNCFXbel2gqth2-J26ejwqdDA-9MFybsXPpSFbSPxwheoE-m64SGi5kEJeE9DKXZTXv6D8qN3sAN7-OQQxoG4J-PLuf9APsopHi6uZ8KhoeQ_7ieK0Q8gBc0zSbd3EDt6oaeg3Cp7lAsz5KZodWk6atpopZBRR9gb4BHpZuWd5RY2-omDpET4yANdW-EOqtyQdygxGscoRY9GDVjR1fqAktsMWKU1JnmJjmz084LDJLBzb1dnT9ui-_eF64hgqzT4RhUpWMG6qJ5k42cd2M3nRNYI99v2-LkP9MlWV9uGqdCSxN3QQpLECq5_6IdalQp-qK5rFkyn82mNnxGE02aQ_0OoYxQ6ZDA8qt-DsjgPmVBoz4sa09JZFUEqQreC_7su26Xo5tTQXC_OBUlUM-dPWWJfTYiboNlXCRuHz8spCD13DoiO7Z0AYZjzp7qlo-EPEQyg1l65Ga5eaLGUTc7J04GBKWlWG8zhRb3Eow8UTCf9WbHLpPoqcP5TMi0Kqahwk59BEYwm8T0NfEbrhMryXzaayL5EocfuirT5cnLLL9GMq26DI2Vzc-dyVfrEEF_TrIqKc= + id: rs_04129cd8130df26a006adb38d4324c87d09716372ce68d8c5c + summary: [] + type: reasoning + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctc_04129cd8130df26a006adb38d5638c87d09c6ce0569b880ef2 + input: 'fahrenheit + + ' + name: set_temperature_unit status: completed - type: message + type: custom_tool_call parallel_tool_calls: true presence_penalty: 0.0 previous_response_id: null @@ -891,24 +1947,59 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + output_schema: null + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 83 + input_tokens: 228 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 111 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 88 + reasoning_tokens: 94 + total_tokens: 339 user: null headers: content-type: application/json status_code: 200 -- filename: t10 +- filename: t11 request: body: {} headers: @@ -916,7 +2007,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0e3fe24990ca0d3e006adb2da7df58819b990cf42bfab87be1/input_items + path: /v1/responses/resp_04129cd8130df26a006adb38d3b9c087d08af4c0a6518b95ce/input_items query_params: limit: '100' order: asc @@ -924,96 +2015,72 @@ turns: body: data: - content: - - text: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. - type: input_text - id: msg_0e3fe24990ca0d3e006adb2da7f954819ba2743cd71418c47d - role: user - status: completed - type: message - - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a - name: say_hello - output: hello - server_label: counter - status: completed - type: mcp_call - - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' type: input_text - id: msg_0e3fe24990ca0d3e006adb2da7f9a0819b9be58b3eacf1bc65 + id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 role: user status: completed type: message - first_id: msg_0e3fe24990ca0d3e006adb2da7f954819ba2743cd71418c47d + first_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 has_more: false - last_id: msg_0e3fe24990ca0d3e006adb2da7f9a0819b9be58b3eacf1bc65 + last_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 object: list headers: content-type: application/json status_code: 200 -- filename: t11 +- filename: t12 request: body: input: - - content: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + output: '{"status": "ok", "unit": "fahrenheit"}' + type: custom_tool_call_output + - content: What did you find for the weather and the stock price, and what temperature + unit did you just set? role: user - - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - name: say_hello - output: hello - server_label: counter - status: completed - type: mcp_call - - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_04129cd8130df26a006adb38d3b9c087d08af4c0a6518b95ce store: true stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -1028,33 +2095,40 @@ turns: background: false billing: payer: developer - completed_at: 1792748970 - created_at: 1792748970 + completed_at: 1792751844 + created_at: 1792751842 error: null frequency_penalty: 0.0 - id: resp_0e3fe24990ca0d3e006adb2da9de74819b9ef63ece99c3d3db + id: resp_04129cd8130df26a006adb38e2687487d0aa7eb850fd3dc49b incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] - logprobs: [] - text: OK - type: output_text - id: msg_0e3fe24990ca0d3e006adb2daa9ce4819b88d72e9aaab847b3 - phase: final_answer - role: assistant - status: completed - type: message + - content: [] + encrypted_content: gAAAAABq2zjk-Q88j6343lMXRDqYMj6SVAjaqaSqCE0krxWASwZsueveToMDqb0Ih01Uyt4A7XwVoKv1RGm2wCMzQBXGtj-WsQ-XUq7J7GavcDrow9WyMiKkmNlixPXSr166J1jOa1862dmF1BUnm5IzYCSYEJrGnczYrlCJmL7TK3kGpOnTjuR_jLBKeM1SmVzG2OaT_qD8FWHQlK0CXhzXlwAuBC-7xoiFTy0yW4vR474IoP2coIcAxNIP0OuXcuJViUkovefBIZAQEN2DdNbQGqCiyqLCjNO3dIPf7NjAuXadZoN21C-QaNrMrFq73_lTdkIiFwDmIF8fzmAf3cqKNF61Lrx9FD1DDp2SfktAnf6yNMWdQOGoZF7rJ5L3p3O3QOLXOTXC_7M2sPji1h10z6Pfdt4O7zjLKxbfO8-aUU-FD3vSbpuLZ4u2boKiO3DqNFpq-mdzgolkpOw-dFe2tfUfI3Kf8ed_XmBncuOnOs-ek9ZEgeypztXQw_CM4naNygljH58TGChRkWYuoHTjPGLpFijahmVa4zlABlpQtXTw7007s62hhUIX6dATNYWx03fCm_sSDF2Vs4XN8Rxxc9XDRiTBkHNZKlcbM-diVHl1Bitmf-HFcleHEonZo9exhwvxdLDSJeUzhHL_6YFVBcUffgM5UoPbNpWsAB8pap9gHaTqq5DW2FBjXzDL7B-31P9n4FDrfhf7I5J9EIoshDedl_Ou6MjzbVb176Y1hp8wnO8nzHImH2fZEblw2DOP_bRXTBpyG0Ln8fu1rlgwl0Fi0Kbjj13Ajdmti72eItgMszPKc0dMoDrq0us5fIJo5caOkO_Sk9R6cNOkJzaLc5YA1-NB9VeQqgh64TQyAumqTV1h-FxpEhTomkmxwTqSsXPaSU0jMLgNi44QMpLtVuS8dsDKdANGxSTSmQHLnWxNna5v2d83QLIi74-FU4dOofCzoJ93IEFuVV5ZQBtUNwyl1r4tiARPC67MEdJjRiJcZuZqOMM5aF8uYix2Nx5UZ1EonT1SVUaENcHSgIJGHPTlZDrMr7UJDw9YCuyH99k1tH8bqb7w_euqAdNeASudD2ul2HCamXvwfg9pC7hFyNIZcrVOCdevaUMBUBHgtBSfmb64ucNSF6K9pFcBxy8GBJcbWw2QaD_6f1Ci3qFBVRp8lc9Rnw_TqMoRf9QZ2dELjNequCFCo0TXLTuAoZmGhqAo4Lff0x-JU6YcgGxrJQi_FmLAYdDeNOVfL-KTa8D5_rDY7zMdQgshiV7PzyoP4wY9vHJj7B9803vC6h1wGCUpptkcRWboRtlLr3QP_7ACxmYJbcWO2a5uOjUxVm12EOfM8jq3K6AEnnvTBMHqCz_FOBsm9Y_PMPsW3vVVrIbFFnrzJ1rgUZmqYNEeRsIbhVjyfCwv3snmthPV-lFL5lIgMWYMfRiEAJzV0qrYFfKiGYTmONSPZgJi6Mmflk7ldGeKna80baImyR7S9yy6xFPCKDbsaf9B1nuwgFh0D7lPXIrNaaRgXOBe3CrfGYrROHMaKcqVwnnVaRZBOfbGmv8oL11WVVhEqz95k2RMWoXuphUzblWPL12oLOM2PRFlmWzAfirK01UOklracwHZVzH1qoksah-8w0da-Y1yLkkMzTpAiVo= + id: rs_04129cd8130df26a006adb38e37a3487d0b740e78a1882f194 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e4077887d091779ed5c296e758 + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker":"AAPL"}' + call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e4079087d0872a5b5e6e151666 + name: get_stock_price + status: completed + type: function_call parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_04129cd8130df26a006adb38d3b9c087d08af4c0a6518b95ce prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -1085,24 +2159,59 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + output_schema: null + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 63 + input_tokens: 389 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 68 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 68 + reasoning_tokens: 16 + total_tokens: 457 user: null headers: content-type: application/json status_code: 200 -- filename: t12 +- filename: t13 request: body: {} headers: @@ -1110,7 +2219,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0e3fe24990ca0d3e006adb2da9de74819b9ef63ece99c3d3db/input_items + path: /v1/responses/resp_04129cd8130df26a006adb38e2687487d0aa7eb850fd3dc49b/input_items query_params: limit: '100' order: asc @@ -1118,97 +2227,101 @@ turns: body: data: - content: - - text: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' type: input_text - id: msg_0e3fe24990ca0d3e006adb2da9fcdc819bbb9f2cce171f5dfd + id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 role: user status: completed type: message - - id: mcpl_0e3fe24990ca0d3e006adb2da9fcfc819b855f39c543a9783d - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - id: mcp_0e3fe24990ca0d3e006adb2da9fd14819bb77d3fa6f284c22d - name: say_hello - output: hello - server_label: counter + - content: [] + id: rs_04129cd8130df26a006adb38d4324c87d09716372ce68d8c5c + summary: [] + type: reasoning + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctc_04129cd8130df26a006adb38d5638c87d09c6ce0569b880ef2 + input: 'fahrenheit + + ' + name: set_temperature_unit status: completed - type: mcp_call - - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - phase: final_answer - role: assistant + type: custom_tool_call + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctco_04129cd8130df26a006adb38e274e087d0b728c283ec955761 + output: '{"status": "ok", "unit": "fahrenheit"}' status: completed - type: message + type: custom_tool_call_output - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? type: input_text - id: msg_0e3fe24990ca0d3e006adb2daa007c819b9c0d4284e94e36bd + id: msg_04129cd8130df26a006adb38e2750c87d0a70dd0177d1f054f role: user status: completed type: message - first_id: msg_0e3fe24990ca0d3e006adb2da9fcdc819bbb9f2cce171f5dfd + first_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 has_more: false - last_id: msg_0e3fe24990ca0d3e006adb2daa007c819b9c0d4284e94e36bd + last_id: msg_04129cd8130df26a006adb38e2750c87d0a70dd0177d1f054f object: list headers: content-type: application/json status_code: 200 -- filename: t13 +- filename: t14 request: body: input: - - content: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + type: function_call_output + - content: 'Do both of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' role: user - - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a - name: say_hello - output: hello - server_label: counter - type: mcp_call - - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_04129cd8130df26a006adb38e2687487d0aa7eb850fd3dc49b store: true stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -1223,33 +2336,40 @@ turns: background: false billing: payer: developer - completed_at: 1792748972 - created_at: 1792748971 + completed_at: 1792751847 + created_at: 1792751846 error: null frequency_penalty: 0.0 - id: resp_0e3fe24990ca0d3e006adb2dabd1c8819bb1f382c542666508 + id: resp_04129cd8130df26a006adb38e6292087d0844805567e083e12 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] - logprobs: [] - text: OK - type: output_text - id: msg_0e3fe24990ca0d3e006adb2dac9304819b8dffeddfac44ab36 - phase: final_answer - role: assistant - status: completed - type: message + - content: [] + encrypted_content: gAAAAABq2zjn8mZlk2dGU78-vHpb1-omcloCjQxppAhbQ9DRdfAJ1tO5ppTJobfvjMYPhyiLRhUVQg7fRptn6aErhABODzsQ_O3kZugsy6HW9GPA8orDzomlb9mjrP74K3_9pE4tJ__Lu-3Ty9VQlBhxEYtKeffpHZM9MzbyphrkCpwiYqmVHs2wsIhjivw9ehN4LpxVfNUK4_DEB3SXvv27_xlD5xPBsPae3_t5cb1-MuYre0nWTd_BVxs41h0tsXoawP1GKRkC0MVl3eeNAoHwbtTf8HD9kersAbUylUQmkxQ3u5xMgLgPIo_LbNa_pd9CyeE5Bz_v9XL0XyGNnyEp4i66GS_bQieurPJ1HL0DDerLQ7yp6O-y3feiFtnTuNyO_Wuv4sB_NzAQvmnxgjX5xz9ABqHlNvTW9wVcH-RL1-EMWLjQuKijUc9ayFIPNTrWAbOBso0ZdAIttrHbIwII2HqTRJHVqLhTosJLUnefHIqluCbuo0SxPaLSJkgftsa0cW84pk8c10p1UwYE2aj8rN_w_XWVWjURstsmPXv1llkdp5BDTo-a3hN3JFLtBn2hcItbNwYX2MI_eOd-Q0zO7uSdXtAOiWJemyROEd6TUI0ejeevlCEyNtXBxCANDWPN9VEuWvmcKHQ2tKMRC7J9XFmCTWd4yimL6sJE50i_jEnZ0ZVMC6se2fED2B2AZqOR03pcASkKbdH1j9NAJ4JBYagbCSIKMF6kLvKGw0NGP6__BReHUh-Gi6-LhgeaQURo2ktJZu-YXrys6O6RxRxd_ry9HOxZ8BG62JLlZzijYiYJwJdT3C7XTtZVLSb8dEpCuwFDELt9BnRl5Pt7NJvStMqm3K_bVKmi3IaA5sM_Dyf72LZaCtOGnGJpbckMSYvEGfrmvNh2AsqUCPuhlPvR4u6oQaJyuMgkIEg2AKqZPVqfeyhzuGDL3JJ65RWRg35wpdDZ6FbjLjGx8Bcj_--VTH0CawRJInaePkuhctlM1s-davqAxR-8Hb3UYIEQN6OnaraTgUBxQJD9RtpUDs1AF4fOxE96O-2hhSa2agxMX_01BouIBtYS9-U-CRNcw9CxyLEwhufTIjPBCnAcZo9GHbFZelTG3k8DjO0c7rJUmOjUGT9thP2-7RNFqlHxpiNZP_5aNOYtJg2k6DoHiJlWuUzkOft49ymAXqV1fsDnOKtQUWybAr6kDPDm2i5AkOT2Q62sFvnlj_2iBWaCuGi9lbF7H1I4bTSemwVueO9xjN12KkOss07CXQs4b46Oq7ClPiPcPH_nN3gwnZ2sxfgQ-2lxudttrd4m0DTUzjFuKYvJ9yEaQzs5W-BAuULPmMjAQ4GrLe5aNMoqr_EjCltiG1RNbcQjkuY6pZ4MMfZPinY_9xRjSqjxJszYEQZKCCr6oFdzWmJQMmeCF-vhMTZfyfEEQIbsOwOhOOURrK5gAcFceVBQwqNEpleLwNE51GwsLdwTGAG-5JSjxwtNXBlCQWDVQRazfDdVPjRQP8vBVuk7XJ8Z-Rv25MznSd3ex3aNAXYeM7pYnqqCTjTbOdT_nViCwPT4Ln0DXWJtNd88MG53B2MjXjw= + id: rs_04129cd8130df26a006adb38e6bf3087d0b211b13918eab15b + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + id: fc_04129cd8130df26a006adb38e73f0c87d0b98cb10fc48fa31a + name: get_weather + status: completed + type: function_call + - arguments: '{"city":"Paris"}' + call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + id: fc_04129cd8130df26a006adb38e73f1c87d0aae66b51389c1a79 + name: get_weather + status: completed + type: function_call parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_04129cd8130df26a006adb38e2687487d0aa7eb850fd3dc49b prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -1280,24 +2400,59 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + output_schema: null + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 83 + input_tokens: 567 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 63 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 88 + reasoning_tokens: 13 + total_tokens: 630 user: null headers: content-type: application/json status_code: 200 -- filename: t14 +- filename: t15 request: body: {} headers: @@ -1305,7 +2460,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0e3fe24990ca0d3e006adb2dabd1c8819bb1f382c542666508/input_items + path: /v1/responses/resp_04129cd8130df26a006adb38e6292087d0844805567e083e12/input_items query_params: limit: '100' order: asc @@ -1313,95 +2468,134 @@ turns: body: data: - content: - - text: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' type: input_text - id: msg_0e3fe24990ca0d3e006adb2dabe320819bb59a1cae672a60eb + id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 role: user status: completed type: message - - id: mcpl_0e3fe24990ca0d3e006a6b125b02b8819b85656b1528326216 - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - id: mcp_0e3fe24990ca0d3e006a6b125cb0e4819bb4e6ce6bf14fdb4a - name: say_hello - output: hello - server_label: counter + - content: [] + id: rs_04129cd8130df26a006adb38d4324c87d09716372ce68d8c5c + summary: [] + type: reasoning + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctc_04129cd8130df26a006adb38d5638c87d09c6ce0569b880ef2 + input: 'fahrenheit + + ' + name: set_temperature_unit status: completed - type: mcp_call + type: custom_tool_call + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctco_04129cd8130df26a006adb38e274e087d0b728c283ec955761 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - phase: final_answer - role: assistant + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_04129cd8130df26a006adb38e2750c87d0a70dd0177d1f054f + role: user status: completed type: message + - content: [] + id: rs_04129cd8130df26a006adb38e37a3487d0b740e78a1882f194 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e4077887d091779ed5c296e758 + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker":"AAPL"}' + call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e4079087d0872a5b5e6e151666 + name: get_stock_price + status: completed + type: function_call + - call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e6362487d0a1e72650acb1c425 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e6364487d0ba35541709380bc4 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' type: input_text - id: msg_0e3fe24990ca0d3e006adb2dabe364819bb1fecc1c05711c88 + id: msg_04129cd8130df26a006adb38e6365c87d0b23e95f98b2b71c1 role: user status: completed type: message - first_id: msg_0e3fe24990ca0d3e006adb2dabe320819bb59a1cae672a60eb + first_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 has_more: false - last_id: msg_0e3fe24990ca0d3e006adb2dabe364819bb1fecc1c05711c88 + last_id: msg_04129cd8130df26a006adb38e6365c87d0b23e95f98b2b71c1 object: list headers: content-type: application/json status_code: 200 -- filename: t15 +- filename: t16 request: body: input: - - content: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - content: What's the weather in each of those two cities? role: user - - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - name: say_hello - output: hello - server_label: counter - type: mcp_call - - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_04129cd8130df26a006adb38e6292087d0844805567e083e12 store: true stream: false + tool_choice: auto + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -1416,33 +2610,40 @@ turns: background: false billing: payer: developer - completed_at: 1792748974 - created_at: 1792748973 + completed_at: 1792751852 + created_at: 1792751851 error: null frequency_penalty: 0.0 - id: resp_0e3fe24990ca0d3e006adb2dadbfa4819b858da14bdd89b375 + id: resp_04129cd8130df26a006adb38eb6fac87d0bfafa9e5182f1a4f incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: + - content: [] + encrypted_content: gAAAAABq2zjtsLDuG3Ef6PrszFTKmiaY_YBB4v2lwyXMGKEG8k8m79UhSAFJoulroV6ZppxMSqt-D7AJmIvD06N9F_Jm6zqKEsrPAMONMIfaX2sWHqSRFdtQJXHQuhygh280NZ9vqjH00_F4GaZ48A8VCZqqYOC7Wte29t89K-Q2xQb-wqgshAhTl6datyggOT8loYoJjgatggBxN7DZpGRrquriFDwpE1HM3har-DvHWKPrHAi53RSEQCl-u6Ef7T9fpeWglvTigHPbR9U3iEelK50NQ0B9_Yas4LMhuum1QkparrkgRh1ydV_1HumXwWkLjjt0LPtPYJNrc5jmi-HlSgGZGzXGFIOQNt_XbH7yw5bdyTlw6EDlwgGy7X8PPTevYoHM4v3OCCiSLCkVd67u-kbvbzbllxm5j7EwywUs6uaJhGTi0NKulcRUQR8HTyIBB3AWh0O0ATzggvkVZYtn9qR692F1raNcg9OyLSrkvMct5HA3icISBRtJkEyhp2escQ9hCBWOFtQzQ-WdPkJdxU-FdxNLx0rivpltYGN9lXhybumTEvPR1XsMpl24vxYZ1GqgFbIIDQL1Poo3KZe9oxRA3cVCGQvY6ARbDFgjSCcK2X_k_dunPgvgMNC0n5I-WVDcTLnMY8ToMYcMHzYp6u9BL768ukEbkSX2jpfj0gH69bC4reM_KCnxx6NYe_ZrI5w8DWuw3Ybb6Uhw3z96fQCC_kElUzZv7hR8nCzAukekflEYfoLlFmq61XbGdEuLMETTdrYH0J01GGWqr1ND1ITS0ZIJVo7NuHK4NuVUIvprxh7MCS_e5nC84tgjkElw3_nkdrrL4Wh03BxIOr1c8BZHRwil2QBce0SWikcwjFSuBgrjV7Lzv4tzIYeg5bDPoex3kY9bh4mYW3QUGyute68sr9rGb9Eb7UA8Vy3uFNWcmzSlGTP-MX_ppJV6iKSdIF4gmkAUDBk_Ole73QbQpX5gewmMmY1vHCk8TcxHxEhya8rfxinxFsrBpwfXR4orrn-ARuoWZ6Bfaruuqedrypl8N9ba5raj4wsEAG3X5S78DbXWm8E2xkOIY6w_GcoSy2myCZu6MXRda2QA0YcuTBHsxbJHxrFpKzUOJ03bK1iR-OAGVSorxjQavH6qtIfn1DYbV6JEy6_4fa83Jh0I6zjSZd5PK0VaYU9YPifI-bFhhlD_lj8ArrnTJRVcF1g8-My6NYEe7wQQfJl0ggwLDQsFKd8H6Ejnihyy1jltk0v1CxC_8DpHKRwMaBpL6KvU84A0ps1Fdw9SYaVpg5-ybvw4fgKgCNiu5KUTJtDiQBIzItC-o2Xg4MvVYqWYWUwTbyyeP8TG-x0-lm6ehjLogYRpIWA5UsMUk18eGYP6U1joqdkqEnp909sFlteYI46_3hVUD_WEKY0bOjq9sjUyEebHaPPxmxAS11VFasHnhVnz-dgm3VsidhoFyrzIRUZSYXG91_rrrDbmcpf477_Er3H9t_qEUs9ZQ0dLYyFPUW5KwgWiJbuJX57YwmYAxDn--DZ19aKK6wNnxzY2syvIHisc8y-BhQBttB6VNqOGhsjiwGlMTej0A4veISKGwnwooxB21mJ-jQaxKAiAj9dBDttzniAEDLnszKHKBauNKA7ouwnlb_Ayv7P2-h_Pa3a3ZwbTtcaLRyOnA5ic-27Z-2MkA3iOgA== + id: rs_04129cd8130df26a006adb38ec332887d0a18b5f9366fd8e0c + summary: [] + type: reasoning - content: - annotations: [] logprobs: [] - text: OK + text: '- **Tokyo:** Clear, **71.6°F** + + - **Paris:** Clear, **71.6°F**' type: output_text - id: msg_0e3fe24990ca0d3e006adb2daea174819bb33ffb88a04df474 + id: msg_04129cd8130df26a006adb38ec70fc87d0aef01d0bf66fab89 phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_04129cd8130df26a006adb38e6292087d0844805567e083e12 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -1473,24 +2674,59 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + output_schema: null + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 63 + input_tokens: 702 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 56 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 68 + reasoning_tokens: 26 + total_tokens: 758 user: null headers: content-type: application/json status_code: 200 -- filename: t16 +- filename: t17 request: body: {} headers: @@ -1498,7 +2734,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0e3fe24990ca0d3e006adb2dadbfa4819b858da14bdd89b375/input_items + path: /v1/responses/resp_04129cd8130df26a006adb38eb6fac87d0bfafa9e5182f1a4f/input_items query_params: limit: '100' order: asc @@ -1506,88 +2742,161 @@ turns: body: data: - content: - - text: Call counter__say_hello exactly once with {}. Do not call any other - tool. Return exactly MCP_SAYS=hello. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' type: input_text - id: msg_0e3fe24990ca0d3e006adb2dadd7f4819b8d15e120b8a6629e + id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 role: user status: completed type: message - - id: mcpl_0e3fe24990ca0d3e006adb2dadd810819baffca9debc56b778 - server_label: counter - tools: - - annotations: - read_only: false - description: Say hello to the client - input_schema: - properties: {} - type: object - name: say_hello - type: mcp_list_tools - - approval_request_id: null - arguments: '{}' - error: null - id: mcp_0e3fe24990ca0d3e006adb2dadd854819b9340cdf6b97c764e - name: say_hello - output: hello - server_label: counter + - content: [] + id: rs_04129cd8130df26a006adb38d4324c87d09716372ce68d8c5c + summary: [] + type: reasoning + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctc_04129cd8130df26a006adb38d5638c87d09c6ce0569b880ef2 + input: 'fahrenheit + + ' + name: set_temperature_unit status: completed - type: mcp_call + type: custom_tool_call + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctco_04129cd8130df26a006adb38e274e087d0b728c283ec955761 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output - content: - - annotations: [] - logprobs: [] - text: MCP_SAYS=hello - type: output_text - id: msg_0e3fe24990ca0d3e006a6b125d62b8819ba94430a4f73a8cc1 - phase: final_answer - role: assistant + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_04129cd8130df26a006adb38e2750c87d0a70dd0177d1f054f + role: user + status: completed + type: message + - content: [] + id: rs_04129cd8130df26a006adb38e37a3487d0b740e78a1882f194 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e4077887d091779ed5c296e758 + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker":"AAPL"}' + call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e4079087d0872a5b5e6e151666 + name: get_stock_price + status: completed + type: function_call + - call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e6362487d0a1e72650acb1c425 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e6364487d0ba35541709380bc4 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output + - content: + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' + type: input_text + id: msg_04129cd8130df26a006adb38e6365c87d0b23e95f98b2b71c1 + role: user status: completed type: message + - content: [] + id: rs_04129cd8130df26a006adb38e6bf3087d0b211b13918eab15b + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + id: fc_04129cd8130df26a006adb38e73f0c87d0b98cb10fc48fa31a + name: get_weather + status: completed + type: function_call + - arguments: '{"city":"Paris"}' + call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + id: fc_04129cd8130df26a006adb38e73f1c87d0aae66b51389c1a79 + name: get_weather + status: completed + type: function_call + - call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + id: fc_04129cd8130df26a006adb38eb7b3887d0b90de6204c87905f + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + id: fc_04129cd8130df26a006adb38eb7b5887d09aa80da51ec701f5 + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: What's the weather in each of those two cities? type: input_text - id: msg_0e3fe24990ca0d3e006adb2dadd87c819bab391e666aa99d02 + id: msg_04129cd8130df26a006adb38eb7b7487d093df0daf75619730 role: user status: completed type: message - first_id: msg_0e3fe24990ca0d3e006adb2dadd7f4819b8d15e120b8a6629e + first_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 has_more: false - last_id: msg_0e3fe24990ca0d3e006adb2dadd87c819bab391e666aa99d02 + last_id: msg_04129cd8130df26a006adb38eb7b7487d093df0daf75619730 object: list headers: content-type: application/json status_code: 200 -- filename: t17 +- filename: t18 request: body: input: - - content: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. + - content: Do not call tools again. Reply only OK. role: user - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") - - ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - id: ci_020ffaaa85f1ecf6016ab617b64c4c87d1b4d893d7936f5244 - outputs: null - status: completed - type: code_interpreter_call - - content: - - annotations: [] - logprobs: [] - text: CODE_INTERPRETER_OK=385 - type: output_text - id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 - phase: final_answer - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_04129cd8130df26a006adb38eb6fac87d0bfafa9e5182f1a4f store: true stream: false + tool_choice: none + tools: + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -1602,14 +2911,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748976 - created_at: 1792748975 + completed_at: 1792751857 + created_at: 1792751855 error: null frequency_penalty: 0.0 - id: resp_020ffaaa85f1ecf6006adb2dafe05487d183b6c417597c17cd + id: resp_04129cd8130df26a006adb38ef67bc87d084d3bb0b37002d9b incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -1621,14 +2930,14 @@ turns: logprobs: [] text: OK type: output_text - id: msg_020ffaaa85f1ecf6006adb2db09aa487d199d76c9b12a69006 + id: msg_04129cd8130df26a006adb38f0f08487d0bbf61c454457358e phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_04129cd8130df26a006adb38eb6fac87d0bfafa9e5182f1a4f prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -1645,7 +2954,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -1659,24 +2968,59 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Paris + type: string + required: + - city + type: object + strict: true + type: function + - description: Get the current stock price for a given ticker symbol. + name: get_stock_price + output_schema: null + parameters: + additionalProperties: false + properties: + ticker: + description: Stock ticker symbol, e.g. AAPL + type: string + required: + - ticker + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 126 + input_tokens: 774 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 131 + total_tokens: 779 user: null headers: content-type: application/json status_code: 200 -- filename: t18 +- filename: t19 request: body: {} headers: @@ -1684,7 +3028,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_020ffaaa85f1ecf6006adb2dafe05487d183b6c417597c17cd/input_items + path: /v1/responses/resp_04129cd8130df26a006adb38ef67bc87d084d3bb0b37002d9b/input_items query_params: limit: '100' order: asc @@ -1692,146 +3036,172 @@ turns: body: data: - content: - - text: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call get_stock_price for "AAPL", and (3) call set_temperature_unit + to set my preferred unit to "fahrenheit" for the rest of this conversation.' type: input_text - id: msg_020ffaaa85f1ecf6006adb2dafea6487d18f06d0d363a8f9fe + id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 role: user status: completed type: message - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") + - content: [] + id: rs_04129cd8130df26a006adb38d4324c87d09716372ce68d8c5c + summary: [] + type: reasoning + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctc_04129cd8130df26a006adb38d5638c87d09c6ce0569b880ef2 + input: 'fahrenheit ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - id: ci_020ffaaa85f1ecf6006adb2dafeaa487d19557cc928c7f8f26 - outputs: null + name: set_temperature_unit status: completed - type: code_interpreter_call + type: custom_tool_call + - call_id: call_bThuNT6uHSEesGGGfFydRfHD + id: ctco_04129cd8130df26a006adb38e274e087d0b728c283ec955761 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output - content: - - annotations: [] - logprobs: [] - text: CODE_INTERPRETER_OK=385 - type: output_text - id: msg_020ffaaa85f1ecf6006adb2dafeaf887d1bb8342a6268df5df - phase: final_answer - role: assistant + - text: What did you find for the weather and the stock price, and what temperature + unit did you just set? + type: input_text + id: msg_04129cd8130df26a006adb38e2750c87d0a70dd0177d1f054f + role: user status: completed type: message + - content: [] + id: rs_04129cd8130df26a006adb38e37a3487d0b740e78a1882f194 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e4077887d091779ed5c296e758 + name: get_weather + status: completed + type: function_call + - arguments: '{"ticker":"AAPL"}' + call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e4079087d0872a5b5e6e151666 + name: get_stock_price + status: completed + type: function_call + - call_id: call_1QGTsq7QjZvsBbqV7PI1laDZ + id: fc_04129cd8130df26a006adb38e6362487d0a1e72650acb1c425 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: call_h9C8lU46xPL4Q3ZV4G0Wk68a + id: fc_04129cd8130df26a006adb38e6364487d0ba35541709380bc4 + output: '{"ticker": "AAPL", "price": 231.45, "currency": "USD"}' + status: completed + type: function_call_output - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Do both of these right now, in parallel, in this single turn -- do + not wait for one before starting another: (1) call get_weather for "Tokyo", + and (2) call get_weather for "Paris".' type: input_text - id: msg_020ffaaa85f1ecf6006adb2dafead887d18a40330286c9b6aa + id: msg_04129cd8130df26a006adb38e6365c87d0b23e95f98b2b71c1 role: user status: completed type: message - first_id: msg_020ffaaa85f1ecf6006adb2dafea6487d18f06d0d363a8f9fe - has_more: false - last_id: msg_020ffaaa85f1ecf6006adb2dafead887d18a40330286c9b6aa - object: list - headers: - content-type: application/json - status_code: 200 -- filename: t19 - request: - body: - input: - - content: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. + - content: [] + id: rs_04129cd8130df26a006adb38e6bf3087d0b211b13918eab15b + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + id: fc_04129cd8130df26a006adb38e73f0c87d0b98cb10fc48fa31a + name: get_weather + status: completed + type: function_call + - arguments: '{"city":"Paris"}' + call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + id: fc_04129cd8130df26a006adb38e73f1c87d0aae66b51389c1a79 + name: get_weather + status: completed + type: function_call + - call_id: call_SQDtr2NwQhbeXCPLktdfyDSX + id: fc_04129cd8130df26a006adb38eb7b3887d0b90de6204c87905f + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - call_id: call_yyXC5pmY7FCLCbWYJxi81IgT + id: fc_04129cd8130df26a006adb38eb7b5887d09aa80da51ec701f5 + output: '{"city": "Paris", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output + - content: + - text: What's the weather in each of those two cities? + type: input_text + id: msg_04129cd8130df26a006adb38eb7b7487d093df0daf75619730 role: user - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") - - ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - outputs: null status: completed - type: code_interpreter_call + type: message + - content: [] + id: rs_04129cd8130df26a006adb38ec332887d0a18b5f9366fd8e0c + summary: [] + type: reasoning - content: - annotations: [] logprobs: [] - text: CODE_INTERPRETER_OK=385 + text: '- **Tokyo:** Clear, **71.6°F** + + - **Paris:** Clear, **71.6°F**' type: output_text - id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 + id: msg_04129cd8130df26a006adb38ec70fc87d0aef01d0bf66fab89 phase: final_answer role: assistant status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_04129cd8130df26a006adb38ef6e9487d0affb74650bbcc07d role: user - model: gpt-5.6 - store: true - stream: false - headers: - accept: '*/*' - authorization: Bearer *** - content-type: application/json - user-agent: python-httpx/0.28.1 - method: POST - path: /v1/responses - query_params: {} - response: - body: - error: - code: missing_required_parameter - message: 'Missing required parameter: ''input[1].id''.' - param: input[1].id - type: invalid_request_error + status: completed + type: message + first_id: msg_04129cd8130df26a006adb38d3c5ec87d0aa85e4a251c611e2 + has_more: false + last_id: msg_04129cd8130df26a006adb38ef6e9487d0affb74650bbcc07d + object: list headers: content-type: application/json - status_code: 400 + status_code: 200 - filename: t20 - request: - body: {} - headers: - accept: '*/*' - authorization: Bearer *** - user-agent: python-httpx/0.28.1 - method: GET - path: /v1/responses/resp_missing/input_items - query_params: - limit: '100' - order: asc - response: - body: - error: - code: null - message: Response with id 'resp_missing' not found. - param: null - type: invalid_request_error - headers: - content-type: application/json - status_code: 404 -- filename: t21 request: body: - input: - - content: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. - role: user - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") - - ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - id: ci_020ffaaa85f1ecf6016ab617b64c4c87d1b4d893d7936f5244 - outputs: null - type: code_interpreter_call - - content: - - annotations: [] - logprobs: [] - text: CODE_INTERPRETER_OK=385 - type: output_text - id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + input: 'Do all three of these right now, in parallel, in this single turn -- + do not wait for one before starting another: (1) call get_weather for "Tokyo", + (2) call set_temperature_unit to set my preferred unit to "fahrenheit" for + the rest of this conversation, and (3) search the web for the exact query + "Tokyo weather today".' + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true store: true stream: false + tool_choice: auto + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -1846,30 +3216,33 @@ turns: background: false billing: payer: developer - completed_at: 1792748980 - created_at: 1792748979 + completed_at: 1792751862 + created_at: 1792751859 error: null frequency_penalty: 0.0 - id: resp_020ffaaa85f1ecf6006adb2db3388887d1bc850fef29be776d + id: resp_06c0f7faf6143021006adb38f33a8087d0a446531ae5719e61 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] - logprobs: [] - text: OK - type: output_text - id: msg_020ffaaa85f1ecf6006adb2db4149087d197835deda97e68b0 - phase: final_answer - role: assistant + - content: [] + encrypted_content: gAAAAABq2zj2VFSwN0YDUuf2i2JfxORTHfHfiGFqJw9bwHBkNJ4lSEneimhUyiztHuei4-lXBBYT-5TzpQxMMWGGCp7TF4YRoKb_g1Bkpfs1HFdZiNUnYumZKecr3lBPfYSODcJz1JyB--T6g4YWvTvveLpqlHwVD2o7jSETKrmrLWi2bF-lOc-LccqxROLHEiw2PIL1GD30dtgzZt9NspBE8IZM0wuu6Hi8QCftGyS0BCNBcIbTNV60yB7kLrS0UUJK35qB8jYszNpuAME55HkWD7AaugPve7Rp6dAL_625WgSlreJLeGv28ZRt1OB7zEopnV-uDMbnI2_6ybfVA0jUIJHql55T3Yn39xVST5nGGvw0ndshY6O4XsAsI3yEK4sLqaBKLR41_YDT070eaylb-KUBd7AIya9seKVFeAMWSo7lhsVPgg-L1YKc7CcYRDz4azLKaUSLLGPit7NOsq1MF9_JsB_x_F4bhffT_oOJK2chMV8KTVYS50NtLf9seiTstSu2QJJEy_tA-xL5ClkFNtzWAmY9PDQ7I4z9pKub5wd5h0feUimvMlikm4PeR_025ZLPchrrPPZ9Nqz2PLvTm6acgdE9wt0C8AqRU080uyQn4tkThiKS-kQtwif7haCmOlGcrIkZRFL78np9ghNmKLN21WRrzlp1pI5n32l_WkY3ZIkaC29x7bCBc8c0CxPn9WietbNqKuYiOpGqME0TCGGfHbpyDjW82YZ5tI7-uvqTSnuuHyatbtf0H7e7vjg5i-7F-em_7CHWXg6t8YiQoZ1o5y_4zEDmx9Lrr-rFFeY9ps1SMfDMr1OH6HEzzTt2AcncYTPrsVRsxclXadmYcNMugyLw0lAYu0GMUSrjslKe2GZF_yacuegtFlArCdxhK4rJ4a79CNz8OEM7fAipokmg62V6fF0CZoD2Cdz9vh67Q8jJFCCFJilYMfc5fThCpJTn-WxsVvdCs9_5tG-U6t9UPxl9x1LjpI0YhT0j0AeQ9Djmh8OmIf0D-8kfJNPdaruiM4AW8OxGbh1_koBB4ZeB1wm9Qsaf1D6OfNatTEAltghvDg1zGB30DeVcrx91DyOGo8AsA0FKz7heSYZ3MXROR4RIcKhI7wsGUfawzzWtivOkKO-TabyrkdMhrgq6o0xSiAMUQlFSN0LeGiAaQ5NHDe0d6mmSOHr45R6KtjuHfYfG-bokd45VbB6uMju6fKzxttbpQGYk_JTJaYqKs3i6tckNpyRaoBN5PeT5eCAds6F5cbXfH1agj7sjziZrBsuIz9Q6rUSi3LULvIrm3_f0-m4wkfTqcwZ23QkvIk8FhcvFCb5A6TyvgoRPn6Faoc06ffzpyjgRKty001QsNi9WCvFcvBC4uYLzzPG2kmcfZck-yIaNuR1iQ6cOUQDZvgaQzXES_F1c0US9Q9G0GVbKfoPh4oDSMt5jxiILtBQRbDVEt8pNoSW1brYu1f8R-Bx6mCgKWpCcIed0adP5sJN3SZwTTvLCzAIuRXPF4RQ4wUUFfxyIP1lVGHGYPiSXQ6p2YWju94Wn76J7NNyKeG6Q3K_mj4tRCs0jXWiKlErPv12qZuNa8cG5HdViJX0FzaLg1f-21JLjkSJsOQEZSp6rQPr5mkvLK-iS5LXARbxnFr1vpLr0vBl913NriC9uMD5uqWxelsVR0DEAFGk7WT651mwiRDT_vULAMRuumvK43iGguVGpua57PoR64QGWKFgHaQ5VAhjFhEMMnUOaDmOXScMWYOec0bKbcbMHpsg4MQps0PY9kJA6wwrNvvSC82wNrHMNynLtYZmJUEp0CxtEoB2mQ_r-N-yrT-TnSuoir7HXdVlzaTpptbTyR8McVlk7VTVsQvVBjpXPT-y9ifzTtVE9dPX_AJgSn6hcJq01bReERpt48pt4B8oWeok242bmZhhFcFkEsCDONQVS7m7QFv0d24iD2aGTXLUOXbtHRlZx7He-vzSkQOWBonokKrHXZq3EGpUdd8VcAemOiZ0S4hcyC7_TVVWsWWyE9-mYPIln7DzM3FkcyANFe7ChWbK1nWglenZPTMiStn71FNILvhaprQ0Bvj83uH3hx4U1qeNHFDSRMr_LhjAjuBkLsuvxI3O4b6weeuSSQXxC-yf3_DOVkE7GtTmgb2Z6M-9pFBcr-2AmJADJiXosyxVdlFg_BCMxs5ST160TTnQcCi7DF8vdHaDYomTTvd76bXYMgbQz7qqYTSVw0xIAWLv6J3-ezyQC5eeSVw03ZhGxdV_hJy1EzCkUwy8TE5CXJQdW5-5AWTHUmP8SaQgTFjvtj6OAX96bhC-suV6cG_j-3uK-oXQ30E8tFMsfms5uPc1GYXsSabiMtMIU3-M47xKul9QPSLSMfvZdQT5lIDDnBDYKzT6kFDpeyxIYeHAWQWwTcJEnqquxLIexLEuDgexw578ORjOgIDqznucDsLKSf6iBOkb9B54CdZGC_ZBJcw4vYIGu_eOaJ338hTTlM7qncYKE6sgOccbALc-zAHtMquIAHj0uobxAF9rlf3LXYHl_MSbgqK2VV-dihI3n1qZfkVGtyciYO7wbwy6qQGD7yCDEEAtIFts3XiKHNGXkBJuAPPfb15gLC_giap1-EALXyNonfzyCfmLn9daNxaEs7UH6MxaY9dcG7ieldgH6bkt2vnrwCcWO_xs73Q6oGErIeBVeDhukd97PYAqaiB2PNhTriIg899Js_P6u4diqfjn6ZdCLs7mKSyqzONSRMJRC0uBtrQJR + id: rs_06c0f7faf6143021006adb38f4ab7c87d084de79cfd460b698 + summary: [] + type: reasoning + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + id: ctc_06c0f7faf6143021006adb38f5e44887d0980d5c58c96707d5 + input: 'fahrenheit + + ' + name: set_temperature_unit status: completed - type: message + type: custom_tool_call parallel_tool_calls: true presence_penalty: 0.0 previous_response_id: null @@ -1903,24 +3276,55 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 126 + input_tokens: 4630 input_tokens_details: cache_write_tokens: 0 - cached_tokens: 0 - output_tokens: 5 + cached_tokens: 4627 + output_tokens: 118 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 131 + reasoning_tokens: 101 + total_tokens: 4748 user: null headers: content-type: application/json status_code: 200 -- filename: t22 +- filename: t21 request: body: {} headers: @@ -1928,7 +3332,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_020ffaaa85f1ecf6006adb2db3388887d1bc850fef29be776d/input_items + path: /v1/responses/resp_06c0f7faf6143021006adb38f33a8087d0a446531ae5719e61/input_items query_params: limit: '100' order: asc @@ -1936,75 +3340,61 @@ turns: body: data: - content: - - text: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. - type: input_text - id: msg_020ffaaa85f1ecf6006adb2db3421087d1a784151b5a023421 - role: user - status: completed - type: message - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") - - ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - id: ci_020ffaaa85f1ecf6006adb2db3424887d1bbbbd8b6c686b4b2 - outputs: null - status: completed - type: code_interpreter_call - - content: - - annotations: [] - logprobs: [] - text: CODE_INTERPRETER_OK=385 - type: output_text - id: msg_020ffaaa85f1ecf6006adb2db3429c87d1b55ceab0e354d62d - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' type: input_text - id: msg_020ffaaa85f1ecf6006adb2db3428087d1b9c1c7cd003b1f28 + id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d role: user status: completed type: message - first_id: msg_020ffaaa85f1ecf6006adb2db3421087d1a784151b5a023421 + first_id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d has_more: false - last_id: msg_020ffaaa85f1ecf6006adb2db3428087d1b9c1c7cd003b1f28 + last_id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d object: list headers: content-type: application/json status_code: 200 -- filename: t23 +- filename: t22 request: body: input: - - content: Use the python tool to calculate the sum of the squares of the integers - from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. - After it runs, answer exactly CODE_INTERPRETER_OK=385. + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + output: '{"status": "ok", "unit": "fahrenheit"}' + type: custom_tool_call_output + - content: Now report Tokyo's current temperature using the unit I just told + you to prefer. role: user - - code: 'print(f"CODE_INTERPRETER_OK={sum(i**2 for i in range(1, 11))}") - - ' - container_id: cntr_6ab617b594c4819382eee217438ef95a0f2c410c10efc2e0 - outputs: null - type: code_interpreter_call - - content: - - annotations: [] - logprobs: [] - text: CODE_INTERPRETER_OK=385 - type: output_text - id: msg_020ffaaa85f1ecf6016ab617b7bd7487d1968233eda02e0739 - phase: final_answer - role: assistant - status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_06c0f7faf6143021006adb38f33a8087d0a446531ae5719e61 store: true stream: false + tool_choice: auto + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -2015,15 +3405,117 @@ turns: query_params: {} response: body: - error: - code: missing_required_parameter - message: 'Missing required parameter: ''input[1].id''.' - param: input[1].id - type: invalid_request_error + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751865 + created_at: 1792751864 + error: null + frequency_penalty: 0.0 + id: resp_06c0f7faf6143021006adb38f84d6c87d0be263c3949425023 + incomplete_details: null + instructions: null + max_output_tokens: 2048 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: [] + encrypted_content: gAAAAABq2zj6lCtq4w6wLm67juuQJLtebvzh9UVzZtsWF-8lkNhqIXoR17r8IycQbOsG_sCRzT7jL2Q3Ro5iWjF4E4kldGDP2JtRmwmQs1bfrRXecWQ4BBNtdj5RrUahl1Fotyg1lffm0N-BKIpO707grTTuHI6YFiuU6dqs6WRg2lWG8Aev32WTLFvLGrVgCp3ttfCs7VbrgNruteTRqyJmYFjPs7XCw1rjmn4gFeUcLfNIQIczVidIIDU8Qz0cC5pdF5REbMD6NVFZKh6k86jG4oForJEmQ2nLONv24NDKwywCY8_Q3Qzjq7epWsunAgOIQKF3E9WrjktyCVCzR1qGpeM74-PC_g1lQBNa0-BQoKA1C2FG1_oDQXANlQ09ypm_WIy8YBF5Akye--TfdDH0QsKJAa7I9FeTdsH8CXUlEyEvDW-tamuq5XLszFJWSEsiK2RV9xfRinJSvRVVWoryamYL302iyggdkFA-P7VvI-GJsdJD7ldOU-6-hr6KUDVW55taG9BNH-bJ4Om_MdSAOrjlsQjTFWfmVu_rqNsYCiHzRGrz1EXisHhg0tbIi7xkaCHc-844Hm3DiqxXvWHxk3W4_U-sgcX8zKl-H0iUfdn2ifzAMXH-Szg-YIyp2F0wZwGnykihVxZmFCopobScVv-YQ1KkddSnEPGdjD1sXGkZjPH-YqWsW8SdxgsHftV4yOJlCYbVSi9X0Kp7rfGCF4BrktDGHyQvwyarEGmmDIZJM58_-PpH4bFZO6_0pFj2PpqCsdXkQTOPWmii-JAWCM6bdjFgq47Z-zTBc1GtHC0G3Ii1c6e-Pq2e1TSEm11HGtnO--uk7Jwqdep6GYmFQ0Fa4NQ59gtTacC2IGlvro0ndge--lCkbzaBgjVESVfIuetAReJkCuy5NysX1Rh-RV_D05URKfgUBpnF6PD1ztxzAPuimiJPx5JCZSfcnuqG8BQh0hAqrz23aW8gMGyv35sUBpDtruyBnHIGgA-ippVYAZyJIBN42r0j_JQqPu005tGl3RZ8Ea2EcGbdWqz5oqF-fDSDEEcRoCk0eSQRyZ8EkLWh0oJU-0DdqhE9hpXhNbAVklLvtvKF4tJexokOV351NM_XFtsR1QIx1b9LaIFM06zecCLB-7n-3eTESqIT4796DFZVIZAr5HfcORZ27KXv8xa46MfZbIPT7Pf_53S9lPJK4tvolWd9hQcamZbTi_GmTP9Xna8AHE2aI0fvZThhUnfPgx7RmdEe77uZmGExA3DDawtTFx9SShSgzoY9DWv8Dlzrzg7MNqJ91mRr1JmLajBUgQTGel7zehbp4KI03sTPMVq5tLEN5wq1si-_0WkgHMks-A5D12JH9TZAX8sMeE9sVQ1H2aO8nw5Z7csbdhQ8TaD0OVytp7-kZNhXJmpL0aSvXeXCCKiGAwTwloY4FhupYRVL2AakbUuwDGdYYh6KhcMUqJlkEMMCm_EABGxDpADFQRmX9ujBS5wqYIsQp_l7SZhcwMMl58f0VaXU8vT8vOVbW8P0qOk7MKMuHWatmsdzK9_F6zDOoLY_5ldUNgLtbgyvzun8-0dOgfP4oWbVBRLcUqwCYB3oWOFSZsHMyBhSr2UjsAUniktaqCKLNQNCAA== + id: rs_06c0f7faf6143021006adb38f8d09887d092eb442374b8a638 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_XypZu8IaaAq5MfS2TooJCbyA + id: fc_06c0f7faf6143021006adb38f8f86887d0a33540e90ce42375 + name: get_weather + status: completed + type: function_call + parallel_tool_calls: true + presence_penalty: 0.0 + previous_response_id: resp_06c0f7faf6143021006adb38f33a8087d0a446531ae5719e61 + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 4793 + input_tokens_details: + cache_write_tokens: 163 + cached_tokens: 4627 + output_tokens: 36 + output_tokens_details: + reasoning_tokens: 16 + total_tokens: 4829 + user: null headers: content-type: application/json - status_code: 400 -- filename: t24 + status_code: 200 +- filename: t23 request: body: {} headers: @@ -2031,77 +3523,93 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_missing/input_items + path: /v1/responses/resp_06c0f7faf6143021006adb38f84d6c87d0be263c3949425023/input_items query_params: limit: '100' order: asc response: body: - error: - code: null - message: Response with id 'resp_missing' not found. - param: null - type: invalid_request_error - headers: - content-type: application/json - status_code: 404 -- filename: t25 - request: - body: - input: - - content: 'The local shell environment is Linux bash. Use the shell tool exactly - once with this exact action, preserving the commands array and its order: - {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": - 4096}. Wait for the client to return shell_call_output before interpreting - the results.' + data: + - content: + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' + type: input_text + id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d role: user + status: completed + type: message - content: [] - encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + id: rs_06c0f7faf6143021006adb38f4ab7c87d084de79cfd460b698 summary: [] type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a - status: completed - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - max_output_length: 4096 - output: - - outcome: - exit_code: 0 - type: exit - stderr: '' - stdout: 'SHELL_OK + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + id: ctc_06c0f7faf6143021006adb38f5e44887d0980d5c58c96707d5 + input: 'fahrenheit - ' - type: shell_call_output - - content: Use the shell output above without calling any more tools. For each - command, report its stdout, stderr, and exit code or timeout outcome in - order. - role: user - type: message + ' + name: set_temperature_unit + status: completed + type: custom_tool_call + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + id: ctco_06c0f7faf6143021006adb38f85dfc87d08fd39f9a08a01f58 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant + - text: Now report Tokyo's current temperature using the unit I just told + you to prefer. + type: input_text + id: msg_06c0f7faf6143021006adb38f85e1c87d0bc882285027eb956 + role: user status: completed type: message - - content: This is completed tool history. Do not call tools. Reply only OK. + first_id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d + has_more: false + last_id: msg_06c0f7faf6143021006adb38f85e1c87d0bc882285027eb956 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t24 + request: + body: + input: + - call_id: call_XypZu8IaaAq5MfS2TooJCbyA + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + type: function_call_output + - content: Do not call tools again. Reply only OK. role: user + type: message + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: true + previous_response_id: resp_06c0f7faf6143021006adb38f84d6c87d0be263c3949425023 store: true stream: false + tool_choice: none + tools: + - type: web_search_preview + - description: Get the current weather for a given city. + name: get_weather + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + name: set_temperature_unit + type: custom headers: accept: '*/*' authorization: Bearer *** @@ -2116,14 +3624,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748983 - created_at: 1792748982 + completed_at: 1792751869 + created_at: 1792751868 error: null frequency_penalty: 0.0 - id: resp_0d2fb655988faaf0006adb2db6bca087d084eb452458cb70fb + id: resp_06c0f7faf6143021006adb38fbdfc087d084ce36f575dbf197 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -2135,14 +3643,14 @@ turns: logprobs: [] text: OK type: output_text - id: msg_0d2fb655988faaf0006adb2db7532487d0aa7bd5ed3557a630 + id: msg_06c0f7faf6143021006adb38fd0bf887d0a5d15f31e55a129f phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_06c0f7faf6143021006adb38f84d6c87d0be263c3949425023 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -2159,7 +3667,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -2173,24 +3681,55 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - description: Get the current weather for a given city. + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + description: City name, e.g. Tokyo + type: string + required: + - city + type: object + strict: true + type: function + - search_content_types: + - text + search_context_size: medium + type: web_search_preview + user_location: + city: null + country: US + region: null + timezone: null + type: approximate + - description: Set the user's preferred temperature unit. This changes how temperatures + should be reported for the rest of this conversation. Call with the raw + unit as plain text input, e.g. fahrenheit -- not JSON. + format: + type: text + name: set_temperature_unit + type: custom top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 345 + input_tokens: 4873 input_tokens_details: - cache_write_tokens: 0 - cached_tokens: 0 + cache_write_tokens: 80 + cached_tokens: 4718 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 350 + total_tokens: 4878 user: null headers: content-type: application/json status_code: 200 -- filename: t26 +- filename: t25 request: body: {} headers: @@ -2198,7 +3737,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0d2fb655988faaf0006adb2db6bca087d084eb452458cb70fb/input_items + path: /v1/responses/resp_06c0f7faf6143021006adb38fbdfc087d084ce36f575dbf197/input_items query_params: limit: '100' order: asc @@ -2206,133 +3745,87 @@ turns: body: data: - content: - - text: 'The local shell environment is Linux bash. Use the shell tool exactly - once with this exact action, preserving the commands array and its order: - {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": - 4096}. Wait for the client to return shell_call_output before interpreting - the results.' + - text: 'Do all three of these right now, in parallel, in this single turn + -- do not wait for one before starting another: (1) call get_weather for + "Tokyo", (2) call set_temperature_unit to set my preferred unit to "fahrenheit" + for the rest of this conversation, and (3) search the web for the exact + query "Tokyo weather today".' type: input_text - id: msg_0d2fb655988faaf0006adb2db6d17c87d0aa102ef98557cc7d + id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d role: user status: completed type: message - content: [] - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + id: rs_06c0f7faf6143021006adb38f4ab7c87d084de79cfd460b698 summary: [] type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a - status: completed - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - id: sho_0d2fb655988faaf0006adb2db6d1b887d0b624f670294527d6 - max_output_length: null - output: - - outcome: - exit_code: 0 - type: exit - stderr: '' - stdout: 'SHELL_OK + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + id: ctc_06c0f7faf6143021006adb38f5e44887d0980d5c58c96707d5 + input: 'fahrenheit - ' + ' + name: set_temperature_unit status: completed - type: shell_call_output + type: custom_tool_call + - call_id: call_sl8IRw8WVurN49oScf2ZmIqj + id: ctco_06c0f7faf6143021006adb38f85dfc87d08fd39f9a08a01f58 + output: '{"status": "ok", "unit": "fahrenheit"}' + status: completed + type: custom_tool_call_output - content: - - text: Use the shell output above without calling any more tools. For each - command, report its stdout, stderr, and exit code or timeout outcome in - order. + - text: Now report Tokyo's current temperature using the unit I just told + you to prefer. type: input_text - id: msg_0d2fb655988faaf0006adb2db6d1d087d0ac4c0057b273e0cb + id: msg_06c0f7faf6143021006adb38f85e1c87d0bc882285027eb956 role: user status: completed type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant + - content: [] + id: rs_06c0f7faf6143021006adb38f8d09887d092eb442374b8a638 + summary: [] + type: reasoning + - arguments: '{"city":"Tokyo"}' + call_id: call_XypZu8IaaAq5MfS2TooJCbyA + id: fc_06c0f7faf6143021006adb38f8f86887d0a33540e90ce42375 + name: get_weather status: completed - type: message + type: function_call + - call_id: call_XypZu8IaaAq5MfS2TooJCbyA + id: fc_06c0f7faf6143021006adb38fbfdec87d0acd87250be328ce1 + output: '{"city": "Tokyo", "temperature_c": 22, "condition": "Clear"}' + status: completed + type: function_call_output - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: Do not call tools again. Reply only OK. type: input_text - id: msg_0d2fb655988faaf0006adb2db6d1f087d0a4593221d350eac5 + id: msg_06c0f7faf6143021006adb38fbfe1487d0ae7a4f68d0542044 role: user status: completed type: message - first_id: msg_0d2fb655988faaf0006adb2db6d17c87d0aa102ef98557cc7d + first_id: msg_06c0f7faf6143021006adb38f34ab487d095a19a1d4d76045d has_more: false - last_id: msg_0d2fb655988faaf0006adb2db6d1f087d0a4593221d350eac5 + last_id: msg_06c0f7faf6143021006adb38fbfe1487d0ae7a4f68d0542044 object: list headers: content-type: application/json status_code: 200 -- filename: t27 +- filename: t26 request: body: - input: - - content: 'The local shell environment is Linux bash. Use the shell tool exactly - once with this exact action, preserving the commands array and its order: - {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": - 4096}. Wait for the client to return shell_call_output before interpreting - the results.' - role: user - - content: [] - encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 - summary: [] - type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - status: completed - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - max_output_length: 4096 - output: - - outcome: - exit_code: 0 - type: exit - stderr: '' - stdout: 'SHELL_OK - - ' - type: shell_call_output - - content: Use the shell output above without calling any more tools. For each - command, report its stdout, stderr, and exit code or timeout outcome in - order. - role: user - type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + input: 'The local shell environment is Linux bash. Use the shell tool exactly + once with this exact action, preserving the commands array and its order: + {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": + 4096}. Wait for the client to return shell_call_output before interpreting + the results.' + max_output_tokens: 4096 model: gpt-5.6 store: true stream: false + tool_choice: auto + tools: + - environment: + type: local + type: shell headers: accept: '*/*' authorization: Bearer *** @@ -2347,30 +3840,35 @@ turns: background: false billing: payer: developer - completed_at: 1792748985 - created_at: 1792748984 + completed_at: 1792751873 + created_at: 1792751871 error: null frequency_penalty: 0.0 - id: resp_0d2fb655988faaf0006adb2db8ed6c87d0a5066123901cd60f + id: resp_00865c2538464a9c006adb38ff90a487d089a30999d7aaa52e incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 4096 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] - logprobs: [] - text: OK - type: output_text - id: msg_0d2fb655988faaf0006adb2db9b01c87d0a3a70577e5c9291e - phase: final_answer - role: assistant + - content: [] + encrypted_content: gAAAAABq2zkCMsNa30fEJfc8SbTBXVZK_kvPogjRHT4DVy4x6-p5EdbjXhyAIsNxvGKk__WFqZSdNQgWNqZyyIYezVTdjpwzjuJIup-Z-v95L_r841cavGiiDwbTdcsqdkSQN3fDQSgvYKWghKxy09K4mxhPSD69ihDk1RUo_jkwtffgCvv6gGRI2KNToIUjAPijbm13JMzzDh6oh_9xu8KMgT0GSvCuuRx2QJM5shEfgxQycRJEsCRQy6CRX5kDUP96GnDCY1oN8LfFZxGJ3ba-jDLa9OAvpjtHi6-WmR1e9c1YFpiurbY2igd0ryf7ASAomAl48YudY6WGVH-_ZeG_mEk3RhBGEUURd3pvLyz6L0dok3oQfwR6vNjcUhBjxmITxKSWJhKb0848_VIAiB3bmYHlL8JwzeqkozZ8o1tOAICjIr7ttVoL4oOQ_fLBXr_wwjyq6NpklBwavHpu47j3Vcuou96B6H6pJtbAreopjxdGYBr51UNhbUY_rUrt-jG7-DFKPhv5h6YS37rP0BtCg3G0lORCSALcR3yWozUDcbJQ_q3jlL7eUmRN5i0KxtEuONCL10o3MADPUKcljQMPmSpfMQQyt-A-egvRRMxTsXH-KMBrjFoa2QYIWAir5DAdbPnwHPgebwkExxER0Jfo4H6lKu8ywgqXcNfdKQQHQTduwWXLM8ZMbNvDL5qTKFlNxe7WpaB9aS5NUyNLb1QawniTfFozVPsWcy3kGgbaoa8kIeHkY3t7bQEb9Ye9fwunufBCS-LfnGJLYBiUhIakWpwai27erWIeGv-Rw0dVmawwVcEV8uMdUTN83TsTN0WHnP-INqKJt3KIDAmp6059EqMV50ukuArGy2wjZs3au2ZoD3I1i3sSFM_l2Iee11QM5UBCxyNFPOXriQDJulWWKXGYn7qJXBRT1R4_Mp7zHenX4JL6PDu6b90-q0UVK95sSXY04QqPjTSTNMlm3yZtY3PwffZaqaGwnCVk-Ueu1V5mk70mrB2ihsRX-FZkNOaUQmedvCCGJwaVvgyukHl0n-hsAyxatXwnUI6PvpEpeYw_jpw1LBRxWBGXN39B4wWXJg-9OhU7_SF_ffKusvILiPy9HgNNTCoKJj04Gag2VMtkKV3VIEMJhCvFdD6d762x9_I98Cw4FtIzhIH3lRhJjkkO8NombBGzepI5DW8kRX_v6Bund3q78m8bgyHS-Edy2WSBSO8GfWKhiGzOfJaNR3lZhXalUhdnRU4Toz7mlVg4DSVpci768oYVnqfUvOzrpopDNR8Blu50bor5o8MIg9kGiyruGGgd0jSfJZ2x78D-tx3MKujf4zpLDtDzBHGODZ6zYgmuwo_CnAMW4uaNTennwrcBw-lZzcPOkMOlyntIgsh-UCH-EuKtroQIoi_p0qbor7oH08fgCaJUbEdLNfKHZH3LCFfbfzD-Te6paR4OaTYg1swPRUEwpYYaulkuT2pHJx71njNDy8rE3ojsl6_ekCwW_lbQTTVrXbB-qdSjoA1CGL5YUjwYVbLCmKSSgQNkuR1faL3ql_La4lMqp69GLpYEfuMBJZFuolZEMcSD-ewlVaydoUv16u6VLm25bAdZhim4g_w87fVyeM1IeHGZmSpFXxs0jfrKrQJIvNmoAG4_cI364B2q8R_6_QjD3AtRfZkz_kfbgfIt5NucJqzlEbgWgD4fJvSiiL3IfI4jEa5QhRd2O8IreSzmpX-4T5bTqgYm6Ss3EWz7ZMnq7wm9eT9M1Au-zt1N66cTAk_Y2C2jHbD7zArJBrNXc0zIb5qyY2Fc1WhK7BnwnlEUCb7Q8t10jPu3yMd54aYWEk-iI4SyWLS-0ZhFx-4EZks1vJlj_S-rN5FOoDAGWh5ot0PSFCXXbXvAQLfERvy2P9nhhpzmnppzMGBmwEOwgh2CZf-y1qFldR8V9ssqzp1BUvgJ1Act3ylkuk9OoSlhN75QwTWeZP_F9PvXh6gsZpvu3s8ad68Pid6TlfFnrix7-yZi4bYSPjmrXx7IevHOACncx4ZxbcWWvyXQp78H2xDZSfkK6yd_iqF3x361Kwp6iPEhnZzF_VGyOrLIDykG1vgAx_EBOyvB42ad7SDbjXQ6i-aBe-y74kHaFwqjUYoL4KOMn_wUj-hGE8IRUDA2apPr2npLL8QH14WwWlfpj41hAqNokH61JpMisJPZwpXihulqjJ11VoeNH-SMs0pvv-4ES79UVp3j-FqCkOfWUDN9p7rBhuChl97NUSQVhRpiNPoF4J5rK3hXxxKlq5-d8RI1xDaNkG9DUuVktfOQHYLqhrYyAmhts6lclYLw-eOoR-ZsOApJ2NeHAFISd8_4XVGfHV8X1FCYGwcRNWT-9yZHw_vRKLD-H4Up-a4GxPety2JBe8dDbSEiMEn0-lvTOc3U-4BhipHUfNFm-TD9OppmSiEPu3AHeNo7UkgYMXt39ag1W7IK0W98qsUY2jyVMt8a52q30sjX5MnDkHlqOydciBoCES9mxZLr5ovH-icpF8rEpQCtLhz_58wJCXQgqyA6gw2oVE0BCcsBIWBIwYWNOjLWEO0p + id: rs_00865c2538464a9c006adb39003df087d0a5c43cc4a399a190 + summary: [] + type: reasoning + - action: + commands: + - printf 'SHELL_OK\n' + max_output_length: 4096 + timeout_ms: 1000 + call_id: call_XLfMoSsfAoKWH1VGy8fcWglb + environment: null + id: sh_00865c2538464a9c006adb39017b7487d0a179d433561fe4da status: completed - type: message + type: shell_call parallel_tool_calls: true presence_penalty: 0.0 previous_response_id: null @@ -2404,24 +3902,27 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - environment: + type: local + type: shell top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 345 + input_tokens: 314 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 122 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 350 + reasoning_tokens: 83 + total_tokens: 436 user: null headers: content-type: application/json status_code: 200 -- filename: t28 +- filename: t27 request: body: {} headers: @@ -2429,7 +3930,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0d2fb655988faaf0006adb2db8ed6c87d0a5066123901cd60f/input_items + path: /v1/responses/resp_00865c2538464a9c006adb38ff90a487d089a30999d7aaa52e/input_items query_params: limit: '100' order: asc @@ -2443,96 +3944,22 @@ turns: 4096}. Wait for the client to return shell_call_output before interpreting the results.' type: input_text - id: msg_0d2fb655988faaf0006adb2db8f8c487d086435fe04f3ea615 + id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 role: user status: completed type: message - - content: [] - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 - summary: [] - type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - id: sh_0d2fb655988faaf0006adb2db8f90887d0a9f997dc4de5c96a - status: in_progress - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - id: sho_0d2fb655988faaf0006adb2db8f95087d0b2def1f37e948d07 - max_output_length: null - output: - - outcome: - exit_code: 0 - type: exit - stderr: '' - stdout: 'SHELL_OK - - ' - status: completed - type: shell_call_output - - content: - - text: Use the shell output above without calling any more tools. For each - command, report its stdout, stderr, and exit code or timeout outcome in - order. - type: input_text - id: msg_0d2fb655988faaf0006adb2db8f96487d0826bdc912180a83c - role: user - status: completed - type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. - type: input_text - id: msg_0d2fb655988faaf0006adb2db8f97c87d0a5fe376f3ed09861 - role: user - status: completed - type: message - first_id: msg_0d2fb655988faaf0006adb2db8f8c487d086435fe04f3ea615 + first_id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 has_more: false - last_id: msg_0d2fb655988faaf0006adb2db8f97c87d0a5fe376f3ed09861 + last_id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 object: list headers: content-type: application/json status_code: 200 -- filename: t29 +- filename: t28 request: body: input: - - content: 'The local shell environment is Linux bash. Use the shell tool exactly - once with this exact action, preserving the commands array and its order: - {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": - 4096}. Wait for the client to return shell_call_output before interpreting - the results.' - role: user - - content: [] - encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 - summary: [] - type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + - call_id: call_XLfMoSsfAoKWH1VGy8fcWglb max_output_length: 4096 output: - outcome: @@ -2548,22 +3975,16 @@ turns: order. role: user type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 4096 model: gpt-5.6 + previous_response_id: resp_00865c2538464a9c006adb38ff90a487d089a30999d7aaa52e store: true stream: false + tool_choice: auto + tools: + - environment: + type: local + type: shell headers: accept: '*/*' authorization: Bearer *** @@ -2578,14 +3999,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748988 - created_at: 1792748987 + completed_at: 1792751877 + created_at: 1792751875 error: null frequency_penalty: 0.0 - id: resp_0d2fb655988faaf0006adb2dbb31cc87d09a6aaecad597ec64 + id: resp_00865c2538464a9c006adb3903dfd887d0aeef4dcccbf01327 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 4096 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -2595,16 +4016,17 @@ turns: - content: - annotations: [] logprobs: [] - text: OK + text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ + \ *(empty)*\n - **exit code:** `0`" type: output_text - id: msg_0d2fb655988faaf0006adb2dbc270c87d08f9bab7037ca8bb1 + id: msg_00865c2538464a9c006adb39047fb887d085b830d039821589 phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_00865c2538464a9c006adb38ff90a487d089a30999d7aaa52e prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -2635,24 +4057,27 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - environment: + type: local + type: shell top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 345 + input_tokens: 499 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 - output_tokens: 5 + output_tokens: 43 output_tokens_details: reasoning_tokens: 0 - total_tokens: 350 + total_tokens: 542 user: null headers: content-type: application/json status_code: 200 -- filename: t30 +- filename: t29 request: body: {} headers: @@ -2660,7 +4085,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0d2fb655988faaf0006adb2dbb31cc87d09a6aaecad597ec64/input_items + path: /v1/responses/resp_00865c2538464a9c006adb3903dfd887d0aeef4dcccbf01327/input_items query_params: limit: '100' order: asc @@ -2674,12 +4099,12 @@ turns: 4096}. Wait for the client to return shell_call_output before interpreting the results.' type: input_text - id: msg_0d2fb655988faaf0006adb2dbb4a8487d0a80c3f37865b09de + id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 role: user status: completed type: message - content: [] - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + id: rs_00865c2538464a9c006adb39003df087d0a5c43cc4a399a190 summary: [] type: reasoning - action: @@ -2687,13 +4112,13 @@ turns: - printf 'SHELL_OK\n' max_output_length: 4096 timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + call_id: call_XLfMoSsfAoKWH1VGy8fcWglb environment: null - id: sh_0d2fb655988faaf0006a9f947dbf0087d09b831815734fc46a + id: sh_00865c2538464a9c006adb39017b7487d0a179d433561fe4da status: completed type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - id: sho_0d2fb655988faaf0006adb2dbb4ab887d0a44a88f0a35fabe5 + - call_id: call_XLfMoSsfAoKWH1VGy8fcWglb + id: sho_00865c2538464a9c006adb3903eba487d09772108166174d21 max_output_length: null output: - outcome: @@ -2710,90 +4135,34 @@ turns: command, report its stdout, stderr, and exit code or timeout outcome in order. type: input_text - id: msg_0d2fb655988faaf0006adb2dbb4ad087d08262acca50df3e00 - role: user - status: completed - type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. - type: input_text - id: msg_0d2fb655988faaf0006adb2dbb4aec87d085485737deeb5175 + id: msg_00865c2538464a9c006adb3903ebc087d08ac20d6e11c6f3ff role: user status: completed type: message - first_id: msg_0d2fb655988faaf0006adb2dbb4a8487d0a80c3f37865b09de + first_id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 has_more: false - last_id: msg_0d2fb655988faaf0006adb2dbb4aec87d085485737deeb5175 + last_id: msg_00865c2538464a9c006adb3903ebc087d08ac20d6e11c6f3ff object: list headers: content-type: application/json status_code: 200 -- filename: t31 +- filename: t30 request: body: input: - - content: 'The local shell environment is Linux bash. Use the shell tool exactly - once with this exact action, preserving the commands array and its order: - {"commands": ["printf ''SHELL_OK\\n''"], "timeout_ms": 1000, "max_output_length": - 4096}. Wait for the client to return shell_call_output before interpreting - the results.' - role: user - - content: [] - encrypted_content: gAAAAABqn5R-qMzrhpfY55yyGL_98KwKb54soVWwMvEcVuzUxeLAGaxmxNMZ7qtzDWHmWx1WHDxR8qhZHt7FnPZJqgJaT7GkkDihHYAFGBzLvtRmt5XKSyvBkHpfHloYXXCHOIkgmOVBXuDghceosCIQT7FRjVBSOQBrEwfE9byvQWzoF2W8CpgIgWhFB_4CZ4ENoXJpVdl-ucaIEUqfFlqDnxIfjEI7ktZDf9nBDfGxHXBsYVrlyJ_XCZ5PyZ_keVi5daXuwMeps9-qDjzQmIg6PqIPBIeNDqTnyE5KZxLJTa1liAxOt6UvijBCg-tmbbMEqI19AE-L12-mvKCFzbmRx1ZHhqzaafmDSz5zIdJ4EGXYS-O1_PLRQgMus5Dd2ffQT9BBsHaIoTseZlftGiQ_AFk_Inf3lOgSKdCQ95_a2-KpVd8OntZgfpIlNcI3miJSOAE9LB9BcZ7MeOChEDN5nBhcWVooLaY8WGBhJN2KaGbxVfTgPcZnKVHEd8DeYgZS0P3iJktA6IXKo7qyicPadQsSZeq6rIuErbI_a_S-AJdKNhouRB1UF1R-Vxa7p5ZaifLNIHohOmq_TaYpS_KzFAOgZt2gxLr1dP7s3g-DSCTimQuN5H0wHQ2kEPWAmjc8nHTQUqvjsi9jvWoLxMHPkxXRjmupSaoSt3GS5Zm97J3Sv2UFtMIgblOisWchozi6C_7-rxn9L9LnUPoe83Cp6loAnmt21mGQ17JNj-OGjIIq95_PSuoDO05VNUKYUKwPFaMhha-0KxpNKBEIl7PN4XJT51PEkm4KRo6xKXr2WgkEk9vi8kO5cOs1HcQZ90dHicWEOEcDe4rwUMefbfxbrBto9u-C1GFWOWb9DihjwpasYkJ7jPAdVkFWqbNmYsTs5j-PSXUE-zE5bjSQ-eP-ozhqhITQlUnOUvkLn3ozAoiDr48a16JI-7OsWGj-Cs7iTFPxhGqQ9KhsoBlOkryx8alUfFqfMOK5zJY4TCxsmxxB_PQF7eFzjkstBRyxdNMukZFFd1o5LyIsCoN7nUKCgetuSefmq3NixbGOF8ylQTsTnzL8N6oXC_aG0m_TWqC9LE1otg2WiVwfg4cdhVbZq_RSA77jT1SBz9VZ8RLf9rZLYw5OOr6O0tfQuRH9tsROcFasYphDG0sZPAViJmUMQX270pR-1A5JtZheZOTbD98iu_g1tyyjpXYEGxHYXEruEyNw3FwtZ8craAvDhgBGyswM8Vy_U3Bqmg_Mh0QTAIWh0AVXRlFblx10Xm7rimxG1iwaQ-_GVzQriK2h53-Qj-i2YRcDBuqiGaoZ27aLVlKCsH0x795nadIPgLm9IDQq8e1jk5zyPfSvmAzLun2CXju88XU4wQ3iND4mvXpVo0Gyu63_pOeaowHZqH1oB8G6Kf7lsY-FUFTLsYfFTT_RB_07CITmyNthyIAE0fgUgofmVlPlRLQpq2GSMWvwJ12pmBRQHAyR1C4FcOCdid9iU3c4ToZaToOTdWh_aufHk7bZad14Do8S0c54lgqQC6N58H-1M9ZzR7cVET5_OnE_LSAAKNVrImGkGuh2CbJElSnYKRXOgP9NNOy0c-lDH1HkE7EwQUiPOfbB2Ca1FQF6Rb2IU4de-CNm8sA2bltAeMbaybn8KOsgMC8osnZI_xEgZAAulq2WeOzfihip0gBc_32r3NOcJg440LieuQdRxCVeDEMzwFJ7yamzuXygNR13eF0ytGm5yEH_dV7zb0c0SgAJC8VIskSFmFE3XuP4QKU5Ik6kWFb-TAeYeF-O3c39_ABg-9cmtMnuYF1miScU6vjFgDiTaAqt8sMRfVr-Zm18fBmCPQLV938-crLhPi5tscP1PH4q - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 - summary: [] - type: reasoning - - action: - commands: - - printf 'SHELL_OK\n' - max_output_length: 4096 - timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - environment: null - type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - max_output_length: 4096 - output: - - outcome: - exit_code: 0 - type: exit - stderr: '' - stdout: 'SHELL_OK - - ' - type: shell_call_output - - content: Use the shell output above without calling any more tools. For each - command, report its stdout, stderr, and exit code or timeout outcome in - order. + - content: Do not call tools again. Reply only OK. role: user type: message - - content: - - annotations: [] - logprobs: [] - text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ - \ *(empty)*\n - **exit code:** `0`" - type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 4096 model: gpt-5.6 + previous_response_id: resp_00865c2538464a9c006adb3903dfd887d0aeef4dcccbf01327 store: true stream: false + tool_choice: none + tools: + - environment: + type: local + type: shell headers: accept: '*/*' authorization: Bearer *** @@ -2808,14 +4177,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748990 - created_at: 1792748989 + completed_at: 1792751879 + created_at: 1792751879 error: null frequency_penalty: 0.0 - id: resp_0d2fb655988faaf0006adb2dbdb97487d0b1812a2c7f6ef70e + id: resp_00865c2538464a9c006adb39070a5887d0adbde2475b2f754a incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 4096 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -2827,14 +4196,14 @@ turns: logprobs: [] text: OK type: output_text - id: msg_0d2fb655988faaf0006adb2dbe7a5c87d09f60c3302658b4dd + id: msg_00865c2538464a9c006adb3907b78487d0b2f14a2db9401c60 phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_00865c2538464a9c006adb3903dfd887d0aeef4dcccbf01327 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -2851,7 +4220,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -2865,24 +4234,27 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - environment: + type: local + type: shell top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 345 + input_tokens: 558 input_tokens_details: cache_write_tokens: 0 cached_tokens: 0 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 350 + total_tokens: 563 user: null headers: content-type: application/json status_code: 200 -- filename: t32 +- filename: t31 request: body: {} headers: @@ -2890,7 +4262,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_0d2fb655988faaf0006adb2dbdb97487d0b1812a2c7f6ef70e/input_items + path: /v1/responses/resp_00865c2538464a9c006adb39070a5887d0adbde2475b2f754a/input_items query_params: limit: '100' order: asc @@ -2904,12 +4276,12 @@ turns: 4096}. Wait for the client to return shell_call_output before interpreting the results.' type: input_text - id: msg_0d2fb655988faaf0006adb2dbdc90487d097906ecab451ffa9 + id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 role: user status: completed type: message - content: [] - id: rs_0d2fb655988faaf0006a9f947c071c87d0add00f0d0c6155f6 + id: rs_00865c2538464a9c006adb39003df087d0a5c43cc4a399a190 summary: [] type: reasoning - action: @@ -2917,13 +4289,13 @@ turns: - printf 'SHELL_OK\n' max_output_length: 4096 timeout_ms: 1000 - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 + call_id: call_XLfMoSsfAoKWH1VGy8fcWglb environment: null - id: sh_0d2fb655988faaf0006adb2dbdc93c87d08eefa2e05e9b9150 - status: in_progress + id: sh_00865c2538464a9c006adb39017b7487d0a179d433561fe4da + status: completed type: shell_call - - call_id: call_oA7WtsxOcHnQE7xCPKcFmO23 - id: sho_0d2fb655988faaf0006adb2dbdc97c87d09985d9f947e1851b + - call_id: call_XLfMoSsfAoKWH1VGy8fcWglb + id: sho_00865c2538464a9c006adb3903eba487d09772108166174d21 max_output_length: null output: - outcome: @@ -2940,7 +4312,7 @@ turns: command, report its stdout, stderr, and exit code or timeout outcome in order. type: input_text - id: msg_0d2fb655988faaf0006adb2dbdc99087d081b9be311e6e5598 + id: msg_00865c2538464a9c006adb3903ebc087d08ac20d6e11c6f3ff role: user status: completed type: message @@ -2950,62 +4322,41 @@ turns: text: "1. `printf 'SHELL_OK\\n'`\n - **stdout:** `SHELL_OK`\n - **stderr:**\ \ *(empty)*\n - **exit code:** `0`" type: output_text - id: msg_0d2fb655988faaf0006a9f947f64b887d092916891b8f7c083 + id: msg_00865c2538464a9c006adb39047fb887d085b830d039821589 phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: Do not call tools again. Reply only OK. type: input_text - id: msg_0d2fb655988faaf0006adb2dbdc9a887d0a5c926b453dafb8e + id: msg_00865c2538464a9c006adb390714c087d0a9f29ace3ff1ab14 role: user status: completed type: message - first_id: msg_0d2fb655988faaf0006adb2dbdc90487d097906ecab451ffa9 + first_id: msg_00865c2538464a9c006adb38ff998087d09fd49b89b3a6d7d5 has_more: false - last_id: msg_0d2fb655988faaf0006adb2dbdc9a887d0a5c926b453dafb8e + last_id: msg_00865c2538464a9c006adb390714c087d0a9f29ace3ff1ab14 object: list headers: content-type: application/json status_code: 200 -- filename: t33 +- filename: t32 request: body: - input: - - content: You must call the agentic_raw_echo custom tool exactly once with - exactly CUSTOM_CASSETTE_OK as its raw text input. - role: user - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - status: completed - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - output: CUSTOM_CASSETTE_OUTPUT_OK - type: custom_tool_call_output - - content: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. - role: user - type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + input: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. After + it runs, answer exactly CODE_INTERPRETER_OK=385. + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: false store: true stream: false + tool_choice: required + tools: + - container: + type: auto + type: code_interpreter headers: accept: '*/*' authorization: Bearer *** @@ -3020,31 +4371,39 @@ turns: background: false billing: payer: developer - completed_at: 1792748993 - created_at: 1792748992 + completed_at: 1792751884 + created_at: 1792751882 error: null frequency_penalty: 0.0 - id: resp_01014eb0097f04f3006adb2dc09728819baac1adac0f700482 + id: resp_01bcbaf6cfee0657006adb3909fbe487d08551f2737f8cacf7 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: + - code: 'print(f"CODE_INTERPRETER_OK={sum(i*i for i in range(1, 11))}") + + ' + container_id: cntr_6adb390a65948198be981976260eb856091bae5c5de1f431 + id: ci_01bcbaf6cfee0657006adb390acdbc87d0b4f8864ca4572367 + outputs: null + status: completed + type: code_interpreter_call - content: - annotations: [] logprobs: [] - text: OK + text: CODE_INTERPRETER_OK=385 type: output_text - id: msg_01014eb0097f04f3006adb2dc15744819bbe0cf7a7e091c54f + id: msg_01bcbaf6cfee0657006adb390bee0087d089b9e1b5e50bb01a phase: final_answer role: assistant status: completed type: message - parallel_tool_calls: true + parallel_tool_calls: false presence_penalty: 0.0 previous_response_id: null prompt_cache_key: null @@ -3063,7 +4422,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: required tool_usage: image_gen: input_tokens: 0 @@ -3077,24 +4436,27 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - container: + type: auto + type: code_interpreter top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 134 + input_tokens: 1315 input_tokens_details: cache_write_tokens: 0 - cached_tokens: 0 - output_tokens: 5 + cached_tokens: 1143 + output_tokens: 43 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 139 + reasoning_tokens: 32 + total_tokens: 1358 user: null headers: content-type: application/json status_code: 200 -- filename: t34 +- filename: t33 request: body: {} headers: @@ -3102,7 +4464,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_01014eb0097f04f3006adb2dc09728819baac1adac0f700482/input_items + path: /v1/responses/resp_01bcbaf6cfee0657006adb3909fbe487d08551f2737f8cacf7/input_items query_params: limit: '100' order: asc @@ -3110,94 +4472,39 @@ turns: body: data: - content: - - text: You must call the agentic_raw_echo custom tool exactly once with exactly - CUSTOM_CASSETTE_OK as its raw text input. - type: input_text - id: msg_01014eb0097f04f3006adb2dc0d030819ba646d33b6ee18db3 - role: user - status: completed - type: message - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - status: completed - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctco_01014eb0097f04f3006adb2dc0d06c819b9406a9592450c766 - output: CUSTOM_CASSETTE_OUTPUT_OK - status: completed - type: custom_tool_call_output - - content: - - text: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. - type: input_text - id: msg_01014eb0097f04f3006adb2dc0d088819b9b7ae022bcf4108b - role: user - status: completed - type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. type: input_text - id: msg_01014eb0097f04f3006adb2dc0d0ac819bbd7ebfb1307fd2bf + id: msg_01bcbaf6cfee0657006adb390a12c487d08f8851d895ecc3ae role: user status: completed type: message - first_id: msg_01014eb0097f04f3006adb2dc0d030819ba646d33b6ee18db3 + first_id: msg_01bcbaf6cfee0657006adb390a12c487d08f8851d895ecc3ae has_more: false - last_id: msg_01014eb0097f04f3006adb2dc0d0ac819bbd7ebfb1307fd2bf + last_id: msg_01bcbaf6cfee0657006adb390a12c487d08f8851d895ecc3ae object: list headers: content-type: application/json status_code: 200 -- filename: t35 +- filename: t34 request: body: input: - - content: You must call the agentic_raw_echo custom tool exactly once with - exactly CUSTOM_CASSETTE_OK as its raw text input. - role: user - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - status: completed - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - output: CUSTOM_CASSETTE_OUTPUT_OK - type: custom_tool_call_output - - content: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + - content: Do not call tools again. Reply only OK. role: user type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + max_output_tokens: 2048 model: gpt-5.6 + parallel_tool_calls: false + previous_response_id: resp_01bcbaf6cfee0657006adb3909fbe487d08551f2737f8cacf7 store: true stream: false + tool_choice: none + tools: + - container: + type: auto + type: code_interpreter headers: accept: '*/*' authorization: Bearer *** @@ -3212,14 +4519,14 @@ turns: background: false billing: payer: developer - completed_at: 1792748996 - created_at: 1792748995 + completed_at: 1792751887 + created_at: 1792751886 error: null frequency_penalty: 0.0 - id: resp_01014eb0097f04f3006adb2dc34dc0819b859bc81611f5b439 + id: resp_01bcbaf6cfee0657006adb390ead4487d0a59983bebb064bcb incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 2048 max_tool_calls: null metadata: {} model: gpt-5.6-sol @@ -3231,14 +4538,14 @@ turns: logprobs: [] text: OK type: output_text - id: msg_01014eb0097f04f3006adb2dc3f85c819b82e96d0897b24d3b + id: msg_01bcbaf6cfee0657006adb390f5e7087d081decc7e31c2fba8 phase: final_answer role: assistant status: completed type: message - parallel_tool_calls: true + parallel_tool_calls: false presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_01bcbaf6cfee0657006adb3909fbe487d08551f2737f8cacf7 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -3255,7 +4562,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -3269,24 +4576,27 @@ turns: total_tokens: 0 web_search: num_requests: 0 - tools: [] + tools: + - container: + type: auto + type: code_interpreter top_logprobs: 0 top_p: 0.98 truncation: disabled usage: - input_tokens: 134 + input_tokens: 1331 input_tokens_details: - cache_write_tokens: 0 - cached_tokens: 0 + cache_write_tokens: 66 + cached_tokens: 1190 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 139 + total_tokens: 1336 user: null headers: content-type: application/json status_code: 200 -- filename: t36 +- filename: t35 request: body: {} headers: @@ -3294,7 +4604,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_01014eb0097f04f3006adb2dc34dc0819b859bc81611f5b439/input_items + path: /v1/responses/resp_01bcbaf6cfee0657006adb390ead4487d0a59983bebb064bcb/input_items query_params: limit: '100' order: asc @@ -3302,94 +4612,159 @@ turns: body: data: - content: - - text: You must call the agentic_raw_echo custom tool exactly once with exactly - CUSTOM_CASSETTE_OK as its raw text input. + - text: Use the python tool to calculate the sum of the squares of the integers + from 1 through 10. The code must print exactly CODE_INTERPRETER_OK=385. + After it runs, answer exactly CODE_INTERPRETER_OK=385. type: input_text - id: msg_01014eb0097f04f3006adb2dc369f8819b85b55bffd078e5ca + id: msg_01bcbaf6cfee0657006adb390a12c487d08f8851d895ecc3ae role: user status: completed type: message - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006adb2dc36a14819bb9937b6b4d5ceb4b - input: 'CUSTOM_CASSETTE_OK + - code: 'print(f"CODE_INTERPRETER_OK={sum(i*i for i in range(1, 11))}") ' - name: agentic_raw_echo - status: in_progress - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctco_01014eb0097f04f3006adb2dc36a30819bac7740c6a0ef2c31 - output: CUSTOM_CASSETTE_OUTPUT_OK - status: completed - type: custom_tool_call_output - - content: - - text: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. - type: input_text - id: msg_01014eb0097f04f3006adb2dc36a44819bb382685a81d5d24a - role: user + container_id: cntr_6adb390a65948198be981976260eb856091bae5c5de1f431 + id: ci_01bcbaf6cfee0657006adb390acdbc87d0b4f8864ca4572367 + outputs: null status: completed - type: message + type: code_interpreter_call - content: - annotations: [] logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK + text: CODE_INTERPRETER_OK=385 type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + id: msg_01bcbaf6cfee0657006adb390bee0087d089b9e1b5e50bb01a phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: Do not call tools again. Reply only OK. type: input_text - id: msg_01014eb0097f04f3006adb2dc36a64819bb9fca37be0224385 + id: msg_01bcbaf6cfee0657006adb390eb8ec87d097c592a090ed71f5 role: user status: completed type: message - first_id: msg_01014eb0097f04f3006adb2dc369f8819b85b55bffd078e5ca + first_id: msg_01bcbaf6cfee0657006adb390a12c487d08f8851d895ecc3ae has_more: false - last_id: msg_01014eb0097f04f3006adb2dc36a64819bb9fca37be0224385 + last_id: msg_01bcbaf6cfee0657006adb390eb8ec87d097c592a090ed71f5 object: list headers: content-type: application/json status_code: 200 -- filename: t37 +- filename: t36 request: body: - input: - - content: You must call the agentic_raw_echo custom tool exactly once with - exactly CUSTOM_CASSETTE_OK as its raw text input. - role: user - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - output: CUSTOM_CASSETTE_OUTPUT_OK - type: custom_tool_call_output - - content: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. - role: user - type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user + input: First call tool_search exactly once to find both a current-weather function + and a travel time-zone function. Do not call any other tool yet. + max_output_tokens: 4096 model: gpt-5.6 + parallel_tool_calls: false store: true stream: false + tool_choice: required + tools: + - description: Search the client tool catalog for tools that can satisfy the + request. + execution: client + parameters: + additionalProperties: false + properties: + query: + description: A concise description of the needed capabilities. + type: string + required: + - query + type: object + type: tool_search + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get the exchange rate between two currencies + name: get_exchange_rate + parameters: + additionalProperties: false + properties: + base: + type: string + quote: + type: string + required: + - base + - quote + type: object + strict: true + type: function + - defer_loading: true + description: Search for hotels in a city + name: search_hotels + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get latitude and longitude for a city + name: get_coordinates + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Calculate the distance between two cities + name: calculate_distance + parameters: + additionalProperties: false + properties: + destination: + type: string + origin: + type: string + required: + - origin + - destination + type: object + strict: true + type: function + type: namespace headers: accept: '*/*' authorization: Bearer *** @@ -3404,26 +4779,1728 @@ turns: background: false billing: payer: developer - completed_at: 1792748998 - created_at: 1792748998 + completed_at: 1792751891 + created_at: 1792751889 error: null frequency_penalty: 0.0 - id: resp_01014eb0097f04f3006adb2dc62acc819bbcfc572bf84f8e0b + id: resp_0e035120236cff10006adb3911a39887d0a306b7fcc32cb181 incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 4096 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: - - annotations: [] + - arguments: + query: Find both (1) a current weather function for a specified location + and (2) a travel time-zone function that can determine time zone or time + difference for a destination. Return relevant tools for both needs. + call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tsc_0e035120236cff10006adb391236dc87d0b67d59fa50fd0390 + status: completed + type: tool_search_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: required + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get the exchange rate between two currencies + name: get_exchange_rate + output_schema: null + parameters: + additionalProperties: false + properties: + base: + type: string + quote: + type: string + required: + - base + - quote + type: object + strict: true + type: function + - defer_loading: true + description: Search for hotels in a city + name: search_hotels + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Search the client tool catalog for tools that can satisfy the + request. + execution: client + parameters: + additionalProperties: false + properties: + query: + description: A concise description of the needed capabilities. + type: string + required: + - query + type: object + type: tool_search + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Get latitude and longitude for a city + name: get_coordinates + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - defer_loading: true + description: Calculate the distance between two cities + name: calculate_distance + output_schema: null + parameters: + additionalProperties: false + properties: + destination: + type: string + origin: + type: string + required: + - origin + - destination + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 166 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 60 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 226 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t37 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e035120236cff10006adb3911a39887d0a306b7fcc32cb181/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + role: user + status: completed + type: message + first_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + has_more: false + last_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t38 + request: + body: + input: + - call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + role: user + type: message + max_output_tokens: 4096 + model: gpt-5.6 + parallel_tool_calls: false + previous_response_id: resp_0e035120236cff10006adb3911a39887d0a306b7fcc32cb181 + store: true + stream: false + tool_choice: + name: get_weather + type: function + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751894 + created_at: 1792751893 + error: null + frequency_penalty: 0.0 + id: resp_0e035120236cff10006adb3915969487d0b245c858158c0259 + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - arguments: '{"city":"Paris"}' + call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3916730c87d09559504da4475039 + name: get_weather + status: completed + type: function_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_0e035120236cff10006adb3911a39887d0a306b7fcc32cb181 + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: + name: get_weather + type: function + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 309 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 18 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 327 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t39 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e035120236cff10006adb3915969487d0b245c858158c0259/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + role: user + status: completed + type: message + - arguments: + query: Find both (1) a current weather function for a specified location + and (2) a travel time-zone function that can determine time zone or time + difference for a destination. Return relevant tools for both needs. + call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tsc_0e035120236cff10006adb391236dc87d0b67d59fa50fd0390 + status: completed + type: tool_search_call + - call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tso_0e035120236cff10006adb3915a05487d0a761af95676687af + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_0e035120236cff10006adb3915a07887d0ac12823a297dc92e + role: user + status: completed + type: message + first_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + has_more: false + last_id: msg_0e035120236cff10006adb3915a07887d0ac12823a297dc92e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t40 + request: + body: + input: + - call_id: call_5wX9unmBE5JnciJb2IxcROge + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + type: function_call_output + - content: Now call the loaded travel namespace member get_timezone exactly + once with {"city":"Paris"}. Do not call any other tool. + role: user + type: message + max_output_tokens: 4096 + model: gpt-5.6 + parallel_tool_calls: false + previous_response_id: resp_0e035120236cff10006adb3915969487d0b245c858158c0259 + store: true + stream: false + tool_choice: auto + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751898 + created_at: 1792751897 + error: null + frequency_penalty: 0.0 + id: resp_0e035120236cff10006adb39199d5487d0aab42c6312dac41c + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - arguments: '{"city":"Paris"}' + call_id: call_fPY4IUstfYwpfd9zYZCKoenA + id: fc_0e035120236cff10006adb391a9bcc87d09c79e144ade2e145 + name: get_timezone + namespace: travel + status: completed + type: function_call + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_0e035120236cff10006adb3915969487d0b245c858158c0259 + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 381 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 18 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 399 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t41 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e035120236cff10006adb39199d5487d0aab42c6312dac41c/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + role: user + status: completed + type: message + - arguments: + query: Find both (1) a current weather function for a specified location + and (2) a travel time-zone function that can determine time zone or time + difference for a destination. Return relevant tools for both needs. + call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tsc_0e035120236cff10006adb391236dc87d0b67d59fa50fd0390 + status: completed + type: tool_search_call + - call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tso_0e035120236cff10006adb3915a05487d0a761af95676687af + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_0e035120236cff10006adb3915a07887d0ac12823a297dc92e + role: user + status: completed + type: message + - arguments: '{"city":"Paris"}' + call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3916730c87d09559504da4475039 + name: get_weather + status: completed + type: function_call + - call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3919a68c87d09c86701c2b8f101c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_0e035120236cff10006adb3919a6a487d0bfbc62b38f51e51e + role: user + status: completed + type: message + first_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + has_more: false + last_id: msg_0e035120236cff10006adb3919a6a487d0bfbc62b38f51e51e + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t42 + request: + body: + input: + - call_id: call_fPY4IUstfYwpfd9zYZCKoenA + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + type: function_call_output + - content: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + role: user + type: message + max_output_tokens: 4096 + model: gpt-5.6 + parallel_tool_calls: false + previous_response_id: resp_0e035120236cff10006adb39199d5487d0aab42c6312dac41c + store: true + stream: false + tool_choice: none + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751903 + created_at: 1792751902 + error: null + frequency_penalty: 0.0 + id: resp_0e035120236cff10006adb391e29c087d0917e2756afc3bf1a + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: PARIS_MIXED_TOOLS_OK + type: output_text + id: msg_0e035120236cff10006adb391ed0f087d0a82003ad1338ff29 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_0e035120236cff10006adb39199d5487d0aab42c6312dac41c + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: none + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 448 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 12 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 460 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t43 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e035120236cff10006adb391e29c087d0917e2756afc3bf1a/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + role: user + status: completed + type: message + - arguments: + query: Find both (1) a current weather function for a specified location + and (2) a travel time-zone function that can determine time zone or time + difference for a destination. Return relevant tools for both needs. + call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tsc_0e035120236cff10006adb391236dc87d0b67d59fa50fd0390 + status: completed + type: tool_search_call + - call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tso_0e035120236cff10006adb3915a05487d0a761af95676687af + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_0e035120236cff10006adb3915a07887d0ac12823a297dc92e + role: user + status: completed + type: message + - arguments: '{"city":"Paris"}' + call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3916730c87d09559504da4475039 + name: get_weather + status: completed + type: function_call + - call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3919a68c87d09c86701c2b8f101c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_0e035120236cff10006adb3919a6a487d0bfbc62b38f51e51e + role: user + status: completed + type: message + - arguments: '{"city":"Paris"}' + call_id: call_fPY4IUstfYwpfd9zYZCKoenA + id: fc_0e035120236cff10006adb391a9bcc87d09c79e144ade2e145 + name: get_timezone + namespace: travel + status: completed + type: function_call + - call_id: call_fPY4IUstfYwpfd9zYZCKoenA + id: fc_0e035120236cff10006adb391e33e087d0a5a484cd7035ecda + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + status: completed + type: function_call_output + - content: + - text: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + type: input_text + id: msg_0e035120236cff10006adb391e340087d09e5c68d3001f7aa4 + role: user + status: completed + type: message + first_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + has_more: false + last_id: msg_0e035120236cff10006adb391e340087d09e5c68d3001f7aa4 + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t44 + request: + body: + input: + - content: Do not call tools again. Reply only OK. + role: user + type: message + max_output_tokens: 4096 + model: gpt-5.6 + parallel_tool_calls: false + previous_response_id: resp_0e035120236cff10006adb391e29c087d0917e2756afc3bf1a + store: true + stream: false + tool_choice: none + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751906 + created_at: 1792751905 + error: null + frequency_penalty: 0.0 + id: resp_0e035120236cff10006adb3921a9f887d099e4209cda635d38 + incomplete_details: null + instructions: null + max_output_tokens: 4096 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: OK + type: output_text + id: msg_0e035120236cff10006adb39229e9c87d0a57ff6f659a0bcb6 + phase: final_answer + role: assistant + status: completed + type: message + parallel_tool_calls: false + presence_penalty: 0.0 + previous_response_id: resp_0e035120236cff10006adb391e29c087d0917e2756afc3bf1a + prompt_cache_key: null + prompt_cache_retention: 24h + reasoning: + context: all_turns + effort: medium + mode: standard + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: none + tool_usage: + image_gen: + input_tokens: 0 + input_tokens_details: + image_tokens: 0 + text_tokens: 0 + output_tokens: 0 + output_tokens_details: + image_tokens: 0 + text_tokens: 0 + total_tokens: 0 + web_search: + num_requests: 0 + tools: + - description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + top_logprobs: 0 + top_p: 0.98 + truncation: disabled + usage: + input_tokens: 476 + input_tokens_details: + cache_write_tokens: 0 + cached_tokens: 0 + output_tokens: 5 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 481 + user: null + headers: + content-type: application/json + status_code: 200 +- filename: t45 + request: + body: {} + headers: + accept: '*/*' + authorization: Bearer *** + user-agent: python-httpx/0.28.1 + method: GET + path: /v1/responses/resp_0e035120236cff10006adb3921a9f887d099e4209cda635d38/input_items + query_params: + limit: '100' + order: asc + response: + body: + data: + - content: + - text: First call tool_search exactly once to find both a current-weather + function and a travel time-zone function. Do not call any other tool yet. + type: input_text + id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + role: user + status: completed + type: message + - arguments: + query: Find both (1) a current weather function for a specified location + and (2) a travel time-zone function that can determine time zone or time + difference for a destination. Return relevant tools for both needs. + call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tsc_0e035120236cff10006adb391236dc87d0b67d59fa50fd0390 + status: completed + type: tool_search_call + - call_id: call_DNaw1Lxs7wTkXj99MX2XhYLp + execution: client + id: tso_0e035120236cff10006adb3915a05487d0a761af95676687af + status: completed + tools: + - defer_loading: true + description: Get the current weather for a city + name: get_weather + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + - description: Travel location tools + name: travel + tools: + - defer_loading: true + description: Get the IANA time zone for a city + name: get_timezone + output_schema: null + parameters: + additionalProperties: false + properties: + city: + type: string + required: + - city + type: object + strict: true + type: function + type: namespace + type: tool_search_output + - content: + - text: Now call get_weather exactly once with {"city":"Paris"}. Do not call + any other tool. + type: input_text + id: msg_0e035120236cff10006adb3915a07887d0ac12823a297dc92e + role: user + status: completed + type: message + - arguments: '{"city":"Paris"}' + call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3916730c87d09559504da4475039 + name: get_weather + status: completed + type: function_call + - call_id: call_5wX9unmBE5JnciJb2IxcROge + id: fc_0e035120236cff10006adb3919a68c87d09c86701c2b8f101c + output: '{"city":"Paris","condition":"clear","temperature_c":21}' + status: completed + type: function_call_output + - content: + - text: Now call the loaded travel namespace member get_timezone exactly once + with {"city":"Paris"}. Do not call any other tool. + type: input_text + id: msg_0e035120236cff10006adb3919a6a487d0bfbc62b38f51e51e + role: user + status: completed + type: message + - arguments: '{"city":"Paris"}' + call_id: call_fPY4IUstfYwpfd9zYZCKoenA + id: fc_0e035120236cff10006adb391a9bcc87d09c79e144ade2e145 + name: get_timezone + namespace: travel + status: completed + type: function_call + - call_id: call_fPY4IUstfYwpfd9zYZCKoenA + id: fc_0e035120236cff10006adb391e33e087d0a5a484cd7035ecda + output: '{"city":"Paris","iana_timezone":"Europe/Paris"}' + status: completed + type: function_call_output + - content: + - text: Use both function outputs and call no more tools. Reply with exactly + PARIS_MIXED_TOOLS_OK. + type: input_text + id: msg_0e035120236cff10006adb391e340087d09e5c68d3001f7aa4 + role: user + status: completed + type: message + - content: + - annotations: [] + logprobs: [] + text: PARIS_MIXED_TOOLS_OK + type: output_text + id: msg_0e035120236cff10006adb391ed0f087d0a82003ad1338ff29 + phase: final_answer + role: assistant + status: completed + type: message + - content: + - text: Do not call tools again. Reply only OK. + type: input_text + id: msg_0e035120236cff10006adb3921b8d887d0bcec1fe8c006df5f + role: user + status: completed + type: message + first_id: msg_0e035120236cff10006adb3911b09887d0accb43c387c44988 + has_more: false + last_id: msg_0e035120236cff10006adb3921b8d887d0bcec1fe8c006df5f + object: list + headers: + content-type: application/json + status_code: 200 +- filename: t46 + request: + body: + input: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains these + lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, payload):; + 4: if user_id not in CACHE:; 5: CACHE[user_id] = pickle.loads(payload); + 6: return CACHE[user_id]. The function accepts payload bytes from an unauthenticated + HTTP request, caches profiles without expiration, and returns the cached mutable + dictionary directly. The only existing test loads one valid payload for one + user.' + max_output_tokens: 16384 + model: gpt-5.6-sol + multi_agent: + enabled: true + max_concurrent_subagents: 3 + store: true + stream: false + headers: + accept: '*/*' + authorization: Bearer *** + content-type: application/json + openai-beta: responses_multi_agent=v1 + user-agent: python-httpx/0.28.1 + method: POST + path: /v1/responses + query_params: {} + response: + body: + access_programs: null + background: false + billing: + payer: developer + completed_at: 1792751947 + created_at: 1792751909 + error: null + frequency_penalty: 0.0 + id: resp_0728b898e06dffcb006adb39258f4c87d0ad23c42f61a95c20 + incomplete_details: null + instructions: null + max_output_tokens: 16384 + max_tool_calls: null + metadata: {} + model: gpt-5.6-sol + moderation: null + object: response + output: + - agent: + agent_name: /root + content: [] + encrypted_content: gAAAAABq2zlM401z9tQS4a90MO-P69UnWWipN9dAx_2TIRliLJ5lXvRS_jr7xMeLz64xZsv7BjSc69wBXc2_aLD3uZepd0I08SfbXqfy9gRoODwJ9Ue2Q85TMmcaiIYlCoIXxBDpmHFw6R2bypXU5Wz0LaQRpe8Sm50rzos2mZIB0xdv_IwaI5Di4i5S0f8cSaUB6u6Dsi73JllW3XK9Tum1E-5QkVnspYogtcWrizvfCFDjL4w7C2pS-vtX4x0hz8XaycNYgveQDBXMfNZNl3sipR0eEZDDxX37zwcj4BTI5p6-qkRQIO5BIqrhcnL7Tv4ykBlr6OgQ7u-NPMBi6MJpcscT7jpW4S1sKv4xPInlwdiX1kZsF6QO8ySlwITAtVvnQrjzqxVKoyxvEMbIqOJmf6TigM09xhbcmkl99e305Uz950w4cjMYzgkhbGp5_mCdxGRILeB0g0TSLPNWPjVTXy4eLjG7b-gWm5hMccUmHnwU4LmVYekPPQtBzXnap94d8mBxSzRDT7EKoGZYPDnY4BhiOlnPQZutSA-t7lq2NADTxfwSlvUBwx0uK9SFtBvChycyNhBMdGE8ogCcYy8R2QRxXOjtMckXlI5ACZ32twCBuJSoNBeRBPKAIPH6IUrjgaA-RZ5XugAOf5ZzKyoWc1xuzWbzphtC0PXJ6NMBU4J8wdij_-xa94BQuu7PJGM8QzlqDGVZJUx_7KmgC-9v_LJwS7qj5Hi_4rFM0HaMfT3RPeqYTTFc-wZNpzscdsEHDIzqaIR7jqdx85ZCAV1xvRzDSQno4sVp92PiCV0_kk9a7sAm11BcX0s19EM4EzjH5Y4Xn3MAhwTVNVmjK_VHSWhx43U8o1RCV6jPRZpsNrT1ZwsIUiyxVuNfFW_JlcBKToFyCwD8P5Z49aN9QUyVVCkD2gB9FxO-TGXakaEV4Ly_bRJ3LGgjtmjxAz2nU_pctHotjq_1B1r3eClU3JBFNGOncjY9mycZMqCQy-Af6aDG0h6TuDcnrWVMP6RmP-R3P-z4a9rcy5Lc_KMOTvyp3D6mkWey4E_pFL5YJ4vspRiR9z5CvWJ1vSpiVJWiuUx9DH93WzjsZd9H5bSsgpxFhYU6lsBYqa_yyQZ4WouWm_hSd3BohuY25pQ9IZpeQoC_Nw23e3D5d_M5GPBYWpz4sUCqRWUu9WcAvDwfiiPSzmc9nzR4eCfqRVoVJZo5GpTnp3PUe539Oage1wfCCzbKMvx-q9QSc9MuekXEveoi_MilroXdgYs7R3utEWdY_uzh2EknjluhDWlsIaxnpWs9NLlW7CZVeDkdAcNumi7HcrhOQUapRkHAN05UkU1jvTRgRnRP9Uimh5JG9VuWXDEeO8FqB2SdO2uUHdQZ6syXzupK8y9JBUoUbnEL6GTna-kfXMrIFna3D-NtSYwnjP-EobFfCESGAhb31_I_eF3BpFIH8YhZe3TUtAoOt2ktvGM6bPMwuPvG4lS0Wap-2vAKuuSlSNbLPR3QBwDVryQM24Lcwcc-hvzsHVdDMZnM2QWslaDE5kpzZ7uyxX91_w7_paFuLmTq6gUdbWE7CNBr0EMpDAzhpApQOqie2s4Y1DVRDKfuZ3ppEPXEDSMqMXr9v4cPOtgNSYvb6Um8-htYBsc7_m5cIu6NsTxmSiHMcXEyfMzH05yWepua3yRHL_82VSFDpR5UwcAapk4CELMY5TvyB73DZu1xmSSKfvbJl9GvY_lUzH1SAds0Iko_JESrEWYKIjSgtw== + id: rs_0728b898e06dffcb006adb39269b8c87d0ac8a0d890240ef0d + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"correctness_review","message":"gAAAAABq2zknHsxGichM95AQ5cTbugSDz7fRYR4L7MGXsAq4EHmFVajchfnHxYgRjMue7v_AHmHhurrxo1INngNkTcRjGQcTjqisK3V87FCWUkmo4Gyfdlxr5bx_t4J_Ek87mx-fG9ze0EP9NqUy3_eVAgXyaz6mZXxjt7z9XwBH19o-oLm-jXEnyJSipOBp_IPDzFz4AGyUjxy1ycqDwt_3Gh1kSQNgAmM54_gs04fSmqH6zSL4tH3nukUj62K8dBaahEQ9S66uGI75h5Kegf0JlyZTyXKmeHwpr-YTnVYrh7WSi0FA07Q20UkqVXhbfxe_BFyNbUx_sZvv7F9YNffOdLpWfBuoUsCadk1iIV7aWon9DepT6uU7DiX1k3WWMtkjPV7RDW4_eIhtMQy0aCNI2r3Lr2UfXg==","fork_turns":"all"}' + call_id: call_FuDavN3956eBtvCyLaIZmFBb + id: mac_0728b898e06dffcb006adb3926fa7087d0add620154a8e5964 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_0728b898e06dffcb006adb39279a5887d0b6c976e1977acb69 + role: user + status: completed + type: message + - agent: + agent_name: /root/correctness_review + author: /root + content: + - encrypted_content: gAAAAABq2zknrtdcnjiGht4ds5zqh8N7_b1v3QcFvd0NzdEBzXyCMYY_oqDaLc_OfcYEDvYzCjfAyzs4v_3B40r60TOq-DxDHI5AC1rnR276fROFJECNykUcVFXeW7OW64r8H2vilVk00MwKAGyWAuF2uooTwX758nLa1i8-vdPnlR2AqC3b0h-ReHR-SlgWBQiNaj584lE8IOFMrR1DTOloo7Fk0rb9EAGaVYSM7Wb06iboeY9GPy4-SJkkfSC5DwXAtMgTnhlK-1LSt_uMf0QC8QZvPecsJmRYDpELrg6FT_Z1sFhJqqzI2-fcbpmI2O_gXslcfBpRt_Gew9wT4KA7d-VBUU37DMAYz7byaYbEBxxr8SJ-E82w7il4oW9jbHQWnhUSXSE6YeHj94ugp4VaIcQiLG4aBXlmdxIphHxgSKF5cei4jFjg5UAJLNwhLZ4Xyc-QH7NVrCvpaxWHlhhY84B_7jrioxxCjGuijRwJHU7WYezKQKDn5JAOtpQD_sTBPTPlI4LmeT1nBUrS8yF9xGjO-Db2Dg== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb39279bc087d0978eaefccd007f17 + recipient: /root/correctness_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_FuDavN3956eBtvCyLaIZmFBb + id: maco_0728b898e06dffcb006adb3927a6ec87d09a6fd459acccd732 + output: + - annotations: [] logprobs: [] - text: OK + text: '{"task_name": "/root/correctness_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/correctness_review + content: [] + encrypted_content: gAAAAABq2zlMZramnBjfcJ9qKjClB8yrS_thlQQ6EXwpcFcIJwXyl5QqK7g2FoycvJlCbOGMyvyOIea2JB6ul1eCvIOLL5R97F2O1bcYp4Jn_z_a2DowzYnUa9Ti0Xrjb3KbAxhEuOmcCoSUpOQa0B1sWj5vvn5-hZ-pSZ5uDDfgNdquq3XxDBdi2udifumgmAXE1pbVBnno7TT3g4MD_0Jxj7JrJvFJofJLmGTfs7RqotBToiMqRiXTQgVvDK2Zwr44L_80d1TvUHAy8iBBxDB6oeBf3eA8eGL0XTdYSxBWdx363F1wqC9DWeyhEdlvcjzJPVEnp4iRQoR1yE864oKX6vb2hEJxv8jMP7yhTz6gVDfZwe3LtYNnZEOd4v4IeGU5poYHdyN_5_vjJaW8tTjLrD1xdLiF2aHmnjr9Xe0_JQO4vXEe4VfDhJ3AXCWNbfxxD8XWCzmRnLYHaJ-WHnOcr7cKJNyxTkkEkSLkePuOFiia13ed2ZCx6OwTTuKp17XmTv83brI3SMjyAvzdKDx2qQBIutqlyNPDBo0bQy_T_X9CPf4edbVdWfoFAWNUI2U2N9-fV4o2sFsWKxS04RUbA_u8gXXgraC_tD7sUKn3kZgH7SCGSoS17KfFYRMuXuQFOmnwKyM3jSO24Cy9vdUOADWqroMQ7JFNnmVVP0l7oGlBRiZuMeFFVyeawd754luTyMzS2X5OQ9QQJ4os__9_vbV92sRSHCSFI0ZqXH_NmTFT-FUyRK8TYzFssCW6DnjIDiilU9Bg-1RjDOPr3_PLIifVCzbBIP87WWBd8u772iSmz-NkcXyEwgoz59KSHEbDzz8Dl7e4aIyyNU6QDZutAmmu57Hfr2cz1-qRdkp39TVONnWfb_ak7iTZ-ldTgLhyLhnQj-jnX7aWPz9JdJZmby3QoCDJ6_7EliCcrhjCJxgVS_lD4Jp9aiYdu03ngyH5IS-J-J_Y29dTfQ1r7OjUOXfP9qZ3blIABslhHu0nd4Hg-5Y0AT8CnpOiqiH4kClyEH4wof7Br5m9nwdpi3nunWm7btDhzbgC2KVR6Fc5qH1OscSUF2qBFXOu8FDUy1eYh4_fPgh7hDDsXwjf03ftNdMmt0iu58aOvAq6p6p7TuPBCBKQCAL49fl73yXcmA3jWbQ7kIWhpG9Vc0JifVz6g6zcoDBNjkv8cWNAwUybi4rjR_SAt5ob9xmzesYDBFGdJ7RpnuNV-D-2YujnuZnh4G0Gv5fS0MbIiTdsiUwZPU_dNjkWCLZVd0r6zO984q1t9sG857Pmw48PAp7uXqSbxJgLaN0ccOT-9GIp8CeMw8eLT5DAIvWGZ8-rmEay9atezEUGIPcvlWaB2Wq9BpsFMun0xuyaTZcd0nCtIN5tbQY_-pdGxXgQ7Uu6jKkR2JhaqlKPO5x7gSGqzlVVerpBd-i3jH8yWkjySZ-75srY2OosvukFX04aI-JnmZg9KEnk14RyOI1unaDkZolWdo3jh2FHl-ZnF04xCrbadjXamNT8iiGz4htbqUh5ZuDyIdDJaeTkNVORM06v7igxtpZpuewbhRBIusxy8GHg5ZFL-F2CWCsL2JmXI-wlkMs8GgA_rBkODXIjwxXRndEXDsT8g4GzaGxI2LIARmDOH2Q8BaiQjrJoUP7RbAaNDqI8gn-zFCbzZCUpgqbTTGiAXS00tp3tM3K9xgx4KOf92sj3IstPd55QE1T38gLxmsKfXy1HWfiCfjStsnskWCUTYkmBJHLDDKqCF6XOdJdgTEdkaQ9eak93uqBcO3FsF9mnvWi_V7l4GT7ErxYWtm9KPohaUQ-2gw0spddxVgKQ7ROPxVglvwjGgD4eJEmGK7ckYlsjXQyu0snUIRBBExmvFTDNEhJ_hqt97BB6aVlU55f4y9ZSId5hHvg-Ax5zYjvruMmGkpobqNSJAwhf3ID_3jq2sLnBGBNJQpj6IXeVs6qIgxGv08DcVasf8Vadik1i-1HF9cvfbUuXI9vUL7cGoyRiGcBDdgiBrCn08uvRTssfMyofQVmGgevGT7CwadcHJGCziY3M_WaelR37tbVMpk4OhACr7EtTusYKS5tdRZkOjWRYmoDicGFxLVQc-3gPV9BS84FDzSpPMxTvZ7X4Du_XIPpc7ewKQt13XPX99FHTz7h6apfRK_T24K_hhmWTKDD65PvEIOD_xOD7SZdSy_qCgpGl3JVvgwdN6KguRWxUH9khfSVguhihoch5W_pvWBmw4rV8WcV43l4I2BfgUwqMLXz6w2U21Vl0GNOO3f1T7sHrdxkQooFtMcKEbueosI-TBnOo8WbocHOgZmf84Le3gDBMrrN81aMPyUKp55_WM-rT3tQY2b-lD4wcc38ihYLOYQXSOmvxU1CuhGKlPJC-k9io_yMs7-d3UY0q-uBVVDL7D8wNB4HzIEtOIJAOiFoA-KCzI2HK-sXV4EaJSmdmF-dkM5m6F-PYYNUMzQun0oPkRXD345J0smrUdM3wyd4gsqQ09HysG0OzeuWn48xD6IUQ2SjNbt0iqvo2fTnLITvbBZHXjoPoUTDws364--HzqoHa-yEhwsPytBKc7sw_0gVJLpnKuNsKcil0q0wsnaz7-xvy9rbzHPWyoKrVj5iQ9mlN-uUIrkKoRJZNM_Y17UnaNTY-U06JKMJLFqDCIPQTiC9zfnWQjBZWFjKyyW8tLOo06bs-ndA2u7lMmAaNs2EkVicTTjHuy-U0_0OiFvfhwkc5h2doXLimnHrfQxTZBQXqPr47sFPSbLFFX3NEqA9M0VTpw5IR7vnXncqbovWrKhfT9sFJ6tMzqe0T8lOTuD7QWhm9atWdHMmH6PMxDXRdmW7XeysZeFb_On3A1cA6zN6GSqxRdNBZ5De7qpO9kUlnjawx9UVd_dzo5E8dCi7duS7fcRoSC4H48aB0oeUV1SkyVIT8zJCInEIwrHlsWVie45cRTiCz5ZeMo05TVTyB7oWjxaJyvVyPGYO8e1LR4lAZGFjb-9_g7wTXB8skCE8kg7anT33kFZWwI8fbjarp-Fp33S6Z55EUnqtiaW7ujj879xS4aLRhVJSR-1vQdHRbKcPOoFlYR5SkAz8lqnUjo3zpF7vlYj4Df2mqN_7y5LYxw6u22U8Kl_LNKMORfJh6NPUOHDPy7wC6nWwf6FM-u7t6lmZ6WV2Ot9BNt3S_7kBcrUnuijISdQNLlxIGRLq6zQ4CSHvsiQBXH1tHCLetGefxCiBEI9SUthsdP8m6cqQDlR-FZM3ZFxgMLVEwa-ptOIVq5Dozec1JpvnVwvRmLFDoOsx0nbqdmsNINomXvbPOUKW5W_CBPnzYFUnoiOHO1U5Z7QMi-GGr9NZqdiiy6icFLbNEigxnAZbk-teKP2YFYq9BdJwum425BjMHo7-yIW2pXsFj5xGagA5aNGLnY0pMLr5-JivCEHqap4vkDJRbP1LokZeYhV19r3WFdLGz5K2vccTzAllItEVge0VlfpjSdrd2znCg2aDzzYhETL5bGVjYxVevBfIFE4V2u1aGNc3yuS5IuOHkdC8o9yvwkNGB-jmAvnOzxvpHr56n6OdT-VfduOJ7CchR10RsErYyN3AJ5Mjlepecc3EviV-j_kxB-dkkN44btDb4aEqp6-1Ku9RrdTnEMcJ-UuGGvLVKeNGgo-p8nGQuZGy5Jlx1qBa7sGG5khbaQOPHnDI_sSRVoP6rc5kRgz7pC9tVu2daNR_bNdbFdTs1qXCXtCXedQuJqQOTlSJY996-7A8T5zNde-2EKRAu0X7YnBIGHgUGDrfCVUXZTaJQVhJ-MB81lQXAkGPUxM_5IBF__9nOJUnnEw5BdNaXIvn5PyxVzhF5062DKzS4MINeGm5-Vg4FLtAplElU8U88mmVt2-XRJzBUBv1vaNOume3FwlLQqMtA9xb5WCRbfe3LWwHhi8BU-QioeoxB0NdhJeCXL4Iao3V1xG23DUrkjAax3xFNwn26EmR5W_6TimQBXZe30HXAREZ3YO3o4DnhqDp7nqBAHTql3TP1kXGRcapmnNQ3tSoll82po0_5aQ9gufBNnWdgu8jkcEmzx9qqA_n0q6_ByOHN6_r_ChQTgddMJP7Fj2o2LZ8Md0vIIOEGvmFCOt67sbPpyc961WdFj4aLyUY0z-y-QRg1oJnrPAYuW-bUjRTq3E5n-STtca1no2yLlpRIe754jHXM2BKCF10_2-FEBiCs8fo= + id: rs_0728b898e06dffcb006adb392838b087d09219230d65443951 + summary: [] + type: reasoning + - agent: + agent_name: /root/correctness_review + content: + - annotations: [] + logprobs: [] + text: '### Correctness/reliability findings + + + - **High — stale or incorrect profile returned (lines 4–6):** Cache identity + is only `user_id`; after the first load, every later `payload` for that + user is silently ignored. Profile updates, corrected payloads, or reused + identifiers return old data indefinitely. **Fix:** define invalidation/refresh + semantics, use a payload/version-aware key, or replace the entry when + authoritative data changes. + + + - **High — shared cache corruption through mutable return value (line + 6):** Returning the cached dictionary directly lets any caller mutate + the canonical cached object. Later requests then observe unintended changes, + and concurrent callers can interfere. **Fix:** return a defensive copy/deep + copy or expose an immutable profile representation. + + + - **High — check-then-set race (lines 4–5):** Concurrent requests for + the same uncached user can both deserialize different payloads and overwrite + each other. A request may cache one profile but return another depending + on timing. **Fix:** synchronize lookup/population per key or use an atomic + cache API; also specify how conflicting payloads are resolved. + + + - **Medium — unbounded, permanent process memory growth (lines 2, 5):** + Every distinct `user_id` remains in `CACHE` forever, causing stale data + and eventual memory exhaustion/restarts under sustained traffic. **Fix:** + use a bounded cache with TTL/LRU eviction and explicit invalidation. + + + - **Medium — no validation of decoded object (line 5):** `pickle.loads` + may produce a non-dictionary or a dictionary missing required profile + fields, violating the function’s apparent return contract and moving failures + downstream. **Fix:** validate type/schema before insertion and reject + invalid profiles. + + + - **Medium — malformed payload failures are unmanaged (line 5):** Invalid/truncated + bytes raise deserialization exceptions directly, likely becoming HTTP + 500 responses unless handled elsewhere. **Fix:** catch expected decode + errors, return a controlled client error, and never populate the cache + on failure. + + + - **Medium — global cache is process-local (line 2):** Multi-worker deployments + can return different profiles for the same user, and all entries disappear + on restart. **Fix:** if consistency/persistence is required, use a shared + cache or backing store; otherwise document these semantics.' + type: output_text + id: msg_0728b898e06dffcb006adb392b70c887d0b21da0f74784b1d1 + phase: final_answer + role: assistant + status: completed + type: message + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"security_review","message":"gAAAAABq2zku4hAewQpr9HOv9tYtg_YVd3W_W8mw54eDD_f60-BS--PTXS9baLVfgGp9BPTSjiMuQZAhqloCO5aYhNBjeHVGfNUjwFVaqgJ_QkOlVecmElCpWBiKx9KquU99WJbf9G7d6ZcJitS98mLzR8PdEqc97YWu4cTE5JNdQm_HFIGZvZrBcZbxi9N3o0awZTCd1_GFZPql5vLnanc28sAgVTKJtnukZdNB_mJfJPAgG1xwmX0Z0qAkmXaNyzTwp6Fc8bjRTG2VvCbNe9tuh3lcKzCHt5StSnRG1iKnWPS_cRIm4R1jqbrnsdh_B75ZkQd6tJDBiLxHGxjq67o-mpsEyu0CzftAiGMtvlQhFleiH5mkReFEYQuZqWodRM239H1hbICpa-hLoSFwe0XxD0OHPFzuvgcR3mbPYOTgAj3nrsZ0HuA=","fork_turns":"all"}' + call_id: call_UDRhRMC2GExMCPFQL1gBhS2x + id: mac_0728b898e06dffcb006adb392de8a887d0b4a0af170fa2f2e9 + type: multi_agent_call + - agent: + agent_name: /root/security_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_0728b898e06dffcb006adb392e6b8887d0b5214e1b597773fd + role: user + status: completed + type: message + - agent: + agent_name: /root/security_review + author: /root + content: + - encrypted_content: gAAAAABq2zkuG821zDn2CEZ4xL4Zo0Buh_uxng7MM6zKpQPfDjcnvmwxdv_1XYLrimDUFonQHy7C19D04AByo1WpO3vnn6qylNDnMTvHerM7PNqE3ctej3rUWsO83ztVvwkwvOargMVYgfa0hnI7jSNIeFnWeT_8zEW2oNzCvh02WW5QBcDgV3h-_8j7MJRZm9fZfyLP4702cVywAV53pcZSx_7FZsPlzwnkQTJVAVhAItPwh46fq2AdfB7zwPsXQW9eXDMuZivGdLNtuw-nLSwmiYY4yIDfc5CbjhSHLFD4oZ5APKaRNAvDAOc82pH8QFr466D6QmwwHxeCHpz2ny-WB_JL-SIG2n12pt_QgwB-0QiuVI5-5vELb6RONK5SGsOKhhPtQZVbyDlubH8NA3F3s67EpkyXgipfDzYphKDQHWxvpGOGP0TT7BRpvO97eJeiOwxsdfQJHlYzkWcG-eR8dOinoZafj57i3FC0a8Ee82pnMRUFrZelI5FwagPPA16XPYL-usitdfhn-BYXUeobAIipPkp9Ng== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb392e6e3c87d0a2cdd09f589e1b25 + recipient: /root/security_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_UDRhRMC2GExMCPFQL1gBhS2x + id: maco_0728b898e06dffcb006adb392e786487d08220ef8a4e87669a + output: + - annotations: [] + logprobs: [] + text: '{"task_name": "/root/security_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/security_review + content: [] + encrypted_content: gAAAAABq2zlMQbfcjLD-NAyUUqrOjEIqAZcI6c8UzTQNs3f7poryjfOHZYeZ51UZAtpBq4TakUYnjyeQu_uT06XzCjISoypD4IlVZ1evgpez_BejbVa9I3fZs5cGUJQmsiFSsE87Iuw-ICS9V9rWOrti9h-4NxjwK2upo5aQMOUWPJ4wYc7SKXmaTe_QRr53Ab0dmNWmRAxt_7rVgWartLbhadENA6vwLHZGfVIuVHIpp2bGxG1E1cfnWOxK3-imFfS6w-sOROE0TfblPZ1NtcxlcCLrjlobCp6s6ZQNOYyyTaGSldcwOMdQujqOVfJebCNdcalNyNWfbaDnp1w6gjDT8diECQzMD1-DXIZWBlFk7e64aIrfRu9eRjNp4xIirw10_XHcTVNGPdpg4N59RTkczdUl0VrD9KcIUgjJZldLMG_FjG9RGRzhOxLCVaKGuGKlIMSmUZhuXKsNZoqahc__M68rErMHJzShSl3FbbDAUcGTxlJhSl5irswB1RkawvcQT-JWLhoRF5qy6ojgKnK-9Z4V-htkulBg9c-HxKN49A93jYOX1QQi46p-I768vItIb7qJR05GyyFt7_1i-mzUfWlx79fDWLtAF6Czp3g_THyWibzwccwWUsColaB70lJj289fFa6Ia2B7Rm1WtT03F6YfpFk9xNDoDS_91-0Hs6KWgwIUgEwboXxz5Vxp9E_aqZifcMFuSubWlkKXSLFozVVu5NqT2HHaaahdcDXDkADd983228dv1_LJI7xXCQt62KUtZiQbIZ6ZBDuruT-hbgSCg8ov1EQICA_RE57VNF7f4WkIg28vqtIOjc77lVDWfICNLkvXPu0D2j_934oBjmAhFBbcH6d9zfkjTRxzmjBZ_Qo2hk_2enGVAo_bxrq1H1C_nHD_pxu1adZQJa0_sQiLDiC-HSBw3hH9PIX0fFpFbH98PdPzy04PCWfUal0mt4y_KsVzDotCvEpXoHbhb0o8INPY_91kh0LKmHWlxwXHFWy7oxFmBtTPDblzzWXDCHr-lj93F01X_Yv7w6oeJl-HJAc7h6fvdI4a5Hh8fodNLGqdW53C1VH-bB0d7qKd9IvAHIucrl5OseFlVINR21uQFRqwWV9jxWKVmLPyHC7jj2HHN2pP0mVgmAzpeGslNTnzekgYsF0Xt-o7ARnHSvADx2r1IsksfI5B50All0ZsJpjVhrgYlLhwTQR8dqrVjxrPtpNyhMUbnC153vJks0QM6JMrqW8oKCn8QFkBO6ACLBn0TfDo4Z_GfxZWJcy4-I7sej57_AjMWH6oRixiGKZt-Jblx_B-dG9Ldw3uNuRLWDNnNT5emruG8cwQlE_iqLiM3tCOtUFsaP611R2hYL7368jtC7F3vS7vxKweBYNOpAFDSLD4JOi11AC_Va9O6ISrPy81P2K7QIcgAHykimVS8qSw7Vr47Sm_JlNe-uM6Yks1eaqDHR_tTPb6twEmQnZmjXubJIChmZKXByjHto7TRCbkHDaEyW3fN2NOJ4eaekg8-aAehEyxDq8Z9I9taLwsgjgWXvWAzBOnXHTZd9NAjcgtwQCgAwUW8kf60g-doMSv06rKhoH7eqhNztX7uekcBH0V-475HuLtyUKi8blNKrZ7Tl4Y61Qq_4vcdFvDTB82KCTr7kjQObBifCKH1KEpcgSWBtx70128dpzC07lTo2yn-UxXafs_D5ZjlelxmACjIGFP4fvQhfNgIlNPDKGHAPGHx7aNOuKtqTuuqHS7iKAdnDo4bIfPlE1hbMJfbU0sgCXXOnBP5GRmOAjWGP2J1ZtMqlF-zh65rfXI0HQHk3R_An1aH3hj5_8pNokwnsdbnktR21mKXT1-KOlmNYsOWPTgMBRzy5RW_2dE0r3XKMzYC6rxCaklbqBmN5SRuCjNwfzRoXin5eGWgVz5DjtbYh9rZHg4pNM_sXWi3KYCcl7Rp6aFGBuHA7aVz3uByh5jESyuRWUrq6fGG1CMiW1YoZ75qmljmcGyZAKYzJzYnBUZl6tKwfxYx1wOO8N1zho0hryLdtQiS2NHuvEaieZt4ErGoCXivsCx3dKZM0OjSsdCn4KDhu2C_LKa1zWbAi1HhhzWi9PBNsm5lyhC8y9HgAVtwhtjqI__43nutZ4czxxWzZW0S9M7r4kFjagOtKdSsLEykWSPDF_iA7kSowRrMSKZmHoiLm_DhADGizObdNCDppLqRIJpbOz9gBfdF5vUZS_aPYZ2_SJ9enBwD-ius0Iz8pZvZdtRSpBIySt8CuiTyzvB9TJ2Ay68o3bvVlXRLiHqi1HKnCyHFV8FlpFM9GJUd6GcH76tPnUe5wA38kLXgEppfn3QVPeFH_x5XdhcahpYGpODUKdXRBjAKdx2Xrx72ugzaKdZ0xOJQ_lsjRYuIve0prd2neiIy8uNVTiuWejMPaKutRJcxXfUnEMaqW0C305rsVTS2Pbqoca77k1WS4kbwLJXqOQBi6a8rDZQwCTRcA0CoqiLE95i46GjOI135VRKtDyKPrAuOZ_jG6joZi8hDjcKQP25mRQc3UbGORiPRCAUomoMdAzIz93KJqGzk0hLVq3Tf3CIAWLeqv0-bbIaH8AOTBpHo2rvlXkio1FbLCGjyBcpz1NjEhc89zh1KOiXj-lJ34DKd_lhju-uRoBWCuZdAIWx80WCafojjnQVx00lSgHzJkKo4fURr86SE_NV4fXyIZvtB5qS6NGa_GEJD8sAzZhWASUfo1acHNtEvlIFjZxKD_qQl5LCn6DStNgWeKyXFJQmhdUUYis4Vx84SBnuPWx9QCAAZ1oBRND_p1BYOiFhCWwNjK06B2eC769by2uSjLSTqwc4Lc9OdNT2bTUJJ-FkYZrd0WDDboQ7g6cOLLgzF6w48ave-zsIIsHSDdpWdxEhiOfEn0M4LE4RT4J5YFfbvoIGi9uiSQkjWvX4x8lq8fPyldFiwyS1FhG_q2pwA8bHGw1fIPdI6SYt7KR4uK9BdSGTNWH4jNTrr1ouiaOM39QSQxMrvNX_3u3_5SLqJFo1RwyOOhZ3_8Lfh4Bbr0W2ILX1oDaRLJ_mZ3C6AMaQPjjJ98ssEEogAZsjQchWBGiey0y1ECS_eomigeZ8bClYqAdmGekQ2DKswTls89mvT_Oxn2H2xGUmbGRIxv3o93u2-oyIUh3fwgDYYbSiKqkZBDQOTxWD3zWmVnaaNBimgcVFCk6Vj-UobWR3ALJk7dC1u0vTMKw_lGXnKfhET4_4DqEzlcajp9SE9-rsoXWq7vC6h5_OTewXhY4DnMB95ZkRFP8i0MhbYPGQ0pBEkOCPPnLyV4VFc_9z3FRzO4RKWwWMIGC8f6vnbEth6zu7U8YJ--LPDT7uSvFo5MNW0krpq6ZlNuuijq_GOyFfBVGtaN5tQNrm9tNRWmmq_ktJzcXZB5nPYVKALcJ_0oUozO_md-Wm8KJFkSTGDeFsWvEPK02X6qKTLeB8abg8beCloMJzUhvLeqQkjr8ukeMd2x0Zk_VwO3LL4yBFG5WCac_ekvQ3TDUdb9fVJk9JjzayRZVxUMRUNe2BWeHFq6VKsXDUqCobpFzn9KQ5bQYLXGjSSjKZxfGCcJUFEuqp11DJK-ClPPBlY56bV-iGwlyH7RG7gZ_sMlMSnc1c32aPPFfnnGp08l7sXsHaXihzL_YprIHcrxbSsPO0Wl7Q25cEtgzCkSCp_o0dr_8h9BglsC2SwHSzVoejczOKunTHb9FY_JLyZqkhNONeu-PM7a7faCz7xNTWeqXx5B5ydHtVS5XFSbq0oFU16T_65bq5fzgN_y1VVWXXcHfaVsRU9PCdYbLP1VMsbIfTnqaUr7Z74yLBsy1Hq4ozCY-BLqplMf6R3g6ZRSRl1AimznDQdSSdrrONIo-BLVPN5l8bRRLNIzDtXh3ol5G-pr0Jtr_-LTE8uK-0ziV2sidqwHXQxJn3zC0dV8QVB_jB8eI03DKMi5odYulnhcDLl3Sf + id: rs_0728b898e06dffcb006adb392f034087d09be0c9ca27181d72 + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"missing_tests_review","message":"gAAAAABq2zkw0jNDUF5j71HJvBZWk2kCzJJ5kYTFPd-GXYEdMvrYOcqYW3ng-4OLQBlfhzOZ_V6Zr1uGD9OZsQoS8aGIqvfDcsumPVK6P3omVDEc9FlewSfr5hqAeO7LJdFz_LVsI8lNEdrUwwSYzaqPDvVQFIDr3Ckg6L-B0HU9fJfCdy5BDBBQJByCK7cwCCynu7TWS6_DDqXLZfAr6tBDgzJzDkMcD0uHeNPmM_xGLkqJijDO8RBaCsoyfF3I9cD6tq9o8-7T3T7hxvs6PJgYDsDXvFKWhxlsPsznWVXD0RlktjTO2Y3ZCdnwF2RZP51CVp66Gopi8N_RSQLuJXqDZ0r0eG49oqFHuYq4dUjkhdZepXw-7mfS8wM95_Goqo7rtmyODDRzuJjxmW6bbv7g5LygegXhaLzYKha6wYjIi0nv4JSyoE4=","fork_turns":"all"}' + call_id: call_qZdIWvLPqnp98UcZqDcegXfv + id: mac_0728b898e06dffcb006adb392fc0f087d086ff3836e0edfdc1 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_0728b898e06dffcb006adb393051d887d0bf7d07885e934141 + role: user + status: completed + type: message + - agent: + agent_name: /root/missing_tests_review + author: /root + content: + - encrypted_content: gAAAAABq2zkwT4RTJERqhHuiGhPXkCTLt66HuosEzOi6Zq4nhzebgxAxHIw9kxV31_YBHf3iSD9HX97p9shsKd3yT3lcl3Bqwuizfpdx-_89x29yUQsN9thJWxKk09RmQs_-diIs1XczPIev_EpStbScZdKbkkpso1pgtTjEcWIEeW8Dn878-u2B0upaSP03iv64pmrgWOAB_2YCaGQ629ZynHNa9oDI2NWbwUS4qN9wtdS-ktIFm3NitIgF1NT3l6FMmM8UqRUyocr0RkzYoBz6-k3uONNQxO2DShjZQHHN0zxMI6reY7IllMns44kqfNJ56KVP3tbqa-AatTMMfW_-GeDOIZDuT15RazagGpl1rFnR-nGbSZEi13fzKxDSC5ydjPewvNjlCrKzmmu4KD0SUV2PJDvpWKK4L-Vw9ma16Ru0lqVxRagAqjtES9oT6ZQgSNTTwRjxlWK2Zg4FFG_ipBhE5ts68eAHPus970Grsccd34f0iXxjIekhOUNz5y8jTmVJ4FZQvenPqFwmHp38qhNa1LLkYA== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb3930528c87d09e49739e4b76c167 + recipient: /root/missing_tests_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_qZdIWvLPqnp98UcZqDcegXfv + id: maco_0728b898e06dffcb006adb39305f8087d0b6c0f96d85eb8314 + output: + - annotations: [] + logprobs: [] + text: '{"task_name": "/root/missing_tests_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/missing_tests_review + content: [] + encrypted_content: gAAAAABq2zlM0d1mGCocXd1q4kCL7-WfDfmApW-rlKFYzFIuiSzpAOu4hvl3g6YcN2RuOXwT1QE6QEUtC1RYhYsfC-rfuzxd-P9ZU8jte5GAw5kYw87snJGrGUnbM84ZjILVkyBn5RzbrjgbGG6FSmbsIMrKeAlgW9v1FZXRQRiAppGzKDQaMyjkQVbUyLkKXf3-rfeadZK0iWSXRBUPhnoRwV0gFNbmDb67E3SLTs7iogsb9GsCFw8J50ARwggqWc1B84CFpqDqxcnswHfxy7q1HYl8dhvcHEHNFiH1kh9nD1FzLEB4Hed9tGg5kqnOzbRb3skbPv23DairMyu3o9EOKhl9x0UyihLbZ5JDiNLUrT1JJsq7x-MNyJbSirNgIY7R76I4hPZXv7FctyKsOheMynGzNK1bJGvqIV1t1gd_k50I4dNTQAHhnmyOwtna_vpB2srLAX9HcQnuXBHL9oOZ9x_ZsUyBJTNndugmEOCTBgBqwx3HDPUC_flijNEOYD6o7lo5jir7ga_j82Xwk8rNE8xDNCOvemZTD2d_LU-eW1chR8Fvnto2u9gxALKEpD5JNY0eSbegIdKWfJKNHVRmwIf9NmEBuit00ySJvijKjM6fniJo6fkIXkNVs6g0JymGtA39f8ZG42cVEJAELHjflhefG3XD8E54A9OUE0c1TF6Hjztp2WLa4URZ79db_h8JT_QQmgYUfc5cb_PF5R0Oje2FmjJfJMu78mNvmoLnYdnI738xl06uCezb5GafIUy-_g2580_acvf0xH7QpfGh8awWi2cGYDUU13jhoftPgeN9SnihH_018D2cDKZ5VmxTLSlFjh40TRPe3KS2NuziRFtI9CVn0nRTtRFdmNiGYeG40qyqmW_Cx5uqGfC_awamOXXXGyTwHBtM7AlTY6L1fIyc1or8L0UOjIsPf8yHBKoi6ugR1NOV2W7wb_q6O98-9Y52PDBWQQcNtJ5fXuERCVE0B_rEPwt8-v6kd21nylwugxQuIOtCTi7L2QxAkBlE6Ca5OFoq1THQeTeghr40O1b_pV8-jnY02rLDtPccrn3Y6qiIRT70vL4AeyEb0Djttd6HSrGA3aAs-vTkkSjTaYIV9rr3MeLKschuF86url_w9Rv_NKPXpiRTAaTNlv57VNsoscyX3B8dVxUh-eenjhEyILYQ9BBG-NGLimGyPpLJrxPpYXr8ctpVkS3onqU9PI4IzLg6MH3CaOmGHvzN1O4V25gUQYCgmfY-_Txj7jTuFWrDepgLCS7DPwDtV-N5Ic7DGkAbWg21GCeV9B3FbEuJXiTrH5dbGSpdsEnSNpztOzNfHjr184QDydtvNCyWnkfq7s3QGBB4SyPvShxApL-6j5Yk7fucW7yV44fpjCFvDyRCZmd3sFYcE1A2AsmDGsWM-wtKCpWw-jkpFi9QtVbM60XFyFTsku7zE9Na8eKRJSoP3WxSspyreiF9pdguNANGSuPKhQY1s8QHQWlEWj1AO3HN4xqFbUPlmR2pf_BuZ8m0AmpFDUAqPPNFHBq0Jnpccgzkf8dArGNv3ir3sjkYNrgiDhBk98ZuV6xUVC2LNna4pZ5FCtb32TYbSoU6_QXTFNtfgaVorVoXMjFCtJrNIJsvf5mexBYsgg9Lwkk8EOBnHmOIWw-QRxTh5kM0ml-Xgd3lUpXS2qnug83goouQID2YdvnFf-0crpib5oZQvQGKOxRnfgSPwtH54Cdigc6Plh52YGbLZZY2n0Uv9JVZD4OFujzIlLJY6ZJJ3UVLrdXd40cO3g7XZumlSrFNCSwfvD9TcAW3QUqBsieWcBzGqXQcFnvrhFZCy6lbJg4G5P2K0Y4vIhp-B1AgxoLmA4Ay0X4CRMa-n5skxYZrYC7DB0ZPz8Sxap_hfSFdSDQH9t6IRyr4JpMh7hjTo5DeT8xNq9JRhuID5Vv2zypZPKWyrcEJUF8jLsR8FfXeBL6Gc2u-WYyALCK7-MaCtBROYSKA2QVzuieAwQWzaFtRy0czd3zMZB8bQR5CzdrJctICLxhuzHYOH0QQvQAtysRPu_IqXvy_M_HfqczjuKsYxP-QgTHxqqrO2QEgr3dnpGcMF_UzPSg0OtQqAVL8NDuBGPyWeH6be8Mg3e7tdXgSHjZRPgbU5e4tZiw1ex9Jadn4j1u9Gyl-Pi-aKIu_8Qa8jKZxB6EjR2r1YPSOM7e45ET6U5aHTyXvlOVvr4XmqOKlSNWOhcww7l3NQMXyexpnWfUOeng_iWSUODBYtZohFYrdnF3P5WHutEmh7aLNYWFrn9l-8EYVx8wUGdIK6eyEQfL92iefpQXu5MWublpN7FoCLCaupl931pE2HYs-VWogdavS4Mr_zz-6mPvS0sj82UroEczZZZKM1Y3dCPMuPCbwKhvT1gy1PvGkqWLOeSs7zH536mIlitQyEVvl8b-TIf4PCDqpsg0-2zPJnMnTIe5N7kwjvRXjoaSe0Wy_eGdMzSR5FRNdtIYKZvTp8OD1GAGSydJvu_vzAAKGwp40diNOQY2WguHrU5hgLRA3ESI6dG7MbzjHNATs8w-FmMqkDvUCxzWe5rT99e-LKI8wV5bt7ejTrzZfDgb_Mlh-gYqkyQq6SwY92ANs140MioDUnCRJDPYy6iax-hUPiw9dHEExWuJ2k07zwU74KLhOeW9C5v6qsrKALjCVBf7lNTS9ETyJBkFWCtCwwQUx6uiUDdvcepHI9YPSo1TvyAlzuNynd6FA5exm9__jCMnMrXuYN-72sSGhrRk0vWi38S4bPEFj8YzsUI3RjiMcrJfZBaVsCKYuRy3eGxT3Tn62ZezW8ZZkDTqtd2PK9JU2NdtpZ6pjQwV256Bd3OSVmmF-_TIdyTAfEldPUqPZadt51LIEK6imPXSv9-WPpnEn2TCJQx6hKb3cywjD8WJrzb0LYUw3Fz6jRpz1yiEVIHBX4bzFu8vpiJz7mY04FziIuuvGMl9WCbGglE26sgHEqksWZRdxAd-CXmvOOTOm4954c720WzhNC1KcA_75FQ-3tFsDAW6dVkUdzkhylT-UphTLkZb0OE3Fj7o6JUk1GT1N8R1SNdd3Jfg0ZP4gsXrJlrdWEVNN1aIZBuFyvqwDCPG3mbzpMR3yegJCx55JUZZg8RfzD31DhAQAwlteEp3I6H8d_Z09aRGHArLDnYrR6RNsVwPE7DNtMqEB4NGs__Bq0TBa1hz2j-vG60vwz7Y-OHVGga7h0npF_tTsuUnE4U352CvYvs-IbLb5DIcbIjZkfteA8L0D21nweLi4eqHa19c9KIGI7hm9Dy8YiYXx53TGLV1QYZYt0aek882kqySEk1Q3zTNlJedX-dUJE3hJpKi1ugUUeLkKmsDycitKCzGshDWmbZ4lxwnDd075U4AHztkFWYLF71pMO8vus05D_UlPLXIOcRGy1DAum9HB8RWYom0xYMrVluxOfoTUYalzPv4oj-lTGoFOQWOrhC4pFGTx9uSzjrv9DYBcPpa1udguRncs_Fba3ePcCHMhfTGM7pqTHsh-MLqYc33cqDrIWptW4-ORjRQrG6BcOT3BVkBI-vb3L7B17P6t5R1RXAWBBc1MIh8sm_23sC_vVxmHur0PjK7Zp_qHgohbltr9hxIXsg5OESqur8XRy-raLsanE0Z5WsycSpEyqc71DgDVCw7sZRmSWImTxHdxBxuPlTA6rfbAW_UNqoXdcs3MboT4n8ZOnQY0LLd1UoDm5AR1O1-aEdl4ysVzYXc-yJq67dhi2O1MmFharXdXSEA2wL59vxMdUwCMoIWjtB0myc5mKO_hxr4dhZ076w== + id: rs_0728b898e06dffcb006adb3931b3b087d0bb09e4a025a2b526 + summary: [] + type: reasoning + - agent: + agent_name: /root/security_review + content: + - annotations: [] + logprobs: [] + text: "### Security findings\n\n1. **Critical — Unauthenticated remote code\ + \ execution (line 5)** \n `pickle.loads(payload)` deserializes attacker-controlled\ + \ bytes. Malicious pickle opcodes can execute arbitrary Python code with\ + \ the server process’s privileges. \n **Impact:** full application/host\ + \ compromise, credential theft, data destruction, lateral movement. \n\ + \ **Remediation:** never unpickle untrusted input. Use a non-executable\ + \ format such as JSON plus strict schema/type validation. Authentication\ + \ or pickle “restricted unpicklers” are not sufficient general fixes.\n\ + \n2. **High — Unbounded cache enables memory exhaustion (lines 2, 4–5)**\ + \ \n An unauthenticated attacker can submit many unique `user_id` values\ + \ and large payloads. Entries never expire or evict. \n **Impact:**\ + \ sustained memory growth and process/container denial of service. \n\ + \ **Remediation:** authenticate requests; bind IDs to the authenticated\ + \ principal; enforce request/payload size limits, per-client rate limits/quotas,\ + \ bounded LRU capacity, TTLs, and eviction.\n\n3. **High — Cache poisoning\ + \ / profile integrity failure (lines 4–6)** \n The first request for\ + \ an uncached `user_id` permanently determines that profile. If IDs are\ + \ attacker-selectable, an attacker can prepopulate another user’s cache\ + \ entry; later legitimate payloads are ignored. \n **Impact:** forged\ + \ or corrupted profiles, possible cross-user authorization or data-integrity\ + \ failures depending on downstream use. \n **Remediation:** authorize\ + \ ownership of `user_id`, use trusted server-side profile data, define\ + \ safe refresh/invalidation semantics, and namespace keys by authenticated\ + \ tenant/user identity.\n\n4. **Medium — Mutable cached object is exposed\ + \ directly (line 6)** \n Returning the cached dictionary lets callers\ + \ mutate shared global state without validation. \n **Impact:** persistent\ + \ cross-request corruption, privilege-field tampering, and accidental\ + \ cross-user effects. Pickle may also return a non-dictionary object.\ + \ \n **Remediation:** validate the decoded schema, store immutable\ + \ representations, and return a defensive copy/read-only view; restrict\ + \ updates through validated APIs." + type: output_text + id: msg_0728b898e06dffcb006adb3932176487d08f2307c47afddab0 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/correctness_review + content: + - encrypted_content: gAAAAABq2zkyuT7PZXDdMN-Uhmu53z-5yhJO_jBql3wBHjLOpvKoRPX0k9ZUyxj-kDquwbNLpFhsh06oSOMvqTUX56xiuYYkpkPjIFoDtZuiXyEBJVlUxeHvF4CdAhMIvX2TLtYN-zb6EIifxOLTt_W7SJzWfcNxQ18BD5aIQV8u960B03Z5X1WEI_M9T3Dd8y_tDaIfcbaaL5e-OQWUZNy-FfKmk38ukl9GPdAeOUZfk84WsOKOPsXnfEDBLFAMJyBdXzUr-2oKmwD569AvSyFl_Zm04OQ79ioH_QSGbz8qc3QSThzaOMB0jaQRS8As3nCfBu1y7jS9YpiS3oGku7eKG24NtNmexvMJjwG8RIysa6BamX5uRB-AM_c0cWL_Hbg5av5NBmjrsWwe4TSbc8YKzc_xvQ5G1TdLsi05_lcZlDytWkb2EBXNd7vOu7Ivz0TS6W00ez32aK95LUbgIS4c3RoO4xyx_jNplUfpfivWtw94p1k4Klio2Ij90xPSVXNfse5Rh-bD6EPSD-ruhagr4JVJTEqrnWeByWMcE6H-zOp1Hrsx_ncIE9fln_xE0iiOuc1Hx3-jIkVTiv8gFdW1OBrhv2XhOz_-L3G0n83Aspjst41DhWxi8SeQLTkRs5pWE8dvblniZodwUgkazKvtFcvE7rj1oqPpUKwQ1il1kW98K1kFFSxQnO6KOxXreeVhBYA0Jrp7yG0ZvCrmnszGQ5-wUuLemOM1FYbuXCjnRRH0X_-MrPJqe-klJ9te6VO107HocR_VshzzTR3haLNxsnNHNsFiQ_IqOVxWLsi4etC2XguF68NcX2gbZK8zI9kTX2lSOsSAoW-O218Lw1UeI4MEoOyfawu7MM3PiHAd9K5wsREKUpUov3ZPpkzZDaoFUm6hulUenffHujuq6MGFAFKA4TDiQ3MKkZc3HKHH-5eezontuPe0oXTiO5PFxbHQ6Dx2OyRuYvvbZenhf5Sp3Aa7KYRzJiA9tBA9lOYq2INtvIhrfviP85SizkNO6uREx6yvBa6slz0ZoxSUWqrZAUrdYI3I0IRFLVlFaaupU44EULWmN-d0Qk_AxKvRaciiQxerp-9uCdQftDSnKVoew4QV7rlqHanofNtglC-gFEB3AdYfUQjcfwxtjEf0x-L0-TTclCHmQXMgEcFiiadM9N6KFyFFFoXJBK7pUCoccie-5DoPw0FTSVt71PSQ_xsJ0qxpeFwEBptaDu3MmPIEdxECJYidB8wSLqiFGYQHL6gh1GxMGEq9FZRFAwK4PrSWU0IBx3zYQN4kRFIoNjOeliQugkSBe7pfY8FSL2aVviZmM5kgxIQXWocVA7mQEaFFS9qM_58vm2fuThctgXk8U0tkEOjIakeu11FrV4ddzxWHhZ9Is3sjIdwOZFHAwMCBZIk8bZ88A0VElyR3liAYjxgP7_zCiSgoadufzQgn-EK-OdbrAH6NjgaNDe_BU1FIVxLS3xAaKJdzZJiOPOzaGg9ttSL4_5H1y9VyZ32dqv-61f0XxP4m4E2KDO9zkps9pwDajQike8A3yytwLVLQnQpr9X7e51bUpQ6QNufpCogK58Cci-tLJ3ZxuR37lh9FMdbAoERNAiAUc4p-tcDMOPuCA_ZncX1zfAJGNsl3tfVfNh394aK9hQCTnNP8Vn1moi60OWGHBrsRNisl_wFqzdZfsC2XmSeiKPmk45QrsiDSCRLrNqrhAAU6eRfsIOLT0fA1fXdNIeV9Z8LAkSOEhftJ71dpVly1pX-N0soqNSx7NR9ZhQfoZhlpOE79lx6yUHwFj4Jp11_NuO4rL-4oepI6XB4GZtUxjYSXG4r6l-F7WkZ3EjskchxqQB6EZKM596YqiFjYhOythPpjz4a_kKCu_t5tojfUk_-TxtyZddRDmnuGmD9XKA7GMFXOxLog0xvAdtt0gAl9wDZtdi9AsrlpXgeIrVd7epTz6EshfEmI58x_k_AhllmesPOaMTZiIPLN3yJGhKp9V6KYTIdw2hQhul7uZzvOCl99lWpUFwFLteFjqWmXMdSUW6PKpGZGT8xTm4yFxN9wTGb0CzQ-f8hF4rUl1X5tCUdJzLcCCnwCgGofImn31-Rz8LXHk9h1Hcij8w0c7Uawjk8rZ48n55XPjVbKP7kmfz4ar2NU7Qy9gzvpSq0JVRTLUNd4OgaSIOGpgkXma1V8HzerpVQDD8spPNa1P0OxEwImwKXHRz10eEUTMalL3_nQPfN4Yv7zjSSHcfvpcUtuhmp8CcEVEeLeUV2XbUDJBaQqNEydt5es2IZ-ypFUQkt-LN6L4pSYR3o-FNZSrc-iEkNd6eWcJsFIt1PrKdL2ctffFViklzknXGH-JfMuz9XkRlC76hq7OEWHcTaBsg2IEiSPd28sElbkVoVPPUs_PKWEnb_l9lz3bTtN8UIhq3E7q1jfUv_SdFNr4j-OCH-HvZ904nRQHRzTqvO-FMGTIummijHvBmpOQ1rPrASxDEepkxfGYqblIqKeenjUckYZtfXho4UsrIZYI9moT6IRtJ60jc8ip5CLLSAspbxYB3Kt4RP8v3ErbJ0cQW5AFiLNJWIeXlj-wdIrx-3-K7aAohE0GvNdrSCcJFvrqqkImxFe6PH2cA-05IDJTZ6bgV9ELwBXCbpRZEv5QcCn9rOP5WwapMY44QecY8Yq19OWeNqPMeLNerLAWcy-l14flvsWSN4b9ke4wyzdHLEm5LFokmvAaZTZkTDDiuUoE0Kv87vcAXYP8LlIpswyN5TzxjJFDjK2OTT_3JFOzBHGPE1E5fQzbvOMO42woYoNNuorqOUTgjgpqYe2o25tHxrPsn9JdgZok1N4hQqUzVquimy8Zrp_2BodPKiyMl8uctTOtsnbZQVI9oh_pV1Vpzwss3oCY__c4trf9FuyQGH6jnzom8OTPWtbcvMtGz9tqQEiavcWp8gNO15YqasNU5MVfna6HeWAzSQGxtBNFGAWrrIKzairBWy-hudTpHVbOE6-3p3vGZWhy_jp2e5jftJDn6tnDvrGBnvWcXn8zriSeFvNKzIlri4HEuX3ln0ii94W56kkXfEE8FCmFiiqY-Mp_OsRrmgtP137k3qICKBTYlSk1Kdf7P6dGhBmNYHitnFM7kj84LStOmb4kMvXdvCqozRGmlzOfkoiHAb9t79NkicflqTXlmgiKsIKxS8MOAlR2geHDQSXP7RecycIQ2hvLYECZBTDd1VjA2eCXHyb08XCpJ4PnZfzEAo8vHw3NZzrvoextygDRaJJ_ay9e8NG6DOm_Y2DsMHh1IniHMF3usYKZ4LIZ1whYvEESzgTYB8J7AqZzWi8iE9w-Y_JX7H7NExIYtaDuPSwWdKVTDhQvTijkq1V5D4bBMB24zYwWpABQkVFAh8booB5e2MdDo9V + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393242b087d0b37dd408126bdf1b + recipient: /root + type: agent_message + - agent: + agent_name: /root + content: [] + encrypted_content: gAAAAABq2zlM0d_8wzhwiurs3IL_8dKynu3_T__T-VfEnqzsQ7df8OiHniDp63iP1v2PD5-spkex6XTnzgkjX8OtZml1d7oKTa1scJGiFy2lkQFiLutXFq7c4hXSKO7BNEv3Iewm5NKynhxCAF8v2d_I50jN-Cc_RMwJXLd6HZUUDynPbCD6OgIV4ZmkRStr8-gI-vKmO3-R8eKDOofGkxqlQCKnV8E8S5YKqAHyz_ubqjhcfQ2MN1SlxK3HdMdseKX5xY4hhQ4fd0wi7To5iaWbDOz3W6qAlJiQKdCwYM5QeRQCgy_oJV16HavVj9F4eFWLuLZ3dc0NUZfsSkSGgAguiZCFUNzRap2Y8vr43_meaRu5-01LvdklkgX9OAEGeQUZ5qE6adIRyhpdMPN36g_RKQcsUyQMHn_Ugj_u6xNKr_UcDW25l7xCJsicV7DGayiMiwwqr4QMFmWJlzuLyCP-l5vfZd7rcd8vRTe1a2q2SNCUW4qFzt0rk25EORACCJGlJA-x7JJwC1wVDXgNBSLSgVoxEU4gXKlnKyjEa2_-v-iNtSFnho2a7J6ZzKOfOjfKf2fI6rpFewsaStVT8csxw4y5Z3FGyF3bV63vy53_NE4dj-FFJxPrzkkeKnrhsUkF3wEfjFjruQJWExvuXKzIStLF-18X8YpJuRKdmwlu-FmvnvMxc_iZzVC3SwMvQ7hLKwhY0hezcHf7ym4u602mWPD_NLDpgINjZNeLGS-x0Ci5lRTBOKl-Hi6I9P_jzzt7k8j9godupDK9AOF58-99RsnX6puKhCLfHzzzBX8Hly3rYReptWAtkjSg7hX0BqjOSSATC9Yz17AWbfeoIb1U39psN8fv58e6F_4r4tVj-nrNSRgSBSIktEKCbESo3xQW-mPIYEbEqyiwY8Ips0FAMS6kA3L0pXpJwbefZPZDKNI4vyflIFG6aaWekwEoY-DAQR-bBOndSadigdmpl3G1mt4T67WXF8cOifq1VbGcx7GhauvBHsQHHFd1C9hM4fZxWXifT58NzLH4nQ9hhX09leDsOGqb-jj-jII6jTYbZlIGXcgCt-agl9ijqLqrFPqPHzJs6xoRxbZzrTkX1uetF7-YfqwHCZf3izWz7zm23pOze6QnL-YdV3bHKS9jzzG8Z-YtQQSzxPOCKQ7nAVHrJb_waPH7EoFAxr-wA8cefvxZ1BEJozUitdhfZcLS1y9CNlTHDH0-ATjhJ8PMSiV6_cPHten23UZ8rbtNYUjr227ixjHV3kyd8GI2ohV3UhzTTzs3C_ON7qX8l_xNPyFdzL8Bv083X6qDPAbc9V1ZiNAwXH0GaAO7FeVfWD5ws33KjCQNU9ZueaHtW-noe6oMBdC-m6WKAgY25DDsbwb9Q2mc2Y2d0fl4VJsDchAh345QxmIbBwyHJf8uI2VlVX2IWe4pS6xmRkGo9-heW11A4CosgJ3g96s9x8dIL5ppIOG0hOwsqx3TQHyX4o62xf54dQe2tOa47xv1b1HwHGDH6hlhd5jSqezIIgDYvwiB2Jq2sB4Z73MONUrLEwIsXf3CqlXkH8odY9iJnEezWya6W96gCVuYUyUaTK-t6G3hJaMyIKfr94DoLtnOUGjQTc7TWjigJ18FRmvOYXFcNCKj6bq0O_Np_-8czQFHvRoK2oRSuggdJAi7_gh73OW4bdZzYx_9env9-5lLNUO_oNAXpwSUw3JnKewi8-Rl99JXjAP9K8ytRgrLa-SF3S0LW009u-JPb09IuZEnf0oCJFu6C4NSYvXlxxCKdOGO6g8fCpU8t4zxzNkRtezw5yqsXljofJKAiTr725xL2DL1iJOM1Ri2rOpgtrjjd0kNElL75aRrnxF9xxEosdagJk1ObKIX5doKP4zgpmK9AVQsNkgfURVGo3_18Vm148PgoeN-lwQZbwiO2pBpkMswdbcGJO52VCvryklttNgkU0HoE0BIF90p9zdBYWc0SJpw0NwbSfbkjQT9AloIWtgGGctCcLa_96khTJMlPwWgKceXDpNIsQYjRsL5wXMIKs19UO4prtuy4Om9a0uIHuKyQKD5VMJPXu948nymBfjX4yuklI4WcGH919dBqjhdP6_Ognpz30Hb02rMdCgFsSJo9dnPCJySAyuDeBvRUfMrCN3NMphZwQZf8Uga2dlsHuj8qba5Sn202g6KDs0dza3RLxxGSFjaQV6eoKkEynHgeAoeq9uMQnwo4YYyIvKmnOioSIC0RvAF2SxJQuVELhQvTMyGfPPdYdEirS5nBliNe32Yb527qCZa2z_1OQ_--HviVNW5Y-a2a2tyEQ5YXTRRvSoqR2S3Sfgg6oCYsrRTCLbcCCpjTCDQxH9sb7rXRpB4jPplGqZuFwKVuGO6LCj2uYWHju_-KvKyY4dTOI1MimNAPfGppzll0MCIoP8ZxfJFQl9-JfvTw_UrWoCrOdwIMbaTsLgocwX2hMMOk9lzetU9mvUYdBvxy0Ox7Qigj6vwlkyrp_l4JerBVo-uKluIUGl5s6pkM5f-sxv01YoKumCTULBlxgQ9CYUF9YDtKBz3djXOLZ40Gnm5u9aHqGuiRPMSPhtxpUacigTzsTGFM3I7Le3MJjgJFVkTe20iEL-EErDuqm6iiMkVrSg9F9vt4lK-jq4Z0F9LOtBls6we8jI0LaDkphFJ5jZ3Gh-htTFWfd2M5ZuVdYJpgfGzEkHXaIAYb1Tuf_viFRNcXmljcIdSdwkY-apBIvfbbI14ND82IhDMZNgfmLi8crp5shxwnLGjvVRmLdzveJQqKI5tNAXJQ1lR6v9uwVYKMlJDamNFvNSUYanrRYVjFBKQwrskAx4xzQGxBkn9rfh9FyLWTcbNehZl3xMW0H7QpCNzNIc0E_xjY_hAhRIzlVr9xRLlwkLkKt8jonSoK72elVnqfvvVVGPVz6P5gSQ_2O4Bdl3SfFbQ2OfeJzI0z66VYLc5aMngt3CupcXWry4yX7_oiKIBa0rPtf2vPoW4N9RHu2G2jKu1m77p-41JX8CdboIeRQpm6Ig5YOc7v9D3op1qJvAcV8vI_f5VQ06kyFDCmvDoK50EmaZWBP63yfVv7mDh6V5bsnQ_9jsLLraYeovHgKYzovwSTTaoz279cZE8cOfaW-3cy20s0mbkThAUVZUJ6B_yrgqJuYYsK5xrlbRuPkMK5-98a5_p74GevUdLt22evPVy1k91bX51UchEq6p9N7knlbB9yUSO1JbXXd9vRbroFJzaeEgowbAjLZmqAHW6JQBIZdceqpgMnZM27D58xuxrF1gjJgQsDSnpx_UtwuS4TYkEDu0iRDxjDbApplC8t8j2yk4JTKG-Zo7-s0mxaw3YhFwQRHtLprcf7krKY52fpjX74qXnV_RmfXyqN8PgtsJNftCEhjd7utblfnz6SWXX4vfw79ekGHSnKZq6PmBv12YZGinjz6rcBx-Awaa181L_sUs4DRGjqhAXsJVhM5n32WHj8dPN-JU86ToiKorD4pZfyu6TrqiD8oXIdUzvIYLK2T7VznGVL87hN_UNYbv7f6wim4PLTDZ9JgrY9kbd6xQLEoxmx6vVZ_T0xJ1AnkwpU26hPVJHIi1He0qkibgzW_2UAHTXxGy8Jsz4F3tBj_MxeR2jx2cHsQOaioiegxw0nVMQlf7pfNPPfimKDE7LxVGrrJX1MawenEHkqiP5XNrp4YerlL2I9f7-paElCkrWL-ZZz339ZtezkqvaC9bjuituBUFGE69U_BTNJWXnRRWxDsirKieoJvhspT3aiz-1qXBuJifkJfz6KUFaAyWiHSl16tSXmrtuFA== + id: rs_0728b898e06dffcb006adb3932476c87d09ad0355ac91fbfae + summary: [] + type: reasoning + - action: send_message + agent: + agent_name: /root/missing_tests_review + arguments: '{"target":"/root","message":"gAAAAABq2zk6uUSQHuW1EbtVDt3JfuErn-4gtzTPrsQyMgJ_1HuigfAcEFt9f7qhHTtNtSJ_c4EvDu2ak-AMWzm4SHGAU6pQPmCZEDPVa4RSf4UXR_RTk0Q_HwAvUSjX3DhAt-A-avGsSYpYlUimBGjIniMP7u179OhjCvmDF73GSB_dtmMVPfbrQiuIXfu0B5KvRvPxMp3qtAOD1NbSULsO5mt4A1vRGsiNx4XFxWV07GKhhm7wtw0A6lzKnA7gPeZEiKkOzdBo5vly8TDmlJI-ibIoOx-oltAvXf2s6HipROd_HP7wY7Ovwb_A8U9oTeriQUHHZ0kZpE4nabtPBrQtycfEUjrN4ohqAhKgIjZPKJuSO-p4FKnQ23r8dhfx4g3CBJykjyjEAuw4mr7Mecev2yXkDWyig66QqFbgDvWcu01IuNY52CvnD1YQYx9LGzwpOVSBwJ8SpiLZK5Ng_YQOv-HzjKwsOoFvfq3T0Jk_fMUfVNP096Pwo9IcJdZnKlr05QGxG3_7DBqElLmWpDtVLNHAGaJQtu-uZtygiYXna7c3qOTEq3dK7bCMlekQ44cYSslhjpoyka3WYf7WlZABUOhNifoIGlMgZRZB9FegCfyXNW-xxNUdKtmMmJ4KDLw6njyc_B9-b2y7B5zs7aAl_soJP5rFB55BMTqnJDxHzuqrRNFMsvszFi6NSInYOZT1HU5pAplMB6Hk8Fxdj-wWDJSlBCELnkB7NxLUIkv_p2R2jMR3g3ft9PgJImZr8wrfeJfB9p-KTQA0CW7TWCSfJYsvFf1-Jj-3DpFbuue8789JpQ8k8b16suNaOj7Rc4cmTFbC-jDVpvpoXVYYJ_ROpRO7w7tphdM32x7IXgaRfl5iNWJzEE5PgZ6enj6dHVgoXmYGXrclqJqUwmF7Mrp_KA2NaB_xwJrKSJPN5YHpUpsvmF0JIDswQHZ3crMjwN5R-BkVkBuZ50uSwpLNodjktGHV1ZBDrzJyXoHyfBKp-qyOCtfpRRbXQOOuYRtyZMT9GN8uGQXLn9iLMf4aBw2KJ75ak1PWfJLNP2_Rh05LZA-BsqoSlCbkbo4pf9jTW5ZrAim-4TiVXKG3TwSq5Q5Q08PoMZzOLJBYb90BLAWoakt12tj0CMBNlj6o_Hau1nbcWGUO_u6m553rmsgZNuren-kigdmwDUL8QTGVe1D4zlD4jVa0ADCfVBjJHfj8HFVDGAhMV5N_DrjAEVog1iquVwZo7MesTH4gAue4HDeqGEY8KBBEE0GxUso9dWIXtrpjXTrfKGLBMD7vXILkgNDj9uGt3AictF0hWXsBEFA5MryFy8XXpMkZslhIjagJwQFJtSdF1G_mqWY4RhZ8f_QDxPMeMeGLDGyGH2envqQliBDTnSgHNKktoKXWlR2OBnNfLJ-c_idZF9aQ421K61GwFgwYWXK02siHNcNekUsxkjQ1_38tHE_quOUQGJFDDINI9hgvpYqnkepNf7HakDIXhzOsLaVL7_3JaFMDyDOlmk6qkbGMw1XgrHOm-6ho4CjSPaXQN4kOnMQds2fA29RSV5gcCBx6HnN7x2x49nHTTM5Qq2CtOR4huvNTJZrRdhSs4sHlsMhICY15CxK5qvG5IIPCzJzE9Y-TpbwD7fwpqFbXg7spjsRxnlzCXB8agJGAkcpxJS1cwwZyA6iN454H0YDKiEcYYC4SkcryutrVMoWlVTNg62FBLyhdxuENpYYvEh4RH4Rqtu-EjZzrlXPgwFGWhGDfg6Vt1ibrXGcQvLDbDBF1U0TRzaWG4UQao6NM4NwIdKHMu7vrNrzAiXk4H18g50A5uP2V_vvJFMorHCgxr7RVykwIkJKeIKphA7ZsIvv87aXVd_1mTmNoJLO6CpzRUmmwMJvS9T1xo6mEqUtp6HC1VxBBR6xezwQkYuBnlbF2YImE0M-cPaSqiaM5RURX1if2QkCSfr7Vz3a0_nhzYRMv9GsRJgKI7cBpSbnTBFtt5rNAOoTs5pHAuW-AQYSYLtcTlxixqToyTwCGleA1IYSpsofF5rMFOeH3G-j7nXt1e8XAB0BodKcQJ-iEUe-WWT3TUPopMeSqHQEi-yBUTQyK-mHEgteW3J1GqczBTtWf0UTZTXMtJRTBdQkh2zCc2xXTOyF5NAMqtTF8pm6Mw7pRe-gaR2kyBlK8ihu5vjzYdQTzIz3SHI7N4rEM2JRGMFmupmGD9_2-pT8HkrFJz5Zw2peAALcE8t6Aq2lJaFWZIan0_5YfxlUP8EdpVj0VXlH-HcfprNYTWI89SQBWJTAmAtJz80qt10WB6K5vj0m9KG6cMH6VGrQkFU2HFvZ-S0Mp51toG73angzx38uo7lDPxx6DRD4KaUNAbdqzzHrXDmMXFsWYDkZkG8NLZ4H6AIdmfthlH2v-13Q__S2v-pPvr0_6JR3LYNuKNrzJfyf9184fVymHP8rd5HxtVNpsY2W-h-qXoGshZBjxlkTZnOEg5f20FExs5JtXJ8U1EL5A3dgk"}' + call_id: call_lJkkJiqgeFy0IKz2v91FNVKE + id: mac_0728b898e06dffcb006adb3934fa0c87d0a6ed4baa4ebc67d0 + type: multi_agent_call + - agent: + agent_name: /root + content: [] + encrypted_content: gAAAAABq2zlMjd8kmNOA2_Vvlnk7UrzX1geVU35P8bZ35SyVt5Cz01LLSPyV93kKYl7vA1usS6cgtvdXgQ689UNTjCV8tK7d_liUdLw_cyLjO0Ju1aWDc4Tfp6kM71wc5Zs_JfigGP7TzNfvrK8el3Q6L07fZV9YczugfwXJvjUCMiqEp2reoiNN1GbkTny0_LFNUd4JaLo4NV8VOMGgxHmiRFsLcPuH8-RggaK8c_I2EFG36TTCMYvme_t22uXl9MadlklXEADHpst6LeNANhwNv8QNA8kE1CbqV_02p26dnsu82Bt6TM6yyabzSdJVF0AXpaB8-E-z-0gADxyazM7sqjfWrIZfovwGnN8RNX6asmz_oFBwlvfoY_hB0oasKgSf_GvDh2M2ap5uwyv46iZ4hivo4Q5-bYXh5e5xv5BtmLvW_RDwYXp7j8nHG3fUJzDn0r0tZ4AiRlYMVMy2jg488NhQixnkCfROgLKS9NvEBdy14hyEoybk9QR573b_duCQhjYJoBzovbuNpw90nlHeDdS5K4ou_p8k3Y9jMkE6WueMzpnS94OfZ8vvTtaIbcZVGi1YDLPO2Bu8325Wo5qphaFtgd-OKEVgeJH-HluxEBYBtcCXNS9eW_MItkRHWjpeXvxm8jQ-PdWmhaBVId7UlOUeutNCkPN4oPmqrp_W9Ctcqts2DCduSlggG7AuBcgsPM3ude6W6QmxG2NRJyEXln3gk9ibVY_1A7hn8Z-xt-j4N56-Wt_8Qz77yV9sXqC5d_mIKSNeNwrlcjowU2HxnjwZhjW99LeVYPnuFfiiILVixscxIImaKdnOgeH3ytlWiSNMiQaMLDqkRPD_TvwmHVtUqr0JLjd6tweUQ41mXN2doiCNtUsuN49AjxOWAU7a9yEZufIOF6lTCNg4W9cbINSch8j2OizsbJQXyZGl-noqonxp9AR730Ac59oj2cqeKbrZ8zf34jw7PkG2WnFTDD5cWS3k-K8mV1e6Hpasynxpf8u4wsZ9fUtk5Lv354LSU-ieTKQMoYFIIVRSnNcocCAEeScL0DMykneVbJw1Wzg1V8sDqRdcj1k2ZDE2secqlLqenEp4iaDo6wwv59nJUsKN-W6w3D1kGePNya0OULwJbuYg6OYPUyYmJK9frOysMSD0y6d1_d9L3ybhS-XPyImJPSg6j2tty1CpusEsBfb-R02KyZgMC27ZPfhet6FPeM1RsNRmgJGmKmIyv40a52QPMPD4q92icV4f343bGsHU7cUUbJ1AxphNPDCTdf50dFiRbX3BFwkylyfm8xY_rcOE3SIPZcwO6g-qmmjIOJYjtyBfkYLgrYKF3YNqv2pLIUHfMbvPaI6tRnpuLU6zaGLwQOlT2x6j8EMRCEI1tdBO0_0LpQXT1wINl0tD0a8UDnUK1ZLJ8GE0Yi56rm3S3Lk4iti7qLUIAjLYsvfnAMNLNeY96bSPWXjmbpaQ7XLkO-emH5Jn + id: rs_0728b898e06dffcb006adb3938239487d0b156ce9ce5de7a7f + summary: [] + type: reasoning + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":30000}' + call_id: call_hhJwnOjF2Z4PCF1AIbeb45VU + id: mac_0728b898e06dffcb006adb3938547887d0b64bd489f080d313 + type: multi_agent_call + - agent: + agent_name: /root + author: /root/security_review + content: + - encrypted_content: gAAAAABq2zk4704nz3LAqQ7VpTpNY8XvHAaMbHa3UmYsoEoptpdA7wGLClaZUmD6haEZHJfw_yHjhXADHfncSZ8rvcEe3cnvpH3RhULSvaTU_AX8n9kXscNidw-SdyBAHaE697RGC9wtyuxXDJLG3UErfKUrbCBi8DXmkmzqIdhGD7rDwXj_LNaABgZYf7DkHlYNVTUJ8CA9PBeOPkwfaLeS198-XOhvJVAXSWrBnfv7byGJmyR2fDzs5RNmtIq9zZPbrf0IDA-evv_nW_HxcenX9mpl4n6Yh2vQRkOWJSK0K_SqkeTKUMxyr30ZRuVjgcngMv6bUf0lp6hSNvRF4dhKPk-CUqIATTGbdSs3b1sQD5wjkJfzz4k0Izg8fulZm_Gqy4KwbArsXytalgk6kX5JGCBjqrWtoNMKLWE0OYIxeyXGEm1qKwRvK8mrr_xWWKysE7NJo0sWon5yv_mgt7EWEaukNrkKLmmCIaRA2WuOE-oJw0y6X0VWxTke29secPqx1Lv9itWMSKVhuU5qR97prsYbUfVLfZnHti4qD3hOUZsNi1wWbeUj1sw7ZhDfnDCvPE9XDcWY3VHr61pwRnhkfvBsLokMMh3CqlTNPVEPQmF8WwMu1B_mdYXj9r74zOGsRLWcdk6hzFEFd5q6oymp3Mw3a4RVUqXHYF5XVcyGswXRmJKnO6HMQHmp3aNeZNVSRdl15TsMERY3JITnmy3pA3Lr_MUwZZa5ctZP5JtvEa6H08ium1H5JoLX4dZy7EMccXk2H9gNxZYcYX1Qxv7N3gmYAZvLrs_xqJO-LWNlT3DZC2uFAJcc-if_PVln7y3x3khOm27Nal8bLfFV-rZ4mir7IYkvRG17EgtHWsBArwOqH8MpVWxnKZDk5D1XmCfTyktHBRV-boAh4QiEmbAL6Sdwx7aP1LtLS3exNG6cpqCRpPUOB8kNKfzePe6pDArIKKKEhGDfDWTNaiykcPgkt01bx8K20LE2c_Gv_KG6W-z5FmTivLWvW90A0FLXbF8K1aKN_aqh4dZbu4xEHA0hONTC4oOybQ2KIIMECbaueRLJU2dgKEJWhmoCuUhPNrjGMC4_C6JeTRaEd_PdCYFlSj-l6ZnjAuEw0V7cHD2LD-JTpex6VLVbvEOplwShYPtQ9wXjEDipf0Ri0YMB15_0lOobc6IhATYKTQB5dwYM9Na5vdKCxVUzJi6TtMfm3u8ewbT0UPrx_oGSWp9ZvfOgTDkcaMGErjxkJ3CM3VT_O0gEVZWJxKkasbgak4_BrfQjN3S00Be3z3QBCcN-3dMFeUTA49mRZN1rd_cBUMoJ7z_6eIZv3eqAYh7PYtClgkjMQXWdw9pEFRY2uR4lBn0ajmP6W7boS0RsT3ZESmfG2WRS8GsB-_HrhZBV0yzgIkiIpk_gHpbxvohkjChLZxygouKRM4m40BC4gtVDSIn-7NlC6-fWEpEpo1ILrLDDwMq-iD5K27uGT5wKU8bgCMi1Ate3pTBTtpFXTY6KF7qsIV1TPdSWVAYE9UutG_4YwkQy2O35giNsokTkgpd2LKWvrwr2dcn-9x11iYiH6b0zCmO_V_8PlzrclombaHBbyzsQBs9KucUodWfKPgQy0MAkkzDrj44orzVZMfoGjeFeT4vCBaWYqwf_m9AMMkecIyEV5qeuQ5Tv4LlKTUCdzjA-djKEDWs90W3SHG7CrrIEunzEyzvB2koxU1rbwoWmb0kLOpdhfOzTF8x9uebzrILwLamn6zyinAOHRYfm-epVmxF5zSrVBmNMS9WZLMd3QozlRF9nIUySvrEnRg5PspYvG8tnxtsVs7kDnjW5IDgdym87lKYb1yM58vR5Q7QkMgULgg_U2xmfRDx898MS4Bi9rSWeth24TGy_ETdERsgHOu7T9Fo793PO4DqWytCs_KBITwRMmKDlW0D9SN5dJbRqnpapxCfADygI6o-wZx_L3iRr5sOmYxwnlv3XaC2SbxoTYrPEeEV5KfPHBNtq5xxE0qGu_EXcEV8_66yuHdsHHkHtmhto1eCAX7ME2qXhP7uZWbNjLpGzmQEb9uWv3xivWCYLrVcQk9Zgc2zjce9F3A2Tx5K5YY_ZSVyAN0MUuRSLg1TMRsmZy7A7woBEgSL6ibdmWThRZgmail7Z_cpXr9oWnNTjeu6pAZRc491BOzVexjlfW8TzasiwTjf8wTd9e1Qyv102IzFvl5E34u21pPWeHLTY1ZvnwRGXRsPwBFQ5o4zECH0arMBltsIVdct7VIapt53kl3-WaLU6tjgEwYtNS7M9DyNOp13LZcFmBdsCB1X38-jhpSrzMZFfrurlqGp6CE1JY9mSUmZHlNdxtHiIfDk3ujvzjjdYQAbNb9IyyejkW0BHNG4Ih1L7busQj6bnjk0aKLKh_0sTRdc51Fv9BCD-M5Z2EidPADuJpI7Vhyn8C-nK0A_Ykz8SaqEG0KISsPZiupgAtWahgHcgSA7P_YEjcHIihf5M3KLlVtbGgcjpEhPIM11fS2lKErbqz1zXnGxibkdXcKf4lZ2plRWdlTdKe9J0hcUrcwraWRIzMR-7CP_MxYvmDKRuv7smXgETmKZ7Gt4fE8i_1bsREt5SvORywihc1IgEmRjappeIKcbdKfPMtZHIl3H2V9gvgxXpVeaTtDIoEff3R8Rt1tSesyQvD5AOqFqUNGnarhAnkPkvHovkpGD6WV1-13e45ZIwE9Cil6n-bFwwIDBu6A2rcv_0ZbWHLkKGt3x-vSGo2Rr5XgJO0HqA2h_UwLnd89fJLnMHSxtfuWnXz_ShvXD0hq3WqCN6Kb_vyKXJi_PDYu8r9DQbUmPQ1w2lHTYKbod3d8oc76RHquNInSVQskFmCSHCbm__kYeCJ9gFoqDeU_ci_JQXZfVzCmlJy7Q88hyin9aTcyiEOeYrdli5OSa_Sex6a4fvpmaNEIyaBDyjg2gjH7IiBbMA0rMoPrwOwSAJmZUgUgbI7uks15og2nFKy7rDWKYE8uuFh_ecxdGPFgvsJuep44uHJcLuz79vbKdGO8x9HFgesdsbZ_LJY85p-vHuWKkmSnqJux7ZShVz1ZF0HQ2zUeSw-qL6MRm37_qilJlKvo6NDmr9COarFpNI3oTkfkqj_rj493o2KlYpJPMwuLY00qTaYkDgdlF1fRyiGN9gSw== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb3938e0fc87d0b283fc85b25352d9 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: call_hhJwnOjF2Z4PCF1AIbeb45VU + id: maco_0728b898e06dffcb006adb3938ea2487d0abaae5013611ba8b + output: + - annotations: [] + logprobs: [] + text: '{"message": "Wait completed.", "timed_out": false}' + type: output_text + type: multi_agent_call_output + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":30000}' + call_id: call_EehCbCF4FDjJXimRhhkpczod + id: mac_0728b898e06dffcb006adb393a569087d09d0127132a4b92d4 + type: multi_agent_call + - action: send_message + agent: + agent_name: /root/missing_tests_review + call_id: call_lJkkJiqgeFy0IKz2v91FNVKE + id: maco_0728b898e06dffcb006adb393aaec087d0a3131ac81e678268 + output: + - annotations: [] + logprobs: [] + text: '' + type: output_text + type: multi_agent_call_output + - action: wait_agent + agent: + agent_name: /root + call_id: call_EehCbCF4FDjJXimRhhkpczod + id: maco_0728b898e06dffcb006adb393ab8fc87d0ada17d482d52e083 + output: + - annotations: [] + logprobs: [] + text: '{"message": "Wait completed.", "timed_out": false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + author: /root/missing_tests_review + content: + - encrypted_content: gAAAAABq2zk82GERbxB2TMzJRbm7dWxysoYieFSOCWQZwvJtRDy9Fri_A7SqewXf-nFPTktjdlUb3-cNcMMudhqgWQ5e1uzYTxKfa2hm1xQ4dH7K0NyzauszHS8Kil-b_bYiQddTwpUmx5mBzrOALtICgQqSmME5E1JUhz-iADRxT6XHlGX1WCnm17PiwQz1hu7Sphy-4ETycre76N2tEndze5lR5ODB4eH-2u76_wvMsYfNZl2ORlzRcE1m7lkX9T5jqYy_Kq1440MPGp_ZUQW6Rt7F6SQMY_tbvzMmD1sqK_czEpHg-4CB2kl80nhYPdG4gOMqxTeV7dhBH5lWYmnT-qhgF0qAmkW86wc_bpURo5JstVDdzrP53W2nB6kcs4ZYSjOM8_Nxrpu9v3-q8EGba-hUgz6duk-Lwv587a_1O5OeuVtWLci0E4cWjfDzDS8-p-d-LGl5TcQ29O-LhxlKZBxrbC9FBOWm-82Ji8g3FZDMk7yn7ejFCXFJ5Dhbw1gLrLSin1iRnwig1nXVPsykM5FIle5XWUA0JClTiKnuPeqJMBaFboot9-VghM_LKxsN-g1i_J_4JW6t9bZMuRF4gJOtsRAr8h1LftS2cENcTjdKUlncnhkG8PuQQFNmp6R_SnBWhATYsqhjI-Vk3cEtEzo-a1KyfRMqCVn1ZiTQ4PtmJdckIdL2MttqWj5sWHkBDYiOE0CryNPB84Ll2trO0tx4XGhDbbzmwYRPLb7lTtMIwdTC-dNyAqoFcUuuvTBaw2-gRnAGFEsUjVgxnGyRtFd32tQDOsYDFTk7eIa2LK_U-EWizyt3brNZN1WfxLOYbatT80i5QFwXaZjPgkzoviAV7l-iIKmqQHRieOxNAEu9nkBGWCv2_vYa-09rX6BqjUNUSZYAQxWSBnmC1YtSmX4flDERHrcPYBhrKMNe0H0V7aakTyjuLo2k9Gv3syFGoaoBYwvMc1fJzWLr9vJBbjKvtaohDiMvdwNj0zbwYG5OjLRfX5dIuKiiJOTys1ZTza9gPHWeTJoG0MhTnsNk4_ppWQRqjHdcl8pXczi8OgxkiW7azszFSJoHIaGIXVbZOXkUEoY2jI6nhDEoaKZAdvIU0_D_YqvcV6Y0j-P-N-35Muv9jD_9X0XuaiI0uFzgunXvjltaxCNl7adc77zwAjCXHfzoGmTPHmXJWSqTOeyTVQQvh6PulazjHBuO7ycwYAg3YaxzIkF6gYHTPdrBZWPnH3w3Afe0F-Y9kyOFKANRBaFGSd4e444Pf48Fiog1ZzDyE9WnoQNRjZkTRysSJb6TXOzuHMacCbC2SJ_4KWl4dQ6-dDVcgXwuGq6ATuHRbS7Crum9Z35t_vtCE4e2aLd1c8hf1QRgPQIVZ5q6rHdk0stN2ds4K_5pK04fGkn7LAW8GlYi2uDA6RddyBpr3URR7T8J8PW4A1ST3MUHZF6ygLnW_iKUidM07qY8rurh5MDOyUkTfAxlT_e8p9-RolNQzDQkzYY1mrCwW8mGCt0iRlKpy7Foyy9G1syKL_WPFRacZ03PzJ9SmT9TecKB15O4Ar2grIxM-5F5X4895_s7tIyIv9yJSDkkcYaUg9TlbOg6zQzumShLRbRKoTWH8Inw58_kxPz665JBc4iH9gWjrbOvGJ1NROjH1Nae_MwpLchtwPUOkdq7POs8lbD0UCAU7nXnaVbsMozqIJlfiFOftEd6M-f27kW0rf_MqOPn3aX6oSi2PLXybPl1f9uuLeMo9X6GHdv0co4Sqo7StVjqbQx708VvxN5KM6nyqLb9vfhzmd9M3P7IZf1BqH-VggOEA5zrXcgmEvUreOq4ycY-c1fa7SHbAe8rxxq7XB453us2nTMKUkmwTX6gR6iKWM751QALUsVU9sUHvY2Cn8OYwWLEELCoXrb8Va5hlkXS4slJlwXb-tnSSpVmC2z9NAkO6xYECdO9AJ5ikrriYDL_RzEXJg63E3iNjnefeJIumvUsL2qZB17P8cdiNtO-LC5GwoDbU_8ikWbZh0ttmXJrvASKZ5wJxrmlNqX79cEjmZ4bnHt7xc5JmlJHIcfCLpzypeg9RA8tzvQx41UzcAS-YqN3IFJ-Yql9bGFsBJ-A0a_9wYW9LNAbkKntEZQtdnb8q2pgUnwVPrBq_vTIa8cTWfJjdjmek9ubVQH23dctuMG6u0ZqHuixVxLeP-z5FOVyYVGaN2YKKR52vL4cXity70BAO2D25H6DRItxFeoFu9tlAw_vIyjkNdSb4ZIQSg3GPDqd6X6VGzKvvovPXeU5TYTqMjzMHE7p_Lufne9piqhH5Yhvmuts0tjv1FQNjPkO3Vddikyeo-IVLGBGZQSfNMNlSQdLp-ftcpXCSvXXsHpNEUyR70AEaAmovsXQLbAhJhMHJXFYw7aTIMZSrFo93RUAJ3qcVOKLp1QthechnCewWDB9INjq3ZDNTYyJPLvFmJ6YMqq4IMcEYDCas2U_15rFt3i4q-8ww0Fxa6U9Wj2W9b3whc3e27U1D48KLOVRI20GjitqU0eUtBt_OeozlCYb69sV4lpqjU34rCUo2YJX7Jr_7XriRUIMTI68WzK2pdydvsIlX2sixnE4ASupxB4Qyjo= + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393b357c87d09dca5b928ba01c53 + recipient: /root + type: agent_message + - agent: + agent_name: /root/missing_tests_review + content: + - annotations: [] + logprobs: [] + text: Missing-test findings sent to the parent, prioritized around unsafe + deserialization, cache poisoning, mutable aliasing, resource limits/expiration, + concurrency, validation, and multi-user isolation. type: output_text - id: msg_01014eb0097f04f3006adb2dc6d8a4819bbfdcbd36d9ea6696 + id: msg_0728b898e06dffcb006adb393c11b487d098917ff83c68a5f6 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + content: [] + encrypted_content: gAAAAABq2zlMkX0WAO8MGHvbS9ysMSYNz6K6-HjgcwCK9aM_nIoIJrYXRqW1CWZdsP_ZN6Vno_1msd1h6ClYKNemrDt68eVUoD3pqDcdB7YvPWunLXFJLBHb934YH6Hu_soul_PSTBAjCCI8pHp-AKL-D5_31Hu5HC5BiHe_PSWDax7QJqJ6fCWW3qyNxWUBb8rITLBOKlodx9YAuQraiAQkNHFPnmrui5G0viTN2149eTuV3FJ_ldw5f2fk3WgvgKNW1f-HyqFtnQeSF9SwEu6e0gw_RqUG4FITuvYucSCGIAiPq29l5eYSuDBofWuMbs9zU9yQFwLNnOowgpHNsNNsRPKb8mclZbaWNNGTGW8mcC4l3uI04kjKEFMZOLFKeFimOrBxjU35tpSH1VGfVYJRcVprGdXe0QPiS_arbWlN8SyoNA01NY8CW2a5N9dy6OexJCQPMrLytZpTV5X_PggMVoRQhiy44lJR8sI_MWerNm84KnxmAu4G5iSt66EKlmRvXi-6PqMM2OvQQHETTerBmA2Z5Ji_wQH2OGiMczat8Ikaa7KxaSJWLXRPT4ihaRiqmFqqrrUnluiuSJvz1m2dWUaV92SmdIKFFpUXrxVghtRZJ-2rsH3F36Z2RQRLC5lS5wECu14nMPvP__tSniBW0zRIj4snVRLN_Ns2k_9iMVo6mEXfDONT_A0lEzdZPfo-XcDRiJN5gb8ab4wF5Y-JlsZee2fdhQq363GU3oUpR6LtY62IbUPlZUJBJgrTSgFLcGZPN0RUJ1YcPPbHYaSVYv_pZjiIFr1DAF0n7ysGDk2rwb11cPIfPJfVHbMTsR-_2Ij1WF4Lw4gGppg5RHQWVv3YGS2a0wWzXCJjAt61IpbFjQA9dn-kXNBA5HGufSLg98egN6es25Uiydt8W0cmkdL9aYUUvyuVh87w7nsgFTDQ6e6rqyIvwfRUj-VbfcYAnrCy8zmK8-Fl9bxR0N9qttK8zmZ7t6wmTk3kTGo33skCHiUOfnqdoGKIg5wGLYgpTIFO86TcQafdt7XGUpuZ-7dVSJh03mlYGPv2xq8ZawoH0SNMQhO2L0HrIZcM4o7v9Jp7p6Wws1pdZbwlMsuOORnQiZJxrBVC95YUN-2Uwr5siAYxM0vKBS_j7RX1s4SkLLWpD3dSecAwfp8tYDkjXuK-OYDk4n9G5DeHcQXjFv9wrwWYpdHA9BhI260TfawJoHLEkIqN7HJgVGGFdw6TxVzLdBD1MJ2HLRu9VyPZkUIW3MFGCQzEO8l0g8yPC5V6Nnm23DmAsJw2gRg4NRpITr4wakTZCvA4MVfUO0mijyj_gI7Ij5Yi4XnlLLptRr2A_HjlFK3qnaAkWy7dDtpk85bccFcexF-8EBjDoLWcpszJuuDf0qvC_Y0S-UpJEyXByEUaLD65os9x_uYTXI7UiwsflhDvyiHopA0ecQPfgvHRc15NWPyw83YXJFLg89Sxwq5J-UMkTuIB46WiQXHSxz7XfHPCiM_pySLc8PZAN4Y3cYrlao8urXFaPz9uIl1151UD2loW3GI35qj2NVCOFmeybw6ovbwy8JrsGNERvUldI-l02B2CDC8g_fERoVfmFezsBLTqOAb_HT2hGj7W1XYfV9n3cUGMZd3aZQeNMs21Xar9Zj4qD1C2aVVyk5gbxwG9YWYpePpKjiJ2M1daYjSoYOOvxd0dTrRWzIHDAsYBY8mzltieBwv_2IAvIS4hUx7g35vuTtIdn-f14_iCpyDLBQHd0kA1P89po30pFXHgwaaIMDCqCm0oOgtqBvah8RGv04EIcKqnBsefuhM5Yknu8pAi67dSkFmDwYsa8hvECWiiPGPyubrIqw4ei6ht9zXFh5ktnXN0RjhyvKwTgiZqy64YT3s3Ohf317hoWP4sEBmawnCyY3MZEeJGD8Gio2sSPKCrIVCeMX94Hgxi5Zu9T-akoBpaewJao_uzDoxcvNlyYqW3BwbR4_RZHH7dx8RK1QlytAqotEe-IT_PEkKgqSSyXnQIHyLXihMa1R4wjn3n15D7K7rusnqU3l38byOoaDnsRAzHCaQztYo4dAc3xUMyt1GgIY_R4--zqgW8YSiDoSiFSHnot15LfSul1asdWpbnlCjm5-OFnqaHvaO5vVSf74kwwWlE74sm-MtTq5malFumq5S6rFvi_25m2gXEzzE5hNfl_yOOLFUufDT0oT-baA1wbPlNwLZjzStXr3DZoAb1_vIxKC1GOr59KkwSenrbvT1Yn0AKfa1_B9JoIwgtImYa2OQGy2x1Wj-9elO0yC8za1VowInRDwI4LHXRqGKKkS23kblccMq6AIb8rothdE2NWa5qUQkAA7qyYRH8aynAKNtG1wiuJRJPxwp-2bS3fbABd2OBhzoBNJys6i6txXXaQ6jQ7Dbsk3JaXs4PMl7EcUJEvJFU6Mv4H6I1ouUG0Hx-dIhPB1OEhllrfOYHY6diWHBFEX7YllX-xYaYxLEvd6hlk_8ygtMDqApQLdEf2rstd4EHVGVZluNerW6nFAQJ_AsvglharUMCLyZ1HaMDvP-LTSSK5gVnwvGPj0qYtSKxQzioe5b96l-Huoli_h_iChRI7briUnpKA4JWoqyhbIwgp3oibOEda_4B8t-HW2hxXhcmwtJGdO1ocue5vuY2zpe9VjOMsBNktyMvNM6VnlLPtaPO18vuE9K58cbs9TzkzsjYLwrt1yxtUt_WcgceCAFIqq1hkdC7VyQ_G8e8z_ZT3D6OqClG3zTpZzlI0aldGcMxabsByYoaurXV3_4ruGAL-IYvMhEr8zlRpf82xSmnSMbgsfFiBzZuCGe_02DwW_2hpVtFugfBZS30Cxapmw80GyFvkyD920eNdzM-F7vx-KLKbZaFv-AGSkj4FJCxNWkIY7n8M8Lb2tOu5eTnilg6_PpC1aRRk9GFPYi4j7qzrMHIZXHkC6ViRN8UAu1belf3hABXGUXdd70Jxg-MZ3qq_4ZKBsNVAay7WykHwI2MSdNuusldqetNHxVSOnB5IldcnxCgeSC_l3iNltizj6eEu3nzV_l3BHg7APjjVz-TMeN_7XYdpAxBjL4w1w13ovfzhdKdPiRsIJsBp3Xf-PHxIt5NFxdrsnAnuinMT0EH5IVK8UpukgNSLRKgCV31TNsU7RrC5ZRO4-9ZZn9Y-nnw2Oi6vZlbxHZ21gPv2ZyPvXoC9gshAcnbUmj0YNt6XPHwxpoWkqS-jnzyMtBsQytAUiMCwAxHLalLWAa0aB7v79fcAbPtGINfOAcrM4l0STAZOxdDoEfqMdASP-n37V28P-QtX5UiIiDcuIPP5P3hGHYLRjB4xHiFaEUyC6ObBEiyLOvAOf7KOgKy-xSM-JVdLA1YwnIBQXnMY6U821Oe5ZMmuXC6rsrCEKaTww_n0pO_IUGd8sNXaJy_AAFktWyLeQTD3bJPUUODkb6gg_aIV6drOm-1Vqs9F03uFHDS0FEEoZ7i6tEHiKygm1VV8sacq6S7DVB4ybHq7GUj-PoTYIgqZCCIFhP15u_z1WDI7P_y9FfPa4_ngWvBTvErd1DFyNgl1eZhWvBE-59sJ4r74gSjOdMH426kTF25KORrPrqIr8Gmdi5yWYJMg79RyncD0lBm_cyL4IrHdF5M3GkBKJv_hceoBl69BAJmhsyZSOOBwafPzREXKx5raXoWNKolCaHQfwQ4a8Df-w43ibnju3v3WmAhrMK9pjW1GAHP6ox5X6j5yAWp5QVSsn2Xa9HmvfbWVoOLUt-dSDSkYJm_Yd7RaqpZ2yh9rdJYRxqcmGSjsAT7n6TLdyGL2YQ0fMEjy312xqFgsPwZ5pU6hk5KfSSV1gGUCgOYgj2-ZaBtRwnQk-HfDdK8UN0GCm-BJJx2dX6CjcoYmHcUNLojryNwTVfpJOnvLRchc1AySe1bFpwVuxoyeGExyL72u15Sn4uByPiPLLF-IN4CrWz7yV1vpheLh3DH5PUEQ4Sp3KaFDoUFstlnTNgamygxbR6-s_Xhiq6PLccuqbPdnCB5eGLtNhaVYHCgf1e_BAxxbJI4HCEXZX-SXIkOG18uh1_VHEtvjKskAFy9M-GlZl6GZD8lHpKhan-Ji7InxgxlSOX1Pe6CrWNv7ku3ZXDfYVSnLAGeuk5gIHDugq_yguasTquz3Qml05Tk0QgLgO76Mz9iyab6dcyKhrl1RR7og1S5656bB8IPdqYiQnMe95t7cR6rwDrggvZ00Frk7C9xiCE1-P88GLWaP7Rm4cEesgmRhE0cXFWPDcomh3JGk3yEJ0blhUyFFgN2rE6_7nIwfUfHT7uZ9C5nXzZJygcIMfv5xeZ_owj5JqXRXhcOCU5xyTuNJvzpFKtv4dJBhqOtYy__pRWPQ03mGt5ErkLaVcGU_NFisJY69LhNspw2636G8x2GngD_zZiwx2kixhRu28fBKBuRur9BgcsSmcUXkeUMX9n5JBp0QDB8y3rkkFkl0lNKeQ2gIdpuWxFJEtmwu9iAk7150C0sY-r4S0k9z75oAZ6GBOy5VVO3WgWY2Y1U7fytn4rk9_oTc_dzFE7hkoiYd85dmDnb6cU73r8gMyCZJg-yA85ebhXbH0GoMS63Fze9hanNqDBct9WBlxrDuTbAl5bgfJ-f9J1LDz0Ad87lqES5D28yE1R-ycQYOREzWfMfUnD8dwEB6hCcTmxm0o6PeUWXuzB3oxhXlHtJjb2Yv9op9yS13h1cHY1tLHlx4hDeusi1ZBWN8mF5puV9VMS4Rj75hsxrOSubqeHPEkL9tgVvoovBZwGBuwb8MEZjjFSOZqbTMjTcxpmanvbUk9_kqIlxSxgKgVGuGcTXhNxF8359NGliPX7q-HR1It83LtCWnhr47c3dWXVjm3YqK8b4S1vhJSOJV8SIkXjDrTX_2ogWF9MmNu2CQsaeKKALzHAYjN_52QHPXsS77Do-EZTBTg5B1pFg0pAu-XMHfnIx71AYdYHGerCa3553dtUvM3_FdCcBIzFBFFB3mnfFUDNkM9idQTkWbBPw0XKMmrkUtD2mxrvOnMUfX0mQgSKFWO0ew28rPY-TtIm5cxoZJxOb70ITUrbZQWf7UtEu1Jwe0CSF4GlgC47rjx4UQdxy42PNs_sC7zxb1EurFknaWYOPobthXrIt67Nw4n7TBRkM_b_pZ48D2exX5KI8gSa_OIq95CyVtvKsAbuTH-Sy + id: rs_0728b898e06dffcb006adb393c3c3c87d0ae6a94107f916ae9 + summary: [] + type: reasoning + - agent: + agent_name: /root + author: /root/missing_tests_review + content: + - encrypted_content: gAAAAABq2zk89ydXgHp2WpRvXWu1Wvo4LU18Q9Wb0Gq8qiM7s2HslOUpktFFvkEkR6cMXAUzDAd-yplqTuEEH3FC3UrkDnkfnEoRBp11gz8H3xII0aCBYiM-fbx1XySMHoHCQX-VkIYADgv7XJOoB-Uwg3NekWPjulFhkTkhT17Cmd5hGaZsXw2wuYSItSrdTeOJ1nMwySftxEeRUbk90tdK_ASpSiSA-d60pf5bgdd8cH_AzncdXHjNuM8RFtzcZZdk8lOWBorWMJd7LvJeZ_xU4KgQaW3Mf9ADVZBX3hWh3GsCm74oknBM0Rrsy3vcslx7k4d1qH_hqwfLhVZoPuHknfAD2KnoS3dZ0Rr53X9GXNvyePti9ToNqSsrkt9x8UqmeGXhUgWykIc2Mc8248uQq3JWSAIdQldxgEhWsWedM3M3dPk8QkqW9fSzuq6yX1Gfg6z-BDIEZUdGj2LHwG41hNXt0x3KGm8MHEmyLYZePKzqhHRG7g8= + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393cd0a487d0a64936a3faf3889c + recipient: /root + type: agent_message + - agent: + agent_name: /root + content: + - annotations: [] + logprobs: [] + text: "## Consolidated review from three subagents\n\n**Recommendation:\ + \ block/stop-ship.** The change introduces a critical remote-code-execution\ + \ vulnerability and several cache-integrity and availability problems.\n\ + \n### Security review\n\n- **Critical — attacker-controlled deserialization\ + \ (line 5):** \n `pickle.loads(payload)` can execute arbitrary code\ + \ embedded in a pickle. Because `payload` comes from an unauthenticated\ + \ HTTP request, this can lead to full server compromise. \n **Fix:**\ + \ never unpickle untrusted input. Prefer JSON or another non-executable\ + \ format with strict schema validation. Authentication alone does not\ + \ make untrusted pickle safe.\n\n- **High — cache poisoning (lines 4–6):**\ + \ \n The first request for a `user_id` permanently controls that cache\ + \ entry. An attacker can seed a victim’s ID before the legitimate request,\ + \ whose later payload will then be ignored. \n **Fix:** bind the key\ + \ to an authenticated identity and load authoritative profile data server-side.\ + \ Define explicit refresh and invalidation behavior.\n\n- **High — possible\ + \ unauthorized profile disclosure (line 6):** \n If callers can choose\ + \ `user_id`, requesting an already-cached victim ID returns that victim’s\ + \ profile without authentication. \n **Fix:** authenticate and authorize\ + \ access to the requested profile; do not trust a caller-provided identity.\n\ + \n- **High — denial of service (lines 2, 4–5):** \n Attackers can submit\ + \ many unique IDs or large profiles, growing the cache indefinitely. \ + \ \n **Fix:** enforce payload-size limits, rate limits, quotas, bounded\ + \ capacity, TTLs, and eviction.\n\n### Correctness review\n\n- **High\ + \ — stale data (lines 4–6):** subsequent payloads for a cached user are\ + \ silently ignored forever.\n- **High — mutable shared state (line 6):**\ + \ callers can modify the returned dictionary and thereby corrupt future\ + \ responses. Nested values also require protection, so a shallow copy\ + \ may be insufficient.\n- **High — race condition (lines 4–5):** concurrent\ + \ misses can deserialize conflicting payloads and overwrite one another\ + \ nondeterministically.\n- **Medium — missing validation (line 5):** a\ + \ pickle can produce any object, not necessarily a valid profile dictionary.\n\ + - **Medium — unmanaged errors (line 5):** malformed input will raise deserialization\ + \ exceptions, likely producing HTTP 500 responses.\n- **Medium — process-local\ + \ behavior (line 2):** different workers may hold inconsistent profiles,\ + \ and all state disappears on restart.\n\nUse an established bounded cache\ + \ with atomic population, expiration, and invalidation. Store validated\ + \ immutable values and return defensive deep copies or read-only representations.\n\ + \n### Missing tests\n\nThe existing single happy-path test is insufficient.\ + \ Add, in priority order:\n\n1. Untrusted input cannot cause code execution;\ + \ malformed/truncated input produces a controlled client error and does\ + \ not populate the cache.\n2. An unauthenticated caller cannot seed, overwrite,\ + \ read, or otherwise affect another user’s profile.\n3. Mutating a returned\ + \ profile—including nested structures—does not mutate cached state.\n\ + 4. Cache size is bounded; TTL, eviction, and refresh behavior work as\ + \ specified.\n5. Concurrent requests for the same uncached ID behave atomically\ + \ and deterministically.\n6. Non-dictionary values, invalid schemas, oversized\ + \ payloads, and invalid IDs are rejected without insertion.\n7. Multiple\ + \ users remain isolated, repeated hits behave correctly, and the global\ + \ cache is reset between tests.\n\nRather than testing that malicious\ + \ pickle gadgets are safely handled, the preferred redesign should eliminate\ + \ `pickle.loads` from this request path entirely." + type: output_text + id: msg_0728b898e06dffcb006adb3942a2a487d0ab2f97a66cc8bdfd phase: final_answer role: assistant status: completed @@ -3466,19 +6543,19 @@ turns: top_p: 0.98 truncation: disabled usage: - input_tokens: 134 + input_tokens: 7987 input_tokens_details: - cache_write_tokens: 0 + cache_write_tokens: 1223 cached_tokens: 0 - output_tokens: 5 + output_tokens: 2922 output_tokens_details: - reasoning_tokens: 0 - total_tokens: 139 + reasoning_tokens: 379 + total_tokens: 10909 user: null headers: content-type: application/json status_code: 200 -- filename: t38 +- filename: t47 request: body: {} headers: @@ -3486,7 +6563,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_01014eb0097f04f3006adb2dc62acc819bbcfc572bf84f8e0b/input_items + path: /v1/responses/resp_0728b898e06dffcb006adb39258f4c87d0ad23c42f61a95c20/input_items query_params: limit: '100' order: asc @@ -3494,97 +6571,47 @@ turns: body: data: - content: - - text: You must call the agentic_raw_echo custom tool exactly once with exactly - CUSTOM_CASSETTE_OK as its raw text input. - type: input_text - id: msg_01014eb0097f04f3006adb2dc63ff4819b91d239d7b351104f - role: user - status: completed - type: message - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006a71c72e2d40819bbefefdd49524c78b - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - status: completed - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctco_01014eb0097f04f3006adb2dc64020819ba310f92062beec75 - output: CUSTOM_CASSETTE_OUTPUT_OK - status: completed - type: custom_tool_call_output - - content: - - text: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. - type: input_text - id: msg_01014eb0097f04f3006adb2dc64038819bbcab940a76439917 - role: user - status: completed - type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' type: input_text - id: msg_01014eb0097f04f3006adb2dc64060819b8b0f4e0b6122c6f1 + id: msg_0728b898e06dffcb006adb3925978487d0a45c51be5137805e role: user status: completed type: message - first_id: msg_01014eb0097f04f3006adb2dc63ff4819b91d239d7b351104f + first_id: msg_0728b898e06dffcb006adb3925978487d0a45c51be5137805e has_more: false - last_id: msg_01014eb0097f04f3006adb2dc64060819b8b0f4e0b6122c6f1 + last_id: msg_0728b898e06dffcb006adb3925978487d0a45c51be5137805e object: list headers: content-type: application/json status_code: 200 -- filename: t39 +- filename: t48 request: body: input: - - content: You must call the agentic_raw_echo custom tool exactly once with - exactly CUSTOM_CASSETTE_OK as its raw text input. - role: user - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - input: 'CUSTOM_CASSETTE_OK - - ' - name: agentic_raw_echo - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - output: CUSTOM_CASSETTE_OUTPUT_OK - type: custom_tool_call_output - - content: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + - content: Do not call tools again. Reply only OK. role: user type: message - - content: - - annotations: [] - logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK - type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc - phase: final_answer - role: assistant - status: completed - type: message - - content: This is completed tool history. Do not call tools. Reply only OK. - role: user - model: gpt-5.6 + max_output_tokens: 16384 + model: gpt-5.6-sol + multi_agent: + enabled: true + max_concurrent_subagents: 3 + previous_response_id: resp_0728b898e06dffcb006adb39258f4c87d0ad23c42f61a95c20 store: true stream: false + tool_choice: none headers: accept: '*/*' authorization: Bearer *** content-type: application/json + openai-beta: responses_multi_agent=v1 user-agent: python-httpx/0.28.1 method: POST path: /v1/responses @@ -3595,33 +6622,35 @@ turns: background: false billing: payer: developer - completed_at: 1792749002 - created_at: 1792749000 + completed_at: 1792751952 + created_at: 1792751951 error: null frequency_penalty: 0.0 - id: resp_01014eb0097f04f3006adb2dc8bda4819b88feec8f807aa201 + id: resp_0728b898e06dffcb006adb394f375c87d080ede33befebaebf incomplete_details: null instructions: null - max_output_tokens: null + max_output_tokens: 16384 max_tool_calls: null metadata: {} model: gpt-5.6-sol moderation: null object: response output: - - content: + - agent: + agent_name: /root + content: - annotations: [] logprobs: [] text: OK type: output_text - id: msg_01014eb0097f04f3006adb2dcae238819b83a09efc49b21b6c + id: msg_0728b898e06dffcb006adb395058c087d0a4a161f28b8aa32b phase: final_answer role: assistant status: completed type: message parallel_tool_calls: true presence_penalty: 0.0 - previous_response_id: null + previous_response_id: resp_0728b898e06dffcb006adb39258f4c87d0ad23c42f61a95c20 prompt_cache_key: null prompt_cache_retention: 24h reasoning: @@ -3638,7 +6667,7 @@ turns: format: type: text verbosity: medium - tool_choice: auto + tool_choice: none tool_usage: image_gen: input_tokens: 0 @@ -3657,19 +6686,19 @@ turns: top_p: 0.98 truncation: disabled usage: - input_tokens: 134 + input_tokens: 4279 input_tokens_details: - cache_write_tokens: 0 - cached_tokens: 0 + cache_write_tokens: 3053 + cached_tokens: 1223 output_tokens: 5 output_tokens_details: reasoning_tokens: 0 - total_tokens: 139 + total_tokens: 4284 user: null headers: content-type: application/json status_code: 200 -- filename: t40 +- filename: t49 request: body: {} headers: @@ -3677,7 +6706,7 @@ turns: authorization: Bearer *** user-agent: python-httpx/0.28.1 method: GET - path: /v1/responses/resp_01014eb0097f04f3006adb2dc8bda4819b88feec8f807aa201/input_items + path: /v1/responses/resp_0728b898e06dffcb006adb394f375c87d080ede33befebaebf/input_items query_params: limit: '100' order: asc @@ -3685,54 +6714,468 @@ turns: body: data: - content: - - text: You must call the agentic_raw_echo custom tool exactly once with exactly - CUSTOM_CASSETTE_OK as its raw text input. + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_0728b898e06dffcb006adb3925978487d0a45c51be5137805e + role: user + status: completed + type: message + - agent: + agent_name: /root + content: [] + id: rs_0728b898e06dffcb006adb39269b8c87d0ac8a0d890240ef0d + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"correctness_review","message":"gAAAAABq2zknHsxGichM95AQ5cTbugSDz7fRYR4L7MGXsAq4EHmFVajchfnHxYgRjMue7v_AHmHhurrxo1INngNkTcRjGQcTjqisK3V87FCWUkmo4Gyfdlxr5bx_t4J_Ek87mx-fG9ze0EP9NqUy3_eVAgXyaz6mZXxjt7z9XwBH19o-oLm-jXEnyJSipOBp_IPDzFz4AGyUjxy1ycqDwt_3Gh1kSQNgAmM54_gs04fSmqH6zSL4tH3nukUj62K8dBaahEQ9S66uGI75h5Kegf0JlyZTyXKmeHwpr-YTnVYrh7WSi0FA07Q20UkqVXhbfxe_BFyNbUx_sZvv7F9YNffOdLpWfBuoUsCadk1iIV7aWon9DepT6uU7DiX1k3WWMtkjPV7RDW4_eIhtMQy0aCNI2r3Lr2UfXg==","fork_turns":"all"}' + call_id: call_FuDavN3956eBtvCyLaIZmFBb + id: mac_0728b898e06dffcb006adb3926fa7087d0add620154a8e5964 + type: multi_agent_call + - agent: + agent_name: /root/correctness_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' type: input_text - id: msg_01014eb0097f04f3006adb2dc8c8c4819bbfcadcb9d1edbc3e + id: msg_0728b898e06dffcb006adb39279a5887d0b6c976e1977acb69 role: user status: completed type: message - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctc_01014eb0097f04f3006adb2dc8c8e0819b8f64300ac362aba4 - input: 'CUSTOM_CASSETTE_OK + - agent: + agent_name: /root/correctness_review + author: /root + content: + - encrypted_content: gAAAAABq2zknrtdcnjiGht4ds5zqh8N7_b1v3QcFvd0NzdEBzXyCMYY_oqDaLc_OfcYEDvYzCjfAyzs4v_3B40r60TOq-DxDHI5AC1rnR276fROFJECNykUcVFXeW7OW64r8H2vilVk00MwKAGyWAuF2uooTwX758nLa1i8-vdPnlR2AqC3b0h-ReHR-SlgWBQiNaj584lE8IOFMrR1DTOloo7Fk0rb9EAGaVYSM7Wb06iboeY9GPy4-SJkkfSC5DwXAtMgTnhlK-1LSt_uMf0QC8QZvPecsJmRYDpELrg6FT_Z1sFhJqqzI2-fcbpmI2O_gXslcfBpRt_Gew9wT4KA7d-VBUU37DMAYz7byaYbEBxxr8SJ-E82w7il4oW9jbHQWnhUSXSE6YeHj94ugp4VaIcQiLG4aBXlmdxIphHxgSKF5cei4jFjg5UAJLNwhLZ4Xyc-QH7NVrCvpaxWHlhhY84B_7jrioxxCjGuijRwJHU7WYezKQKDn5JAOtpQD_sTBPTPlI4LmeT1nBUrS8yF9xGjO-Db2Dg== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb39279bc087d0978eaefccd007f17 + recipient: /root/correctness_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_FuDavN3956eBtvCyLaIZmFBb + id: maco_0728b898e06dffcb006adb3927a6ec87d09a6fd459acccd732 + output: + - annotations: [] + logprobs: [] + text: '{"task_name": "/root/correctness_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/correctness_review + content: [] + id: rs_0728b898e06dffcb006adb392838b087d09219230d65443951 + summary: [] + type: reasoning + - agent: + agent_name: /root/correctness_review + content: + - annotations: [] + logprobs: [] + text: '### Correctness/reliability findings - ' - name: agentic_raw_echo - status: in_progress - type: custom_tool_call - - call_id: call_JDSSJwEPgXXYnkfXkb7CLVLF - id: ctco_01014eb0097f04f3006adb2dc8c8f8819ba473098f6a4b24e9 - output: CUSTOM_CASSETTE_OUTPUT_OK + + - **High — stale or incorrect profile returned (lines 4–6):** Cache identity + is only `user_id`; after the first load, every later `payload` for that + user is silently ignored. Profile updates, corrected payloads, or reused + identifiers return old data indefinitely. **Fix:** define invalidation/refresh + semantics, use a payload/version-aware key, or replace the entry when + authoritative data changes. + + + - **High — shared cache corruption through mutable return value (line + 6):** Returning the cached dictionary directly lets any caller mutate + the canonical cached object. Later requests then observe unintended changes, + and concurrent callers can interfere. **Fix:** return a defensive copy/deep + copy or expose an immutable profile representation. + + + - **High — check-then-set race (lines 4–5):** Concurrent requests for + the same uncached user can both deserialize different payloads and overwrite + each other. A request may cache one profile but return another depending + on timing. **Fix:** synchronize lookup/population per key or use an atomic + cache API; also specify how conflicting payloads are resolved. + + + - **Medium — unbounded, permanent process memory growth (lines 2, 5):** + Every distinct `user_id` remains in `CACHE` forever, causing stale data + and eventual memory exhaustion/restarts under sustained traffic. **Fix:** + use a bounded cache with TTL/LRU eviction and explicit invalidation. + + + - **Medium — no validation of decoded object (line 5):** `pickle.loads` + may produce a non-dictionary or a dictionary missing required profile + fields, violating the function’s apparent return contract and moving failures + downstream. **Fix:** validate type/schema before insertion and reject + invalid profiles. + + + - **Medium — malformed payload failures are unmanaged (line 5):** Invalid/truncated + bytes raise deserialization exceptions directly, likely becoming HTTP + 500 responses unless handled elsewhere. **Fix:** catch expected decode + errors, return a controlled client error, and never populate the cache + on failure. + + + - **Medium — global cache is process-local (line 2):** Multi-worker deployments + can return different profiles for the same user, and all entries disappear + on restart. **Fix:** if consistency/persistence is required, use a shared + cache or backing store; otherwise document these semantics.' + type: output_text + id: msg_0728b898e06dffcb006adb392b70c887d0b21da0f74784b1d1 + phase: final_answer + role: assistant status: completed - type: custom_tool_call_output - - content: - - text: Use the custom tool output provided above. Do not call any tool again. - Reply with exactly CUSTOM_CASSETTE_OUTPUT_OK. + type: message + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"security_review","message":"gAAAAABq2zku4hAewQpr9HOv9tYtg_YVd3W_W8mw54eDD_f60-BS--PTXS9baLVfgGp9BPTSjiMuQZAhqloCO5aYhNBjeHVGfNUjwFVaqgJ_QkOlVecmElCpWBiKx9KquU99WJbf9G7d6ZcJitS98mLzR8PdEqc97YWu4cTE5JNdQm_HFIGZvZrBcZbxi9N3o0awZTCd1_GFZPql5vLnanc28sAgVTKJtnukZdNB_mJfJPAgG1xwmX0Z0qAkmXaNyzTwp6Fc8bjRTG2VvCbNe9tuh3lcKzCHt5StSnRG1iKnWPS_cRIm4R1jqbrnsdh_B75ZkQd6tJDBiLxHGxjq67o-mpsEyu0CzftAiGMtvlQhFleiH5mkReFEYQuZqWodRM239H1hbICpa-hLoSFwe0XxD0OHPFzuvgcR3mbPYOTgAj3nrsZ0HuA=","fork_turns":"all"}' + call_id: call_UDRhRMC2GExMCPFQL1gBhS2x + id: mac_0728b898e06dffcb006adb392de8a887d0b4a0af170fa2f2e9 + type: multi_agent_call + - agent: + agent_name: /root/security_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' type: input_text - id: msg_01014eb0097f04f3006adb2dc8c90c819bae5146fe70453d7e + id: msg_0728b898e06dffcb006adb392e6b8887d0b5214e1b597773fd role: user status: completed type: message - - content: + - agent: + agent_name: /root/security_review + author: /root + content: + - encrypted_content: gAAAAABq2zkuG821zDn2CEZ4xL4Zo0Buh_uxng7MM6zKpQPfDjcnvmwxdv_1XYLrimDUFonQHy7C19D04AByo1WpO3vnn6qylNDnMTvHerM7PNqE3ctej3rUWsO83ztVvwkwvOargMVYgfa0hnI7jSNIeFnWeT_8zEW2oNzCvh02WW5QBcDgV3h-_8j7MJRZm9fZfyLP4702cVywAV53pcZSx_7FZsPlzwnkQTJVAVhAItPwh46fq2AdfB7zwPsXQW9eXDMuZivGdLNtuw-nLSwmiYY4yIDfc5CbjhSHLFD4oZ5APKaRNAvDAOc82pH8QFr466D6QmwwHxeCHpz2ny-WB_JL-SIG2n12pt_QgwB-0QiuVI5-5vELb6RONK5SGsOKhhPtQZVbyDlubH8NA3F3s67EpkyXgipfDzYphKDQHWxvpGOGP0TT7BRpvO97eJeiOwxsdfQJHlYzkWcG-eR8dOinoZafj57i3FC0a8Ee82pnMRUFrZelI5FwagPPA16XPYL-usitdfhn-BYXUeobAIipPkp9Ng== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb392e6e3c87d0a2cdd09f589e1b25 + recipient: /root/security_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_UDRhRMC2GExMCPFQL1gBhS2x + id: maco_0728b898e06dffcb006adb392e786487d08220ef8a4e87669a + output: + - annotations: [] + logprobs: [] + text: '{"task_name": "/root/security_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/security_review + content: [] + id: rs_0728b898e06dffcb006adb392f034087d09be0c9ca27181d72 + summary: [] + type: reasoning + - action: spawn_agent + agent: + agent_name: /root + arguments: '{"task_name":"missing_tests_review","message":"gAAAAABq2zkw0jNDUF5j71HJvBZWk2kCzJJ5kYTFPd-GXYEdMvrYOcqYW3ng-4OLQBlfhzOZ_V6Zr1uGD9OZsQoS8aGIqvfDcsumPVK6P3omVDEc9FlewSfr5hqAeO7LJdFz_LVsI8lNEdrUwwSYzaqPDvVQFIDr3Ckg6L-B0HU9fJfCdy5BDBBQJByCK7cwCCynu7TWS6_DDqXLZfAr6tBDgzJzDkMcD0uHeNPmM_xGLkqJijDO8RBaCsoyfF3I9cD6tq9o8-7T3T7hxvs6PJgYDsDXvFKWhxlsPsznWVXD0RlktjTO2Y3ZCdnwF2RZP51CVp66Gopi8N_RSQLuJXqDZ0r0eG49oqFHuYq4dUjkhdZepXw-7mfS8wM95_Goqo7rtmyODDRzuJjxmW6bbv7g5LygegXhaLzYKha6wYjIi0nv4JSyoE4=","fork_turns":"all"}' + call_id: call_qZdIWvLPqnp98UcZqDcegXfv + id: mac_0728b898e06dffcb006adb392fc0f087d086ff3836e0edfdc1 + type: multi_agent_call + - agent: + agent_name: /root/missing_tests_review + content: + - text: 'Use three subagents to review this Python change: one for correctness, + one for security, and one for missing tests. File cache.py now contains + these lines: 1: import pickle; 2: CACHE = {}; 3: def load_profile(user_id, + payload):; 4: if user_id not in CACHE:; 5: CACHE[user_id] + = pickle.loads(payload); 6: return CACHE[user_id]. The function accepts + payload bytes from an unauthenticated HTTP request, caches profiles without + expiration, and returns the cached mutable dictionary directly. The only + existing test loads one valid payload for one user.' + type: input_text + id: msg_0728b898e06dffcb006adb393051d887d0bf7d07885e934141 + role: user + status: completed + type: message + - agent: + agent_name: /root/missing_tests_review + author: /root + content: + - encrypted_content: gAAAAABq2zkwT4RTJERqhHuiGhPXkCTLt66HuosEzOi6Zq4nhzebgxAxHIw9kxV31_YBHf3iSD9HX97p9shsKd3yT3lcl3Bqwuizfpdx-_89x29yUQsN9thJWxKk09RmQs_-diIs1XczPIev_EpStbScZdKbkkpso1pgtTjEcWIEeW8Dn878-u2B0upaSP03iv64pmrgWOAB_2YCaGQ629ZynHNa9oDI2NWbwUS4qN9wtdS-ktIFm3NitIgF1NT3l6FMmM8UqRUyocr0RkzYoBz6-k3uONNQxO2DShjZQHHN0zxMI6reY7IllMns44kqfNJ56KVP3tbqa-AatTMMfW_-GeDOIZDuT15RazagGpl1rFnR-nGbSZEi13fzKxDSC5ydjPewvNjlCrKzmmu4KD0SUV2PJDvpWKK4L-Vw9ma16Ru0lqVxRagAqjtES9oT6ZQgSNTTwRjxlWK2Zg4FFG_ipBhE5ts68eAHPus970Grsccd34f0iXxjIekhOUNz5y8jTmVJ4FZQvenPqFwmHp38qhNa1LLkYA== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb3930528c87d09e49739e4b76c167 + recipient: /root/missing_tests_review + type: agent_message + - action: spawn_agent + agent: + agent_name: /root + call_id: call_qZdIWvLPqnp98UcZqDcegXfv + id: maco_0728b898e06dffcb006adb39305f8087d0b6c0f96d85eb8314 + output: + - annotations: [] + logprobs: [] + text: '{"task_name": "/root/missing_tests_review"}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root/missing_tests_review + content: [] + id: rs_0728b898e06dffcb006adb3931b3b087d0bb09e4a025a2b526 + summary: [] + type: reasoning + - agent: + agent_name: /root/security_review + content: + - annotations: [] + logprobs: [] + text: "### Security findings\n\n1. **Critical — Unauthenticated remote code\ + \ execution (line 5)** \n `pickle.loads(payload)` deserializes attacker-controlled\ + \ bytes. Malicious pickle opcodes can execute arbitrary Python code with\ + \ the server process’s privileges. \n **Impact:** full application/host\ + \ compromise, credential theft, data destruction, lateral movement. \n\ + \ **Remediation:** never unpickle untrusted input. Use a non-executable\ + \ format such as JSON plus strict schema/type validation. Authentication\ + \ or pickle “restricted unpicklers” are not sufficient general fixes.\n\ + \n2. **High — Unbounded cache enables memory exhaustion (lines 2, 4–5)**\ + \ \n An unauthenticated attacker can submit many unique `user_id` values\ + \ and large payloads. Entries never expire or evict. \n **Impact:**\ + \ sustained memory growth and process/container denial of service. \n\ + \ **Remediation:** authenticate requests; bind IDs to the authenticated\ + \ principal; enforce request/payload size limits, per-client rate limits/quotas,\ + \ bounded LRU capacity, TTLs, and eviction.\n\n3. **High — Cache poisoning\ + \ / profile integrity failure (lines 4–6)** \n The first request for\ + \ an uncached `user_id` permanently determines that profile. If IDs are\ + \ attacker-selectable, an attacker can prepopulate another user’s cache\ + \ entry; later legitimate payloads are ignored. \n **Impact:** forged\ + \ or corrupted profiles, possible cross-user authorization or data-integrity\ + \ failures depending on downstream use. \n **Remediation:** authorize\ + \ ownership of `user_id`, use trusted server-side profile data, define\ + \ safe refresh/invalidation semantics, and namespace keys by authenticated\ + \ tenant/user identity.\n\n4. **Medium — Mutable cached object is exposed\ + \ directly (line 6)** \n Returning the cached dictionary lets callers\ + \ mutate shared global state without validation. \n **Impact:** persistent\ + \ cross-request corruption, privilege-field tampering, and accidental\ + \ cross-user effects. Pickle may also return a non-dictionary object.\ + \ \n **Remediation:** validate the decoded schema, store immutable\ + \ representations, and return a defensive copy/read-only view; restrict\ + \ updates through validated APIs." + type: output_text + id: msg_0728b898e06dffcb006adb3932176487d08f2307c47afddab0 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + author: /root/correctness_review + content: + - encrypted_content: gAAAAABq2zkyuT7PZXDdMN-Uhmu53z-5yhJO_jBql3wBHjLOpvKoRPX0k9ZUyxj-kDquwbNLpFhsh06oSOMvqTUX56xiuYYkpkPjIFoDtZuiXyEBJVlUxeHvF4CdAhMIvX2TLtYN-zb6EIifxOLTt_W7SJzWfcNxQ18BD5aIQV8u960B03Z5X1WEI_M9T3Dd8y_tDaIfcbaaL5e-OQWUZNy-FfKmk38ukl9GPdAeOUZfk84WsOKOPsXnfEDBLFAMJyBdXzUr-2oKmwD569AvSyFl_Zm04OQ79ioH_QSGbz8qc3QSThzaOMB0jaQRS8As3nCfBu1y7jS9YpiS3oGku7eKG24NtNmexvMJjwG8RIysa6BamX5uRB-AM_c0cWL_Hbg5av5NBmjrsWwe4TSbc8YKzc_xvQ5G1TdLsi05_lcZlDytWkb2EBXNd7vOu7Ivz0TS6W00ez32aK95LUbgIS4c3RoO4xyx_jNplUfpfivWtw94p1k4Klio2Ij90xPSVXNfse5Rh-bD6EPSD-ruhagr4JVJTEqrnWeByWMcE6H-zOp1Hrsx_ncIE9fln_xE0iiOuc1Hx3-jIkVTiv8gFdW1OBrhv2XhOz_-L3G0n83Aspjst41DhWxi8SeQLTkRs5pWE8dvblniZodwUgkazKvtFcvE7rj1oqPpUKwQ1il1kW98K1kFFSxQnO6KOxXreeVhBYA0Jrp7yG0ZvCrmnszGQ5-wUuLemOM1FYbuXCjnRRH0X_-MrPJqe-klJ9te6VO107HocR_VshzzTR3haLNxsnNHNsFiQ_IqOVxWLsi4etC2XguF68NcX2gbZK8zI9kTX2lSOsSAoW-O218Lw1UeI4MEoOyfawu7MM3PiHAd9K5wsREKUpUov3ZPpkzZDaoFUm6hulUenffHujuq6MGFAFKA4TDiQ3MKkZc3HKHH-5eezontuPe0oXTiO5PFxbHQ6Dx2OyRuYvvbZenhf5Sp3Aa7KYRzJiA9tBA9lOYq2INtvIhrfviP85SizkNO6uREx6yvBa6slz0ZoxSUWqrZAUrdYI3I0IRFLVlFaaupU44EULWmN-d0Qk_AxKvRaciiQxerp-9uCdQftDSnKVoew4QV7rlqHanofNtglC-gFEB3AdYfUQjcfwxtjEf0x-L0-TTclCHmQXMgEcFiiadM9N6KFyFFFoXJBK7pUCoccie-5DoPw0FTSVt71PSQ_xsJ0qxpeFwEBptaDu3MmPIEdxECJYidB8wSLqiFGYQHL6gh1GxMGEq9FZRFAwK4PrSWU0IBx3zYQN4kRFIoNjOeliQugkSBe7pfY8FSL2aVviZmM5kgxIQXWocVA7mQEaFFS9qM_58vm2fuThctgXk8U0tkEOjIakeu11FrV4ddzxWHhZ9Is3sjIdwOZFHAwMCBZIk8bZ88A0VElyR3liAYjxgP7_zCiSgoadufzQgn-EK-OdbrAH6NjgaNDe_BU1FIVxLS3xAaKJdzZJiOPOzaGg9ttSL4_5H1y9VyZ32dqv-61f0XxP4m4E2KDO9zkps9pwDajQike8A3yytwLVLQnQpr9X7e51bUpQ6QNufpCogK58Cci-tLJ3ZxuR37lh9FMdbAoERNAiAUc4p-tcDMOPuCA_ZncX1zfAJGNsl3tfVfNh394aK9hQCTnNP8Vn1moi60OWGHBrsRNisl_wFqzdZfsC2XmSeiKPmk45QrsiDSCRLrNqrhAAU6eRfsIOLT0fA1fXdNIeV9Z8LAkSOEhftJ71dpVly1pX-N0soqNSx7NR9ZhQfoZhlpOE79lx6yUHwFj4Jp11_NuO4rL-4oepI6XB4GZtUxjYSXG4r6l-F7WkZ3EjskchxqQB6EZKM596YqiFjYhOythPpjz4a_kKCu_t5tojfUk_-TxtyZddRDmnuGmD9XKA7GMFXOxLog0xvAdtt0gAl9wDZtdi9AsrlpXgeIrVd7epTz6EshfEmI58x_k_AhllmesPOaMTZiIPLN3yJGhKp9V6KYTIdw2hQhul7uZzvOCl99lWpUFwFLteFjqWmXMdSUW6PKpGZGT8xTm4yFxN9wTGb0CzQ-f8hF4rUl1X5tCUdJzLcCCnwCgGofImn31-Rz8LXHk9h1Hcij8w0c7Uawjk8rZ48n55XPjVbKP7kmfz4ar2NU7Qy9gzvpSq0JVRTLUNd4OgaSIOGpgkXma1V8HzerpVQDD8spPNa1P0OxEwImwKXHRz10eEUTMalL3_nQPfN4Yv7zjSSHcfvpcUtuhmp8CcEVEeLeUV2XbUDJBaQqNEydt5es2IZ-ypFUQkt-LN6L4pSYR3o-FNZSrc-iEkNd6eWcJsFIt1PrKdL2ctffFViklzknXGH-JfMuz9XkRlC76hq7OEWHcTaBsg2IEiSPd28sElbkVoVPPUs_PKWEnb_l9lz3bTtN8UIhq3E7q1jfUv_SdFNr4j-OCH-HvZ904nRQHRzTqvO-FMGTIummijHvBmpOQ1rPrASxDEepkxfGYqblIqKeenjUckYZtfXho4UsrIZYI9moT6IRtJ60jc8ip5CLLSAspbxYB3Kt4RP8v3ErbJ0cQW5AFiLNJWIeXlj-wdIrx-3-K7aAohE0GvNdrSCcJFvrqqkImxFe6PH2cA-05IDJTZ6bgV9ELwBXCbpRZEv5QcCn9rOP5WwapMY44QecY8Yq19OWeNqPMeLNerLAWcy-l14flvsWSN4b9ke4wyzdHLEm5LFokmvAaZTZkTDDiuUoE0Kv87vcAXYP8LlIpswyN5TzxjJFDjK2OTT_3JFOzBHGPE1E5fQzbvOMO42woYoNNuorqOUTgjgpqYe2o25tHxrPsn9JdgZok1N4hQqUzVquimy8Zrp_2BodPKiyMl8uctTOtsnbZQVI9oh_pV1Vpzwss3oCY__c4trf9FuyQGH6jnzom8OTPWtbcvMtGz9tqQEiavcWp8gNO15YqasNU5MVfna6HeWAzSQGxtBNFGAWrrIKzairBWy-hudTpHVbOE6-3p3vGZWhy_jp2e5jftJDn6tnDvrGBnvWcXn8zriSeFvNKzIlri4HEuX3ln0ii94W56kkXfEE8FCmFiiqY-Mp_OsRrmgtP137k3qICKBTYlSk1Kdf7P6dGhBmNYHitnFM7kj84LStOmb4kMvXdvCqozRGmlzOfkoiHAb9t79NkicflqTXlmgiKsIKxS8MOAlR2geHDQSXP7RecycIQ2hvLYECZBTDd1VjA2eCXHyb08XCpJ4PnZfzEAo8vHw3NZzrvoextygDRaJJ_ay9e8NG6DOm_Y2DsMHh1IniHMF3usYKZ4LIZ1whYvEESzgTYB8J7AqZzWi8iE9w-Y_JX7H7NExIYtaDuPSwWdKVTDhQvTijkq1V5D4bBMB24zYwWpABQkVFAh8booB5e2MdDo9V + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393242b087d0b37dd408126bdf1b + recipient: /root + type: agent_message + - agent: + agent_name: /root + content: [] + id: rs_0728b898e06dffcb006adb3932476c87d09ad0355ac91fbfae + summary: [] + type: reasoning + - action: send_message + agent: + agent_name: /root/missing_tests_review + arguments: '{"target":"/root","message":"gAAAAABq2zk6uUSQHuW1EbtVDt3JfuErn-4gtzTPrsQyMgJ_1HuigfAcEFt9f7qhHTtNtSJ_c4EvDu2ak-AMWzm4SHGAU6pQPmCZEDPVa4RSf4UXR_RTk0Q_HwAvUSjX3DhAt-A-avGsSYpYlUimBGjIniMP7u179OhjCvmDF73GSB_dtmMVPfbrQiuIXfu0B5KvRvPxMp3qtAOD1NbSULsO5mt4A1vRGsiNx4XFxWV07GKhhm7wtw0A6lzKnA7gPeZEiKkOzdBo5vly8TDmlJI-ibIoOx-oltAvXf2s6HipROd_HP7wY7Ovwb_A8U9oTeriQUHHZ0kZpE4nabtPBrQtycfEUjrN4ohqAhKgIjZPKJuSO-p4FKnQ23r8dhfx4g3CBJykjyjEAuw4mr7Mecev2yXkDWyig66QqFbgDvWcu01IuNY52CvnD1YQYx9LGzwpOVSBwJ8SpiLZK5Ng_YQOv-HzjKwsOoFvfq3T0Jk_fMUfVNP096Pwo9IcJdZnKlr05QGxG3_7DBqElLmWpDtVLNHAGaJQtu-uZtygiYXna7c3qOTEq3dK7bCMlekQ44cYSslhjpoyka3WYf7WlZABUOhNifoIGlMgZRZB9FegCfyXNW-xxNUdKtmMmJ4KDLw6njyc_B9-b2y7B5zs7aAl_soJP5rFB55BMTqnJDxHzuqrRNFMsvszFi6NSInYOZT1HU5pAplMB6Hk8Fxdj-wWDJSlBCELnkB7NxLUIkv_p2R2jMR3g3ft9PgJImZr8wrfeJfB9p-KTQA0CW7TWCSfJYsvFf1-Jj-3DpFbuue8789JpQ8k8b16suNaOj7Rc4cmTFbC-jDVpvpoXVYYJ_ROpRO7w7tphdM32x7IXgaRfl5iNWJzEE5PgZ6enj6dHVgoXmYGXrclqJqUwmF7Mrp_KA2NaB_xwJrKSJPN5YHpUpsvmF0JIDswQHZ3crMjwN5R-BkVkBuZ50uSwpLNodjktGHV1ZBDrzJyXoHyfBKp-qyOCtfpRRbXQOOuYRtyZMT9GN8uGQXLn9iLMf4aBw2KJ75ak1PWfJLNP2_Rh05LZA-BsqoSlCbkbo4pf9jTW5ZrAim-4TiVXKG3TwSq5Q5Q08PoMZzOLJBYb90BLAWoakt12tj0CMBNlj6o_Hau1nbcWGUO_u6m553rmsgZNuren-kigdmwDUL8QTGVe1D4zlD4jVa0ADCfVBjJHfj8HFVDGAhMV5N_DrjAEVog1iquVwZo7MesTH4gAue4HDeqGEY8KBBEE0GxUso9dWIXtrpjXTrfKGLBMD7vXILkgNDj9uGt3AictF0hWXsBEFA5MryFy8XXpMkZslhIjagJwQFJtSdF1G_mqWY4RhZ8f_QDxPMeMeGLDGyGH2envqQliBDTnSgHNKktoKXWlR2OBnNfLJ-c_idZF9aQ421K61GwFgwYWXK02siHNcNekUsxkjQ1_38tHE_quOUQGJFDDINI9hgvpYqnkepNf7HakDIXhzOsLaVL7_3JaFMDyDOlmk6qkbGMw1XgrHOm-6ho4CjSPaXQN4kOnMQds2fA29RSV5gcCBx6HnN7x2x49nHTTM5Qq2CtOR4huvNTJZrRdhSs4sHlsMhICY15CxK5qvG5IIPCzJzE9Y-TpbwD7fwpqFbXg7spjsRxnlzCXB8agJGAkcpxJS1cwwZyA6iN454H0YDKiEcYYC4SkcryutrVMoWlVTNg62FBLyhdxuENpYYvEh4RH4Rqtu-EjZzrlXPgwFGWhGDfg6Vt1ibrXGcQvLDbDBF1U0TRzaWG4UQao6NM4NwIdKHMu7vrNrzAiXk4H18g50A5uP2V_vvJFMorHCgxr7RVykwIkJKeIKphA7ZsIvv87aXVd_1mTmNoJLO6CpzRUmmwMJvS9T1xo6mEqUtp6HC1VxBBR6xezwQkYuBnlbF2YImE0M-cPaSqiaM5RURX1if2QkCSfr7Vz3a0_nhzYRMv9GsRJgKI7cBpSbnTBFtt5rNAOoTs5pHAuW-AQYSYLtcTlxixqToyTwCGleA1IYSpsofF5rMFOeH3G-j7nXt1e8XAB0BodKcQJ-iEUe-WWT3TUPopMeSqHQEi-yBUTQyK-mHEgteW3J1GqczBTtWf0UTZTXMtJRTBdQkh2zCc2xXTOyF5NAMqtTF8pm6Mw7pRe-gaR2kyBlK8ihu5vjzYdQTzIz3SHI7N4rEM2JRGMFmupmGD9_2-pT8HkrFJz5Zw2peAALcE8t6Aq2lJaFWZIan0_5YfxlUP8EdpVj0VXlH-HcfprNYTWI89SQBWJTAmAtJz80qt10WB6K5vj0m9KG6cMH6VGrQkFU2HFvZ-S0Mp51toG73angzx38uo7lDPxx6DRD4KaUNAbdqzzHrXDmMXFsWYDkZkG8NLZ4H6AIdmfthlH2v-13Q__S2v-pPvr0_6JR3LYNuKNrzJfyf9184fVymHP8rd5HxtVNpsY2W-h-qXoGshZBjxlkTZnOEg5f20FExs5JtXJ8U1EL5A3dgk"}' + call_id: call_lJkkJiqgeFy0IKz2v91FNVKE + id: mac_0728b898e06dffcb006adb3934fa0c87d0a6ed4baa4ebc67d0 + type: multi_agent_call + - agent: + agent_name: /root + content: [] + id: rs_0728b898e06dffcb006adb3938239487d0b156ce9ce5de7a7f + summary: [] + type: reasoning + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":30000}' + call_id: call_hhJwnOjF2Z4PCF1AIbeb45VU + id: mac_0728b898e06dffcb006adb3938547887d0b64bd489f080d313 + type: multi_agent_call + - agent: + agent_name: /root + author: /root/security_review + content: + - encrypted_content: gAAAAABq2zk4704nz3LAqQ7VpTpNY8XvHAaMbHa3UmYsoEoptpdA7wGLClaZUmD6haEZHJfw_yHjhXADHfncSZ8rvcEe3cnvpH3RhULSvaTU_AX8n9kXscNidw-SdyBAHaE697RGC9wtyuxXDJLG3UErfKUrbCBi8DXmkmzqIdhGD7rDwXj_LNaABgZYf7DkHlYNVTUJ8CA9PBeOPkwfaLeS198-XOhvJVAXSWrBnfv7byGJmyR2fDzs5RNmtIq9zZPbrf0IDA-evv_nW_HxcenX9mpl4n6Yh2vQRkOWJSK0K_SqkeTKUMxyr30ZRuVjgcngMv6bUf0lp6hSNvRF4dhKPk-CUqIATTGbdSs3b1sQD5wjkJfzz4k0Izg8fulZm_Gqy4KwbArsXytalgk6kX5JGCBjqrWtoNMKLWE0OYIxeyXGEm1qKwRvK8mrr_xWWKysE7NJo0sWon5yv_mgt7EWEaukNrkKLmmCIaRA2WuOE-oJw0y6X0VWxTke29secPqx1Lv9itWMSKVhuU5qR97prsYbUfVLfZnHti4qD3hOUZsNi1wWbeUj1sw7ZhDfnDCvPE9XDcWY3VHr61pwRnhkfvBsLokMMh3CqlTNPVEPQmF8WwMu1B_mdYXj9r74zOGsRLWcdk6hzFEFd5q6oymp3Mw3a4RVUqXHYF5XVcyGswXRmJKnO6HMQHmp3aNeZNVSRdl15TsMERY3JITnmy3pA3Lr_MUwZZa5ctZP5JtvEa6H08ium1H5JoLX4dZy7EMccXk2H9gNxZYcYX1Qxv7N3gmYAZvLrs_xqJO-LWNlT3DZC2uFAJcc-if_PVln7y3x3khOm27Nal8bLfFV-rZ4mir7IYkvRG17EgtHWsBArwOqH8MpVWxnKZDk5D1XmCfTyktHBRV-boAh4QiEmbAL6Sdwx7aP1LtLS3exNG6cpqCRpPUOB8kNKfzePe6pDArIKKKEhGDfDWTNaiykcPgkt01bx8K20LE2c_Gv_KG6W-z5FmTivLWvW90A0FLXbF8K1aKN_aqh4dZbu4xEHA0hONTC4oOybQ2KIIMECbaueRLJU2dgKEJWhmoCuUhPNrjGMC4_C6JeTRaEd_PdCYFlSj-l6ZnjAuEw0V7cHD2LD-JTpex6VLVbvEOplwShYPtQ9wXjEDipf0Ri0YMB15_0lOobc6IhATYKTQB5dwYM9Na5vdKCxVUzJi6TtMfm3u8ewbT0UPrx_oGSWp9ZvfOgTDkcaMGErjxkJ3CM3VT_O0gEVZWJxKkasbgak4_BrfQjN3S00Be3z3QBCcN-3dMFeUTA49mRZN1rd_cBUMoJ7z_6eIZv3eqAYh7PYtClgkjMQXWdw9pEFRY2uR4lBn0ajmP6W7boS0RsT3ZESmfG2WRS8GsB-_HrhZBV0yzgIkiIpk_gHpbxvohkjChLZxygouKRM4m40BC4gtVDSIn-7NlC6-fWEpEpo1ILrLDDwMq-iD5K27uGT5wKU8bgCMi1Ate3pTBTtpFXTY6KF7qsIV1TPdSWVAYE9UutG_4YwkQy2O35giNsokTkgpd2LKWvrwr2dcn-9x11iYiH6b0zCmO_V_8PlzrclombaHBbyzsQBs9KucUodWfKPgQy0MAkkzDrj44orzVZMfoGjeFeT4vCBaWYqwf_m9AMMkecIyEV5qeuQ5Tv4LlKTUCdzjA-djKEDWs90W3SHG7CrrIEunzEyzvB2koxU1rbwoWmb0kLOpdhfOzTF8x9uebzrILwLamn6zyinAOHRYfm-epVmxF5zSrVBmNMS9WZLMd3QozlRF9nIUySvrEnRg5PspYvG8tnxtsVs7kDnjW5IDgdym87lKYb1yM58vR5Q7QkMgULgg_U2xmfRDx898MS4Bi9rSWeth24TGy_ETdERsgHOu7T9Fo793PO4DqWytCs_KBITwRMmKDlW0D9SN5dJbRqnpapxCfADygI6o-wZx_L3iRr5sOmYxwnlv3XaC2SbxoTYrPEeEV5KfPHBNtq5xxE0qGu_EXcEV8_66yuHdsHHkHtmhto1eCAX7ME2qXhP7uZWbNjLpGzmQEb9uWv3xivWCYLrVcQk9Zgc2zjce9F3A2Tx5K5YY_ZSVyAN0MUuRSLg1TMRsmZy7A7woBEgSL6ibdmWThRZgmail7Z_cpXr9oWnNTjeu6pAZRc491BOzVexjlfW8TzasiwTjf8wTd9e1Qyv102IzFvl5E34u21pPWeHLTY1ZvnwRGXRsPwBFQ5o4zECH0arMBltsIVdct7VIapt53kl3-WaLU6tjgEwYtNS7M9DyNOp13LZcFmBdsCB1X38-jhpSrzMZFfrurlqGp6CE1JY9mSUmZHlNdxtHiIfDk3ujvzjjdYQAbNb9IyyejkW0BHNG4Ih1L7busQj6bnjk0aKLKh_0sTRdc51Fv9BCD-M5Z2EidPADuJpI7Vhyn8C-nK0A_Ykz8SaqEG0KISsPZiupgAtWahgHcgSA7P_YEjcHIihf5M3KLlVtbGgcjpEhPIM11fS2lKErbqz1zXnGxibkdXcKf4lZ2plRWdlTdKe9J0hcUrcwraWRIzMR-7CP_MxYvmDKRuv7smXgETmKZ7Gt4fE8i_1bsREt5SvORywihc1IgEmRjappeIKcbdKfPMtZHIl3H2V9gvgxXpVeaTtDIoEff3R8Rt1tSesyQvD5AOqFqUNGnarhAnkPkvHovkpGD6WV1-13e45ZIwE9Cil6n-bFwwIDBu6A2rcv_0ZbWHLkKGt3x-vSGo2Rr5XgJO0HqA2h_UwLnd89fJLnMHSxtfuWnXz_ShvXD0hq3WqCN6Kb_vyKXJi_PDYu8r9DQbUmPQ1w2lHTYKbod3d8oc76RHquNInSVQskFmCSHCbm__kYeCJ9gFoqDeU_ci_JQXZfVzCmlJy7Q88hyin9aTcyiEOeYrdli5OSa_Sex6a4fvpmaNEIyaBDyjg2gjH7IiBbMA0rMoPrwOwSAJmZUgUgbI7uks15og2nFKy7rDWKYE8uuFh_ecxdGPFgvsJuep44uHJcLuz79vbKdGO8x9HFgesdsbZ_LJY85p-vHuWKkmSnqJux7ZShVz1ZF0HQ2zUeSw-qL6MRm37_qilJlKvo6NDmr9COarFpNI3oTkfkqj_rj493o2KlYpJPMwuLY00qTaYkDgdlF1fRyiGN9gSw== + type: encrypted_content + id: amsg_0728b898e06dffcb006adb3938e0fc87d0b283fc85b25352d9 + recipient: /root + type: agent_message + - action: wait_agent + agent: + agent_name: /root + call_id: call_hhJwnOjF2Z4PCF1AIbeb45VU + id: maco_0728b898e06dffcb006adb3938ea2487d0abaae5013611ba8b + output: + - annotations: [] + logprobs: [] + text: '{"message": "Wait completed.", "timed_out": false}' + type: output_text + type: multi_agent_call_output + - action: wait_agent + agent: + agent_name: /root + arguments: '{"timeout_ms":30000}' + call_id: call_EehCbCF4FDjJXimRhhkpczod + id: mac_0728b898e06dffcb006adb393a569087d09d0127132a4b92d4 + type: multi_agent_call + - action: send_message + agent: + agent_name: /root/missing_tests_review + call_id: call_lJkkJiqgeFy0IKz2v91FNVKE + id: maco_0728b898e06dffcb006adb393aaec087d0a3131ac81e678268 + output: + - annotations: [] + logprobs: [] + text: '' + type: output_text + type: multi_agent_call_output + - action: wait_agent + agent: + agent_name: /root + call_id: call_EehCbCF4FDjJXimRhhkpczod + id: maco_0728b898e06dffcb006adb393ab8fc87d0ada17d482d52e083 + output: + - annotations: [] + logprobs: [] + text: '{"message": "Wait completed.", "timed_out": false}' + type: output_text + type: multi_agent_call_output + - agent: + agent_name: /root + author: /root/missing_tests_review + content: + - encrypted_content: gAAAAABq2zk82GERbxB2TMzJRbm7dWxysoYieFSOCWQZwvJtRDy9Fri_A7SqewXf-nFPTktjdlUb3-cNcMMudhqgWQ5e1uzYTxKfa2hm1xQ4dH7K0NyzauszHS8Kil-b_bYiQddTwpUmx5mBzrOALtICgQqSmME5E1JUhz-iADRxT6XHlGX1WCnm17PiwQz1hu7Sphy-4ETycre76N2tEndze5lR5ODB4eH-2u76_wvMsYfNZl2ORlzRcE1m7lkX9T5jqYy_Kq1440MPGp_ZUQW6Rt7F6SQMY_tbvzMmD1sqK_czEpHg-4CB2kl80nhYPdG4gOMqxTeV7dhBH5lWYmnT-qhgF0qAmkW86wc_bpURo5JstVDdzrP53W2nB6kcs4ZYSjOM8_Nxrpu9v3-q8EGba-hUgz6duk-Lwv587a_1O5OeuVtWLci0E4cWjfDzDS8-p-d-LGl5TcQ29O-LhxlKZBxrbC9FBOWm-82Ji8g3FZDMk7yn7ejFCXFJ5Dhbw1gLrLSin1iRnwig1nXVPsykM5FIle5XWUA0JClTiKnuPeqJMBaFboot9-VghM_LKxsN-g1i_J_4JW6t9bZMuRF4gJOtsRAr8h1LftS2cENcTjdKUlncnhkG8PuQQFNmp6R_SnBWhATYsqhjI-Vk3cEtEzo-a1KyfRMqCVn1ZiTQ4PtmJdckIdL2MttqWj5sWHkBDYiOE0CryNPB84Ll2trO0tx4XGhDbbzmwYRPLb7lTtMIwdTC-dNyAqoFcUuuvTBaw2-gRnAGFEsUjVgxnGyRtFd32tQDOsYDFTk7eIa2LK_U-EWizyt3brNZN1WfxLOYbatT80i5QFwXaZjPgkzoviAV7l-iIKmqQHRieOxNAEu9nkBGWCv2_vYa-09rX6BqjUNUSZYAQxWSBnmC1YtSmX4flDERHrcPYBhrKMNe0H0V7aakTyjuLo2k9Gv3syFGoaoBYwvMc1fJzWLr9vJBbjKvtaohDiMvdwNj0zbwYG5OjLRfX5dIuKiiJOTys1ZTza9gPHWeTJoG0MhTnsNk4_ppWQRqjHdcl8pXczi8OgxkiW7azszFSJoHIaGIXVbZOXkUEoY2jI6nhDEoaKZAdvIU0_D_YqvcV6Y0j-P-N-35Muv9jD_9X0XuaiI0uFzgunXvjltaxCNl7adc77zwAjCXHfzoGmTPHmXJWSqTOeyTVQQvh6PulazjHBuO7ycwYAg3YaxzIkF6gYHTPdrBZWPnH3w3Afe0F-Y9kyOFKANRBaFGSd4e444Pf48Fiog1ZzDyE9WnoQNRjZkTRysSJb6TXOzuHMacCbC2SJ_4KWl4dQ6-dDVcgXwuGq6ATuHRbS7Crum9Z35t_vtCE4e2aLd1c8hf1QRgPQIVZ5q6rHdk0stN2ds4K_5pK04fGkn7LAW8GlYi2uDA6RddyBpr3URR7T8J8PW4A1ST3MUHZF6ygLnW_iKUidM07qY8rurh5MDOyUkTfAxlT_e8p9-RolNQzDQkzYY1mrCwW8mGCt0iRlKpy7Foyy9G1syKL_WPFRacZ03PzJ9SmT9TecKB15O4Ar2grIxM-5F5X4895_s7tIyIv9yJSDkkcYaUg9TlbOg6zQzumShLRbRKoTWH8Inw58_kxPz665JBc4iH9gWjrbOvGJ1NROjH1Nae_MwpLchtwPUOkdq7POs8lbD0UCAU7nXnaVbsMozqIJlfiFOftEd6M-f27kW0rf_MqOPn3aX6oSi2PLXybPl1f9uuLeMo9X6GHdv0co4Sqo7StVjqbQx708VvxN5KM6nyqLb9vfhzmd9M3P7IZf1BqH-VggOEA5zrXcgmEvUreOq4ycY-c1fa7SHbAe8rxxq7XB453us2nTMKUkmwTX6gR6iKWM751QALUsVU9sUHvY2Cn8OYwWLEELCoXrb8Va5hlkXS4slJlwXb-tnSSpVmC2z9NAkO6xYECdO9AJ5ikrriYDL_RzEXJg63E3iNjnefeJIumvUsL2qZB17P8cdiNtO-LC5GwoDbU_8ikWbZh0ttmXJrvASKZ5wJxrmlNqX79cEjmZ4bnHt7xc5JmlJHIcfCLpzypeg9RA8tzvQx41UzcAS-YqN3IFJ-Yql9bGFsBJ-A0a_9wYW9LNAbkKntEZQtdnb8q2pgUnwVPrBq_vTIa8cTWfJjdjmek9ubVQH23dctuMG6u0ZqHuixVxLeP-z5FOVyYVGaN2YKKR52vL4cXity70BAO2D25H6DRItxFeoFu9tlAw_vIyjkNdSb4ZIQSg3GPDqd6X6VGzKvvovPXeU5TYTqMjzMHE7p_Lufne9piqhH5Yhvmuts0tjv1FQNjPkO3Vddikyeo-IVLGBGZQSfNMNlSQdLp-ftcpXCSvXXsHpNEUyR70AEaAmovsXQLbAhJhMHJXFYw7aTIMZSrFo93RUAJ3qcVOKLp1QthechnCewWDB9INjq3ZDNTYyJPLvFmJ6YMqq4IMcEYDCas2U_15rFt3i4q-8ww0Fxa6U9Wj2W9b3whc3e27U1D48KLOVRI20GjitqU0eUtBt_OeozlCYb69sV4lpqjU34rCUo2YJX7Jr_7XriRUIMTI68WzK2pdydvsIlX2sixnE4ASupxB4Qyjo= + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393b357c87d09dca5b928ba01c53 + recipient: /root + type: agent_message + - agent: + agent_name: /root/missing_tests_review + content: + - annotations: [] + logprobs: [] + text: Missing-test findings sent to the parent, prioritized around unsafe + deserialization, cache poisoning, mutable aliasing, resource limits/expiration, + concurrency, validation, and multi-user isolation. + type: output_text + id: msg_0728b898e06dffcb006adb393c11b487d098917ff83c68a5f6 + phase: final_answer + role: assistant + status: completed + type: message + - agent: + agent_name: /root + content: [] + id: rs_0728b898e06dffcb006adb393c3c3c87d0ae6a94107f916ae9 + summary: [] + type: reasoning + - agent: + agent_name: /root + author: /root/missing_tests_review + content: + - encrypted_content: gAAAAABq2zk89ydXgHp2WpRvXWu1Wvo4LU18Q9Wb0Gq8qiM7s2HslOUpktFFvkEkR6cMXAUzDAd-yplqTuEEH3FC3UrkDnkfnEoRBp11gz8H3xII0aCBYiM-fbx1XySMHoHCQX-VkIYADgv7XJOoB-Uwg3NekWPjulFhkTkhT17Cmd5hGaZsXw2wuYSItSrdTeOJ1nMwySftxEeRUbk90tdK_ASpSiSA-d60pf5bgdd8cH_AzncdXHjNuM8RFtzcZZdk8lOWBorWMJd7LvJeZ_xU4KgQaW3Mf9ADVZBX3hWh3GsCm74oknBM0Rrsy3vcslx7k4d1qH_hqwfLhVZoPuHknfAD2KnoS3dZ0Rr53X9GXNvyePti9ToNqSsrkt9x8UqmeGXhUgWykIc2Mc8248uQq3JWSAIdQldxgEhWsWedM3M3dPk8QkqW9fSzuq6yX1Gfg6z-BDIEZUdGj2LHwG41hNXt0x3KGm8MHEmyLYZePKzqhHRG7g8= + type: encrypted_content + id: amsg_0728b898e06dffcb006adb393cd0a487d0a64936a3faf3889c + recipient: /root + type: agent_message + - agent: + agent_name: /root + content: - annotations: [] logprobs: [] - text: CUSTOM_CASSETTE_OUTPUT_OK + text: "## Consolidated review from three subagents\n\n**Recommendation:\ + \ block/stop-ship.** The change introduces a critical remote-code-execution\ + \ vulnerability and several cache-integrity and availability problems.\n\ + \n### Security review\n\n- **Critical — attacker-controlled deserialization\ + \ (line 5):** \n `pickle.loads(payload)` can execute arbitrary code\ + \ embedded in a pickle. Because `payload` comes from an unauthenticated\ + \ HTTP request, this can lead to full server compromise. \n **Fix:**\ + \ never unpickle untrusted input. Prefer JSON or another non-executable\ + \ format with strict schema validation. Authentication alone does not\ + \ make untrusted pickle safe.\n\n- **High — cache poisoning (lines 4–6):**\ + \ \n The first request for a `user_id` permanently controls that cache\ + \ entry. An attacker can seed a victim’s ID before the legitimate request,\ + \ whose later payload will then be ignored. \n **Fix:** bind the key\ + \ to an authenticated identity and load authoritative profile data server-side.\ + \ Define explicit refresh and invalidation behavior.\n\n- **High — possible\ + \ unauthorized profile disclosure (line 6):** \n If callers can choose\ + \ `user_id`, requesting an already-cached victim ID returns that victim’s\ + \ profile without authentication. \n **Fix:** authenticate and authorize\ + \ access to the requested profile; do not trust a caller-provided identity.\n\ + \n- **High — denial of service (lines 2, 4–5):** \n Attackers can submit\ + \ many unique IDs or large profiles, growing the cache indefinitely. \ + \ \n **Fix:** enforce payload-size limits, rate limits, quotas, bounded\ + \ capacity, TTLs, and eviction.\n\n### Correctness review\n\n- **High\ + \ — stale data (lines 4–6):** subsequent payloads for a cached user are\ + \ silently ignored forever.\n- **High — mutable shared state (line 6):**\ + \ callers can modify the returned dictionary and thereby corrupt future\ + \ responses. Nested values also require protection, so a shallow copy\ + \ may be insufficient.\n- **High — race condition (lines 4–5):** concurrent\ + \ misses can deserialize conflicting payloads and overwrite one another\ + \ nondeterministically.\n- **Medium — missing validation (line 5):** a\ + \ pickle can produce any object, not necessarily a valid profile dictionary.\n\ + - **Medium — unmanaged errors (line 5):** malformed input will raise deserialization\ + \ exceptions, likely producing HTTP 500 responses.\n- **Medium — process-local\ + \ behavior (line 2):** different workers may hold inconsistent profiles,\ + \ and all state disappears on restart.\n\nUse an established bounded cache\ + \ with atomic population, expiration, and invalidation. Store validated\ + \ immutable values and return defensive deep copies or read-only representations.\n\ + \n### Missing tests\n\nThe existing single happy-path test is insufficient.\ + \ Add, in priority order:\n\n1. Untrusted input cannot cause code execution;\ + \ malformed/truncated input produces a controlled client error and does\ + \ not populate the cache.\n2. An unauthenticated caller cannot seed, overwrite,\ + \ read, or otherwise affect another user’s profile.\n3. Mutating a returned\ + \ profile—including nested structures—does not mutate cached state.\n\ + 4. Cache size is bounded; TTL, eviction, and refresh behavior work as\ + \ specified.\n5. Concurrent requests for the same uncached ID behave atomically\ + \ and deterministically.\n6. Non-dictionary values, invalid schemas, oversized\ + \ payloads, and invalid IDs are rejected without insertion.\n7. Multiple\ + \ users remain isolated, repeated hits behave correctly, and the global\ + \ cache is reset between tests.\n\nRather than testing that malicious\ + \ pickle gadgets are safely handled, the preferred redesign should eliminate\ + \ `pickle.loads` from this request path entirely." type: output_text - id: msg_01014eb0097f04f3006a71c72fd210819bb751b115cd0730fc + id: msg_0728b898e06dffcb006adb3942a2a487d0ab2f97a66cc8bdfd phase: final_answer role: assistant status: completed type: message - content: - - text: This is completed tool history. Do not call tools. Reply only OK. + - text: Do not call tools again. Reply only OK. type: input_text - id: msg_01014eb0097f04f3006adb2dc8c92c819b82487bf31f975e67 + id: msg_0728b898e06dffcb006adb394f427087d0bada52ee50b2be20 role: user status: completed type: message - first_id: msg_01014eb0097f04f3006adb2dc8c8c4819bbfcadcb9d1edbc3e + first_id: msg_0728b898e06dffcb006adb3925978487d0a45c51be5137805e has_more: false - last_id: msg_01014eb0097f04f3006adb2dc8c92c819b82487bf31f975e67 + last_id: msg_0728b898e06dffcb006adb394f427087d0bada52ee50b2be20 object: list headers: content-type: application/json diff --git a/crates/agentic-server-core/tests/compaction_cassette_test.rs b/crates/agentic-server-core/tests/compaction_cassette_test.rs index 1642f11c6..086a9c28d 100644 --- a/crates/agentic-server-core/tests/compaction_cassette_test.rs +++ b/crates/agentic-server-core/tests/compaction_cassette_test.rs @@ -227,7 +227,10 @@ async fn repeated_compaction_drops_tool_pair_and_replaces_prior_checkpoint() { ); let requests = fixture.request_bodies().await; - assert_eq!(requests[0]["input"], turn.request.body.input); + let mut expected = turn.request.body.input.clone(); + assert_eq!(expected[4]["type"], "function_call_output"); + expected[4]["status"] = json!("completed"); + assert_eq!(requests[0]["input"], expected); } #[tokio::test] diff --git a/crates/agentic-server-core/tests/mcp_tool_test.rs b/crates/agentic-server-core/tests/mcp_tool_test.rs index e17241d5e..4f8d1a947 100644 --- a/crates/agentic-server-core/tests/mcp_tool_test.rs +++ b/crates/agentic-server-core/tests/mcp_tool_test.rs @@ -3,7 +3,7 @@ use agentic_core::executor::accumulator::ResponseAccumulator; use agentic_core::tool::ToolDeclaration; use agentic_core::tool::{GatewayExecutors, ToolOwnership, ToolRegistry, ToolType}; use agentic_core::types::io::output::McpListTools; -use agentic_core::types::io::{McpCall, OutputItem, ToolChoice}; +use agentic_core::types::io::{ItemPage, McpCall, OutputItem, ToolChoice}; use agentic_core::types::request_response::RequestPayload; use agentic_core::types::tools::ResponsesTool; use axum::Router; @@ -897,3 +897,37 @@ fn successful_blocking_mcp_call_matches_openai() { assert_calls_match_openai(&openai_output, &gateway_output, false); assert_eq!(output_text(&gateway_output), output_text(&openai_output)); } + +#[tokio::test] +async fn mcp_input_items_return_stored_call_and_result() { + let mcp_url = spawn_forecast_mcp().await; + let fixture = support::TestFixture::new_with_responses(forecast_inference_responses(false, "Paris")).await; + let Either::Left(response) = ExecuteRequest::new(forecast_request(false, &mcp_url), Arc::clone(&fixture.exec_ctx)) + .run() + .await + .unwrap() + else { + panic!("expected JSON response") + }; + let page = fixture + .exec_ctx + .resp_handler + .list_input_items(&response.id, ItemPage::default()) + .await + .unwrap(); + let page = serde_json::to_value(page).unwrap(); + assert_eq!(page["data"].as_array().unwrap().len(), 3); + assert_eq!(page["data"][0]["type"], "function_call_output"); + assert_eq!(page["data"][1]["type"], "function_call"); + assert_eq!(page["data"][2]["type"], "message"); + let bodies = fixture.request_bodies().await; + for kind in ["function_call", "function_call_output"] { + assert!( + bodies[1]["input"] + .as_array() + .unwrap() + .iter() + .any(|item| item["type"] == kind && item["call_id"] == "call_forecast") + ); + } +} diff --git a/crates/agentic-server-core/tests/responses_api/recordings.rs b/crates/agentic-server-core/tests/responses_api/recordings.rs index 949d23f94..72857863a 100644 --- a/crates/agentic-server-core/tests/responses_api/recordings.rs +++ b/crates/agentic-server-core/tests/responses_api/recordings.rs @@ -288,75 +288,95 @@ fn error_metadata_comparison_detects_each_field() { } #[test] -fn recorded_tool_history_ids_and_statuses() { - let turns = cassette("tool-items", "openai").expect("OpenAI tool-history recording"); - assert_eq!(turns.len(), 40); - let groups: &[&[(&str, &str)]] = &[ - &[("web_search_call", "ws_")], - &[("mcp_list_tools", "mcpl_"), ("mcp_call", "mcp_")], - &[("code_interpreter_call", "ci_")], - &[("shell_call", "sh_"), ("shell_call_output", "sho_")], - &[("custom_tool_call", "ctc_"), ("custom_tool_call_output", "ctco_")], +fn recorded_tool_execution_ids_and_statuses() { + for provider in ["openai", "gateway"] { + assert_tool_execution_ids_and_statuses(provider); + } +} + +fn assert_tool_execution_ids_and_statuses(provider: &str) { + let turns = cassette("tool-items", provider).expect("tool-execution recording"); + assert_eq!( + turns.iter().filter(|turn| turn["request"]["method"] == "POST").count(), + 24 + ); + let expected = [ + ("web_search_call", "ws_"), + ("mcp_list_tools", "mcpl_"), + ("mcp_call", "mcp_"), + ("function_call", "fc_"), + ("function_call_output", "fc_"), + ("code_interpreter_call", "ci_"), + ("shell_call", "sh_"), + ("shell_call_output", "sho_"), + ("custom_tool_call", "ctc_"), + ("custom_tool_call_output", "ctco_"), + ("tool_search_call", "tsc_"), + ("tool_search_output", "tso_"), + ("multi_agent_call", "mac_"), + ("multi_agent_call_output", "maco_"), + ("agent_message", "amsg_"), ]; - for (group, probes) in groups.iter().zip(turns.as_chunks::<8>().0) { - for (variant, pair) in probes.as_chunks::<2>().0.iter().enumerate() { - let request = &pair[0]["request"]["body"]; - let created = terminal(&pair[0]["response"]); - let page = terminal(&pair[1]["response"]); - let omitted_id = variant % 2 == 1; - let omitted_status = variant >= 2; - let submitted: Vec<_> = request["input"] - .as_array() - .unwrap() - .iter() - .filter(|item| group.iter().any(|(kind, _)| item["type"] == *kind)) - .collect(); - assert_eq!(submitted.len(), group.len()); - for item in &submitted { - if omitted_id { - assert!(item.get("id").is_none()); - } - if omitted_status { - assert!(item.get("status").is_none()); - } - } - if group[0].0 == "code_interpreter_call" && omitted_id { - assert_eq!(pair[0]["response"]["status_code"], 400); - assert_eq!(created["error"]["code"], "missing_required_parameter"); - assert_eq!(created["error"]["param"], "input[1].id"); - assert_eq!(pair[1]["response"]["status_code"], 404); - continue; - } - assert_eq!(pair[0]["response"]["status_code"], 200, "{created}"); - assert_eq!(pair[1]["response"]["status_code"], 200); + let mut seen = HashSet::new(); + let mut requests = turns.iter(); + while let Some(post) = requests.next() { + let request = &post["request"]["body"]; + let created = terminal(&post["response"]); + assert_eq!(post["request"]["method"], "POST"); + if request.get("previous_response_id").is_none() && request["multi_agent"]["enabled"] != true { + assert!(!request["tools"].as_array().unwrap().is_empty()); + } + assert_eq!(post["response"]["status_code"], 200, "{created}"); + assert_eq!(created["status"], "completed"); + let mut data = Vec::new(); + let mut after = Value::Null; + loop { + let listing = requests.next().expect("input-items page"); + let page = terminal(&listing["response"]); + assert_eq!(listing["response"]["status_code"], 200, "{page}"); + assert_eq!(listing["request"]["method"], "GET"); + assert_eq!(listing["request"]["query_params"]["after"], after); assert_eq!( - pair[1]["request"]["path"], + listing["request"]["path"], format!("/v1/responses/{}/input_items", created["id"].as_str().unwrap()) ); - let data = page["data"].as_array().unwrap(); - assert_eq!(data.len(), request["input"].as_array().unwrap().len()); - for ((kind, prefix), input) in group.iter().zip(submitted) { - let item = data.iter().find(|item| item["type"] == *kind).unwrap(); - assert!(item["id"].as_str().unwrap().starts_with(prefix)); - if input.get("id").is_some() { - if *kind == "code_interpreter_call" { - assert_ne!(item["id"], input["id"], "OpenAI reassigns the recorded code-call ID"); - } else { - assert_eq!(item["id"], input["id"]); - } - } - assert_eq!(item["call_id"], input["call_id"]); - if *kind == "mcp_list_tools" { - assert!(item.get("status").is_none()); - } else { - let status = if omitted_id && matches!(*kind, "shell_call" | "custom_tool_call") { - "in_progress" - } else { - "completed" - }; - assert_eq!(item["status"], status, "{kind}: variant {variant}"); + data.extend(page["data"].as_array().unwrap().iter().cloned()); + if page["has_more"] == false { + break; + } + assert_ne!(page["last_id"], after); + after = page["last_id"].clone(); + assert!(after.is_string()); + } + for item in &data { + let Some(&(kind, prefix)) = expected.iter().find(|(kind, _)| item["type"] == *kind) else { + continue; + }; + seen.insert(kind); + assert!(item["id"].as_str().unwrap().starts_with(prefix), "{item}"); + if matches!( + kind, + "mcp_list_tools" | "multi_agent_call" | "multi_agent_call_output" | "agent_message" + ) { + assert!(item.get("status").is_none()); + } else { + assert_eq!(item["status"], "completed", "{item}"); + } + } + if let Some(previous) = request["previous_response_id"].as_str() { + let parent = turns + .iter() + .find(|turn| turn["response"]["body"]["id"] == previous) + .unwrap(); + for output in parent["response"]["body"]["output"].as_array().unwrap() { + if expected.iter().any(|(kind, _)| output["type"] == *kind) { + let listed = data.iter().find(|item| item["id"] == output["id"]).unwrap(); + assert_eq!(listed["type"], output["type"]); + assert_eq!(listed["status"], output["status"]); + assert_eq!(listed["call_id"], output["call_id"]); } } } } + assert_eq!(seen, expected.iter().map(|(kind, _)| *kind).collect()); } diff --git a/crates/agentic-server-core/tests/responses_api_test.rs b/crates/agentic-server-core/tests/responses_api_test.rs index 40b283c2c..dd54992af 100644 --- a/crates/agentic-server-core/tests/responses_api_test.rs +++ b/crates/agentic-server-core/tests/responses_api_test.rs @@ -126,11 +126,30 @@ fn error_metadata(body: &Value) -> Value { fn compare_items(expected: &[Value], actual: &[Value], ids: &mut HashMap, location: &str) { assert_eq!(expected.len(), actual.len(), "{location}: item count"); for (left, right) in expected.iter().zip(actual) { - assert_eq!(item_signature(left), item_signature(right), "{location}: item content"); + let mut expected = item_signature(left); + let result_prefix = match left["type"].as_str() { + Some("function_call_output") => Some("fc_"), + Some("custom_tool_call_output") => Some("ctco_"), + Some("shell_call_output") => Some("sho_"), + _ => None, + }; + if let Some(prefix) = result_prefix { + // Older gateway captures lack these fields. The refreshed OpenAI + // tool-execution reference establishes their required public values. + assert!( + right["id"].as_str().is_some_and(|id| id.starts_with(prefix)), + "{location}: result ID" + ); + assert_eq!(right["status"], "completed", "{location}: result status"); + if left["type"] != "function_call_output" && expected["status"].is_null() { + expected["status"] = json!("completed"); + } + } + assert_eq!(expected, item_signature(right), "{location}: item content"); let recorded = left["id"].as_str(); let live = right["id"].as_str(); // Function-result resource IDs are deferred; all other item identities remain required. - if left["type"] != "function_call_output" { + if recorded.is_some() || left["type"] != "function_call_output" { assert_eq!(recorded.is_some(), live.is_some(), "{location}: item ID presence"); } if let (Some(recorded), Some(live)) = (recorded, live) { @@ -355,7 +374,7 @@ async fn replay(scenario: &str, provider: &str, turns: &[Value]) { &client, &url, turn, - &reference[index], + if provider == "openai" { turn } else { &reference[index] }, &mut ids, &format!("{scenario}/{provider} t{}", index + 1), ) @@ -427,3 +446,32 @@ replay_tests! { (replay_replayed_items_stream_openai, "replayed-items-stream", "openai"), (replay_replayed_items_stream_gateway, "replayed-items-stream", "gateway"), } + +#[tokio::test] +async fn replay_client_tool_input_items_gateway() { + let turns = cassette("tool-items", "gateway").expect("gateway tool-execution recording"); + let mut selected = false; + let turns: Vec<_> = turns + .into_iter() + .filter(|turn| { + let body = &turn["request"]["body"]; + if turn["request"]["method"] == "POST" && body["previous_response_id"].is_null() { + selected = body["tools"].as_array().is_some_and(|tools| { + !tools.is_empty() + && tools.iter().all(|tool| { + matches!( + tool["type"].as_str(), + Some("function" | "custom" | "shell" | "tool_search" | "namespace") + ) + }) + }); + } + selected + }) + .collect(); + assert_eq!( + turns.iter().filter(|turn| turn["request"]["method"] == "POST").count(), + 13 + ); + replay("tool-items", "gateway", &turns).await; +} diff --git a/crates/agentic-server-core/tests/session_continuation_test.rs b/crates/agentic-server-core/tests/session_continuation_test.rs index 6c2c96c26..fca23cb93 100644 --- a/crates/agentic-server-core/tests/session_continuation_test.rs +++ b/crates/agentic-server-core/tests/session_continuation_test.rs @@ -395,10 +395,26 @@ async fn explicit_conversation_tool_history(use_session: bool, gateway_tool: boo assert_eq!(calls.load(Ordering::Relaxed), usize::from(gateway_tool)); let requests = model.requests.lock().await; assert_eq!(requests.len(), if gateway_tool { 3 } else { 2 }); - let model_history = requests.last().unwrap()["input"].clone(); + let mut model_history = requests.last().unwrap()["input"].clone(); drop(requests); let history = conversations.rehydrate(&conversation.conversation_id).await.unwrap(); - let stored_history = serde_json::to_value(InOutItem::into_input_items(history)).unwrap(); + let mut stored_history = serde_json::to_value(InOutItem::into_input_items(history)).unwrap(); + // Independently executed sessions generate different result item IDs. Check + // persistence within each execution before comparing their semantic history. + for item in model_history.as_array_mut().unwrap() { + if item["type"] == "function_call_output" && !item["id"].is_null() { + assert!(item["id"].as_str().unwrap().starts_with("fc_")); + let stored = stored_history + .as_array_mut() + .unwrap() + .iter_mut() + .find(|stored| stored["type"] == "function_call_output" && stored["call_id"] == item["call_id"]) + .unwrap(); + assert_eq!(stored["id"], item["id"]); + item.as_object_mut().unwrap().remove("id"); + stored.as_object_mut().unwrap().remove("id"); + } + } model.close().await; pool.close().await; (model_history, stored_history) diff --git a/crates/agentic-server-core/tests/shell_tool_test.rs b/crates/agentic-server-core/tests/shell_tool_test.rs index e2307b3fd..47b6d6da9 100644 --- a/crates/agentic-server-core/tests/shell_tool_test.rs +++ b/crates/agentic-server-core/tests/shell_tool_test.rs @@ -301,6 +301,7 @@ async fn submitted_shell_items_preserve_public_fields_in_storage() { InOutItem::Output(_) => None, }) .collect::>(); + output["status"] = json!("completed"); assert_eq!(inputs, vec![call, output]); let requests = fixture.request_bodies().await; assert_eq!(requests[0]["input"][0]["type"], "function_call"); diff --git a/crates/agentic-server-core/tests/stateful_responses_integration.rs b/crates/agentic-server-core/tests/stateful_responses_integration.rs index 9ed439447..8a7d19bd1 100644 --- a/crates/agentic-server-core/tests/stateful_responses_integration.rs +++ b/crates/agentic-server-core/tests/stateful_responses_integration.rs @@ -431,6 +431,8 @@ async fn test_previous_response_id_rehydrates_function_call_before_tool_output() let mut second = make_request("ignored", true, false, Some(p1.id), None); second.input = ResponsesInput::from(vec![InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_1".to_string(), output: "{\"stdout\":\"/workspace\"}".into(), })]); @@ -493,6 +495,8 @@ async fn test_encrypted_only_persisted_reasoning_fails_before_upstream() { for stream in [false, true] { let mut continuation = make_request("continue", true, stream, Some(first.id.clone()), None); continuation.input = ResponsesInput::from(vec![InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: "call_prior".to_owned(), output: "tool output".into(), })]); @@ -1222,6 +1226,8 @@ fn upstream_mcp_fixture_call(id: &str, call_id: &str, name: &str, arguments: &st fn tool_output(call_id: &str, output: &str) -> InputItem { InputItem::FunctionCallOutput(FunctionToolResultMessage { + id: None, + status: None, call_id: call_id.to_string(), output: output.into(), }) diff --git a/crates/agentic-server-core/tests/web_search_tool_test.rs b/crates/agentic-server-core/tests/web_search_tool_test.rs index 45726dce5..e0b0f613b 100644 --- a/crates/agentic-server-core/tests/web_search_tool_test.rs +++ b/crates/agentic-server-core/tests/web_search_tool_test.rs @@ -10,7 +10,7 @@ use agentic_core::tool::{GatewayExecutor, ToolOutput, WebSearchHandler, model_vi use agentic_core::types::event::MessageStatus; use agentic_core::types::io::output::{FunctionToolCall, GatewayCallStatus}; use agentic_core::types::io::{ - FunctionToolResultMessage, InputItem, OutputItem, ResponsesInput, ToolCallOutput, ToolChoice, + FunctionToolResultMessage, InputItem, ItemOrder, ItemPage, OutputItem, ResponsesInput, ToolCallOutput, ToolChoice, }; use agentic_core::types::request_response::{RequestPayload, ResponsePayload, ResponseTextConfig}; use agentic_core::types::tools::{ResponsesTool, WebSearchToolParam}; @@ -1901,6 +1901,8 @@ async fn execute_returns_mixed_client_tool_calls_without_followup_model_request( previous_response_id: Some(response.id), ..base_payload(ResponsesInput::from(vec![InputItem::FunctionCallOutput( FunctionToolResultMessage { + id: None, + status: None, call_id: "call_weather".to_owned(), output: ToolCallOutput::Text("{\"city\":\"San Francisco\",\"temperature_c\":18}".to_owned()), }, @@ -2925,3 +2927,83 @@ async fn cache_usage_accumulates_tool_rounds_without_inventing_writes() { } } } + +#[tokio::test] +async fn input_pages_return_stored_items_without_changing_model_history() { + let (you_url, _captured, _you) = spawn_mock_you().await; + let llm = support::MockServer::start_deque(vec![ + mixed_web_search_and_client_function_response(), + support::text_response("continued after mixed tools"), + ]) + .await; + let ctx = build_exec_ctx(llm.url(), you_url).await; + let payload: RequestPayload = serde_json::from_value(serde_json::json!({ + "model": "test-model", "input": "look up rust async and weather", "store": true, + "tools": [{"type": "web_search_preview"}, { + "type": "function", "name": "get_weather", "parameters": {"type": "object"} + }] + })) + .unwrap(); + let Either::Left(root) = ExecuteRequest::new(payload, Arc::clone(&ctx)).run().await.unwrap() else { + panic!("expected JSON response"); + }; + let page = ctx + .resp_handler + .list_input_items(&root.id, ItemPage::default()) + .await + .unwrap(); + assert_eq!( + page.data.len(), + 3, + "stored input items include the gateway call and result" + ); + let payload: RequestPayload = serde_json::from_value(serde_json::json!({ + "model": "test-model", "store": true, "previous_response_id": root.id, + "input": [{"type": "function_call_output", "call_id": "call_weather", "output": "sunny"}] + })) + .unwrap(); + let Either::Left(child) = ExecuteRequest::new(payload, Arc::clone(&ctx)).run().await.unwrap() else { + panic!("expected JSON continuation"); + }; + let page = ctx + .resp_handler + .list_input_items( + &child.id, + ItemPage { + order: ItemOrder::Asc, + ..ItemPage::default() + }, + ) + .await + .unwrap(); + let public = serde_json::to_value(&page.data).unwrap(); + let public = public.as_array().unwrap(); + assert_eq!( + public + .iter() + .map(|item| item["type"].as_str().unwrap()) + .collect::>(), + [ + "message", + "function_call", + "function_call_output", + "web_search_call", + "function_call", + "function_call_output" + ] + ); + assert_eq!(public[1]["call_id"], "call_search"); + assert_eq!(public[2]["call_id"], "call_search"); + assert_eq!(public[4]["name"], "get_weather"); + assert_eq!(public[5]["call_id"], "call_weather"); + let bodies = llm.request_bodies().await; + let model_input = bodies[1]["input"].as_array().unwrap(); + for kind in ["function_call", "function_call_output"] { + assert!( + model_input + .iter() + .any(|item| item["type"] == kind && item["call_id"] == "call_search"), + "canonical search {kind} must remain in inference history" + ); + } +}