summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/routes
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-16 17:30:14 +0530
committerShoubhit Dash <[email protected]>2026-04-16 17:30:14 +0530
commit2e18a603f0ea24154908e748493fd4bfaa74fc00 (patch)
treeb723e00af2821b213573b16fa199b64babd5f1f5 /packages/console/app/src/routes
parent9819eb04614fd607cacb07d754052f1531a82331 (diff)
parent7341718f9234b0cf3a8758c87e91d2006b71bff6 (diff)
downloadopencode-2e18a603f0ea24154908e748493fd4bfaa74fc00.tar.gz
opencode-2e18a603f0ea24154908e748493fd4bfaa74fc00.zip
merge dev
Diffstat (limited to 'packages/console/app/src/routes')
-rw-r--r--packages/console/app/src/routes/auth/status.ts2
-rw-r--r--packages/console/app/src/routes/bench/[id].tsx2
-rw-r--r--packages/console/app/src/routes/black/subscribe/[plan].tsx2
-rw-r--r--packages/console/app/src/routes/debug/index.ts2
-rw-r--r--packages/console/app/src/routes/download/[channel]/[platform].ts15
-rw-r--r--packages/console/app/src/routes/download/index.css3
-rw-r--r--packages/console/app/src/routes/download/index.tsx2
-rw-r--r--packages/console/app/src/routes/go/index.css24
-rw-r--r--packages/console/app/src/routes/go/index.tsx61
-rw-r--r--packages/console/app/src/routes/index.tsx7
-rw-r--r--packages/console/app/src/routes/temp.tsx2
-rw-r--r--packages/console/app/src/routes/user-menu.tsx2
-rw-r--r--packages/console/app/src/routes/workspace-picker.tsx2
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx4
-rw-r--r--packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx24
-rw-r--r--packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx6
-rw-r--r--packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx8
-rw-r--r--packages/console/app/src/routes/workspace/[id]/members/member-section.tsx22
-rw-r--r--packages/console/app/src/routes/workspace/[id]/model-section.tsx8
-rw-r--r--packages/console/app/src/routes/workspace/[id]/provider-section.tsx17
-rw-r--r--packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx4
-rw-r--r--packages/console/app/src/routes/zen/index.tsx2
-rw-r--r--packages/console/app/src/routes/zen/util/dataDumper.ts4
-rw-r--r--packages/console/app/src/routes/zen/util/error.ts1
-rw-r--r--packages/console/app/src/routes/zen/util/handler.ts64
-rw-r--r--packages/console/app/src/routes/zen/util/ipRateLimiter.ts (renamed from packages/console/app/src/routes/zen/util/rateLimiter.ts)9
-rw-r--r--packages/console/app/src/routes/zen/util/keyRateLimiter.ts39
-rw-r--r--packages/console/app/src/routes/zen/util/provider/anthropic.ts2
-rw-r--r--packages/console/app/src/routes/zen/util/provider/google.ts2
-rw-r--r--packages/console/app/src/routes/zen/util/provider/openai-compatible.ts15
-rw-r--r--packages/console/app/src/routes/zen/util/provider/openai.ts2
-rw-r--r--packages/console/app/src/routes/zen/v1/models.ts2
-rw-r--r--packages/console/app/src/routes/zen/v1/models/[model].ts4
34 files changed, 242 insertions, 127 deletions
diff --git a/packages/console/app/src/routes/auth/status.ts b/packages/console/app/src/routes/auth/status.ts
index 215cae698..ed522d740 100644
--- a/packages/console/app/src/routes/auth/status.ts
+++ b/packages/console/app/src/routes/auth/status.ts
@@ -1,7 +1,7 @@
import { APIEvent } from "@solidjs/start"
import { useAuthSession } from "~/context/auth"
-export async function GET(input: APIEvent) {
+export async function GET(_input: APIEvent) {
const session = await useAuthSession()
return Response.json(session.data)
}
diff --git a/packages/console/app/src/routes/bench/[id].tsx b/packages/console/app/src/routes/bench/[id].tsx
index dd96bcbbc..c6d10826b 100644
--- a/packages/console/app/src/routes/bench/[id].tsx
+++ b/packages/console/app/src/routes/bench/[id].tsx
@@ -1,7 +1,7 @@
import { Title } from "@solidjs/meta"
import { createAsync, query, useParams } from "@solidjs/router"
import { createSignal, For, Show } from "solid-js"
-import { Database, desc, eq } from "@opencode-ai/console-core/drizzle/index.js"
+import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
import { BenchmarkTable } from "@opencode-ai/console-core/schema/benchmark.sql.js"
import { useI18n } from "~/context/i18n"
diff --git a/packages/console/app/src/routes/black/subscribe/[plan].tsx b/packages/console/app/src/routes/black/subscribe/[plan].tsx
index 19b56eabe..52e640876 100644
--- a/packages/console/app/src/routes/black/subscribe/[plan].tsx
+++ b/packages/console/app/src/routes/black/subscribe/[plan].tsx
@@ -298,7 +298,7 @@ export default function BlackSubscribe() {
// Resolve stripe promise once
createEffect(() => {
- stripePromise.then((s) => {
+ void stripePromise.then((s) => {
if (s) setStripe(s)
})
})
diff --git a/packages/console/app/src/routes/debug/index.ts b/packages/console/app/src/routes/debug/index.ts
index 2bdd269e7..4bfb63394 100644
--- a/packages/console/app/src/routes/debug/index.ts
+++ b/packages/console/app/src/routes/debug/index.ts
@@ -3,7 +3,7 @@ import { json } from "@solidjs/router"
import { Database } from "@opencode-ai/console-core/drizzle/index.js"
import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
-export async function GET(evt: APIEvent) {
+export async function GET(_evt: APIEvent) {
return json({
data: await Database.use(async (tx) => {
const result = await tx.$count(UserTable)
diff --git a/packages/console/app/src/routes/download/[channel]/[platform].ts b/packages/console/app/src/routes/download/[channel]/[platform].ts
index e9b3f23e7..082dfe707 100644
--- a/packages/console/app/src/routes/download/[channel]/[platform].ts
+++ b/packages/console/app/src/routes/download/[channel]/[platform].ts
@@ -1,7 +1,7 @@
import type { APIEvent } from "@solidjs/start"
import type { DownloadPlatform } from "../types"
-const assetNames: Record<string, string> = {
+const prodAssetNames: Record<string, string> = {
"darwin-aarch64-dmg": "opencode-desktop-darwin-aarch64.dmg",
"darwin-x64-dmg": "opencode-desktop-darwin-x64.dmg",
"windows-x64-nsis": "opencode-desktop-windows-x64.exe",
@@ -10,6 +10,15 @@ const assetNames: Record<string, string> = {
"linux-x64-rpm": "opencode-desktop-linux-x86_64.rpm",
} satisfies Record<DownloadPlatform, string>
+const betaAssetNames: Record<string, string> = {
+ "darwin-aarch64-dmg": "opencode-electron-mac-arm64.dmg",
+ "darwin-x64-dmg": "opencode-electron-mac-x64.dmg",
+ "windows-x64-nsis": "opencode-electron-win-x64.exe",
+ "linux-x64-deb": "opencode-electron-linux-amd64.deb",
+ "linux-x64-appimage": "opencode-electron-linux-x86_64.AppImage",
+ "linux-x64-rpm": "opencode-electron-linux-x86_64.rpm",
+} satisfies Record<DownloadPlatform, string>
+
// Doing this on the server lets us preserve the original name for platforms we don't care to rename for
const downloadNames: Record<string, string> = {
"darwin-aarch64-dmg": "OpenCode Desktop.dmg",
@@ -18,7 +27,7 @@ const downloadNames: Record<string, string> = {
} satisfies { [K in DownloadPlatform]?: string }
export async function GET({ params: { platform, channel } }: APIEvent) {
- const assetName = assetNames[platform]
+ const assetName = channel === "stable" ? prodAssetNames[platform] : betaAssetNames[platform]
if (!assetName) return new Response(null, { status: 404 })
const resp = await fetch(
@@ -37,5 +46,5 @@ export async function GET({ params: { platform, channel } }: APIEvent) {
const headers = new Headers(resp.headers)
if (downloadName) headers.set("content-disposition", `attachment; filename="${downloadName}"`)
- return new Response(resp.body, { ...resp, headers })
+ return new Response(resp.body, { status: resp.status, statusText: resp.statusText, headers })
}
diff --git a/packages/console/app/src/routes/download/index.css b/packages/console/app/src/routes/download/index.css
index 705302616..b2176c34a 100644
--- a/packages/console/app/src/routes/download/index.css
+++ b/packages/console/app/src/routes/download/index.css
@@ -316,7 +316,8 @@
/* Download Hero Section */
[data-component="download-hero"] {
- display: grid;
+ /* display: grid; */
+ display: none;
grid-template-columns: 260px 1fr;
gap: 4rem;
padding-bottom: 2rem;
diff --git a/packages/console/app/src/routes/download/index.tsx b/packages/console/app/src/routes/download/index.tsx
index 0278d8622..b5c202a5e 100644
--- a/packages/console/app/src/routes/download/index.tsx
+++ b/packages/console/app/src/routes/download/index.tsx
@@ -77,7 +77,7 @@ export default function Download() {
const handleCopyClick = (command: string) => (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
- navigator.clipboard.writeText(command)
+ void navigator.clipboard.writeText(command)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
diff --git a/packages/console/app/src/routes/go/index.css b/packages/console/app/src/routes/go/index.css
index 68f27a1bb..cd4406f25 100644
--- a/packages/console/app/src/routes/go/index.css
+++ b/packages/console/app/src/routes/go/index.css
@@ -1017,6 +1017,30 @@ body {
[data-slot="faq-answer"] {
margin-left: 40px;
margin-bottom: 32px;
+
+ [data-slot="faq-models"] {
+ margin-top: 16px;
+ overflow-x: auto;
+
+ table {
+ width: 100%;
+ min-width: 28rem;
+ border-collapse: collapse;
+ }
+
+ th,
+ td {
+ padding: 12px 16px;
+ text-align: left;
+ white-space: nowrap;
+ }
+
+ th {
+ background: var(--color-background-weak);
+ color: var(--color-text-strong);
+ font-weight: 500;
+ }
+ }
}
}
diff --git a/packages/console/app/src/routes/go/index.tsx b/packages/console/app/src/routes/go/index.tsx
index e4628fe11..82b3caf66 100644
--- a/packages/console/app/src/routes/go/index.tsx
+++ b/packages/console/app/src/routes/go/index.tsx
@@ -1,5 +1,5 @@
import "./index.css"
-import { createAsync, query, redirect } from "@solidjs/router"
+import { createAsync, query } from "@solidjs/router"
import { Title, Meta } from "@solidjs/meta"
import { For, createMemo, createSignal, onCleanup, onMount } from "solid-js"
//import { HttpHeader } from "@solidjs/start"
@@ -12,7 +12,7 @@ import { Footer } from "~/component/footer"
import { Header } from "~/component/header"
import { config } from "~/config"
import { getLastSeenWorkspaceID } from "../workspace/common"
-import { IconMiniMax, IconMiMo, IconZai } from "~/component/icon"
+import { IconMiniMax, IconMiMo, IconZai, IconAlibaba } from "~/component/icon"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
import { LocaleLinks } from "~/component/locale-links"
@@ -22,6 +22,18 @@ const checkLoggedIn = query(async () => {
return await getLastSeenWorkspaceID().catch(() => undefined)
}, "checkLoggedIn.get")
+const models = [
+ { name: "GLM-5.1", provider: "DeepInfra, Z.ai" },
+ { name: "GLM-5", provider: "DeepInfra, Z.ai" },
+ { name: "Kimi K2.5", provider: "Moonshot AI" },
+ { name: "MiMo-V2-Pro", provider: "Xiaomi MiMo" },
+ { name: "MiMo-V2-Omni", provider: "Xiaomi MiMo" },
+ { name: "Qwen3.5 Plus", provider: "Alibaba Cloud Model Studio" },
+ { name: "Qwen3.6 Plus", provider: "Alibaba Cloud Model Studio" },
+ { name: "MiniMax M2.7", provider: "MiniMax" },
+ { name: "MiniMax M2.5", provider: "MiniMax" },
+]
+
function LimitsGraph(props: { href: string }) {
let root!: HTMLElement
const [visible, setVisible] = createSignal(false)
@@ -44,13 +56,13 @@ function LimitsGraph(props: { href: string }) {
})
const free = 200
- const models = [
+ const graph = [
{ id: "glm-5.1", name: "GLM-5.1", req: 880, d: "100ms" },
- { id: "glm-5", name: "GLM-5", req: 1150, d: "120ms" },
{ id: "mimo-v2-pro", name: "MiMo-V2-Pro", req: 1290, d: "150ms" },
{ id: "kimi", name: "Kimi K2.5", req: 1850, d: "240ms" },
- { id: "minimax-m2.7", name: "MiniMax M2.7", req: 14000, d: "330ms" },
- { id: "minimax-m2.5", name: "MiniMax M2.5", req: 20000, d: "360ms" },
+ { id: "qwen3.6-plus", name: "Qwen3.6 Plus", req: 3300, d: "280ms" },
+ { id: "minimax-m2.7", name: "MiniMax M2.7", req: 3400, d: "300ms" },
+ { id: "qwen3.5-plus", name: "Qwen3.5 Plus", req: 10200, d: "360ms" },
]
const w = 720
@@ -62,7 +74,7 @@ function LimitsGraph(props: { href: string }) {
const plot = w - left - right
const ratio = (n: number) => n / free
- const rmax = Math.max(1, ...models.map((m) => ratio(m.req)))
+ const rmax = Math.max(1, ...graph.map((m) => ratio(m.req)))
const log = (n: number) => Math.log10(Math.max(n, 1))
const base = 24
const p = 1.8
@@ -93,7 +105,7 @@ function LimitsGraph(props: { href: string }) {
const sep = bh + 40
const fy = top + 22
const gy = (i: number) => fy + sep + step * i
- const my = models.length < 2 ? gy(0) : (gy(0) + gy(models.length - 1)) / 2
+ const my = graph.length < 2 ? gy(0) : (gy(0) + gy(graph.length - 1)) / 2
const px = (n: number) => `${(n / w) * 100}%`
const py = (n: number) => `${(n / h) * 100}%`
const lx = px(left - 16)
@@ -132,7 +144,7 @@ function LimitsGraph(props: { href: string }) {
<rect x={left} y={fy - bh / 2} width={Math.max(0, x(1) - left)} height={bh} data-bar data-kind="free" />
</g>
- <For each={models}>
+ <For each={graph}>
{(m, i) => (
<g style={{ "--d": m.d } as any}>
<rect
@@ -174,7 +186,7 @@ function LimitsGraph(props: { href: string }) {
<span data-value>{free.toLocaleString()}</span>
<span data-name>{i18n.t("go.graph.freePill")}</span>
</span>
- <For each={models}>
+ <For each={graph}>
{(m, i) => (
<span
data-item
@@ -301,6 +313,9 @@ export default function Home() {
<IconZai width="24" height="24" />
</div>
<div>
+ <IconAlibaba width="24" height="24" />
+ </div>
+ <div>
<IconMiMo width="24" height="24" />
</div>
{/*
@@ -420,7 +435,31 @@ export default function Home() {
<Faq question={i18n.t("go.faq.q1")}>{i18n.t("go.faq.a1")}</Faq>
</li>
<li>
- <Faq question={i18n.t("go.faq.q2")}>{i18n.t("go.faq.a2")}</Faq>
+ <Faq question={i18n.t("go.faq.q2")}>
+ {i18n.t("go.faq.a2")}
+ {language.locale() === "en" && (
+ <div data-slot="faq-models">
+ <table>
+ <thead>
+ <tr>
+ <th>{i18n.t("workspace.models.table.model")}</th>
+ <th>{i18n.t("workspace.providers.table.provider")}</th>
+ </tr>
+ </thead>
+ <tbody>
+ <For each={models}>
+ {(m) => (
+ <tr>
+ <td>{m.name}</td>
+ <td>{m.provider}</td>
+ </tr>
+ )}
+ </For>
+ </tbody>
+ </table>
+ </div>
+ )}
+ </Faq>
</li>
<li>
<Faq question={i18n.t("go.faq.q9")}>{i18n.t("go.faq.a9")}</Faq>
diff --git a/packages/console/app/src/routes/index.tsx b/packages/console/app/src/routes/index.tsx
index e47134d2b..c046a56a2 100644
--- a/packages/console/app/src/routes/index.tsx
+++ b/packages/console/app/src/routes/index.tsx
@@ -12,7 +12,6 @@ import { Header } from "~/component/header"
import { Footer } from "~/component/footer"
import { Legal } from "~/component/legal"
import { github } from "~/lib/github"
-import { createMemo } from "solid-js"
import { config } from "~/config"
import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
@@ -30,14 +29,12 @@ function CopyStatus() {
export default function Home() {
const i18n = useI18n()
const language = useLanguage()
- const githubData = createAsync(() => github())
- const release = createMemo(() => githubData()?.release)
-
+ const _githubData = createAsync(() => github())
const handleCopyClick = (event: Event) => {
const button = event.currentTarget as HTMLButtonElement
const text = button.textContent
if (text) {
- navigator.clipboard.writeText(text)
+ void navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
diff --git a/packages/console/app/src/routes/temp.tsx b/packages/console/app/src/routes/temp.tsx
index 4eed47857..6bbabc9ea 100644
--- a/packages/console/app/src/routes/temp.tsx
+++ b/packages/console/app/src/routes/temp.tsx
@@ -27,7 +27,7 @@ export default function Home() {
const callback = () => {
const text = button.textContent
if (text) {
- navigator.clipboard.writeText(text)
+ void navigator.clipboard.writeText(text)
button.setAttribute("data-copied", "")
setTimeout(() => {
button.removeAttribute("data-copied")
diff --git a/packages/console/app/src/routes/user-menu.tsx b/packages/console/app/src/routes/user-menu.tsx
index fa1c1f60b..7b305d8ea 100644
--- a/packages/console/app/src/routes/user-menu.tsx
+++ b/packages/console/app/src/routes/user-menu.tsx
@@ -6,7 +6,7 @@ import { useI18n } from "~/context/i18n"
import { useLanguage } from "~/context/language"
import "./user-menu.css"
-const logout = action(async () => {
+const _logout = action(async () => {
"use server"
const auth = await useAuthSession()
const event = getRequestEvent()
diff --git a/packages/console/app/src/routes/workspace-picker.tsx b/packages/console/app/src/routes/workspace-picker.tsx
index ffec2f3be..8778abefd 100644
--- a/packages/console/app/src/routes/workspace-picker.tsx
+++ b/packages/console/app/src/routes/workspace-picker.tsx
@@ -1,5 +1,5 @@
import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router"
-import { For, Show, createEffect } from "solid-js"
+import { For, createEffect } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Actor } from "@opencode-ai/console-core/actor.js"
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
index b8f089864..5b4389466 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
@@ -116,9 +116,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setUseBalance = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const useBalance = form.get("useBalance")?.toString() === "true"
+ const useBalance = (form.get("useBalance") as string | null) === "true"
return json(
await withActor(async () => {
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
index ef54b8409..7da1de238 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
@@ -10,11 +10,11 @@ import { formError, localizeError } from "~/lib/form-error"
const setMonthlyLimit = action(async (form: FormData) => {
"use server"
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
if (!limit) return { error: formError.limitRequired }
const numericLimit = parseInt(limit)
if (numericLimit < 0) return { error: formError.monthlyLimitInvalid }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
index 90c9d7a2e..c9a72c087 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
@@ -12,7 +12,7 @@ import { formError, formErrorReloadAmountMin, formErrorReloadTriggerMin, localiz
const reload = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Billing.reload(), workspaceID), {
revalidate: queryBillingInfo.key,
@@ -21,11 +21,11 @@ const reload = action(async (form: FormData) => {
const setReload = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const reloadValue = form.get("reload")?.toString() === "true"
- const amountStr = form.get("reloadAmount")?.toString()
- const triggerStr = form.get("reloadTrigger")?.toString()
+ const reloadValue = (form.get("reload") as string | null) === "true"
+ const amountStr = form.get("reloadAmount") as string | null
+ const triggerStr = form.get("reloadTrigger") as string | null
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
@@ -90,9 +90,9 @@ export function ReloadSection() {
}
const info = billingInfo()!
setStore("show", true)
- setStore("reload", info.reload ? true : true)
- setStore("reloadAmount", info.reloadAmount.toString())
- setStore("reloadTrigger", info.reloadTrigger.toString())
+ setStore("reload", true)
+ setStore("reloadAmount", String(info.reloadAmount))
+ setStore("reloadTrigger", String(info.reloadTrigger))
}
function hide() {
@@ -152,11 +152,11 @@ export function ReloadSection() {
data-component="input"
name="reloadAmount"
type="number"
- min={billingInfo()?.reloadAmountMin.toString()}
+ min={String(billingInfo()?.reloadAmountMin ?? "")}
step="1"
value={store.reloadAmount}
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
- placeholder={billingInfo()?.reloadAmount.toString()}
+ placeholder={String(billingInfo()?.reloadAmount ?? "")}
disabled={!store.reload}
/>
</div>
@@ -166,11 +166,11 @@ export function ReloadSection() {
data-component="input"
name="reloadTrigger"
type="number"
- min={billingInfo()?.reloadTriggerMin.toString()}
+ min={String(billingInfo()?.reloadTriggerMin ?? "")}
step="1"
value={store.reloadTrigger}
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
- placeholder={billingInfo()?.reloadTrigger.toString()}
+ placeholder={String(billingInfo()?.reloadTrigger ?? "")}
disabled={!store.reload}
/>
</div>
diff --git a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
index 70d6aea7a..d0f812182 100644
--- a/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/go/lite-section.tsx
@@ -120,9 +120,9 @@ const createSessionUrl = action(async (workspaceID: string, returnUrl: string) =
const setLiteUseBalance = action(async (form: FormData) => {
"use server"
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const useBalance = form.get("useBalance")?.toString() === "true"
+ const useBalance = (form.get("useBalance") as string | null) === "true"
return json(
await withActor(async () => {
@@ -292,6 +292,8 @@ export function LiteSection() {
<li>Mimo-V2-Omni</li>
<li>MiniMax M2.5</li>
<li>MiniMax M2.7</li>
+ <li>Qwen3.5 Plus</li>
+ <li>Qwen3.6 Plus</li>
</ul>
<p data-slot="promo-description">{i18n.t("workspace.lite.promo.footer")}</p>
<div data-slot="subscribe-actions">
diff --git a/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx b/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
index 837ab743a..cb273a422 100644
--- a/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/keys/key-section.tsx
@@ -12,18 +12,18 @@ import { formError, localizeError } from "~/lib/form-error"
const removeKey = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Key.remove({ id }), workspaceID), { revalidate: listKeys.key })
}, "key.remove")
const createKey = action(async (form: FormData) => {
"use server"
- const name = form.get("name")?.toString().trim()
+ const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.nameRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
index 5a440632f..00edb400c 100644
--- a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx
@@ -24,13 +24,13 @@ const listMembers = query(async (workspaceID: string) => {
const inviteMember = action(async (form: FormData) => {
"use server"
- const email = form.get("email")?.toString().trim()
+ const email = (form.get("email") as string | null)?.trim()
if (!email) return { error: formError.emailRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const role = form.get("role")?.toString() as (typeof UserRole)[number]
+ const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired }
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
return json(
@@ -47,9 +47,9 @@ const inviteMember = action(async (form: FormData) => {
const removeMember = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
@@ -66,13 +66,13 @@ const removeMember = action(async (form: FormData) => {
const updateMember = action(async (form: FormData) => {
"use server"
- const id = form.get("id")?.toString()
+ const id = form.get("id") as string | null
if (!id) return { error: formError.idRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const role = form.get("role")?.toString() as (typeof UserRole)[number]
+ const role = form.get("role") as (typeof UserRole)[number] | null
if (!role) return { error: formError.roleRequired }
- const limit = form.get("limit")?.toString()
+ const limit = form.get("limit") as string | null
const monthlyLimit = limit && limit.trim() !== "" ? parseInt(limit) : null
if (monthlyLimit !== null && monthlyLimit < 0) return { error: formError.monthlyLimitInvalid }
@@ -118,7 +118,7 @@ function MemberRow(props: {
}
setStore("editing", true)
setStore("selectedRole", props.member.role)
- setStore("limit", props.member.monthlyLimit?.toString() ?? "")
+ setStore("limit", props.member.monthlyLimit != null ? String(props.member.monthlyLimit) : "")
}
function hide() {
diff --git a/packages/console/app/src/routes/workspace/[id]/model-section.tsx b/packages/console/app/src/routes/workspace/[id]/model-section.tsx
index bf19f81cd..b9cdf3bc3 100644
--- a/packages/console/app/src/routes/workspace/[id]/model-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/model-section.tsx
@@ -67,11 +67,11 @@ const getModelsInfo = query(async (workspaceID: string) => {
const updateModel = action(async (form: FormData) => {
"use server"
- const model = form.get("model")?.toString()
+ const model = form.get("model") as string | null
if (!model) return { error: formError.modelRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
- const enabled = form.get("enabled")?.toString() === "true"
+ const enabled = (form.get("enabled") as string | null) === "true"
return json(
withActor(async () => {
if (enabled) {
@@ -163,7 +163,7 @@ export function ModelSection() {
<form action={updateModel} method="post">
<input type="hidden" name="model" value={id} />
<input type="hidden" name="workspaceID" value={params.id} />
- <input type="hidden" name="enabled" value={isEnabled().toString()} />
+ <input type="hidden" name="enabled" value={String(isEnabled())} />
<label data-slot="model-toggle-label">
<input
type="checkbox"
diff --git a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
index fe04bf7c0..acdd89742 100644
--- a/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/provider-section.tsx
@@ -21,9 +21,9 @@ function maskCredentials(credentials: string) {
const removeProvider = action(async (form: FormData) => {
"use server"
- const provider = form.get("provider")?.toString()
+ const provider = form.get("provider") as string | null
if (!provider) return { error: formError.providerRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
revalidate: listProviders.key,
@@ -32,11 +32,11 @@ const removeProvider = action(async (form: FormData) => {
const saveProvider = action(async (form: FormData) => {
"use server"
- const provider = form.get("provider")?.toString()
- const credentials = form.get("credentials")?.toString()
+ const provider = form.get("provider") as string | null
+ const credentials = form.get("credentials") as string | null
if (!provider) return { error: formError.providerRequired }
if (!credentials) return { error: formError.apiKeyRequired }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
@@ -59,10 +59,13 @@ function ProviderRow(props: { provider: Provider }) {
const params = useParams()
const i18n = useI18n()
const providers = createAsync(() => listProviders(params.id!))
- const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
+ const saveSubmission = useSubmission(
+ saveProvider,
+ ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
+ )
const removeSubmission = useSubmission(
removeProvider,
- ([fd]) => fd.get("provider")?.toString() === props.provider.key,
+ ([fd]) => (fd.get("provider") as string | null) === props.provider.key,
)
const [store, setStore] = createStore({ editing: false })
diff --git a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
index 5e8331e26..a9950c41e 100644
--- a/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/settings/settings-section.tsx
@@ -30,10 +30,10 @@ const getWorkspaceInfo = query(async (workspaceID: string) => {
const updateWorkspace = action(async (form: FormData) => {
"use server"
- const name = form.get("name")?.toString().trim()
+ const name = (form.get("name") as string | null)?.trim()
if (!name) return { error: formError.workspaceNameRequired }
if (name.length > 255) return { error: formError.nameTooLong }
- const workspaceID = form.get("workspaceID")?.toString()
+ const workspaceID = form.get("workspaceID") as string | null
if (!workspaceID) return { error: formError.workspaceRequired }
return json(
await withActor(
diff --git a/packages/console/app/src/routes/zen/index.tsx b/packages/console/app/src/routes/zen/index.tsx
index 62e8f5d37..6285a0bd8 100644
--- a/packages/console/app/src/routes/zen/index.tsx
+++ b/packages/console/app/src/routes/zen/index.tsx
@@ -1,5 +1,5 @@
import "./index.css"
-import { createAsync, query, redirect } from "@solidjs/router"
+import { createAsync, query } from "@solidjs/router"
import { Title, Meta } from "@solidjs/meta"
//import { HttpHeader } from "@solidjs/start"
import zenLogoLight from "../../asset/zen-ornate-light.svg"
diff --git a/packages/console/app/src/routes/zen/util/dataDumper.ts b/packages/console/app/src/routes/zen/util/dataDumper.ts
index b852ca0b5..bc88c3813 100644
--- a/packages/console/app/src/routes/zen/util/dataDumper.ts
+++ b/packages/console/app/src/routes/zen/util/dataDumper.ts
@@ -26,14 +26,14 @@ export function createDataDumper(sessionId: string, requestId: string, projectId
const minute = timestamp.substring(10, 12)
const second = timestamp.substring(12, 14)
- waitUntil(
+ void waitUntil(
Resource.ZenDataNew.put(
`data/${data.modelName}/${year}/${month}/${day}/${hour}/${minute}/${second}/${requestId}.json`,
JSON.stringify({ timestamp, ...data }),
),
)
- waitUntil(
+ void waitUntil(
Resource.ZenDataNew.put(
`meta/${data.modelName}/${sessionId}/${requestId}.json`,
JSON.stringify({ timestamp, ...metadata }),
diff --git a/packages/console/app/src/routes/zen/util/error.ts b/packages/console/app/src/routes/zen/util/error.ts
index a3a93d2ef..b2a1d30d0 100644
--- a/packages/console/app/src/routes/zen/util/error.ts
+++ b/packages/console/app/src/routes/zen/util/error.ts
@@ -11,5 +11,6 @@ class LimitError extends Error {
this.retryAfter = retryAfter
}
}
+export class RateLimitError extends LimitError {}
export class FreeUsageLimitError extends LimitError {}
export class SubscriptionUsageLimitError extends LimitError {}
diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts
index 3e191918e..d1c5985a8 100644
--- a/packages/console/app/src/routes/zen/util/handler.ts
+++ b/packages/console/app/src/routes/zen/util/handler.ts
@@ -21,6 +21,7 @@ import {
MonthlyLimitError,
UserLimitError,
ModelError,
+ RateLimitError,
FreeUsageLimitError,
SubscriptionUsageLimitError,
} from "./error"
@@ -35,7 +36,8 @@ import { anthropicHelper } from "./provider/anthropic"
import { googleHelper } from "./provider/google"
import { openaiHelper } from "./provider/openai"
import { oaCompatHelper } from "./provider/openai-compatible"
-import { createRateLimiter } from "./rateLimiter"
+import { createRateLimiter as createIpRateLimiter } from "./ipRateLimiter"
+import { createRateLimiter as createKeyRateLimiter } from "./keyRateLimiter"
import { createDataDumper } from "./dataDumper"
import { createTrialLimiter } from "./trialLimiter"
import { createStickyTracker } from "./stickyProviderTracker"
@@ -92,6 +94,8 @@ export async function handler(
const isStream = opts.parseIsStream(url, body)
const rawIp = input.request.headers.get("x-real-ip") ?? ""
const ip = rawIp.includes(":") ? rawIp.split(":").slice(0, 4).join(":") : rawIp
+ const rawZenApiKey = opts.parseApiKey(input.request.headers)
+ const zenApiKey = rawZenApiKey === "public" ? undefined : rawZenApiKey
const sessionId = input.request.headers.get("x-opencode-session") ?? ""
const requestId = input.request.headers.get("x-opencode-request") ?? ""
const projectId = input.request.headers.get("x-opencode-project") ?? ""
@@ -106,19 +110,15 @@ export async function handler(
const zenData = ZenData.list(opts.modelList)
const modelInfo = validateModel(zenData, model)
const dataDumper = createDataDumper(sessionId, requestId, projectId)
- const trialLimiter = createTrialLimiter(modelInfo.trialProviders, ip)
+ const trialLimiter = createTrialLimiter(modelInfo.trialProvider, ip)
const trialProviders = await trialLimiter?.check()
- const rateLimiter = createRateLimiter(
- modelInfo.id,
- modelInfo.allowAnonymous,
- modelInfo.rateLimit,
- ip,
- input.request,
- )
+ const rateLimiter = modelInfo.allowAnonymous
+ ? createIpRateLimiter(modelInfo.id, modelInfo.rateLimit, ip, input.request)
+ : createKeyRateLimiter(modelInfo.id, zenApiKey, input.request)
await rateLimiter?.check()
const stickyTracker = createStickyTracker(modelInfo.stickyProvider, sessionId)
const stickyProvider = await stickyTracker?.get()
- const authInfo = await authenticate(modelInfo)
+ const authInfo = await authenticate(modelInfo, zenApiKey)
const billingSource = validateBilling(authInfo, modelInfo)
logger.metric({ source: billingSource })
@@ -144,7 +144,7 @@ export async function handler(
providerInfo.modifyBody({
...createBodyConverter(opts.format, providerInfo.format)(body),
model: providerInfo.model,
- ...(providerInfo.payloadModifier ?? {}),
+ ...providerInfo.payloadModifier,
...Object.fromEntries(
Object.entries(providerInfo.payloadMappings ?? {})
.map(([k, v]) => [k, input.request.headers.get(v)])
@@ -222,22 +222,23 @@ export async function handler(
logger.debug("STATUS: " + res.status + " " + res.statusText)
// Handle non-streaming response
- if (!isStream) {
+ if (!isStream || res.status === 429) {
const json = await res.json()
- const usageInfo = providerInfo.normalizeUsage(json.usage)
- const costInfo = calculateCost(modelInfo, usageInfo)
- await trialLimiter?.track(usageInfo)
await rateLimiter?.track()
- await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
- await reload(billingSource, authInfo, costInfo)
+ if (json.usage) {
+ const usageInfo = providerInfo.normalizeUsage(json.usage)
+ const costInfo = calculateCost(modelInfo, usageInfo)
+ await trialLimiter?.track(usageInfo)
+ await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
+ await reload(billingSource, authInfo, costInfo)
+ json.cost = calculateOccurredCost(billingSource, costInfo)
+ }
+ if (json.error?.message) {
+ json.error.message = `Error from provider${providerInfo.displayName ? ` (${providerInfo.displayName})` : ""}: ${json.error.message}`
+ }
const responseConverter = createResponseConverter(providerInfo.format, opts.format)
- const body = JSON.stringify(
- responseConverter({
- ...json,
- cost: calculateOccurredCost(billingSource, costInfo),
- }),
- )
+ const body = JSON.stringify(responseConverter(json))
logger.metric({ response_length: body.length })
logger.debug("RESPONSE: " + body)
dataDumper?.provideResponse(body)
@@ -344,7 +345,7 @@ export async function handler(
logger.metric({
"error.cause2": JSON.stringify(error.cause),
})
- } catch (e) {}
+ } catch {}
}
// Note: both top level "type" and "error.type" fields are used by the @ai-sdk/anthropic client to render the error message.
@@ -363,7 +364,11 @@ export async function handler(
{ status: 401 },
)
- if (error instanceof FreeUsageLimitError || error instanceof SubscriptionUsageLimitError) {
+ if (
+ error instanceof RateLimitError ||
+ error instanceof FreeUsageLimitError ||
+ error instanceof SubscriptionUsageLimitError
+ ) {
const headers = new Headers()
if (error.retryAfter) {
headers.set("retry-after", String(error.retryAfter))
@@ -392,7 +397,7 @@ export async function handler(
function validateModel(zenData: ZenData, reqModel: string) {
if (!(reqModel in zenData.models)) throw new ModelError(t("zen.api.error.modelNotSupported", { model: reqModel }))
- const modelId = reqModel as keyof typeof zenData.models
+ const modelId = reqModel
const modelData = Array.isArray(zenData.models[modelId])
? zenData.models[modelId].find((model) => opts.format === model.formatFilter)
: zenData.models[modelId]
@@ -492,9 +497,8 @@ export async function handler(
}
}
- async function authenticate(modelInfo: ModelInfo) {
- const apiKey = opts.parseApiKey(input.request.headers)
- if (!apiKey || apiKey === "public") {
+ async function authenticate(modelInfo: ModelInfo, zenApiKey?: string) {
+ if (!zenApiKey) {
if (modelInfo.allowAnonymous) return
throw new AuthError(t("zen.api.error.missingApiKey"))
}
@@ -573,7 +577,7 @@ export async function handler(
isNull(LiteTable.timeDeleted),
),
)
- .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
+ .where(and(eq(KeyTable.key, zenApiKey), isNull(KeyTable.timeDeleted)))
.then((rows) => rows[0]),
)
diff --git a/packages/console/app/src/routes/zen/util/rateLimiter.ts b/packages/console/app/src/routes/zen/util/ipRateLimiter.ts
index 160633981..d22ab4ae2 100644
--- a/packages/console/app/src/routes/zen/util/rateLimiter.ts
+++ b/packages/console/app/src/routes/zen/util/ipRateLimiter.ts
@@ -6,14 +6,7 @@ import { i18n } from "~/i18n"
import { localeFromRequest } from "~/lib/language"
import { Subscription } from "@opencode-ai/console-core/subscription.js"
-export function createRateLimiter(
- modelId: string,
- allowAnonymous: boolean | undefined,
- rateLimit: number | undefined,
- rawIp: string,
- request: Request,
-) {
- if (!allowAnonymous) return
+export function createRateLimiter(modelId: string, rateLimit: number | undefined, rawIp: string, request: Request) {
const dict = i18n(localeFromRequest(request))
const limits = Subscription.getFreeLimits()
diff --git a/packages/console/app/src/routes/zen/util/keyRateLimiter.ts b/packages/console/app/src/routes/zen/util/keyRateLimiter.ts
new file mode 100644
index 000000000..e3e0fb18f
--- /dev/null
+++ b/packages/console/app/src/routes/zen/util/keyRateLimiter.ts
@@ -0,0 +1,39 @@
+import { Database, eq, and, sql } from "@opencode-ai/console-core/drizzle/index.js"
+import { KeyRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
+import { RateLimitError } from "./error"
+import { i18n } from "~/i18n"
+import { localeFromRequest } from "~/lib/language"
+
+export function createRateLimiter(modelId: string, zenApiKey: string | undefined, request: Request) {
+ if (!zenApiKey) return
+ const dict = i18n(localeFromRequest(request))
+
+ const LIMIT = 100
+ const yyyyMMddHHmm = new Date(Date.now())
+ .toISOString()
+ .replace(/[^0-9]/g, "")
+ .substring(0, 12)
+ const interval = `${modelId.substring(0, 27)}-${yyyyMMddHHmm}`
+
+ return {
+ check: async () => {
+ const rows = await Database.use((tx) =>
+ tx
+ .select({ interval: KeyRateLimitTable.interval, count: KeyRateLimitTable.count })
+ .from(KeyRateLimitTable)
+ .where(and(eq(KeyRateLimitTable.key, zenApiKey), eq(KeyRateLimitTable.interval, interval))),
+ ).then((rows) => rows[0])
+ const count = rows?.count ?? 0
+
+ if (count >= LIMIT) throw new RateLimitError(dict["zen.api.error.rateLimitExceeded"], 60)
+ },
+ track: async () => {
+ await Database.use((tx) =>
+ tx
+ .insert(KeyRateLimitTable)
+ .values({ key: zenApiKey, interval, count: 1 })
+ .onDuplicateKeyUpdate({ set: { count: sql`${KeyRateLimitTable.count} + 1` } }),
+ )
+ },
+ }
+}
diff --git a/packages/console/app/src/routes/zen/util/provider/anthropic.ts b/packages/console/app/src/routes/zen/util/provider/anthropic.ts
index b63be8688..0f6f11da7 100644
--- a/packages/console/app/src/routes/zen/util/provider/anthropic.ts
+++ b/packages/console/app/src/routes/zen/util/provider/anthropic.ts
@@ -153,7 +153,7 @@ export const anthropicHelper: ProviderHelper = ({ reqModel, providerModel }) =>
let json
try {
json = JSON.parse(data.slice(6))
- } catch (e) {
+ } catch {
return
}
diff --git a/packages/console/app/src/routes/zen/util/provider/google.ts b/packages/console/app/src/routes/zen/util/provider/google.ts
index f6f7d6e19..ef7937c35 100644
--- a/packages/console/app/src/routes/zen/util/provider/google.ts
+++ b/packages/console/app/src/routes/zen/util/provider/google.ts
@@ -48,7 +48,7 @@ export const googleHelper: ProviderHelper = ({ providerModel }) => ({
let json
try {
json = JSON.parse(chunk.slice(6)) as { usageMetadata?: Usage }
- } catch (e) {
+ } catch {
return
}
diff --git a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts
index bdc12ba8b..97b0abc64 100644
--- a/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts
+++ b/packages/console/app/src/routes/zen/util/provider/openai-compatible.ts
@@ -6,12 +6,14 @@ type Usage = {
total_tokens?: number
// used by moonshot
cached_tokens?: number
- // used by xai
+ // used by xai & alibaba
prompt_tokens_details?: {
text_tokens?: number
audio_tokens?: number
image_tokens?: number
cached_tokens?: number
+ // used by alibaba
+ cache_creation_input_tokens?: number
}
completion_tokens_details?: {
reasoning_tokens?: number
@@ -28,7 +30,7 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
headers.set("authorization", `Bearer ${apiKey}`)
headers.set("x-session-affinity", headers.get("x-opencode-session") ?? "")
},
- modifyBody: (body: Record<string, any>, workspaceID?: string) => {
+ modifyBody: (body: Record<string, any>, _workspaceID?: string) => {
return {
...body,
...(body.stream ? { stream_options: { include_usage: true } } : {}),
@@ -47,7 +49,7 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
let json
try {
json = JSON.parse(chunk.slice(6)) as { usage?: Usage }
- } catch (e) {
+ } catch {
return
}
@@ -62,6 +64,7 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
const outputTokens = usage.completion_tokens ?? 0
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? undefined
let cacheReadTokens = usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? undefined
+ const cacheWriteTokens = usage.prompt_tokens_details?.cache_creation_input_tokens ?? undefined
if (adjustCacheUsage && !cacheReadTokens) {
cacheReadTokens = Math.floor(inputTokens * 0.9)
@@ -72,7 +75,7 @@ export const oaCompatHelper: ProviderHelper = ({ adjustCacheUsage, safetyIdentif
outputTokens,
reasoningTokens,
cacheReadTokens,
- cacheWrite5mTokens: undefined,
+ cacheWrite5mTokens: cacheWriteTokens,
cacheWrite1hTokens: undefined,
}
},
@@ -286,7 +289,7 @@ export function fromOaCompatibleResponse(resp: any): CommonResponse {
index: 0,
message: {
role: "assistant" as const,
- ...(content.length > 0 && content.some((c) => c.type === "text")
+ ...(content.some((c) => c.type === "text")
? {
content: content
.filter((c) => c.type === "text")
@@ -294,7 +297,7 @@ export function fromOaCompatibleResponse(resp: any): CommonResponse {
.join(""),
}
: {}),
- ...(content.length > 0 && content.some((c) => c.type === "tool_use")
+ ...(content.some((c) => c.type === "tool_use")
? {
tool_calls: content
.filter((c) => c.type === "tool_use")
diff --git a/packages/console/app/src/routes/zen/util/provider/openai.ts b/packages/console/app/src/routes/zen/util/provider/openai.ts
index 3c5831a9a..bee1e01ec 100644
--- a/packages/console/app/src/routes/zen/util/provider/openai.ts
+++ b/packages/console/app/src/routes/zen/util/provider/openai.ts
@@ -36,7 +36,7 @@ export const openaiHelper: ProviderHelper = ({ workspaceID }) => ({
let json
try {
json = JSON.parse(data.slice(6)) as { response?: { usage?: Usage } }
- } catch (e) {
+ } catch {
return
}
diff --git a/packages/console/app/src/routes/zen/v1/models.ts b/packages/console/app/src/routes/zen/v1/models.ts
index d2592d20b..6b4a878fc 100644
--- a/packages/console/app/src/routes/zen/v1/models.ts
+++ b/packages/console/app/src/routes/zen/v1/models.ts
@@ -5,7 +5,7 @@ import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.j
import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
import { ZenData } from "@opencode-ai/console-core/model.js"
-export async function OPTIONS(input: APIEvent) {
+export async function OPTIONS(_input: APIEvent) {
return new Response(null, {
status: 200,
headers: {
diff --git a/packages/console/app/src/routes/zen/v1/models/[model].ts b/packages/console/app/src/routes/zen/v1/models/[model].ts
index a4edd5861..bc1168eb0 100644
--- a/packages/console/app/src/routes/zen/v1/models/[model].ts
+++ b/packages/console/app/src/routes/zen/v1/models/[model].ts
@@ -6,8 +6,8 @@ export function POST(input: APIEvent) {
format: "google",
modelList: "full",
parseApiKey: (headers: Headers) => headers.get("x-goog-api-key") ?? undefined,
- parseModel: (url: string, body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
- parseIsStream: (url: string, body: any) =>
+ parseModel: (url: string, _body: any) => url.split("/").pop()?.split(":")?.[0] ?? "",
+ parseIsStream: (url: string, _body: any) =>
// ie. url: https://opencode.ai/zen/v1/models/gemini-3-pro:streamGenerateContent?alt=sse'
url.split("/").pop()?.split(":")?.[1]?.startsWith("streamGenerateContent") ?? false,
})