agent_block_testkit/lib.rs
1//! In-process mock LLM servers and a provider wire-shape catalog for testing
2//! tool-calling clients.
3//!
4//! Two pieces:
5//!
6//! - [`shapes`] — builders for provider response bodies. Alongside the
7//! spec-conformant shapes, the catalog carries **deliberately broken
8//! variants observed in the wild** on OpenAI-compatible stacks (object
9//! `arguments`, missing `id`, malformed argument strings). A client that is
10//! only exercised against spec-shaped input silently loses tolerance for
11//! the shapes real serving stacks emit; the broken variants exist to guard
12//! exactly that failure mode.
13//! - [`server`] — a small axum-backed mock server ([`server::MockLlm`]) that
14//! serves an Anthropic Messages (`/v1/messages`) or OpenAI Chat Completions
15//! (`/chat/completions`) endpoint from a caller-supplied responder closure,
16//! while recording request facts (tool declarations, `tool_call_id`s of
17//! returned tool results) for assertions.
18//!
19//! ```no_run
20//! use agent_block_testkit::server::MockLlm;
21//! use agent_block_testkit::shapes::openai;
22//! use serde_json::json;
23//!
24//! # async fn demo() {
25//! let handle = MockLlm::openai(|req| {
26//! if !req.has_tool_results {
27//! // Broken-but-observed shape: object arguments, no id.
28//! openai::tool_calls_response(vec![openai::tool_call_object_args_no_id(
29//! "apply_search_replace",
30//! json!({"path": "/tmp/f.lua", "search": "a", "replace": "b"}),
31//! )])
32//! } else {
33//! openai::text_response("DONE")
34//! }
35//! })
36//! .spawn()
37//! .await;
38//! // point the client under test at handle.base_url ...
39//! assert_eq!(handle.state.call_count(), 0);
40//! handle.ct.cancel();
41//! # }
42//! ```
43
44pub mod server;
45pub mod shapes;