From: Stefan Gasser Date: Wed, 4 Mar 2026 08:28:29 +0000 (+0100) Subject: feat: add configurable request timeout (#79) X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=8133a3f2b1c0b4ada2b37562c1f7c0b09fa82d18;p=sgasser-llm-shield.git feat: add configurable request timeout (#79) Add server.request_timeout config option (default: 600 seconds). Previously hardcoded to 120 seconds which caused timeouts for complex Opus queries. Set to 0 to disable timeout entirely. Closes #78 --- diff --git a/config.example.yaml b/config.example.yaml index 3c20aaf..07ebd73 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -11,6 +11,7 @@ mode: mask server: port: 3000 host: "0.0.0.0" + # request_timeout: 600 # Seconds (0 = no timeout, default: 600) # Providers - API endpoints # Can be cloud (OpenAI, Anthropic, Azure) or self-hosted (vLLM, LiteLLM proxy, etc.) diff --git a/docs/configuration/overview.mdx b/docs/configuration/overview.mdx index 74674a1..96130df 100644 --- a/docs/configuration/overview.mdx +++ b/docs/configuration/overview.mdx @@ -30,12 +30,14 @@ See [Mask Mode](/concepts/mask-mode) and [Route Mode](/concepts/route-mode) for server: port: 3000 host: "0.0.0.0" + request_timeout: 600 ``` | Option | Default | Description | |--------|---------|-------------| | `port` | `3000` | HTTP port | | `host` | `0.0.0.0` | Bind address | +| `request_timeout` | `600` | Request timeout in seconds (0 = no timeout) | ## Dashboard diff --git a/src/config.ts b/src/config.ts index 00e24ab..29457c6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -72,6 +72,7 @@ const PIIDetectionSchema = z.object({ const ServerSchema = z.object({ port: z.coerce.number().int().min(1).max(65535).default(3000), host: z.string().default("0.0.0.0"), + request_timeout: z.coerce.number().int().min(0).default(600), }); const LoggingSchema = z.object({ @@ -163,6 +164,7 @@ export type AnthropicProviderConfig = z.infer; export type LocalProviderConfig = z.infer; export type MaskingConfig = z.infer; export type SecretsDetectionConfig = z.infer; +export type ServerConfig = z.infer; /** * Replaces ${VAR} and ${VAR:-default} patterns with environment variable values diff --git a/src/constants/timeouts.ts b/src/constants/timeouts.ts index e4dd8d0..1c948fd 100644 --- a/src/constants/timeouts.ts +++ b/src/constants/timeouts.ts @@ -1,2 +1 @@ -export const REQUEST_TIMEOUT_MS = 120_000; export const HEALTH_CHECK_TIMEOUT_MS = 5_000; diff --git a/src/providers/anthropic/client.ts b/src/providers/anthropic/client.ts index f73b69e..41c095c 100644 --- a/src/providers/anthropic/client.ts +++ b/src/providers/anthropic/client.ts @@ -2,8 +2,7 @@ * Anthropic client - simple functions for Anthropic Messages API */ -import type { AnthropicProviderConfig } from "../../config"; -import { REQUEST_TIMEOUT_MS } from "../../constants/timeouts"; +import { type AnthropicProviderConfig, getConfig } from "../../config"; import { ProviderError } from "../errors"; import type { AnthropicRequest, AnthropicResponse } from "./types"; @@ -68,11 +67,12 @@ export async function callAnthropic( headers["anthropic-beta"] = clientHeaders.beta; } + const timeoutMs = getConfig().server.request_timeout * 1000; const response = await fetch(`${baseUrl}/v1/messages`, { method: "POST", headers, body: JSON.stringify(request), - signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), + signal: timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined, }); if (!response.ok) { diff --git a/src/providers/local.ts b/src/providers/local.ts index 6670655..cb20624 100644 --- a/src/providers/local.ts +++ b/src/providers/local.ts @@ -3,8 +3,8 @@ * Used in route mode for PII-containing requests (no masking needed) */ -import type { LocalProviderConfig } from "../config"; -import { HEALTH_CHECK_TIMEOUT_MS, REQUEST_TIMEOUT_MS } from "../constants/timeouts"; +import { getConfig, type LocalProviderConfig } from "../config"; +import { HEALTH_CHECK_TIMEOUT_MS } from "../constants/timeouts"; import type { AnthropicResult } from "./anthropic/client"; import type { AnthropicRequest, AnthropicResponse } from "./anthropic/types"; import { ProviderError, type ProviderResult } from "./openai/client"; @@ -27,12 +27,13 @@ export async function callLocal( } const isStreaming = request.stream ?? false; + const timeoutMs = getConfig().server.request_timeout * 1000; const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify({ ...request, model: config.model, stream: isStreaming }), - signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), + signal: timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined, }); if (!response.ok) { @@ -70,12 +71,13 @@ export async function callLocalAnthropic( } const isStreaming = request.stream ?? false; + const timeoutMs = getConfig().server.request_timeout * 1000; const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify({ ...request, model: config.model, stream: isStreaming }), - signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), + signal: timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined, }); if (!response.ok) { diff --git a/src/providers/openai/client.ts b/src/providers/openai/client.ts index 9768abc..902ee08 100644 --- a/src/providers/openai/client.ts +++ b/src/providers/openai/client.ts @@ -2,8 +2,7 @@ * OpenAI client - simple functions for OpenAI API */ -import type { OpenAIProviderConfig } from "../../config"; -import { REQUEST_TIMEOUT_MS } from "../../constants/timeouts"; +import { getConfig, type OpenAIProviderConfig } from "../../config"; import { ProviderError } from "../errors"; import type { OpenAIRequest, OpenAIResponse } from "./types"; @@ -66,11 +65,12 @@ export async function callOpenAI( delete body.max_tokens; } + const timeoutMs = getConfig().server.request_timeout * 1000; const response = await fetch(endpoint, { method: "POST", headers, body: JSON.stringify(body), - signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), + signal: timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined, }); if (!response.ok) {