From 0bb550d51dc752e8c71f9b91894e255449fc22a3 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Fri, 31 Jul 2026 14:25:31 +0200 Subject: [PATCH 1/3] fix(ai-gateway): fit generation routes within Vercel timeout --- apps/web/src/app/api/gateway/[...path]/route.ts | 2 +- apps/web/src/app/api/openrouter/[...path]/route.ts | 4 ++-- .../ai-gateway/providers/upstream-request.generation.test.ts | 4 ++-- apps/web/src/lib/ai-gateway/providers/upstream-request.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/api/gateway/[...path]/route.ts b/apps/web/src/app/api/gateway/[...path]/route.ts index 03b89c3f47..3fecdd812b 100644 --- a/apps/web/src/app/api/gateway/[...path]/route.ts +++ b/apps/web/src/app/api/gateway/[...path]/route.ts @@ -1,3 +1,3 @@ export { POST } from '@/app/api/openrouter/[...path]/route'; -export const maxDuration = 1800; +export const maxDuration = 600; diff --git a/apps/web/src/app/api/openrouter/[...path]/route.ts b/apps/web/src/app/api/openrouter/[...path]/route.ts index c73b55f53a..a913a696cc 100644 --- a/apps/web/src/app/api/openrouter/[...path]/route.ts +++ b/apps/web/src/app/api/openrouter/[...path]/route.ts @@ -107,7 +107,7 @@ import { import { redactProviderHints } from '@kilocode/auto-routing-contracts'; import { logExceptInTest } from '@/lib/utils.server'; -export const maxDuration = 1800; +export const maxDuration = 600; const MAX_TOKENS_LIMIT = 99999999999; // GPT4.1 default is ~32k @@ -621,7 +621,7 @@ export async function POST(request: NextRequest): Promise MAX_TOKENS_LIMIT) { console.warn(`SECURITY: Max tokens limit exceeded: ${user.id}`, { diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts index 440c76af50..0d3dbd306e 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts @@ -36,7 +36,7 @@ describe('fetchGeneration', () => { jest.useRealTimers(); }); - it('uses a longer, slower retry window', async () => { + it('limits generation polling to about one minute', async () => { mockFetchWithBackoff.mockResolvedValue( new Response(JSON.stringify({ id: 'generation-id' }), { status: 200, @@ -56,7 +56,7 @@ describe('fetchGeneration', () => { }, expect.objectContaining({ baseDelayMs: 5_000, - maxDelayMs: 5 * 60 * 1_000, + maxDelayMs: 60 * 1_000, }) ); diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts index 1cef743716..cf24f960f0 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts @@ -25,7 +25,7 @@ type UpstreamFetchFailureFamily = // Longer than Vercel AI Gateway's 13min timeout, shorter than Vercel Function's 30min timeout. const TIMEOUT_MS = 15 * 60 * 1000; -const GENERATION_FETCH_MAX_DELAY_MS = 5 * 60 * 1000; +const GENERATION_FETCH_MAX_DELAY_MS = 60 * 1000; function getProviderTargetHost(apiUrl: string): string { try { From 4d7367ad45b346acfa7154881e069a2dd0c8b192 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Fri, 31 Jul 2026 14:36:02 +0200 Subject: [PATCH 2/3] fix(ai-gateway): reserve post-stream timeout headroom --- apps/web/src/app/api/gateway/[...path]/route.ts | 2 +- apps/web/src/app/api/openrouter/[...path]/route.ts | 4 ++-- .../providers/upstream-request.generation.test.ts | 2 +- apps/web/src/lib/ai-gateway/providers/upstream-request.ts | 7 ++++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/api/gateway/[...path]/route.ts b/apps/web/src/app/api/gateway/[...path]/route.ts index 3fecdd812b..2daeecbc0b 100644 --- a/apps/web/src/app/api/gateway/[...path]/route.ts +++ b/apps/web/src/app/api/gateway/[...path]/route.ts @@ -1,3 +1,3 @@ export { POST } from '@/app/api/openrouter/[...path]/route'; -export const maxDuration = 600; +export const maxDuration = 800; diff --git a/apps/web/src/app/api/openrouter/[...path]/route.ts b/apps/web/src/app/api/openrouter/[...path]/route.ts index a913a696cc..b8555300c4 100644 --- a/apps/web/src/app/api/openrouter/[...path]/route.ts +++ b/apps/web/src/app/api/openrouter/[...path]/route.ts @@ -107,7 +107,7 @@ import { import { redactProviderHints } from '@kilocode/auto-routing-contracts'; import { logExceptInTest } from '@/lib/utils.server'; -export const maxDuration = 600; +export const maxDuration = 800; const MAX_TOKENS_LIMIT = 99999999999; // GPT4.1 default is ~32k @@ -621,7 +621,7 @@ export async function POST(request: NextRequest): Promise MAX_TOKENS_LIMIT) { console.warn(`SECURITY: Max tokens limit exceeded: ${user.id}`, { diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts index 0d3dbd306e..9cd3b48437 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.generation.test.ts @@ -56,7 +56,7 @@ describe('fetchGeneration', () => { }, expect.objectContaining({ baseDelayMs: 5_000, - maxDelayMs: 60 * 1_000, + maxDelayMs: 75 * 1_000, }) ); diff --git a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts index cf24f960f0..69eee0db58 100644 --- a/apps/web/src/lib/ai-gateway/providers/upstream-request.ts +++ b/apps/web/src/lib/ai-gateway/providers/upstream-request.ts @@ -23,9 +23,10 @@ type UpstreamFetchFailureFamily = | 'abort' | 'unknown'; -// Longer than Vercel AI Gateway's 13min timeout, shorter than Vercel Function's 30min timeout. -const TIMEOUT_MS = 15 * 60 * 1000; -const GENERATION_FETCH_MAX_DELAY_MS = 60 * 1000; +// Leave 200s of the Vercel function budget for post-stream work. +const TIMEOUT_MS = 10 * 60 * 1000; +// fetchWithBackoff reserves the next delay before retrying, so 75s yields about one minute. +const GENERATION_FETCH_MAX_DELAY_MS = 75 * 1000; function getProviderTargetHost(apiUrl: string): string { try { From 1270cc4e25adcfc971b50f673e089abeaf272f5a Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Fri, 31 Jul 2026 14:47:16 +0200 Subject: [PATCH 3/3] fix(ai-gateway): cap transcription route duration --- apps/web/src/app/api/gateway/audio/transcriptions/route.ts | 2 +- apps/web/src/app/api/gateway/v1/audio/transcriptions/route.ts | 2 +- .../src/app/api/openrouter/audio/transcriptions/route.test.ts | 2 +- apps/web/src/app/api/openrouter/audio/transcriptions/route.ts | 2 +- .../web/src/app/api/openrouter/v1/audio/transcriptions/route.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/api/gateway/audio/transcriptions/route.ts b/apps/web/src/app/api/gateway/audio/transcriptions/route.ts index d385686382..86c952a4b4 100644 --- a/apps/web/src/app/api/gateway/audio/transcriptions/route.ts +++ b/apps/web/src/app/api/gateway/audio/transcriptions/route.ts @@ -1,3 +1,3 @@ export { POST } from '@/app/api/openrouter/audio/transcriptions/route'; -export const maxDuration = 1800; +export const maxDuration = 800; diff --git a/apps/web/src/app/api/gateway/v1/audio/transcriptions/route.ts b/apps/web/src/app/api/gateway/v1/audio/transcriptions/route.ts index d385686382..86c952a4b4 100644 --- a/apps/web/src/app/api/gateway/v1/audio/transcriptions/route.ts +++ b/apps/web/src/app/api/gateway/v1/audio/transcriptions/route.ts @@ -1,3 +1,3 @@ export { POST } from '@/app/api/openrouter/audio/transcriptions/route'; -export const maxDuration = 1800; +export const maxDuration = 800; diff --git a/apps/web/src/app/api/openrouter/audio/transcriptions/route.test.ts b/apps/web/src/app/api/openrouter/audio/transcriptions/route.test.ts index a65bd610be..0699f7e901 100644 --- a/apps/web/src/app/api/openrouter/audio/transcriptions/route.test.ts +++ b/apps/web/src/app/api/openrouter/audio/transcriptions/route.test.ts @@ -85,7 +85,7 @@ describe('POST /api/gateway/v1/audio/transcriptions', () => { import('@/app/api/gateway/v1/audio/transcriptions/route'), ]); - expect(routes.map(route => route.maxDuration)).toEqual([1800, 1800, 1800, 1800]); + expect(routes.map(route => route.maxDuration)).toEqual([800, 800, 800, 800]); }); it('proxies transcription requests to OpenRouter', async () => { diff --git a/apps/web/src/app/api/openrouter/audio/transcriptions/route.ts b/apps/web/src/app/api/openrouter/audio/transcriptions/route.ts index 9b4bae48a1..e8a491973a 100644 --- a/apps/web/src/app/api/openrouter/audio/transcriptions/route.ts +++ b/apps/web/src/app/api/openrouter/audio/transcriptions/route.ts @@ -31,7 +31,7 @@ import { } from '@/lib/ai-gateway/transcriptions/transcription-request'; import type { Provider } from '@/lib/ai-gateway/providers/types'; -export const maxDuration = 1800; +export const maxDuration = 800; const PAID_MODEL_AUTH_REQUIRED = 'PAID_MODEL_AUTH_REQUIRED'; diff --git a/apps/web/src/app/api/openrouter/v1/audio/transcriptions/route.ts b/apps/web/src/app/api/openrouter/v1/audio/transcriptions/route.ts index d385686382..86c952a4b4 100644 --- a/apps/web/src/app/api/openrouter/v1/audio/transcriptions/route.ts +++ b/apps/web/src/app/api/openrouter/v1/audio/transcriptions/route.ts @@ -1,3 +1,3 @@ export { POST } from '@/app/api/openrouter/audio/transcriptions/route'; -export const maxDuration = 1800; +export const maxDuration = 800;