fix(server): keep provider lists JSON-safe (#26550)
This commit is contained in:
@@ -935,6 +935,16 @@ export const ConfigProvidersResult = Schema.Struct({
|
||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||
export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>
|
||||
|
||||
export function toPublicInfo(provider: Info): Info {
|
||||
return JSON.parse(
|
||||
JSON.stringify(provider, (_, value) => {
|
||||
if (typeof value === "function" || typeof value === "symbol" || value === undefined) return undefined
|
||||
if (typeof value === "bigint") return value.toString()
|
||||
return value
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1299,7 +1309,7 @@ const layer: Layer.Layer<
|
||||
const options = yield* Effect.promise(() =>
|
||||
plugin.auth!.loader!(
|
||||
() => bridge.promise(auth.get(providerID).pipe(Effect.orDie)) as any,
|
||||
database[plugin.auth!.provider],
|
||||
toPublicInfo(database[plugin.auth!.provider]),
|
||||
),
|
||||
)
|
||||
const opts = options ?? {}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const configHandlers = HttpApiBuilder.group(InstanceHttpApi, "config", (h
|
||||
const providers = Effect.fn("ConfigHttpApi.providers")(function* () {
|
||||
const providers = yield* providerSvc.list()
|
||||
return {
|
||||
providers: Object.values(providers),
|
||||
providers: Object.values(providers).map(Provider.toPublicInfo),
|
||||
default: Provider.defaultModelIDs(providers),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ export const providerHandlers = HttpApiBuilder.group(InstanceHttpApi, "provider"
|
||||
connected,
|
||||
)
|
||||
return {
|
||||
all: Object.values(providers),
|
||||
all: Object.values(providers).map(Provider.toPublicInfo),
|
||||
default: Provider.defaultModelIDs(providers),
|
||||
connected: Object.keys(connected),
|
||||
}
|
||||
|
||||
@@ -6,39 +6,13 @@ import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Cause, Effect } from "effect"
|
||||
import { HttpRouter, HttpServerError, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiError } from "effect/unstable/httpapi"
|
||||
import { HttpApiSchemaError } from "effect/unstable/httpapi/HttpApiError"
|
||||
|
||||
const log = Log.create({ service: "server" })
|
||||
|
||||
function badRequestResponse() {
|
||||
return HttpServerResponse.jsonUnsafe(
|
||||
{
|
||||
data: {},
|
||||
errors: [],
|
||||
success: false,
|
||||
},
|
||||
{ status: 400 },
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeEmptyBadRequest(response: HttpServerResponse.HttpServerResponse) {
|
||||
if (response.status !== 400 || response.body._tag !== "Empty") return response
|
||||
return badRequestResponse()
|
||||
}
|
||||
|
||||
// Keep typed HttpApi failures on their declared error path; this boundary only replaces defect-only empty 500s.
|
||||
export const errorLayer = HttpRouter.middleware<{ handles: unknown }>()((effect) =>
|
||||
effect.pipe(
|
||||
Effect.catch((error) => {
|
||||
if (error instanceof HttpApiError.BadRequest) return Effect.succeed(badRequestResponse())
|
||||
return Effect.fail(error)
|
||||
}),
|
||||
Effect.map(normalizeEmptyBadRequest),
|
||||
Effect.catchCause((cause) => {
|
||||
const schemaError = cause.reasons.filter(Cause.isDieReason).find((reason) => HttpApiSchemaError.is(reason.defect))
|
||||
if (schemaError) return Effect.succeed(badRequestResponse())
|
||||
|
||||
const defect = cause.reasons.filter(Cause.isDieReason).find((reason) => {
|
||||
if (HttpServerResponse.isHttpServerResponse(reason.defect)) return false
|
||||
if (HttpServerError.isHttpServerError(reason.defect)) return false
|
||||
|
||||
Reference in New Issue
Block a user