mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-25 18:13:32 +00:00
fix(feishu): avoid media regressions from global HTTP timeout (#36500)
* fix(feishu): avoid media regressions from global http timeout * fix(feishu): source HTTP timeout from config * fix(feishu): apply media timeout override to image uploads * fix(feishu): invalidate cached client when timeout changes * fix(feishu): clamp timeout values and cover image download
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import * as Lark from "@larksuiteoapi/node-sdk";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import type { FeishuDomain, ResolvedFeishuAccount } from "./types.js";
|
||||
import type { FeishuConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js";
|
||||
|
||||
/** Default HTTP timeout for Feishu API requests (30 seconds). */
|
||||
export const FEISHU_HTTP_TIMEOUT_MS = 30_000;
|
||||
export const FEISHU_HTTP_TIMEOUT_MAX_MS = 300_000;
|
||||
export const FEISHU_HTTP_TIMEOUT_ENV_VAR = "OPENCLAW_FEISHU_HTTP_TIMEOUT_MS";
|
||||
|
||||
function getWsProxyAgent(): HttpsProxyAgent<string> | undefined {
|
||||
const proxyUrl =
|
||||
@@ -20,7 +22,7 @@ const clientCache = new Map<
|
||||
string,
|
||||
{
|
||||
client: Lark.Client;
|
||||
config: { appId: string; appSecret: string; domain?: FeishuDomain };
|
||||
config: { appId: string; appSecret: string; domain?: FeishuDomain; httpTimeoutMs: number };
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -39,11 +41,11 @@ function resolveDomain(domain: FeishuDomain | undefined): Lark.Domain | string {
|
||||
* but injects a default request timeout to prevent indefinite hangs
|
||||
* (e.g. when the Feishu API is slow, causing per-chat queue deadlocks).
|
||||
*/
|
||||
function createTimeoutHttpInstance(): Lark.HttpInstance {
|
||||
function createTimeoutHttpInstance(defaultTimeoutMs: number): Lark.HttpInstance {
|
||||
const base: Lark.HttpInstance = Lark.defaultHttpInstance as unknown as Lark.HttpInstance;
|
||||
|
||||
function injectTimeout<D>(opts?: Lark.HttpRequestOptions<D>): Lark.HttpRequestOptions<D> {
|
||||
return { timeout: FEISHU_HTTP_TIMEOUT_MS, ...opts } as Lark.HttpRequestOptions<D>;
|
||||
return { timeout: defaultTimeoutMs, ...opts } as Lark.HttpRequestOptions<D>;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -67,14 +69,40 @@ export type FeishuClientCredentials = {
|
||||
appId?: string;
|
||||
appSecret?: string;
|
||||
domain?: FeishuDomain;
|
||||
httpTimeoutMs?: number;
|
||||
config?: Pick<FeishuConfig, "httpTimeoutMs">;
|
||||
};
|
||||
|
||||
function resolveConfiguredHttpTimeoutMs(creds: FeishuClientCredentials): number {
|
||||
const clampTimeout = (value: number): number => {
|
||||
const rounded = Math.floor(value);
|
||||
return Math.min(Math.max(rounded, 1), FEISHU_HTTP_TIMEOUT_MAX_MS);
|
||||
};
|
||||
|
||||
const envRaw = process.env[FEISHU_HTTP_TIMEOUT_ENV_VAR];
|
||||
if (envRaw) {
|
||||
const envValue = Number(envRaw);
|
||||
if (Number.isFinite(envValue) && envValue > 0) {
|
||||
return clampTimeout(envValue);
|
||||
}
|
||||
}
|
||||
|
||||
const fromConfig = creds.config?.httpTimeoutMs;
|
||||
const fromDirectField = creds.httpTimeoutMs;
|
||||
const timeout = fromDirectField ?? fromConfig;
|
||||
if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) {
|
||||
return FEISHU_HTTP_TIMEOUT_MS;
|
||||
}
|
||||
return clampTimeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or get a cached Feishu client for an account.
|
||||
* Accepts any object with appId, appSecret, and optional domain/accountId.
|
||||
*/
|
||||
export function createFeishuClient(creds: FeishuClientCredentials): Lark.Client {
|
||||
const { accountId = "default", appId, appSecret, domain } = creds;
|
||||
const defaultHttpTimeoutMs = resolveConfiguredHttpTimeoutMs(creds);
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
throw new Error(`Feishu credentials not configured for account "${accountId}"`);
|
||||
@@ -86,7 +114,8 @@ export function createFeishuClient(creds: FeishuClientCredentials): Lark.Client
|
||||
cached &&
|
||||
cached.config.appId === appId &&
|
||||
cached.config.appSecret === appSecret &&
|
||||
cached.config.domain === domain
|
||||
cached.config.domain === domain &&
|
||||
cached.config.httpTimeoutMs === defaultHttpTimeoutMs
|
||||
) {
|
||||
return cached.client;
|
||||
}
|
||||
@@ -97,13 +126,13 @@ export function createFeishuClient(creds: FeishuClientCredentials): Lark.Client
|
||||
appSecret,
|
||||
appType: Lark.AppType.SelfBuild,
|
||||
domain: resolveDomain(domain),
|
||||
httpInstance: createTimeoutHttpInstance(),
|
||||
httpInstance: createTimeoutHttpInstance(defaultHttpTimeoutMs),
|
||||
});
|
||||
|
||||
// Cache it
|
||||
clientCache.set(accountId, {
|
||||
client,
|
||||
config: { appId, appSecret, domain },
|
||||
config: { appId, appSecret, domain, httpTimeoutMs: defaultHttpTimeoutMs },
|
||||
});
|
||||
|
||||
return client;
|
||||
|
||||
Reference in New Issue
Block a user