Skip to content

Commit 0712ef6

Browse files
committed
Default dev and beta to HttpApi
1 parent 379e7f3 commit 0712ef6

9 files changed

Lines changed: 109 additions & 22 deletions

File tree

packages/opencode/src/server/middleware.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
1010
import { basicAuth } from "hono/basic-auth"
1111
import { cors } from "hono/cors"
1212
import { compress } from "hono/compress"
13+
import * as ServerRuntime from "./runtime"
1314

1415
const log = Log.create({ service: "server" })
1516

@@ -49,20 +50,20 @@ export const AuthMiddleware: MiddlewareHandler = (c, next) => {
4950
return basicAuth({ username, password })(c, next)
5051
}
5152

52-
export const LoggerMiddleware: MiddlewareHandler = async (c, next) => {
53-
const skip = c.req.path === "/log"
54-
if (!skip) {
55-
log.info("request", {
53+
export function LoggerMiddleware(runtimeAttributes: ServerRuntime.Attributes): MiddlewareHandler {
54+
return async (c, next) => {
55+
const skip = c.req.path === "/log"
56+
if (skip) return next()
57+
const attributes = {
5658
method: c.req.method,
5759
path: c.req.path,
58-
})
60+
...runtimeAttributes,
61+
}
62+
log.info("request", attributes)
63+
const timer = log.time("request", attributes)
64+
await next()
65+
timer.stop()
5966
}
60-
const timer = log.time("request", {
61-
method: c.req.method,
62-
path: c.req.path,
63-
})
64-
await next()
65-
if (!skip) timer.stop()
6667
}
6768

6869
export function CorsMiddleware(opts?: { cors?: string[] }): MiddlewareHandler {

packages/opencode/src/server/routes/instance/httpapi/server.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { TuiApi, tuiHandlers } from "./tui"
5555
import { WorkspaceApi, workspaceHandlers } from "./workspace"
5656
import { disposeMiddleware } from "./lifecycle"
5757
import { memoMap } from "@opencode-ai/core/effect/memo-map"
58+
import * as ServerRuntime from "@/server/runtime"
5859

5960
const Query = Schema.Struct({
6061
directory: Schema.optional(Schema.String),
@@ -99,6 +100,18 @@ const instance = HttpRouter.middleware()(
99100
}),
100101
).layer
101102

103+
const runtime = HttpRouter.middleware()(
104+
Effect.succeed((effect) =>
105+
Effect.gen(function* () {
106+
const selected = ServerRuntime.select()
107+
yield* Effect.annotateCurrentSpan(
108+
ServerRuntime.attributes(ServerRuntime.force(selected, "effect-httpapi")),
109+
)
110+
return yield* effect
111+
}),
112+
),
113+
).layer
114+
102115
const controlRoutes = HttpApiBuilder.layer(ControlApi).pipe(Layer.provide(controlHandlers))
103116
const globalRoutes = HttpApiBuilder.layer(GlobalApi).pipe(Layer.provide(globalHandlers))
104117
const instanceApiRoutes = Layer.mergeAll(
@@ -125,6 +138,7 @@ const instanceRoutes = Layer.mergeAll(eventRoute, ptyConnectRoute, instanceApiRo
125138

126139
export const routes = Layer.mergeAll(controlRoutes, globalRoutes, instanceRoutes)
127140
.pipe(
141+
Layer.provide(runtime),
128142
Layer.provide(Account.defaultLayer),
129143
Layer.provide(Agent.defaultLayer),
130144
Layer.provide(Auth.defaultLayer),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Flag } from "@opencode-ai/core/flag/flag"
2+
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
3+
4+
export type Runtime = "effect-httpapi" | "hono"
5+
6+
export type Selection = {
7+
runtime: Runtime
8+
reason: "env" | "channel" | "stable" | "explicit"
9+
}
10+
11+
export type Attributes = ReturnType<typeof attributes>
12+
13+
const channelDefaultsToHttpApi = () =>
14+
InstallationChannel === "local" ||
15+
InstallationChannel === "dev" ||
16+
InstallationChannel === "beta" ||
17+
InstallationVersion.includes("-dev") ||
18+
InstallationVersion.includes("-beta")
19+
20+
export function select(): Selection {
21+
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return { runtime: "effect-httpapi", reason: "env" }
22+
if (channelDefaultsToHttpApi()) return { runtime: "effect-httpapi", reason: "channel" }
23+
return { runtime: "hono", reason: "stable" }
24+
}
25+
26+
export function attributes(selection: Selection): Record<string, string> {
27+
return {
28+
"opencode.server.runtime": selection.runtime,
29+
"opencode.server.runtime.reason": selection.reason,
30+
"opencode.installation.channel": InstallationChannel,
31+
"opencode.installation.version": InstallationVersion,
32+
}
33+
}
34+
35+
export function force(selection: Selection, runtime: Runtime): Selection {
36+
return {
37+
runtime,
38+
reason: selection.runtime === runtime ? selection.reason : "explicit",
39+
}
40+
}

packages/opencode/src/server/server.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { WorkspaceRouterMiddleware } from "./workspace"
1717
import { InstanceMiddleware } from "./routes/instance/middleware"
1818
import { WorkspaceRoutes } from "./routes/control/workspace"
1919
import { ExperimentalHttpApiServer } from "./routes/instance/httpapi/server"
20+
import * as ServerRuntime from "./runtime"
2021

2122
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
2223
globalThis.AI_SDK_LOG_WARNINGS = false
@@ -37,13 +38,38 @@ type ServerApp = {
3738
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
3839
}
3940

40-
const DefaultHono = lazy(() => createHono({}))
41-
const DefaultHttpApi = lazy(() => createHttpApi())
42-
export const Default = () => (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI ? DefaultHttpApi() : DefaultHono())
41+
const DefaultHono = lazy(() => withRuntime({ runtime: "hono", reason: "stable" }, createHono({}, { runtime: "hono", reason: "stable" })))
42+
const DefaultHttpApi = lazy(() => createDefaultHttpApi())
43+
44+
function select() {
45+
return ServerRuntime.select()
46+
}
47+
48+
export const runtime = select
49+
50+
export const Default = () => {
51+
const selected = select()
52+
return selected.runtime === "effect-httpapi" ? DefaultHttpApi() : DefaultHono()
53+
}
4354

4455
function create(opts: { cors?: string[] }) {
45-
if (Flag.OPENCODE_EXPERIMENTAL_HTTPAPI) return createHttpApi()
46-
return createHono(opts)
56+
const selected = select()
57+
return selected.runtime === "effect-httpapi"
58+
? withRuntime(selected, createHttpApi())
59+
: withRuntime(selected, createHono(opts, selected))
60+
}
61+
62+
export function Legacy(opts: { cors?: string[] } = {}) {
63+
return withRuntime({ runtime: "hono", reason: "explicit" }, createHono(opts, { runtime: "hono", reason: "explicit" }))
64+
}
65+
66+
function createDefaultHttpApi() {
67+
return withRuntime(select(), createHttpApi())
68+
}
69+
70+
function withRuntime<T extends { app: ServerApp; runtime: unknown }>(selection: ServerRuntime.Selection, built: T) {
71+
log.info("server runtime selected", ServerRuntime.attributes(selection))
72+
return built
4773
}
4874

4975
function createHttpApi() {
@@ -60,11 +86,12 @@ function createHttpApi() {
6086
}
6187
}
6288

63-
function createHono(opts: { cors?: string[] }) {
89+
function createHono(opts: { cors?: string[] }, selection: ServerRuntime.Selection = ServerRuntime.force(select(), "hono")) {
90+
const runtimeAttributes = ServerRuntime.attributes(selection)
6491
const app = new Hono()
6592
.onError(ErrorMiddleware)
6693
.use(AuthMiddleware)
67-
.use(LoggerMiddleware)
94+
.use(LoggerMiddleware(runtimeAttributes))
6895
.use(CompressionMiddleware)
6996
.use(CorsMiddleware(opts))
7097
.route("/global", GlobalRoutes())

packages/opencode/test/server/httpapi-bridge.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ afterEach(async () => {
146146
})
147147

148148
describe("HttpApi server", () => {
149+
test("defaults local/dev builds to the Effect HttpApi runtime", () => {
150+
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
151+
expect(Server.runtime().runtime).toBe("effect-httpapi")
152+
})
153+
149154
test("covers every generated OpenAPI route with Effect HttpApi contracts", async () => {
150155
const honoRoutes = openApiRouteKeys(await Server.openapi())
151156
const effectRoutes = openApiRouteKeys(effectOpenApi())

packages/opencode/test/server/httpapi-json-parity.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const original = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
1919

2020
function app(experimental: boolean) {
2121
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
22-
return Server.Default().app
22+
return experimental ? Server.Default().app : Server.Legacy().app
2323
}
2424
type TestApp = ReturnType<typeof app>
2525

packages/opencode/test/server/httpapi-mcp.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const it = testEffect(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))
1919

2020
function app(experimental: boolean) {
2121
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
22-
return Server.Default().app
22+
return experimental ? Server.Default().app : Server.Legacy().app
2323
}
2424
type TestApp = ReturnType<typeof app>
2525

packages/opencode/test/server/httpapi-provider.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const oauthInstructions = "Finish OAuth"
1919

2020
function app(experimental: boolean) {
2121
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = experimental
22-
return Server.Default().app
22+
return experimental ? Server.Default().app : Server.Legacy().app
2323
}
2424

2525
function requestAuthorize(input: {

packages/opencode/test/server/httpapi-sync.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
1616

1717
function app(httpapi = true) {
1818
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = httpapi
19-
return Server.Default().app
19+
return httpapi ? Server.Default().app : Server.Legacy().app
2020
}
2121

2222
function runSession<A, E>(fx: Effect.Effect<A, E, Session.Service>) {

0 commit comments

Comments
 (0)