|
1 | 1 | use anyhow::Result; |
2 | 2 | use codex_config::types::MemoryBackend; |
3 | 3 | use codex_features::Feature; |
| 4 | +use codex_models_manager::bundled_models_response; |
4 | 5 | use core_test_support::responses::ev_apply_patch_function_call; |
5 | 6 | use core_test_support::responses::ev_assistant_message; |
6 | 7 | use core_test_support::responses::ev_completed; |
@@ -256,3 +257,108 @@ async fn pre_tool_enrichment_skips_non_matching_tools() -> Result<()> { |
256 | 257 |
|
257 | 258 | Ok(()) |
258 | 259 | } |
| 260 | + |
| 261 | +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] |
| 262 | +#[serial(agentmemory_env)] |
| 263 | +async fn glob_lane_enrichment_and_post_tool_capture_use_native_contract() -> Result<()> { |
| 264 | + let model_server = start_mock_server().await; |
| 265 | + let agentmemory_server = MockServer::start().await; |
| 266 | + mount_agentmemory_runtime(&agentmemory_server).await; |
| 267 | + Mock::given(method("POST")) |
| 268 | + .and(path("/agentmemory/enrich")) |
| 269 | + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ |
| 270 | + "context": "<agentmemory-context>glob lane note</agentmemory-context>", |
| 271 | + }))) |
| 272 | + .expect(1) |
| 273 | + .mount(&agentmemory_server) |
| 274 | + .await; |
| 275 | + |
| 276 | + let mut builder = test_codex().with_config({ |
| 277 | + let agentmemory_base_url = agentmemory_server.uri(); |
| 278 | + move |config| { |
| 279 | + config.memories.backend = MemoryBackend::Agentmemory; |
| 280 | + config.memories.agentmemory.base_url = agentmemory_base_url; |
| 281 | + config.memories.agentmemory.inject_context = true; |
| 282 | + let mut model_catalog = bundled_models_response() |
| 283 | + .unwrap_or_else(|err| panic!("bundled models.json should parse: {err}")); |
| 284 | + let model = model_catalog |
| 285 | + .models |
| 286 | + .iter_mut() |
| 287 | + .find(|model| model.slug == "gpt-5.3-codex") |
| 288 | + .expect("gpt-5.3-codex exists in bundled models.json"); |
| 289 | + model |
| 290 | + .experimental_supported_tools |
| 291 | + .push("list_dir".to_string()); |
| 292 | + config.model_catalog = Some(model_catalog); |
| 293 | + config |
| 294 | + .features |
| 295 | + .disable(Feature::MemoryTool) |
| 296 | + .expect("test config should allow feature update"); |
| 297 | + } |
| 298 | + }); |
| 299 | + let test = builder.build(&model_server).await?; |
| 300 | + std::fs::write(test.config.cwd.join("glob_lane.txt"), "harbor note")?; |
| 301 | + let call_id = "agentmemory-list-dir"; |
| 302 | + let args = json!({ |
| 303 | + "dir_path": test.config.cwd.display().to_string(), |
| 304 | + "offset": 1, |
| 305 | + "limit": 5, |
| 306 | + "depth": 1, |
| 307 | + }); |
| 308 | + let responses = mount_sse_sequence( |
| 309 | + &model_server, |
| 310 | + vec![ |
| 311 | + sse(vec![ |
| 312 | + ev_response_created("resp-1"), |
| 313 | + ev_function_call(call_id, "list_dir", &serde_json::to_string(&args)?), |
| 314 | + ev_completed("resp-1"), |
| 315 | + ]), |
| 316 | + sse(vec![ |
| 317 | + ev_response_created("resp-2"), |
| 318 | + ev_assistant_message("msg-1", "listed"), |
| 319 | + ev_completed("resp-2"), |
| 320 | + ]), |
| 321 | + ], |
| 322 | + ) |
| 323 | + .await; |
| 324 | + |
| 325 | + test.submit_turn("list the directory").await?; |
| 326 | + test.codex.shutdown_and_wait().await?; |
| 327 | + |
| 328 | + let requests = responses.requests(); |
| 329 | + assert_eq!(requests.len(), 2); |
| 330 | + let agentmemory_requests = agentmemory_server |
| 331 | + .received_requests() |
| 332 | + .await |
| 333 | + .unwrap_or_default(); |
| 334 | + let agentmemory_paths = agentmemory_requests |
| 335 | + .iter() |
| 336 | + .map(|request| request.url.path().to_string()) |
| 337 | + .collect::<Vec<_>>(); |
| 338 | + let observe_bodies = agentmemory_requests |
| 339 | + .iter() |
| 340 | + .filter(|request| request.url.path() == "/agentmemory/observe") |
| 341 | + .map(|request| String::from_utf8_lossy(&request.body).into_owned()) |
| 342 | + .collect::<Vec<_>>(); |
| 343 | + assert!( |
| 344 | + requests[1].body_contains_text("glob lane note"), |
| 345 | + "glob lane enrichment should inject context into the follow-up model turn: body={}; paths={agentmemory_paths:?}; observe={observe_bodies:?}", |
| 346 | + requests[1].body_json(), |
| 347 | + ); |
| 348 | + let post_tool_use = agentmemory_requests |
| 349 | + .into_iter() |
| 350 | + .filter(|request| request.url.path() == "/agentmemory/observe") |
| 351 | + .map(|request| { |
| 352 | + serde_json::from_slice::<serde_json::Value>(&request.body) |
| 353 | + .expect("agentmemory observe body should be valid json") |
| 354 | + }) |
| 355 | + .find(|payload| { |
| 356 | + payload["hookType"] == "post_tool_use" && payload["data"]["tool_name"] == "Glob" |
| 357 | + }) |
| 358 | + .expect("list_dir should emit glob post_tool_use observe payload"); |
| 359 | + assert_eq!(post_tool_use["source"], "codex-native"); |
| 360 | + assert_eq!(post_tool_use["payload_version"], "1"); |
| 361 | + assert_eq!(post_tool_use["persistence_class"], "persistent"); |
| 362 | + |
| 363 | + Ok(()) |
| 364 | +} |
0 commit comments