Commit 22fdc81e authored by edy's avatar edy

fix(gateway-client): increase chat completion timeout from 30s to 120s, add retry on timeout

- Add configurable chatRunTimeoutMs option (default 120s) to GatewayClientOptions
- Bump RPC request timeout from 15s to 30s
- Mark 'Timed out waiting for chat completion' as recoverable for auto-retry
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent d92166aa
Pipeline #18507 failed
...@@ -47,6 +47,7 @@ export function isRecoverableGatewayChatError(message?: string): boolean { ...@@ -47,6 +47,7 @@ export function isRecoverableGatewayChatError(message?: string): boolean {
const normalized = message.toLowerCase(); const normalized = message.toLowerCase();
return normalized.includes("gateway websocket is not open") return normalized.includes("gateway websocket is not open")
|| normalized.includes("timed out waiting for chat completion")
|| isTransientLocalGatewayError(message); || isTransientLocalGatewayError(message);
} }
......
...@@ -60,6 +60,8 @@ interface GatewayClientOptions { ...@@ -60,6 +60,8 @@ interface GatewayClientOptions {
deviceToken?: string; deviceToken?: string;
deviceIdentity?: GatewayDeviceIdentityProvider; deviceIdentity?: GatewayDeviceIdentityProvider;
onDeviceToken?: (deviceToken: string) => Promise<void> | void; onDeviceToken?: (deviceToken: string) => Promise<void> | void;
/** Maximum idle time (ms) between stream events before chat run times out. Default: 120_000 (2 min). */
chatRunTimeoutMs?: number;
} }
interface GatewayErrorShape { interface GatewayErrorShape {
...@@ -216,6 +218,7 @@ export class GatewayClient { ...@@ -216,6 +218,7 @@ export class GatewayClient {
private deviceToken?: string; private deviceToken?: string;
private readonly deviceIdentity?: GatewayDeviceIdentityProvider; private readonly deviceIdentity?: GatewayDeviceIdentityProvider;
private readonly onDeviceToken?: (deviceToken: string) => Promise<void> | void; private readonly onDeviceToken?: (deviceToken: string) => Promise<void> | void;
private readonly chatRunTimeoutMs: number;
private websocket?: WebSocket; private websocket?: WebSocket;
private statusSnapshot: GatewayStatus; private statusSnapshot: GatewayStatus;
private readonly logs: LogEntry[] = []; private readonly logs: LogEntry[] = [];
...@@ -231,6 +234,7 @@ export class GatewayClient { ...@@ -231,6 +234,7 @@ export class GatewayClient {
this.deviceToken = options.deviceToken; this.deviceToken = options.deviceToken;
this.deviceIdentity = options.deviceIdentity; this.deviceIdentity = options.deviceIdentity;
this.onDeviceToken = options.onDeviceToken; this.onDeviceToken = options.onDeviceToken;
this.chatRunTimeoutMs = options.chatRunTimeoutMs ?? 120_000;
this.statusSnapshot = this.createStatus("unknown", "Gateway client initialized."); this.statusSnapshot = this.createStatus("unknown", "Gateway client initialized.");
this.appendLog("info", `Gateway client initialized for ${this.url}.`); this.appendLog("info", `Gateway client initialized for ${this.url}.`);
} }
...@@ -717,7 +721,7 @@ export class GatewayClient { ...@@ -717,7 +721,7 @@ export class GatewayClient {
const timer = setTimeout(() => { const timer = setTimeout(() => {
this.pendingRequests.delete(id); this.pendingRequests.delete(id);
reject(new Error(`Gateway request timed out: ${method}`)); reject(new Error(`Gateway request timed out: ${method}`));
}, 15000); }, 30000);
this.pendingRequests.set(id, { this.pendingRequests.set(id, {
resolve, resolve,
...@@ -770,7 +774,7 @@ export class GatewayClient { ...@@ -770,7 +774,7 @@ export class GatewayClient {
const error = new Error("Timed out waiting for chat completion."); const error = new Error("Timed out waiting for chat completion.");
pending.onError?.({ sessionId: sessionKey, runId, error }); pending.onError?.({ sessionId: sessionKey, runId, error });
pending.reject(error); pending.reject(error);
}, 30000); }, this.chatRunTimeoutMs);
} }
private refreshChatRunTimer(runId: string): void { private refreshChatRunTimer(runId: string): void {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment