feat(core): add command registry (#30624)

This commit is contained in:
Dax 2026-06-04 02:57:43 -04:00 committed by GitHub
commit 1ff19103a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
150 changed files with 4642 additions and 2546 deletions

View file

@ -0,0 +1,60 @@
import { ServerAuth } from "../auth"
import { UnauthorizedError } from "../errors"
import { Effect, Encoding, Layer, Redacted } from "effect"
import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
const AUTH_TOKEN_QUERY = "auth_token"
const WWW_AUTHENTICATE = 'Basic realm="Secure Area"'
export class V2Authorization extends HttpApiMiddleware.Service<V2Authorization>()(
"@opencode/ExperimentalHttpApiV2Authorization",
{
error: UnauthorizedError,
},
) {}
function emptyCredential() {
return { username: "", password: Redacted.make("") }
}
function decodeCredential(input: string) {
return Effect.fromResult(Encoding.decodeBase64String(input)).pipe(
Effect.match({
onFailure: emptyCredential,
onSuccess: (header) => {
const separator = header.indexOf(":")
if (separator === -1) return emptyCredential()
return { username: header.slice(0, separator), password: Redacted.make(header.slice(separator + 1)) }
},
}),
)
}
function credentialFromRequest(request: HttpServerRequest.HttpServerRequest) {
const url = new URL(request.url, "http://localhost")
const token = url.searchParams.get(AUTH_TOKEN_QUERY)
if (token) return decodeCredential(token)
const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "")
if (match) return decodeCredential(match[1])
return Effect.succeed(emptyCredential())
}
export const v2AuthorizationLayer = Layer.effect(
V2Authorization,
Effect.gen(function* () {
const config = yield* ServerAuth.Config
if (!ServerAuth.required(config)) return V2Authorization.of((effect) => effect)
return V2Authorization.of((effect) =>
Effect.gen(function* () {
const request = yield* HttpServerRequest.HttpServerRequest
const credential = yield* credentialFromRequest(request)
if (ServerAuth.authorized(credential, config)) return yield* effect
yield* HttpEffect.appendPreResponseHandler((_request, response) =>
Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)),
)
return yield* new UnauthorizedError({ message: "Authentication required" })
}),
)
}),
)

View file

@ -0,0 +1,23 @@
import * as Log from "@opencode-ai/core/util/log"
import { Effect } from "effect"
import { HttpApiMiddleware } from "effect/unstable/httpapi"
import { InvalidRequestError } from "../errors"
const log = Log.create({ service: "server" })
const REASON_LIMIT = 1024
function truncateReason(reason: string) {
if (reason.length <= REASON_LIMIT) return reason
return reason.slice(0, REASON_LIMIT) + `... (${reason.length - REASON_LIMIT} more chars)`
}
export class SchemaErrorMiddleware extends HttpApiMiddleware.Service<SchemaErrorMiddleware>()(
"@opencode/HttpApiSchemaError",
{ error: InvalidRequestError },
) {}
export const schemaErrorLayer = HttpApiMiddleware.layerSchemaErrorTransform(SchemaErrorMiddleware, (error) => {
const reason = truncateReason(error.cause.message)
log.warn("schema rejection", { kind: error.kind, reason })
return Effect.fail(new InvalidRequestError({ message: reason, kind: error.kind }))
})