From 7243bd9e129ad7a615814981e58a64703e44f601 Mon Sep 17 00:00:00 2001 From: Dax Date: Tue, 21 Jul 2026 10:30:18 -0400 Subject: [PATCH] fix: preserve runtime option defaults (#38118) --- packages/core/src/models-dev.ts | 2 +- packages/core/test/models.test.ts | 10 ++++++++++ packages/server/src/options.ts | 2 +- packages/server/src/routes.ts | 4 +--- packages/server/test/options.test.ts | 14 ++++++++++++++ 5 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 packages/server/test/options.test.ts diff --git a/packages/core/src/models-dev.ts b/packages/core/src/models-dev.ts index 2e181493e4..9261637ce6 100644 --- a/packages/core/src/models-dev.ts +++ b/packages/core/src/models-dev.ts @@ -552,7 +552,7 @@ export const layer = (options?: Options) => Layer.effect( ), ) - const source = options?.url ?? "https://models.dev" + const source = options?.url || "https://models.dev" const fetch = options?.fetch ?? true const userAgent = `opencode/${InstallationChannel}/${InstallationVersion}/${options?.client ?? "cli"}` const filepath = path.join( diff --git a/packages/core/test/models.test.ts b/packages/core/test/models.test.ts index 0060281f37..81301ac3ca 100644 --- a/packages/core/test/models.test.ts +++ b/packages/core/test/models.test.ts @@ -236,6 +236,16 @@ describe("ModelsDev Service", () => { }), ) + it.live("uses the default models URL when the configured URL is empty", () => + Effect.gen(function* () { + const state = yield* Ref.make(initialState) + yield* ModelsDev.Service.use((service) => service.get()).pipe( + Effect.provide(buildLayer(state, { url: "", fetch: true })), + ) + expect((yield* Ref.get(state)).calls[0]?.url).toBe("https://models.dev/api.json") + }), + ) + it.live("get() is single-flight under concurrent calls", () => Effect.gen(function* () { yield* writeCache(fixture) diff --git a/packages/server/src/options.ts b/packages/server/src/options.ts index 30625dcfa0..6c49373395 100644 --- a/packages/server/src/options.ts +++ b/packages/server/src/options.ts @@ -7,7 +7,7 @@ export const ServerOptions = Schema.Struct({ client: Schema.optional(Schema.String), hostname: Schema.optional(Schema.String), port: Schema.optional( - Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(65_535)), + Schema.Int.check(Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(65_535)), ), password: Schema.optional(Schema.String), simulation: Schema.optional(Schema.Boolean), diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index 37878b9f7d..29802cf54e 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -30,7 +30,7 @@ import { SdkPlugins } from "@opencode-ai/core/plugin/sdk" import { ToolOutputStore } from "@opencode-ai/core/tool-output-store" import { WellKnown } from "@opencode-ai/core/wellknown" import { Watcher } from "@opencode-ai/core/filesystem/watcher" -import { HttpRouter, HttpServer } from "effect/unstable/http" +import { HttpRouter } from "effect/unstable/http" import { HttpApiBuilder } from "effect/unstable/httpapi" import { Context, Effect, Layer, Option } from "effect" import { Api } from "./api" @@ -143,5 +143,3 @@ function makeRoutes( }), ) } - -export const webHandler = () => HttpRouter.toWebHandler(createRoutes().pipe(Layer.provide(HttpServer.layerServices))) diff --git a/packages/server/test/options.test.ts b/packages/server/test/options.test.ts new file mode 100644 index 0000000000..8b7d4665d7 --- /dev/null +++ b/packages/server/test/options.test.ts @@ -0,0 +1,14 @@ +import { expect, test } from "bun:test" +import { ServerOptions } from "@opencode-ai/server/options" +import { Option, Schema } from "effect" + +const decode = Schema.decodeUnknownOption(ServerOptions) + +test("accepts ephemeral port zero", () => { + expect(Option.isSome(decode({ port: 0 }))).toBe(true) +}) + +test("rejects ports outside the valid range", () => { + expect(Option.isNone(decode({ port: -1 }))).toBe(true) + expect(Option.isNone(decode({ port: 65_536 }))).toBe(true) +})