fix(server): keep provider lists JSON-safe (#26550)

This commit is contained in:
Kit Langton
2026-05-09 13:40:46 -04:00
committed by GitHub
parent b1cd25de3d
commit 27fa297a42
5 changed files with 101 additions and 29 deletions

View File

@@ -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 ?? {}

View File

@@ -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),
}
})

View File

@@ -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),
}

View File

@@ -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