agenttap 0.1.0

Wire-level prompt introspection for LLM SDK calls. See exactly what was sent, with credentials redacted by default.
Documentation

agenttap

crates.io docs.rs License: MIT

Wire-level prompt introspection for LLM SDK calls. See exactly what was sent. Credentials redacted by default.

[dependencies]
agenttap = "0.1"
# Or, with reqwest-middleware integration:
agenttap = { version = "0.1", features = ["reqwest"] }

Why

Five years into the SDK era, "what was actually sent to the model?" remains a hard question. SDK debug logging is verbose, leaks API keys, and reformats payloads. agenttap provides redaction primitives plus an optional reqwest-middleware impl so you can capture the exact wire payload.

Quick start (manual)

use agenttap::{Tap, Redactor};
use serde_json::json;

let tap = Tap::new();

// After your HTTP call, hand the request and response details to the tap:
tap.record(
    "POST",
    "https://api.anthropic.com/v1/messages",
    [
        ("authorization".to_string(), "Bearer sk-ant-secret9876543210xyz".to_string()),
        ("content-type".to_string(), "application/json".to_string()),
    ],
    Some(json!({"model": "claude", "messages": [{"role": "user", "content": "hi"}]})),
    200,
    Vec::new(),
    None,
    420,
);

let last = tap.last().unwrap();
println!("{}", last.pretty_request());
println!("auth header now: {}", last.request_headers["authorization"]);
// auth header now: ***REDACTED***

With reqwest-middleware (auto-capture)

Enable the reqwest feature, then plug TapMiddleware into a reqwest_middleware::ClientBuilder:

# #[cfg(feature = "reqwest")]
# {
use agenttap::{Tap, TapMiddleware};
use reqwest::Client;
use reqwest_middleware::ClientBuilder;

let tap = Tap::new();
let client = ClientBuilder::new(Client::new())
    .with(TapMiddleware::new(tap.clone()))
    .build();

// `client` is now a reqwest_middleware::ClientWithMiddleware that records
// every request/response into `tap`.
# }

Default redaction

  • Headers: authorization, x-api-key, api-key, cookie, set-cookie, anthropic-api-key, openai-organization, x-amz-security-token, x-google-api-key.
  • Body strings matching: OpenAI/Anthropic sk-…, AWS AKIA…, Google AIza…, Slack xox[baprs]-….

Custom redactor:

use agenttap::{Tap, Redactor};

let red = Redactor::default()
    .with_extra_header("x-internal-secret")
    .with_extra_pattern(r"prod-token-\w+")
    .unwrap();
let tap = Tap::new().with_redactor(red);

What it doesn't do

  • Not a proxy. Not a server.
  • v0.1 captures full bodies in memory only; persistence is your call.
  • Streaming request bodies (where as_bytes() is None) aren't captured for the request side.

Sibling: Python agenttap

Python users with httpx-based SDKs: see MukundaKatta/agenttap.

License

MIT