Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/core/socketio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,37 @@ pub struct WebChannelEvent {
/// Type of error, if the event represents a failure.
#[serde(skip_serializing_if = "Option::is_none")]
pub error_type: Option<String>,
/// Structured rate-limit / error metadata produced by
/// `classify_inference_error` (issue #2606). All four fields are
/// additive — older FE clients that only read `message`/`error_type`
/// keep working; new clients can read these to render countdown,
/// retry-button, and fallback-CTA UI without regexing the message.
///
/// Where the limit originated:
/// `"provider"` | `"openhuman_budget"` | `"agent_loop"`
/// | `"openhuman_billing"` | `"transport"` | `"config"`.
#[serde(skip_serializing_if = "Option::is_none")]
pub error_source: Option<String>,
/// Whether the same prompt can be retried in this same thread.
/// `false` for non-retryable business 429s, auth, model_unavailable,
/// context_overflow, and billing exhaustion.
#[serde(skip_serializing_if = "Option::is_none")]
pub error_retryable: Option<bool>,
/// Milliseconds to wait before retrying, as supplied by the upstream
/// `Retry-After:` / `retry_after:` header. `None` when the upstream
/// didn't supply one or the error class has no retry-after concept.
#[serde(skip_serializing_if = "Option::is_none")]
pub error_retry_after_ms: Option<u64>,
/// Provider name extracted from `"<provider> API error (...)"`
/// envelopes. `None` for non-provider errors (OpenHuman budget cap,
/// agent loop) and for transport failures without a provider prefix.
#[serde(skip_serializing_if = "Option::is_none")]
pub error_provider: Option<String>,
/// `Some(false)` once the reliable-provider chain has exhausted
/// every configured `model_fallbacks` entry. `None` means "unknown
/// — FE should not promise a fallback".
#[serde(skip_serializing_if = "Option::is_none")]
pub error_fallback_available: Option<bool>,
/// Name of the tool being called.
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_name: Option<String>,
Expand Down
5 changes: 5 additions & 0 deletions src/openhuman/channels/proactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ impl EventHandler for ProactiveMessageSubscriber {
full_response: Some(message.clone()),
message: None,
error_type: None,
error_source: None,
error_retryable: None,
error_retry_after_ms: None,
error_provider: None,
error_fallback_available: None,
tool_name: None,
skill_id: None,
args: None,
Expand Down
15 changes: 15 additions & 0 deletions src/openhuman/channels/providers/presentation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ pub async fn deliver_response(
full_response: Some(full_response.to_string()),
message: None,
error_type: None,
error_source: None,
error_retryable: None,
error_retry_after_ms: None,
error_provider: None,
error_fallback_available: None,
tool_name: None,
skill_id: None,
args: None,
Expand Down Expand Up @@ -92,6 +97,11 @@ pub async fn deliver_response(
full_response: Some(segment.clone()),
message: None,
error_type: None,
error_source: None,
error_retryable: None,
error_retry_after_ms: None,
error_provider: None,
error_fallback_available: None,
tool_name: None,
skill_id: None,
args: None,
Expand Down Expand Up @@ -124,6 +134,11 @@ pub async fn deliver_response(
full_response: Some(full_response.to_string()),
message: None,
error_type: None,
error_source: None,
error_retryable: None,
error_retry_after_ms: None,
error_provider: None,
error_fallback_available: None,
tool_name: None,
skill_id: None,
args: None,
Expand Down
Loading
Loading