mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(ai): bound every provider fetch with a 60 s AbortSignal timeout
The OpenAI, Anthropic, and local OpenAI-compatible clients all used a bare `fetch()` with no `signal`. A tar-pit upstream (a misconfigured local Ollama on a stalled GPU, a flaky proxy in front of a hosted provider, or a deliberately malicious user-configured LOCAL endpoint) could accept the POST and then hang indefinitely, pinning the request handler until the connection died on the socket level. A 60 s ceiling lands well outside any legitimate completion budget (production timings live in `getEvent()?.addExternalCall` traces) while capping the worst case. The Codex client uses an SSE stream with its own loop and stays as-is for now; a follow-up can thread the same ceiling through the streaming read once the contract there is settled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2d5b4cfac1
commit
fc83d8541f
3 files changed
+10
No files matched your search
@@ -49,6 +49,8 @@ export class AnthropicClient implements AIProvider {
|
||||
system: params.systemPrompt,
|
||||
messages: [{ role: "user", content: wrapForJson(params.userPrompt) }],
|
||||
}),
|
||||
// 60 s ceiling — see openai-client.ts for the rationale.
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -50,6 +50,10 @@ export class LocalOpenAICompatibleClient implements AIProvider {
|
||||
temperature: params.temperature ?? 0.3,
|
||||
max_tokens: params.maxTokens ?? 1000,
|
||||
}),
|
||||
// 60 s ceiling — a misbehaving local endpoint (Ollama on a stalled
|
||||
// GPU, a tar-pit proxy) shouldn't be able to pin a worker
|
||||
// indefinitely. Real local completions land well inside this.
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
@@ -46,6 +46,10 @@ export class OpenAIClient implements AIProvider {
|
||||
max_tokens: params.maxTokens ?? 1000,
|
||||
response_format: { type: "json_object" },
|
||||
}),
|
||||
// Cap any single completion at 60 s so a tar-pit upstream cannot
|
||||
// pin a worker indefinitely. Real completions land well inside this
|
||||
// budget — see `getEvent()?.addExternalCall` timings in production.
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
|
||||
Reference in new issue
Block a user