zen: model management helper

This commit is contained in:
Frank
2025-09-26 15:18:22 -04:00
parent f321661b4c
commit 57e1bffbd5
5 changed files with 92 additions and 27 deletions

View File

@@ -10,6 +10,7 @@ import { Resource } from "@opencode/console-resource"
import { Billing } from "../../../../core/src/billing" import { Billing } from "../../../../core/src/billing"
import { Actor } from "@opencode/console-core/actor.js" import { Actor } from "@opencode/console-core/actor.js"
import { WorkspaceTable } from "@opencode/console-core/schema/workspace.sql.js" import { WorkspaceTable } from "@opencode/console-core/schema/workspace.sql.js"
import { ZenModel } from "@opencode/console-core/model.js"
export async function handler( export async function handler(
input: APIEvent, input: APIEvent,
@@ -34,32 +35,7 @@ export async function handler(
class MonthlyLimitError extends Error {} class MonthlyLimitError extends Error {}
class ModelError extends Error {} class ModelError extends Error {}
const ModelCostSchema = z.object({ type Model = z.infer<typeof ZenModel.ModelSchema>
input: z.number(),
output: z.number(),
cacheRead: z.number().optional(),
cacheWrite5m: z.number().optional(),
cacheWrite1h: z.number().optional(),
})
const ModelSchema = z.object({
cost: ModelCostSchema,
cost200K: ModelCostSchema.optional(),
allowAnonymous: z.boolean().optional(),
providers: z.array(
z.object({
id: z.string(),
api: z.string(),
apiKey: z.string(),
model: z.string(),
weight: z.number().optional(),
headerMappings: z.record(z.string(), z.string()).optional(),
disabled: z.boolean().optional(),
}),
),
})
type Model = z.infer<typeof ModelSchema>
const FREE_WORKSPACES = [ const FREE_WORKSPACES = [
"wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
@@ -230,7 +206,7 @@ export async function handler(
function validateModel(reqModel: string) { function validateModel(reqModel: string) {
const json = JSON.parse(Resource.ZEN_MODELS.value) const json = JSON.parse(Resource.ZEN_MODELS.value)
const allModels = z.record(z.string(), ModelSchema).parse(json) const allModels = ZenModel.ModelsSchema.parse(json)
if (!(reqModel in allModels)) { if (!(reqModel in allModels)) {
throw new ModelError(`Model ${reqModel} not supported`) throw new ModelError(`Model ${reqModel} not supported`)

View File

@@ -20,6 +20,9 @@
"db": "sst shell drizzle-kit", "db": "sst shell drizzle-kit",
"db-dev": "sst shell --stage dev -- drizzle-kit", "db-dev": "sst shell --stage dev -- drizzle-kit",
"db-prod": "sst shell --stage production -- drizzle-kit", "db-prod": "sst shell --stage production -- drizzle-kit",
"update-models": "script/update-models.ts",
"promote-models-to-dev": "script/promote-models.ts dev",
"promote-models-to-prod": "script/promote-models.ts production",
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import { ZenModel } from "../src/model"
const stage = process.argv[2]
if (!stage) throw new Error("Stage is required")
const root = path.resolve(process.cwd(), "..", "..", "..")
// read the secret
const ret = await $`bun sst secret list`.cwd(root).text()
const value = ret
.split("\n")
.find((line) => line.startsWith("ZEN_MODELS"))
?.split("=")[1]
if (!value) throw new Error("ZEN_MODELS not found")
// validate value
ZenModel.ModelsSchema.parse(JSON.parse(value))
// update the secret
await $`bun sst secret set ZEN_MODELS ${value} --stage ${stage}`

View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bun
import { $ } from "bun"
import path from "path"
import os from "os"
import { ZenModel } from "../src/model"
const root = path.resolve(process.cwd(), "..", "..", "..")
const models = await $`bun sst secret list`.cwd(root).text()
console.log("models", models)
// read the line starting with "ZEN_MODELS"
const oldValue = models
.split("\n")
.find((line) => line.startsWith("ZEN_MODELS"))
?.split("=")[1]
if (!oldValue) throw new Error("ZEN_MODELS not found")
console.log("oldValue", oldValue)
// store the prettified json to a temp file
const filename = `models-${Date.now()}.json`
const tempFile = Bun.file(path.join(os.tmpdir(), filename))
await tempFile.write(JSON.stringify(JSON.parse(oldValue), null, 2))
console.log("tempFile", tempFile.name)
// open temp file in vim and read the file on close
await $`vim ${tempFile.name}`
const newValue = JSON.parse(await tempFile.text())
ZenModel.ModelsSchema.parse(newValue)
// update the secret
await $`bun sst secret set ZEN_MODELS ${JSON.stringify(newValue)}`

View File

@@ -0,0 +1,30 @@
import { z } from "zod"
export namespace ZenModel {
const ModelCostSchema = z.object({
input: z.number(),
output: z.number(),
cacheRead: z.number().optional(),
cacheWrite5m: z.number().optional(),
cacheWrite1h: z.number().optional(),
})
export const ModelSchema = z.object({
cost: ModelCostSchema,
cost200K: ModelCostSchema.optional(),
allowAnonymous: z.boolean().optional(),
providers: z.array(
z.object({
id: z.string(),
api: z.string(),
apiKey: z.string(),
model: z.string(),
weight: z.number().optional(),
headerMappings: z.record(z.string(), z.string()).optional(),
disabled: z.boolean().optional(),
}),
),
})
export const ModelsSchema = z.record(z.string(), ModelSchema)
}