1use std::sync::Arc;
2
3use serde_json::{Map, Value};
4use sim_codec::Input;
5use sim_kernel::{Cx, DefaultFactory, EvalFabric, Expr, NoopEvalPolicy};
6
7use crate::{
8 clock::{GatewayClock, SystemGatewayClock},
9 codec_openai::{
10 OpenAiSseSurface, decode_openai_request, encode_gateway_events_sse,
11 encode_openai_responses_response,
12 },
13 content_id::content_id_for_expr,
14 objects::{GatewayRequest, GatewayResponse, GatewayResponseValue},
15 plan::{
16 check_plan, eval_plan_report_with_cache, eval_plan_report_with_cache_and_runners,
17 eval_plan_report_with_cache_runners_and_federation, parse_plan,
18 },
19 runtime::{
20 OpenAiGatewayFabric, OpenAiPlanCache, OpenAiRunnerRegistry, run_tool_loop_with_cache,
21 },
22 server::GatewayRouteState,
23 storage::{GatewayResponseObjectStore, GatewayStateStore, GatewayStore, StoredGatewayResponse},
24};
25
26pub use super::response_runtime::{
27 ResponseExecution, ResponseIdGenerators, ResponseRuntimeTargets,
28};
29use super::{
30 errors::OpenAiRouteError,
31 execution_record::{
32 EventInput, EventLog, RunPrologue, append_event, begin_run, response_usage_expr,
33 },
34 response_text::response_delta_chunks,
35 thread_context::normalize_response_request,
36};
37
38pub const RESPONSES_PATH: &str = "/v1/responses";
40pub const RESPONSE_RETRIEVAL_PREFIX: &str = "/v1/responses/";
42pub const RESPONSE_RETRIEVAL_ROUTE: &str = "/v1/responses/{id}";
44
45type RouteResult<T> = std::result::Result<T, OpenAiRouteError>;
46
47pub fn handle_responses(request: &GatewayRequest, state: &GatewayRouteState) -> GatewayResponse {
50 let mut clock = SystemGatewayClock;
51 let seed = clock.now_ms().unwrap_or(1);
52 let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
53 let fabric = OpenAiGatewayFabric::with_state_system(state.clone(), seed);
54 match state
55 .keys()
56 .with_effective_capabilities(&mut cx, request, |cx| {
57 fabric.realize(
58 cx,
59 OpenAiGatewayFabric::eval_request_for_gateway_request(request),
60 )
61 }) {
62 Ok(reply) => {
63 let Some(response) = reply.value.object().downcast_ref::<GatewayResponseValue>() else {
64 return OpenAiRouteError::internal_message(
65 "openai gateway fabric returned a non-response value",
66 )
67 .into_response();
68 };
69 response.response().clone()
70 }
71 Err(err) => OpenAiRouteError::internal_message(format!("gateway realize failed: {err}"))
72 .into_response(),
73 }
74}
75
76pub fn handle_response_retrieval(
79 request: &GatewayRequest,
80 state: &GatewayRouteState,
81) -> GatewayResponse {
82 let Some(response_id) = response_id_from_path(request.path()) else {
83 return OpenAiRouteError::not_found("response").into_response();
84 };
85 match state.store().lock() {
86 Ok(store) => retrieve_response(&*store, response_id),
87 Err(err) => OpenAiRouteError::internal_message(format!("gateway store lock failed: {err}"))
88 .into_response(),
89 }
90}
91
92pub fn retrieve_response<S>(store: &S, response_id: &str) -> GatewayResponse
95where
96 S: GatewayResponseObjectStore,
97{
98 store
99 .response_object(response_id)
100 .map(|record| record.response().clone())
101 .unwrap_or_else(|| OpenAiRouteError::not_found(response_id).into_response())
102}
103
104pub fn execute_response_request<S, C>(
107 cx: &mut Cx,
108 store: &mut S,
109 ids: &mut ResponseIdGenerators,
110 clock: &mut C,
111 request: &GatewayRequest,
112) -> ResponseExecution
113where
114 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
115 C: GatewayClock,
116{
117 let mut cache = OpenAiPlanCache::new();
118 execute_response_request_with_cache(cx, store, &mut cache, ids, clock, request)
119}
120
121pub fn execute_response_request_with_cache<S, C>(
124 cx: &mut Cx,
125 store: &mut S,
126 cache: &mut OpenAiPlanCache,
127 ids: &mut ResponseIdGenerators,
128 clock: &mut C,
129 request: &GatewayRequest,
130) -> ResponseExecution
131where
132 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
133 C: GatewayClock,
134{
135 match try_execute_response_request(cx, store, cache, ids, clock, request, None) {
136 Ok(execution) => execution,
137 Err(error) => ResponseExecution::error(error),
138 }
139}
140
141pub fn execute_response_request_with_runners<S, C>(
144 cx: &mut Cx,
145 store: &mut S,
146 ids: &mut ResponseIdGenerators,
147 clock: &mut C,
148 request: &GatewayRequest,
149 runners: &OpenAiRunnerRegistry,
150) -> ResponseExecution
151where
152 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
153 C: GatewayClock,
154{
155 let mut cache = OpenAiPlanCache::new();
156 execute_response_request_with_cache_and_runners(
157 cx, store, &mut cache, ids, clock, request, runners,
158 )
159}
160
161pub fn execute_response_request_with_cache_and_runners<S, C>(
164 cx: &mut Cx,
165 store: &mut S,
166 cache: &mut OpenAiPlanCache,
167 ids: &mut ResponseIdGenerators,
168 clock: &mut C,
169 request: &GatewayRequest,
170 runners: &OpenAiRunnerRegistry,
171) -> ResponseExecution
172where
173 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
174 C: GatewayClock,
175{
176 match try_execute_response_request(
177 cx,
178 store,
179 cache,
180 ids,
181 clock,
182 request,
183 Some(ResponseRuntimeTargets::runners(runners)),
184 ) {
185 Ok(execution) => execution,
186 Err(error) => ResponseExecution::error(error),
187 }
188}
189
190pub fn execute_response_request_with_cache_runners_and_federation<S, C>(
193 cx: &mut Cx,
194 store: &mut S,
195 cache: &mut OpenAiPlanCache,
196 ids: &mut ResponseIdGenerators,
197 clock: &mut C,
198 request: &GatewayRequest,
199 targets: ResponseRuntimeTargets<'_>,
200) -> ResponseExecution
201where
202 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
203 C: GatewayClock,
204{
205 match try_execute_response_request(cx, store, cache, ids, clock, request, Some(targets)) {
206 Ok(execution) => execution,
207 Err(error) => ResponseExecution::error(error),
208 }
209}
210
211fn try_execute_response_request<S, C>(
212 cx: &mut Cx,
213 store: &mut S,
214 cache: &mut OpenAiPlanCache,
215 ids: &mut ResponseIdGenerators,
216 clock: &mut C,
217 request: &GatewayRequest,
218 targets: Option<ResponseRuntimeTargets<'_>>,
219) -> RouteResult<ResponseExecution>
220where
221 S: GatewayStore + GatewayResponseObjectStore + GatewayStateStore,
222 C: GatewayClock,
223{
224 let normalized = normalize_response_request(store, request)?;
225 let object = normalized.object;
226 let model = required_string(&object, "model")?.to_owned();
227 require_input(&object)?;
228 let store_response = object
229 .get("store")
230 .and_then(Value::as_bool)
231 .unwrap_or(false);
232 let stream_response = object
233 .get("stream")
234 .and_then(Value::as_bool)
235 .unwrap_or(false);
236
237 let request_expr = decode_openai_request(Input::Bytes(normalized.request.body().to_vec()))
238 .map_err(OpenAiRouteError::bad_request_from_error)?;
239 let plan = parse_plan(&model).map_err(OpenAiRouteError::bad_model_from_error)?;
240 check_plan(&plan).map_err(OpenAiRouteError::bad_model_from_error)?;
241
242 let RunPrologue {
243 recorded_request,
244 request_content_id,
245 run_id,
246 run_content_id,
247 } = begin_run(store, ids, clock, &normalized.request, None, store_response)?;
248
249 let mut event_log = EventLog::default();
250 let mut sequence = 0;
251 append_event(
252 store,
253 ids,
254 clock,
255 &run_id,
256 EventInput::new(sequence, "request-start", recorded_request.to_expr()),
257 store_response,
258 &mut event_log,
259 )?;
260 sequence += 1;
261 append_event(
262 store,
263 ids,
264 clock,
265 &run_id,
266 EventInput::new(sequence, "plan-start", plan.clone()),
267 store_response,
268 &mut event_log,
269 )?;
270 sequence += 1;
271 append_event(
272 store,
273 ids,
274 clock,
275 &run_id,
276 EventInput::new(sequence, "model-start", Expr::String(model.clone())),
277 store_response,
278 &mut event_log,
279 )?;
280 sequence += 1;
281
282 let initial_plan_report = match targets {
283 Some(targets) => {
284 if let Some(federation) = targets.federation_ref() {
285 eval_plan_report_with_cache_runners_and_federation(
286 cx,
287 &plan,
288 &request_expr,
289 cache,
290 targets.runners_ref(),
291 federation,
292 )
293 } else {
294 eval_plan_report_with_cache_and_runners(
295 cx,
296 &plan,
297 &request_expr,
298 cache,
299 targets.runners_ref(),
300 )
301 }
302 }
303 None => eval_plan_report_with_cache(cx, &plan, &request_expr, cache),
304 }
305 .map_err(|err| OpenAiRouteError::model(err, &model))?;
306 let plan_report = run_tool_loop_with_cache(
307 cx,
308 &plan,
309 &request_expr,
310 &object,
311 cache,
312 initial_plan_report,
313 )
314 .map_err(|err| OpenAiRouteError::model(err, &model))?;
315 for event in &plan_report.events {
316 append_event(
317 store,
318 ids,
319 clock,
320 &run_id,
321 EventInput::from_symbol(sequence, event.kind.clone(), event.payload.clone()),
322 store_response,
323 &mut event_log,
324 )?;
325 sequence += 1;
326 }
327 let model_response = plan_report.response;
328 for delta in response_delta_chunks(&model_response, stream_response)? {
329 append_event(
330 store,
331 ids,
332 clock,
333 &run_id,
334 EventInput::new(sequence, "delta", Expr::String(delta)),
335 store_response,
336 &mut event_log,
337 )?;
338 sequence += 1;
339 }
340 append_event(
341 store,
342 ids,
343 clock,
344 &run_id,
345 EventInput::new(sequence, "usage", response_usage_expr(&model_response)),
346 store_response,
347 &mut event_log,
348 )?;
349 sequence += 1;
350 append_event(
351 store,
352 ids,
353 clock,
354 &run_id,
355 EventInput::new(sequence, "final", model_response.clone()),
356 store_response,
357 &mut event_log,
358 )?;
359
360 let response_id = ids.response.next_id().map_err(OpenAiRouteError::internal)?;
361 let response_created_at = clock.now_ms().map_err(OpenAiRouteError::internal)?;
362 let response_body =
363 encode_openai_responses_response(&model_response, &response_id, response_created_at)
364 .map_err(OpenAiRouteError::internal)?;
365 let final_response = GatewayResponse::json(200, response_body);
366 let response_content_id = if store_response {
367 let id =
368 content_id_for_expr(&final_response.to_expr()).map_err(OpenAiRouteError::internal)?;
369 let mut record =
370 StoredGatewayResponse::new(response_id.clone(), id.clone(), final_response.clone());
371 record.request_content_id = Some(request_content_id.clone());
372 record.run_content_id = Some(run_content_id.clone());
373 record.event_content_ids = event_log.content_ids.clone();
374 store
375 .put_response_object(record)
376 .map_err(OpenAiRouteError::internal)?;
377 Some(id)
378 } else {
379 None
380 };
381 let response = if stream_response {
382 GatewayResponse::sse(
383 200,
384 encode_gateway_events_sse(
385 &event_log.events,
386 OpenAiSseSurface::Responses,
387 &response_id,
388 response_created_at,
389 )
390 .map_err(OpenAiRouteError::internal)?,
391 )
392 } else {
393 final_response
394 };
395
396 Ok(ResponseExecution {
397 response,
398 request_content_id: Some(request_content_id),
399 run_content_id: Some(run_content_id),
400 event_content_ids: event_log.content_ids,
401 events: event_log.events,
402 response_id: Some(response_id),
403 response_created_at_ms: Some(response_created_at),
404 response_content_id,
405 })
406}
407
408fn response_id_from_path(path: &str) -> Option<&str> {
409 super::path::id_from_path(path, RESPONSE_RETRIEVAL_PREFIX)
410}
411
412use crate::routes::request_json::required_string;
413
414fn require_input(object: &Map<String, Value>) -> RouteResult<()> {
415 if object.contains_key("input") || object.contains_key("messages") {
416 Ok(())
417 } else {
418 Err(OpenAiRouteError::missing_required("input"))
419 }
420}