Skip to content

Commit cf04f7f

Browse files
bloveclaude
andauthored
fix(ag-ui-proxy): move out of /api/ namespace (#587)
The previous route /ag-ui/<t>/agent → /api/ag-ui-proxy/<t> got shadowed by the langgraph proxy: Vercel's check:true causes route re-evaluation, and ^/api/(.*) matches the rewritten path before the filesystem handle can find the ag-ui-proxy function. Production smoke caught it — requests landed on the langgraph proxy (which returned 404 with FastAPI's body because the rewrite to LangGraph Cloud failed). Move the function to functions/ag-ui-proxy/[[...path]].func (no /api/ prefix), update the route dest to /ag-ui-proxy/$1$2, and update parseProxyPath in ag-ui-proxy.ts to expect the new shape. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4fd7f54 commit cf04f7f

2 files changed

Lines changed: 18 additions & 11 deletions

File tree

scripts/ag-ui-proxy.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
* middleware before any /agent/<topic> handler runs.
1313
*
1414
* Bundled by scripts/assemble-examples.ts into
15-
* `.vercel/output/functions/api/ag-ui-proxy/[[...path]].func/index.js`
16-
* and reachable via the route rewrite
17-
* /ag-ui/<topic>/agent* → /api/ag-ui-proxy/<topic>*
15+
* `.vercel/output/functions/ag-ui-proxy/[[...path]].func/index.js`
16+
* (NOT under functions/api/ — the route table's catch-all `^/api/(.*)` for
17+
* the langgraph proxy would otherwise re-match the rewrite via check:true
18+
* and shadow this function). Reachable via the route rewrite
19+
* /ag-ui/<topic>/agent* → /ag-ui-proxy/<topic>*
1820
*
1921
* AG-UI uses streaming responses (Server-Sent Events / chunked), so the
2022
* upstream body is piped chunk-by-chunk rather than buffered.
@@ -86,16 +88,16 @@ function getOrigin(headers: VercelRequest['headers']): string | undefined {
8688
}
8789

8890
/**
89-
* Parse `/api/ag-ui-proxy/<topic>[/<rest>]` into { topic, rest } so the
91+
* Parse `/ag-ui-proxy/<topic>[/<rest>]` into { topic, rest } so the
9092
* upstream URL can be built as `<railway>/agent/<topic><rest>`.
9193
*/
9294
function parseProxyPath(url: string): { topic: string; rest: string } | null {
9395
const u = new URL(url, 'http://placeholder');
9496
const segments = u.pathname.split('/').filter(Boolean);
95-
// Expected: ['api', 'ag-ui-proxy', '<topic>', ...rest]
96-
if (segments[0] !== 'api' || segments[1] !== 'ag-ui-proxy' || !segments[2]) return null;
97-
const topic = segments[2];
98-
const rest = segments.slice(3).join('/');
97+
// Expected: ['ag-ui-proxy', '<topic>', ...rest]
98+
if (segments[0] !== 'ag-ui-proxy' || !segments[1]) return null;
99+
const topic = segments[1];
100+
const rest = segments.slice(2).join('/');
99101
const restPath = rest ? `/${rest}` : '';
100102
return { topic, rest: restPath };
101103
}

scripts/assemble-examples.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ for (const cap of capabilities) {
9595
const outputDir = resolve(deployDir, '.vercel/output');
9696
const staticDir = resolve(outputDir, 'static');
9797
const funcDir = resolve(outputDir, 'functions/api/[[...path]].func');
98-
const agUiFuncDir = resolve(outputDir, 'functions/api/ag-ui-proxy/[[...path]].func');
98+
// NOTE: deliberately NOT under functions/api/ — that would re-trigger the
99+
// langgraph proxy rule on the rewrite (check: true causes re-evaluation,
100+
// and ^/api/(.*) would catch /api/ag-ui-proxy/* and shadow the function).
101+
const agUiFuncDir = resolve(outputDir, 'functions/ag-ui-proxy/[[...path]].func');
99102

100103
// Copy static files to the output directory
101104
mkdirSync(staticDir, { recursive: true });
@@ -137,10 +140,12 @@ writeFileSync(resolve(agUiFuncDir, '.vc-config.json'), JSON.stringify({
137140
writeFileSync(resolve(outputDir, 'config.json'), JSON.stringify({
138141
version: 3,
139142
routes: [
140-
// ag-ui proxy: /ag-ui/<topic>/agent[/rest] → /api/ag-ui-proxy/<topic>[/rest]
143+
// ag-ui proxy: /ag-ui/<topic>/agent[/rest] → /ag-ui-proxy/<topic>[/rest]
141144
// Must precede the filesystem handle so static index.html lookups for
142145
// /ag-ui/<topic>/ still resolve while POSTs to /agent are proxied.
143-
{ src: '^/ag-ui/([^/]+)/agent(/.*)?$', dest: '/api/ag-ui-proxy/$1$2', check: true },
146+
// Deliberately NOT under /api/ — that would re-match the langgraph
147+
// proxy rule below via check: true re-evaluation.
148+
{ src: '^/ag-ui/([^/]+)/agent(/.*)?$', dest: '/ag-ui-proxy/$1$2', check: true },
144149
{ src: '^/api/(.*)', dest: '/api/[[...path]]', check: true },
145150
{ handle: 'filesystem' },
146151
{ src: '^/(langgraph|deep-agents|render|chat|ag-ui)/([^/]+)/(.+\\..+)$', dest: '/$1/$2/$3' },

0 commit comments

Comments
 (0)