- 1
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] - 2
- 3
use std::sync::{Arc, Mutex}; - 4
- 5
use tokio_util::sync::CancellationToken; - 6
- 7
use vak_core::Core; - 8
use vak_llm::stream; - 9
use vak_llm::types::{AssistantMessage, ChatRequest, ContentBlock, Usage}; - 10
use vak_llm::{EventStream, LlmError, Provider}; - 11
- 12
/// Records every request it receives so tests can assert exactly what the - 13
/// model would have seen. - 14
struct Recording { - 15
seen: Arc<Mutex<Vec<ChatRequest>>>, - 16
reply: String, - 17
} - 18
- 19
#[async_trait::async_trait] - 20
impl Provider for Recording { - 21
fn name(&self) -> &str { - 22
"recording" - 23
} - 24
- 25
async fn stream( - 26
&self, - 27
request: ChatRequest, - 28
_cancel: CancellationToken, - 29
) -> Result<EventStream, LlmError> { - 30
self.seen.lock().unwrap().push(request); - 31
let (mut sink, rx) = stream::channel(16); - 32
sink.push(stream::StreamEvent::Start { - 33
partial: AssistantMessage { - 34
content: vec![ContentBlock::text(self.reply.clone())], - 35
stop_reason: vak_llm::types::StopReason::EndTurn, - 36
usage: Usage::default(), - 37
model: "test-model".into(), - 38
response_id: None, - 39
}, - 40
}); - 41
sink.close_message(AssistantMessage { - 42
content: vec![ContentBlock::text(self.reply.clone())], - 43
stop_reason: vak_llm::types::StopReason::EndTurn, - 44
usage: Usage::default(), - 45
model: "test-model".into(), - 46
response_id: None, - 47
}) - 48
.await; - 49
Ok(rx) - 50
} - 51
} - 52
- 53
const PNG_B64: &str = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4nGNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="; - 54
- 55
#[tokio::test(flavor = "multi_thread", worker_threads = 4)] - 56
async fn gateway_inbound_carries_images_to_the_model() { - 57
let seen: Arc<Mutex<Vec<ChatRequest>>> = Arc::new(Mutex::new(Vec::new())); - 58
let core_provider = Arc::new(Recording { - 59
seen: seen.clone(), - 60
reply: "I see a tiny red pixel.".into(), - 61
}); - 62
- 63
let dir = tempfile::tempdir().unwrap(); - 64
let cwd = dir.path().to_path_buf(); - 65
// Hermetic against the developer's global config (reflection=true): - 66
let _ = std::fs::create_dir_all(cwd.join(".vak")); - 67
let _ = std::fs::write( - 68
cwd.join(".vak/config.toml"), - 69
"[memory]\nreflection = false\n[gateway]\nchat_allowlist_open = true\n", - 70
); - 71
vak_config::paths::isolate_home_for_tests(); - 72
let core = Core::new_with_trust(cwd.clone(), true).unwrap(); - 73
core.set_sessions_home(dir.path().join("home")); - 74
core.set_permission_mode(vak_config::PermissionMode::FullAccess); - 75
core.set_provider_instance(core_provider); - 76
std::mem::forget(dir); - 77
- 78
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - 79
let addr = listener.local_addr().unwrap(); - 80
tokio::spawn(async move { - 81
axum::serve(listener, vak_server::gateway_router(core)) - 82
.await - 83
.unwrap(); - 84
}); - 85
let base = format!("http://{addr}"); - 86
let client = reqwest::Client::new(); - 87
- 88
let res = client - 89
.post(format!("{base}/gateway/inbound")) - 90
.json(&serde_json::json!({ - 91
"surface": "telegram", - 92
"chat": "42", - 93
"text": "what is this?", - 94
"wait": true, - 95
"attachments": [ - 96
{"mime": "image/png", "data": PNG_B64} - 97
] - 98
})) - 99
.send() - 100
.await - 101
.unwrap(); - 102
assert_eq!(res.status(), 200); - 103
let body: serde_json::Value = res.json().await.unwrap(); - 104
assert_eq!(body["text"], "I see a tiny red pixel."); - 105
- 106
// The MODEL saw the image block... - 107
{ - 108
let requests = seen.lock().unwrap(); - 109
assert_eq!(requests.len(), 1); - 110
// Checked across every user message rather than the first one: the - 111
// projection also emits runtime control blocks in the user role (the - 112
// work-contract summary, and this turn's intent note), so "the first - 113
// user message" is not necessarily the user's. - 114
assert!( - 115
requests[0] - 116
.messages - 117
.iter() - 118
.filter(|m| m.role == vak_llm::Role::User) - 119
.any(|m| m.content.iter().any(|b| matches!( - 120
b, - 121
ContentBlock::Image { source } - 122
if source.data == PNG_B64 && source.media_type == "image/png" - 123
))), - 124
"image block reached the request" - 125
); - 126
} - 127
- 128
// ...and the LEDGER stored it verbatim (invariant 1). - 129
let sid: String = { - 130
// wait:true response carries session_id only on completed; re-read - 131
// status for the binding. - 132
let st: serde_json::Value = client - 133
.get(format!("{base}/gateway/status")) - 134
.send() - 135
.await - 136
.unwrap() - 137
.json() - 138
.await - 139
.unwrap(); - 140
st["bindings"][0]["session_id"] - 141
.as_str() - 142
.unwrap() - 143
.to_string() - 144
}; - 145
let t: serde_json::Value = client - 146
.get(format!("{base}/sessions/{sid}/transcript")) - 147
.send() - 148
.await - 149
.unwrap() - 150
.json() - 151
.await - 152
.unwrap(); - 153
let raw = serde_json::to_string(&t).unwrap(); - 154
assert!( - 155
raw.contains("iVBORw0KGgo") && raw.contains("\"type\":\"image\""), - 156
"image persisted on the ledger" - 157
); - 158
} - 159
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.