From 3aeb870e7191e7390ca8cf781bf1d2d686a89d8b Mon Sep 17 00:00:00 2001 From: Developer Date: Sat, 9 May 2026 18:54:42 -0400 Subject: [PATCH 1/2] fix(config): allow active provider model status --- packages/opencode/src/config/provider.ts | 2 +- packages/opencode/src/provider/models.ts | 2 +- .../test/server/httpapi-config.test.ts | 37 +++++++++++++++++++ packages/sdk/js/src/gen/types.gen.ts | 4 +- packages/sdk/js/src/v2/gen/types.gen.ts | 2 +- packages/sdk/openapi.json | 2 +- 6 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index 239a0164d8..e501227ae5 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -49,7 +49,7 @@ export const Model = Schema.Struct({ }), ), experimental: Schema.optional(Schema.Boolean), - status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])), + status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated", "active"])), provider: Schema.optional( Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), ), diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index 77e217eb7f..a2b6ee2a79 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -71,7 +71,7 @@ export const Model = Schema.Struct({ ), }), ), - status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])), + status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated", "active"])), provider: Schema.optional( Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), ), diff --git a/packages/opencode/test/server/httpapi-config.test.ts b/packages/opencode/test/server/httpapi-config.test.ts index 3be04b57a3..509a067d08 100644 --- a/packages/opencode/test/server/httpapi-config.test.ts +++ b/packages/opencode/test/server/httpapi-config.test.ts @@ -47,4 +47,41 @@ describe("config HttpApi", () => { lsp: false, }) }) + + test("serves config with active provider model status", async () => { + await using tmp = await tmpdir({ + config: { + formatter: false, + lsp: false, + provider: { + omniroute: { + models: { + "gpt-4o": { + status: "active", + }, + }, + }, + }, + }, + }) + + const response = await app().request("/config", { + headers: { + "x-opencode-directory": tmp.path, + }, + }) + + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ + provider: { + omniroute: { + models: { + "gpt-4o": { + status: "active", + }, + }, + }, + }, + }) + }) }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 8eefe5bfe9..62e1b8fe8d 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1065,7 +1065,7 @@ export type ProviderConfig = { output: Array<"text" | "audio" | "image" | "video" | "pdf"> } experimental?: boolean - status?: "alpha" | "beta" | "deprecated" + status?: "alpha" | "beta" | "deprecated" | "active" options?: { [key: string]: unknown } @@ -3012,7 +3012,7 @@ export type ProviderListResponses = { output: Array<"text" | "audio" | "image" | "video" | "pdf"> } experimental?: boolean - status?: "alpha" | "beta" | "deprecated" + status?: "alpha" | "beta" | "deprecated" | "active" options: { [key: string]: unknown } diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 0d8ab61179..186128242f 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1060,7 +1060,7 @@ export type ProviderConfig = { output: Array<"text" | "audio" | "image" | "video" | "pdf"> } experimental?: boolean - status?: "alpha" | "beta" | "deprecated" + status?: "alpha" | "beta" | "deprecated" | "active" provider?: { npm?: string api?: string diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 270749dc1a..3717f57ec0 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11725,7 +11725,7 @@ }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": ["alpha", "beta", "deprecated", "active"] }, "provider": { "type": "object", From d4212600380e5fa431f3967449309dd9a3fd76df Mon Sep 17 00:00:00 2001 From: Developer Date: Sat, 9 May 2026 19:01:34 -0400 Subject: [PATCH 2/2] refactor(provider): share model status schema --- packages/opencode/src/config/provider.ts | 3 +- .../opencode/src/provider/model-status.ts | 6 ++ packages/opencode/src/provider/models.ts | 3 +- packages/opencode/src/provider/provider.ts | 3 +- packages/opencode/src/v2/model.ts | 3 +- .../test/provider/model-status.test.ts | 57 +++++++++++++++++++ 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 packages/opencode/src/provider/model-status.ts create mode 100644 packages/opencode/test/provider/model-status.test.ts diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts index e501227ae5..af9aac6964 100644 --- a/packages/opencode/src/config/provider.ts +++ b/packages/opencode/src/config/provider.ts @@ -1,6 +1,7 @@ import { Schema } from "effect" import { zod } from "@opencode-ai/core/effect-zod" import { PositiveInt, withStatics } from "@opencode-ai/core/schema" +import { ModelStatus } from "@/provider/model-status" export const Model = Schema.Struct({ id: Schema.optional(Schema.String), @@ -49,7 +50,7 @@ export const Model = Schema.Struct({ }), ), experimental: Schema.optional(Schema.Boolean), - status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated", "active"])), + status: Schema.optional(ModelStatus), provider: Schema.optional( Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), ), diff --git a/packages/opencode/src/provider/model-status.ts b/packages/opencode/src/provider/model-status.ts new file mode 100644 index 0000000000..ae3457dbca --- /dev/null +++ b/packages/opencode/src/provider/model-status.ts @@ -0,0 +1,6 @@ +import { Schema } from "effect" + +export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"]) +export type ModelStatus = typeof ModelStatus.Type + +export * as ProviderModelStatus from "./model-status" diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index a2b6ee2a79..e11bdde058 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -8,6 +8,7 @@ import { Flock } from "@opencode-ai/core/util/flock" import { Hash } from "@opencode-ai/core/util/hash" import { AppFileSystem } from "@opencode-ai/core/filesystem" import { withTransientReadRetry } from "@/util/effect-http-client" +import { ModelStatus } from "./model-status" const Cost = Schema.Struct({ input: Schema.Finite, @@ -71,7 +72,7 @@ export const Model = Schema.Struct({ ), }), ), - status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated", "active"])), + status: Schema.optional(ModelStatus), provider: Schema.optional( Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }), ), diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 18974ea646..c27b69b6a2 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -28,6 +28,7 @@ import { optionalOmitUndefined, withStatics } from "@opencode-ai/core/schema" import * as ProviderTransform from "./transform" import { ModelID, ProviderID } from "./schema" +import { ModelStatus } from "./model-status" const log = Log.create({ service: "provider" }) @@ -897,7 +898,7 @@ export const Model = Schema.Struct({ capabilities: ProviderCapabilities, cost: ProviderCost, limit: ProviderLimit, - status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), + status: ModelStatus, options: Schema.Record(Schema.String, Schema.Any), headers: Schema.Record(Schema.String, Schema.String), release_date: Schema.String, diff --git a/packages/opencode/src/v2/model.ts b/packages/opencode/src/v2/model.ts index a3ee882107..56357ab400 100644 --- a/packages/opencode/src/v2/model.ts +++ b/packages/opencode/src/v2/model.ts @@ -1,4 +1,5 @@ import { withStatics } from "@opencode-ai/core/schema" +import { ModelStatus } from "@/provider/model-status" import { Array, Context, Effect, HashMap, Layer, Option, Order, pipe, Schema } from "effect" import { DateTimeUtcFromMillis } from "effect/Schema" @@ -114,7 +115,7 @@ export class Info extends Schema.Class("Model.Info")({ released: DateTimeUtcFromMillis, }), cost: Cost.pipe(Schema.Array), - status: Schema.Literals(["alpha", "beta", "deprecated", "active"]), + status: ModelStatus, limit: Schema.Struct({ context: Schema.Int, input: Schema.Int.pipe(Schema.optional), diff --git a/packages/opencode/test/provider/model-status.test.ts b/packages/opencode/test/provider/model-status.test.ts new file mode 100644 index 0000000000..214c4322dc --- /dev/null +++ b/packages/opencode/test/provider/model-status.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "bun:test" +import { Schema } from "effect" +import { ConfigProvider } from "@/config/provider" +import { ModelStatus } from "@/provider/model-status" +import { ModelsDev } from "@/provider/models" +import { Provider } from "@/provider/provider" + +describe("provider model status schemas", () => { + test("accept active status across config, models.dev, and public provider schemas", () => { + expect(Schema.decodeUnknownSync(ModelStatus)("active")).toBe("active") + expect(Schema.decodeUnknownSync(ConfigProvider.Model)({ status: "active" }).status).toBe("active") + expect( + Schema.decodeUnknownSync(ModelsDev.Model)({ + id: "test-model", + name: "Test Model", + release_date: "2026-01-01", + attachment: false, + reasoning: false, + temperature: true, + tool_call: true, + limit: { context: 128000, output: 8192 }, + status: "active", + }).status, + ).toBe("active") + expect( + Schema.decodeUnknownSync(Provider.Model)({ + id: "test-model", + providerID: "test-provider", + api: { + id: "test-model", + url: "", + npm: "@ai-sdk/openai-compatible", + }, + name: "Test Model", + capabilities: { + temperature: true, + reasoning: false, + attachment: false, + toolcall: true, + input: { text: true, audio: false, image: false, video: false, pdf: false }, + output: { text: true, audio: false, image: false, video: false, pdf: false }, + interleaved: false, + }, + cost: { + input: 0, + output: 0, + cache: { read: 0, write: 0 }, + }, + limit: { context: 128000, output: 8192 }, + status: "active", + options: {}, + headers: {}, + release_date: "2026-01-01", + }).status, + ).toBe("active") + }) +})