fix: add 120s timeout per tool and increase route maxDuration to 300s

- Add AbortController timeout (120s) to Railway fetch requests
- Gracefully handle timeout errors and report to health check system
- Increase /api/chat maxDuration from 60s to 300s (5 minutes)
- Prevents entire chat from timing out when one tool has large dependencies
- Tools that timeout are logged and skipped, allowing others to load

Fixes issue where tools like ctx-zip with many dependencies would
cause the entire chat request to timeout after 60 seconds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-12-05 00:43:13 +10:00
parent e3b6ff6d25
commit 1bd484797f
2 changed files with 66 additions and 35 deletions

View file

@ -12,7 +12,7 @@ import { loadAllTools, sanitizeToolName } from '~/lib/tool-loader';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 60;
export const maxDuration = 300; // 5 minutes for complex tool loading
// Add conversation state tracking (in-memory for MVP)
// biome-ignore lint/suspicious/noExplicitAny: Tool types from AI SDK are complex

View file

@ -111,47 +111,78 @@ export async function loadToolDynamically(
console.log(`🔗 Railway URL: ${RAILWAY_SERVICE_URL}`);
// Call Railway service to load and describe tool
const response = await fetch(`${RAILWAY_SERVICE_URL}/load-and-describe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
version,
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
env: env || {},
}),
});
// 120 second timeout per tool to handle large dependency downloads
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 120000);
if (!response.ok) {
const errorText = await response.text();
console.error(`❌ Railway service error (${response.status}): ${errorText}`);
let response;
let data;
// Trigger health check update in background (non-blocking)
reportToolFailure(
packageName,
exportName,
`Railway service error (${response.status}): ${errorText}`,
'import'
).catch((err) =>
console.error(`Failed to report tool failure for ${packageName}/${exportName}:`, err)
);
try {
response = await fetch(`${RAILWAY_SERVICE_URL}/load-and-describe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
packageName,
exportName,
version,
importUrl: importUrl || `https://esm.sh/${packageName}@${version}`,
env: env || {},
}),
signal: controller.signal,
});
return null;
}
clearTimeout(timeout);
const data = await response.json();
if (!response.ok) {
const errorText = await response.text();
console.error(`❌ Railway service error (${response.status}): ${errorText}`);
if (!data.success) {
console.error(`❌ Failed to load tool: ${data.error}`);
// Trigger health check update in background (non-blocking)
reportToolFailure(packageName, exportName, data.error || 'Unknown error', 'import').catch(
(err) =>
// Trigger health check update in background (non-blocking)
reportToolFailure(
packageName,
exportName,
`Railway service error (${response.status}): ${errorText}`,
'import'
).catch((err) =>
console.error(`Failed to report tool failure for ${packageName}/${exportName}:`, err)
);
);
return null;
return null;
}
data = await response.json();
if (!data.success) {
console.error(`❌ Failed to load tool: ${data.error}`);
// Trigger health check update in background (non-blocking)
reportToolFailure(packageName, exportName, data.error || 'Unknown error', 'import').catch(
(err) =>
console.error(`Failed to report tool failure for ${packageName}/${exportName}:`, err)
);
return null;
}
} catch (fetchError) {
clearTimeout(timeout);
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
console.error(`❌ Railway request timeout after 120s for ${packageName}/${exportName}`);
reportToolFailure(
packageName,
exportName,
'Railway service timeout (120s) - tool dependencies may be too large',
'import'
).catch((err) =>
console.error(`Failed to report tool failure for ${packageName}/${exportName}:`, err)
);
return null;
}
throw fetchError;
}
console.log(`✅ Tool loaded from Railway: ${cacheKey}`);