Skip to content

Commit c92a7f7

Browse files
Add OpenCode Zen CORS proxy (Deno Deploy)
1 parent e26722c commit c92a7f7

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Deno Deploy: CORS proxy for opencode.ai/zen
3+
* Forwards POST /zen/v1/chat/completions to opencode.ai
4+
* and adds Access-Control-Allow-Origin: * so Android WebViews can reach it.
5+
*/
6+
7+
const TARGET_BASE = "https://opencode.ai";
8+
9+
const CORS_HEADERS: Record<string, string> = {
10+
"Access-Control-Allow-Origin": "*",
11+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
12+
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
13+
"Access-Control-Expose-Headers": "Content-Type, X-Request-Id",
14+
"Access-Control-Max-Age": "86400",
15+
};
16+
17+
Deno.serve(async (request: Request): Promise<Response> => {
18+
// CORS preflight – must respond immediately with 200 (not 204, some WebViews reject 204)
19+
if (request.method === "OPTIONS") {
20+
return new Response(null, { status: 200, headers: CORS_HEADERS });
21+
}
22+
23+
if (request.method !== "POST") {
24+
return new Response("Method not allowed", { status: 405, headers: CORS_HEADERS });
25+
}
26+
27+
const url = new URL(request.url);
28+
const targetUrl = TARGET_BASE + url.pathname + url.search;
29+
30+
// Forward headers (pass Authorization through if present)
31+
const forwardHeaders = new Headers();
32+
forwardHeaders.set("Content-Type", "application/json");
33+
forwardHeaders.set("Accept", "text/event-stream, application/json");
34+
const auth = request.headers.get("Authorization");
35+
if (auth) forwardHeaders.set("Authorization", auth);
36+
37+
let body: string;
38+
try {
39+
body = await request.text();
40+
} catch (e) {
41+
const msg = e instanceof Error ? e.message : String(e);
42+
return new Response(
43+
JSON.stringify({ error: { message: "Failed to read request body: " + msg, type: "proxy_error" } }),
44+
{ status: 400, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } }
45+
);
46+
}
47+
48+
let upstreamResponse: Response;
49+
try {
50+
upstreamResponse = await fetch(targetUrl, {
51+
method: "POST",
52+
headers: forwardHeaders,
53+
body,
54+
});
55+
} catch (e) {
56+
const msg = e instanceof Error ? e.message : String(e);
57+
return new Response(
58+
JSON.stringify({ error: { message: "Proxy could not reach opencode.ai: " + msg, type: "proxy_error" } }),
59+
{ status: 502, headers: { ...CORS_HEADERS, "Content-Type": "application/json" } }
60+
);
61+
}
62+
63+
// Build response headers: start from upstream, overlay CORS headers
64+
const responseHeaders = new Headers();
65+
// Copy safe upstream headers
66+
for (const [k, v] of upstreamResponse.headers.entries()) {
67+
const kl = k.toLowerCase();
68+
if (kl === "content-type" || kl === "x-request-id" || kl === "transfer-encoding") {
69+
responseHeaders.set(k, v);
70+
}
71+
}
72+
// Always set CORS headers (overrides anything upstream sent)
73+
for (const [k, v] of Object.entries(CORS_HEADERS)) {
74+
responseHeaders.set(k, v);
75+
}
76+
77+
return new Response(upstreamResponse.body, {
78+
status: upstreamResponse.status,
79+
headers: responseHeaders,
80+
});
81+
});

0 commit comments

Comments
 (0)