Skip to content

fix(ag-ui-proxy): name function in route dest so Vercel actually invokes it#589

Merged
blove merged 1 commit into
mainfrom
claude/ag-ui-proxy-invoke-fix
Jun 6, 2026
Merged

fix(ag-ui-proxy): name function in route dest so Vercel actually invokes it#589
blove merged 1 commit into
mainfrom
claude/ag-ui-proxy-invoke-fix

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented Jun 6, 2026

Problem

After #587 moved the proxy out of /api/, production smoke showed a direct request to /ag-ui-proxy/streaming returning Vercel's own NOT_FOUND — the function was unreachable, not just the route.

Root cause: a Vercel Build Output API function is only invoked when a route's dest names its catch-all placeholder ([[...path]]). The { handle: 'filesystem' } phase does not auto-serve catch-all functions. The previous dest: '/ag-ui-proxy/$1$2' produced a literal path (/ag-ui-proxy/streaming) that matched no static file and no function → 404.

The langgraph proxy works because its route names the function: { src: '^/api/(.*)', dest: '/api/[[...path]]' }.

Fix

Mirror the langgraph rule exactly:

{ src: '^/ag-ui/([^/]+)/agent(/.*)?$', dest: '/ag-ui-proxy/[[...path]]', check: true }

Vercel invokes the ag-ui-proxy catch-all function and preserves the original request URL in req.url (same mechanism langgraph relies on). So parseProxyPath now parses the public /ag-ui/<topic>/agent[/rest] path instead of the rewritten /ag-ui-proxy/.... Also preserves query strings on the upstream Railway call.

Verified locally: esbuild bundles clean; parse logic unit-checked (/ag-ui/streaming/agent → topic=streaming; /ag-ui/interrupts/agent/foo/bar → topic=interrupts, rest=/foo/bar).

Test plan (post-merge, against examples.threadplane.ai)

  • POST /ag-ui/streaming/agent with allowed Origin → streaming AG-UI SSE (RUN_STARTED … RUN_FINISHED)
  • same with wrong Origin → 403 {"error":"origin_not_allowed"}
  • spam >10 req/min with allowed Origin → 429
  • GET /ag-ui/streaming/ → 200 (SPA unaffected)

🤖 Generated with Claude Code

…y invoked

Production smoke: even a direct GET/POST to /ag-ui-proxy/streaming returned
Vercel NOT_FOUND — the function was never reachable. A Vercel Build Output
function is only invoked when a route's `dest` names its catch-all
(`[[...path]]`); the filesystem handle does NOT auto-serve catch-all
functions. The previous dest `/ag-ui-proxy/$1$2` produced a plain path that
matched no function.

Mirror the proven langgraph rule exactly:
  { src: '^/ag-ui/([^/]+)/agent(/.*)?$', dest: '/ag-ui-proxy/[[...path]]', check: true }
Vercel invokes the function and preserves the ORIGINAL request URL in
req.url, so parseProxyPath now parses the public `/ag-ui/<topic>/agent[/rest]`
path (was parsing the rewritten `/ag-ui-proxy/...`). Also preserve any
query string on the upstream Railway call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blove blove enabled auto-merge (squash) June 6, 2026 15:31
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment Jun 6, 2026 5:40pm

Request Review

@blove blove merged commit ffc7a83 into main Jun 6, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant