mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-17 19:34:49 +00:00
Compare commits
4 Commits
kit/ns-sta
...
kit/config
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5435a699b8 | ||
|
|
2a118b5214 | ||
|
|
0c430ada0b | ||
|
|
370690c1f8 |
175
packages/opencode/src/server/instance/httpapi/config.ts
Normal file
175
packages/opencode/src/server/instance/httpapi/config.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import { AppLayer } from "@/effect/app-runtime"
|
||||
import { memoMap } from "@/effect/run-service"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Effect, Layer, Schema } from "effect"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import type { Handler } from "hono"
|
||||
import { mapValues } from "remeda"
|
||||
|
||||
const ApiInfo = Schema.Struct({
|
||||
id: Schema.String,
|
||||
url: Schema.String,
|
||||
npm: Schema.String,
|
||||
}).annotate({ identifier: "ConfigProvidersModelApi" })
|
||||
|
||||
const Mode = Schema.Struct({
|
||||
text: Schema.Boolean,
|
||||
audio: Schema.Boolean,
|
||||
image: Schema.Boolean,
|
||||
video: Schema.Boolean,
|
||||
pdf: Schema.Boolean,
|
||||
}).annotate({ identifier: "ConfigProvidersModelMode" })
|
||||
|
||||
const Interleaved = Schema.Union([
|
||||
Schema.Boolean,
|
||||
Schema.Struct({
|
||||
field: Schema.Literals(["reasoning_content", "reasoning_details"]),
|
||||
}),
|
||||
]).annotate({ identifier: "ConfigProvidersModelInterleaved" })
|
||||
|
||||
const Capabilities = Schema.Struct({
|
||||
temperature: Schema.Boolean,
|
||||
reasoning: Schema.Boolean,
|
||||
attachment: Schema.Boolean,
|
||||
toolcall: Schema.Boolean,
|
||||
input: Mode,
|
||||
output: Mode,
|
||||
interleaved: Interleaved,
|
||||
}).annotate({ identifier: "ConfigProvidersModelCapabilities" })
|
||||
|
||||
const Cache = Schema.Struct({
|
||||
read: Schema.Number,
|
||||
write: Schema.Number,
|
||||
}).annotate({ identifier: "ConfigProvidersModelCache" })
|
||||
|
||||
const ExperimentalOver200K = Schema.Struct({
|
||||
input: Schema.Number,
|
||||
output: Schema.Number,
|
||||
cache: Cache,
|
||||
})
|
||||
.pipe(Schema.optional)
|
||||
.annotate({ identifier: "ConfigProvidersModelExperimentalOver200K" })
|
||||
|
||||
const Cost = Schema.Struct({
|
||||
input: Schema.Number,
|
||||
output: Schema.Number,
|
||||
cache: Cache,
|
||||
experimentalOver200K: ExperimentalOver200K,
|
||||
}).annotate({ identifier: "ConfigProvidersModelCost" })
|
||||
|
||||
const Limit = Schema.Struct({
|
||||
context: Schema.Number,
|
||||
input: Schema.optional(Schema.Number),
|
||||
output: Schema.Number,
|
||||
}).annotate({ identifier: "ConfigProvidersModelLimit" })
|
||||
|
||||
const Model = Schema.Struct({
|
||||
id: Schema.String,
|
||||
providerID: Schema.String,
|
||||
api: ApiInfo,
|
||||
name: Schema.String,
|
||||
family: Schema.optional(Schema.String),
|
||||
capabilities: Capabilities,
|
||||
cost: Cost,
|
||||
limit: Limit,
|
||||
status: Schema.Union([
|
||||
Schema.Literal("alpha"),
|
||||
Schema.Literal("beta"),
|
||||
Schema.Literal("deprecated"),
|
||||
Schema.Literal("active"),
|
||||
]),
|
||||
options: Schema.Record(Schema.String, Schema.Unknown),
|
||||
headers: Schema.Record(Schema.String, Schema.String),
|
||||
release_date: Schema.String,
|
||||
variants: Schema.optional(Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown))),
|
||||
}).annotate({ identifier: "ConfigProvidersModel" })
|
||||
|
||||
const ProviderInfo = Schema.Struct({
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
source: Schema.Union([
|
||||
Schema.Literal("env"),
|
||||
Schema.Literal("config"),
|
||||
Schema.Literal("custom"),
|
||||
Schema.Literal("api"),
|
||||
]),
|
||||
env: Schema.Array(Schema.String),
|
||||
key: Schema.optional(Schema.String),
|
||||
options: Schema.Record(Schema.String, Schema.Unknown),
|
||||
models: Schema.Record(Schema.String, Schema.Unknown),
|
||||
}).annotate({ identifier: "ConfigProvidersProvider" })
|
||||
|
||||
const Providers = Schema.Unknown
|
||||
|
||||
const root = "/experimental/httpapi/config"
|
||||
|
||||
const Api = HttpApi.make("config")
|
||||
.add(
|
||||
HttpApiGroup.make("config")
|
||||
.add(
|
||||
HttpApiEndpoint.get("providers", `${root}/providers`, {
|
||||
success: Providers,
|
||||
}).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.",
|
||||
}),
|
||||
)
|
||||
|
||||
const ConfigLive = HttpApiBuilder.group(
|
||||
Api,
|
||||
"config",
|
||||
Effect.fn("ConfigHttpApi.handlers")(function* (handlers) {
|
||||
const svc = yield* Provider.Service
|
||||
|
||||
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
|
||||
const all = mapValues(yield* svc.list(), (item) => item)
|
||||
return Schema.decodeUnknownSync(Providers)(
|
||||
JSON.parse(
|
||||
JSON.stringify({
|
||||
providers: Object.values(all),
|
||||
default: mapValues(all, (item) => Provider.sort(Object.values(item.models))[0].id),
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
return handlers.handle("providers", providers)
|
||||
}),
|
||||
).pipe(Layer.provide(Provider.defaultLayer))
|
||||
|
||||
const web = lazy(() =>
|
||||
HttpRouter.toWebHandler(
|
||||
Layer.mergeAll(
|
||||
AppLayer,
|
||||
HttpApiBuilder.layer(Api, { openapiPath: `${root}/doc` }).pipe(
|
||||
Layer.provide(ConfigLive),
|
||||
Layer.provide(HttpServer.layerServices),
|
||||
),
|
||||
),
|
||||
{
|
||||
disableLogger: true,
|
||||
memoMap,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
export const ConfigHttpApiHandler: Handler = (c, _next) => web().handler(c.req.raw)
|
||||
@@ -1,7 +1,12 @@
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Hono } from "hono"
|
||||
import { ConfigHttpApiHandler } from "./config"
|
||||
import { QuestionHttpApiHandler } from "./question"
|
||||
|
||||
export const HttpApiRoutes = lazy(() =>
|
||||
new Hono().all("/question", QuestionHttpApiHandler).all("/question/*", QuestionHttpApiHandler),
|
||||
new Hono()
|
||||
.all("/question", QuestionHttpApiHandler)
|
||||
.all("/question/*", QuestionHttpApiHandler)
|
||||
.all("/config", ConfigHttpApiHandler)
|
||||
.all("/config/*", ConfigHttpApiHandler),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { Server } from "../../src/server/server"
|
||||
import { Log } from "../../src/util/log"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
|
||||
Log.init({ print: false })
|
||||
|
||||
afterEach(async () => {
|
||||
await Instance.disposeAll()
|
||||
})
|
||||
|
||||
describe("experimental config providers httpapi", () => {
|
||||
test("lists config providers and serves docs", async () => {
|
||||
await using tmp = await tmpdir({ git: true })
|
||||
const app = Server.Default().app
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
"x-opencode-directory": tmp.path,
|
||||
}
|
||||
|
||||
const res = await app.request("/experimental/httpapi/config/providers", { headers })
|
||||
expect(res.status).toBe(200)
|
||||
const body = await res.json()
|
||||
expect(Array.isArray(body.providers)).toBe(true)
|
||||
expect(typeof body.default).toBe("object")
|
||||
|
||||
const doc = await app.request("/experimental/httpapi/config/doc", { headers })
|
||||
expect(doc.status).toBe(200)
|
||||
const spec = await doc.json()
|
||||
expect(spec.paths["/experimental/httpapi/config/providers"]?.get?.operationId).toBe("config.providers")
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user