Skip to content

feat: streaming + JSON-RPC core; openai, anthropic, openrouter, acp, codex SDKs - #754

Draft
sam-goodwin wants to merge 14 commits into
mainfrom
feat/ai-harness-sdks
Draft

sam-goodwin wants to merge 14 commits into
mainfrom
feat/ai-harness-sdks

Conversation

@sam-goodwin

@sam-goodwin sam-goodwin commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Streaming and JSON-RPC in core, plus SDKs for OpenAI, Anthropic, OpenRouter, the Agent Client Protocol and the Codex app-server.

REST + streaming

import * as OpenAI from "@distilled.cloud/openai";
import * as Anthropic from "@distilled.cloud/anthropic";

// plain request/response
const models = yield* OpenAI.models.listModels({});

// `text/event-stream` endpoints are Streams of typed events
yield* OpenAI.responses.createResponseStream({ model: "gpt-5-mini", input: "hi" }).pipe(
  Stream.runForEach((event) => Console.log(event.type)),
);

yield* Anthropic.createMessageStream({
  model: "claude-haiku-4-5-20251001",
  max_tokens: 256,
  messages: [{ role: "user", content: "hi" }],
}).pipe(Stream.runForEach(render));

Dual-mode endpoints keep their JSON op and gain an <Op>Stream sibling that sends stream: true for you. A non-2xx response fails the stream with the op's typed error.

JSON-RPC (ACP, Codex)

Operations are typed callables, like REST ops. The peer can also call back into you through typed handlers.

Distilled only speaks the protocol. You hand it an endpoint, and it never spawns or hosts anything:

import * as Acp from "@distilled.cloud/acp";
import * as JsonRpc from "@distilled.cloud/core/jsonrpc";

// stdio of a process you started, a socket, or JsonRpc.memoryPair in tests
const transport = JsonRpc.fromStreams({ readable: agent.stdout, writable: agent.stdin });
// const transport = yield* JsonRpc.fromSocket(socket);

const peer = yield* Acp.connect(transport, {
  handlers: {
    sessionRequestPermission: ({ options }) =>
      Effect.succeed({ outcome: { outcome: "selected", optionId: options[0]!.optionId } }),
  },
});

yield* Effect.gen(function* () {
  yield* Acp.initialize({ protocolVersion: Acp.ACP_PROTOCOL_VERSION });
  const { sessionId } = yield* Acp.sessionNew({ cwd, mcpServers: [] });
  yield* Acp.sessionUpdates.pipe(Stream.runForEach(render), Effect.forkScoped);
  return yield* Acp.sessionPrompt({ sessionId, prompt: [{ type: "text", text: "fix the bug" }] });
}).pipe(Effect.provideService(Acp.AcpConnection, peer));

Codex.connect(transport, { handlers }) works the same way.

Error codes map to typed errors through the usual errorMatchers (AcpAuthRequired, CodexServerOverloaded, …), so they're patchable like any provider.

Tree-shaking

Services are namespaced (OpenAI.responses, OpenAI.adminApiKeys, …), and namespace access tree-shakes completely. Measured with rolldown, minified, effect external:

Program Bundle
import * as OpenAI → OpenAI.responses.createResponseStream 24.5 KB
import { createResponseStream } from "@distilled.cloud/openai/responses" 24.5 KB
the first, plus OpenAI.assistants.createAssistant 63.8 KB

The root import costs nothing over the subpath import. Unused services never enter the bundle.

What changed

  • core: event-stream ops (API.makeStream, com.distilled#eventStream), and the JSON-RPC protocol family (bidirectional Peer, NDJSON framing, fromStreams / fromSocket / in-memory transports, the json-schema-rpc dialect and emission).
  • core/openapi: const becomes a one-value enum, x-speakeasy-sse-sentinel sets done, stream siblings get their own errors, and union cases can opt in to being named from their titles.
  • packages:
    • openai: 349 ops, 47 service modules
    • anthropic: 237 ops, from the last published Stainless spec
    • openrouter: 151 ops
    • acp
    • codex
    • opencode regenerated, so subscribeEvent now streams
    • cloudflare/artifacts: the Artifacts REST API (namespaces, repos, tokens) from a manual spec, with typed errors
  • Spec mirrors: added to SPEC_REPOS, and created by the distilled-submodules stack on merge.

🤖 Generated with Claude Code

sam-goodwin and others added 5 commits October 5, 2026 22:33
Operations whose success response is `text/event-stream` are now emitted
with `API.makeStream` and return a `Stream` of decoded events.

- `com.distilled#eventStream` operation trait ({ requestFlag?, done? })
- OpenAPI converter: stream-only endpoints become stream ops; dual-mode
  endpoints (JSON + SSE) keep their JSON op and gain an `<Op>Stream`
  sibling that forces the `stream` body flag
- `Protocol.decodeEvent`: REST applies the same key mapping, strict
  validation and sensitive wrapping to each event as to a JSON body
- opencode regenerated: subscribeEvent, subscribeV2Event,
  subscribeGlobalEvent and v2SessionEvents now stream

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A general JSON-RPC 2.0 stack so any JSON-RPC API becomes a distilled
package: ACP and Codex app-server now, MCP / LSP / EVM chains next.

Runtime (`core/jsonrpc`):
- bidirectional `Peer`: request/response correlation, inbound request
  dispatch, notification pub/sub, termination fails pending calls
- swappable parts: NDJSON framing; child-process stdio and in-memory
  transports
- typed operations like REST: `JsonRpc.request` / `notify` /
  `notifications`, per-package `Connection` tag + `protocol` const;
  error codes map to typed errors through `errorMatchers`
- `bindHandlers` turns a generated inbound table + typed handlers into
  the peer's dispatch maps

Codegen (`core/codegen/jsonrpc`):
- IR traits: com.distilled.jsonrpc#{jsonRpc2,method,notification,inbound}
- `json-schema-rpc` dialect: JSON Schema definitions + an explicit
  method list → Smithy, reusing the OpenAPI schema converter
- `jsonRpcEmission`: SdkSpec operation/header/footer hooks emitting
  callables, notification streams, and the inbound handler table

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found building the acp and codex packages:

- normalize JSON Schema `const` → `enum` and literal-only oneOf/anyOf →
  one enum, so discriminated unions narrow
- non-object params (unions) are the operation input directly, not a
  `{ body }` wrapper
- methods without params take no argument and send no `params`
  (`com.distilled.jsonrpc#noParams`)
- an operation name colliding with a definition or shape fails loudly
- `jsonRpcEmission().postProcess` drops an unused traits import

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found building the openai, anthropic and openrouter packages:

- JSON Schema `const` is a one-value enum, so `type: { const: "x" }`
  discriminators narrow
- `x-speakeasy-sse-sentinel` sets the event stream's `done`
- a dual-mode op's `*Stream` sibling gets its own `errors` list, so
  patches appending to one no longer leak into the other
- opt-in `unionCaseTitles`: name union cases after branch titles
  instead of `Case<n>` (on by default for json-schema-rpc)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- openai: 349 ops in 47 service modules from openai/openai-openapi;
  project and admin keys chosen per operation; responses/chat/completions
  event streams
- anthropic: 237 ops from the last published Stainless spec; admin and
  api keys per route, beta routes, messages + session event streams
- openrouter: 151 ops from openrouter.ai/openapi.json; management vs
  inference keys; chat/responses/messages/images event streams
- acp: Agent Client Protocol (JSON-RPC over stdio) from the ACP schema
  release; typed inbound handlers for permissions, fs, terminals
- codex: Codex app-server protocol from openai/codex's published JSON
  Schemas; omits the jsonrpc version member like the server

Spec mirrors are added to SPEC_REPOS and created by the
distilled-submodules stack on merge.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@alchemy-version-bot

alchemy-version-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

Distilled

@distilled.cloud/core

bun add https://pkg.ing/@distilled.cloud/core/5695317

@distilled.cloud/aws

bun add https://pkg.ing/@distilled.cloud/aws/5695317

@distilled.cloud/axiom

bun add https://pkg.ing/@distilled.cloud/axiom/5695317

@distilled.cloud/azure

bun add https://pkg.ing/@distilled.cloud/azure/5695317

@distilled.cloud/cloudflare

bun add https://pkg.ing/@distilled.cloud/cloudflare/5695317

@distilled.cloud/coinbase

bun add https://pkg.ing/@distilled.cloud/coinbase/5695317

@distilled.cloud/discord

bun add https://pkg.ing/@distilled.cloud/discord/5695317

@distilled.cloud/expo-eas

bun add https://pkg.ing/@distilled.cloud/expo-eas/5695317

@distilled.cloud/fly-io

bun add https://pkg.ing/@distilled.cloud/fly-io/5695317

@distilled.cloud/forgejo

bun add https://pkg.ing/@distilled.cloud/forgejo/5695317

@distilled.cloud/gcp

bun add https://pkg.ing/@distilled.cloud/gcp/5695317

@distilled.cloud/github

bun add https://pkg.ing/@distilled.cloud/github/5695317

@distilled.cloud/hetzner

bun add https://pkg.ing/@distilled.cloud/hetzner/5695317

@distilled.cloud/kubernetes

bun add https://pkg.ing/@distilled.cloud/kubernetes/5695317

@distilled.cloud/mongodb-atlas

bun add https://pkg.ing/@distilled.cloud/mongodb-atlas/5695317

@distilled.cloud/neon

bun add https://pkg.ing/@distilled.cloud/neon/5695317

@distilled.cloud/planetscale

bun add https://pkg.ing/@distilled.cloud/planetscale/5695317

@distilled.cloud/posthog

bun add https://pkg.ing/@distilled.cloud/posthog/5695317

@distilled.cloud/prisma

bun add https://pkg.ing/@distilled.cloud/prisma/5695317

@distilled.cloud/railway

bun add https://pkg.ing/@distilled.cloud/railway/5695317

@distilled.cloud/stripe

bun add https://pkg.ing/@distilled.cloud/stripe/5695317

@distilled.cloud/supabase

bun add https://pkg.ing/@distilled.cloud/supabase/5695317

@distilled.cloud/turso

bun add https://pkg.ing/@distilled.cloud/turso/5695317

@distilled.cloud/typesense

bun add https://pkg.ing/@distilled.cloud/typesense/5695317

@distilled.cloud/vercel

bun add https://pkg.ing/@distilled.cloud/vercel/5695317

@distilled.cloud/workos

bun add https://pkg.ing/@distilled.cloud/workos/5695317

sam-goodwin and others added 6 commits October 5, 2026 23:56
…dmin_api_keys)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- openai: ProjectNotFound, ServiceAccountNotFound, ProjectApiKeyNotFound
  on the projects/service-account/api-key ops; nested
  ResponseErrorEvent.error matching the wire; service-account and admin
  key `value` marked sensitive
- openrouter: KeyNotFound and GuardrailNotFound; createKey `key` marked
  sensitive

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Distilled only speaks the protocol. Drop JsonRpc.childProcess/layer and
the acp/codex layerChildProcess/layerTransport; add JsonRpc.fromStreams,
JsonRpc.fromSocket and JsonRpc.connect, plus Acp.connect / Codex.connect
(transport, { handlers }).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Manual Smithy spec for the Artifacts namespaces/repos/tokens API (in
Cloudflare's OpenAPI, absent from the docs the converter reads), with
typed errors verified live.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A JSON Schema object with shared properties beside a oneOf/anyOf of
object variants means base AND one variant; the converter kept one side.
ACP's SessionConfigOption lost id/name/category and
SetSessionConfigOptionRequest lost value (so set_config_option, the
model switch, was unusable). Regenerates acp and codex.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Regenerate opencode from main's newer spec with this branch's
event-stream codegen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@alchemy-version-bot

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

@distilled.cloud

pnpm install https://pkg.distilled.cloud/@distilled.cloud/acme/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/acp/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/adyen/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/anthropic/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/apache-superset/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/archil/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/argocd/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/auth0/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/aws/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/axiom/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/azure/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/boat-dev/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/celld/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/chronosphere/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/clerk/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/cloudflare/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/codex/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/coinbase/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/coolify/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/core/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/customerio/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/datadog/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/daytona/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/digitalocean/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/discord/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/docker/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/doppler/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/elasticsearch/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/expo-eas/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/fly-io/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/forgejo/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/gcp/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/github/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/google-workspace/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/grafana/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/growthbook/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/gusto/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/hetzner/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/hostinger/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/huggingface/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/iceberg/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/infisical/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/inngest/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/intercom/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/kubernetes/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/launchdarkly/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/meilisearch/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/mercury/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/metabase/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/modal/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/modrinth/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/mongodb-atlas/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/neon/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/notion/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/okta/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/onepassword/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/openai/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/opencode/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/openrouter/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/ovh/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/paypal/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/plaid/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/planetscale/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/polar/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/porkbun/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/posthog/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/prisma/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/railway/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/redis-cloud/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/remote/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/render/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/resend/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/s2/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/sentry/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/slack/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/spacetimedb/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/squarespace/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/stackit/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/stripe/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/supabase/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/surrealdb/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/temporal/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/trigger-dev/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/turbopuffer/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/turso/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/typesense/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/unkey/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/vanta/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/vercel/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/whop/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/workos/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/xata/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/zendesk/pr:754:b3217ab
pnpm install https://pkg.distilled.cloud/@distilled.cloud/zerossl/pr:754:b3217ab

Published Oct 9, 2026, 4:27 PM UTC. Expires Oct 16, 2026, 4:27 PM UTC, extended while this pull request is open.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant