summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-16 23:10:45 -0400
committerGitHub <[email protected]>2026-04-16 23:10:45 -0400
commitee7339f2c6a4575a81bcaff2240076571db03e11 (patch)
tree6352fb593d4033ba79a6e4ae1a9ad84d4811d15a
parentc51f3e35cabb5cbb49d4fddc240b880c58286a97 (diff)
downloadopencode-ee7339f2c6a4575a81bcaff2240076571db03e11.tar.gz
opencode-ee7339f2c6a4575a81bcaff2240076571db03e11.zip
refactor: move provider and config provider routes onto HttpApi (#23004)
-rw-r--r--packages/opencode/src/provider/auth.ts38
-rw-r--r--packages/opencode/src/provider/provider.ts228
-rw-r--r--packages/opencode/src/server/instance/config.ts12
-rw-r--r--packages/opencode/src/server/instance/httpapi/config.ts51
-rw-r--r--packages/opencode/src/server/instance/httpapi/provider.ts104
-rw-r--r--packages/opencode/src/server/instance/httpapi/server.ts3
-rw-r--r--packages/opencode/src/server/instance/index.ts19
-rw-r--r--packages/opencode/src/server/instance/provider.ts26
8 files changed, 323 insertions, 158 deletions
diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts
index c0c73b2cc..5d8b2765d 100644
--- a/packages/opencode/src/provider/auth.ts
+++ b/packages/opencode/src/provider/auth.ts
@@ -58,6 +58,18 @@ export class Authorization extends Schema.Class<Authorization>("ProviderAuthAuth
static readonly zod = zod(this)
}
+export const AuthorizeInput = Schema.Struct({
+ method: Schema.Number.annotate({ description: "Auth method index" }),
+ inputs: Schema.optional(Schema.Record(Schema.String, Schema.String)).annotate({ description: "Prompt inputs" }),
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
+export type AuthorizeInput = Schema.Schema.Type<typeof AuthorizeInput>
+
+export const CallbackInput = Schema.Struct({
+ method: Schema.Number.annotate({ description: "Auth method index" }),
+ code: Schema.optional(Schema.String).annotate({ description: "OAuth authorization code" }),
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
+export type CallbackInput = Schema.Schema.Type<typeof CallbackInput>
+
export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", z.object({ providerID: ProviderID.zod }))
export const OauthCodeMissing = NamedError.create(
@@ -86,12 +98,12 @@ type Hook = NonNullable<Hooks["auth"]>
export interface Interface {
readonly methods: () => Effect.Effect<Methods>
- readonly authorize: (input: {
- providerID: ProviderID
- method: number
- inputs?: Record<string, string>
- }) => Effect.Effect<Authorization | undefined, Error>
- readonly callback: (input: { providerID: ProviderID; method: number; code?: string }) => Effect.Effect<void, Error>
+ readonly authorize: (
+ input: {
+ providerID: ProviderID
+ } & AuthorizeInput,
+ ) => Effect.Effect<Authorization | undefined, Error>
+ readonly callback: (input: { providerID: ProviderID } & CallbackInput) => Effect.Effect<void, Error>
}
interface State {
@@ -153,11 +165,9 @@ export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> =
)
})
- const authorize = Effect.fn("ProviderAuth.authorize")(function* (input: {
- providerID: ProviderID
- method: number
- inputs?: Record<string, string>
- }) {
+ const authorize = Effect.fn("ProviderAuth.authorize")(function* (
+ input: { providerID: ProviderID } & AuthorizeInput,
+ ) {
const { hooks, pending } = yield* InstanceState.get(state)
const method = hooks[input.providerID].methods[input.method]
if (method.type !== "oauth") return
@@ -180,11 +190,7 @@ export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> =
}
})
- const callback = Effect.fn("ProviderAuth.callback")(function* (input: {
- providerID: ProviderID
- method: number
- code?: string
- }) {
+ const callback = Effect.fn("ProviderAuth.callback")(function* (input: { providerID: ProviderID } & CallbackInput) {
const pending = (yield* InstanceState.get(state)).pending
const match = pending.get(input.providerID)
if (!match) return yield* Effect.fail(new OauthMissing({ providerID: input.providerID }))
diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts
index a7297634e..711481d80 100644
--- a/packages/opencode/src/provider/provider.ts
+++ b/packages/opencode/src/provider/provider.ts
@@ -16,14 +16,16 @@ import { Env } from "../env"
import { Instance } from "../project/instance"
import { InstallationVersion } from "../installation/version"
import { Flag } from "../flag/flag"
+import { zod } from "@/util/effect-zod"
import { iife } from "@/util/iife"
import { Global } from "../global"
import path from "path"
-import { Effect, Layer, Context } from "effect"
+import { Effect, Layer, Context, Schema, Types } from "effect"
import { EffectBridge } from "@/effect"
import { InstanceState } from "@/effect"
import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { isRecord } from "@/util/record"
+import { withStatics } from "@/util/schema"
import * as ProviderTransform from "./transform"
import { ModelID, ProviderID } from "./schema"
@@ -796,91 +798,111 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
}
}
-export const Model = z
- .object({
- id: ModelID.zod,
- providerID: ProviderID.zod,
- api: z.object({
- id: z.string(),
- url: z.string(),
- npm: z.string(),
- }),
- name: z.string(),
- family: z.string().optional(),
- capabilities: z.object({
- temperature: z.boolean(),
- reasoning: z.boolean(),
- attachment: z.boolean(),
- toolcall: z.boolean(),
- input: z.object({
- text: z.boolean(),
- audio: z.boolean(),
- image: z.boolean(),
- video: z.boolean(),
- pdf: z.boolean(),
- }),
- output: z.object({
- text: z.boolean(),
- audio: z.boolean(),
- image: z.boolean(),
- video: z.boolean(),
- pdf: z.boolean(),
- }),
- interleaved: z.union([
- z.boolean(),
- z.object({
- field: z.enum(["reasoning_content", "reasoning_details"]),
- }),
- ]),
- }),
- cost: z.object({
- input: z.number(),
- output: z.number(),
- cache: z.object({
- read: z.number(),
- write: z.number(),
- }),
- experimentalOver200K: z
- .object({
- input: z.number(),
- output: z.number(),
- cache: z.object({
- read: z.number(),
- write: z.number(),
- }),
- })
- .optional(),
- }),
- limit: z.object({
- context: z.number(),
- input: z.number().optional(),
- output: z.number(),
+const ProviderApiInfo = Schema.Struct({
+ id: Schema.String,
+ url: Schema.String,
+ npm: Schema.String,
+})
+
+const ProviderModalities = Schema.Struct({
+ text: Schema.Boolean,
+ audio: Schema.Boolean,
+ image: Schema.Boolean,
+ video: Schema.Boolean,
+ pdf: Schema.Boolean,
+})
+
+const ProviderInterleaved = Schema.Union([
+ Schema.Boolean,
+ Schema.Struct({
+ field: Schema.Literals(["reasoning_content", "reasoning_details"]),
+ }),
+])
+
+const ProviderCapabilities = Schema.Struct({
+ temperature: Schema.Boolean,
+ reasoning: Schema.Boolean,
+ attachment: Schema.Boolean,
+ toolcall: Schema.Boolean,
+ input: ProviderModalities,
+ output: ProviderModalities,
+ interleaved: ProviderInterleaved,
+})
+
+const ProviderCacheCost = Schema.Struct({
+ read: Schema.Number,
+ write: Schema.Number,
+})
+
+const ProviderCost = Schema.Struct({
+ input: Schema.Number,
+ output: Schema.Number,
+ cache: ProviderCacheCost,
+ experimentalOver200K: Schema.optional(
+ Schema.Struct({
+ input: Schema.Number,
+ output: Schema.Number,
+ cache: ProviderCacheCost,
}),
- status: z.enum(["alpha", "beta", "deprecated", "active"]),
- options: z.record(z.string(), z.any()),
- headers: z.record(z.string(), z.string()),
- release_date: z.string(),
- variants: z.record(z.string(), z.record(z.string(), z.any())).optional(),
- })
- .meta({
- ref: "Model",
- })
-export type Model = z.infer<typeof Model>
-
-export const Info = z
- .object({
- id: ProviderID.zod,
- name: z.string(),
- source: z.enum(["env", "config", "custom", "api"]),
- env: z.string().array(),
- key: z.string().optional(),
- options: z.record(z.string(), z.any()),
- models: z.record(z.string(), Model),
- })
- .meta({
- ref: "Provider",
- })
-export type Info = z.infer<typeof Info>
+ ),
+})
+
+const ProviderLimit = Schema.Struct({
+ context: Schema.Number,
+ input: Schema.optional(Schema.Number),
+ output: Schema.Number,
+})
+
+export const Model = Schema.Struct({
+ id: ModelID,
+ providerID: ProviderID,
+ api: ProviderApiInfo,
+ name: Schema.String,
+ family: Schema.optional(Schema.String),
+ capabilities: ProviderCapabilities,
+ cost: ProviderCost,
+ limit: ProviderLimit,
+ status: Schema.Literals(["alpha", "beta", "deprecated", "active"]),
+ options: Schema.Record(Schema.String, Schema.Any),
+ headers: Schema.Record(Schema.String, Schema.String),
+ release_date: Schema.String,
+ variants: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Any))),
+})
+ .annotate({ identifier: "Model" })
+ .pipe(withStatics((s) => ({ zod: zod(s) })))
+export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
+
+export const Info = Schema.Struct({
+ id: ProviderID,
+ name: Schema.String,
+ source: Schema.Literals(["env", "config", "custom", "api"]),
+ env: Schema.Array(Schema.String),
+ key: Schema.optional(Schema.String),
+ options: Schema.Record(Schema.String, Schema.Any),
+ models: Schema.Record(Schema.String, Model),
+})
+ .annotate({ identifier: "Provider" })
+ .pipe(withStatics((s) => ({ zod: zod(s) })))
+export type Info = Types.DeepMutable<Schema.Schema.Type<typeof Info>>
+
+const DefaultModelIDs = Schema.Record(Schema.String, Schema.String)
+
+export const ListResult = Schema.Struct({
+ all: Schema.Array(Info),
+ default: DefaultModelIDs,
+ connected: Schema.Array(Schema.String),
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
+export type ListResult = Types.DeepMutable<Schema.Schema.Type<typeof ListResult>>
+
+export const ConfigProvidersResult = Schema.Struct({
+ providers: Schema.Array(Info),
+ default: DefaultModelIDs,
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
+export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>
+
+export function defaultModelIDs<T extends { models: Record<string, { id: string }> }>(providers: Record<string, T>) {
+ return mapValues(providers, (item) => sort(Object.values(item.models))[0].id)
+}
export interface Interface {
readonly list: () => Effect.Effect<Record<ProviderID, Info>>
@@ -928,7 +950,7 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
}
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
- const m: Model = {
+ const base: Model = {
id: ModelID.make(model.id),
providerID: ProviderID.make(provider.id),
name: model.name,
@@ -972,9 +994,10 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
variants: {},
}
- m.variants = mapValues(ProviderTransform.variants(m), (v) => v)
-
- return m
+ return {
+ ...base,
+ variants: mapValues(ProviderTransform.variants(base), (v) => v),
+ }
}
export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
@@ -983,17 +1006,22 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
models[key] = fromModelsDevModel(provider, model)
for (const [mode, opts] of Object.entries(model.experimental?.modes ?? {})) {
const id = `${model.id}-${mode}`
- const m = fromModelsDevModel(provider, model)
- m.id = ModelID.make(id)
- m.name = `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`
- if (opts.cost) m.cost = mergeDeep(m.cost, cost(opts.cost))
- // convert body params to camelCase for ai sdk compatibility
- if (opts.provider?.body)
- m.options = Object.fromEntries(
- Object.entries(opts.provider.body).map(([k, v]) => [k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()), v]),
- )
- if (opts.provider?.headers) m.headers = opts.provider.headers
- models[id] = m
+ const base = fromModelsDevModel(provider, model)
+ models[id] = {
+ ...base,
+ id: ModelID.make(id),
+ name: `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`,
+ cost: opts.cost ? mergeDeep(base.cost, cost(opts.cost)) : base.cost,
+ options: opts.provider?.body
+ ? Object.fromEntries(
+ Object.entries(opts.provider.body).map(([k, v]) => [
+ k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
+ v,
+ ]),
+ )
+ : base.options,
+ headers: opts.provider?.headers ?? base.headers,
+ }
}
}
return {
diff --git a/packages/opencode/src/server/instance/config.ts b/packages/opencode/src/server/instance/config.ts
index e3291a8c3..15c393fe5 100644
--- a/packages/opencode/src/server/instance/config.ts
+++ b/packages/opencode/src/server/instance/config.ts
@@ -3,7 +3,6 @@ import { describeRoute, validator, resolver } from "hono-openapi"
import z from "zod"
import { Config } from "../../config"
import { Provider } from "../../provider"
-import { mapValues } from "remeda"
import { errors } from "../error"
import { lazy } from "../../util/lazy"
import { AppRuntime } from "../../effect/app-runtime"
@@ -70,12 +69,7 @@ export const ConfigRoutes = lazy(() =>
description: "List of providers",
content: {
"application/json": {
- schema: resolver(
- z.object({
- providers: Provider.Info.array(),
- default: z.record(z.string(), z.string()),
- }),
- ),
+ schema: resolver(Provider.ConfigProvidersResult.zod),
},
},
},
@@ -84,10 +78,10 @@ export const ConfigRoutes = lazy(() =>
async (c) =>
jsonRequest("ConfigRoutes.providers", c, function* () {
const svc = yield* Provider.Service
- const providers = mapValues(yield* svc.list(), (item) => item)
+ const providers = yield* svc.list()
return {
providers: Object.values(providers),
- default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
+ default: Provider.defaultModelIDs(providers),
}
}),
),
diff --git a/packages/opencode/src/server/instance/httpapi/config.ts b/packages/opencode/src/server/instance/httpapi/config.ts
new file mode 100644
index 000000000..14aa94f9f
--- /dev/null
+++ b/packages/opencode/src/server/instance/httpapi/config.ts
@@ -0,0 +1,51 @@
+import { Config } from "@/config"
+import { Provider } from "@/provider"
+import { Effect, Layer } from "effect"
+import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
+
+const root = "/config"
+
+export const ConfigApi = HttpApi.make("config")
+ .add(
+ HttpApiGroup.make("config")
+ .add(
+ HttpApiEndpoint.get("providers", `${root}/providers`, {
+ success: Provider.ConfigProvidersResult,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "config.providers",
+ summary: "List config providers",
+ description: "Get a list of all configured AI providers and their default models.",
+ }),
+ ),
+ )
+ .annotateMerge(
+ OpenApi.annotations({
+ title: "config",
+ description: "Experimental HttpApi config routes.",
+ }),
+ ),
+ )
+ .annotateMerge(
+ OpenApi.annotations({
+ title: "opencode experimental HttpApi",
+ version: "0.0.1",
+ description: "Experimental HttpApi surface for selected instance routes.",
+ }),
+ )
+
+export const configHandlers = Layer.unwrap(
+ Effect.gen(function* () {
+ const svc = yield* Provider.Service
+
+ const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
+ const providers = yield* svc.list()
+ return {
+ providers: Object.values(providers),
+ default: Provider.defaultModelIDs(providers),
+ }
+ })
+
+ return HttpApiBuilder.group(ConfigApi, "config", (handlers) => handlers.handle("providers", providers))
+ }),
+).pipe(Layer.provide(Provider.defaultLayer), Layer.provide(Config.defaultLayer))
diff --git a/packages/opencode/src/server/instance/httpapi/provider.ts b/packages/opencode/src/server/instance/httpapi/provider.ts
index 31dd1446a..67831a1fa 100644
--- a/packages/opencode/src/server/instance/httpapi/provider.ts
+++ b/packages/opencode/src/server/instance/httpapi/provider.ts
@@ -1,6 +1,11 @@
import { ProviderAuth } from "@/provider"
-import { Effect, Layer } from "effect"
-import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
+import { Config } from "@/config"
+import { ModelsDev } from "@/provider"
+import { Provider } from "@/provider"
+import { ProviderID } from "@/provider/schema"
+import { mapValues } from "remeda"
+import { Effect, Layer, Schema } from "effect"
+import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
const root = "/provider"
@@ -8,6 +13,15 @@ export const ProviderApi = HttpApi.make("provider")
.add(
HttpApiGroup.make("provider")
.add(
+ HttpApiEndpoint.get("list", root, {
+ success: Provider.ListResult,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "provider.list",
+ summary: "List providers",
+ description: "Get a list of all available AI providers, including both available and connected ones.",
+ }),
+ ),
HttpApiEndpoint.get("auth", `${root}/auth`, {
success: ProviderAuth.Methods,
}).annotateMerge(
@@ -17,6 +31,28 @@ export const ProviderApi = HttpApi.make("provider")
description: "Retrieve available authentication methods for all AI providers.",
}),
),
+ HttpApiEndpoint.post("authorize", `${root}/:providerID/oauth/authorize`, {
+ params: { providerID: ProviderID },
+ payload: ProviderAuth.AuthorizeInput,
+ success: ProviderAuth.Authorization,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "provider.oauth.authorize",
+ summary: "Start OAuth authorization",
+ description: "Start the OAuth authorization flow for a provider.",
+ }),
+ ),
+ HttpApiEndpoint.post("callback", `${root}/:providerID/oauth/callback`, {
+ params: { providerID: ProviderID },
+ payload: ProviderAuth.CallbackInput,
+ success: Schema.Boolean,
+ }).annotateMerge(
+ OpenApi.annotations({
+ identifier: "provider.oauth.callback",
+ summary: "Handle OAuth callback",
+ description: "Handle the OAuth callback from a provider after user authorization.",
+ }),
+ ),
)
.annotateMerge(
OpenApi.annotations({
@@ -35,12 +71,72 @@ export const ProviderApi = HttpApi.make("provider")
export const providerHandlers = Layer.unwrap(
Effect.gen(function* () {
+ const cfg = yield* Config.Service
+ const provider = yield* Provider.Service
const svc = yield* ProviderAuth.Service
+ const list = Effect.fn("ProviderHttpApi.list")(function* () {
+ const config = yield* cfg.get()
+ const all = yield* Effect.promise(() => ModelsDev.get())
+ const disabled = new Set(config.disabled_providers ?? [])
+ const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
+ const filtered: Record<string, (typeof all)[string]> = {}
+ for (const [key, value] of Object.entries(all)) {
+ if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
+ filtered[key] = value
+ }
+ }
+ const connected = yield* provider.list()
+ const providers = Object.assign(
+ mapValues(filtered, (item) => Provider.fromModelsDevProvider(item)),
+ connected,
+ )
+ return {
+ all: Object.values(providers),
+ default: Provider.defaultModelIDs(providers),
+ connected: Object.keys(connected),
+ }
+ })
+
const auth = Effect.fn("ProviderHttpApi.auth")(function* () {
return yield* svc.methods()
})
- return HttpApiBuilder.group(ProviderApi, "provider", (handlers) => handlers.handle("auth", auth))
+ const authorize = Effect.fn("ProviderHttpApi.authorize")(function* (ctx: {
+ params: { providerID: ProviderID }
+ payload: ProviderAuth.AuthorizeInput
+ }) {
+ const result = yield* svc
+ .authorize({
+ providerID: ctx.params.providerID,
+ method: ctx.payload.method,
+ inputs: ctx.payload.inputs,
+ })
+ .pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
+ if (!result) return yield* new HttpApiError.BadRequest({})
+ return result
+ })
+
+ const callback = Effect.fn("ProviderHttpApi.callback")(function* (ctx: {
+ params: { providerID: ProviderID }
+ payload: ProviderAuth.CallbackInput
+ }) {
+ yield* svc
+ .callback({
+ providerID: ctx.params.providerID,
+ method: ctx.payload.method,
+ code: ctx.payload.code,
+ })
+ .pipe(Effect.catch(() => Effect.fail(new HttpApiError.BadRequest({}))))
+ return true
+ })
+
+ return HttpApiBuilder.group(ProviderApi, "provider", (handlers) =>
+ handlers.handle("list", list).handle("auth", auth).handle("authorize", authorize).handle("callback", callback),
+ )
}),
-).pipe(Layer.provide(ProviderAuth.defaultLayer))
+).pipe(
+ Layer.provide(ProviderAuth.defaultLayer),
+ Layer.provide(Provider.defaultLayer),
+ Layer.provide(Config.defaultLayer),
+)
diff --git a/packages/opencode/src/server/instance/httpapi/server.ts b/packages/opencode/src/server/instance/httpapi/server.ts
index 362d0970b..64332fd2a 100644
--- a/packages/opencode/src/server/instance/httpapi/server.ts
+++ b/packages/opencode/src/server/instance/httpapi/server.ts
@@ -10,6 +10,7 @@ import { InstanceBootstrap } from "@/project/bootstrap"
import { Instance } from "@/project/instance"
import { lazy } from "@/util/lazy"
import { Filesystem } from "@/util"
+import { ConfigApi, configHandlers } from "./config"
import { PermissionApi, permissionHandlers } from "./permission"
import { ProviderApi, providerHandlers } from "./provider"
import { QuestionApi, questionHandlers } from "./question"
@@ -108,8 +109,10 @@ const instance = HttpRouter.middleware()(
const QuestionSecured = QuestionApi.middleware(Authorization)
const PermissionSecured = PermissionApi.middleware(Authorization)
const ProviderSecured = ProviderApi.middleware(Authorization)
+const ConfigSecured = ConfigApi.middleware(Authorization)
export const routes = Layer.mergeAll(
+ HttpApiBuilder.layer(ConfigSecured).pipe(Layer.provide(configHandlers)),
HttpApiBuilder.layer(QuestionSecured).pipe(Layer.provide(questionHandlers)),
HttpApiBuilder.layer(PermissionSecured).pipe(Layer.provide(permissionHandlers)),
HttpApiBuilder.layer(ProviderSecured).pipe(Layer.provide(providerHandlers)),
diff --git a/packages/opencode/src/server/instance/index.ts b/packages/opencode/src/server/instance/index.ts
index 9ef6da63a..6a290093c 100644
--- a/packages/opencode/src/server/instance/index.ts
+++ b/packages/opencode/src/server/instance/index.ts
@@ -1,7 +1,7 @@
import { describeRoute, resolver, validator } from "hono-openapi"
import { Hono } from "hono"
import type { UpgradeWebSocket } from "hono/ws"
-import { Effect } from "effect"
+import { Context, Effect } from "effect"
import z from "zod"
import { Format } from "../../format"
import { TuiRoutes } from "./tui"
@@ -41,12 +41,17 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono => {
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) {
const handler = ExperimentalHttpApiServer.webHandler().handler
- app
- .all("/question", (c) => handler(c.req.raw))
- .all("/question/*", (c) => handler(c.req.raw))
- .all("/permission", (c) => handler(c.req.raw))
- .all("/permission/*", (c) => handler(c.req.raw))
- .all("/provider/auth", (c) => handler(c.req.raw))
+ const context = Context.empty() as Context.Context<unknown>
+ app.get("/question", (c) => handler(c.req.raw, context))
+ app.post("/question/:requestID/reply", (c) => handler(c.req.raw, context))
+ app.post("/question/:requestID/reject", (c) => handler(c.req.raw, context))
+ app.get("/permission", (c) => handler(c.req.raw, context))
+ app.post("/permission/:requestID/reply", (c) => handler(c.req.raw, context))
+ app.get("/config/providers", (c) => handler(c.req.raw, context))
+ app.get("/provider", (c) => handler(c.req.raw, context))
+ app.get("/provider/auth", (c) => handler(c.req.raw, context))
+ app.post("/provider/:providerID/oauth/authorize", (c) => handler(c.req.raw, context))
+ app.post("/provider/:providerID/oauth/callback", (c) => handler(c.req.raw, context))
}
return app
diff --git a/packages/opencode/src/server/instance/provider.ts b/packages/opencode/src/server/instance/provider.ts
index c1580437d..a81ae00d5 100644
--- a/packages/opencode/src/server/instance/provider.ts
+++ b/packages/opencode/src/server/instance/provider.ts
@@ -25,13 +25,7 @@ export const ProviderRoutes = lazy(() =>
description: "List of providers",
content: {
"application/json": {
- schema: resolver(
- z.object({
- all: Provider.Info.array(),
- default: z.record(z.string(), z.string()),
- connected: z.array(z.string()),
- }),
- ),
+ schema: resolver(Provider.ListResult.zod),
},
},
},
@@ -59,7 +53,7 @@ export const ProviderRoutes = lazy(() =>
)
return {
all: Object.values(providers),
- default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
+ default: Provider.defaultModelIDs(providers),
connected: Object.keys(connected),
}
}),
@@ -116,13 +110,7 @@ export const ProviderRoutes = lazy(() =>
providerID: ProviderID.zod.meta({ description: "Provider ID" }),
}),
),
- validator(
- "json",
- z.object({
- method: z.number().meta({ description: "Auth method index" }),
- inputs: z.record(z.string(), z.string()).optional().meta({ description: "Prompt inputs" }),
- }),
- ),
+ validator("json", ProviderAuth.AuthorizeInput.zod),
async (c) => {
const providerID = c.req.valid("param").providerID
const { method, inputs } = c.req.valid("json")
@@ -162,13 +150,7 @@ export const ProviderRoutes = lazy(() =>
providerID: ProviderID.zod.meta({ description: "Provider ID" }),
}),
),
- validator(
- "json",
- z.object({
- method: z.number().meta({ description: "Auth method index" }),
- code: z.string().optional().meta({ description: "OAuth authorization code" }),
- }),
- ),
+ validator("json", ProviderAuth.CallbackInput.zod),
async (c) => {
const providerID = c.req.valid("param").providerID
const { method, code } = c.req.valid("json")