Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/go/v1/chat/completions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseOpenAiVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "oa-compat",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/go/v1/messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseAnthropicVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "anthropic",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/go/v1/responses.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseOpenAiVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "openai",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/util/cors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
}
15 changes: 11 additions & 4 deletions packages/console/app/src/routes/zen/util/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
import { logger } from "./logger"
import { corsHeaders } from "./cors"
import {
AuthError,
CreditsError,
Expand Down Expand Up @@ -304,6 +305,9 @@ export async function handler(
resHeaders.set(k, v)
}
}
for (const [k, v] of Object.entries(corsHeaders)) {
resHeaders.set(k, v)
}
logger.debug("STATUS: " + res.status + " " + res.statusText)

// Handle non-streaming response
Expand Down Expand Up @@ -450,7 +454,7 @@ export async function handler(
// metric and 500 and return a quiet client-closed response.
if (input.request.signal.aborted || error?.name === "AbortError") {
logger.debug("REQUEST ABORTED BY CALLER")
return new Response(null, { status: 499 })
return new Response(null, { status: 499, headers: corsHeaders })
}

logger.metric({
Expand All @@ -472,7 +476,7 @@ export async function handler(
type: "error",
error: { type: error.constructor.name, message: error.message },
}),
{ status: 403 },
{ status: 403, headers: corsHeaders },
)

// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
Expand All @@ -488,7 +492,7 @@ export async function handler(
type: "error",
error: { type: error.constructor.name, message: error.message },
}),
{ status: 401 },
{ status: 401, headers: corsHeaders },
)

if (
Expand All @@ -498,6 +502,9 @@ export async function handler(
error instanceof BlackUsageLimitError
) {
const headers = new Headers()
for (const [k, v] of Object.entries(corsHeaders)) {
headers.set(k, v)
}
if (error.retryAfter) {
headers.set("retry-after", String(error.retryAfter))
}
Expand Down Expand Up @@ -528,7 +535,7 @@ export async function handler(
message: "Internal server error",
},
}),
{ status: 500 },
{ status: 500, headers: corsHeaders },
)
}

Expand Down
9 changes: 4 additions & 5 deletions packages/console/app/src/routes/zen/util/modelsHandler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { corsHeaders } from "./cors"

export async function buildOptionsResponse() {
return new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
headers: corsHeaders,
})
}

Expand All @@ -25,6 +23,7 @@ export async function buildModelsResponse(models: string[]) {
{
headers: {
"Content-Type": "application/json",
...corsHeaders,
},
},
)
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/v1/chat/completions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseOpenAiVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "oa-compat",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/v1/messages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseAnthropicVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "anthropic",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/v1/models/[model].ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseGoogleVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "google",
Expand Down
5 changes: 5 additions & 0 deletions packages/console/app/src/routes/zen/v1/responses.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { APIEvent } from "@solidjs/start/server"
import { handler } from "~/routes/zen/util/handler"
import { corsHeaders } from "~/routes/zen/util/cors"
import { parseOpenAiVariant } from "~/routes/zen/util/variant"

export function OPTIONS(_input: APIEvent) {
return new Response(null, { status: 200, headers: corsHeaders })
}

export function POST(input: APIEvent) {
return handler(input, {
format: "openai",
Expand Down
39 changes: 39 additions & 0 deletions packages/console/app/test/cors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { describe, expect, test } from "bun:test"
import { corsHeaders } from "../src/routes/zen/util/cors"
import { buildModelsResponse, buildOptionsResponse } from "../src/routes/zen/util/modelsHandler"

describe("corsHeaders", () => {
test("allows all origins", () => {
expect(corsHeaders["Access-Control-Allow-Origin"]).toBe("*")
})

test("allows GET, POST, and OPTIONS", () => {
expect(corsHeaders["Access-Control-Allow-Methods"]).toBe("GET, POST, OPTIONS")
})

test("allows content-type and authorization headers", () => {
expect(corsHeaders["Access-Control-Allow-Headers"]).toBe("Content-Type, Authorization")
})
})

describe("buildOptionsResponse", () => {
test("returns 200 with CORS headers", async () => {
const response = await buildOptionsResponse()
expect(response.status).toBe(200)
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*")
expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, POST, OPTIONS")
expect(response.headers.get("Access-Control-Allow-Headers")).toBe("Content-Type, Authorization")
})
})

describe("buildModelsResponse", () => {
test("returns models with CORS headers", async () => {
const response = await buildModelsResponse(["model-1", "model-2"])
expect(response.status).toBe(200)
expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*")
expect(response.headers.get("Content-Type")).toBe("application/json")

const body = await response.json()
expect(body.data.map((model: { id: string }) => model.id)).toEqual(["model-1", "model-2"])
})
})
Loading