Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
66813eb
feat(fastwire): record per-attempt tier outcomes and price from them
olddonkey Aug 18, 2026
a424413
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
7e8b300
feat(fastwire): separate Fast capability from caller-tier forwarding
olddonkey Aug 18, 2026
d7553d4
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
d7e6c11
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
330350d
fix(fastwire): address B0 review findings
olddonkey Aug 18, 2026
93bd90d
Merge branch 'codex/fastwire-b0-observability' into codex/fastwire-b1…
olddonkey Aug 18, 2026
f4d43c2
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
3c8c633
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
ef4e768
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b0-ob…
olddonkey Aug 18, 2026
4d87bce
fix(fastwire): address B0 follow-up findings
olddonkey Aug 18, 2026
d2fe94a
Merge branch 'codex/fastwire-b0-observability' into codex/fastwire-b1…
olddonkey Aug 18, 2026
5f748cf
fix(fastwire): address B1 review findings
olddonkey Aug 18, 2026
e2fb0b2
Merge remote-tracking branch 'upstream/dev' into codex/fastwire-b1-ca…
olddonkey Aug 18, 2026
88e85f2
fix(fastwire): address follow-up review findings
olddonkey Aug 18, 2026
0ceb061
Merge remote-tracking branch 'origin/dev' into codex/fastwire-b1-capa…
lidge-jun Aug 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions docs-site/src/content/docs/reference/configuration/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ differing backup and rewrites known legacy namespaced selected ids to bare ids.
| `requestPacing?` | `{ enabled, requestsPerMinute?, minIntervalMs?, models? }` | Optional client-side outbound request-start pacing, separate from upstream usage, billing, and rate-limit indicators. RPM is converted to an even interval; `minIntervalMs` may impose a longer interval. Provider limits apply across all models, while `models` entries use exact upstream model IDs (for example `nvidia/llama-3.1-nemotron-ultra-253b-v1`) and can only add delay. Queue waits do not consume the upstream response-header timeout. HTTP, Responses WebSocket, and explicit adapter `fetchResponse`/`runTurn` dispatches are covered. |
| `upstreamHttpVersion?` | `"auto" \| "http1.1" \| "h1" \| "http2" \| "h2"` | Pin the HTTP version used for upstream requests to this provider. Defaults to `auto`, which lets Bun negotiate. Set `http1.1` when a provider's HTTP/2 SSE stream stalls instead of delivering events — the symptom is a long-running streaming request that produces nothing and eventually times out. Management `POST`/`PATCH` accept `null` to clear it back to `auto`. |
| `responsesPath?` | `string` | Relative resource path for key-auth `openai-responses` requests. It must start with `/` and contain no scheme, query, or fragment. |
| `supportsServiceTier?` | `boolean` | Tri-state `service_tier` capability fallback. `true`: fast mode may inject and caller values are preserved. `false`: the field is stripped and never injected, and exact model declarations cannot reopen it. Absent: the provider is unclassified — caller-supplied values are preserved untouched and fast mode never injects unless an exact model is enabled. The registry classifies canonical OpenAI (`true`), DeepSeek, and Volcengine Ark (`false`); set it explicitly only for custom gateways that genuinely support tiers. Chat routes additionally need provider-wide or exact-model Chat authorization. |
| `modelSupportsServiceTier?` | `Record<string, boolean>` | Exact upstream model capability overrides. Exact `true` authorizes that Chat model even without `chatServiceTier`; exact `false` narrows provider defaults and Chat authorization. An explicit provider-level `supportsServiceTier: false` remains fail-closed and cannot be reopened. Undeclared models fall back to provider-wide behavior. Management `PATCH /api/providers` merges entries and accepts `null` to clear one. |
| `chatServiceTier?` | `boolean` | Provider-wide wire opt-in for serializing `service_tier` on `/chat/completions`. Exact models may instead opt in through `modelSupportsServiceTier`; undeclared models remain blocked when this flag is absent or false. |
| `supportsServiceTier?` | `boolean` | Tri-state canonical Fast capability fallback. `true` publishes Fast in the catalog, satisfies service-tier routing requirements, contributes a supported fingerprint, and lets fast mode inject the provider's canonical wire value on a compatible final adapter. `false` strips the field and never injects, and exact model declarations cannot reopen it. Absent leaves the provider unclassified: fast mode does not inject or normalize a canonical caller value, and caller values obey the final wire's forwarding permission (`chatServiceTier` on Chat; passthrough on Responses). The registry classifies canonical OpenAI (`true`), DeepSeek, and Volcengine Ark (`false`); set it explicitly only for custom gateways that genuinely support tiers. |
| `modelSupportsServiceTier?` | `Record<string, boolean>` | Exact upstream model capability overrides. Exact `true` enables canonical Fast for that model; exact `false` narrows provider defaults. An explicit provider-level `supportsServiceTier: false` remains fail-closed and cannot be reopened. Exact `true` does not authorize foreign caller-tier forwarding on Chat. Undeclared models fall back to provider-wide behavior. Management `PATCH /api/providers` merges entries and accepts `null` to clear one. |
| `chatServiceTier?` | `boolean` | Provider-wide Chat-wire opt-in for forwarding caller `service_tier` values. On a classified route it governs foreign values such as `flex`, not proxy-owned canonical Fast after capability validation; on an unclassified route it governs every caller value because no Fast capability has been validated. Exact model capability does not authorize foreign forwarding. Responses routes retain their capability-based caller forwarding behavior. |
| `preserveResponsesReasoningContent?` | `boolean` | Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it (blanking is the ChatGPT backend's rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek. Proxy-minted `ocxr1` envelopes are always stripped. |
| `disabled?` | `boolean` | Keep the provider on disk but exclude it from routing and model/catalog listings. |
| `apiKey?` | `string` | API key, or an `${ENV_VAR}` / `$ENV_VAR` reference resolved at request time. |
Expand Down Expand Up @@ -130,6 +130,27 @@ differing backup and rewrites known legacy namespaced selected ids to bare ids.
| `unsafeAllowNativeLocalExec?` | `boolean` | Cursor legacy boolean, equivalent to `nativeLocalExec: "on"` only when the newer field is unset. |
| `nativeLocalExec?` | `"off" \| "codex-sandbox" \| "on"` | Cursor local-exec policy. `off` is default; `codex-sandbox` currently fails closed like `off`. |

### FastWire B1 capability migration

Fast capability and arbitrary Chat caller-tier forwarding are independent after FastWire B1. The
[provider-field definitions](#provider-entries-ocxproviderconfig) above remain the authoritative
contract; existing configurations see these migration deltas:

1. A Chat provider/model declared Fast-capable no longer needs `chatServiceTier: true` for canonical
Fast. Publication, routing eligibility, and injection still require an eligible policy and a
compatible FastWire mapping on the final adapter. On classified routes, `fastMode: false` still
removes canonical Fast. Set `supportsServiceTier: false` or an exact-model `false` when the route
is not Fast-capable.
2. On an eligible classified route, caller spellings `fast` and `FAST` normalize through
`fastWire.canonicalToWire.priority`; caller `priority` remains canonical. Configure a verified
mapping to `fast` only when that is the upstream's canonical value. Unclassified routes retain
their existing forwarding behavior.
3. Exact-model `true` no longer authorizes foreign Chat tiers such as `flex` or vendor-specific
values. Those still require `chatServiceTier: true`; otherwise they are removed and recorded as
dropped caller tiers.

Explicit capability `false` and Responses caller-tier forwarding retain their existing contracts.

Comment thread
olddonkey marked this conversation as resolved.
API-key providers may hold a literal key or an environment reference. OAuth providers use the
credential store populated by `ocx login`; subscription-backed Claude Code launch behavior is
configured under [`claudeCode.authMode`](/reference/configuration/server/#claude-code).
Expand Down
10 changes: 10 additions & 0 deletions src/adapters/base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AdapterEvent, OcxParsedRequest } from "../types";
import type { TranslatorBudget } from "../lib/translator-budget";
import type { AdapterTierMetadata } from "../providers/fastwire";

/** Metadata about the caller's incoming request, for auth-forwarding adapters. */
export interface IncomingMeta {
Expand Down Expand Up @@ -39,6 +40,9 @@ export interface ProviderAdapter {
incoming: IncomingMeta,
emit: (event: AdapterEvent) => void,
): Promise<void>;

/** Exact no-field observation for runTurn adapters, which expose no AdapterRequest object. */
tierLogForRunTurn?(parsed: OcxParsedRequest): AdapterTierMetadata | undefined;
}

export interface AdapterRequest {
Expand Down Expand Up @@ -67,6 +71,12 @@ export interface AdapterRequest {
wireField: "reasoning_effort" | "reasoning.effort" | "thinking.type";
wireValue: string;
};
/**
* Exact tier outcome seeded after this adapter serialized the outbound request.
* This is a live shared observer: response-phase methods mutate `outcome`, so retain
* the reference rather than cloning or snapshotting it.
*/
tierLog?: AdapterTierMetadata;
usageLog?: {
inputTokens?: number;
estimated?: boolean;
Expand Down
42 changes: 30 additions & 12 deletions src/adapters/openai-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import { identifyRoutedModel } from "./identity";
import { peekReasoningForCall } from "../responses/reasoning-replay-cache";
import { buildNonOpenAIToolCatalogNudgeForTools, shouldInjectNonOpenAIToolCatalogNudge } from "./tool-catalog-nudge";
import { openRouterProviderPayload, resolveOpenRouterRouting } from "../providers/openrouter-routing";
import { canSerializeServiceTierForChatModel } from "../providers/service-tier";
import {
canForwardForeignServiceTierForChatModel,
supportsServiceTierForModel,
} from "../providers/service-tier";
import {
canonicalFastTierMarker,
createAdapterTierMetadata,
} from "../providers/fastwire";
import { openaiChatCompletionsUrl } from "./openai-chat-url";
import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema";
import {
Expand Down Expand Up @@ -1287,17 +1294,20 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
messages,
stream: parsed.stream,
};
// Preserve a caller-selected service tier for OpenAI-compatible chat gateways. The
// request pipeline deliberately does not inject fast mode for this adapter, but dropping
// an explicit value here makes the Responses parser's serviceTier projection ineffective.
//
// Opt-in, like `prompt_cache_key` directly below: `service_tier` is an OpenAI-specific
// extension and 66 registry providers share this adapter. A provider-wide Chat opt-in
// authorizes undeclared models; an exact model declaration can authorize or deny one
// model. Provider-level false remains fail-closed.
if (canSerializeServiceTierForChatModel(provider, parsed.modelId)
&& parsed.options.serviceTier !== undefined) {
body.service_tier = parsed.options.serviceTier;
// A policy-produced canonical decision has already passed capability validation. Without
// that decision, a canonical caller value still requires an explicit true capability;
// unclassified Chat routes remain behind the caller-forwarding opt-in.
const serviceTier = parsed.options.serviceTier;
const tierDecision = parsed.options.tierDecision;
const callerCanonicalFast = canonicalFastTierMarker(serviceTier) !== undefined;
const callerTierForwardAllowed = canForwardForeignServiceTierForChatModel(provider, parsed.modelId);
const canonicalFastCapability = callerCanonicalFast
&& supportsServiceTierForModel(provider, parsed.modelId) === true;
const canSerializeServiceTier = tierDecision?.kind === "set"
|| tierDecision?.kind === "forward-caller"
|| (tierDecision === undefined && (callerTierForwardAllowed || canonicalFastCapability));
if (canSerializeServiceTier && serviceTier !== undefined) {
body.service_tier = serviceTier;
}
if (modelInList(provider.reasoningSplitModels, parsed.modelId)) body.reasoning_split = true;
const maxTokens = resolveMaxTokens(provider, parsed);
Expand Down Expand Up @@ -1430,6 +1440,13 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
if (parsed.stream) body.stream_options = { include_usage: true };

const bodyJson = JSON.stringify(body);
const actualServiceTier = typeof body.service_tier === "string" ? body.service_tier : null;
const tierLog = createAdapterTierMetadata(
parsed.options.tierObservation,
parsed.options.tierDecision,
actualServiceTier === null ? null : "service-tier",
actualServiceTier,
);
if (isDebugEnabled()) {
let host = "upstream";
try { host = new URL(url).host; } catch { /* keep fallback */ }
Expand All @@ -1450,6 +1467,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
headers,
body: bodyJson,
...(reasoningLog ? { reasoningLog } : {}),
...(tierLog ? { tierLog } : {}),
};
},

Expand Down
18 changes: 16 additions & 2 deletions src/adapters/openai-responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { modelRecordValue } from "../reasoning-effort";
import type { TranslatorBudget } from "../lib/translator-budget";
import { rewriteRoutedCustomToolsForUpstream } from "../responses/custom-tool-compat";
import { openaiResponsesUrl } from "./openai-responses-url";
import {
createAdapterTierMetadata,
} from "../providers/fastwire";

// Headers relayed verbatim from the caller in OAuth-passthrough ("forward") mode.
// Exported so the web-search sidecar reuses the exact same forwarded-auth set for its ChatGPT call.
Expand Down Expand Up @@ -1426,11 +1429,21 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
convertedRoutedCustomToolNames = rewritten.names;
}
const sanitizedBody = normalizeToolSchemas(stripSparkCompatibility(stripUnsupportedReasoningParams(stripItemIdsWhenUnstored(stripInvalidItemIds(stripUnsupportedHostedTools(sanitizeReasoningInputContent(scrubOcxCompactionItems(outBody), { preserveRawReasoningContent: provider.preserveResponsesReasoningContent === true })))))));
const body = JSON.stringify(stripDisabledReasoningSummaries(
const finalBody = stripDisabledReasoningSummaries(
normalizeConfiguredReasoningSummaryDelivery(sanitizedBody, provider, parsed.modelId),
provider,
parsed.modelId,
));
);
const actualServiceTier = isPlainObject(finalBody) && typeof finalBody.service_tier === "string"
? finalBody.service_tier
: null;
const tierLog = createAdapterTierMetadata(
parsed.options?.tierObservation,
parsed.options?.tierDecision,
actualServiceTier === null ? null : "service-tier",
actualServiceTier,
);
const body = JSON.stringify(finalBody);
const releaseBodyObservation = translatorBudget.observeExternallyCapped(
"passthrough_serialization",
new TextEncoder().encode(body).byteLength,
Expand All @@ -1442,6 +1455,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
body,
releaseBodyObservation,
...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}),
...(tierLog ? { tierLog } : {}),
};
},

Expand Down
31 changes: 30 additions & 1 deletion src/adapters/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { createMimoFreeAdapter } from "./mimo-free";
import { createOpenAIChatAdapter } from "./openai-chat";
import { createResponsesPassthroughAdapter } from "./openai-responses";
import type { OcxProviderConfig } from "../types";
import { createAdapterTierMetadata } from "../providers/fastwire";

export type AdapterCacheRetention = "none" | "short" | "long";

Expand Down Expand Up @@ -142,5 +143,33 @@ export function createRegisteredAdapter(
): ProviderAdapter {
const definition = getAdapterDefinition(provider.adapter);
if (!definition) throw new Error(`Unknown adapter: ${provider.adapter}`);
return definition.create(provider, context);
const adapter = definition.create(provider, context);
const buildRequest = adapter.buildRequest.bind(adapter);
adapter.buildRequest = (parsed, incoming) => {
const attachTierMetadata = (request: Awaited<ReturnType<ProviderAdapter["buildRequest"]>>) => {
// OpenAI-family adapters report the exact emitted field themselves. Other adapters
// still report an exact absence at this serialization boundary, which makes a routed
// Fast downgrade observable without asking core to infer an outbound body shape.
request.tierLog ??= createAdapterTierMetadata(
parsed.options.tierObservation,
parsed.options.tierDecision,
null,
null,
);
return request;
};
const request = buildRequest(parsed, incoming);
return request instanceof Promise
? request.then(attachTierMetadata)
: attachTierMetadata(request);
};
if (adapter.runTurn && !adapter.tierLogForRunTurn) {
adapter.tierLogForRunTurn = parsed => createAdapterTierMetadata(
parsed.options.tierObservation,
parsed.options.tierDecision,
null,
null,
);
}
return adapter;
}
4 changes: 2 additions & 2 deletions src/codex/catalog/provider-fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,12 +408,12 @@ function captureProviderGather(
const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured));
enrichProviderFromRegistry(name, enriched);
const registryTransportMatch = providerMatchesRegistryTransport(name, enriched);
const provider = recursivelyFreeze(enriched);
const fastPolicyAuthority = captureFastPolicyAuthority(
name,
enriched,
provider,
registryTransportMatch,
);
const provider = recursivelyFreeze(enriched);
const observedAuth = authResolver.kind === "observed"
&& provider.authMode !== "forward"
&& provider.liveModels !== false
Expand Down
30 changes: 12 additions & 18 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,15 +733,7 @@ const providerConfigSchema = z.object({
repairInvalidIds: z.boolean().optional(),
}).strict().optional(),
responsesSnapshotRepair: z.boolean().optional(),
}).passthrough().superRefine((provider, ctx) => {
if (hasFastWireCapabilityConflict(provider)) {
ctx.addIssue({
code: "custom",
path: ["fastWire"],
message: "fastWire=null conflicts with supportsServiceTier=true",
});
}
});
}).passthrough();

const RESERVED_PROVIDER_NAMES = new Set([
// JavaScript prototype-pollution guards.
Expand Down Expand Up @@ -1403,6 +1395,13 @@ const configSchema = z.object({
});
}
const provider = config.providers[name];
if (hasFastWireCapabilityConflict(provider)) {
ctx.addIssue({
code: "custom",
path: ["providers", redactSecretString(name), "fastWire"],
message: "fastWire=null conflicts with supportsServiceTier=true",
});
}
const openRouterRoutingError = openRouterRoutingConfigError(provider);
if (openRouterRoutingError) {
ctx.addIssue({
Expand Down Expand Up @@ -2143,8 +2142,10 @@ function warnDegradedNativeSubagentConfig(rawParsed: unknown, config: OcxConfig)

/**
* Registry metadata can gain service-tier capability after a config was written. An explicit
* `fastWire: null` remains authoritative on load; rejecting the file would discard unrelated
* providers and API keys. Live writes remain strict through validateConfigCandidate().
* `fastWire: null` remains authoritative on load and on whole-document writes; rejecting either
* would discard or lock access to unrelated providers and API keys. Direct contradictions within
* one provider row remain schema errors through the outer config refinement, where the dynamic
* provider name can be redacted before it reaches diagnostics.
*/
function inheritedFastWireConflictProviderNames(
config: Pick<OcxConfig, "providers">,
Expand Down Expand Up @@ -2530,13 +2531,6 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
const result = configSchema.safeParse(value);
if (result.success) {
const config = normalizeApiKeyIds(result.data as OcxConfig);
const inheritedConflicts = inheritedFastWireConflictProviderNames(config);
if (inheritedConflicts.length > 0) {
return {
ok: false,
error: `schema_invalid: ${inheritedFastWireConflictWarning(inheritedConflicts[0]!)}`,
};
}
return { ok: true, config };
}
return { ok: false, error: schemaDiagnosticsError(result.error) };
Expand Down
Loading
Loading