use crate::error::{ErrorCode, ErrorDetail, ErrorKind, ErrorSource};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::hash::{Hash, Hasher};
pub const UNABLE_ENVELOPE_KEY: &str = "unable";
fn default_error_code_other() -> ErrorCode {
ErrorCode::Other
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct AgentData {
pub name: String,
pub provider: String,
pub model_name: String,
pub system_prompt: Option<String>,
#[serde(default)]
pub thinking: bool,
#[serde(default)]
pub temperature: Option<f64>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum UnableCategory {
InputMissing,
InputAmbiguous,
InputConflicts,
Capability,
Other,
PartialRetry,
}
impl UnableCategory {
pub fn as_wire_str(&self) -> &'static str {
match self {
UnableCategory::InputMissing => "input_missing",
UnableCategory::InputAmbiguous => "input_ambiguous",
UnableCategory::InputConflicts => "input_conflicts",
UnableCategory::Capability => "capability",
UnableCategory::Other => "other",
UnableCategory::PartialRetry => "partial_retry",
}
}
pub fn from_wire_str(s: &str) -> Option<Self> {
match s {
"input_missing" => Some(UnableCategory::InputMissing),
"input_ambiguous" => Some(UnableCategory::InputAmbiguous),
"input_conflicts" => Some(UnableCategory::InputConflicts),
"capability" => Some(UnableCategory::Capability),
"other" => Some(UnableCategory::Other),
"partial_retry" => Some(UnableCategory::PartialRetry),
_ => None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct UnableRecord {
pub reason: String,
#[serde(default)]
pub missing: Vec<String>,
pub category: UnableCategory,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum Value {
String(String),
Int(i64),
Decimal(f64),
List(Vec<Value>),
Document(String),
AgentRef(AgentData),
Object(HashMap<String, Value>),
Bool(bool),
Unable(UnableRecord),
Union {
variant: String,
payload: Box<Value>,
},
FatalError {
message: String,
kind: ErrorKind,
#[serde(default = "default_error_code_other")]
code: ErrorCode,
#[serde(default)]
user_message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
retry_after_ms: Option<u64>,
#[serde(default, skip_serializing_if = "ErrorSource::is_empty")]
source: ErrorSource,
},
Json(serde_json::Value),
Null,
}
impl Value {
pub fn to_wire_json(&self) -> serde_json::Value {
self.to_json()
}
pub fn to_json(&self) -> serde_json::Value {
match self {
Value::String(s) | Value::Document(s) => serde_json::Value::String(s.clone()),
Value::Int(i) => serde_json::json!(i),
Value::Decimal(f) => serde_json::Number::from_f64(*f)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
Value::Bool(b) => serde_json::json!(b),
Value::Null => serde_json::Value::Null,
Value::List(items) => {
serde_json::Value::Array(items.iter().map(|v| v.to_json()).collect())
}
Value::Object(map) => {
let obj: serde_json::Map<String, serde_json::Value> =
map.iter().map(|(k, v)| (k.clone(), v.to_json())).collect();
serde_json::Value::Object(obj)
}
Value::Unable(rec) => {
serde_json::json!({
UNABLE_ENVELOPE_KEY: {
"reason": rec.reason,
"missing": rec.missing,
"category": rec.category.as_wire_str(),
}
})
}
Value::Union { variant, payload } => {
let mut inner = match payload.to_json() {
serde_json::Value::Object(m) => m,
other => {
let mut m = serde_json::Map::new();
m.insert("payload".to_string(), other);
m
}
};
inner.insert(
"kind".to_string(),
serde_json::Value::String(variant.clone()),
);
serde_json::Value::Object(inner)
}
Value::FatalError {
message,
kind,
code,
user_message,
retry_after_ms,
source,
} => {
serde_json::json!({
"FatalError": message,
"error_kind": kind,
"code": code.as_wire(),
"error_detail": {
"kind": kind,
"code": code.as_wire(),
"message": message,
"user_message": user_message,
"retry_after_ms": retry_after_ms,
"source": source,
},
})
}
Value::AgentRef(data) => serde_json::json!({ "agent": data.name }),
Value::Json(j) => j.clone(),
}
}
pub fn from_json(v: &serde_json::Value) -> Self {
match v {
serde_json::Value::String(s) => Value::String(s.clone()),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Value::Int(i)
} else if let Some(f) = n.as_f64() {
Value::Decimal(f)
} else {
Value::Json(serde_json::Value::Number(n.clone()))
}
}
serde_json::Value::Bool(b) => Value::Bool(*b),
serde_json::Value::Null => Value::Null,
serde_json::Value::Array(arr) => {
Value::List(arr.iter().map(Value::from_json).collect())
}
serde_json::Value::Object(map) => Value::Object(
map.iter()
.map(|(k, v)| (k.clone(), Value::from_json(v)))
.collect(),
),
}
}
pub fn from_json_with_union_arms(v: &serde_json::Value, arm_names: &[&str]) -> Self {
if let serde_json::Value::Object(map) = v {
if let Some(serde_json::Value::String(kind)) = map.get("kind") {
if arm_names.contains(&kind.as_str()) {
let mut stripped = map.clone();
stripped.remove("kind");
let payload = Value::from_json(&serde_json::Value::Object(stripped));
return Value::Union {
variant: kind.clone(),
payload: Box::new(payload),
};
}
}
}
Value::from_json(v)
}
pub fn fatal(detail: ErrorDetail) -> Self {
Value::FatalError {
message: detail.message,
kind: detail.kind,
code: detail.code,
user_message: detail.user_message,
retry_after_ms: detail.retry_after_ms,
source: detail.source,
}
}
pub fn fatal_kind(kind: ErrorKind, message: impl Into<String>) -> Self {
Value::fatal(ErrorDetail::from_kind(kind, message))
}
pub fn fatal_code(code: ErrorCode, message: impl Into<String>) -> Self {
Value::fatal(ErrorDetail::new(code, message))
}
pub fn as_fatal_detail(&self) -> Option<ErrorDetail> {
if let Value::FatalError {
message,
kind,
code,
user_message,
retry_after_ms,
source,
} = self
{
Some(ErrorDetail {
kind: *kind,
code: *code,
message: message.clone(),
user_message: user_message.clone(),
retry_after_ms: *retry_after_ms,
source: source.clone(),
})
} else {
None
}
}
#[deprecated(note = "use Value::fatal_code(ErrorCode::X, msg) instead")]
pub fn fatal_with_code(
message: impl Into<String>,
kind: ErrorKind,
code: impl AsRef<str>,
) -> Self {
let code = ErrorCode::from_wire(code.as_ref()).unwrap_or(ErrorCode::Other);
let detail = ErrorDetail::new(code, message);
Value::FatalError {
message: detail.message,
kind,
code: detail.code,
user_message: detail.user_message,
retry_after_ms: detail.retry_after_ms,
source: detail.source,
}
}
}
pub fn normalized_f64_bits(f: f64) -> u64 {
if f.is_nan() {
f64::NAN.to_bits()
} else if f == 0.0 {
0u64
} else {
f.to_bits()
}
}
impl Hash for Value {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Value::String(s) | Value::Document(s) => s.hash(state),
Value::Int(i) => i.hash(state),
Value::Decimal(f) => normalized_f64_bits(*f).hash(state),
Value::Bool(b) => b.hash(state),
Value::Null => {}
Value::List(items) => items.hash(state),
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
for k in keys {
k.hash(state);
map[k].hash(state);
}
}
Value::AgentRef(a) => a.hash(state),
Value::Unable(rec) => rec.hash(state),
Value::Union { variant, payload } => {
variant.hash(state);
payload.hash(state);
}
Value::FatalError {
message,
kind,
code,
..
} => {
message.hash(state);
kind.hash(state);
code.hash(state);
}
Value::Json(j) => j.to_string().hash(state),
}
}
}
impl Hash for AgentData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.provider.hash(state);
self.model_name.hash(state);
self.system_prompt.hash(state);
self.thinking.hash(state);
self.temperature.map(normalized_f64_bits).hash(state);
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::String(s) | Value::Document(s) => write!(f, "{}", s),
Value::Int(i) => write!(f, "{}", i),
Value::Decimal(fl) => write!(f, "{}", fl),
Value::Bool(b) => write!(f, "{}", b),
Value::Null => write!(f, "null"),
Value::List(items) => {
write!(f, "[")?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}", item)?;
}
write!(f, "]")
}
Value::Object(map) => {
write!(f, "{{")?;
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
for (i, k) in keys.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{}: {}", k, map[*k])?;
}
write!(f, "}}")
}
Value::Unable(rec) => {
write!(f, "Unable({}: {})", rec.category.as_wire_str(), rec.reason)
}
Value::Union { variant, payload } => write!(f, "{}({})", variant, payload),
Value::FatalError { message, .. } => write!(f, "{}", message),
Value::AgentRef(data) => write!(f, "<agent:{}>", data.name),
Value::Json(j) => write!(f, "{}", j),
}
}
}