Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions bin/lib/catalog-dev-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const credentialFixtures: Record<string, CredentialFixture> = {
credentialName: "xAI Direct Key",
credentialSecret: "xai-fake-key-for-seed-data",
},
"OpenAI Responses": {
credentialName: "OpenAI Responses Key",
credentialSecret: "sk-responses-fake-key-for-seed-data",
},
};

// Dev pricing as decimal strings, matching the API's string money fields.
Expand Down Expand Up @@ -88,6 +92,18 @@ export const priceFixtures: Record<string, Record<string, PriceFixture>> = {
"grok-4.5": { input: "0.000002", output: "0.000006" },
"grok-4.6": { input: "0.000002", output: "0.000006" },
},
"OpenAI Responses": {
"gpt-6-astra": { input: "0.00001", output: "0.00005" },
"gpt-6.1-sol": { input: "0.000002", output: "0.00001" },
"gpt-6.1-terra": { input: "0.000001", output: "0.000005" },
"gpt-6-luna": { input: "0.0000001", output: "0.0000005" },
"gpt-6-sol": { input: "0.000002", output: "0.00001" },
"gpt-5.6-sol": { input: "0.000004", output: "0.00002" },
"gpt-5.6-terra": { input: "0.000002", output: "0.000012" },
"gpt-5.6-luna": { input: "0.0000002", output: "0.0000012" },
"gpt-5.5": { input: "0.000005", output: "0.00003" },
"gpt-5-mini": { input: "0.00000025", output: "0.000002" },
},
};

// Offerings intentionally seeded without pricing, keyed by provider name to the
Expand Down
16 changes: 16 additions & 0 deletions bin/lib/discover-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const REQUIRED_ENV_BY_PROVIDER: Record<string, readonly string[]> = {
"google-genai": ["GOOGLE_API_KEY"],
"opencode-zen": ["OPENCODE_API_KEY", "OPENCODE_BASE_URL"],
openai: ["OPENAI_API_KEY"],
"openai-responses": ["OPENAI_API_KEY"],
xai: ["XAI_API_KEY"],
};

Expand All @@ -31,6 +32,7 @@ describe("PLUGIN_REGISTRY provider contract", () => {
"google-genai",
"opencode-zen",
"openai",
"openai-responses",
"xai",
]);
});
Expand Down Expand Up @@ -112,6 +114,20 @@ describe("create factory env wiring", () => {
expect(() => requireEntry("openai").create({})).toThrow("OPENAI_API_KEY");
});

test("openai-responses builds a gpt-5.5 plug-in from OPENAI_API_KEY", () => {
const plugin = requireEntry("openai-responses").create({
OPENAI_API_KEY: "test-key",
});
expect(plugin.name).toBe("openai-responses");
expect(plugin.models).toContain("gpt-5.5");
});

test("openai-responses raises when OPENAI_API_KEY is absent", () => {
expect(() => requireEntry("openai-responses").create({})).toThrow(
"OPENAI_API_KEY",
);
});

test("xai builds a grok-4.5 plug-in from XAI_API_KEY", () => {
const plugin = requireEntry("xai").create({
XAI_API_KEY: "test-key",
Expand Down
14 changes: 14 additions & 0 deletions bin/lib/discover-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from "@intx/inference-discovery-google-genai";
import {
createOpenAIPlugin,
createOpenAIResponsesPlugin,
createOpencodeZenPlugin,
createXaiPlugin,
} from "@intx/inference-discovery-openai";
Expand Down Expand Up @@ -79,6 +80,14 @@ function opencodeZenCreate(env: Record<string, string>): ProviderPlugin {
return createOpencodeZenPlugin({ apiKey, baseUrl });
}

function openaiResponsesCreate(env: Record<string, string>): ProviderPlugin {
const apiKey = env.OPENAI_API_KEY;
if (apiKey === undefined) {
throw new Error("OPENAI_API_KEY missing from validated env");
}
return createOpenAIResponsesPlugin({ apiKey });
}

function xaiCreate(env: Record<string, string>): ProviderPlugin {
const apiKey = env.XAI_API_KEY;
if (apiKey === undefined) {
Expand Down Expand Up @@ -108,6 +117,11 @@ export const PLUGIN_REGISTRY: readonly RegisteredPlugin[] = [
requiredEnv: ["OPENAI_API_KEY"],
create: openaiCreate,
},
{
name: "openai-responses",
requiredEnv: ["OPENAI_API_KEY"],
create: openaiResponsesCreate,
},
{
name: "xai",
requiredEnv: ["XAI_API_KEY"],
Expand Down
22 changes: 22 additions & 0 deletions packages/inference-catalog/src/catalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
AnthropicQuirks,
GoogleGenAIQuirks,
OpenAIQuirks,
ResponsesQuirks,
} from "@intx/inference/providers";
import {
baseURLForCatalogProvider,
Expand All @@ -26,6 +27,7 @@ const quirkValidatorByPlugin: Record<CatalogPlugin, Type> = {
anthropic: AnthropicQuirks,
openai: OpenAIQuirks,
"openai-compatible": OpenAIQuirks,
"openai-responses": ResponsesQuirks,
"google-genai": GoogleGenAIQuirks,
};

Expand Down Expand Up @@ -71,6 +73,26 @@ describe("first-party OpenAI maxEffort", () => {
}
});

describe("OpenAI Responses reasoning", () => {
// Same maxEffort contract as above, read from the Responses adapter's
// nested key, plus the summary every Responses offering requests.
// Exact equality, so a removed or swapped field fails the build the
// same as for Direct.
const expected: Record<string, unknown> = {
"gpt-5.5": { maxEffort: "xhigh", summary: "detailed" },
"gpt-5.6-sol": { maxEffort: "max", summary: "detailed" },
};
const responses = catalogProviders.find((p) => p.name === "OpenAI Responses");
if (responses === undefined) throw new Error("OpenAI Responses is missing");
for (const offering of responses.offerings) {
test(`${offering.model} carries the expected reasoning quirks`, () => {
expect(offering.quirks["reasoning"]).toEqual(
expected[offering.model] ?? { summary: "detailed" },
);
});
}
});

describe("catalog offering capabilities", () => {
for (const provider of catalogProviders) {
for (const offering of provider.offerings) {
Expand Down
5 changes: 5 additions & 0 deletions packages/inference-catalog/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,9 @@ export const catalogModels: CatalogModelSpec[] = [
{ canonicalName: "grok-4.5", displayName: "Grok 4.5" },
{ canonicalName: "grok-4.6", displayName: "Grok 4.6" },
{ canonicalName: "grok-build-0.1", displayName: "Grok Build 0.1" },
{ canonicalName: "gpt-6-astra", displayName: "GPT-6 Astra" },
{ canonicalName: "gpt-6.1-sol", displayName: "GPT-6.1 Sol" },
{ canonicalName: "gpt-6.1-terra", displayName: "GPT-6.1 Terra" },
{ canonicalName: "gpt-6-luna", displayName: "GPT-6 Luna" },
{ canonicalName: "gpt-6-sol", displayName: "GPT-6 Sol" },
];
127 changes: 127 additions & 0 deletions packages/inference-catalog/src/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import {
OPENAI_FIRSTPARTY_QUIRKS,
OPENAI_FIRSTPARTY_XHIGH_QUIRKS,
OPENAI_REASONING_QUIRKS,
OPENAI_RESPONSES_MAX_QUIRKS,
OPENAI_RESPONSES_REASONING_QUIRKS,
OPENAI_RESPONSES_XHIGH_QUIRKS,
} from "./quirks";

export type CatalogPlugin =
| "anthropic"
| "openai"
| "openai-compatible"
| "openai-responses"
| "google-genai";

export type DiscoverySource = {
Expand Down Expand Up @@ -109,6 +113,33 @@ const OPENAI_REASONING_NO_VISION_CAPABILITIES: Capability[] = [
"structured-output-streaming",
];

// The Responses-wire sets proven by the openai-responses matrix rows:
// gpt-5-mini carries the full builder surface; every other offering carries
// the representative streaming, buffered, tool follow-up, and reasoning
// subset. The guard test re-bakes each offering from its provenance tuple.
const OPENAI_RESPONSES_FULL_OFFERING_CAPABILITIES: Capability[] = [
"plain-text",
"plain-text-streaming",
"function-calling",
"function-calling-multi-turn",
"function-calling-multi-turn-streaming",
"vision-input",
"document-input",
"code-execution",
"code-execution-streaming",
"reasoning-content",
"reasoning-content-streaming",
"grounding",
"grounding-streaming",
];

const OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES: Capability[] = [
"plain-text",
"plain-text-streaming",
"function-calling-multi-turn",
"reasoning-content",
];

// Plain-text + reasoning only (no vision, no structured output): glm-5, glm-5.1,
// deepseek-v4-pro, deepseek-v4-flash.
const OPENAI_REASONING_TEXT_CAPABILITIES: Capability[] = [
Expand Down Expand Up @@ -510,6 +541,102 @@ export const catalogProviders: CatalogProviderSpec[] = [
},
],
},
{
name: "OpenAI Responses",
plugin: "openai-responses",
baseURL: "https://api.openai.com/v1",
offerings: [
{
model: "gpt-6-astra",
priority: 0,
discoverySource: { provider: "openai-responses", model: "gpt-6-astra" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-6.1-sol",
priority: 5,
discoverySource: { provider: "openai-responses", model: "gpt-6.1-sol" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-6.1-terra",
priority: 10,
discoverySource: {
provider: "openai-responses",
model: "gpt-6.1-terra",
},
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-6-luna",
priority: 15,
discoverySource: { provider: "openai-responses", model: "gpt-6-luna" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-6-sol",
priority: 20,
discoverySource: { provider: "openai-responses", model: "gpt-6-sol" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-5.6-sol",
priority: 25,
discoverySource: { provider: "openai-responses", model: "gpt-5.6-sol" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_MAX_QUIRKS,
},
{
model: "gpt-5.6-terra",
priority: 30,
discoverySource: {
provider: "openai-responses",
model: "gpt-5.6-terra",
},
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-5.6-luna",
priority: 35,
discoverySource: {
provider: "openai-responses",
model: "gpt-5.6-luna",
},
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
{
model: "gpt-5.5",
priority: 40,
discoverySource: { provider: "openai-responses", model: "gpt-5.5" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_REPRESENTATIVE_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_XHIGH_QUIRKS,
},
{
model: "gpt-5-mini",
priority: 45,
discoverySource: { provider: "openai-responses", model: "gpt-5-mini" },
curatedCapabilities: [],
capabilities: OPENAI_RESPONSES_FULL_OFFERING_CAPABILITIES,
quirks: OPENAI_RESPONSES_REASONING_QUIRKS,
},
],
},
{
name: "Gemini Direct",
plugin: "google-genai",
Expand Down
19 changes: 19 additions & 0 deletions packages/inference-catalog/src/quirks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ export const OPENAI_FIRSTPARTY_MAX_QUIRKS: Record<string, unknown> = {
maxEffort: "max",
};

// Responses-wire counterparts of the maxEffort quirks above: the Responses
// adapter reads the value from `reasoning.maxEffort`, not the top level, and
// rejects the Chat keys. The values are carried from the Chat entries for
// the same models — no Responses-side capture has yet sent `xhigh` or `max`
// on this wire (INTR-720 tracks per-capture rig options to prove them).
// Every Responses offering also requests a `detailed` reasoning summary:
// the fixtures prove the server returns summary text when asked, and the
// adapter only forwards `summary` when this quirk is set — without it,
// `reasoning-content` calls would yield empty thinking blocks.
export const OPENAI_RESPONSES_REASONING_QUIRKS: Record<string, unknown> = {
reasoning: { summary: "detailed" },
};
export const OPENAI_RESPONSES_XHIGH_QUIRKS: Record<string, unknown> = {
Comment thread
alexanderguy marked this conversation as resolved.
Resolved
reasoning: { maxEffort: "xhigh", summary: "detailed" },
};
export const OPENAI_RESPONSES_MAX_QUIRKS: Record<string, unknown> = {
reasoning: { maxEffort: "max", summary: "detailed" },
};

// The reasoning-bearing opencode-zen relay offerings — the kimi models plus
// qwen, deepseek, glm, and mimo — advertise reasoning_content on the wire. The
// OpenAI adapter no longer forces reasoning content by default, so
Expand Down
45 changes: 43 additions & 2 deletions packages/inference-discovery-openai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ is organised in two layers:
- `deployments/` — concrete deployments built on the protocol
layer. Each deployment names a provider, lists its models,
declares its auth and redaction policy, and (where needed)
overrides reasoning extraction. Three deployments ship today: the
OpenCode Zen relay, first-party OpenAI, and first-party xAI.
overrides reasoning extraction. Four deployments ship today: the
OpenCode Zen relay, first-party OpenAI (Chat Completions), first-party
OpenAI Responses, and first-party xAI.

See [`@intx/inference-discovery`](../inference-discovery/README.md)
for the runtime, the plug-in contract, and the `discover` CLI.
Expand Down Expand Up @@ -88,6 +89,46 @@ limitation.
| ---------------- | ------------------------------------------------------------ |
| `OPENAI_API_KEY` | Sent as `Authorization: Bearer <key>`. Redacted in fixtures. |

## OpenAI Responses

The `openai-responses` deployment probes first-party `api.openai.com` on
the Responses wire (`/v1/responses`), under provider name
`openai-responses` (sessions land in this package's `sessions/` tree
alongside the Chat Completions deployments). The base URL is fixed to
`https://api.openai.com/v1`; the deployment reads only `OPENAI_API_KEY`.

```ts
import { createOpenAIResponsesPlugin } from "@intx/inference-discovery-openai";

const plugin = createOpenAIResponsesPlugin({
apiKey: process.env.OPENAI_API_KEY,
});
// Hand off to runCapture from @intx/inference-discovery.
```

Models: the reasoning-capable first-party models (`gpt-6-astra`,
`gpt-6.1-sol`, `gpt-6.1-terra`, `gpt-6-luna`, `gpt-6-sol`, `gpt-5.6-sol`,
`gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.5`, `gpt-5`, `gpt-5-mini`,
`gpt-5-nano`, `gpt-5.1`, `gpt-5.2`, `gpt-5.4`, `gpt-5.4-mini`,
`gpt-5.4-nano`, `o1`, `o3`, `o3-mini`, `o4-mini`). The gpt-4 family is
excluded pending verification that those models serve `/responses`.

The Responses body builder emits `plain-text`, `plain-text-streaming`,
`function-calling`, `function-calling-multi-turn`, `reasoning-content`,
`reasoning-content-streaming`, `vision-input`, `document-input`,
`code-execution`, `code-execution-streaming`, `grounding`, and
`grounding-streaming`. Structured output and URL-form file input are out of
scope until the runtime adapter supports them and discovery verifies them;
audio, video, and image-output are not built. Refresh the fixtures with a
live capture run (`bin/discover --provider openai-responses --all` against
a funded account).

### Environment

| Variable | Purpose |
| ---------------- | ------------------------------------------------------------ |
| `OPENAI_API_KEY` | Sent as `Authorization: Bearer <key>`. Redacted in fixtures. |

## xAI

The `xai` deployment probes first-party `api.x.ai` directly, under provider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"Content-Type": "application/json",
"Authorization": "<REDACTED>"
}
Loading
Loading