"""Stable machine-readable error codes returned by the broker. Every failure crosses the socket/MCP boundary as {code, message} only -- never with credential material attached. """ class BrokerError(Exception): """Error that is safe to surface to the agent (no secret material).""" def __init__(self, code, message): super().__init__(message) self.code = code self.message = message def to_dict(self): return {"code": self.code, "message": self.message} def locked(): return BrokerError("BROKER_LOCKED", "broker is locked; run 'agent-broker unlock'") def unconfigured(detail="missing API client credentials"): return BrokerError( "UNCONFIGURED", f"broker not configured: {detail}", ) def not_found(ref): return BrokerError("NOT_FOUND", f"no credential with ref {ref!r}") def ambiguous(ref, count): return BrokerError("AMBIGUOUS_REF", f"ref {ref!r} matches {count} items; fix agent.ref duplicates") def adapter_denied(ref, adapter): return BrokerError("ADAPTER_DENIED", f"credential {ref!r} is not allowed for adapter {adapter!r}") def origin_denied(ref, target): return BrokerError("ORIGIN_DENIED", f"credential {ref!r} is not authorized for target {target!r}") def denied(message): return BrokerError("DENIED", message) def invalid_argument(message): return BrokerError("DENIED_INVALID_ARGUMENT", message) def confirmation_required(ticket=None): err = BrokerError("CONFIRMATION_REQUIRED", "operation requires interactive confirmation") err.ticket = ticket return err def unsafe_tool_integration(tool): return BrokerError( "UNSAFE_TOOL_INTEGRATION", f"{tool}: no credential-provider or safe injection path exists; refusing integration", ) def backend_error(message): """Vault backend failure (e.g. `bw` subprocess). Never silently falls back to another backend.""" return BrokerError("VAULT_BACKEND_ERROR", message) def internal(message): """Server-side fault. Message must already be secret-free.""" return BrokerError("INTERNAL_ERROR", message)