blob: 60d1fd5796b93973bb69cf213d8a6084302d72a4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use maka_agent_facts::model::prompt::Message;
use maka_agent_facts::model::request::ProviderKind;
use maka_agent_facts::model::request::Request as ModelRequest;
use serde_json::Value;
use sha2::{Digest, Sha256};
use std::{
io,
sync::{Arc, Mutex},
};
/// The transport is only a cache. Confirmation must come from the canonical
/// projection after all local tool outcomes have committed.
#[derive(Clone, Default)]
pub struct Lane {
pub(crate) transport: crate::transport::ResponsesLane,
semantic: Arc<Mutex<Semantic>>,
}
#[derive(Default)]
struct Semantic {
pending: Option<Prefix>,
confirmed: Option<(Prefix, String)>,
}
struct Prefix {
length: usize,
digest: [u8; 32],
}
impl Prefix {
fn new(messages: &[Message]) -> Self {
struct Writer(Sha256);
impl io::Write for Writer {
fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
self.0.update(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
let mut writer = Writer(Sha256::new());
// Message serialization cannot fail with this infallible writer.
serde_json::to_writer(&mut writer, messages).expect("JSON value serialization");
Self {
length: messages.len(),
digest: writer.0.finalize().into(),
}
}
fn matches(&self, messages: &[Message]) -> bool {
messages
.get(..self.length)
.is_some_and(|prefix| Self::new(prefix).digest == self.digest)
}
}
impl Lane {
/// No readback is needed when there is no completed, cached WS response.
pub fn needs_confirmation(&self) -> bool {
self.transport.response_id().is_some()
}
/// Call once with the post-settlement model view. A changed prefix or
/// missing outcome disables reuse; later view changes are checked again.
pub fn confirm(
&self,
replay: &[Message],
settled_tool_call_ids: &[&str],
response_id: Option<&str>,
) -> bool {
let mut state = self.semantic.lock().unwrap();
state.confirmed = None;
let Some(pending) = state.pending.take() else {
return false;
};
let Some(id) = self.transport.response_id() else {
return false;
};
if response_id != Some(id.as_str()) {
return false;
}
if !pending.matches(replay) {
return false;
}
let response_end = match replay[pending.length..]
.iter()
.position(|message| matches!(message, Message::Tool { .. }))
{
Some(offset) => pending.length + offset,
None if settled_tool_call_ids.is_empty() => replay.len(),
None => return false,
};
if response_end <= pending.length {
return false;
}
let mut settled = settled_tool_call_ids.iter();
for message in &replay[response_end..] {
let Message::Tool { content, .. } = message else {
return false;
};
for part in content {
if !part.is_notification()
&& settled.next().copied() != Some(part.tool_call_id.as_str())
{
return false;
}
}
}
if settled.next().is_some() {
return false;
}
state.confirmed = Some((Prefix::new(&replay[..response_end]), id));
true
}
pub(crate) fn prepare(&self, request: &mut ModelRequest) {
let mut state = self.semantic.lock().unwrap();
let confirmed = state.confirmed.take();
state.pending = None;
if !matches!(
request.provider.kind,
ProviderKind::OpenaiResponses | ProviderKind::OpenResponses(_)
) || !request.provider.headers.is_empty()
|| request
.provider
.body_overlay
.as_ref()
.is_some_and(|body| !body.is_empty())
{
return;
}
state.pending = Some(Prefix::new(&request.prompt));
let Some((prefix, id)) = confirmed else {
return;
};
if self.transport.response_id().as_ref() != Some(&id)
|| request.prompt.len() <= prefix.length
|| !prefix.matches(&request.prompt)
{
return;
}
// Never replace caller-specified options with an optimization.
let Some(options) = request.provider_options.as_object_mut() else {
return;
};
let openai = options
.entry("openai")
.or_insert_with(|| serde_json::json!({}));
let Some(openai) = openai.as_object_mut() else {
return;
};
if openai.contains_key("previousResponseId") {
return;
}
openai.insert("previousResponseId".into(), Value::String(id));
request.prompt.drain(..prefix.length);
}
}