169 lines
4.9 KiB
TypeScript
169 lines
4.9 KiB
TypeScript
|
|
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.Union([Schema.Literal("reasoning_content"), Schema.Literal("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 providers = Effect.fn("ConfigHttpApi.providers")(function* () {
|
||
|
|
const svc = yield* Provider.Service
|
||
|
|
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),
|
||
|
|
}),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
const ConfigLive = HttpApiBuilder.group(Api, "config", (handlers) => handlers.handle("providers", providers))
|
||
|
|
|
||
|
|
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)
|