From f8a10268072ca642d82f7438870f9e8bba4238d2 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 10:28:20 -0700 Subject: [PATCH 1/2] docs: rewrite README around a runnable quickstart and quirks table One-sentence intro, a single install line that includes the peer dependencies, and a quickstart that streams one turn through createOpenAIResponsesAdapter and fails loudly when the credential is unset. The sidecar manifest paragraph and the Grok-specific example give way to plain registry wiring, a generic responsesAdapterFactory example, and a reference table of every quirk. How it works and Development move to a new contributing guide. --- CONTRIBUTING.md | 21 +++++++ README.md | 145 +++++++++++++++++++++++++++--------------------- 2 files changed, 103 insertions(+), 63 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..02ba00e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing + +## How it works + +`quirks` are JSON on `InferenceSource` (persisted, sent over the wire). +`hooks` are code, applied once at `responsesAdapterFactory` construction. +Defaults are protocol-native: system prompt, `maxTokens`, and `temperature` +go through unless a quirk opts a backend out. The host owns provider ids; a +reasoning signature is tagged with the id in effect when it was issued. + +## Development + +```sh +git clone https://github.com/corbitsdev/corbits-openai-responses.git +cd corbits-openai-responses +bun install +bun run check +``` + +`bun run check` is typecheck + lint + format:check + test. `bun run format` +rewrites the tree. diff --git a/README.md b/README.md index 94378a0..7c9b4d2 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,106 @@ # @corbits/openai-responses -An Interchange `ProviderAdapter` for the OpenAI Responses API wire protocol: -text, tool calls, reasoning with `encrypted_content` replay, image and PDF -input, SSE and non-streaming. Vendor differences (Codex, xAI/Grok, plain -OpenAI) are a `ResponsesQuirks` bag, not a forked adapter. Current scope covers -text, tool calls, reasoning replay, and image/PDF input. - -## Runtime support - -Bun >= 1.2 is the development runtime. The package ships compiled `dist/` -(`main`/`types` and `exports` point at `dist/index.js` / `dist/index.d.ts`, -built with `bun run build`); both Bun and Node >= 24 load the built output. -`@intx/inference` and `@intx/types` -are peer dependencies and must resolve to the host's own copy. +Use this adapter to run Interchange inference against any OpenAI Responses +API endpoint (OpenAI, Codex, xAI/Grok), with text, tool calls, reasoning +replay, and image/PDF input. ## Quickstart ```sh -npm add @corbits/openai-responses -pnpm add @corbits/openai-responses -yarn add @corbits/openai-responses -bun add @corbits/openai-responses +npm add @corbits/openai-responses @intx/inference @intx/types ``` -Bake a vendor's wire shape into a quirks bag, then hand it to -`responsesAdapterFactory` to get back an `AdapterFactory`. This mirrors how -`@corbits/xai-provider` wires up Grok's Responses-speaking CLI proxy: - ```ts +import { createDependencies, runInference } from "@intx/inference"; import { - responsesAdapterFactory, - type ResponsesQuirks, + createOpenAIResponsesAdapter, + OPENAI_RESPONSES_PROVIDER, } from "@corbits/openai-responses"; -import type { AdapterFactory } from "@intx/inference"; -// Mirrors the vendor's own request shape: headers, system-prompt -// placement, reasoning summary depth, which stock fields to suppress. -const grokResponsesQuirks: ResponsesQuirks = { - path: "/v1/responses", - headers: { - static: { "x-grok-client-identifier": "my-harness" }, +const deps = createDependencies({ + has: (provider) => provider === OPENAI_RESPONSES_PROVIDER, + resolve: (source, quirks) => createOpenAIResponsesAdapter(source, quirks), +}); + +let seq = 0; +for await (const event of runInference({ + deps, + source: { + id: "openai", + provider: OPENAI_RESPONSES_PROVIDER, + baseURL: "https://api.openai.com/v1", + credentialId: "OPENAI_API_KEY", + model: "gpt-5-mini", }, - sessionIdOption: "sessionId", - systemPrompt: { role: "system", shape: "string" }, - contentShape: "flat", - reasoning: { summary: "detailed" }, - maxOutputTokens: false, - temperature: false, -}; - -export const createGrokResponsesAdapter: AdapterFactory = - responsesAdapterFactory(grokResponsesQuirks); + turns: [ + { + role: "user", + timestamp: Date.now(), + content: [{ type: "text", text: "Say hello." }], + }, + ], + nextSeq: () => seq++, + readMaterial: (id) => { + const secret = process.env[id]; + if (secret === undefined) throw new Error(`${id} is not set`); + return { secret }; + }, +})) { + if (event.type === "inference.text.delta") { + process.stdout.write(event.data.token); + } +} ``` -A sidecar host registers the resulting export on its -`SIDECAR_ADAPTER_MANIFEST` (one entry per provider id, `specifier` naming an -already-installed module) — this package ships no manifest entry itself; the -vendor package wrapping it does, e.g. -`{"provider":"xai","specifier":"@corbits/xai-provider","export":"createXaiResponsesAdapter"}`. +`@intx/inference` and `@intx/types` are peer dependencies; the host supplies +its own copy. Requires Node >= 24 or Bun >= 1.2. -## How it works +## Using with Interchange -`quirks` are JSON on `InferenceSource` (persisted, sent over the wire). -`hooks` are code, applied once at `responsesAdapterFactory` construction. -Defaults are protocol-native: system prompt, `maxTokens`, and `temperature` -go through unless a quirk opts a backend out. The host owns provider ids; a -reasoning signature is tagged with the id in effect when it was issued. +Register `createOpenAIResponsesAdapter` under the `openai-responses` +provider id in your host's `AdapterRegistry`, as the quickstart does. A +source's `quirks` bag is passed through as the factory's second argument. -## Development +For a vendor whose backend deviates from the protocol, bake its quirks into a +factory once with `responsesAdapterFactory`: -```sh -git clone https://github.com/corbitsdev/corbits-openai-responses.git -cd corbits-openai-responses -bun install -bun run typecheck -bun run lint -bun run format:check -bun run test -bun run check +```ts +import { responsesAdapterFactory } from "@corbits/openai-responses"; + +export const createVendorAdapter = responsesAdapterFactory({ + path: "/v1/responses", + contentShape: "flat", + temperature: false, +}); ``` -`bun run format` rewrites the tree. `bun run check` is typecheck + lint + -format:check + test. +The optional second argument, `ResponsesHooks`, carries code-shaped +accommodations (`wrapSystemPrompt`, `includeReasoningEffort`) that cannot +live in a JSON quirks bag. + +## Quirks + +Every field is optional. An absent field keeps the protocol-native default. +Unknown keys are rejected. + +| Quirk | Type | Default | Effect on the request | +| ------------------------ | --------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------- | +| `path` | `string` | `"/responses"` | Request path appended to the source's `baseURL`. | +| `headers.static` | `Record` | `{}` | Headers added verbatim; they can override the stock headers. | +| `headers.modelHeader` | `string` | unset | Header name that carries the model id. | +| `headers.fromOption` | `{ optionKey, header }[]` | `[]` | Copies each non-empty string `providerOptions[optionKey]` into `header`. | +| `sessionIdOption` | `string` | unset | `providerOptions` key whose value is sent as `prompt_cache_key`. | +| `sessionIdHeader` | `string` | unset | Also sends that session id in this header. Ignored without `sessionIdOption`. | +| `systemPrompt` | `{ role: "system" \| "developer", shape: "string" \| "parts" }` | `{ role: "system", shape: "string" }` | Role and content shape of the leading system-prompt item. | +| `contentShape` | `"typed" \| "flat"` | `"typed"` | `flat` sends text-only content as a plain string instead of typed parts. | +| `parallelToolCalls` | `boolean` | unset | Unset omits `parallel_tool_calls`; a boolean is sent as given. | +| `maxOutputTokens` | `boolean` | `true` | `false` omits `max_output_tokens` even when the caller sets `maxTokens`. | +| `temperature` | `boolean` | `true` | `false` omits `temperature` even when the caller sets it. | +| `store` | `boolean` | `false` | Sent as `store`. | +| `stream` | `boolean` | `true` | Sent as `stream`; `false` also sends `accept: application/json`. | +| `reasoning.summary` | `"auto" \| "detailed"` | unset | Sent as `reasoning.summary`. | +| `reasoning.effortOption` | `string` | unset | `providerOptions` key whose value is sent as `reasoning.effort`. | +| `instructions` | `string` | unset | Sent as `instructions`. | ## License From c987618892cf5356263ae8abb158262ecdc59d4d Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Fri, 25 Sep 2026 10:34:54 -0700 Subject: [PATCH 2/2] docs: align the README with the Corbits README standard Open with the adapter's identity and placement, list three concrete benefits, and add Where it fits, an exports table, manifest-based Interchange wiring, and Upgrading from 0.1 notes for the removed exports. Note that responsesAdapterFactory ignores per-source quirks. --- README.md | 119 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7c9b4d2..4bb2354 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,27 @@ # @corbits/openai-responses -Use this adapter to run Interchange inference against any OpenAI Responses -API endpoint (OpenAI, Codex, xAI/Grok), with text, tool calls, reasoning -replay, and image/PDF input. +An `@intx/inference` provider adapter for the OpenAI Responses API (`/v1/responses`): SSE and JSON responses, text, tool calls, image and PDF input, and reasoning replay. An inference provider for Corbits and Interchange agents that also works in any host that runs `@intx/inference`. -## Quickstart +## Why @corbits/openai-responses? + +1. **One adapter for every Responses backend.** OpenAI, Codex, xAI and Ollama's `/v1` differ in paths, headers and body fields. Those differences are a JSON `quirks` object on the source, not forked code. +2. **Reasoning survives across turns.** Encrypted reasoning items are tagged with the provider that issued them. They are replayed only to the same provider and model, so multi-turn reasoning keeps its context and the backend never rejects a signature it did not issue. +3. **Interchange error and retry semantics.** Requests run through `runInference`, so rate limits, pacing headers and auth failures behave the same as for the built-in providers. + +It speaks only the Responses protocol. For Chat Completions, use the built-in OpenAI adapter in `@intx/inference`. -```sh -npm add @corbits/openai-responses @intx/inference @intx/types +## Install + +```bash +bun add @corbits/openai-responses @intx/inference@^0.4.0 @intx/types@^0.4.0 ``` +Runs on Bun >= 1.2 or Node >= 24. + +## Quickstart + +Needs `OPENAI_API_KEY` set. + ```ts import { createDependencies, runInference } from "@intx/inference"; import { @@ -46,42 +58,37 @@ for await (const event of runInference({ return { secret }; }, })) { - if (event.type === "inference.text.delta") { + if (event.type === "inference.text.delta") process.stdout.write(event.data.token); - } + if (event.type === "inference.error") + throw new Error(event.data.error.message); } +process.stdout.write("\n"); ``` -`@intx/inference` and `@intx/types` are peer dependencies; the host supplies -its own copy. Requires Node >= 24 or Bun >= 1.2. +## Where it fits -## Using with Interchange +[Interchange](https://github.com/faremeter/interchange) runs AI agents as principals (accounts that hold their own identity, permissions and credentials). Corbits packages add what an agent product needs around it. -Register `createOpenAIResponsesAdapter` under the `openai-responses` -provider id in your host's `AdapterRegistry`, as the quickstart does. A -source's `quirks` bag is passed through as the factory's second argument. +- **Runs in:** the agent sidecar (the runtime next to each agent), or any process that calls `runInference`. No hub is required. +- **Plugs into:** the [`@intx/inference`](https://github.com/faremeter/interchange/tree/main/packages/inference) adapter registry, as the factory for the `openai-responses` provider id. +- **Pairs with:** [`@corbits/ollama-adapter`](https://github.com/corbitsdev/corbits-ollama-adapter) and [`@corbits/system-one`](https://github.com/corbitsdev/corbits-system-one), the other Corbits inference providers. -For a vendor whose backend deviates from the protocol, bake its quirks into a -factory once with `responsesAdapterFactory`: +## Reference -```ts -import { responsesAdapterFactory } from "@corbits/openai-responses"; - -export const createVendorAdapter = responsesAdapterFactory({ - path: "/v1/responses", - contentShape: "flat", - temperature: false, -}); -``` - -The optional second argument, `ResponsesHooks`, carries code-shaped -accommodations (`wrapSystemPrompt`, `includeReasoningEffort`) that cannot -live in a JSON quirks bag. +| Export | Description | +| ----------------------------------------- | ----------------------------------------------------------------------------------- | +| `createOpenAIResponsesAdapter` | `AdapterFactory`. Reads quirks from the source on every resolve. | +| `responsesAdapterFactory(quirks, hooks?)` | Returns an `AdapterFactory` with fixed quirks. Any per-source `quirks` are ignored. | +| `OPENAI_RESPONSES_PROVIDER` | The `"openai-responses"` provider id. | +| `OPENAI_COMPATIBLE_RESPONSES_PROVIDER` | Deprecated `"openai-compatible-responses"` id from 0.1. Removed in 0.3.0. | +| `responsesAdapterFactories` | Record mapping both provider ids to `createOpenAIResponsesAdapter`. | +| `ResponsesQuirks` | Schema and type for the `quirks` object. | +| `ResponsesHooks` | Code hooks: `wrapSystemPrompt`, `includeReasoningEffort`. | -## Quirks +### Quirks -Every field is optional. An absent field keeps the protocol-native default. -Unknown keys are rejected. +Every field is optional. An absent field keeps the protocol default. Unknown keys are rejected. | Quirk | Type | Default | Effect on the request | | ------------------------ | --------------------------------------------------------------- | ------------------------------------- | ----------------------------------------------------------------------------- | @@ -102,6 +109,52 @@ Unknown keys are rejected. | `reasoning.effortOption` | `string` | unset | `providerOptions` key whose value is sent as `reasoning.effort`. | | `instructions` | `string` | unset | Sent as `instructions`. | +## Using with Interchange + +Interchange loads custom adapters from an operator-configured `AdapterManifest`. Add one entry per provider id you serve: + +```ts +import { createDependencies, type AdapterManifest } from "@intx/inference"; +import { loadAdapterRegistry } from "@intx/inference/providers"; + +const manifest: AdapterManifest = [ + { + provider: "openai-responses", + specifier: "@corbits/openai-responses", + export: "createOpenAIResponsesAdapter", + }, + { + provider: "openai-compatible-responses", + specifier: "@corbits/openai-responses", + export: "createOpenAIResponsesAdapter", + }, +]; +const deps = createDependencies(await loadAdapterRegistry(manifest)); +``` + +Pass `deps` to `runInference`. Sources with `provider: "openai-responses"` (or the deprecated `"openai-compatible-responses"`) then resolve to this adapter, and each source's `quirks` configures its backend. To serve a vendor under its own id, add another entry with that `provider` and the same export. Manifest entries override built-in adapters with the same id, so don't reuse `openai` or another built-in id. + +For a vendor that needs code hooks, bake its quirks into a factory in your own module and point a manifest entry at that export: + +```ts +import { responsesAdapterFactory } from "@corbits/openai-responses"; + +export const createVendorAdapter = responsesAdapterFactory( + { path: "/v1/responses", contentShape: "flat", temperature: false }, + { wrapSystemPrompt: (prompt) => `${prompt}` }, +); +``` + +Its manifest entry names your module and that export, for example `{ provider: "vendor-responses", specifier: "./vendor-adapter.js", export: "createVendorAdapter" }`. + +## Upgrading from 0.1 + +- No host change is needed. `responsesAdapterFactories` still maps `openai-compatible-responses` to `createOpenAIResponsesAdapter`, so sources stored under that id keep resolving and running. +- `OPENAI_COMPATIBLE_RESPONSES_PROVIDER` is deprecated and removed in 0.3.0. Move stored sources to `openai-responses` before then. +- `isResponsesStreamTerminal` is no longer exported. The adapter still applies it. +- `@intx/inference` and `@intx/types` peers are now `^0.4.0`. +- Quirks and reasoning signatures are unchanged. + ## License -LGPL-2.1-only. +[LGPL-2.1-only](https://github.com/corbitsdev/corbits-openai-responses/blob/main/LICENSE)