Treat inference hook denials as terminal errors (#60511)

## Why

An explicit denial from a workspace inference hook should stop the request, including retries and compaction model fallback.

## What changed

Recognize `inference_hook_denied` provider errors and preserve the provider's message, with a fallback when none is supplied. Expose the distinct `inferenceHookDenied` error through the app-server protocol and generated schemas and SDK types. Treat the denial as non-retryable during turns, compaction, and guardian review.

## Testing

Add coverage for terminal denials over HTTP, SSE, and WebSocket, error serialization and persistence in `thread/read`, and compaction failures that preserve token usage and history without retrying or falling back to another model.

GitOrigin-RevId: 2ebd480f01e52f4e6bd3db8a0f92d5814d18ccb1
This commit is contained in:
ccui-oai
2026-11-10 03:21:26 +00:00
committed by copyberry
parent 1099fe0694
commit 0811df3976
43 changed files with 292 additions and 51 deletions
+9
View File
@@ -57,6 +57,8 @@ pub enum ApiError {
#[error("invalid prompt: {message}")]
InvalidPrompt { message: String },
#[error("{message}")]
InferenceHookDenied { message: String },
#[error("{message}")]
ContentPolicyViolation {
message: String,
status: Option<StatusCode>,
@@ -96,6 +98,13 @@ pub(crate) fn parse_terminal_provider_error(
.and_then(Value::as_str)
.filter(|message| !message.trim().is_empty());
match (code, status) {
("inference_hook_denied", None | Some(StatusCode::FORBIDDEN)) => {
Some(ApiError::InferenceHookDenied {
message: message
.unwrap_or("Your workspace inference hook blocked this request.")
.to_owned(),
})
}
("context_length_exceeded", None | Some(StatusCode::BAD_REQUEST)) => {
Some(ApiError::ContextWindowExceeded)
}