Compare commits
1 commit
dev
...
otel-serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
013893923f |
4 changed files with 83 additions and 40 deletions
|
|
@ -123,6 +123,20 @@ async function toolError(part: ToolPart) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function embeddedServer() {
|
||||||
|
const { Server } = await import("@/server/server")
|
||||||
|
const server = Server.Default().app
|
||||||
|
const fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||||
|
const request = new Request(input, init)
|
||||||
|
const headers = new Headers(request.headers)
|
||||||
|
const { ServerAuth } = await import("@/server/auth")
|
||||||
|
const auth = ServerAuth.header()
|
||||||
|
if (auth) headers.set("Authorization", auth)
|
||||||
|
return server.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 +916,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 embeddedServer()
|
||||||
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
|
|
||||||
|
|
||||||
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 +939,8 @@ export const RunCommand = effectCmd({
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dieInteractive(error)
|
dieInteractive(error)
|
||||||
|
} finally {
|
||||||
|
await server.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -940,20 +949,17 @@ export const RunCommand = effectCmd({
|
||||||
return await execute(sdk)
|
return await execute(sdk)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
const server = await embeddedServer()
|
||||||
const { Server } = await import("@/server/server")
|
try {
|
||||||
const request = new Request(input, init)
|
const sdk = createOpencodeClient({
|
||||||
const headers = new Headers(request.headers)
|
baseUrl: "http://opencode.internal",
|
||||||
const auth = ServerAuth.header()
|
fetch: server.fetch,
|
||||||
if (auth) headers.set("Authorization", auth)
|
directory,
|
||||||
return Server.Default().app.fetch(new Request(request, { headers }))
|
})
|
||||||
}) as typeof globalThis.fetch
|
await execute(sdk)
|
||||||
const sdk = createOpencodeClient({
|
} finally {
|
||||||
baseUrl: "http://opencode.internal",
|
await server.dispose()
|
||||||
fetch: fetchFn,
|
}
|
||||||
directory,
|
|
||||||
})
|
|
||||||
await execute(sdk)
|
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ interface EffectCmdOpts<Args, A> {
|
||||||
*
|
*
|
||||||
* Errors propagate to the existing top-level handler in `src/index.ts`; use
|
* Errors propagate to the existing top-level handler in `src/index.ts`; use
|
||||||
* `fail("...")` for user-visible domain failures (clean exit, formatted message).
|
* `fail("...")` for user-visible domain failures (clean exit, formatted message).
|
||||||
|
* The AppRuntime is disposed after the command finishes so its scoped resources
|
||||||
|
* are released without making the CLI entrypoint import the full application graph.
|
||||||
*
|
*
|
||||||
* Handlers are typically `Effect.fn("Cli.<name>")(function*(args) { ... })`,
|
* Handlers are typically `Effect.fn("Cli.<name>")(function*(args) { ... })`,
|
||||||
* which adds a named tracing span per CLI invocation. Once all commands use
|
* which adds a named tracing span per CLI invocation. Once all commands use
|
||||||
|
|
@ -74,23 +76,27 @@ export const effectCmd = <Args, A>(opts: EffectCmdOpts<Args, A>) =>
|
||||||
builder: opts.builder as never,
|
builder: opts.builder as never,
|
||||||
async handler(rawArgs) {
|
async handler(rawArgs) {
|
||||||
const { AppRuntime } = await import("@/effect/app-runtime")
|
const { AppRuntime } = await import("@/effect/app-runtime")
|
||||||
// yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
|
|
||||||
const args = rawArgs as unknown as WithDoubleDash<Args>
|
|
||||||
const useInstance = typeof opts.instance === "function" ? opts.instance(args) : opts.instance !== false
|
|
||||||
if (!useInstance) {
|
|
||||||
await AppRuntime.runPromise(opts.handler(args))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { InstanceStore } = await import("@/project/instance-store")
|
|
||||||
const { InstanceRef } = await import("@/effect/instance-ref")
|
|
||||||
const directory = opts.directory?.(args) ?? process.cwd()
|
|
||||||
const { store, ctx } = await AppRuntime.runPromise(
|
|
||||||
InstanceStore.Service.use((store) => store.load({ directory }).pipe(Effect.map((ctx) => ({ store, ctx })))),
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
await AppRuntime.runPromise(opts.handler(args).pipe(Effect.provideService(InstanceRef, ctx)))
|
// yargs typing wraps Args in ArgumentsCamelCase<WithDoubleDash<...>>; cast at the boundary.
|
||||||
|
const args = rawArgs as unknown as WithDoubleDash<Args>
|
||||||
|
const useInstance = typeof opts.instance === "function" ? opts.instance(args) : opts.instance !== false
|
||||||
|
if (!useInstance) {
|
||||||
|
await AppRuntime.runPromise(opts.handler(args))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { InstanceStore } = await import("@/project/instance-store")
|
||||||
|
const { InstanceRef } = await import("@/effect/instance-ref")
|
||||||
|
const directory = opts.directory?.(args) ?? process.cwd()
|
||||||
|
const { store, ctx } = await AppRuntime.runPromise(
|
||||||
|
InstanceStore.Service.use((store) => store.load({ directory }).pipe(Effect.map((ctx) => ({ store, ctx })))),
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
await AppRuntime.runPromise(opts.handler(args).pipe(Effect.provideService(InstanceRef, ctx)))
|
||||||
|
} finally {
|
||||||
|
await AppRuntime.runPromise(store.dispose(ctx))
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await AppRuntime.runPromise(store.dispose(ctx))
|
await AppRuntime.dispose().catch(() => {})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export type Listener = {
|
||||||
type ServerApp = {
|
type ServerApp = {
|
||||||
fetch(request: Request): Response | Promise<Response>
|
fetch(request: Request): Response | Promise<Response>
|
||||||
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
|
request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
|
||||||
|
dispose(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenOptions = CorsOptions & {
|
type ListenOptions = CorsOptions & {
|
||||||
|
|
@ -54,12 +55,13 @@ 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))
|
||||||
},
|
},
|
||||||
|
dispose: web.dispose,
|
||||||
}
|
}
|
||||||
return { app }
|
return { app }
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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 }) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue