Update Moim (#9636)

Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com>
Co-authored-by: Douwe Osinga <douwe@squareup.com>
Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
Douwe Osinga
2026-06-18 16:02:40 -04:00
committed by GitHub
parent b3cf3cbcff
commit 09c8d2be5a
11 changed files with 414 additions and 170 deletions
+2 -1
View File
@@ -1898,7 +1898,8 @@ impl Agent {
&session_config.id,
conversation.clone(),
&self.extension_manager,
&working_dir,
turns_taken,
max_turns,
).await;
let mut stream = Self::stream_response_from_provider(
+4 -66
View File
@@ -1949,51 +1949,7 @@ impl ExtensionManager {
.map(|ext| ext.get_client())
}
pub async fn collect_moim(
&self,
session_id: &str,
working_dir: &std::path::Path,
) -> Option<String> {
// Skip MOIM for models with small context windows to avoid consuming limited context
const MIN_CONTEXT_FOR_MOIM: usize = 32_000;
if let Ok(provider_guard) = self.provider.try_lock() {
if let Some(provider) = provider_guard.as_ref() {
if provider.get_model_config().context_limit() < MIN_CONTEXT_FOR_MOIM {
return None;
}
}
}
// Use minute-level granularity to prevent conversation changes every second
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:00").to_string();
let mut content = format!(
"<info-msg>\nIt is currently {}\nWorking directory: {}\n",
timestamp,
working_dir.display()
);
if let Ok(session) = self
.context
.session_manager
.get_session(session_id, false)
.await
{
if let (Some(total), Some(config)) =
(session.total_tokens, session.model_config.as_ref())
{
let limit = config.context_limit();
if total > 0 && limit > 0 {
let pct = (total as f64 / limit as f64 * 100.0).round() as u32;
content.push_str(&format!(
"Context: ~{}k/{}k tokens used ({}%)\n",
total / 1000,
limit / 1000,
pct
));
}
}
}
pub async fn collect_moim_parts(&self, session_id: &str) -> Vec<String> {
let platform_clients: Vec<(String, McpClientBox)> = {
let extensions = self.extensions.lock().await;
extensions
@@ -2015,17 +1971,14 @@ impl ExtensionManager {
.collect()
};
let mut parts = Vec::new();
for (name, client) in platform_clients {
if let Some(moim_content) = client.get_moim(session_id).await {
tracing::debug!("MOIM content from {}: {} chars", name, moim_content.len());
content.push('\n');
content.push_str(&moim_content);
parts.push(moim_content);
}
}
content.push_str("\n</info-msg>");
Some(content)
parts
}
}
@@ -2417,21 +2370,6 @@ mod tests {
assert_eq!(result, "abc$KEY");
}
#[tokio::test]
async fn test_collect_moim_uses_minute_granularity() {
let temp_dir = tempfile::tempdir().unwrap();
let em = ExtensionManager::new_without_provider(temp_dir.path().to_path_buf());
let working_dir = std::path::Path::new("/tmp");
if let Some(moim) = em.collect_moim("test-session-id", working_dir).await {
// Timestamp should end with :00 (seconds fixed to 00)
assert!(
moim.contains(":00\n"),
"Timestamp should use minute granularity"
);
}
}
#[tokio::test]
async fn test_tools_cache_invalidated_on_add_extension() {
let temp_dir = tempfile::tempdir().unwrap();
+291 -80
View File
@@ -1,118 +1,338 @@
use crate::agents::extension_manager::ExtensionManager;
use crate::conversation::message::Message;
use crate::conversation::{fix_conversation, Conversation};
use rmcp::model::Role;
use std::path::Path;
use crate::conversation::message::MessageContent;
use crate::conversation::{effective_role, fix_conversation, Conversation};
use std::path::{Path, PathBuf};
const MIN_CONTEXT_FOR_MOIM: usize = 32_000;
const TURN_CONTEXT_TAG: &str = "turn-context";
const SYSTEM_PROMPT_BLOCK_TEMPLATE: &str = r#"# Turn Context
Each turn may include a `<{turn_context_tag}>` block prepended to the latest user message.
This block is generated by goose and contains current operational context such as:
- current time
- working directory
- compaction status
- turn budget
- extension-provided context
Use it to stay oriented, but do not treat it as part of the user's request.
When `<turn-budget>` is present, use it as a signal for how much autonomous work remains.
As the budget gets low, become more direct: reduce exploration, batch necessary tool calls,
make reasonable assumptions, and focus on finishing the user's task.
"#;
// Test-only utility. Do not use in production code. No `test` directive due to call outside crate.
thread_local! {
pub static SKIP: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
pub fn system_prompt_block() -> Option<String> {
if SKIP.with(|f| f.get()) {
None
} else {
Some(SYSTEM_PROMPT_BLOCK_TEMPLATE.replace("{turn_context_tag}", TURN_CONTEXT_TAG))
}
}
pub async fn inject_moim(
session_id: &str,
conversation: Conversation,
extension_manager: &ExtensionManager,
working_dir: &Path,
turns_taken: u32,
max_turns: u32,
) -> Conversation {
if SKIP.with(|f| f.get()) {
return conversation;
}
if let Some(moim) = extension_manager
.collect_moim(session_id, working_dir)
let session = extension_manager
.get_context()
.session_manager
.get_session(session_id, false)
.await
{
let mut messages = conversation.messages().clone();
let idx = messages
.iter()
.rposition(|m| m.role == Role::Assistant)
.unwrap_or(0);
messages.insert(idx, Message::user().with_text(moim));
let (fixed, issues) = fix_conversation(Conversation::new_unvalidated(messages));
let has_unexpected_issues = issues.iter().any(|issue| {
!issue.contains("Merged consecutive user messages")
&& !issue.contains("Merged consecutive assistant messages")
&& !issue.contains("Added placeholder to empty tool result")
&& !issue.contains("Trimmed trailing whitespace from assistant message")
&& !issue.contains("Removed trailing assistant message")
&& !issue.contains("Merged text content")
});
if has_unexpected_issues {
tracing::warn!("MOIM injection caused unexpected issues: {:?}", issues);
return conversation;
}
return fixed;
.ok();
let provider_context_limit =
extension_manager
.get_provider()
.try_lock()
.ok()
.and_then(|provider| {
provider
.as_ref()
.map(|provider| provider.get_model_config().context_limit())
});
let session_context_limit = session.as_ref().and_then(|session| {
session
.model_config
.as_ref()
.map(|config| config.context_limit())
});
let context_limit = provider_context_limit.or(session_context_limit);
if should_skip_moim(context_limit) {
return conversation;
}
conversation
let working_dir = session
.as_ref()
.map(|session| session.working_dir.clone())
.unwrap_or_else(|| PathBuf::from("."));
let total_tokens = session.as_ref().and_then(|session| session.total_tokens);
let compaction_threshold = crate::config::Config::global()
.get_param::<f64>("GOOSE_AUTO_COMPACT_THRESHOLD")
.unwrap_or(crate::context_mgmt::DEFAULT_COMPACTION_THRESHOLD);
let extension_parts = extension_manager.collect_moim_parts(session_id).await;
let moim = compose_moim(
&working_dir,
total_tokens,
context_limit,
compaction_threshold,
turns_taken,
max_turns,
extension_parts,
);
let mut messages = conversation.messages().clone();
let Some(idx) = messages
.iter()
.rposition(|m| m.is_agent_visible() && effective_role(m) == "user")
else {
return conversation;
};
let insert_idx = messages[idx]
.content
.iter()
.take_while(|content| matches!(content, MessageContent::ToolResponse(_)))
.count();
messages[idx]
.content
.insert(insert_idx, MessageContent::text(moim));
let (fixed, issues) = fix_conversation(Conversation::new_unvalidated(messages));
let has_unexpected_issues = issues.iter().any(|issue| {
!issue.contains("Merged consecutive user messages")
&& !issue.contains("Merged consecutive assistant messages")
&& !issue.contains("Added placeholder to empty tool result")
&& !issue.contains("Trimmed trailing whitespace from assistant message")
&& !issue.contains("Removed trailing assistant message")
&& !issue.contains("Merged text content")
});
if has_unexpected_issues {
tracing::warn!("MOIM injection caused unexpected issues: {:?}", issues);
return conversation;
}
fixed
}
fn should_skip_moim(context_limit: Option<usize>) -> bool {
context_limit.is_some_and(|limit| limit < MIN_CONTEXT_FOR_MOIM)
}
fn compose_moim(
working_dir: &Path,
total_tokens: Option<i32>,
context_limit: Option<usize>,
compaction_threshold: f64,
turns_taken: u32,
max_turns: u32,
extension_parts: Vec<String>,
) -> String {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:00");
let mut lines = vec![
open_tag(TURN_CONTEXT_TAG),
tag("current-time", &timestamp.to_string()),
tag("working-directory", &working_dir.display().to_string()),
];
if let Some(value) =
compaction_remaining_line(total_tokens, context_limit, compaction_threshold)
{
lines.push(tag("compaction", &value));
}
if let Some(value) = turn_budget_line(turns_taken, max_turns) {
lines.push(tag("turn-budget", &value));
}
for part in extension_parts {
if !part.trim().is_empty() {
lines.push(String::new());
lines.push(part);
}
}
lines.push(close_tag(TURN_CONTEXT_TAG));
lines.join("\n")
}
fn open_tag(name: &str) -> String {
format!("<{name}>")
}
fn close_tag(name: &str) -> String {
format!("</{name}>")
}
fn tag(name: &str, value: &str) -> String {
format!("<{name}>{}</{name}>", escape_xml_text(value))
}
fn escape_xml_text(value: &str) -> String {
value
.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
}
fn compaction_remaining_line(
total_tokens: Option<i32>,
context_limit: Option<usize>,
threshold: f64,
) -> Option<String> {
let total_tokens = total_tokens?;
let context_limit = context_limit?;
if total_tokens <= 0 || context_limit == 0 || threshold <= 0.0 || threshold >= 1.0 {
return None;
}
let compaction_at = (context_limit as f64 * threshold) as i32;
if compaction_at <= 0 || (total_tokens as f64 / compaction_at as f64) < 0.5 {
return None;
}
Some(format!(
"~{}k tokens remaining",
compaction_at.saturating_sub(total_tokens) / 1000
))
}
fn turn_budget_line(turns_taken: u32, max_turns: u32) -> Option<String> {
if max_turns == 0 || turns_taken.saturating_mul(2) < max_turns {
return None;
}
Some(format!("{turns_taken}/{max_turns} used"))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conversation::message::Message;
use rmcp::model::CallToolRequestParams;
use std::path::PathBuf;
fn text_at(message: &crate::conversation::message::Message, index: usize) -> &str {
message.content[index].as_text().unwrap()
}
fn is_moim(content: &MessageContent) -> bool {
content
.as_text()
.is_some_and(|text| text.starts_with(&format!("<{}>\n", TURN_CONTEXT_TAG)))
}
#[tokio::test]
async fn test_moim_injection_before_assistant() {
async fn test_moim_prepended_to_latest_user_message() {
let temp_dir = tempfile::tempdir().unwrap();
let em = ExtensionManager::new_without_provider(temp_dir.path().to_path_buf());
let working_dir = PathBuf::from("/test/dir");
let session = em
.get_context()
.session_manager
.create_session(
PathBuf::from("/test/dir"),
"test".to_string(),
crate::session::SessionType::User,
crate::config::GooseMode::Auto,
)
.await
.unwrap();
let conv = Conversation::new_unvalidated(vec![
Message::user().with_text("Hello"),
Message::assistant().with_text("Hi"),
Message::user().with_text("Bye"),
]);
let result = inject_moim("test-session-id", conv, &em, &working_dir).await;
let result = inject_moim(&session.id, conv, &em, 0, 100).await;
let msgs = result.messages();
assert_eq!(msgs.len(), 3);
assert_eq!(msgs[0].content[0].as_text().unwrap(), "Hello");
assert_eq!(msgs[1].content[0].as_text().unwrap(), "Hi");
let merged_content = msgs[0]
.content
.iter()
.filter_map(|c| c.as_text())
.collect::<Vec<_>>()
.join("");
assert!(merged_content.contains("Hello"));
assert!(merged_content.contains("<info-msg>"));
assert!(merged_content.contains("Working directory: /test/dir"));
assert_eq!(text_at(&msgs[0], 0), "Hello");
assert_eq!(text_at(&msgs[1], 0), "Hi");
assert!(is_moim(&msgs[2].content[0]));
assert_eq!(text_at(&msgs[2], 1), "Bye");
}
#[tokio::test]
async fn test_moim_injection_no_assistant() {
let temp_dir = tempfile::tempdir().unwrap();
let em = ExtensionManager::new_without_provider(temp_dir.path().to_path_buf());
let working_dir = PathBuf::from("/test/dir");
let session = em
.get_context()
.session_manager
.create_session(
PathBuf::from("/test/dir"),
"test".to_string(),
crate::session::SessionType::User,
crate::config::GooseMode::Auto,
)
.await
.unwrap();
let conv = Conversation::new_unvalidated(vec![Message::user().with_text("Hello")]);
let result = inject_moim("test-session-id", conv, &em, &working_dir).await;
let result = inject_moim(&session.id, conv, &em, 0, 100).await;
assert_eq!(result.messages().len(), 1);
assert!(is_moim(&result.messages()[0].content[0]));
assert_eq!(text_at(&result.messages()[0], 1), "Hello");
}
let merged_content = result.messages()[0]
.content
.iter()
.filter_map(|c| c.as_text())
.collect::<Vec<_>>()
.join("");
assert!(merged_content.contains("Hello"));
assert!(merged_content.contains("<info-msg>"));
assert!(merged_content.contains("Working directory: /test/dir"));
#[tokio::test]
async fn test_moim_skips_user_messages_not_visible_to_agent() {
let temp_dir = tempfile::tempdir().unwrap();
let em = ExtensionManager::new_without_provider(temp_dir.path().to_path_buf());
let session = em
.get_context()
.session_manager
.create_session(
PathBuf::from("/test/dir"),
"test".to_string(),
crate::session::SessionType::User,
crate::config::GooseMode::Auto,
)
.await
.unwrap();
let conv = Conversation::new_unvalidated(vec![
Message::user().with_text("agent visible"),
Message::assistant().with_text("reply"),
Message::user().with_text("user only").user_only(),
]);
let result = inject_moim(&session.id, conv, &em, 0, 100).await;
let msgs = result.messages();
assert_eq!(msgs.len(), 2);
assert!(is_moim(&msgs[0].content[0]));
assert_eq!(text_at(&msgs[0], 1), "agent visible");
assert_eq!(text_at(&msgs[1], 0), "user only");
assert!(!msgs[1].is_agent_visible());
}
#[tokio::test]
async fn test_moim_with_tool_calls() {
let temp_dir = tempfile::tempdir().unwrap();
let em = ExtensionManager::new_without_provider(temp_dir.path().to_path_buf());
let working_dir = PathBuf::from("/test/dir");
let session = em
.get_context()
.session_manager
.create_session(
PathBuf::from("/test/dir"),
"test".to_string(),
crate::session::SessionType::User,
crate::config::GooseMode::Auto,
)
.await
.unwrap();
let conv = Conversation::new_unvalidated(vec![
Message::user().with_text("Search for something"),
@@ -121,27 +341,18 @@ mod tests {
.with_tool_request("search_1", Ok(CallToolRequestParams::new("search"))),
Message::user()
.with_tool_response("search_1", Ok(rmcp::model::CallToolResult::success(vec![]))),
Message::assistant()
.with_text("I need to search more")
.with_tool_request("search_2", Ok(CallToolRequestParams::new("search"))),
Message::user()
.with_tool_response("search_2", Ok(rmcp::model::CallToolResult::success(vec![]))),
]);
let result = inject_moim("test-session-id", conv, &em, &working_dir).await;
let result = inject_moim(&session.id, conv, &em, 0, 100).await;
let msgs = result.messages();
assert_eq!(msgs.len(), 6);
let moim_msg = &msgs[3];
let has_moim = moim_msg
.content
.iter()
.any(|c| c.as_text().is_some_and(|t| t.contains("<info-msg>")));
assert!(
has_moim,
"MOIM should be in message before latest assistant message"
);
assert_eq!(msgs.len(), 3);
assert!(is_moim(&msgs[0].content[0]));
assert_eq!(text_at(&msgs[0], 1), "Search for something");
assert!(matches!(
&msgs[2].content[0],
MessageContent::ToolResponse(_)
));
assert_eq!(msgs[2].content.len(), 1);
}
}
@@ -478,21 +478,8 @@ impl McpClientTrait for CodeExecutionClient {
let disclosure_style_moim = match self.disclosure {
ToolDisclosure::Catalog => {
let functions = code_mode.list_functions().functions;
let sandbox_only: Vec<_> = functions
.iter()
.filter(|f| !crate::agents::extension_manager::is_first_class_extension(&f.namespace))
.map(|f| format!("{}.{}", &f.namespace, &f.name))
.collect();
let mut msg = String::new();
if !sandbox_only.is_empty() {
msg.push_str(&format!(
"Additional functions available ONLY via execute_typescript (do NOT call these as direct tool calls): {}",
sandbox_only.join(", ")
));
}
msg.push_str("\n\n Use the list_functions & get_function_details tools to see tool signatures and input/output types before calling execute_typescript.");
msg
let function_count = code_mode.list_functions().functions.len();
catalog_disclosure_moim(function_count)
}
ToolDisclosure::Filesystem => {
let available_filepaths: Vec<_> = code_mode
@@ -515,6 +502,16 @@ impl McpClientTrait for CodeExecutionClient {
}
}
fn catalog_disclosure_moim(function_count: usize) -> String {
if function_count == 0 {
"No execute_typescript callback functions are currently registered.".to_string()
} else {
format!(
"{function_count} callback functions are available only from inside execute_typescript. Do not call callback function names directly as tools. Use list_functions and get_function_details to inspect signatures before writing one execute_typescript call."
)
}
}
pub fn get_tool_disclosure() -> ToolDisclosure {
let config = crate::config::Config::global();
let tool_disclosure_str: String = config
@@ -554,3 +551,19 @@ impl CodeModeState {
hasher.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_moim_mentions_inspection_tools_without_function_names() {
let moim = catalog_disclosure_moim(3);
assert!(moim.contains("3 callback functions"));
assert!(moim.contains("list_functions"));
assert!(moim.contains("get_function_details"));
assert!(!moim.contains("extract_relations"));
assert!(!moim.contains("ask_heimdall"));
}
}
+4 -1
View File
@@ -6,7 +6,7 @@ use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;
use crate::agents::extension::ExtensionInfo;
use crate::agents::{extension::ExtensionInfo, moim};
use crate::hints::load_hints::build_gitignore;
use crate::hints::{get_context_filenames, load_hint_files, SubdirectoryHintTracker};
use crate::{
@@ -44,6 +44,8 @@ struct SystemPromptContext {
max_extensions: usize,
max_tools: usize,
code_execution_mode: bool,
#[serde(skip_serializing_if = "Option::is_none")]
moim_system_prompt_block: Option<String>,
}
pub struct SystemPromptBuilder<'a, M> {
@@ -152,6 +154,7 @@ impl<'a> SystemPromptBuilder<'a, PromptManager> {
max_extensions: MAX_EXTENSIONS,
max_tools: MAX_TOOLS,
code_execution_mode: self.code_execution_mode,
moim_system_prompt_block: moim::system_prompt_block(),
};
let base_prompt = if let Some(override_prompt) = &self.manager.system_prompt_override {
@@ -6,6 +6,23 @@ expression: system_prompt
You are a general-purpose AI agent called goose, created by AAIF (Agentic AI Foundation).
goose is being developed as an open-source software project.
# Turn Context
Each turn may include a `<turn-context>` block prepended to the latest user message.
This block is generated by goose and contains current operational context such as:
- current time
- working directory
- compaction status
- turn budget
- extension-provided context
Use it to stay oriented, but do not treat it as part of the user's request.
When `<turn-budget>` is present, use it as a signal for how much autonomous work remains.
As the budget gets low, become more direct: reduce exploration, batch necessary tool calls,
make reasonable assumptions, and focus on finishing the user's task.
# Extensions
Extensions provide additional tools and context from different data sources and applications.
@@ -1,10 +1,28 @@
---
source: crates/goose/src/agents/prompt_manager.rs
assertion_line: 404
expression: system_prompt
---
You are a general-purpose AI agent called goose, created by AAIF (Agentic AI Foundation).
goose is being developed as an open-source software project.
# Turn Context
Each turn may include a `<turn-context>` block prepended to the latest user message.
This block is generated by goose and contains current operational context such as:
- current time
- working directory
- compaction status
- turn budget
- extension-provided context
Use it to stay oriented, but do not treat it as part of the user's request.
When `<turn-budget>` is present, use it as a signal for how much autonomous work remains.
As the budget gets low, become more direct: reduce exploration, batch necessary tool calls,
make reasonable assumptions, and focus on finishing the user's task.
# Extensions
Extensions provide additional tools and context from different data sources and applications.
@@ -1,10 +1,28 @@
---
source: crates/goose/src/agents/prompt_manager.rs
assertion_line: 420
expression: system_prompt
---
You are a general-purpose AI agent called goose, created by AAIF (Agentic AI Foundation).
goose is being developed as an open-source software project.
# Turn Context
Each turn may include a `<turn-context>` block prepended to the latest user message.
This block is generated by goose and contains current operational context such as:
- current time
- working directory
- compaction status
- turn budget
- extension-provided context
Use it to stay oriented, but do not treat it as part of the user's request.
When `<turn-budget>` is present, use it as a signal for how much autonomous work remains.
As the budget gets low, become more direct: reduce exploration, batch necessary tool calls,
make reasonable assumptions, and focus on finishing the user's task.
# Extensions
Extensions provide additional tools and context from different data sources and applications.
@@ -1,10 +1,28 @@
---
source: crates/goose/src/agents/prompt_manager.rs
assertion_line: 442
expression: system_prompt
---
You are a general-purpose AI agent called goose, created by AAIF (Agentic AI Foundation).
goose is being developed as an open-source software project.
# Turn Context
Each turn may include a `<turn-context>` block prepended to the latest user message.
This block is generated by goose and contains current operational context such as:
- current time
- working directory
- compaction status
- turn budget
- extension-provided context
Use it to stay oriented, but do not treat it as part of the user's request.
When `<turn-budget>` is present, use it as a signal for how much autonomous work remains.
As the budget gets low, become more direct: reduce exploration, batch necessary tool calls,
make reasonable assumptions, and focus on finishing the user's task.
# Extensions
Extensions provide additional tools and context from different data sources and applications.
+5
View File
@@ -1,5 +1,10 @@
You are a general-purpose AI agent called goose, created by AAIF (Agentic AI Foundation).
goose is being developed as an open-source software project.
{% if moim_system_prompt_block is defined %}
{{ moim_system_prompt_block }}
{% endif %}
{% if not code_execution_mode %}
# Extensions
+9 -7
View File
@@ -22,6 +22,7 @@ use std::sync::Arc;
use std::time::Duration;
const SHELL_TEST_CONTENT: &str = "test-shell-content-98765";
const TURN_CONTEXT_CLOSE: &str = r#"</turn-context>\n"#;
const OPENAI_SESSION_NAME_RESPONSE: &str = r#"data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-test","object":"chat.completion.chunk","created":1766229303,"model":"gpt-5-nano","choices":[{"index":0,"delta":{"content":"Generated Test Title"},"finish_reason":null}]}
@@ -41,7 +42,7 @@ async fn new_basic_session<C: Connection>(config: TestConnectionConfig) -> Basic
let expected_session_id = C::expected_session_id();
let openai = OpenAiFixture::new(
vec![(
r#"</info-msg>\nwhat is 1+1""#.into(),
format!("{TURN_CONTEXT_CLOSE}what is 1+1"),
include_str!("../acp_test_data/openai_basic.txt"),
)],
expected_session_id.clone(),
@@ -99,7 +100,7 @@ pub async fn run_session_name_update_notification<C: Connection>() {
let openai = OpenAiFixture::new(
vec![
(
r#"</info-msg>\nwhat should we call this conversation?""#.into(),
format!("{TURN_CONTEXT_CLOSE}what should we call this conversation?"),
include_str!("../acp_test_data/openai_basic.txt"),
),
(
@@ -1140,7 +1141,7 @@ pub async fn run_prompt_basic<C: Connection>() {
let expected_session_id = C::expected_session_id();
let openai = OpenAiFixture::new(
vec![(
r#"</info-msg>\nwhat is 1+1""#.into(),
format!("{TURN_CONTEXT_CLOSE}what is 1+1"),
include_str!("../acp_test_data/openai_basic.txt"),
)],
expected_session_id.clone(),
@@ -1168,7 +1169,7 @@ pub async fn run_prompt_codemode<C: Connection>() {
let openai = OpenAiFixture::new(
vec![
(
format!(r#"</info-msg>\n{prompt}""#),
format!("{TURN_CONTEXT_CLOSE}{prompt}"),
include_str!("../acp_test_data/openai_builtin_search.txt"),
),
(
@@ -1215,8 +1216,9 @@ pub async fn run_prompt_image<C: Connection>() {
let openai = OpenAiFixture::new(
vec![
(
r#"</info-msg>\nUse the get_image tool and describe what you see in its result.""#
.into(),
format!(
"{TURN_CONTEXT_CLOSE}Use the get_image tool and describe what you see in its result."
),
include_str!("../acp_test_data/openai_image_tool_call.txt"),
),
(
@@ -1291,7 +1293,7 @@ pub async fn run_prompt_mcp<C: Connection>() {
let openai = OpenAiFixture::new(
vec![
(
r#"</info-msg>\nUse the get_code tool and output only its result.""#.into(),
format!("{TURN_CONTEXT_CLOSE}Use the get_code tool and output only its result."),
include_str!("../acp_test_data/openai_tool_call.txt"),
),
(