OpenPay (1 crate, ~2287 LOC, 15 tests)
op-dispute-evidence Chargeback lifecycle + evidence packaging across Visa
VCR (10.x/11.x/12.x/13.x), Mastercard Mastercom
(4837/4853/1240/1442), Amex SafeKey + DRR, Discover DRR,
PayPal. CE3.0 compelling-evidence qualifier (120-365d
window, ≥2 qualifiers, ≥2 linking matches, 10.4-only).
Phase state machine + ReasonCodeCatalog + sealed
EvidencePackageBuilder + win-rate scoring.
Smart Byte (1 crate, ~2479 LOC, 44 tests)
smart-byte-edv Encrypted Data Vaults (DIF v0.10).
JWE ECDH-ES P-256 + HKDF-SHA256-Concat-KDF + AES-256-GCM,
multi-recipient; HMAC-SHA-256 encrypted index with
AND-equality queries; full ZCAP-LD chain validation;
chunked AES-256-GCM streaming with per-chunk AAD;
Vault trait + InMemoryVault + one-way pull replication.
EOC (1 crate, ~2065 LOC, 29 tests)
eoc-agent Agentic loops with per-step joule accounting.
ReAct, Reflexion (PASS/FAIL judge + episodic memory),
Tree of Thoughts (BFS/DFS + self-scoring), LATS
(MCTS-UCT select/expand/simulate/backprop),
Plan-and-Execute, CodeAct (fenced-block + pluggable
runtime). Budget enforcement (tokens/joules/tool-calls),
trace spans, JouleCounter integration.
Three other batch-5 deferrals (op-ewallets-asia, smart-byte-zk, eoc-rag)
will land in wave A3.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
90 lines
2.7 KiB
Rust
90 lines
2.7 KiB
Rust
//! Integration test: budgets trigger early stop.
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::Mutex;
|
|
|
|
use async_trait::async_trait;
|
|
use eoc_agent::agent::{Agent, Budget, LlmProvider, LlmReply};
|
|
use eoc_agent::budget::{JouleBudget, TokenBudget};
|
|
use eoc_agent::error::AgentResult;
|
|
use eoc_agent::agent::ToolBox;
|
|
use eoc_agent::react::ReActLoop;
|
|
use eoc_agent::StopReason;
|
|
|
|
struct Scripted {
|
|
text: &'static str,
|
|
tokens: u64,
|
|
microjoules: u64,
|
|
calls: Mutex<usize>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl LlmProvider for Scripted {
|
|
async fn complete(&self, _prompt: &str) -> AgentResult<LlmReply> {
|
|
*self.calls.lock().expect("lock") += 1;
|
|
Ok(LlmReply::new(self.text, self.tokens, self.microjoules))
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn token_budget_cuts_react_short() {
|
|
let provider = Arc::new(Scripted {
|
|
text: "Thought: t\nAction: nope[x]",
|
|
tokens: 100,
|
|
microjoules: 0,
|
|
calls: Mutex::new(0),
|
|
});
|
|
let budget = Budget {
|
|
tokens: TokenBudget::new(50),
|
|
joules: JouleBudget::unlimited(),
|
|
max_tool_calls: 100,
|
|
tool_calls_used: 0,
|
|
max_iterations: 100,
|
|
};
|
|
let mut agent = ReActLoop::new(provider.clone(), ToolBox::new(), budget, "sys");
|
|
let (_out, stop) = agent.run("g").await.expect("ok");
|
|
assert_eq!(stop, StopReason::BudgetExhausted("tokens".into()));
|
|
// Exactly one call should have happened before the cap tripped.
|
|
assert_eq!(*provider.calls.lock().expect("lock"), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn joule_budget_cuts_react_short() {
|
|
let provider = Arc::new(Scripted {
|
|
text: "Thought: t\nAction: nope[x]",
|
|
tokens: 0,
|
|
microjoules: 10_000,
|
|
calls: Mutex::new(0),
|
|
});
|
|
let budget = Budget {
|
|
tokens: TokenBudget::unlimited(),
|
|
joules: JouleBudget::from_microjoules(5_000),
|
|
max_tool_calls: 100,
|
|
tool_calls_used: 0,
|
|
max_iterations: 100,
|
|
};
|
|
let mut agent = ReActLoop::new(provider.clone(), ToolBox::new(), budget, "sys");
|
|
let (_out, stop) = agent.run("g").await.expect("ok");
|
|
assert_eq!(stop, StopReason::BudgetExhausted("joules".into()));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn iteration_cap_trips_when_no_finish() {
|
|
let provider = Arc::new(Scripted {
|
|
text: "Thought: t\nAction: unknown[x]",
|
|
tokens: 1,
|
|
microjoules: 1,
|
|
calls: Mutex::new(0),
|
|
});
|
|
// Tiny iteration cap.
|
|
let budget = Budget {
|
|
tokens: TokenBudget::unlimited(),
|
|
joules: JouleBudget::unlimited(),
|
|
max_tool_calls: 100,
|
|
tool_calls_used: 0,
|
|
max_iterations: 3,
|
|
};
|
|
let mut agent = ReActLoop::new(provider, ToolBox::new(), budget, "sys");
|
|
let (_out, stop) = agent.run("g").await.expect("ok");
|
|
assert_eq!(stop, StopReason::MaxIterations);
|
|
}
|