use crate::brain::provider::identity;
use crate::brain::tools::provider_vision::vision_headers;
use uuid::Uuid;
const OPENCODE: &str = "https://opencode.ai/zen/go/v1/chat/completions";
const UNKNOWN: &str = "https://gateway.invalid.example/v1/chat/completions";
fn get<'a>(headers: &'a [(String, String)], name: &str) -> Option<&'a str> {
headers
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
#[test]
fn vision_opencode_session_header_matches_the_conversation_id() {
let sid = Uuid::new_v4();
let h = vision_headers(OPENCODE, "test-key", Some(sid));
assert_eq!(
get(&h, "X-Opencode-Session"),
Some(sid.to_string().as_str()),
"the session header must name this conversation"
);
let ua = get(&h, "User-Agent").unwrap_or_default();
assert!(
ua.starts_with("OpenCrabs/"),
"the docs ask for a self-identifying User-Agent, got: {ua}"
);
}
#[test]
fn vision_opencode_session_header_is_stable_per_conversation() {
let a = Uuid::new_v4();
let b = Uuid::new_v4();
let first = vision_headers(OPENCODE, "k", Some(a));
let again = vision_headers(OPENCODE, "k", Some(a));
assert_eq!(
get(&first, "X-Opencode-Session"),
get(&again, "X-Opencode-Session"),
"one conversation must not present two ids"
);
assert_ne!(
get(&first, "X-Opencode-Session"),
get(
&vision_headers(OPENCODE, "k", Some(b)),
"X-Opencode-Session"
),
"two conversations must not share an id"
);
}
#[test]
fn vision_opencode_session_header_survives_a_missing_conversation() {
let h = vision_headers(OPENCODE, "k", None);
assert!(
!get(&h, "X-Opencode-Session").unwrap_or_default().is_empty(),
"no session must not mean no header"
);
}
#[test]
fn vision_opencode_session_header_is_the_chat_paths_identity() {
let sid = Uuid::new_v4();
for url in [OPENCODE, "https://api.z.ai/api/prediction/v1", UNKNOWN] {
let vision = vision_headers(url, "k", Some(sid));
let identity_part = &vision[2..];
assert_eq!(
identity_part,
identity::headers_for(url, Some(sid)).as_slice(),
"vision drifted from the chat path for {url}"
);
}
}
#[test]
fn vision_headers_keep_bearer_auth_on_every_host() {
for url in [OPENCODE, UNKNOWN] {
let h = vision_headers(url, "secret", Some(Uuid::new_v4()));
assert_eq!(h[0].0, "Content-Type");
assert_eq!(h[0].1, "application/json");
assert_eq!(h[1].0, "Authorization");
assert_eq!(h[1].1, "Bearer secret");
}
}
#[test]
fn vision_headers_add_nothing_for_a_gateway_without_a_contract() {
let h = vision_headers(UNKNOWN, "k", Some(Uuid::new_v4()));
assert_eq!(h.len(), 2, "unexpected headers: {h:?}");
assert!(get(&h, "X-Opencode-Session").is_none());
assert!(get(&h, "User-Agent").is_none());
}
#[tokio::test]
async fn vision_headers_reach_the_request() {
use crate::brain::tools::provider_vision::ProviderVisionTool;
use crate::brain::tools::{Tool, ToolExecutionContext};
use serde_json::json;
let mut server = mockito::Server::new_async().await;
let url = format!("{}/v1/chat/completions", server.url());
let mock = server
.mock("POST", "/v1/chat/completions")
.match_header("authorization", "Bearer wire-key")
.match_header("content-type", "application/json")
.with_status(200)
.with_body(r#"{"choices":[{"message":{"content":"a red square, seen over the wire"}}]}"#)
.create_async()
.await;
let img = std::env::temp_dir().join(format!("vision-wire-{}.png", std::process::id()));
std::fs::write(
&img,
[
0x89u8, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0, 0, 0, 0x0D, b'I', b'H', b'D',
b'R', 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0,
],
)
.unwrap();
let tool = ProviderVisionTool::with_candidates(vec![(
"wire-key".to_string(),
url.clone(),
"vision-model".to_string(),
)]);
let ctx = ToolExecutionContext::new(Uuid::new_v4());
let res = tool
.execute(
json!({ "image": img.to_string_lossy(), "question": "what is this" }),
&ctx,
)
.await
.expect("a wire success is a ToolResult, not a transport error");
let _ = std::fs::remove_file(&img);
mock.assert_async().await;
assert!(res.success, "vision call failed: {:?}", res.error);
assert_eq!(res.output.trim(), "a red square, seen over the wire");
}