summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-05-03 19:28:31 +0530
committerGitHub <[email protected]>2026-05-03 19:28:31 +0530
commit8694c5b68fc57e7e1bb8129b72b08e128dce9f17 (patch)
tree9f01358fb4ee2e425802499a219c15258e5b9a2d /packages
parent0a7d02c87cea5092f34aafba846d136870ac27bc (diff)
downloadopencode-8694c5b68fc57e7e1bb8129b72b08e128dce9f17.tar.gz
opencode-8694c5b68fc57e7e1bb8129b72b08e128dce9f17.zip
fix(auth): respect server username in clients (#25596)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/acp.ts10
-rw-r--r--packages/opencode/src/cli/cmd/run.ts9
-rw-r--r--packages/opencode/src/cli/cmd/tui/attach.ts13
-rw-r--r--packages/opencode/src/cli/cmd/tui/worker.ts11
-rw-r--r--packages/opencode/src/plugin/index.ts7
-rw-r--r--packages/opencode/src/server/auth.ts48
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts49
-rw-r--r--packages/opencode/src/server/routes/instance/httpapi/server.ts9
-rw-r--r--packages/opencode/test/server/auth.test.ts59
-rw-r--r--packages/opencode/test/server/httpapi-authorization.test.ts13
-rw-r--r--packages/opencode/test/server/httpapi-ui.test.ts8
11 files changed, 148 insertions, 88 deletions
diff --git a/packages/opencode/src/cli/cmd/acp.ts b/packages/opencode/src/cli/cmd/acp.ts
index 1bf52a0c8..e24262307 100644
--- a/packages/opencode/src/cli/cmd/acp.ts
+++ b/packages/opencode/src/cli/cmd/acp.ts
@@ -4,9 +4,9 @@ import { effectCmd } from "../effect-cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { ACP } from "@/acp/agent"
import { Server } from "@/server/server"
+import { ServerAuth } from "@/server/auth"
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import { withNetworkOptions, resolveNetworkOptions } from "../network"
-import { Flag } from "@opencode-ai/core/flag/flag"
const log = Log.create({ service: "acp-command" })
@@ -27,13 +27,7 @@ export const AcpCommand = effectCmd({
const sdk = createOpencodeClient({
baseUrl: `http://${server.hostname}:${server.port}`,
- headers: Flag.OPENCODE_SERVER_PASSWORD
- ? {
- Authorization: `Basic ${Buffer.from(
- `${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`,
- ).toString("base64")}`,
- }
- : undefined,
+ headers: ServerAuth.headers(),
})
const input = new WritableStream<Uint8Array>({
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 75f68e8ea..2ec0b179b 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -5,6 +5,7 @@ import { Effect } from "effect"
import { UI } from "../ui"
import { effectCmd } from "../effect-cmd"
import { Flag } from "@opencode-ai/core/flag/flag"
+import { ServerAuth } from "@/server/auth"
import { EOL } from "os"
import { Filesystem } from "@/util/filesystem"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
@@ -656,13 +657,7 @@ export const RunCommand = effectCmd({
}
if (args.attach) {
- const headers = (() => {
- const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
- if (!password) return undefined
- const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode"
- const auth = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
- return { Authorization: auth }
- })()
+ const headers = ServerAuth.headers({ password: args.password })
const sdk = createOpencodeClient({ baseUrl: args.attach, directory, headers })
return await execute(sdk)
}
diff --git a/packages/opencode/src/cli/cmd/tui/attach.ts b/packages/opencode/src/cli/cmd/tui/attach.ts
index cb6b95a56..5de937fdc 100644
--- a/packages/opencode/src/cli/cmd/tui/attach.ts
+++ b/packages/opencode/src/cli/cmd/tui/attach.ts
@@ -5,6 +5,7 @@ import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
import { errorMessage } from "@/util/error"
import { validateSession } from "./validate-session"
+import { ServerAuth } from "@/server/auth"
export const AttachCommand = cmd({
command: "attach <url>",
@@ -38,6 +39,11 @@ export const AttachCommand = cmd({
alias: ["p"],
type: "string",
describe: "basic auth password (defaults to OPENCODE_SERVER_PASSWORD)",
+ })
+ .option("username", {
+ alias: ["u"],
+ type: "string",
+ describe: "basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')",
}),
handler: async (args) => {
const unguard = win32InstallCtrlCGuard()
@@ -60,12 +66,7 @@ export const AttachCommand = cmd({
return args.dir
}
})()
- const headers = (() => {
- const password = args.password ?? process.env.OPENCODE_SERVER_PASSWORD
- if (!password) return undefined
- const auth = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}`
- return { Authorization: auth }
- })()
+ const headers = ServerAuth.headers({ password: args.password, username: args.username })
const config = await TuiConfig.get()
try {
diff --git a/packages/opencode/src/cli/cmd/tui/worker.ts b/packages/opencode/src/cli/cmd/tui/worker.ts
index 775f321bb..90ff2b4d4 100644
--- a/packages/opencode/src/cli/cmd/tui/worker.ts
+++ b/packages/opencode/src/cli/cmd/tui/worker.ts
@@ -7,7 +7,7 @@ import { Rpc } from "@/util/rpc"
import { upgrade } from "@/cli/upgrade"
import { Config } from "@/config/config"
import { GlobalBus } from "@/bus/global"
-import { Flag } from "@opencode-ai/core/flag/flag"
+import { ServerAuth } from "@/server/auth"
import { writeHeapSnapshot } from "node:v8"
import { Heap } from "@/cli/heap"
import { AppRuntime } from "@/effect/app-runtime"
@@ -50,7 +50,7 @@ let server: Awaited<ReturnType<typeof Server.listen>> | undefined
export const rpc = {
async fetch(input: { url: string; method: string; headers: Record<string, string>; body?: string }) {
const headers = { ...input.headers }
- const auth = getAuthorizationHeader()
+ const auth = ServerAuth.header()
if (auth && !headers["authorization"] && !headers["Authorization"]) {
headers["Authorization"] = auth
}
@@ -102,10 +102,3 @@ export const rpc = {
}
Rpc.listen(rpc)
-
-function getAuthorizationHeader(): string | undefined {
- const password = Flag.OPENCODE_SERVER_PASSWORD
- if (!password) return undefined
- const username = Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
- return `Basic ${btoa(`${username}:${password}`)}`
-}
diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts
index 95af410ff..7a7f260df 100644
--- a/packages/opencode/src/plugin/index.ts
+++ b/packages/opencode/src/plugin/index.ts
@@ -10,6 +10,7 @@ import { Bus } from "../bus"
import * as Log from "@opencode-ai/core/util/log"
import { createOpencodeClient } from "@opencode-ai/sdk"
import { Flag } from "@opencode-ai/core/flag/flag"
+import { ServerAuth } from "@/server/auth"
import { CodexAuthPlugin } from "./codex"
import { Session } from "@/session/session"
import { NamedError } from "@opencode-ai/core/util/error"
@@ -124,11 +125,7 @@ export const layer = Layer.effect(
const client = createOpencodeClient({
baseUrl: "http://localhost:4096",
directory: ctx.directory,
- headers: Flag.OPENCODE_SERVER_PASSWORD
- ? {
- Authorization: `Basic ${Buffer.from(`${Flag.OPENCODE_SERVER_USERNAME ?? "opencode"}:${Flag.OPENCODE_SERVER_PASSWORD}`).toString("base64")}`,
- }
- : undefined,
+ headers: ServerAuth.headers(),
fetch: async (...args) => Server.Default().app.fetch(...args),
})
const cfg = yield* config.get()
diff --git a/packages/opencode/src/server/auth.ts b/packages/opencode/src/server/auth.ts
new file mode 100644
index 000000000..9630ddbe2
--- /dev/null
+++ b/packages/opencode/src/server/auth.ts
@@ -0,0 +1,48 @@
+export * as ServerAuth from "./auth"
+
+import { ConfigService } from "@/effect/config-service"
+import { Flag } from "@opencode-ai/core/flag/flag"
+import { Config as EffectConfig, Context, Option, Redacted } from "effect"
+
+export type Credentials = {
+ password?: string
+ username?: string
+}
+
+export type DecodedCredentials = {
+ readonly username: string
+ readonly password: Redacted.Redacted
+}
+
+export class Config extends ConfigService.Service<Config>()("@opencode/ServerAuthConfig", {
+ password: EffectConfig.string("OPENCODE_SERVER_PASSWORD").pipe(EffectConfig.option),
+ username: EffectConfig.string("OPENCODE_SERVER_USERNAME").pipe(EffectConfig.withDefault("opencode")),
+}) {}
+
+export type Info = Context.Service.Shape<typeof Config>
+
+export function required(config: Info) {
+ return Option.isSome(config.password) && config.password.value !== ""
+}
+
+export function authorized(credentials: DecodedCredentials, config: Info) {
+ return (
+ Option.isSome(config.password) &&
+ credentials.username === config.username &&
+ Redacted.value(credentials.password) === config.password.value
+ )
+}
+
+export function header(credentials?: Credentials) {
+ const password = credentials?.password ?? Flag.OPENCODE_SERVER_PASSWORD
+ if (!password) return undefined
+
+ const username = credentials?.username ?? Flag.OPENCODE_SERVER_USERNAME ?? "opencode"
+ return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`
+}
+
+export function headers(credentials?: Credentials) {
+ const authorization = header(credentials)
+ if (!authorization) return undefined
+ return { Authorization: authorization }
+}
diff --git a/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts b/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts
index 4edd06479..bd9552edc 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/middleware/authorization.ts
@@ -1,5 +1,5 @@
-import { ConfigService } from "@/effect/config-service"
-import { Config, Context, Effect, Encoding, Layer, Option, Redacted } from "effect"
+import { ServerAuth } from "@/server/auth"
+import { Effect, Encoding, Layer, Redacted } from "effect"
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
import { HttpApiError, HttpApiMiddleware, HttpApiSecurity } from "effect/unstable/httpapi"
@@ -18,41 +18,18 @@ export class Authorization extends HttpApiMiddleware.Service<Authorization>()(
},
) {}
-export class ServerAuthConfig extends ConfigService.Service<ServerAuthConfig>()(
- "@opencode/ExperimentalHttpApiServerAuthConfig",
- {
- password: Config.string("OPENCODE_SERVER_PASSWORD").pipe(Config.option),
- username: Config.string("OPENCODE_SERVER_USERNAME").pipe(Config.withDefault("opencode")),
- },
-) {}
-
function validateCredential<A, E, R>(
effect: Effect.Effect<A, E, R>,
- credential: { readonly username: string; readonly password: Redacted.Redacted },
- config: Context.Service.Shape<typeof ServerAuthConfig>,
+ credential: ServerAuth.DecodedCredentials,
+ config: ServerAuth.Info,
) {
return Effect.gen(function* () {
- if (!isAuthRequired(config)) return yield* effect
- if (!isCredentialAuthorized(credential, config)) return yield* new HttpApiError.Unauthorized({})
+ if (!ServerAuth.required(config)) return yield* effect
+ if (!ServerAuth.authorized(credential, config)) return yield* new HttpApiError.Unauthorized({})
return yield* effect
})
}
-function isAuthRequired(config: Context.Service.Shape<typeof ServerAuthConfig>) {
- return Option.isSome(config.password) && config.password.value !== ""
-}
-
-function isCredentialAuthorized(
- credential: { readonly username: string; readonly password: Redacted.Redacted },
- config: Context.Service.Shape<typeof ServerAuthConfig>,
-) {
- return (
- Option.isSome(config.password) &&
- credential.username === config.username &&
- Redacted.value(credential.password) === config.password.value
- )
-}
-
function decodeCredential(input: string) {
const emptyCredential = {
username: "",
@@ -78,11 +55,11 @@ function decodeCredential(input: string) {
function validateRawCredential<A, E, R>(
effect: Effect.Effect<A, E, R>,
- credential: { readonly username: string; readonly password: Redacted.Redacted },
- config: Context.Service.Shape<typeof ServerAuthConfig>,
+ credential: ServerAuth.DecodedCredentials,
+ config: ServerAuth.Info,
) {
- if (!isAuthRequired(config)) return effect
- if (!isCredentialAuthorized(credential, config))
+ if (!ServerAuth.required(config)) return effect
+ if (!ServerAuth.authorized(credential, config))
return Effect.succeed(
HttpServerResponse.empty({
status: UNAUTHORIZED,
@@ -94,8 +71,8 @@ function validateRawCredential<A, E, R>(
export const authorizationRouterMiddleware = HttpRouter.middleware()(
Effect.gen(function* () {
- const config = yield* ServerAuthConfig
- if (!isAuthRequired(config)) return (effect) => effect
+ const config = yield* ServerAuth.Config
+ if (!ServerAuth.required(config)) return (effect) => effect
return (effect) =>
Effect.gen(function* () {
@@ -122,7 +99,7 @@ export const authorizationRouterMiddleware = HttpRouter.middleware()(
export const authorizationLayer = Layer.effect(
Authorization,
Effect.gen(function* () {
- const config = yield* ServerAuthConfig
+ const config = yield* ServerAuth.Config
return Authorization.of({
basic: (effect, { credential }) => validateCredential(effect, credential, config),
authToken: (effect, { credential }) =>
diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts
index 650efe2b0..2944ced69 100644
--- a/packages/opencode/src/server/routes/instance/httpapi/server.ts
+++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts
@@ -46,8 +46,9 @@ import { Worktree } from "@/worktree"
import { Workspace } from "@/control-plane/workspace"
import { isAllowedCorsOrigin, type CorsOptions } from "@/server/cors"
import { serveUIEffect } from "@/server/shared/ui"
+import { ServerAuth } from "@/server/auth"
import { InstanceHttpApi, RootHttpApi } from "./api"
-import { ServerAuthConfig, authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
+import { authorizationLayer, authorizationRouterMiddleware } from "./middleware/authorization"
import { EventApi, eventHandlers } from "./event"
import { configHandlers } from "./handlers/config"
import { controlHandlers } from "./handlers/control"
@@ -97,7 +98,7 @@ const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(Layer.provide([cont
const instanceRouterLayer = authorizationRouterMiddleware
.combine(instanceRouterMiddleware)
.combine(workspaceRouterMiddleware)
- .layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuthConfig.defaultLayer))
+ .layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuth.Config.defaultLayer))
const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe(
Layer.provide(eventHandlers),
Layer.provide(instanceRouterLayer),
@@ -125,7 +126,7 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
Layer.provide([
- authorizationLayer.pipe(Layer.provide(ServerAuthConfig.defaultLayer)),
+ authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer)),
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
instanceContextLayer,
]),
@@ -137,7 +138,7 @@ const uiRoute = HttpRouter.use((router) =>
const client = yield* HttpClient.HttpClient
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
}),
-).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))))
+).pipe(Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))))
export function createRoutes(corsOptions?: CorsOptions) {
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, uiRoute).pipe(
diff --git a/packages/opencode/test/server/auth.test.ts b/packages/opencode/test/server/auth.test.ts
new file mode 100644
index 000000000..1278e8c72
--- /dev/null
+++ b/packages/opencode/test/server/auth.test.ts
@@ -0,0 +1,59 @@
+import { afterEach, describe, expect, test } from "bun:test"
+import { Option, Redacted } from "effect"
+import { Flag } from "@opencode-ai/core/flag/flag"
+import { ServerAuth } from "../../src/server/auth"
+
+const original = {
+ OPENCODE_SERVER_PASSWORD: Flag.OPENCODE_SERVER_PASSWORD,
+ OPENCODE_SERVER_USERNAME: Flag.OPENCODE_SERVER_USERNAME,
+}
+
+afterEach(() => {
+ Flag.OPENCODE_SERVER_PASSWORD = original.OPENCODE_SERVER_PASSWORD
+ Flag.OPENCODE_SERVER_USERNAME = original.OPENCODE_SERVER_USERNAME
+})
+
+describe("ServerAuth", () => {
+ test("does not emit auth headers without a password", () => {
+ Flag.OPENCODE_SERVER_PASSWORD = undefined
+ Flag.OPENCODE_SERVER_USERNAME = "alice"
+
+ expect(ServerAuth.header()).toBeUndefined()
+ expect(ServerAuth.headers()).toBeUndefined()
+ })
+
+ test("defaults to the opencode username", () => {
+ Flag.OPENCODE_SERVER_PASSWORD = "secret"
+ Flag.OPENCODE_SERVER_USERNAME = undefined
+
+ expect(ServerAuth.headers()).toEqual({
+ Authorization: `Basic ${Buffer.from("opencode:secret").toString("base64")}`,
+ })
+ })
+
+ test("uses the configured username", () => {
+ Flag.OPENCODE_SERVER_PASSWORD = "secret"
+ Flag.OPENCODE_SERVER_USERNAME = "alice"
+
+ expect(ServerAuth.headers()).toEqual({
+ Authorization: `Basic ${Buffer.from("alice:secret").toString("base64")}`,
+ })
+ })
+
+ test("prefers explicit credentials", () => {
+ Flag.OPENCODE_SERVER_PASSWORD = "secret"
+ Flag.OPENCODE_SERVER_USERNAME = "alice"
+
+ expect(ServerAuth.headers({ password: "cli-secret", username: "bob" })).toEqual({
+ Authorization: `Basic ${Buffer.from("bob:cli-secret").toString("base64")}`,
+ })
+ })
+
+ test("validates decoded credentials against effect config", () => {
+ const config = { password: Option.some("secret"), username: "alice" }
+
+ expect(ServerAuth.required(config)).toBe(true)
+ expect(ServerAuth.authorized({ username: "alice", password: Redacted.make("secret") }, config)).toBe(true)
+ expect(ServerAuth.authorized({ username: "opencode", password: Redacted.make("secret") }, config)).toBe(false)
+ })
+})
diff --git a/packages/opencode/test/server/httpapi-authorization.test.ts b/packages/opencode/test/server/httpapi-authorization.test.ts
index c3bab23ac..d780b18f2 100644
--- a/packages/opencode/test/server/httpapi-authorization.test.ts
+++ b/packages/opencode/test/server/httpapi-authorization.test.ts
@@ -3,11 +3,8 @@ import { describe, expect } from "bun:test"
import { Effect, Layer, Option, Schema } from "effect"
import { HttpClient, HttpClientRequest, HttpRouter } from "effect/unstable/http"
import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"
-import {
- Authorization,
- ServerAuthConfig,
- authorizationLayer,
-} from "../../src/server/routes/instance/httpapi/middleware/authorization"
+import { ServerAuth } from "../../src/server/auth"
+import { Authorization, authorizationLayer } from "../../src/server/routes/instance/httpapi/middleware/authorization"
import { testEffect } from "../lib/effect"
const Api = HttpApi.make("test-authorization").add(
@@ -27,9 +24,9 @@ const apiLayer = HttpRouter.serve(
{ disableListenLog: true, disableLogger: true },
).pipe(Layer.provideMerge(NodeHttpServer.layerTest))
-const noAuthLayer = ServerAuthConfig.layer({ password: Option.none(), username: "opencode" })
-const secretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "opencode" })
-const kitSecretLayer = ServerAuthConfig.layer({ password: Option.some("secret"), username: "kit" })
+const noAuthLayer = ServerAuth.Config.layer({ password: Option.none(), username: "opencode" })
+const secretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "opencode" })
+const kitSecretLayer = ServerAuth.Config.layer({ password: Option.some("secret"), username: "kit" })
const it = testEffect(apiLayer.pipe(Layer.provide(noAuthLayer)))
const itSecret = testEffect(apiLayer.pipe(Layer.provide(secretLayer)))
diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts
index 1de8a489c..8b7a6a1ac 100644
--- a/packages/opencode/test/server/httpapi-ui.test.ts
+++ b/packages/opencode/test/server/httpapi-ui.test.ts
@@ -12,10 +12,8 @@ import {
HttpServerResponse,
} from "effect/unstable/http"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
-import {
- ServerAuthConfig,
- authorizationRouterMiddleware,
-} from "../../src/server/routes/instance/httpapi/middleware/authorization"
+import { ServerAuth } from "../../src/server/auth"
+import { authorizationRouterMiddleware } from "../../src/server/routes/instance/httpapi/middleware/authorization"
import { ExperimentalHttpApiServer } from "../../src/server/routes/instance/httpapi/server"
import { serveUIEffect } from "../../src/server/shared/ui"
import { Server } from "../../src/server/server"
@@ -81,7 +79,7 @@ function uiApp(input?: { password?: string; username?: string; client?: Layer.La
yield* router.add("*", "/*", (request) => serveUIEffect(request, { fs, client }))
}),
).pipe(
- Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuthConfig.defaultLayer))),
+ Layer.provide(authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))),
Layer.provide([
AppFileSystem.defaultLayer,
input?.client ?? httpClient(new Response("ui")),