فهرست منبع

fix(opencode): flush embedded server telemetry

Aiden Cline 2 هفته پیش
والد
کامیت
31d25c8f08

+ 27 - 23
packages/opencode/src/cli/cmd/run.ts

@@ -123,6 +123,18 @@ async function toolError(part: ToolPart) {
   }
   }
 }
 }
 
 
+async function embeddedServer(auth?: string) {
+  const { Server } = await import("@/server/server")
+  const server = Server.Default()
+  const fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
+    const request = new Request(input, init)
+    const headers = new Headers(request.headers)
+    if (auth) headers.set("Authorization", auth)
+    return server.app.fetch(new Request(request, { headers }))
+  }) as typeof globalThis.fetch
+  return { fetch, dispose: server.dispose }
+}
+
 export const RunCommand = effectCmd({
 export const RunCommand = effectCmd({
   command: "run [message..]",
   command: "run [message..]",
   describe: "run opencode with a message",
   describe: "run opencode with a message",
@@ -902,19 +914,12 @@ export const RunCommand = effectCmd({
       if (interactive && !args.attach && !args.session && !args.continue) {
       if (interactive && !args.attach && !args.session && !args.continue) {
         const model = pick(args.model)
         const model = pick(args.model)
         const { runInteractiveLocalMode } = await import("./run/runtime")
         const { runInteractiveLocalMode } = await import("./run/runtime")
-        const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
-          const { Server } = await import("@/server/server")
-          const request = new Request(input, init)
-          const headers = new Headers(request.headers)
-          const auth = ServerAuth.header()
-          if (auth) headers.set("Authorization", auth)
-          return Server.Default().app.fetch(new Request(request, { headers }))
-        }) as typeof globalThis.fetch
+        const server = await embeddedServer(ServerAuth.header())
 
 
         try {
         try {
           return await runInteractiveLocalMode({
           return await runInteractiveLocalMode({
             directory: directory ?? root,
             directory: directory ?? root,
-            fetch: fetchFn,
+            fetch: server.fetch,
             resolveAgent: localAgent,
             resolveAgent: localAgent,
             session,
             session,
             share,
             share,
@@ -932,6 +937,8 @@ export const RunCommand = effectCmd({
           })
           })
         } catch (error) {
         } catch (error) {
           dieInteractive(error)
           dieInteractive(error)
+        } finally {
+          await server.dispose()
         }
         }
       }
       }
 
 
@@ -940,20 +947,17 @@ export const RunCommand = effectCmd({
         return await execute(sdk)
         return await execute(sdk)
       }
       }
 
 
-      const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
-        const { Server } = await import("@/server/server")
-        const request = new Request(input, init)
-        const headers = new Headers(request.headers)
-        const auth = ServerAuth.header()
-        if (auth) headers.set("Authorization", auth)
-        return Server.Default().app.fetch(new Request(request, { headers }))
-      }) as typeof globalThis.fetch
-      const sdk = createOpencodeClient({
-        baseUrl: "http://opencode.internal",
-        fetch: fetchFn,
-        directory,
-      })
-      await execute(sdk)
+      const server = await embeddedServer(ServerAuth.header())
+      try {
+        const sdk = createOpencodeClient({
+          baseUrl: "http://opencode.internal",
+          fetch: server.fetch,
+          directory,
+        })
+        await execute(sdk)
+      } finally {
+        await server.dispose()
+      }
     })
     })
   }),
   }),
 })
 })

+ 1 - 0
packages/opencode/src/cli/tui/worker.ts

@@ -72,6 +72,7 @@ export const rpc = {
   async shutdown() {
   async shutdown() {
     await InstanceRuntime.disposeAllInstances()
     await InstanceRuntime.disposeAllInstances()
     if (server) await server.stop(true)
     if (server) await server.stop(true)
+    if (Server.Default.loaded()) await Server.Default().dispose()
     process.off("unhandledRejection", onUnhandledRejection)
     process.off("unhandledRejection", onUnhandledRejection)
     process.off("uncaughtException", onUncaughtException)
     process.off("uncaughtException", onUncaughtException)
   },
   },

+ 3 - 3
packages/opencode/src/server/server.ts

@@ -54,14 +54,14 @@ class ListenerServerService extends Context.Service<ListenerServerService, Liste
 ) {}
 ) {}
 
 
 export const Default = lazy(() => {
 export const Default = lazy(() => {
-  const handler = HttpApiApp.webHandler().handler
+  const web = HttpApiApp.webHandler()
   const app: ServerApp = {
   const app: ServerApp = {
-    fetch: (request: Request) => handler(request, HttpApiApp.context),
+    fetch: (request: Request) => web.handler(request, HttpApiApp.context),
     request(input, init) {
     request(input, init) {
       return app.fetch(input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init))
       return app.fetch(input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init))
     },
     },
   }
   }
-  return { app }
+  return { app, dispose: web.dispose }
 })
 })
 
 
 export async function openapi() {
 export async function openapi() {

+ 29 - 0
packages/opencode/test/cli/run/run-process.test.ts

@@ -23,6 +23,35 @@ describe("opencode run (non-interactive subprocess)", () => {
     60_000,
     60_000,
   )
   )
 
 
+  cliIt.concurrent(
+    "flushes server telemetry before exiting",
+    ({ llm, opencode }) =>
+      Effect.gen(function* () {
+        const requests: string[] = []
+        const collector = yield* Effect.acquireRelease(
+          Effect.sync(() =>
+            Bun.serve({
+              port: 0,
+              fetch(request) {
+                requests.push(new URL(request.url).pathname)
+                return new Response(null, { status: 200 })
+              },
+            }),
+          ),
+          (server) => Effect.sync(() => server.stop(true)),
+        )
+        yield* llm.text("telemetry response")
+
+        const result = yield* opencode.run("say hi", {
+          env: { OTEL_EXPORTER_OTLP_ENDPOINT: collector.url.toString().replace(/\/$/, "") },
+        })
+
+        opencode.expectExit(result, 0)
+        expect(requests).toContain("/v1/traces")
+      }),
+    60_000,
+  )
+
   cliIt.concurrent(
   cliIt.concurrent(
     "prints each completed text part in order around a tool continuation",
     "prints each completed text part in order around a tool continuation",
     ({ llm, opencode }) =>
     ({ llm, opencode }) =>