[FEATURE]: sdk: expose the embedded server's fetch handler on PromiseSdk.Interface · Issue #54803 · anomalyco/opencode · GitHub
Skip to content

[FEATURE]: sdk: expose the embedded server's fetch handler on PromiseSdk.Interface #54803

Open

Description

@mattzcarey

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

PromiseSdk.create (and so OpenCodeWorkerd.create) builds an in-process HTTP handler for the embedded server, host.fetch, and only passes it to OpenCode.make:

https://github.com/anomalyco/opencode/blob/dev/packages/sdk/src/promise.ts#L56

The returned Interface has the typed client, close and plugin, but not the handler. internal/host isn't in the package's exports, so there's no supported way to reach it.

We'd like it on the interface:

export type Interface = Omit<OpenCodeClient, "plugin"> & {
  // ...
  /** The embedded server's HTTP API, in process. */
  readonly fetch: (request: Request) => Promise<Response>
}

return {
  ...client,
  fetch: host.fetch,
  // ...
}

The same field on the Effect OpenCode.Interface would keep the two in line.

Why

On workerd, OpenCode runs inside a Durable Object, and the object owns the only way in from outside: its fetch. With the handler exposed, the object can serve OpenCode's HTTP API from one of its own routes:

async fetch(request: Request) {
  const opencode = await this.opencode()
  return opencode.fetch(stripPrefix(request))
}

That makes the hosted instance reachable by the regular clients, for example:

npx @opencode/cli@2 --server https://example.com/agents/opencode/<name> --session ses_...

What we'd do without it

cloudflare/agents has an OpenCodeHarness built on @opencode/sdk/workerd. An earlier version got the handler by redefining the exported OpenCode.make from @opencode/client and catching the fetch option during create with AsyncLocalStorage. That depends on the SDK and the embedder sharing one copy of @opencode/client, and on create calling OpenCode.make with fetch set. We'd rather not ship that, so the harness has no HTTP passthrough until there's a supported field.

Related: #53577, another option we need for embedding in a Durable Object.

Activity

  1. mattzcarey commented on Oct 12, 2026

    @mattzcarey
    ContributorAuthor

    Tracking on our side: cloudflare/agents#2567

  2. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed request and the use case!

    To restate it: you'd like PromiseSdk.create / OpenCodeWorkerd.create to return the embedded server's in-process HTTP handler (host.fetch) as a fetch field on the interface, with the same field on the Effect OpenCode.Interface. A Durable Object could then serve OpenCode's HTTP API from its own fetch, and regular clients like opencode --server ... could connect to it.

    I checked dev: host.fetch is currently only passed to OpenCode.make, and internal/host isn't exported, so you're right that there's no supported way to reach it today. Maintainers will consider this.

    Related:

  3. Dante-dan commented on Oct 12, 2026

    @Dante-dan
    Contributor

    I can take on the PromiseSdk/Workerd part by returning the existing host.fetch on PromiseSdk.Interface, with its exact inferred transport type. OpenCodeWorkerd already delegates to PromiseSdk.create and aliases its interface, so it would gain the same field without a second host or listener.

    This would expose the same OwnedFetch transport used by the typed client, preserving request cancellation, streamed-response lifetime and rejection after close(). It would also preserve its existing Request/URL/init inputs rather than narrowing them to Request alone. The embedder would own routing and access control: the embedded host does not configure the external server's password authentication.

    For a focused first change, I would keep Effect opencode.http and PTY WebSocket serving in #53736's scope. A fetch handler alone would not promise WebSocket upgrades or complete CLI compatibility. This addresses the supported HTTP passthrough requested in cloudflare/agents#2567 without the rejected OpenCode.make monkey-patch or exporting SDK internals.

    After approval, I would verify that an HTTP session request through the exposed field matches the typed client's result, that Workerd exposes the same field, and that cancellation/streaming/close behavior still follows the existing transport contract. Existing typed methods, plugin registration and disposal would retain their behavior.

    Could the core team approve this additive PromiseSdk/Workerd API scope? I would leave Effect API alignment to the ongoing #53736 design unless you prefer one coordinated interface change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions