mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-06 10:54:29 +00:00
fix(sdk): bind embedded transport at request time (#46971)
This commit is contained in:
parent
206f51547c
commit
a04d72bb39
2 changed files with 71 additions and 3 deletions
|
|
@ -4,7 +4,7 @@ import { OpenCode, type OpenCodeClient } from "@opencode-ai/client/effect"
|
|||
import type { Workspace } from "@opencode-ai/core/workspace"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import type { Config, Scope } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
||||
import { EmbeddedHost } from "../internal/host"
|
||||
import type { SdkInstances } from "../internal/instances"
|
||||
|
||||
|
|
@ -35,9 +35,12 @@ export const create: <R = never>(
|
|||
R = never,
|
||||
>(options: CreateOptions<R> = {}, embed: EmbedOptions = {}) {
|
||||
const host = yield* Effect.acquireRelease(EmbeddedHost.create(options, embed), (host) => Effect.promise(host.close))
|
||||
const httpClient = yield* HttpClient.HttpClient.pipe(Effect.provide(FetchHttpClient.layer))
|
||||
const client = yield* OpenCode.make({ baseUrl: "http://opencode.local" }).pipe(
|
||||
Effect.provide(
|
||||
FetchHttpClient.layer.pipe(Layer.provide(Layer.succeed(FetchHttpClient.Fetch, host.fetch)), Layer.fresh),
|
||||
Effect.provideService(
|
||||
HttpClient.HttpClient,
|
||||
// FetchHttpClient reads Fetch at request time; callers must not replace this host's in-process transport.
|
||||
HttpClient.transformResponse(httpClient, Effect.provideService(FetchHttpClient.Fetch, host.fetch)),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
65
packages/sdk/test/transport.test.ts
Normal file
65
packages/sdk/test/transport.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { expect } from "bun:test"
|
||||
import { Context, Effect, Exit, Layer, Scope, Stream } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { tmpdirScoped } from "../../core/test/fixture/tmpdir"
|
||||
import { testEffect } from "../../core/test/lib/effect"
|
||||
import { AbsolutePath, Location, OpenCode, Session } from "../src/effect"
|
||||
|
||||
const it = testEffect(Layer.empty)
|
||||
|
||||
for (const entrypoint of ["create", "layer"] as const) {
|
||||
it.live(`${entrypoint} keeps requests and streams on its own transport despite an ambient Fetch`, () =>
|
||||
Effect.gen(function* () {
|
||||
const directory = yield* tmpdirScoped()
|
||||
const calls: string[] = []
|
||||
const ambient = Object.assign(
|
||||
(input: RequestInfo | URL) => {
|
||||
calls.push(input instanceof Request ? input.url : String(input))
|
||||
return Promise.reject(new Error("The caller's Fetch must not receive embedded SDK requests"))
|
||||
},
|
||||
{ preconnect: () => undefined },
|
||||
)
|
||||
const parent = yield* Effect.scope
|
||||
const scope = yield* Scope.fork(parent)
|
||||
const options: OpenCode.CreateOptions = {
|
||||
app: { version: "transport-test" },
|
||||
config: { directory: directory.path, project: false, content: "{}" },
|
||||
events: { persist: true },
|
||||
models: { fetch: false },
|
||||
fs: { filewatcher: false },
|
||||
}
|
||||
const client = yield* (
|
||||
entrypoint === "create"
|
||||
? OpenCode.create(options).pipe(Scope.provide(scope))
|
||||
: Layer.buildWithScope(OpenCode.layer(options), scope).pipe(Effect.map(Context.get(OpenCode.Service)))
|
||||
).pipe(Effect.provideService(FetchHttpClient.Fetch, ambient))
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
expect(yield* client.health.get()).toMatchObject({ healthy: true, version: "transport-test" })
|
||||
const session = yield* client.sessions.create({
|
||||
location: Location.Ref.make({ directory: AbsolutePath.make(directory.path) }),
|
||||
})
|
||||
expect((yield* client.sessions.get({ sessionID: session.id })).id).toBe(session.id)
|
||||
const events = yield* client.sessions.log({ sessionID: session.id }).pipe(Stream.runCollect)
|
||||
expect(events.some((event) => event.type === "session.created")).toBe(true)
|
||||
expect(yield* client.events.subscribe().pipe(Stream.take(1), Stream.runCollect)).toMatchObject([
|
||||
{ type: "server.connected" },
|
||||
])
|
||||
expect(yield* client.sessions.get({ sessionID: Session.ID.create() }).pipe(Effect.flip)).toMatchObject({
|
||||
_tag: "SessionNotFoundError",
|
||||
})
|
||||
// Binding the SDK's transport must not change the caller's surrounding context.
|
||||
expect(yield* FetchHttpClient.Fetch).toBe(ambient)
|
||||
}).pipe(Effect.provideService(FetchHttpClient.Fetch, ambient))
|
||||
expect(calls).toEqual([])
|
||||
|
||||
yield* Scope.close(scope, Exit.void)
|
||||
expect(
|
||||
Exit.isFailure(
|
||||
yield* client.health.get().pipe(Effect.provideService(FetchHttpClient.Fetch, ambient), Effect.exit),
|
||||
),
|
||||
).toBe(true)
|
||||
expect(calls).toEqual([])
|
||||
}),
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue