blob: 8055538c684e98548bfb4dffe840f1811d36c249 [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 crate::support::{code_mode, context as fixture, image_projection};
use maka_agent_engine::RunWork;
use maka_agent_facts::context::CheckpointMode;
use maka_agent_facts::context::ModelPurpose;
use maka_agent_facts::context::ModelRequestContext;
use maka_agent_facts::event::Fact;
use maka_agent_facts::event::InvocationOutcome;
use maka_event_log::EventLog;
use serde_json::json;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use tokio::{net::TcpListener, sync::Barrier};
use tokio_util::sync::CancellationToken;
#[path = "auto_context/background.rs"]
mod background;
fn context(window: Option<u64>) -> Option<ModelRequestContext> {
Some(ModelRequestContext {
provider_id: "openai".into(),
context_window: Some(170_000),
model_context_window: Some(200_000),
declared_window: window,
})
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn resolved_threshold_and_matching_latest_main_route_are_required_for_preturn() {
for (declared, matching, expected) in [
(None, true, false),
(Some(70), false, false),
(Some(70), true, true),
] {
let directory = tempfile::tempdir().unwrap();
let log = Arc::new(
EventLog::open(&directory.path().join("events.sqlite"))
.await
.unwrap(),
);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let base = format!("http://{}/v1", listener.local_addr().unwrap());
let server = tokio::spawn(async move {
let (mut socket, _) = listener.accept().await.unwrap();
fixture::read_request(&mut socket).await;
fixture::respond(&mut socket, "old-answer", "stop").await;
let mut requests = Vec::new();
let mut main = None;
for _ in 0..if expected { 2 } else { 1 } {
let (mut socket, _) = listener.accept().await.unwrap();
let request = fixture::read_request(&mut socket).await;
if request["messages"].to_string().contains(
maka_agent_facts::context::default_compaction_prompt(
maka_agent_facts::language::Language::En,
)
.lines()
.next()
.unwrap(),
) {
fixture::respond(&mut socket, fixture::SUMMARY, "stop").await;
} else {
main = Some(socket);
}
requests.push(request);
}
fixture::respond(main.as_mut().unwrap(), "done", "stop").await;
requests
});
let worker = fixture::engine(log.clone());
let mut old = fixture::input(&base, "old", false);
old.context = context(None);
worker.run(old, CancellationToken::new()).await.unwrap();
let mut next = fixture::input(&base, "next", false);
next.main_output_limit = Some(200_000);
next.context = context(declared);
if !matching {
next.configuration.model.as_mut().unwrap().connection_id = "different".into();
}
worker.run(next, CancellationToken::new()).await.unwrap();
worker.drain().await;
let requests = server.await.unwrap();
let (summaries, mains): (Vec<_>, Vec<_>) = requests.iter().partition(|request| {
request["messages"].to_string().contains(
maka_agent_facts::context::default_compaction_prompt(
maka_agent_facts::language::Language::En,
)
.lines()
.next()
.unwrap(),
)
});
assert_eq!(summaries.len(), usize::from(expected));
assert_eq!(mains.len(), 1);
let output = mains[0]["max_tokens"].as_u64().unwrap();
if matching {
// Proactive work runs alongside the main request. Until adoption,
// the matching route still uses its retained provider usage.
assert_eq!(output, 191_930);
} else {
assert!((199_000..200_000).contains(&output));
}
let prefix = log.prefix(100, 128 * 1024).await.unwrap();
assert_eq!(
prefix
.events
.iter()
.filter(|event| matches!(
event.event.fact,
Fact::ModelRequested {
purpose: ModelPurpose::Summary,
..
}
))
.count(),
usize::from(expected)
);
if expected {
assert!(
!summaries[0]["messages"]
.to_string()
.contains("question-next"),
"preturn source excludes current anchor"
);
assert!(mains[0]["messages"].to_string().contains("question-next"));
assert!(mains[0]["messages"].to_string().contains("old-answer"));
}
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn midturn_waits_for_all_code_mode_results_and_replays_exact_image_anchor_after_reopen() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("events.sqlite");
let log = Arc::new(EventLog::open(&path).await.unwrap());
log.create_session("session", "create", &json!({}), 1)
.await
.unwrap();
image_projection::artifact(&log, "anchor-image", 8, true).await;
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let base = format!("http://{}/v1", listener.local_addr().unwrap());
let observed = log.clone();
let server = tokio::spawn(async move {
let (mut first, _) = listener.accept().await.unwrap();
fixture::read_request(&mut first).await;
code_mode::respond(&mut first, true).await;
let (mut summary, _) = listener.accept().await.unwrap();
let summary_request = fixture::read_request(&mut summary).await;
let prefix = observed.prefix(100, 128 * 1024).await.unwrap();
assert_eq!(
prefix
.events
.iter()
.filter(|event| matches!(event.event.fact, Fact::ToolSettled { .. }))
.count(),
4
);
assert!(
!prefix
.events
.iter()
.any(|event| matches!(event.event.fact, Fact::InvocationEnded { .. }))
);
fixture::respond(&mut summary, fixture::SUMMARY, "stop").await;
let (mut next, _) = listener.accept().await.unwrap();
let after = fixture::read_request(&mut next).await;
code_mode::respond(&mut next, true).await;
// An accepted tool step renews compaction within this same turn.
let (mut summary, _) = listener.accept().await.unwrap();
let second_summary = fixture::read_request(&mut summary).await;
assert!(
second_summary["messages"]
.to_string()
.contains("tool_call_id")
);
fixture::respond(&mut summary, fixture::SUMMARY, "stop").await;
let (mut final_step, _) = listener.accept().await.unwrap();
fixture::read_request(&mut final_step).await;
fixture::respond(&mut final_step, "done", "stop").await;
(summary_request, after)
});
let count = Arc::new(AtomicUsize::new(0));
let effects = Arc::new(code_mode::Effects {
log: log.clone(),
count: count.clone(),
together: Arc::new(Barrier::new(2)),
});
let mut input = code_mode::input(&base, "first", effects.clone());
input.context = context(Some(7));
// This test needs adoption before the next main request. Real prompt
// pressure requires that boundary; a usage threshold alone runs in the background.
input.context.as_mut().unwrap().context_window = Some(4096);
input.configuration.system_prompt = Some(maka_agent_facts::execution::SystemPrompt {
sources: Vec::new(),
text: "main prompt pressure ".repeat(600),
policy_revision: 1,
});
input.supports_vision = true;
input.configuration.model = fixture::input(&base, "unused", false).configuration.model;
if let RunWork::Message { message, .. } = &mut input.work {
*message=serde_json::from_value(json!({"text":"question-first","attachments":[image_projection::attachment("anchor-image")]})).unwrap();
}
let worker = fixture::engine(log.clone());
worker.run(input, CancellationToken::new()).await.unwrap();
worker.drain().await;
let (summary_request, after) = server.await.unwrap();
assert!(
summary_request["messages"]
.to_string()
.contains("tool_call_id")
);
assert!(after["messages"].to_string().contains("summary-marker"));
assert!(after["messages"].to_string().contains("image_url"));
let source = log.read_model_context("session", None).await.unwrap();
let anchor = source.anchor.as_ref().unwrap().event.id.clone();
assert!(
matches!(&source.baseline.as_ref().unwrap().checkpoint.mode,CheckpointMode::MidTurn {anchor_event_id} if anchor_event_id==&anchor)
);
assert_eq!(count.load(Ordering::SeqCst), 4);
let prefix = log.prefix(100, 256 * 1024).await.unwrap();
assert_eq!(
prefix
.events
.iter()
.filter(|event| matches!(
event.event.fact,
Fact::ModelRequested {
purpose: ModelPurpose::Summary,
..
}
))
.count(),
2
);
assert_eq!(
prefix
.events
.iter()
.filter(|event| matches!(
event.event.fact,
Fact::InvocationEnded {
outcome: InvocationOutcome::Completed
}
))
.count(),
1
);
drop(worker);
drop(effects);
Arc::try_unwrap(log).ok().unwrap().close().await.unwrap();
let log = Arc::new(EventLog::open(&path).await.unwrap());
assert_eq!(
log.read_model_context("session", None)
.await
.unwrap()
.anchor
.unwrap()
.event
.id,
anchor
);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let base = format!("http://{}/v1", listener.local_addr().unwrap());
let server = tokio::spawn(async move {
let (mut socket, _) = listener.accept().await.unwrap();
let request = fixture::read_request(&mut socket).await;
fixture::respond(&mut socket, "reopened", "stop").await;
request
});
let worker = fixture::engine(log);
let mut next = fixture::input(&base, "reopen", false);
next.supports_vision = true;
worker.run(next, CancellationToken::new()).await.unwrap();
worker.drain().await;
let next = server.await.unwrap();
assert!(next["messages"].to_string().contains("question-first"));
assert!(next["messages"].to_string().contains("image_url"));
}