Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/web/src/app/tui.json/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextResponse } from 'next/server';

/**
* Serves the Kilo CLI TUI config JSON Schema at `app.kilo.ai/tui.json`.
*
* Proxies the upstream opencode schema. Cached at the CDN edge for 1 hour
* with stale-while-revalidate, so the actual upstream fetch happens at most
* once per hour per region.
*/

const UPSTREAM = 'https://opencode.ai/tui.json';
const CACHE_SECONDS = 60 * 60; // 1 hour

export async function GET() {
const res = await fetch(UPSTREAM, { next: { revalidate: CACHE_SECONDS } });
if (!res.ok) {
return NextResponse.json(
{ error: `upstream ${UPSTREAM} returned ${res.status}` },
{ status: 502 }
);
}
const schema = await res.json();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Unhandled res.json() parse failure

If the upstream returns a 2xx response with a non-JSON body (e.g. an HTML gateway page), res.json() will throw, bypassing the res.ok guard and surfacing as an unhandled 500 rather than a clean 502. The sibling config.json/route.ts has the same pattern, but it wraps the result in a typed cast rather than fixing the throw either — worth addressing here while the file is new.

Consider wrapping in a try/catch:

let schema: unknown;
try {
  schema = await res.json();
} catch {
  return NextResponse.json({ error: 'upstream returned non-JSON body' }, { status: 502 });
}


return NextResponse.json(schema, {
headers: {
'cache-control': `public, max-age=0, s-maxage=${CACHE_SECONDS}, stale-while-revalidate=${CACHE_SECONDS}`,
'access-control-allow-origin': '*',
},
});
}