summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFrank <[email protected]>2025-10-08 00:03:34 -0400
committerFrank <[email protected]>2025-10-08 00:03:36 -0400
commit99b72eb1ea136ef6569751061d50b6f691718564 (patch)
tree98e93be1fe291cb884b5e83817613954c447649e
parent22a6849ff8f34ef9d3fab55a295fcfbb766f9d5a (diff)
downloadopencode-99b72eb1ea136ef6569751061d50b6f691718564.tar.gz
opencode-99b72eb1ea136ef6569751061d50b6f691718564.zip
wip: zen
-rw-r--r--packages/console/app/src/routes/workspace/[id].tsx2
-rw-r--r--packages/console/app/src/routes/workspace/model-section.module.css122
-rw-r--r--packages/console/app/src/routes/workspace/model-section.tsx94
-rw-r--r--packages/console/core/migrations/0030_ordinary_ultragirl.sql10
-rw-r--r--packages/console/core/migrations/meta/0030_snapshot.json801
-rw-r--r--packages/console/core/migrations/meta/_journal.json7
-rw-r--r--packages/console/core/src/identifier.ts1
-rw-r--r--packages/console/core/src/model.ts62
-rw-r--r--packages/console/core/src/schema/model.sql.ts13
9 files changed, 1112 insertions, 0 deletions
diff --git a/packages/console/app/src/routes/workspace/[id].tsx b/packages/console/app/src/routes/workspace/[id].tsx
index 09b14056a..952c1417d 100644
--- a/packages/console/app/src/routes/workspace/[id].tsx
+++ b/packages/console/app/src/routes/workspace/[id].tsx
@@ -7,6 +7,7 @@ import { UsageSection } from "./usage-section"
import { KeySection } from "./key-section"
import { MemberSection } from "./member-section"
import { SettingsSection } from "./settings-section"
+import { ModelSection } from "./model-section"
import { Show } from "solid-js"
import { createAsync, query, useParams } from "@solidjs/router"
import { Actor } from "@opencode-ai/console-core/actor.js"
@@ -50,6 +51,7 @@ export default function () {
<Show when={isBeta()}>
<SettingsSection />
<MemberSection />
+ <ModelSection />
</Show>
<BillingSection />
<MonthlyLimitSection />
diff --git a/packages/console/app/src/routes/workspace/model-section.module.css b/packages/console/app/src/routes/workspace/model-section.module.css
new file mode 100644
index 000000000..5a98c9b15
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/model-section.module.css
@@ -0,0 +1,122 @@
+.root {}
+
+[data-slot="section-title"] {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+[data-slot="section-title"] h2 {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--color-text);
+}
+
+[data-slot="section-title"] p {
+ margin: 0;
+ color: var(--color-text-secondary);
+ font-size: 0.875rem;
+}
+
+[data-slot="models-list"] {
+ display: flex;
+ flex-direction: column;
+}
+
+[data-slot="models-table"] {
+ overflow-x: auto;
+}
+
+[data-slot="models-table-element"] {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: var(--font-size-sm);
+
+ thead {
+ border-bottom: 1px solid var(--color-border);
+ }
+
+ th {
+ padding: var(--space-3) var(--space-4);
+ text-align: left;
+ font-weight: normal;
+ color: var(--color-text-muted);
+ text-transform: uppercase;
+ }
+
+ td {
+ padding: var(--space-3) var(--space-4);
+ border-bottom: 1px solid var(--color-border-muted);
+ color: var(--color-text-muted);
+ font-family: var(--font-mono);
+
+ &[data-slot="model-name"] {
+ color: var(--color-text);
+ font-family: var(--font-mono);
+ font-weight: 500;
+ }
+
+ &[data-slot="training-data"] {
+ text-align: center;
+ color: var(--color-text);
+ }
+
+ &[data-slot="model-status"] {
+ text-align: left;
+ color: var(--color-text);
+ }
+
+ &[data-slot="model-toggle"] {
+ text-align: left;
+ font-family: var(--font-sans);
+ }
+ }
+
+ tbody tr {
+ &[data-enabled="false"] {
+ opacity: 0.6;
+ }
+
+ &:last-child td {
+ border-bottom: none;
+ }
+ }
+
+ @media (max-width: 40rem) {
+
+ th,
+ td {
+ padding: var(--space-2) var(--space-3);
+ font-size: var(--font-size-xs);
+ }
+
+ th {
+ &:nth-child(2)
+
+ /* Training Data */
+ {
+ display: none;
+ }
+ }
+
+ td {
+ &:nth-child(2)
+
+ /* Training Data */
+ {
+ display: none;
+ }
+ }
+ }
+}
+
+
+[data-component="empty-state"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 3rem;
+ color: var(--color-text-secondary);
+ font-size: 0.875rem;
+} \ No newline at end of file
diff --git a/packages/console/app/src/routes/workspace/model-section.tsx b/packages/console/app/src/routes/workspace/model-section.tsx
new file mode 100644
index 000000000..078cbfa88
--- /dev/null
+++ b/packages/console/app/src/routes/workspace/model-section.tsx
@@ -0,0 +1,94 @@
+import { Model } from "@opencode-ai/console-core/model.js"
+import { query, action, useParams, createAsync, json } from "@solidjs/router"
+import { createMemo, For, Show } from "solid-js"
+import { withActor } from "~/context/auth.withActor"
+import { ZenModel } from "@opencode-ai/console-core/model.js"
+import styles from "./model-section.module.css"
+
+const getModelsInfo = query(async (workspaceID: string) => {
+ "use server"
+ return withActor(async () => {
+ return {
+ all: Object.keys(ZenModel.list())
+ .filter((model) => !["claude-3-5-haiku", "glm-4.6", "qwen3-max"].includes(model))
+ .sort(([a], [b]) => a.localeCompare(b)),
+ disabled: await Model.listDisabled(),
+ }
+ }, workspaceID)
+}, "model.info")
+
+const updateModel = action(async (form: FormData) => {
+ "use server"
+ const model = form.get("model")?.toString()
+ if (!model) return { error: "Model is required" }
+ const workspaceID = form.get("workspaceID")?.toString()
+ if (!workspaceID) return { error: "Workspace ID is required" }
+ const enabled = form.get("enabled")?.toString() === "true"
+ console.log({ model, workspaceID, enabled })
+ return json(
+ withActor(async () => {
+ if (enabled) {
+ await Model.disable({ model })
+ } else {
+ await Model.enable({ model })
+ }
+ }, workspaceID),
+ { revalidate: getModelsInfo.key },
+ )
+}, "model.toggle")
+
+export function ModelSection() {
+ const params = useParams()
+ const modelsInfo = createAsync(() => getModelsInfo(params.id))
+ return (
+ <section class={styles.root}>
+ <div data-slot="section-title">
+ <h2>Models</h2>
+ <p>Manage models for your workspace.</p>
+ </div>
+ <div data-slot="models-list">
+ <Show
+ when={modelsInfo()}
+ fallback={
+ <div data-component="empty-state">
+ <p>Loading models...</p>
+ </div>
+ }
+ >
+ <div data-slot="models-table">
+ <table data-slot="models-table-element">
+ <thead>
+ <tr>
+ <th>Model</th>
+ <th>Status</th>
+ <th>Action</th>
+ </tr>
+ </thead>
+ <tbody>
+ <For each={modelsInfo()!.all}>
+ {(modelId) => {
+ const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(modelId))
+ return (
+ <tr data-slot="model-row" data-enabled={isEnabled()}>
+ <td data-slot="model-name">{modelId}</td>
+ <td data-slot="model-status">{isEnabled() ? "Enabled" : "Disabled"}</td>
+ <td data-slot="model-toggle">
+ <form action={updateModel} method="post">
+ <input type="hidden" name="model" value={modelId} />
+ <input type="hidden" name="workspaceID" value={params.id} />
+ <input type="hidden" name="enabled" value={isEnabled().toString()} />
+ <button data-color="ghost">{isEnabled() ? "Disable" : "Enable"}</button>
+ </form>
+ </td>
+ </tr>
+ )
+ }}
+ </For>
+ </tbody>
+ </table>
+ </div>
+ </Show>
+ </div>
+ </section>
+ )
+}
diff --git a/packages/console/core/migrations/0030_ordinary_ultragirl.sql b/packages/console/core/migrations/0030_ordinary_ultragirl.sql
new file mode 100644
index 000000000..4d1666a64
--- /dev/null
+++ b/packages/console/core/migrations/0030_ordinary_ultragirl.sql
@@ -0,0 +1,10 @@
+CREATE TABLE `model` (
+ `id` varchar(30) NOT NULL,
+ `workspace_id` varchar(30) NOT NULL,
+ `time_created` timestamp(3) NOT NULL DEFAULT (now()),
+ `time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+ `time_deleted` timestamp(3),
+ `model` varchar(64) NOT NULL,
+ CONSTRAINT `model_workspace_id_id_pk` PRIMARY KEY(`workspace_id`,`id`),
+ CONSTRAINT `model_workspace_model` UNIQUE(`workspace_id`,`model`)
+);
diff --git a/packages/console/core/migrations/meta/0030_snapshot.json b/packages/console/core/migrations/meta/0030_snapshot.json
new file mode 100644
index 000000000..6a6eb38cb
--- /dev/null
+++ b/packages/console/core/migrations/meta/0030_snapshot.json
@@ -0,0 +1,801 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "eae45fcf-dc0f-4756-bc5d-30791f2965a2",
+ "prevId": "33551b4c-fc2e-4753-8d9d-0971f333e65d",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "email": {
+ "name": "email",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "billing": {
+ "name": "billing",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "customer_id": {
+ "name": "customer_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "payment_method_id": {
+ "name": "payment_method_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "payment_method_last4": {
+ "name": "payment_method_last4",
+ "type": "varchar(4)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "balance": {
+ "name": "balance",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monthly_limit": {
+ "name": "monthly_limit",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "monthly_usage": {
+ "name": "monthly_usage",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "time_monthly_usage_updated": {
+ "name": "time_monthly_usage_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reload": {
+ "name": "reload",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reload_error": {
+ "name": "reload_error",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "time_reload_error": {
+ "name": "time_reload_error",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "time_reload_locked_till": {
+ "name": "time_reload_locked_till",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "global_customer_id": {
+ "name": "global_customer_id",
+ "columns": [
+ "customer_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "billing_workspace_id_id_pk": {
+ "name": "billing_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "payment": {
+ "name": "payment",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "customer_id": {
+ "name": "customer_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "invoice_id": {
+ "name": "invoice_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "payment_id": {
+ "name": "payment_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "amount": {
+ "name": "amount",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_refunded": {
+ "name": "time_refunded",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "payment_workspace_id_id_pk": {
+ "name": "payment_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "usage": {
+ "name": "usage",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "input_tokens": {
+ "name": "input_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "output_tokens": {
+ "name": "output_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "reasoning_tokens": {
+ "name": "reasoning_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_read_tokens": {
+ "name": "cache_read_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_5m_tokens": {
+ "name": "cache_write_5m_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cache_write_1h_tokens": {
+ "name": "cache_write_1h_tokens",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "cost": {
+ "name": "cost",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "usage_workspace_id_id_pk": {
+ "name": "usage_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "key": {
+ "name": "key",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_used": {
+ "name": "time_used",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "global_key": {
+ "name": "global_key",
+ "columns": [
+ "key"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "key_workspace_id_id_pk": {
+ "name": "key_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "model": {
+ "name": "model",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "model_workspace_model": {
+ "name": "model_workspace_model",
+ "columns": [
+ "workspace_id",
+ "model"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "model_workspace_id_id_pk": {
+ "name": "model_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_seen": {
+ "name": "time_seen",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "color": {
+ "name": "color",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('admin','member')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "monthly_limit": {
+ "name": "monthly_limit",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "monthly_usage": {
+ "name": "monthly_usage",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "time_monthly_usage_updated": {
+ "name": "time_monthly_usage_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "user_account_id": {
+ "name": "user_account_id",
+ "columns": [
+ "workspace_id",
+ "account_id"
+ ],
+ "isUnique": true
+ },
+ "user_email": {
+ "name": "user_email",
+ "columns": [
+ "workspace_id",
+ "email"
+ ],
+ "isUnique": true
+ },
+ "global_account_id": {
+ "name": "global_account_id",
+ "columns": [
+ "account_id"
+ ],
+ "isUnique": false
+ },
+ "global_email": {
+ "name": "global_email",
+ "columns": [
+ "email"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "user_workspace_id_id_pk": {
+ "name": "user_workspace_id_id_pk",
+ "columns": [
+ "workspace_id",
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "workspace": {
+ "name": "workspace",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "varchar(30)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "time_created": {
+ "name": "time_created",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "time_updated": {
+ "name": "time_updated",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"
+ },
+ "time_deleted": {
+ "name": "time_deleted",
+ "type": "timestamp(3)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "slug": {
+ "name": "slug",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "workspace_id": {
+ "name": "workspace_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/console/core/migrations/meta/_journal.json b/packages/console/core/migrations/meta/_journal.json
index 221718250..b178c11cc 100644
--- a/packages/console/core/migrations/meta/_journal.json
+++ b/packages/console/core/migrations/meta/_journal.json
@@ -211,6 +211,13 @@
"when": 1759811835558,
"tag": "0029_panoramic_harrier",
"breakpoints": true
+ },
+ {
+ "idx": 30,
+ "version": "5",
+ "when": 1759878278492,
+ "tag": "0030_ordinary_ultragirl",
+ "breakpoints": true
}
]
} \ No newline at end of file
diff --git a/packages/console/core/src/identifier.ts b/packages/console/core/src/identifier.ts
index f8e73852e..98c12a6c6 100644
--- a/packages/console/core/src/identifier.ts
+++ b/packages/console/core/src/identifier.ts
@@ -6,6 +6,7 @@ export namespace Identifier {
account: "acc",
billing: "bil",
key: "key",
+ model: "mod",
payment: "pay",
usage: "usg",
user: "usr",
diff --git a/packages/console/core/src/model.ts b/packages/console/core/src/model.ts
index 028d94655..ae636c4f3 100644
--- a/packages/console/core/src/model.ts
+++ b/packages/console/core/src/model.ts
@@ -1,4 +1,11 @@
import { z } from "zod"
+import { eq, and } from "drizzle-orm"
+import { Database } from "./drizzle"
+import { ModelTable } from "./schema/model.sql"
+import { Identifier } from "./identifier"
+import { fn } from "./util/fn"
+import { Actor } from "./actor"
+import { Resource } from "@opencode-ai/console-resource"
export namespace ZenModel {
const ModelCostSchema = z.object({
@@ -27,4 +34,59 @@ export namespace ZenModel {
})
export const ModelsSchema = z.record(z.string(), ModelSchema)
+
+ export const list = fn(z.void(), () => ModelsSchema.parse(JSON.parse(Resource.ZEN_MODELS.value)))
+}
+
+export namespace Model {
+ export const enable = fn(z.object({ model: z.string() }), ({ model }) => {
+ const workspaceID = Actor.workspace()
+ return Database.use((db) =>
+ db.delete(ModelTable).where(and(eq(ModelTable.workspaceID, workspaceID), eq(ModelTable.model, model))),
+ )
+ })
+
+ export const disable = fn(z.object({ model: z.string() }), ({ model }) => {
+ return Database.use((db) =>
+ db
+ .insert(ModelTable)
+ .values({
+ id: Identifier.create("model"),
+ workspaceID: Actor.workspace(),
+ model: model,
+ })
+ .onDuplicateKeyUpdate({
+ set: {
+ timeDeleted: null,
+ },
+ }),
+ )
+ })
+
+ export const listDisabled = fn(z.void(), () => {
+ return Database.use((db) =>
+ db
+ .select({ model: ModelTable.model })
+ .from(ModelTable)
+ .where(eq(ModelTable.workspaceID, Actor.workspace()))
+ .then((rows) => rows.map((row) => row.model)),
+ )
+ })
+
+ export const isDisabled = fn(
+ z.object({
+ model: z.string(),
+ }),
+ ({ model }) => {
+ return Database.use(async (db) => {
+ const result = await db
+ .select()
+ .from(ModelTable)
+ .where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model)))
+ .limit(1)
+
+ return result.length > 0
+ })
+ },
+ )
}
diff --git a/packages/console/core/src/schema/model.sql.ts b/packages/console/core/src/schema/model.sql.ts
new file mode 100644
index 000000000..1c032aad2
--- /dev/null
+++ b/packages/console/core/src/schema/model.sql.ts
@@ -0,0 +1,13 @@
+import { mysqlTable, varchar, uniqueIndex } from "drizzle-orm/mysql-core"
+import { timestamps, workspaceColumns } from "../drizzle/types"
+import { workspaceIndexes } from "./workspace.sql"
+
+export const ModelTable = mysqlTable(
+ "model",
+ {
+ ...workspaceColumns,
+ ...timestamps,
+ model: varchar("model", { length: 64 }).notNull(),
+ },
+ (table) => [...workspaceIndexes(table), uniqueIndex("model_workspace_model").on(table.workspaceID, table.model)],
+)