pub mod ant;
pub mod anthropic;
pub mod auth;
pub mod codex;
pub mod config;
pub mod delegate;
pub mod files;
pub mod oauth;
pub mod openai;
pub mod sse;
pub mod tools;
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mask {
Read,
Write,
Build,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Protocol {
OpenAi,
Anthropic,
Codex,
ClaudeCli,
CodexCli,
}
impl Protocol {
pub fn is_delegate(self) -> bool {
matches!(self, Protocol::ClaudeCli | Protocol::CodexCli)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EffortDialect {
Standard,
Ollama,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Effort {
Off,
Minimal,
Low,
Medium,
High,
Xhigh,
Max,
}
impl Effort {
pub const ALL: [Effort; 7] = [
Effort::Off,
Effort::Minimal,
Effort::Low,
Effort::Medium,
Effort::High,
Effort::Xhigh,
Effort::Max,
];
pub fn as_str(self) -> &'static str {
match self {
Effort::Off => "off",
Effort::Minimal => "minimal",
Effort::Low => "low",
Effort::Medium => "medium",
Effort::High => "high",
Effort::Xhigh => "xhigh",
Effort::Max => "max",
}
}
pub fn parse(raw: &str) -> Result<Effort, HarnessError> {
let normalized = raw.trim().to_ascii_lowercase().replace(['-', '_'], "");
Ok(match normalized.as_str() {
"off" | "none" | "disabled" | "false" => Effort::Off,
"minimal" | "min" => Effort::Minimal,
"low" => Effort::Low,
"medium" | "med" | "default" => Effort::Medium,
"high" => Effort::High,
"xhigh" | "extrahigh" | "veryhigh" => Effort::Xhigh,
"max" | "maximum" | "highest" => Effort::Max,
_ => {
let names: Vec<&str> = Effort::ALL.iter().map(|e| e.as_str()).collect();
return Err(HarnessError::Config(format!(
"unknown effort `{raw}` — use one of: {}",
names.join(", ")
)));
}
})
}
pub fn openai(self, dialect: EffortDialect) -> &'static str {
match dialect {
EffortDialect::Ollama => match self {
Effort::Off => "none",
Effort::Minimal => "minimal",
Effort::Low => "low",
Effort::Medium => "medium",
Effort::High => "high",
Effort::Xhigh => "xhigh",
Effort::Max => "max",
},
EffortDialect::Standard => match self {
Effort::Off => "none",
Effort::Minimal => "minimal",
Effort::Low => "low",
Effort::Medium => "medium",
Effort::High | Effort::Xhigh | Effort::Max => "high",
},
}
}
pub fn openai_fallback(self, dialect: EffortDialect) -> Option<&'static str> {
match dialect {
EffortDialect::Ollama => match self {
Effort::Minimal => Some("low"),
Effort::Xhigh => Some("max"),
_ => None,
},
EffortDialect::Standard => match self {
Effort::Off => Some("minimal"),
_ => None,
},
}
}
pub fn codex(self) -> &'static str {
match self {
Effort::Off => "none",
Effort::Minimal | Effort::Low => "low",
Effort::Medium => "medium",
Effort::High => "high",
Effort::Xhigh => "xhigh",
Effort::Max => "max",
}
}
pub fn anthropic(self) -> Option<&'static str> {
match self {
Effort::Off => None,
Effort::Minimal | Effort::Low => Some("low"),
Effort::Medium => Some("medium"),
Effort::High => Some("high"),
Effort::Xhigh => Some("xhigh"),
Effort::Max => Some("max"),
}
}
pub fn anthropic_budget(self) -> Option<u32> {
match self {
Effort::Off => None,
Effort::Minimal => Some(1024),
Effort::Low => Some(2048),
Effort::Medium => Some(8192),
Effort::High => Some(16384),
Effort::Xhigh => Some(24576),
Effort::Max => Some(32768),
}
}
}
#[derive(Debug, Default)]
pub struct Negotiated {
first: std::cell::Cell<usize>,
}
impl Negotiated {
pub fn start(&self) -> usize {
self.first.get()
}
pub fn accepted(&self, index: usize) {
self.first.set(index);
}
}
#[derive(Debug, Clone)]
pub struct Provider {
pub protocol: Protocol,
pub base_url: String,
pub model: String,
pub key: Option<String>,
pub oauth: bool,
pub source: String,
pub effort_dialect: EffortDialect,
}
#[derive(Debug, Clone)]
pub struct RunOptions {
pub max_turns: usize,
pub max_tokens: u32,
pub mask: Mask,
pub effort: Option<Effort>,
pub delegate_cwd: Option<std::path::PathBuf>,
}
impl Default for RunOptions {
fn default() -> Self {
Self {
max_turns: 15,
max_tokens: 4096,
mask: Mask::Read,
effort: None,
delegate_cwd: None,
}
}
}
#[derive(Debug, Clone)]
pub enum Block {
Text(String),
Thinking {
text: String,
signature: Option<String>,
},
ToolUse {
id: String,
name: String,
args: serde_json::Value,
raw_args: Option<String>,
},
ToolResult {
id: String,
name: String,
content: String,
is_error: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Clone)]
pub struct Msg {
pub role: Role,
pub blocks: Vec<Block>,
}
impl Msg {
pub fn user(text: impl Into<String>) -> Self {
Self {
role: Role::User,
blocks: vec![Block::Text(text.into())],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stop {
EndTurn,
ToolUse,
Length,
}
#[derive(Debug)]
pub struct Turn {
pub blocks: Vec<Block>,
pub stop: Stop,
pub usage: Option<(u64, u64)>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum Event {
ThinkingDelta {
text: String,
},
TextDelta {
text: String,
},
ToolCall {
id: String,
name: String,
args: serde_json::Value,
display: String,
},
ToolResult {
id: String,
name: String,
content: String,
is_error: bool,
},
Usage {
input: u64,
output: u64,
},
TurnEnd {
stop: String,
},
Done {
text: String,
turns: usize,
},
Notice {
message: String,
},
Error {
message: String,
},
}
#[derive(Debug, Clone)]
pub struct ToolOutcome {
pub content: String,
pub is_error: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum HarnessError {
#[error("{0}")]
Config(String),
#[error("{0}")]
Provider(String),
}
pub trait ToolExecutor {
fn execute(&mut self, name: &str, args: &serde_json::Value) -> ToolOutcome;
fn describe(&self, name: &str, args: &serde_json::Value) -> String {
let _ = args;
name.to_string()
}
}
pub const TOOL_RESULT_CAP: usize = 24_000;
pub fn truncate_tool_result(content: &str) -> String {
if content.len() <= TOOL_RESULT_CAP {
return content.to_string();
}
let head_end = floor_char_boundary(content, TOOL_RESULT_CAP * 2 / 3);
let tail_start = ceil_char_boundary(content, content.len() - TOOL_RESULT_CAP / 4);
format!(
"{}\n[... truncated {} bytes — narrow the request (limit/type/path filters) and retry ...]\n{}",
&content[..head_end],
content.len() - head_end - (content.len() - tail_start),
&content[tail_start..]
)
}
fn floor_char_boundary(s: &str, mut at: usize) -> usize {
at = at.min(s.len());
while at > 0 && !s.is_char_boundary(at) {
at -= 1;
}
at
}
fn ceil_char_boundary(s: &str, mut at: usize) -> usize {
at = at.min(s.len());
while at < s.len() && !s.is_char_boundary(at) {
at += 1;
}
at
}
pub fn run(
provider: &Provider,
opts: &RunOptions,
system: &str,
mut messages: Vec<Msg>,
tool_specs: &[tools::ToolSpec],
executor: &mut dyn ToolExecutor,
emit: &mut dyn FnMut(Event),
) -> Result<String, HarnessError> {
if provider.protocol.is_delegate() {
return delegate::run(provider, opts, system, &messages, emit);
}
let mut turns = 0usize;
let mut final_text = String::new();
let negotiated = Negotiated::default();
loop {
turns += 1;
let capped = turns > opts.max_turns;
if capped {
messages.push(Msg::user(
"[The tool-call limit for this run was reached. Answer now with \
what you already have; do not request more tools.]",
));
}
let specs: &[tools::ToolSpec] = if capped { &[] } else { tool_specs };
let turn = match provider.protocol {
Protocol::OpenAi => {
openai::stream_turn(provider, opts, system, &messages, specs, &negotiated, emit)
}
Protocol::Anthropic => {
anthropic::stream_turn(provider, opts, system, &messages, specs, &negotiated, emit)
}
Protocol::Codex => codex::stream_turn(provider, opts, system, &messages, specs, emit),
Protocol::ClaudeCli | Protocol::CodexCli => unreachable!("delegates handled above"),
};
let turn = match turn {
Ok(turn) => turn,
Err(error) => {
emit(Event::Error {
message: error.to_string(),
});
return Err(error);
}
};
if let Some((input, output)) = turn.usage {
emit(Event::Usage { input, output });
}
emit(Event::TurnEnd {
stop: match turn.stop {
Stop::EndTurn => "end_turn",
Stop::ToolUse => "tool_use",
Stop::Length => "length",
}
.to_string(),
});
for block in &turn.blocks {
if let Block::Text(text) = block {
if !final_text.is_empty() {
final_text.push('\n');
}
final_text.push_str(text);
}
}
let calls: Vec<(String, String, serde_json::Value, Option<String>)> = turn
.blocks
.iter()
.filter_map(|block| match block {
Block::ToolUse {
id,
name,
args,
raw_args,
} => Some((id.clone(), name.clone(), args.clone(), raw_args.clone())),
_ => None,
})
.collect();
messages.push(Msg {
role: Role::Assistant,
blocks: turn.blocks,
});
if calls.is_empty() || capped {
let text = final_text.trim().to_string();
emit(Event::Done {
text: text.clone(),
turns,
});
return Ok(text);
}
final_text.clear();
let mut results: Vec<Block> = Vec::with_capacity(calls.len());
for (id, name, args, raw_args) in calls {
emit(Event::ToolCall {
id: id.clone(),
name: name.clone(),
args: args.clone(),
display: executor.describe(&name, &args),
});
let outcome = if raw_args.as_deref().is_some_and(|raw| {
!raw.trim().is_empty() && serde_json::from_str::<serde_json::Value>(raw).is_err()
}) {
ToolOutcome {
content: format!(
"tool arguments were not valid JSON; re-issue the call. raw: {}",
truncate_tool_result(raw_args.as_deref().unwrap_or_default())
),
is_error: true,
}
} else {
executor.execute(&name, &args)
};
emit(Event::ToolResult {
id: id.clone(),
name: name.clone(),
content: outcome.content.clone(),
is_error: outcome.is_error,
});
results.push(Block::ToolResult {
id,
name,
content: outcome.content,
is_error: outcome.is_error,
});
}
messages.push(Msg {
role: Role::User,
blocks: results,
});
}
}
#[cfg(test)]
mod effort_tests {
use super::*;
#[test]
fn parse_accepts_each_vendors_spelling() {
assert_eq!(Effort::parse("xhigh").unwrap(), Effort::Xhigh);
assert_eq!(Effort::parse("x-high").unwrap(), Effort::Xhigh);
assert_eq!(Effort::parse(" HIGH ").unwrap(), Effort::High);
assert_eq!(Effort::parse("none").unwrap(), Effort::Off);
assert_eq!(Effort::parse("maximum").unwrap(), Effort::Max);
assert_eq!(Effort::parse("min").unwrap(), Effort::Minimal);
}
#[test]
fn parse_rejects_unknown_and_lists_the_rungs() {
let error = Effort::parse("turbo").expect_err("turbo is not a level");
let message = error.to_string();
for rung in Effort::ALL {
assert!(
message.contains(rung.as_str()),
"error should list `{}`: {message}",
rung.as_str()
);
}
}
#[test]
fn ollama_dialect_passes_every_rung_through_by_name() {
assert_eq!(Effort::Off.openai(EffortDialect::Ollama), "none");
assert_eq!(Effort::Minimal.openai(EffortDialect::Ollama), "minimal");
assert_eq!(Effort::Xhigh.openai(EffortDialect::Ollama), "xhigh");
assert_eq!(Effort::Max.openai(EffortDialect::Ollama), "max");
}
#[test]
fn ollama_rungs_outside_the_older_set_have_a_fallback() {
assert_eq!(
Effort::Minimal.openai_fallback(EffortDialect::Ollama),
Some("low")
);
assert_eq!(
Effort::Xhigh.openai_fallback(EffortDialect::Ollama),
Some("max")
);
assert_eq!(Effort::High.openai_fallback(EffortDialect::Ollama), None);
assert_eq!(Effort::Max.openai_fallback(EffortDialect::Standard), None);
}
#[test]
fn standard_dialect_clamps_rungs_openai_lacks() {
assert_eq!(Effort::Xhigh.openai(EffortDialect::Standard), "high");
assert_eq!(Effort::Max.openai(EffortDialect::Standard), "high");
}
#[test]
fn off_means_off_on_local_servers_not_a_short_think() {
assert_eq!(Effort::Off.openai(EffortDialect::Standard), "none");
assert_eq!(Effort::Off.openai(EffortDialect::Ollama), "none");
assert_eq!(
Effort::Off.openai_fallback(EffortDialect::Standard),
Some("minimal")
);
}
#[test]
fn the_negotiation_memo_starts_at_zero_and_remembers() {
let memo = Negotiated::default();
assert_eq!(memo.start(), 0);
memo.accepted(1);
assert_eq!(memo.start(), 1, "a refused shape is not re-offered");
}
#[test]
fn codex_and_anthropic_expose_their_top_rungs() {
assert_eq!(Effort::Max.codex(), "max");
assert_eq!(Effort::Xhigh.codex(), "xhigh");
assert_eq!(Effort::Off.codex(), "none");
assert_ne!(
Effort::Minimal.codex(),
"minimal",
"the Responses backend has no `minimal` rung"
);
assert_eq!(Effort::Max.anthropic(), Some("max"));
assert_eq!(Effort::Xhigh.anthropic(), Some("xhigh"));
assert_eq!(Effort::Off.anthropic(), None);
assert_eq!(Effort::Off.anthropic_budget(), None);
}
#[test]
fn budgets_rise_with_the_ladder() {
let budgets: Vec<u32> = Effort::ALL
.iter()
.filter_map(|e| e.anthropic_budget())
.collect();
assert!(
budgets.windows(2).all(|w| w[0] < w[1]),
"legacy budgets must increase monotonically: {budgets:?}"
);
assert!(budgets.iter().all(|b| *b >= 1024));
}
#[test]
fn default_run_options_send_no_reasoning_field() {
assert_eq!(RunOptions::default().effort, None);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truncation_keeps_head_and_tail_with_marker() {
let long = "a".repeat(30_000) + &"z".repeat(10_000);
let cut = truncate_tool_result(&long);
assert!(cut.len() < long.len());
assert!(cut.starts_with('a'));
assert!(cut.ends_with('z'));
assert!(cut.contains("truncated"));
}
#[test]
fn short_results_ride_verbatim() {
assert_eq!(truncate_tool_result("ok"), "ok");
}
}