particle-academy/prism-harness
A testing harness for Laravel's Prism AI package. Fake model responses, record and replay AI interactions, and assert prompts, tool calls, and outputs in your test suite without hitting real APIs or burning tokens.
The v0.15.0 release adds a host-controlled CallCostObserver extension point for capped model calls. This is a meaningful architectural addition but not a breaking change.
Prism\Harness\Contracts\CallCostObserver, register it via RunContext::root(..., observer: $observer) or the RunContext constructor, or fall back to prism-harness.cost_observer. This supports multi-worker deployments where spend must be tracked across processes.reserving() runs after harness admission/reservation but before the provider request; settled() runs once per admitted call. Throwing from reserving() refuses the call before any request, releases the reservation without charge, skips settled(), and fails the run with RunNotPermitted. CallRefused('your_code') lets the host choose a stable refusal code.reserving() and settled() leaves an open reservation that the host must expire. settled() fires exactly once even for failed calls and stopped streams, but if settled() throws, the harness settlement still stands and the exception propagates. An observer attached to an uncapped run is refused with budget_observer_uncapped, so every observed run needs a cap, even a large one.worstCaseUsd, chargedUsd, and usageKnown? Which run trees must carry a cap and observer? How will custom CallRefused codes be governed and documented? What alerting is needed for observer exceptions and open reservations? Does the team understand that children inherit the observer and a run tree cannot drop or replace it once chosen?send, sendStructured, stream, and approve/deny/decide continuations on the capped path.CallCostObserver with atomic reservation, idempotent settlement, and refusal-code mapping. 3) Add caps to all runs that will use the observer. 4) Inject the observer at root runs or configure the fallback. 5) Test refusal before request, settlement on success/failure/stopped stream, worker-death expiry, and settled() exception propagation. 6) Canary with a narrow set of capped runs, then expand.budget_observer_uncapped, host-defined CallRefused codes, fallback call_refused_by_host, open reservations from dead workers, and exceptions thrown from settled(). Logging should distinguish harness refusal from observer persistence failures.reserving() refuse the call cleanly. Observer exceptions during settled() do not undo harness settlement and will propagate. Uncapped runs with an observer are rejected. Stopped streams still produce exactly one settlement, so persistence must handle that outcome.How can I help you explore Laravel packages today?