mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-22 10:14:22 +01:00
zen: model management helper
This commit is contained in:
@@ -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`)
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
24
packages/console/core/script/promote-models.ts
Executable file
24
packages/console/core/script/promote-models.ts
Executable 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}`
|
||||||
32
packages/console/core/script/update-models.ts
Executable file
32
packages/console/core/script/update-models.ts
Executable 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)}`
|
||||||
30
packages/console/core/src/model.ts
Normal file
30
packages/console/core/src/model.ts
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user