fix(core,web): retry transport and quota LLM errors, disable dead sessions on auth failure (#82)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Yaowei ZhengandClaude Fable 5 authored and GitHub committed 2026-07-27 22:29:16 +08:00
1 parent c369e089a7
commit 3d3495528c
54 files changed
+2278 -161

No files matched your search

+17 -3
View File
@@ -269,9 +269,23 @@ export function requestBegin(): OmniMessage<RequestBeginPayload> {
return event({ type: "request_begin" });
}
/** request end event: carries the terminal state (`completed` means this turn was already committed to AgentHub). */
export function requestEnd(status: StopReason): OmniMessage<RequestEndPayload> {
return event({ type: "request_end", status });
/**
* request end event: carries the terminal state (`completed` means this turn was already
* committed to AgentHub), plus — on non-completed statuses — the failure detail (from
* LLMOutcome.message) and, when the engine will retry in-run, the planned backoff wait
* (`retry_in_ms`, rendered by the Web App as a live countdown).
*/
export function requestEnd(
status: StopReason,
message?: string,
retryInMs?: number,
): OmniMessage<RequestEndPayload> {
return event({
type: "request_end",
status,
...(message !== undefined ? { message } : {}),
...(retryInMs !== undefined ? { retry_in_ms: retryInMs } : {}),
});
}
/** compaction begin event: carries the trigger reason, mode, current context usage, and cumulative Session turn count. */
+27 -5
View File
@@ -23,7 +23,7 @@ export type OmniMessageType = "session_meta" | "model_msg" | "event_msg";
export type Role = "user" | "assistant";
/**
* The reason a model response or message generation ended. Only five protocol values are
* The reason a model response or message generation ended. Only six protocol values are
* allowed:
* - `completed`: finished normally, including completed text, thinking, tool requests, or
* tool output;
@@ -31,10 +31,15 @@ export type Role = "user" | "assistant";
* - `aborted`: user-initiated interruption or cancellation;
* - `timeout`: LLM request timed out;
* - `malformed`: the LLM response was malformed (e.g. AgentHub JSON parsing exception).
* Only LLM timeout / malformed trigger a context_engine reconnect.
* Only LLM timeout / malformed trigger a context_engine reconnect;
* - `auth`: the LLM rejected the credentials (see `isAuthenticationError`) — a
* `failed`-shaped stop that no in-run retry can fix; hosts disable input until the
* model's API key is updated (only the model reference is fixed at Session creation —
* credentials come from the current Project config), after which the Session
* continues.
* Docs: /docs/omni-message § "stop_reason".
*/
export type StopReason = "completed" | "failed" | "aborted" | "timeout" | "malformed";
export type StopReason = "completed" | "failed" | "aborted" | "timeout" | "malformed" | "auth";
/** The event phase of a streaming fragment. `stop` marks the end of a fragment and usually carries no incremental content. */
export type StreamEventType = "start" | "delta" | "stop";
@@ -277,8 +282,25 @@ export interface RequestBeginPayload {
export interface RequestEndPayload {
type: "request_end";
/** Terminal state of this Request (reuses the five StopReason values, sharing its source with this turn's complete message's stop_reason / LLMOutcome). */
/** Terminal state of this Request (reuses the six StopReason values, sharing its source with this turn's complete message's stop_reason / LLMOutcome; `auth` is the credentials-failure signal hosts key on). */
status: StopReason;
/**
* Failure detail from `LLMOutcome.message`, present only on non-completed statuses: the
* real reason behind a retried/failed Request (e.g. `403 … (insufficient_user_quota)`),
* for observability — the server's error records / Cost center read it here because a
* retried request never produces an abort event. Additive: old Traces without it replay
* unchanged.
*/
message?: string;
/**
* Planned in-run retry wait (ms) — present ONLY when the engine will retry this failure
* within the same run (status `timeout`/`malformed` with attempts remaining under the
* applicable cap). Computed by the same formula as the actual backoff sleep
* (`reconnectDelayMs`), so the announced wait and the real one cannot drift; the Web App
* renders it as a live countdown to the next attempt. Absent on final failures (an abort
* follows instead) and on completed requests. Additive: old Traces replay unchanged.
*/
retry_in_ms?: number;
}
/** Compaction trigger reason: context threshold / turn-count threshold / user-initiated request. */
@@ -291,7 +313,7 @@ export type CompactionMode = "summarize" | "discard";
* Compaction boundary event: the compaction process exposes
* only this event pair to Human, produced **in pairs** by `context_engine`. Both `reason` and
* `mode` are carried on both events, for stateless frontend rendering; `status` reuses the
* five-value `StopReason` protocol (compaction converges to a terminal state, taking
* six-value `StopReason` protocol (compaction converges to a terminal state, taking
* `completed` / `failed` / `aborted` in practice — `timeout` / `malformed` are handled internally
* by the compaction request's existing retry mechanism, collapsing to `failed` once retries are
* exhausted).