diff --git a/src/adapters/base.ts b/src/adapters/base.ts index 8789a03463..f5ca7a1c7f 100644 --- a/src/adapters/base.ts +++ b/src/adapters/base.ts @@ -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 { @@ -39,6 +40,9 @@ export interface ProviderAdapter { incoming: IncomingMeta, emit: (event: AdapterEvent) => void, ): Promise; + + /** Exact no-field observation for runTurn adapters, which expose no AdapterRequest object. */ + tierLogForRunTurn?(parsed: OcxParsedRequest): AdapterTierMetadata | undefined; } export interface AdapterRequest { @@ -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; diff --git a/src/adapters/openai-chat.ts b/src/adapters/openai-chat.ts index 8275a5f3de..2113e6cc20 100644 --- a/src/adapters/openai-chat.ts +++ b/src/adapters/openai-chat.ts @@ -13,6 +13,9 @@ 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 { + createAdapterTierMetadata, +} from "../providers/fastwire"; import { openaiChatCompletionsUrl } from "./openai-chat-url"; import { stripResponsesOnlyEncryptedMarker } from "./responses-tool-schema"; import { @@ -1430,6 +1433,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 */ } @@ -1450,6 +1460,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd headers, body: bodyJson, ...(reasoningLog ? { reasoningLog } : {}), + ...(tierLog ? { tierLog } : {}), }; }, diff --git a/src/adapters/openai-responses.ts b/src/adapters/openai-responses.ts index 27268a481b..ac103b72f4 100644 --- a/src/adapters/openai-responses.ts +++ b/src/adapters/openai-responses.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; import type { IncomingMeta, ProviderAdapter } from "./base"; -import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage } from "../types"; +import { namespacedToolName, type AdapterEvent, type OcxParsedRequest, type OcxProviderConfig, type OcxUsage, type TierDecision } from "../types"; import { catalogModelSupportsReasoningSummaries } from "../codex/catalog"; import { COMPACT_PROMPT, decodeCompactionSummary, SUMMARY_PREFIX } from "../responses/compaction"; import { collectResponsesToolGroups } from "../responses/tool-groups"; @@ -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. @@ -764,6 +767,15 @@ function stripPreviousResponseId(body: unknown, strip: boolean): unknown { return rest; } +/** Apply the settled tier only to a fresh outbound object; `_rawBody` remains caller-owned. */ +function applyTierDecisionToResponsesBody(body: unknown, decision: TierDecision | undefined): unknown { + if (!decision || decision.kind === "forward-caller" || !isPlainObject(body)) return body; + const next: Record = { ...body }; + if (decision.kind === "set") next.service_tier = decision.value; + else delete next.service_tier; + return next; +} + /** * Drop request parameters a stateless Responses upstream cannot implement, and pin * `store` false. @@ -778,8 +790,8 @@ function stripPreviousResponseId(body: unknown, strip: boolean): unknown { * `prompt` is a reference to a server-stored prompt template — the most stateful * field in the accepted schema. * - * `service_tier` is deliberately NOT dropped: the server writes it for fast mode - * (`responses/core.ts`), and silently deleting a configured knob inside an adapter is + * `service_tier` is deliberately NOT dropped: the final TierDecision is applied to a + * detached outbound body before this sanitizer chain, and silently deleting a configured knob is * worse than forwarding a parameter the upstream ignores. * * MUST run before the composed sanitize chain below: `stripItemIdsWhenUnstored` keys @@ -1367,6 +1379,9 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): parsed._rawBody, forward || parsed._previousResponseInputExpanded === true, ); + // stripPreviousResponseId() intentionally returns its input on a no-op. Detach before the + // tier write so a force-fast/default decision can never mutate parsed._rawBody. + outBody = applyTierDecisionToResponsesBody(outBody, parsed.options?.tierDecision); const stateless = provider.statelessResponses === true; if (stateless) outBody = stripStatefulResponsesParams(outBody); // A replay miss can leave a function_call_output whose paired function_call sat @@ -1414,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, @@ -1430,6 +1455,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig): body, releaseBodyObservation, ...(convertedRoutedCustomToolNames ? { convertedRoutedCustomToolNames } : {}), + ...(tierLog ? { tierLog } : {}), }; }, diff --git a/src/adapters/registry.ts b/src/adapters/registry.ts index 896dd381bc..e9f54bcb8a 100644 --- a/src/adapters/registry.ts +++ b/src/adapters/registry.ts @@ -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"; @@ -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>) => { + // 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; } diff --git a/src/codex/catalog/provider-fetch.ts b/src/codex/catalog/provider-fetch.ts index 2675278d07..82da5c7ffa 100644 --- a/src/codex/catalog/provider-fetch.ts +++ b/src/codex/catalog/provider-fetch.ts @@ -33,10 +33,10 @@ import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, mo import { getModelMetadata, getModelMetadataCaseInsensitive, listModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata"; import { enrichProviderFromRegistry, shouldCaseFoldMetadataModelId } from "../../providers/derive"; import { - captureServiceTierAdapterAuthority, + captureFastPolicyAuthority, serviceTierSupportForModel, - type CapturedServiceTierAdapterAuthority, } from "../../providers/service-tier"; +import type { FastPolicyAuthority } from "../../providers/fastwire"; import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry"; import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models"; import { applyProviderContextCap, providerContextCap } from "../../providers/context-cap"; @@ -155,7 +155,7 @@ interface CapturedProviderGather { readonly discovery: ResolvedProviderModelDiscovery; readonly policy: CatalogProviderDiscoveryPolicySnapshot; readonly request: CapturedModelsRequest; - readonly serviceTierAdapterAuthority: CapturedServiceTierAdapterAuthority; + readonly fastPolicyAuthority: FastPolicyAuthority; readonly observedAuth?: ModelsAuthResolution; /** * Configured model ids this provider must keep even when live discovery omits @@ -408,12 +408,12 @@ function captureProviderGather( const enriched = detachedClone(withCanonicalOpenAiForwardAuthDefault(name, configured)); enrichProviderFromRegistry(name, enriched); const registryTransportMatch = providerMatchesRegistryTransport(name, enriched); - const serviceTierAdapterAuthority = captureServiceTierAdapterAuthority( + 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 @@ -449,7 +449,7 @@ function captureProviderGather( discovery, policy, request, - serviceTierAdapterAuthority, + fastPolicyAuthority, ...(observedAuth ? { observedAuth: Object.freeze({ ...observedAuth }) } : {}), ...(retainConfiguredModelIds && retainConfiguredModelIds.size > 0 ? { retainConfiguredModelIds } @@ -518,7 +518,7 @@ function captureGatherFlight( // It is the one member of a provider row that is legitimately a function, // so it is dropped here rather than allowed to break every encode. provider: omitProviderTransportExecutor(provider.provider), - serviceTierAdapterAuthority: provider.serviceTierAdapterAuthority, + fastPolicyAuthority: provider.fastPolicyAuthority, // Combo retention is capture-time state, not a provider-row field. Two // gathers that share providers but differ in combo targets must not join. retainConfiguredModelIds: [...(provider.retainConfiguredModelIds ?? [])].sort(), diff --git a/src/config.ts b/src/config.ts index d879195e88..6b69e60ffa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -65,9 +65,11 @@ import { type OcxConfig, type OcxApiKeyEntry, type OcxProviderConfig, + type FastWire, type ProviderCostOverlay, } from "./types"; import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "./providers/openai-tiers"; +import { fastWireDeclarationError, hasFastWireCapabilityConflict } from "./providers/fastwire"; import { getProviderRegistryEntry, providerMatchesRegistryTransport, @@ -662,12 +664,14 @@ function resolveRuntimePortPath(): string { } const warnedConfigFallbacks = new Set(); +const warnedInheritedFastWireConflicts = new Set(); let lastWarningReconciledGeneration = 0; export function reconcileConfigWarningMemos(generation: number): number { if (generation <= lastWarningReconciledGeneration) return 0; - const removed = warnedConfigFallbacks.size; + const removed = warnedConfigFallbacks.size + warnedInheritedFastWireConflicts.size; warnedConfigFallbacks.clear(); + warnedInheritedFastWireConflicts.clear(); lastWarningReconciledGeneration = generation; return removed; } @@ -716,6 +720,16 @@ export function requestPacingConfigError(value: unknown): string | null { return "requestPacing must contain enabled and a valid requestsPerMinute/minIntervalMs provider rule or model overrides"; } +const fastWireSchema = z.object({ + kind: z.string(), + canonicalToWire: z.record(z.string().trim(), z.string().trim()), + foreignCallerTiers: z.string(), + betas: z.array(z.string().trim()).optional(), +}).strict().superRefine((fastWire, ctx) => { + const error = fastWireDeclarationError({ fastWire }); + if (error) ctx.addIssue({ code: "custom", message: error }); +}).transform(fastWire => fastWire as FastWire); + /** * Zod schema for one provider entry: known fields are validated strictly while unknown * fields pass through (preserved for runtime extensions). @@ -731,6 +745,7 @@ const providerConfigSchema = z.object({ responsesPath: z.string().min(1).optional(), statelessResponses: z.boolean().optional(), requiresAdjacentResponsesToolResults: z.boolean().optional(), + fastWire: fastWireSchema.nullable().optional(), supportsServiceTier: z.boolean().optional(), modelSupportsServiceTier: z.record(z.string().min(1), z.boolean()).optional(), preserveResponsesReasoningContent: z.boolean().optional(), @@ -1416,6 +1431,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({ @@ -2154,6 +2176,52 @@ 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 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, +): string[] { + const conflicts: string[] = []; + for (const [name, provider] of Object.entries(config.providers)) { + if (provider.fastWire !== null || provider.supportsServiceTier === false) continue; + const registry = providerMatchesRegistryTransport(name, provider) + ? getProviderRegistryEntry(name) + : undefined; + if (!registry) continue; + const effectiveProviderCapability = provider.supportsServiceTier ?? registry.supportsServiceTier; + const effectiveModelCapabilities = { + ...(registry.modelSupportsServiceTier ?? {}), + ...(provider.modelSupportsServiceTier ?? {}), + }; + if ( + effectiveProviderCapability === true + || Object.values(effectiveModelCapabilities).some(value => value === true) + ) { + conflicts.push(name); + } + } + return conflicts; +} + +function inheritedFastWireConflictWarning(name: string): string { + return `providers.${redactSecretString(name)}.fastWire=null overrides service-tier capability inherited from the matching registry entry`; +} + +function warnInheritedFastWireConflicts(configPath: string, config: OcxConfig): void { + const names = inheritedFastWireConflictProviderNames(config); + if (names.length === 0 || warnedInheritedFastWireConflicts.has(configPath)) return; + warnedInheritedFastWireConflicts.add(configPath); + console.warn( + `⚠️ config.json ${names.map(inheritedFastWireConflictWarning).join("; ")}. ` + + "The persisted providers and API keys were preserved.", + ); +} + /** * Load and validate config.json into an OcxConfig. Missing files reset to * defaults and clear stale overlays. Broken existing files also fall back to @@ -2178,6 +2246,7 @@ export function loadConfig(): OcxConfig { const result = configSchema.safeParse(parsed); if (result.success) { const config = normalizeApiKeyIds(result.data as OcxConfig); + warnInheritedFastWireConflicts(configPath, config); warnDegradedStreamMode(parsed, config); warnDegradedHostname(parsed, config); warnDegradedApiKeys(parsed, config); @@ -2202,6 +2271,7 @@ export function loadConfig(): OcxConfig { if (retryResult.success) { warnConfigRepaired(configPath, result.error); const config = normalizeApiKeyIds(retryResult.data as OcxConfig); + warnInheritedFastWireConflicts(configPath, config); warnDegradedHostname(parsed, config); warnDegradedApiKeys(parsed, config); warnDegradedCodexAccountPriorities(parsed, config); @@ -2221,6 +2291,7 @@ export function loadConfig(): OcxConfig { { warnDroppedConfigSections(configPath, salvaged.dropped, salvaged.issues); const config = normalizeApiKeyIds(salvaged.parsed); + warnInheritedFastWireConflicts(configPath, config); warnDegradedHostname(parsed, config); warnDegradedApiKeys(parsed, config); warnDegradedCodexAccountPriorities(parsed, config); @@ -2280,6 +2351,7 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf const rawEffort = rawClaudeSubagentEffort(rawParsed); const normalized = normalizeClaudeSubagentEffort(normalizeNativeSubagentSync(config, rawParsed), rawParsed); const warnings = configPlaceholderWarnings(normalized); + warnings.push(...inheritedFastWireConflictProviderNames(normalized).map(inheritedFastWireConflictWarning)); warnings.push(...degradedCodexAccountPriorityWarnings(rawParsed, normalized)); if (rawEffort !== undefined && !isClaudeSubagentEffort(rawEffort)) { warnings.push(`claudeCode.subagentEffort ignored: expected one of ${CLAUDE_SUBAGENT_EFFORTS.join(", ")}`); @@ -2493,7 +2565,10 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx ?? loopbackListenerPortError(value); if (boundaryError) return { ok: false, error: boundaryError }; const result = configSchema.safeParse(value); - if (result.success) return { ok: true, config: normalizeApiKeyIds(result.data as OcxConfig) }; + if (result.success) { + const config = normalizeApiKeyIds(result.data as OcxConfig); + return { ok: true, config }; + } return { ok: false, error: schemaDiagnosticsError(result.error) }; } diff --git a/src/lab/subject/behavior-fingerprint.ts b/src/lab/subject/behavior-fingerprint.ts index 4cdc571aad..8d878e9df4 100644 --- a/src/lab/subject/behavior-fingerprint.ts +++ b/src/lab/subject/behavior-fingerprint.ts @@ -5,7 +5,7 @@ import type { LabBehaviorSource, LabBehaviorValues } from "../live/types"; const CLOSED_KEYS = new Set([ "wire.adapter", "wire.upstreamProtocol", "wire.responsesPath", "wire.commandCodeVersion", "wire.modelSuffixMode", "auth.mode", "auth.transport", - "responses.stateful", "responses.upstreamStreaming", "responses.serviceTier", "responses.snapshotRepair", "responses.itemIdRepair", + "responses.stateful", "responses.upstreamStreaming", "responses.serviceTier", "responses.fastWireKind", "responses.fastWireValue", "responses.snapshotRepair", "responses.itemIdRepair", "limits.contextWindow", "limits.maxInputTokens", "limits.maxOutputTokens", "modalities.input", "sampling.omitTemperature", "sampling.omitTopP", "sampling.omitPenalties", @@ -72,6 +72,6 @@ export function normalizeBehaviorValues(values: LabBehaviorValues): LabBehaviorV /** Hash the authoritative production resolver output; Lab performs validation/canonicalization only. */ export function buildBehaviorFingerprintV1(values: LabBehaviorValues): string { - const payload = { schemaVersion: 1, resolverVersion: 1, values: normalizeBehaviorValues(values) }; + const payload = { schemaVersion: 1, resolverVersion: 2, values: normalizeBehaviorValues(values) }; return createHash("sha256").update(jcsStringify(payload)).digest("hex"); } diff --git a/src/lib/redact.ts b/src/lib/redact.ts index 45a468b41d..5561d13c0c 100644 --- a/src/lib/redact.ts +++ b/src/lib/redact.ts @@ -443,6 +443,17 @@ export function redactSecretString(value: string): string { return redacted; } +/** Shared bounded representation for caller-controlled scalar metadata stored in logs. */ +export function sanitizeLogMetadataString(value: unknown, maxLength = 64): string | undefined { + if (typeof value !== "string" || !Number.isInteger(maxLength) || maxLength < 1) return undefined; + // Remove every control/line-separator code point that common terminals and log viewers + // can render as a record boundary before the value reaches a single-line log field. + const filtered = value.trim().replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/g, ""); + if (!filtered) return undefined; + const redacted = redactSecretString(filtered).trim(); + return redacted ? redacted.slice(0, maxLength) : undefined; +} + export function redactSecrets(value: unknown): unknown { if (typeof value === "string") return redactSecretString(value); if (Array.isArray(value)) return value.map(item => redactSecrets(item)); diff --git a/src/providers/derive.ts b/src/providers/derive.ts index a61d762675..c5e08065b3 100644 --- a/src/providers/derive.ts +++ b/src/providers/derive.ts @@ -1,4 +1,5 @@ import type { CodexAccountMode, OcxProviderConfig } from "../types"; +import { cloneFastWire } from "./fastwire"; import { PROVIDER_REGISTRY, registryEntryForProviderDestination, @@ -459,6 +460,9 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig } // Registry-only metadata (never seeded into saved config): backfill straight from // the entry so an explicit user value stays distinguishable from the default. + if (prov.fastWire === undefined && entry.fastWire !== undefined) { + prov.fastWire = cloneFastWire(entry.fastWire); + } if (prov.supportsServiceTier === undefined && entry.supportsServiceTier !== undefined) prov.supportsServiceTier = entry.supportsServiceTier; if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent; applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries); diff --git a/src/providers/fastwire.ts b/src/providers/fastwire.ts new file mode 100644 index 0000000000..06e877e03a --- /dev/null +++ b/src/providers/fastwire.ts @@ -0,0 +1,450 @@ +import type { + AttemptTierOutcome, + FastWire, + OcxProviderConfig, + TierDecision, + TierObservationContext, +} from "../types"; +import { MODEL_ADAPTER_OVERRIDE_ALLOWED } from "../types"; +import { redactSecretString, sanitizeLogMetadataString } from "../lib/redact"; +import type { InboundWire, ModelWireDefault } from "./registry"; + +const SERVICE_TIER_ADAPTERS = new Set(["openai-chat", "openai-responses"]); +const FAST_WIRE_ADAPTERS: Readonly>> = { + "service-tier": SERVICE_TIER_ADAPTERS, + // A1 deliberately has no adapter implementation for Anthropic speed. + "anthropic-speed": new Set(), +}; + +const DEFAULT_SERVICE_TIER_FAST_WIRE: FastWire = Object.freeze({ + kind: "service-tier" as const, + canonicalToWire: Object.freeze({ priority: "priority" }), + foreignCallerTiers: "verbatim" as const, +}); + +export type FastPolicyAuthTransport = + | "oauth_bearer" + | "forwarded_authorization" + | "none" + | "x_api_key" + | "authorization_bearer"; + +export interface FastPolicyAuthority { + readonly providerAdapter: string; + readonly fastWireDeclaration: FastWire | null | undefined; + readonly modelWireOverrideAllowed: boolean; + readonly authTransport: FastPolicyAuthTransport; + readonly capability: { + readonly provider?: boolean; + readonly models: Readonly>; + readonly chatServiceTier?: boolean; + }; + readonly modelAdapters: Readonly>; + readonly hardPins: Readonly>; + readonly registryWireDefaults: Readonly>; +} + +export interface ResolvedFastPolicy { + readonly capability: boolean | undefined; + readonly eligibility: + | "eligible" + | "capability-unsupported" + | "unclassified" + | "wire-unavailable" + | "pin-unavailable"; + readonly adapter: string; + readonly fastWire: FastWire | null; + readonly forwardCallerTier: boolean; +} + +/** Adapter-owned response observer paired with the exact body that adapter serialized. */ +export interface AdapterTierMetadata { + readonly outcome: AttemptTierOutcome; + observeResponseServiceTier(value: unknown): void; + markResponseUnparseable(): void; +} + +/** Detach a FastWire declaration from config or registry ownership. */ +export function cloneFastWire( + value: FastWire | null | undefined, + options: { freeze?: boolean } = {}, +): FastWire | null | undefined { + if (value === null || value === undefined) return value; + const canonicalToWire = { ...value.canonicalToWire }; + const betas = value.betas ? [...value.betas] : undefined; + if (options.freeze) { + Object.freeze(canonicalToWire); + if (betas) Object.freeze(betas); + } + const clone: FastWire = { + ...value, + canonicalToWire, + ...(betas ? { betas } : {}), + }; + return options.freeze ? Object.freeze(clone) : clone; +} + +function exactModelValue(record: Readonly>, modelId: string): T | undefined { + if (Object.prototype.hasOwnProperty.call(record, modelId)) return record[modelId]; + const folded = modelId.toLowerCase(); + for (const [key, value] of Object.entries(record)) { + if (key.toLowerCase() === folded) return value; + } + return undefined; +} + +export function resolveProviderAuthTransport( + adapter: string, + mode: NonNullable, + apiKeyTransport?: OcxProviderConfig["apiKeyTransport"], +): FastPolicyAuthTransport { + if (mode === "oauth") return "oauth_bearer"; + if (mode === "forward") return "forwarded_authorization"; + if (mode === "local") return "none"; + if (adapter === "anthropic" && apiKeyTransport !== "bearer") return "x_api_key"; + return "authorization_bearer"; +} + +/** Adapter-derived declaration. This runs only after the final model wire is known. */ +export function defaultFastWireForAdapter(adapter: string): FastWire | null { + return SERVICE_TIER_ADAPTERS.has(adapter) ? DEFAULT_SERVICE_TIER_FAST_WIRE : null; +} + +function registryDefaultForModel( + defaults: Readonly>, + modelId: string, + inbound: InboundWire, +): string | undefined { + const normalizedModelId = modelId.trim().toLowerCase(); + if (!Object.hasOwn(defaults, normalizedModelId)) return undefined; + const declared = defaults[normalizedModelId]; + if (declared === undefined) return undefined; + if (typeof declared !== "string" && !declared.inbound.includes(inbound)) return undefined; + const wire = typeof declared === "string" ? declared : declared.wire; + return MODEL_ADAPTER_OVERRIDE_ALLOWED.has(wire) ? wire : undefined; +} + +function resolvePolicyAdapter( + authority: FastPolicyAuthority, + modelId: string, + inbound: InboundWire, +): { adapter: string; hardPinned: boolean } { + // Hard pins and configured overrides deliberately use the same exact-key semantics as + // resolveWireProtocolOverride(). Registry defaults alone normalize ids at their boundary. + const hardPin = Object.hasOwn(authority.hardPins, modelId) + ? authority.hardPins[modelId] + : undefined; + if (typeof hardPin === "string") return { adapter: hardPin, hardPinned: true }; + if (authority.modelWireOverrideAllowed) { + const configured = Object.hasOwn(authority.modelAdapters, modelId) + ? authority.modelAdapters[modelId] + : undefined; + if (typeof configured === "string" && MODEL_ADAPTER_OVERRIDE_ALLOWED.has(configured)) { + return { adapter: configured, hardPinned: false }; + } + if (MODEL_ADAPTER_OVERRIDE_ALLOWED.has(authority.providerAdapter)) { + const registryDefault = registryDefaultForModel(authority.registryWireDefaults, modelId, inbound); + if (registryDefault !== undefined) return { adapter: registryDefault, hardPinned: false }; + } + } + return { adapter: authority.providerAdapter, hardPinned: false }; +} + +/** A1's retained Chat serializer gate (`chatServiceTier || exact model true`). */ +export function legacyChatEligibility(authority: FastPolicyAuthority, modelId: string): boolean { + const exact = exactModelValue(authority.capability.models, modelId); + if (authority.capability.provider === false || exact === false) return false; + return authority.capability.chatServiceTier === true || exact === true; +} + +export function resolveFastPolicy( + authority: FastPolicyAuthority, + modelId: string, + inbound: InboundWire = "responses", +): ResolvedFastPolicy { + const { adapter, hardPinned } = resolvePolicyAdapter(authority, modelId, inbound); + const exactCapability = exactModelValue(authority.capability.models, modelId); + const capability = authority.capability.provider === false + ? false + : exactCapability ?? authority.capability.provider; + const fastWire = authority.fastWireDeclaration === undefined + ? defaultFastWireForAdapter(adapter) + : authority.fastWireDeclaration; + const wireAvailable = fastWire !== null && FAST_WIRE_ADAPTERS[fastWire.kind].has(adapter); + const chatEligible = adapter !== "openai-chat" || legacyChatEligibility(authority, modelId); + // Explicit null disables Fast injection, but the defensive true+null branch still preserves + // a caller tier on an existing OpenAI service-tier wire. + const callerWireAvailable = wireAvailable + || (fastWire === null && SERVICE_TIER_ADAPTERS.has(adapter)); + const forwardCallerTier = capability !== false && callerWireAvailable && chatEligible; + + let eligibility: ResolvedFastPolicy["eligibility"]; + if (capability === false) eligibility = "capability-unsupported"; + else if (!wireAvailable) { + eligibility = hardPinned && authority.fastWireDeclaration !== null + ? "pin-unavailable" + : "wire-unavailable"; + } + else if (!chatEligible) eligibility = "capability-unsupported"; + else if (capability === undefined) eligibility = "unclassified"; + else eligibility = "eligible"; + + return { capability, eligibility, adapter, fastWire, forwardCallerTier }; +} + +export function canonicalFastTierMarker(callerTier: string | undefined): "priority" | undefined { + const folded = callerTier?.trim().toLowerCase(); + return folded === "priority" || folded === "fast" ? "priority" : undefined; +} + +/** Capture Fast demand before the final A1 serialization action rewrites the parsed tier view. */ +export function tierObservationContext( + policy: ResolvedFastPolicy, + fastMode: boolean | undefined, + callerTier: string | undefined, +): TierObservationContext { + return { + capability: policy.capability, + eligibility: policy.eligibility, + fastWire: policy.fastWire, + demandDecision: fastMode === true ? "force-fast" : fastMode === false ? "force-default" : "inherit", + ...(callerTier !== undefined ? { callerTier } : {}), + }; +} + +function canonicalFromWire( + fastWire: FastWire | null, + wireValue: string, +): string | undefined { + if (!fastWire) return undefined; + for (const [canonical, mapped] of Object.entries(fastWire.canonicalToWire)) { + if (mapped === wireValue) return canonical; + } + return undefined; +} + +function downgradeReasonForUnavailable( + context: TierObservationContext, +): AttemptTierOutcome["fastDowngradeReason"] { + if (context.capability === false || context.eligibility === "capability-unsupported") { + return "route-unsupported"; + } + return "wire-unavailable"; +} + +/** + * Build the mutable observation record only after an adapter has completed serialization. + * `wireKind`/`wireValue` describe the field the adapter actually emitted, never a route guess. + */ +export function createAdapterTierMetadata( + context: TierObservationContext | undefined, + decision: TierDecision | undefined, + wireKind: FastWire["kind"] | null, + wireValue: string | null, +): AdapterTierMetadata | undefined { + if (!context || !decision) return undefined; + + const callerCanonicalFast = canonicalFastTierMarker(context.callerTier) === "priority"; + const callerTierDropped = context.callerTier !== undefined + && !callerCanonicalFast + && wireValue === null; + const callerFastSuppressedByConfig = context.capability !== undefined + && context.demandDecision === "force-default" + && callerCanonicalFast; + const loggedWireValue = wireValue === null ? null : sanitizeLogMetadataString(wireValue); + const outcome: AttemptTierOutcome = { + wireKind, + ...(wireValue === null + ? { wireValue: null } + : loggedWireValue ? { wireValue: loggedWireValue } : {}), + fastOutcome: "unknown", + confirmation: "unknown", + ...(callerTierDropped ? { callerTierDropped: true } : {}), + ...(callerFastSuppressedByConfig ? { callerFastSuppressedByConfig: true } : {}), + }; + + // A0/A1 deliberately make fastMode inert for unclassified routes. Preserve that uncertainty: + // do not infer demand, suppression, or a canonical tier from a verbatim caller passthrough. + if (context.capability === undefined || context.eligibility === "unclassified") { + delete outcome.callerFastSuppressedByConfig; + return { + outcome, + observeResponseServiceTier(value: unknown) { + if (typeof value === "string" && value.trim()) { + outcome.responseServiceTier = redactSecretString(value).slice(0, 64); + } + }, + markResponseUnparseable() {}, + }; + } + + const effectiveFastRequested = context.capability === true + && context.fastWire !== null + && (context.demandDecision === "force-fast" + || (context.demandDecision === "inherit" && callerCanonicalFast)); + // Known-unsupported routes still need a downgrade when the caller/config expressed Fast intent, + // but they are deliberately outside the effective-demand calculation above. + const fastIntent = context.demandDecision === "force-fast" + || (context.demandDecision === "inherit" && callerCanonicalFast); + + if (!fastIntent) { + outcome.fastOutcome = "not-requested"; + } else if (!effectiveFastRequested || context.eligibility !== "eligible" || wireValue === null) { + outcome.fastOutcome = "downgraded"; + outcome.fastDowngradeReason = downgradeReasonForUnavailable(context); + outcome.confirmation = "downgraded"; + } else if (canonicalFromWire(context.fastWire, wireValue) === "priority") { + outcome.canonical = "priority"; + outcome.fastOutcome = "applied"; + outcome.confirmation = "assumed"; + } + + const responseCanConfirmFast = effectiveFastRequested + && context.eligibility === "eligible" + && wireValue !== null; + return { + outcome, + observeResponseServiceTier(value: unknown) { + if (typeof value !== "string" || !value.trim()) { + if (value !== undefined && responseCanConfirmFast) { + delete outcome.canonical; + delete outcome.fastDowngradeReason; + outcome.fastOutcome = "unknown"; + outcome.confirmation = "unknown"; + } + return; + } + outcome.responseServiceTier = redactSecretString(value).slice(0, 64); + if (!responseCanConfirmFast) return; + if (canonicalFromWire(context.fastWire, value) === "priority") { + outcome.canonical = "priority"; + delete outcome.fastDowngradeReason; + outcome.fastOutcome = "applied"; + outcome.confirmation = "confirmed"; + } else { + delete outcome.canonical; + outcome.fastOutcome = "downgraded"; + outcome.fastDowngradeReason = "response-declined"; + outcome.confirmation = "downgraded"; + } + }, + markResponseUnparseable() { + if (!responseCanConfirmFast) return; + delete outcome.canonical; + delete outcome.fastDowngradeReason; + delete outcome.responseServiceTier; + outcome.fastOutcome = "unknown"; + outcome.confirmation = "unknown"; + }, + }; +} + +/** Pure A1 tier state machine. It never changes a caller spelling on inherit. */ +export function decideTier( + policy: ResolvedFastPolicy, + fastMode: boolean | undefined, + callerTier: string | undefined, +): TierDecision { + if (policy.capability === false) return { kind: "drop" }; + if (policy.capability === undefined) { + return policy.forwardCallerTier ? { kind: "forward-caller" } : { kind: "drop" }; + } + if (policy.fastWire === null) { + return policy.forwardCallerTier ? { kind: "forward-caller" } : { kind: "drop" }; + } + if (policy.eligibility !== "eligible") return { kind: "drop" }; + if (fastMode === true) { + const value = policy.fastWire.canonicalToWire.priority; + return typeof value === "string" && value.length > 0 + ? { kind: "set", value } + : { kind: "drop" }; + } + if (fastMode === false) return { kind: "drop" }; + if ( + callerTier !== undefined + && canonicalFastTierMarker(callerTier) === undefined + && policy.fastWire.foreignCallerTiers === "drop" + ) { + return { kind: "drop" }; + } + return { kind: "forward-caller" }; +} + +export function tierValueAfterDecision( + decision: TierDecision, + callerTier: string | undefined, +): string | undefined { + if (decision.kind === "set") return decision.value; + if (decision.kind === "drop") return undefined; + return callerTier; +} + +function isPlainRecord(value: unknown): value is Record { + if (!value || typeof value !== "object" || Array.isArray(value)) return false; + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} + +export function hasFastWireCapabilityConflict(source: { + readonly fastWire?: unknown; + readonly supportsServiceTier?: unknown; + readonly modelSupportsServiceTier?: unknown; +}): boolean { + if (source.fastWire !== null) return false; + if (source.supportsServiceTier === false) return false; + if (source.supportsServiceTier === true) return true; + return isPlainRecord(source.modelSupportsServiceTier) + && Object.values(source.modelSupportsServiceTier).some(value => value === true); +} + +/** Runtime registry validation; config uses the equivalent Zod shape at its boundary. */ +export function fastWireDeclarationError(source: { + readonly fastWire?: unknown; + readonly supportsServiceTier?: unknown; + readonly modelSupportsServiceTier?: unknown; +}): string | null { + const value = source.fastWire; + if (value === undefined) return null; + if (hasFastWireCapabilityConflict(source)) { + return "fastWire=null conflicts with supportsServiceTier=true"; + } + if (value === null) return null; + if (!isPlainRecord(value)) return "fastWire must be an object, null, or absent"; + if (value.kind !== "service-tier" && value.kind !== "anthropic-speed") { + return "fastWire.kind must be service-tier or anthropic-speed"; + } + if (value.foreignCallerTiers !== "verbatim" && value.foreignCallerTiers !== "drop") { + return "fastWire.foreignCallerTiers must be verbatim or drop"; + } + if (!isPlainRecord(value.canonicalToWire)) return "fastWire.canonicalToWire must be an object"; + if (!Object.prototype.hasOwnProperty.call(value.canonicalToWire, "priority")) { + return "fastWire.canonicalToWire must include priority"; + } + const wireValues: string[] = []; + for (const [canonicalTier, wireValue] of Object.entries(value.canonicalToWire)) { + if (canonicalTier.trim().length === 0) { + return "fastWire.canonicalToWire keys must be nonblank strings"; + } + if (typeof wireValue !== "string" || wireValue.trim().length === 0 || wireValue.trim().length > 64) { + return "fastWire.canonicalToWire values must be nonblank strings of at most 64 characters"; + } + wireValues.push(wireValue.trim()); + } + if (new Set(wireValues).size !== wireValues.length) { + return "fastWire.canonicalToWire values must be unique"; + } + if (value.betas !== undefined) { + if (!Array.isArray(value.betas) || value.betas.length > 16) { + return "fastWire.betas must be an array of at most 16 values"; + } + const betas: string[] = []; + for (const beta of value.betas) { + if (typeof beta !== "string" || beta.trim().length === 0) { + return "fastWire.betas values must be nonblank strings"; + } + betas.push(beta.trim()); + } + if (new Set(betas).size !== betas.length) return "fastWire.betas values must be unique"; + } + return null; +} diff --git a/src/providers/registry.ts b/src/providers/registry.ts index d188185ed3..b3343b6018 100644 --- a/src/providers/registry.ts +++ b/src/providers/registry.ts @@ -1,4 +1,5 @@ -import type { CodexAccountMode, OcxProviderConfig } from "../types"; +import type { CodexAccountMode, FastWire, OcxProviderConfig } from "../types"; +import { fastWireDeclarationError } from "./fastwire"; import { KIRO_MODELS, KIRO_MODEL_CONTEXT_WINDOWS, KIRO_MODEL_REASONING_EFFORTS } from "./kiro-models"; import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS, ANTIGRAVITY_MODEL_EFFORTS, ANTIGRAVITY_MODEL_INPUT_MODALITIES } from "./antigravity-models"; import type { ProviderBaseUrlChoice } from "./base-url-choices"; @@ -165,6 +166,8 @@ export interface ProviderRegistryEntry { * of paying a translation hop. */ modelWireDefaults?: Record; + /** Explicit Fast wire declaration; absence derives from the final model adapter. */ + fastWire?: FastWire | null; /** * Registry-only per-model override for the upstream request shape used behind a * Codex Responses WebSocket turn. `false` keeps the client-facing WebSocket but @@ -2531,6 +2534,17 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [ { id: "gitlab-duo", label: "GitLab Duo", baseUrl: "https://cloud.gitlab.com/ai/v1/proxy/openai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://gitlab.com/-/user_settings/personal_access_tokens" }, ]; +export function providerRegistryFastWireError( + entry: Pick, +): string | null { + return fastWireDeclarationError(entry); +} + +for (const entry of PROVIDER_REGISTRY) { + const error = providerRegistryFastWireError(entry); + if (error) throw new TypeError(`Invalid provider registry entry ${entry.id}: ${error}`); +} + export function getProviderRegistryEntry(id: string): ProviderRegistryEntry | undefined { return PROVIDER_REGISTRY.find(entry => entry.id === id); } diff --git a/src/providers/service-tier.ts b/src/providers/service-tier.ts index aa4fab044a..224ffc135e 100644 --- a/src/providers/service-tier.ts +++ b/src/providers/service-tier.ts @@ -1,116 +1,203 @@ import type { OcxProviderConfig } from "../types"; -import { MODEL_ADAPTER_OVERRIDE_ALLOWED } from "../types"; -import { getProviderRegistryEntry, providerModelWireDefault, type InboundWire } from "./registry"; +import { captureWireAdapterHardPins } from "../types"; +import { isCanonicalOpenAiForwardProvider } from "./openai-tiers"; +import { + getProviderRegistryEntry, + providerMatchesRegistryTransport, + type InboundWire, + type ModelWireDefault, +} from "./registry"; +import { + cloneFastWire, + legacyChatEligibility, + resolveFastPolicy, + resolveProviderAuthTransport, + type FastPolicyAuthority, + type ResolvedFastPolicy, +} from "./fastwire"; /** OpenAI-compatible adapters that can carry the standard `service_tier` field. */ export const SERVICE_TIER_ADAPTERS = new Set(["openai-chat", "openai-responses"]); -export type CapturedServiceTierAdapterAuthority = Readonly>; +/** @deprecated A1 evolves this snapshot into the complete FastPolicyAuthority. */ +export type CapturedServiceTierAdapterAuthority = FastPolicyAuthority; -const capturedAdapterAuthority = new WeakMap(); +const capturedFastPolicyAuthorities = new WeakMap(); type ServiceTierCapabilityProvider = Pick< OcxProviderConfig, - "adapter" | "supportsServiceTier" | "modelSupportsServiceTier" | "modelAdapters" | "baseUrl" | "authMode" | "chatServiceTier" + | "adapter" + | "supportsServiceTier" + | "modelSupportsServiceTier" + | "modelAdapters" + | "baseUrl" + | "authMode" + | "apiKeyTransport" + | "chatServiceTier" + | "fastWire" >; +function cloneRegistryWireDefaults( + defaults: Readonly> | undefined, +): Readonly> { + if (!defaults) return Object.freeze({}); + const clone: Record = {}; + for (const [modelId, declaration] of Object.entries(defaults)) { + clone[modelId.trim().toLowerCase()] = typeof declaration === "string" + ? declaration + : Object.freeze({ wire: declaration.wire, inbound: Object.freeze([...declaration.inbound]) }); + } + return Object.freeze(clone); +} + /** - * Read a model map by exact model identity. Service-tier capability is deliberately - * stricter than the older model metadata maps: a family key or a colon-qualified - * fallback must not silently advertise Fast for a sibling model that was never verified. - * A case-insensitive exact match keeps hand-edited ids consistent with the other maps - * without widening the model scope. + * Capture every registry-owned input before an asynchronous catalog flight begins. + * The resolver itself is pure and never reads the live provider registry. */ -function exactModelValue( - record: Record | undefined, - modelId: string, -): T | undefined { - if (!record) return undefined; - if (Object.prototype.hasOwnProperty.call(record, modelId)) return record[modelId]; - const folded = modelId.toLowerCase(); - for (const [key, value] of Object.entries(record)) { - if (key.toLowerCase() === folded) return value; +function buildFastPolicyAuthority( + providerName: string, + provider: ServiceTierCapabilityProvider, + registryTransportMatch: boolean, +): FastPolicyAuthority { + const registry = registryTransportMatch ? getProviderRegistryEntry(providerName) : undefined; + const authority: FastPolicyAuthority = Object.freeze({ + providerAdapter: provider.adapter, + fastWireDeclaration: cloneFastWire( + provider.fastWire !== undefined ? provider.fastWire : registry?.fastWire, + { freeze: true }, + ), + modelWireOverrideAllowed: !isCanonicalOpenAiForwardProvider(provider as OcxProviderConfig), + authTransport: resolveProviderAuthTransport( + provider.adapter, + provider.authMode ?? registry?.authKind ?? "key", + provider.apiKeyTransport, + ), + capability: Object.freeze({ + ...(provider.supportsServiceTier !== undefined ? { provider: provider.supportsServiceTier } : {}), + models: Object.freeze({ ...(provider.modelSupportsServiceTier ?? {}) }), + ...(provider.chatServiceTier !== undefined ? { chatServiceTier: provider.chatServiceTier } : {}), + }), + modelAdapters: Object.freeze({ ...(provider.modelAdapters ?? {}) }), + hardPins: captureWireAdapterHardPins(providerName), + registryWireDefaults: cloneRegistryWireDefaults(registry?.modelWireDefaults), + }); + return authority; +} + +export function captureFastPolicyAuthority( + providerName: string, + provider: ServiceTierCapabilityProvider, + registryTransportMatch: boolean, +): FastPolicyAuthority { + const authority = buildFastPolicyAuthority(providerName, provider, registryTransportMatch); + if (Object.isFrozen(provider)) capturedFastPolicyAuthorities.set(provider, authority); + return authority; +} + +/** @deprecated Use captureFastPolicyAuthority. The legacy inbound argument is now snapshot data. */ +export function captureServiceTierAdapterAuthority( + providerName: string, + provider: ServiceTierCapabilityProvider, + registryTransportMatch: boolean, + _inbound: InboundWire = "responses", +): FastPolicyAuthority { + return captureFastPolicyAuthority(providerName, provider, registryTransportMatch); +} + +function authorityForProvider( + provider: ServiceTierCapabilityProvider, + providerName?: string, +): FastPolicyAuthority { + // Preserve the legacy no-name short circuit: serviceTierSupportForModel() used the + // provider adapter directly when no provider identity was available, so no configured + // override, hard pin, or registry default may participate on this path in A1. + if (providerName === undefined) { + const authority = buildFastPolicyAuthority("", provider, false); + return Object.freeze({ + ...authority, + modelAdapters: Object.freeze({}), + hardPins: Object.freeze({}), + registryWireDefaults: Object.freeze({}), + }); } - return undefined; + const captured = Object.isFrozen(provider) + ? capturedFastPolicyAuthorities.get(provider) + : undefined; + if (captured) return captured; + const registryTransportMatch = providerMatchesRegistryTransport(providerName, provider); + const authority = buildFastPolicyAuthority(providerName, provider, registryTransportMatch); + // Frozen provider snapshots cannot drift, so repeated catalog/runtime projections may safely + // reuse the registry lookup and detached declaration maps. Mutable configs still rebuild. + if (Object.isFrozen(provider)) capturedFastPolicyAuthorities.set(provider, authority); + return authority; +} + +/** Resolve the pure Fast policy for a provider/model pair. */ +export function fastPolicyForModel( + provider: ServiceTierCapabilityProvider, + modelId: string, + providerName?: string, + inbound: InboundWire = "responses", +): ResolvedFastPolicy { + return resolveFastPolicy(authorityForProvider(provider, providerName), modelId, inbound); } /** - * Resolve the declared provider/model capability. An explicit provider-level false is a - * fail-closed boundary and cannot be reopened by a model map. Otherwise an exact model - * declaration wins over the provider default, including an explicit false. The resolver is - * provider-local: the caller must first resolve the final provider, so identical bare model ids - * on two providers cannot share capability state. + * Resolve the declared provider/model capability without applying wire availability. + * Kept as a public compatibility helper for callers that need the pure tri-state. */ export function supportsServiceTierForModel( provider: Pick, modelId: string, ): boolean | undefined { - if (provider.supportsServiceTier === false) return false; - return exactModelValue(provider.modelSupportsServiceTier, modelId) - ?? provider.supportsServiceTier; + const authority: FastPolicyAuthority = { + providerAdapter: "openai-responses", + fastWireDeclaration: undefined, + modelWireOverrideAllowed: true, + authTransport: "authorization_bearer", + capability: { + ...(provider.supportsServiceTier !== undefined ? { provider: provider.supportsServiceTier } : {}), + models: provider.modelSupportsServiceTier ?? {}, + }, + modelAdapters: {}, + hardPins: {}, + registryWireDefaults: {}, + }; + return resolveFastPolicy(authority, modelId).capability; } -/** Whether the Chat serializer may emit a tier for this exact model. */ +/** A1 name retained for the legacy Chat serializer gate. */ export function canSerializeServiceTierForChatModel( provider: Pick, modelId: string, ): boolean { - const exact = exactModelValue(provider.modelSupportsServiceTier, modelId); - if (provider.supportsServiceTier === false || exact === false) return false; - return provider.chatServiceTier === true || exact === true; + return legacyChatEligibility({ + providerAdapter: "openai-chat", + fastWireDeclaration: undefined, + modelWireOverrideAllowed: true, + authTransport: "authorization_bearer", + capability: { + ...(provider.supportsServiceTier !== undefined ? { provider: provider.supportsServiceTier } : {}), + models: provider.modelSupportsServiceTier ?? {}, + ...(provider.chatServiceTier !== undefined ? { chatServiceTier: provider.chatServiceTier } : {}), + }, + modelAdapters: {}, + hardPins: {}, + registryWireDefaults: {}, + }, modelId); } -/** Capture registry-owned model wire defaults before an asynchronous catalog flight begins. */ -export function captureServiceTierAdapterAuthority( - providerName: string, - provider: Pick, - registryTransportMatch: boolean, - inbound: InboundWire = "responses", -): CapturedServiceTierAdapterAuthority { - const authority: Record = {}; - const defaults = registryTransportMatch - ? getProviderRegistryEntry(providerName)?.modelWireDefaults - : undefined; - for (const modelId of Object.keys(defaults ?? {})) { - const adapter = providerModelWireDefault( - providerName, - provider, - modelId, - MODEL_ADAPTER_OVERRIDE_ALLOWED, - inbound, - ); - if (adapter !== undefined) authority[modelId.trim().toLowerCase()] = adapter; - } - const frozen = Object.freeze(authority); - capturedAdapterAuthority.set(provider, frozen); - return frozen; -} - -/** Resolve an explicit model wire override for catalog-time capability projection. */ +/** Final adapter selected by the Fast policy's four-level wire resolver. */ export function serviceTierAdapterForModel( providerName: string, - provider: Pick, + provider: ServiceTierCapabilityProvider, modelId: string, inbound: InboundWire = "responses", ): string { - // Keep this lookup identical to resolveWireProtocolOverride(): configured model-adapter - // entries are exact-case keys, while registry defaults intentionally normalize ids there. - const configured = provider.modelAdapters?.[modelId]; - if (configured !== undefined && MODEL_ADAPTER_OVERRIDE_ALLOWED.has(configured)) return configured; - const captured = capturedAdapterAuthority.get(provider); - if (captured !== undefined) { - return captured[modelId.trim().toLowerCase()] ?? provider.adapter; - } - return providerModelWireDefault( - providerName, - provider, - modelId, - MODEL_ADAPTER_OVERRIDE_ALLOWED, - inbound, - ) ?? provider.adapter; + return fastPolicyForModel(provider, modelId, providerName, inbound).adapter; } -/** Whether the final provider/model pair can actually publish/send OpenAI service tiers. */ +/** Whether the final provider/model pair can publish/send OpenAI service tiers. */ export function canForwardServiceTierForModel( provider: ServiceTierCapabilityProvider, modelId: string, @@ -121,9 +208,8 @@ export function canForwardServiceTierForModel( } /** - * Return the tri-state capability after resolving the model's final wire adapter. - * `false` means either an explicit provider/model denial or an adapter that cannot carry the - * field; `undefined` keeps the existing conservative contract for an unclassified OpenAI wire. + * Compatibility projection for catalog, routing, and fingerprint consumers. The new + * resolver carries richer eligibility internally while preserving the old tri-state bytes. */ export function serviceTierSupportForModel( provider: ServiceTierCapabilityProvider, @@ -131,13 +217,15 @@ export function serviceTierSupportForModel( providerName?: string, inbound: InboundWire = "responses", ): boolean | undefined { - const adapter = providerName === undefined - ? provider.adapter - : serviceTierAdapterForModel(providerName, provider, modelId, inbound); - if (!SERVICE_TIER_ADAPTERS.has(adapter)) return false; - // Treat the Chat serializer decision as authoritative so catalog metadata, routing - // evidence, fast-mode injection, and caller-tier stripping cannot claim support that the - // final request builder will omit. A provider-wide false and an exact false stay closed. - if (adapter === "openai-chat" && !canSerializeServiceTierForChatModel(provider, modelId)) return false; - return supportsServiceTierForModel(provider, modelId); + const policy = fastPolicyForModel(provider, modelId, providerName, inbound); + return serviceTierSupportFromPolicy(policy); +} + +/** Compatibility projection shared by catalog, routing, and request logging. */ +export function serviceTierSupportFromPolicy( + policy: Pick, +): boolean | undefined { + if (policy.eligibility === "eligible") return true; + if (policy.eligibility === "unclassified") return undefined; + return false; } diff --git a/src/router.ts b/src/router.ts index e510a1f06f..723ff8ca84 100644 --- a/src/router.ts +++ b/src/router.ts @@ -13,6 +13,7 @@ import { assertProviderDestinationAllowed } from "./lib/destination-policy"; import { redactSecretString, redactUrlForLog } from "./lib/redact"; import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry"; import { applyDirectReasoningEffortContracts, hasLegacyClinePassReasoningEfforts } from "./providers/derive"; +import { cloneFastWire } from "./providers/fastwire"; import { providerMatchesRegistryTransportWithStaticGuards, providerSupportsLiveModelDiscovery, @@ -333,6 +334,11 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider && registryEntry.requiresAdjacentResponsesToolResults !== undefined ? { requiresAdjacentResponsesToolResults: registryEntry.requiresAdjacentResponsesToolResults } : {}), + ...(provider.fastWire === undefined && registryEntry.fastWire !== undefined + ? { + fastWire: cloneFastWire(registryEntry.fastWire), + } + : {}), ...(provider.supportsServiceTier === undefined && registryEntry.supportsServiceTier !== undefined ? { supportsServiceTier: registryEntry.supportsServiceTier } : {}), diff --git a/src/routing/compatibility/behavior.ts b/src/routing/compatibility/behavior.ts index 5eaa46c4fc..553e531bb1 100644 --- a/src/routing/compatibility/behavior.ts +++ b/src/routing/compatibility/behavior.ts @@ -1,6 +1,7 @@ import type { OcxConfig, OcxProviderConfig } from "../../types"; -import { PROVIDER_REGISTRY, type ProviderAuthKind } from "../../providers/registry"; -import { serviceTierSupportForModel } from "../../providers/service-tier"; +import { PROVIDER_REGISTRY } from "../../providers/registry"; +import { fastPolicyForModel, serviceTierSupportForModel } from "../../providers/service-tier"; +import { resolveProviderAuthTransport } from "../../providers/fastwire"; import { localFingerprint } from "../../lab/digest"; import type { LabBehaviorSource, LabBehaviorValues } from "../../lab/live/types"; @@ -57,18 +58,6 @@ function nonCredentialHeaderDigest( return localFingerprint("nonCredentialHeaders", rows, installationSalt); } -function authTransportFor( - effective: OcxProviderConfig, - adapter: string, - mode: ProviderAuthKind, -): string { - if (mode === "oauth") return "oauth_bearer"; - if (mode === "forward") return "forwarded_authorization"; - if (mode === "local") return "none"; - if (adapter === "anthropic" && effective.apiKeyTransport !== "bearer") return "x_api_key"; - return "authorization_bearer"; -} - function effectiveOpenRouterRouting(effective: OcxProviderConfig, modelId: string) { return effective.modelOpenRouterRouting?.[modelId] ?? effective.openRouterRouting; } @@ -104,6 +93,7 @@ export function resolveProductionBehaviorValues( const project = typeof effective.project === "string" && effective.project ? effective.project : null; const location = typeof effective.location === "string" && effective.location ? effective.location : null; const nativeLocalExec = effective.nativeLocalExec === "on" || effective.unsafeAllowNativeLocalExec === true; + const fastPolicy = fastPolicyForModel(effective, modelId, providerName); const values: LabBehaviorValues = { "wire.adapter": behaviorRow("provider_config", adapter), @@ -115,12 +105,23 @@ export function resolveProductionBehaviorValues( effective.modelSuffixBracketStrip === true ? "bracket_strip" : "none", ), "auth.mode": behaviorRow("provider_config", authMode), - "auth.transport": behaviorRow("provider_config", authTransportFor(effective, adapter, authMode)), + "auth.transport": behaviorRow( + "provider_config", + resolveProviderAuthTransport(adapter, authMode, effective.apiKeyTransport), + ), "responses.stateful": behaviorRow("provider_config", effective.statelessResponses !== true), "responses.serviceTier": behaviorRow( "provider_config", serviceTierSupportForModel(effective, modelId, providerName) ?? null, ), + "responses.fastWireKind": behaviorRow( + "provider_config", + fastPolicy.fastWire?.kind ?? null, + ), + "responses.fastWireValue": behaviorRow( + "provider_config", + fastPolicy.fastWire?.canonicalToWire.priority ?? null, + ), "responses.snapshotRepair": behaviorRow("provider_config", effective.responsesSnapshotRepair === true), "responses.itemIdRepair": behaviorRow("provider_config", effective.responsesItemIdRepair ?? null), "limits.contextWindow": behaviorRow( diff --git a/src/server/management/shared.ts b/src/server/management/shared.ts index 428f7df0eb..77d1f74264 100644 --- a/src/server/management/shared.ts +++ b/src/server/management/shared.ts @@ -92,7 +92,7 @@ export type CostResult = | { kind: "value"; estimate: NonNullable>; estimateReasons: CostEstimateReason[] } | { kind: "unavailable"; reason: MetricUnavailableReason }; -export type MetricSource = Pick & { +export type MetricSource = Pick & { attempts?: readonly PersistedUsageAttempt[]; }; diff --git a/src/server/request-log.ts b/src/server/request-log.ts index c69ea2a110..ee12470924 100644 --- a/src/server/request-log.ts +++ b/src/server/request-log.ts @@ -9,9 +9,10 @@ import { } from "../lib/errors"; import { CODEX_CONFIG_PATH, readRootTomlString } from "../codex/paths"; import { readCodexCatalogPath } from "../codex/catalog"; -import type { OcxUsage } from "../types"; +import type { AttemptTierOutcome, OcxUsage } from "../types"; import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace"; import type { AdapterRequest } from "../adapters/base"; +import type { AdapterTierMetadata } from "../providers/fastwire"; import { redactSecretString } from "../lib/redact"; import { appendUsageEntry, @@ -70,12 +71,15 @@ export interface RequestLogContext { effectiveEffort?: string; reasoningWireField?: string; reasoningWireValue?: string | number | boolean; + callerServiceTier?: string; requestedServiceTier?: string; requestedSpeedLabel?: string; configuredServiceTier?: string; configuredSpeedLabel?: string; modelSupportsServiceTier?: boolean; responseServiceTier?: string; + /** Final-attempt tier summary; attempt rows remain the accounting source of truth. */ + tierOutcome?: AttemptTierOutcome; resolvedModel?: string; /** Internal: client-facing response metadata must not replace the physical routed model. */ preserveResolvedModelFromRoute?: boolean; @@ -86,6 +90,8 @@ export interface RequestLogContext { activeAttempt?: PersistedUsageAttempt; /** Internal wall-clock origin for the committed final attempt; never persisted. */ activeAttemptStartedAt?: number; + /** Internal adapter response observer paired with activeAttempt.tierOutcome. */ + activeTierMetadata?: AdapterTierMetadata; usageDebugBodyKind?: UsageDebugBodyKind; usageDebugBodySample?: string; usageDebugContentType?: string; @@ -140,12 +146,14 @@ export interface RequestLogEntry { effectiveEffort?: string; reasoningWireField?: string; reasoningWireValue?: string | number | boolean; + callerServiceTier?: string; requestedServiceTier?: string; requestedSpeedLabel?: string; configuredServiceTier?: string; configuredSpeedLabel?: string; modelSupportsServiceTier?: boolean; responseServiceTier?: string; + tierOutcome?: AttemptTierOutcome; resolvedModel?: string; status: number; durationMs: number; @@ -251,6 +259,7 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}), ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}), ...(entry.reasoningWireValue !== undefined ? { reasoningWireValue: entry.reasoningWireValue } : {}), + ...(entry.callerServiceTier ? { callerServiceTier: entry.callerServiceTier } : {}), ...(entry.requestedServiceTier ? { requestedServiceTier: entry.requestedServiceTier } : {}), ...(entry.requestedSpeedLabel ? { requestedSpeedLabel: entry.requestedSpeedLabel } : {}), ...(entry.configuredServiceTier ? { configuredServiceTier: entry.configuredServiceTier } : {}), @@ -259,6 +268,7 @@ export function requestLogEntryFromPersistedUsage(entry: PersistedUsageEntry): R ? { modelSupportsServiceTier: entry.modelSupportsServiceTier } : {}), ...(entry.responseServiceTier ? { responseServiceTier: entry.responseServiceTier } : {}), + ...(entry.tierOutcome ? { tierOutcome: entry.tierOutcome } : {}), ...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}), status: entry.status, durationMs: entry.durationMs, @@ -352,6 +362,7 @@ export function addRequestLog(entry: RequestLogEntry) { ...(entry.effectiveEffort ? { effectiveEffort: entry.effectiveEffort } : {}), ...(entry.reasoningWireField ? { reasoningWireField: entry.reasoningWireField } : {}), ...(entry.reasoningWireValue !== undefined ? { reasoningWireValue: entry.reasoningWireValue } : {}), + ...(entry.callerServiceTier ? { callerServiceTier: entry.callerServiceTier } : {}), ...(entry.requestedServiceTier ? { requestedServiceTier: entry.requestedServiceTier } : {}), ...(entry.requestedSpeedLabel ? { requestedSpeedLabel: entry.requestedSpeedLabel } : {}), ...(entry.configuredServiceTier ? { configuredServiceTier: entry.configuredServiceTier } : {}), @@ -360,6 +371,7 @@ export function addRequestLog(entry: RequestLogEntry) { ? { modelSupportsServiceTier: entry.modelSupportsServiceTier } : {}), ...(entry.responseServiceTier ? { responseServiceTier: entry.responseServiceTier } : {}), + ...(entry.tierOutcome ? { tierOutcome: entry.tierOutcome } : {}), status: entry.status, durationMs: entry.durationMs, ...(entry.firstOutputMs !== undefined ? { firstOutputMs: entry.firstOutputMs } : {}), @@ -464,6 +476,35 @@ export function recordAdapterReasoning( } } +/** Attach the serializing adapter's tier observation to the active durable attempt. */ +export function recordAdapterTier( + logCtx: RequestLogContext, + request: AdapterRequest, +): void { + recordAdapterTierMetadata(logCtx, request.tierLog); +} + +/** Attach adapter-owned metadata for transports that expose no AdapterRequest (runTurn). */ +export function recordAdapterTierMetadata( + logCtx: RequestLogContext, + metadata: AdapterTierMetadata | undefined, +): void { + delete logCtx.tierOutcome; + delete logCtx.activeTierMetadata; + const attempt = logCtx.activeAttempt; + if (attempt) delete attempt.tierOutcome; + + try { + const outcome = metadata?.outcome; + if (!metadata || !outcome) return; + logCtx.tierOutcome = outcome; + logCtx.activeTierMetadata = metadata; + if (attempt) attempt.tierOutcome = outcome; + } catch { + // Request logging is best-effort and must not affect request delivery. + } +} + export function requestLogErrorCode( status: number, upstreamError?: string, @@ -552,7 +593,12 @@ export function applyResponseLogMetadata(logCtx: RequestLogContext, payload: unk && model.trim() ) logCtx.resolvedModel = model; const serviceTier = (source as { service_tier?: unknown }).service_tier; - if (typeof serviceTier === "string" && serviceTier.trim()) logCtx.responseServiceTier = serviceTier; + if (typeof serviceTier === "string" && serviceTier.trim()) { + logCtx.responseServiceTier = serviceTier; + logCtx.activeTierMetadata?.observeResponseServiceTier(serviceTier); + } else if (Object.prototype.hasOwnProperty.call(source, "service_tier")) { + logCtx.activeTierMetadata?.observeResponseServiceTier(serviceTier); + } const usage = usageFromResponsesPayload((source as { usage?: unknown }).usage); if (usage && !logCtx.usageFromBridge) { logCtx.usage = usage; @@ -618,6 +664,7 @@ export function inspectResponseLogJson(logCtx: RequestLogContext, text: string): try { applyResponseLogMetadata(logCtx, JSON.parse(text)); } catch { + logCtx.activeTierMetadata?.markResponseUnparseable(); /* body may not be JSON; request log metadata is best-effort only */ } captureUpstreamError(logCtx, text); @@ -648,6 +695,7 @@ export function inspectResponseLogSsePayloadParsed( const debugEnabled = isUsageDebugEnabled(); const sseAlreadyMarked = logCtx.usageDebugBodyKind === "sse"; if (parsed !== undefined) applyResponseLogMetadata(logCtx, parsed); + else logCtx.activeTierMetadata?.markResponseUnparseable(); captureUpstreamErrorParsed(logCtx, payload, parsed); if (debugEnabled) { if (!sseAlreadyMarked) { @@ -849,6 +897,7 @@ export function addFinalRequestLog( ...attempt, recoveryKinds: [...attempt.recoveryKinds], ...(attempt.usage ? { usage: { ...attempt.usage } } : {}), + ...(attempt.tierOutcome ? { tierOutcome: { ...attempt.tierOutcome } } : {}), })); const isCombo = logCtx.comboId !== undefined && (attempts?.length ?? 0) > 0; const aggregate = isCombo ? aggregateAttemptUsage(attempts ?? []) : null; @@ -873,12 +922,16 @@ export function addFinalRequestLog( ...(logCtx.effectiveEffort ? { effectiveEffort: logCtx.effectiveEffort } : {}), ...(logCtx.reasoningWireField ? { reasoningWireField: logCtx.reasoningWireField } : {}), ...(logCtx.reasoningWireValue !== undefined ? { reasoningWireValue: logCtx.reasoningWireValue } : {}), + ...(logCtx.callerServiceTier ? { callerServiceTier: logCtx.callerServiceTier } : {}), ...(logCtx.requestedServiceTier ? { requestedServiceTier: logCtx.requestedServiceTier } : {}), ...(logCtx.requestedSpeedLabel ? { requestedSpeedLabel: logCtx.requestedSpeedLabel } : {}), ...(logCtx.configuredServiceTier ? { configuredServiceTier: logCtx.configuredServiceTier } : {}), ...(logCtx.configuredSpeedLabel ? { configuredSpeedLabel: logCtx.configuredSpeedLabel } : {}), ...(logCtx.modelSupportsServiceTier !== undefined ? { modelSupportsServiceTier: logCtx.modelSupportsServiceTier } : {}), ...(logCtx.responseServiceTier ? { responseServiceTier: logCtx.responseServiceTier } : {}), + ...((attempts?.at(-1)?.tierOutcome ?? logCtx.tierOutcome) + ? { tierOutcome: attempts?.at(-1)?.tierOutcome ?? { ...logCtx.tierOutcome! } } + : {}), ...(logCtx.resolvedModel ? { resolvedModel: logCtx.resolvedModel } : {}), status: effectiveStatus, durationMs: Date.now() - start, diff --git a/src/server/responses/core.ts b/src/server/responses/core.ts index a5ad0191fc..3a39cb6328 100644 --- a/src/server/responses/core.ts +++ b/src/server/responses/core.ts @@ -123,7 +123,18 @@ import { createTranslatorBudget, isTranslatorBudgetExceededError, type Translato import { listOpenAiForwardSidecarCandidates, resolveFirstUsableOpenAiSidecar, type ResolvedOpenAiForwardSidecar } from "../../providers/openai-sidecar"; import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../../providers/openai-tiers"; import { providerContextCap } from "../../providers/context-cap"; -import { SERVICE_TIER_ADAPTERS, serviceTierSupportForModel } from "../../providers/service-tier"; +import { + fastPolicyForModel, + serviceTierSupportFromPolicy, + SERVICE_TIER_ADAPTERS, +} from "../../providers/service-tier"; +import { + canonicalFastTierMarker, + decideTier, + tierObservationContext, + tierValueAfterDecision, + type ResolvedFastPolicy, +} from "../../providers/fastwire"; import { RequestPacingQueueOverloadError, waitForProviderRequestSlot, @@ -149,7 +160,7 @@ import { shouldAttemptImageTierRetry } from "../image-retry"; import { resolveProviderTransport } from "../../providers/xai-transport"; import type { WsData } from "../ws-bridge"; import { codexAccountSelectionForTurn, registerTurn, trackStreamLifetime, unregisterTurn } from "../lifecycle"; -import { redactSecretString } from "../../lib/redact"; +import { redactSecretString, sanitizeLogMetadataString } from "../../lib/redact"; import { readBoundedResponseBody } from "../../lib/bounded-body"; import type { AdmissionLease } from "../../lib/admission"; import { supportedLadderFor } from "../effort-policy"; @@ -167,6 +178,8 @@ import { noteAttemptSend, readConfiguredCodexServiceTier, recordAdapterReasoning, + recordAdapterTier, + recordAdapterTierMetadata, recordAttemptRequestedEffort, requestLogSpeedLabel, sealRequestAttemptIdentity, @@ -600,6 +613,7 @@ async function retryCodexPoolOnAlternateAccount( translatorBudget: options.translatorBudget, }); recordAdapterReasoning(logCtx, request); + recordAdapterTier(logCtx, request); await firstResponse.body?.cancel().catch(() => undefined); options.onCodexAuthContextResolved?.(retryAuthCtx); @@ -969,6 +983,23 @@ const UNREADABLE_ENCRYPTED_AGENT_TASK_MESSAGE = const MAX_UPSTREAM_JSON_BODY_BYTES = 32 * 1024 * 1024; const UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS = 180_000; const UPSTREAM_JSON_BODY_INACTIVITY_TIMEOUT_MS = 30_000; +const MAX_FAST_WIRE_CAPABILITY_WARNINGS = 256; +const warnedFastWireCapabilityGaps = new Set(); + +function warnFastWireCapabilityGap(providerName: string, modelId: string): void { + const safeProvider = sanitizeLogMetadataString(providerName) ?? "unknown"; + const safeModel = sanitizeLogMetadataString(modelId) ?? "unknown"; + const key = `${safeProvider}\0${safeModel}`; + if (warnedFastWireCapabilityGaps.has(key)) return; + if (warnedFastWireCapabilityGaps.size >= MAX_FAST_WIRE_CAPABILITY_WARNINGS) { + const oldest = warnedFastWireCapabilityGaps.values().next().value; + if (oldest !== undefined) warnedFastWireCapabilityGaps.delete(oldest); + } + warnedFastWireCapabilityGaps.add(key); + console.warn( + `[opencodex] Fast policy for ${safeProvider}/${safeModel} has service-tier capability but no Fast wire; preserving only caller-permitted tier behavior`, + ); +} export const UPSTREAM_JSON_BODY_READ_OPTIONS = { maxBytes: MAX_UPSTREAM_JSON_BODY_BYTES, totalTimeoutMs: UPSTREAM_JSON_BODY_TOTAL_TIMEOUT_MS, @@ -1154,23 +1185,21 @@ async function applyFinalRouteRequestNormalization(args: { logCtx.preserveResolvedModelFromRoute = true; } - // Fast mode override only where the final provider/model route explicitly documents - // service-tier support. The same model-scoped resolver is used by catalog generation. - const modelServiceTierSupport = serviceTierSupportForModel( + // Resolve Fast policy after the final route/wire settles. A1 records the decision on parsed + // options; the Responses adapter owns the final outbound body write. + const fastPolicy = fastPolicyForModel( route.provider, route.modelId, route.providerName, inboundWire, ); - if (config.fastMode !== undefined - && SERVICE_TIER_ADAPTERS.has(route.provider.adapter) - && modelServiceTierSupport === true) { - const tier = config.fastMode ? "priority" : undefined; - if (parsed._rawBody && typeof parsed._rawBody === "object") { - if (tier) (parsed._rawBody as Record).service_tier = tier; - else delete (parsed._rawBody as Record).service_tier; - } - parsed.options.serviceTier = tier; + const modelServiceTierSupport = serviceTierSupportFromPolicy(fastPolicy); + const callerTier = parsed.options.serviceTier; + parsed.options.tierObservation = tierObservationContext(fastPolicy, config.fastMode, callerTier); + parsed.options.tierDecision = decideTier(fastPolicy, config.fastMode, callerTier); + parsed.options.serviceTier = tierValueAfterDecision(parsed.options.tierDecision, callerTier); + if (fastPolicy.capability === true && fastPolicy.fastWire === null) { + warnFastWireCapabilityGap(route.providerName, route.modelId); } applyServiceTierGate( route.provider, @@ -1179,6 +1208,7 @@ async function applyFinalRouteRequestNormalization(args: { route.modelId, route.providerName, inboundWire, + fastPolicy, ); if (modelServiceTierSupport === false) { logCtx.requestedServiceTier = undefined; @@ -1579,16 +1609,28 @@ export function applyServiceTierGate( modelId?: string, providerName?: string, inbound: InboundWire = "responses", + resolvedPolicy?: ResolvedFastPolicy, ): void { // A direct unit caller without a model id retains the historical tri-state behavior for // adapters outside the OpenAI service-tier family. Once a model is known, resolve the final // model adapter as well: an explicit override to Anthropic (or another non-OpenAI wire) must // not carry a caller-supplied `service_tier` through a route that cannot forward it. if (modelId === undefined && !SERVICE_TIER_ADAPTERS.has(provider.adapter)) return; - const support = modelId === undefined - ? provider.supportsServiceTier - : serviceTierSupportForModel(provider, modelId, providerName, inbound); - if (support !== false) return; + const policy = modelId === undefined + ? undefined + : resolvedPolicy ?? fastPolicyForModel(provider, modelId, providerName, inbound); + const forwardCallerTier = modelId === undefined + ? provider.supportsServiceTier !== false + : policy!.forwardCallerTier; + const rawTier = rawBody && typeof rawBody === "object" + ? (rawBody as Record).service_tier + : undefined; + const dropForeignCallerTier = policy?.capability === true + && policy.fastWire?.kind === "service-tier" + && policy.fastWire?.foreignCallerTiers === "drop" + && typeof rawTier === "string" + && canonicalFastTierMarker(rawTier) === undefined; + if (forwardCallerTier && !dropForeignCallerTier) return; if (rawBody && typeof rawBody === "object") { delete (rawBody as Record).service_tier; } @@ -1724,6 +1766,7 @@ async function handleResponsesInner( } logCtx.requestedModel = parsed.modelId; logCtx.requestedEffort = parsed.options.reasoning; + logCtx.callerServiceTier = sanitizeLogMetadataString(parsed.options.serviceTier); logCtx.requestedServiceTier = parsed.options.serviceTier; logCtx.requestedSpeedLabel = requestLogSpeedLabel(parsed.options.serviceTier); logCtx.configuredServiceTier = readConfiguredCodexServiceTier(); @@ -2166,6 +2209,9 @@ async function handleResponsesInner( (logCtx.attempts ??= []).push(attempt); } sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, adapter.name, logCtx.accountLogLabel); + if (adapter.runTurn) { + recordAdapterTierMetadata(logCtx, adapter.tierLogForRunTurn?.(parsed)); + } // Optional route-identity linkage for attempt correlation (CL-09 consumes it). The slot // resolves to null unless an opt-in subsystem registered a linker, so an install without // routing profiles does no work here and loads no additional module. The non-throwing @@ -2400,6 +2446,7 @@ async function handleResponsesInner( } : undefined; recordAdapterReasoning(logCtx, request); + recordAdapterTier(logCtx, request); const actualHostKey = upstreamHostHealthKey( route.providerName, safeOriginLabel(request.url), @@ -3196,7 +3243,10 @@ async function handleResponsesInner( stallTimeoutSec: config.stallTimeoutSec, waitForRequestSlot: imageProviderFetch.waitForPacing, fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch, - onRequestBuilt: request => recordAdapterReasoning(logCtx, request), + onRequestBuilt: request => { + recordAdapterReasoning(logCtx, request); + recordAdapterTier(logCtx, request); + }, ...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}), onUsage: usage => { // Cursor may assign _cursorConversationId inside the image loop's first runTurn; @@ -3274,7 +3324,10 @@ async function handleResponsesInner( forceEmptyResponseId: true, abortSignal: options.abortSignal, ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}), - onRequestBuilt: request => recordAdapterReasoning(logCtx, request), + onRequestBuilt: request => { + recordAdapterReasoning(logCtx, request); + recordAdapterTier(logCtx, request); + }, onAttemptSend: (recovery?: AttemptRecoveryKind) => noteAttemptSend(logCtx.activeAttempt, logCtx.usageLogInputTokens, recovery), onUsage: usage => { @@ -3544,6 +3597,7 @@ async function handleResponsesInner( try { initialRequest = await activeAdapter.buildRequest(parsed, { headers: selectedForwardHeaders, translatorBudget }); recordAdapterReasoning(logCtx, initialRequest); + recordAdapterTier(logCtx, initialRequest); inputTokenEstimate = typeof initialRequest.usageLog?.inputTokens === "number" ? initialRequest.usageLog.inputTokens : undefined; @@ -3648,6 +3702,7 @@ async function handleResponsesInner( ...(imageTierBias > 0 ? { imageTierBias } : {}), }); recordAdapterReasoning(logCtx, retryRequest); + recordAdapterTier(logCtx, retryRequest); } catch (err) { // A rotated/rebuilt adapter build failure is a request-shaping error, not an // upstream connect failure: tear the abort link down and map it as 400 (no 413 @@ -3970,6 +4025,7 @@ async function handleResponsesInner( ...(imageTierBias > 0 ? { imageTierBias } : {}), }); recordAdapterReasoning(logCtx, continuationRequest); + recordAdapterTier(logCtx, continuationRequest); } catch (err) { // The main body is already streaming, so there is no HTTP error surface: release // any partial body observation and surface the failure as an in-stream error via diff --git a/src/types.ts b/src/types.ts index 8ae883d23d..85d29e6c95 100644 --- a/src/types.ts +++ b/src/types.ts @@ -301,6 +301,10 @@ export interface OcxRequestOptions { reasoning?: string; hideThinkingSummary?: boolean; serviceTier?: string; + /** Final outbound tier action, resolved after the provider/model wire is settled. */ + tierDecision?: TierDecision; + /** Internal B0 observation inputs; adapters combine these with the wire they actually serialize. */ + tierObservation?: TierObservationContext; presencePenalty?: number; frequencyPenalty?: number; /** Responses prompt-cache affinity key. Passthrough preserves it via _rawBody; routed adapters do not consume it unless their upstream wire supports it. */ @@ -1315,6 +1319,51 @@ export interface ProviderRequestPacingConfig extends RequestPacingRule { models?: Record; } +export interface FastWire { + kind: "service-tier" | "anthropic-speed"; + /** Canonical tier name to upstream wire spelling. */ + canonicalToWire: Readonly>; + /** Policy for non-canonical caller-provided tier values. */ + foreignCallerTiers: "verbatim" | "drop"; + /** Anthropic speed headers/betas reserved for the later wire implementation. */ + betas?: readonly string[]; +} + +/** Durable per-attempt service-tier fact produced at the adapter serialization boundary. */ +export interface AttemptTierOutcome { + canonical?: "priority"; + wireKind?: FastWire["kind"] | null; + wireValue?: string | null; + fastOutcome: "not-requested" | "applied" | "downgraded" | "unknown"; + fastDowngradeReason?: "route-unsupported" | "wire-unavailable" | "response-declined"; + callerTierDropped?: boolean; + callerFastSuppressedByConfig?: boolean; + confirmation: "confirmed" | "assumed" | "downgraded" | "unknown"; + responseServiceTier?: string; +} + +/** + * Request-local observation inputs captured before the final tier action mutates the parsed view. + * This is not persisted; the final adapter turns it into AttemptTierOutcome after serialization. + */ +export interface TierObservationContext { + capability: boolean | undefined; + eligibility: + | "eligible" + | "capability-unsupported" + | "unclassified" + | "wire-unavailable" + | "pin-unavailable"; + fastWire: FastWire | null; + demandDecision: "force-fast" | "force-default" | "inherit"; + callerTier?: string; +} + +export type TierDecision = + | { readonly kind: "forward-caller" } + | { readonly kind: "drop" } + | { readonly kind: "set"; readonly value: string }; + /** * One configured provider entry. `authMode` (default `"key"`) decides whether same-target 429 * retries are allowed; OAuth/forward credentials and local runtimes are never replayed. @@ -1338,6 +1387,11 @@ export interface OcxProviderConfig { * as before. */ modelAdapters?: Record; + /** + * Fast-wire declaration. `null` explicitly disables adapter-derived defaults; + * absence derives from the final model adapter. + */ + fastWire?: FastWire | null; baseUrl: string; /** * Optional relative resource path for key-auth openai-responses requests. Must start with `/` @@ -1750,6 +1804,19 @@ const ANTHROPIC_WIRE_MODELS: Record> = { "opencode-go": new Set(["minimax-m2.5", "minimax-m2.7", "minimax-m3"]), }; +function anthropicWireModelsForProvider(providerName: string): ReadonlySet | undefined { + return Object.hasOwn(ANTHROPIC_WIRE_MODELS, providerName) + ? ANTHROPIC_WIRE_MODELS[providerName] + : undefined; +} + +/** Detached provider-local hard-pin table for pure wire-policy resolution. */ +export function captureWireAdapterHardPins(providerName: string): Readonly> { + const models = anthropicWireModelsForProvider(providerName); + if (!models) return Object.freeze({}); + return Object.freeze(Object.fromEntries([...models].map(modelId => [modelId, "anthropic"]))); +} + /** * True when the upstream speaks exactly one wire for this model, so a configured * override must not apply. @@ -1759,7 +1826,7 @@ const ANTHROPIC_WIRE_MODELS: Record> = { * adapter" would pass on the first pass and then let the override win on the second. */ export function isWirePinnedModel(providerName: string, modelId: string): boolean { - return ANTHROPIC_WIRE_MODELS[providerName]?.has(modelId) ?? false; + return anthropicWireModelsForProvider(providerName)?.has(modelId) ?? false; } /** The wire a pinned model must use, or undefined when the model is not pinned. */ diff --git a/src/usage/cost.ts b/src/usage/cost.ts index 615419f324..5cf798e144 100644 --- a/src/usage/cost.ts +++ b/src/usage/cost.ts @@ -14,7 +14,7 @@ import { getModelMetadata, resolveMetadataProvider, } from "../generated/model-metadata"; -import type { OcxUsage } from "../types"; +import type { AttemptTierOutcome, OcxUsage } from "../types"; import { baseProviderLabel, canonicalUsageProviderLabel } from "../providers/label"; import type { PersistedUsageAttempt, UsageStatus } from "./log"; import { canonicalAntigravityUsageModel } from "../providers/antigravity-models"; @@ -44,6 +44,7 @@ export interface ServiceTierContext { responseServiceTier?: string; requestedServiceTier?: string; configuredServiceTier?: string; + tierOutcome?: AttemptTierOutcome; } export interface CostTokens { @@ -349,6 +350,7 @@ export type ServiceTierInput = string | ServiceTierContext; * and long-context exclusivity depends on that distinction. */ export function serviceTierContext(entry: ServiceTierContext): ServiceTierContext { + if (entry.tierOutcome) return serviceTierContextFromOutcome(entry.tierOutcome); return { responseServiceTier: entry.responseServiceTier, requestedServiceTier: entry.requestedServiceTier, @@ -356,6 +358,30 @@ export function serviceTierContext(entry: ServiceTierContext): ServiceTierContex }; } +/** Convert one adapter-observed attempt outcome into the existing pricing provenance shape. */ +export function serviceTierContextFromOutcome(outcome: AttemptTierOutcome): ServiceTierContext { + if (outcome.canonical === "priority" && outcome.confirmation === "confirmed") { + return { responseServiceTier: "priority" }; + } + if (outcome.responseServiceTier !== undefined) { + return { responseServiceTier: outcome.responseServiceTier }; + } + if (outcome.canonical === "priority" && outcome.confirmation === "assumed") { + return { requestedServiceTier: "priority" }; + } + // An unclassified route makes no canonical Fast claim, but its adapter can still prove that + // it serialized a caller tier. Preserve that wire evidence instead of discarding the legacy + // top-level pricing signal merely because B0 added an outcome row. + if ( + outcome.fastOutcome === "unknown" + && outcome.wireKind === "service-tier" + && typeof outcome.wireValue === "string" + ) { + return { requestedServiceTier: outcome.wireValue }; + } + return {}; +} + function tierScalar(tier?: ServiceTierInput): string | undefined { return typeof tier === "string" ? tier : tier && effectiveServiceTier(tier); } @@ -428,7 +454,7 @@ function applyPriorityMultiplier( * missing so combos can fail closed. */ export function estimateAttemptCost( - attempt: Pick, + attempt: Pick, overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS, serviceTier?: ServiceTierInput, userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(), @@ -438,15 +464,18 @@ export function estimateAttemptCost( if (!tokens) return null; const price = resolveMatchedPrice(attempt.provider, attempt.model, overlays, userOverlays); if (!price) return null; + const attemptServiceTier = attempt.tierOutcome + ? serviceTierContextFromOutcome(attempt.tierOutcome) + : serviceTier; const [tieredCost4, contextTier] = applyContextTier( - price.cost4, attempt.provider, attempt.model, attempt.usage.inputTokens, serviceTier, + price.cost4, attempt.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier, ); // Exclusive both ways: if the long rate applied, the request was NOT served as // Fast (Fast does not support long context), so the Fast multiplier must not // also apply — otherwise a downgraded request bills at both rates. const [effectiveCost4, multiplier] = contextTier ? [tieredCost4, 1] as const - : applyPriorityMultiplier(tieredCost4, attempt.provider, attempt.model, serviceTier); + : applyPriorityMultiplier(tieredCost4, attempt.provider, attempt.model, attemptServiceTier); return { ordinal: attempt.ordinal, provider: attempt.provider, @@ -465,7 +494,7 @@ export function estimateAttemptCost( * attempt is unpriced or unnormalizable, return null rather than a partial sum. */ export function estimateComboCost( - attempts: readonly Pick[], + attempts: readonly Pick[], overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS, serviceTier?: ServiceTierInput, userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(), diff --git a/src/usage/log.ts b/src/usage/log.ts index 08cac2d408..4e9e01ad3f 100644 --- a/src/usage/log.ts +++ b/src/usage/log.ts @@ -4,8 +4,9 @@ import { join } from "node:path"; import { getConfigDir } from "../config"; import { enforceAppOwnedMemoryBudget } from "../lib/app-owned-memory"; import { recordOwnedConfigPath } from "../lib/config-ownership"; +import { sanitizeLogMetadataString } from "../lib/redact"; import { usageDisplayTotalTokens } from "./totals"; -import type { OcxUsage } from "../types"; +import type { AttemptTierOutcome, OcxUsage } from "../types"; import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace"; import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label"; @@ -61,6 +62,8 @@ export interface PersistedUsageAttempt { effectiveEffort?: string; reasoningWireField?: string; reasoningWireValue?: string | number | boolean; + /** Adapter-produced tier fact for this physical attempt; absent on pre-B0 rows. */ + tierOutcome?: AttemptTierOutcome; } export interface PersistedUsageEntry { @@ -87,12 +90,16 @@ export interface PersistedUsageEntry { effectiveEffort?: string; reasoningWireField?: string; reasoningWireValue?: string | number | boolean; + /** Raw caller tier captured before routing, sanitized and bounded for durable logs. */ + callerServiceTier?: string; requestedServiceTier?: string; requestedSpeedLabel?: string; configuredServiceTier?: string; configuredSpeedLabel?: string; modelSupportsServiceTier?: boolean; responseServiceTier?: string; + /** Summary of the final physical attempt for dashboard consumers. */ + tierOutcome?: AttemptTierOutcome; status: number; durationMs: number; /** TTFT relative to the request start (WP4); unset for non-streaming/tool-only. */ @@ -218,6 +225,15 @@ const USAGE_STATUSES = new Set([ "estimated", ]); const LAB_ROUTE_SUBJECT_ID_RE = /^[0-9a-f]{64}$/; +const FAST_OUTCOMES = new Set([ + "not-requested", "applied", "downgraded", "unknown", +]); +const TIER_CONFIRMATIONS = new Set([ + "confirmed", "assumed", "downgraded", "unknown", +]); +const FAST_DOWNGRADE_REASONS = new Set>([ + "route-unsupported", "wire-unavailable", "response-declined", +]); export function isLabRouteSubjectId(value: unknown): value is string { return typeof value === "string" && LAB_ROUTE_SUBJECT_ID_RE.test(value); @@ -246,6 +262,53 @@ function normalizeAttemptUsage(raw: unknown): OcxUsage | null { return normalizeUsageValue(usage as unknown as OcxUsage) ?? null; } +function normalizeAttemptTierOutcome(raw: unknown): AttemptTierOutcome | null { + if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; + const outcome = raw as Record; + if (typeof outcome.fastOutcome !== "string" + || !FAST_OUTCOMES.has(outcome.fastOutcome as AttemptTierOutcome["fastOutcome"]) + || typeof outcome.confirmation !== "string" + || !TIER_CONFIRMATIONS.has(outcome.confirmation as AttemptTierOutcome["confirmation"])) { + return null; + } + if ("canonical" in outcome && outcome.canonical !== "priority") return null; + if ("wireKind" in outcome + && outcome.wireKind !== null + && outcome.wireKind !== "service-tier" + && outcome.wireKind !== "anthropic-speed") return null; + if ("wireValue" in outcome && outcome.wireValue !== null && typeof outcome.wireValue !== "string") return null; + if ("fastDowngradeReason" in outcome + && (typeof outcome.fastDowngradeReason !== "string" + || !FAST_DOWNGRADE_REASONS.has(outcome.fastDowngradeReason as NonNullable))) { + return null; + } + if ("callerTierDropped" in outcome && typeof outcome.callerTierDropped !== "boolean") return null; + if ("callerFastSuppressedByConfig" in outcome + && typeof outcome.callerFastSuppressedByConfig !== "boolean") return null; + if ("responseServiceTier" in outcome && typeof outcome.responseServiceTier !== "string") return null; + return { + ...(outcome.canonical === "priority" ? { canonical: "priority" as const } : {}), + ...(outcome.wireKind === null || outcome.wireKind === "service-tier" || outcome.wireKind === "anthropic-speed" + ? { wireKind: outcome.wireKind } + : {}), + ...(outcome.wireValue === null + ? { wireValue: null } + : typeof outcome.wireValue === "string" ? { wireValue: capMetadataString(outcome.wireValue) } : {}), + fastOutcome: outcome.fastOutcome as AttemptTierOutcome["fastOutcome"], + ...(typeof outcome.fastDowngradeReason === "string" + ? { fastDowngradeReason: outcome.fastDowngradeReason as NonNullable } + : {}), + ...(typeof outcome.callerTierDropped === "boolean" ? { callerTierDropped: outcome.callerTierDropped } : {}), + ...(typeof outcome.callerFastSuppressedByConfig === "boolean" + ? { callerFastSuppressedByConfig: outcome.callerFastSuppressedByConfig } + : {}), + confirmation: outcome.confirmation as AttemptTierOutcome["confirmation"], + ...(typeof outcome.responseServiceTier === "string" + ? { responseServiceTier: capMetadataString(outcome.responseServiceTier) } + : {}), + }; +} + function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null { if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null; const attempt = raw as Record; @@ -272,6 +335,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null { && !isNonNegativeFiniteNumber(attempt.totalTokens)) return null; const usage = "usage" in attempt ? normalizeAttemptUsage(attempt.usage) : undefined; if ("usage" in attempt && usage === null) return null; + const tierOutcome = "tierOutcome" in attempt + ? normalizeAttemptTierOutcome(attempt.tierOutcome) + : undefined; const recoveryKinds = Array.isArray(attempt.recoveryKinds) ? [...new Set(attempt.recoveryKinds.filter( (value): value is AttemptRecoveryKind => typeof value === "string" @@ -321,6 +387,7 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null { ? { reasoningWireValue: capMetadataString(attempt.reasoningWireValue) } : { reasoningWireValue: attempt.reasoningWireValue } : {}), + ...(tierOutcome ? { tierOutcome } : {}), }; } @@ -357,6 +424,8 @@ export function normalizeUsageEntryForTest(entry: PersistedUsageEntry): Persiste function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { const attempts = normalizedAttempts(entry.attempts); + const tierOutcome = entry.tierOutcome ? normalizeAttemptTierOutcome(entry.tierOutcome) : undefined; + const callerServiceTier = sanitizeLogMetadataString(entry.callerServiceTier); const routeDecision = entry.routeDecision ? normalizeRouteDecisionTrace(entry.routeDecision) : undefined; @@ -397,6 +466,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { ? { reasoningWireValue: capMetadataString(entry.reasoningWireValue) } : { reasoningWireValue: entry.reasoningWireValue } : {}), + ...(callerServiceTier ? { callerServiceTier } : {}), ...(typeof entry.requestedServiceTier === "string" && entry.requestedServiceTier ? { requestedServiceTier: capMetadataString(entry.requestedServiceTier) } : {}), @@ -415,6 +485,7 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry { ...(typeof entry.responseServiceTier === "string" && entry.responseServiceTier ? { responseServiceTier: capMetadataString(entry.responseServiceTier) } : {}), + ...(tierOutcome ? { tierOutcome } : {}), status: entry.status, durationMs: entry.durationMs, ...(isNonNegativeFiniteNumber(entry.firstOutputMs) diff --git a/tests/config.test.ts b/tests/config.test.ts index 657953b771..f05f107a8e 100644 --- a/tests/config.test.ts +++ b/tests/config.test.ts @@ -381,6 +381,44 @@ describe("opencodex config defaults", () => { expect(backupNames()).toEqual([]); }); + test("an inherited FastWire conflict warns without wiping persisted providers or keys", () => { + writeConfig({ + port: 12345, + defaultProvider: "openai-apikey", + providers: { + "openai-apikey": { + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + authMode: "key", + fastWire: null, + }, + }, + apiKeys: [{ id: "key-1", name: "default", key: "ocx_persisted", createdAt: "2026-07-28T00:00:00.000Z" }], + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + + try { + const loaded = loadConfig(); + const diagnostics = readConfigDiagnostics(); + + expect(loaded).toMatchObject({ + port: 12345, + defaultProvider: "openai-apikey", + providers: { "openai-apikey": { fastWire: null } }, + apiKeys: [expect.objectContaining({ id: "key-1", key: "ocx_persisted" })], + }); + expect(diagnostics).toMatchObject({ + source: "file", + error: null, + warnings: [expect.stringContaining("fastWire=null overrides service-tier capability")], + }); + expect(backupNames()).toEqual([]); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("persisted providers and API keys were preserved")); + } finally { + warnSpy.mockRestore(); + } + }); + test("a non-string experimentalRealtimeWsBaseUrl degrades to unset without wiping config", () => { // The sideband builder calls overrideBaseUrl?.trim(); a boolean here would crash // it, so the schema degrades the field instead of rejecting the whole config. diff --git a/tests/fastwire-characterization-wire.test.ts b/tests/fastwire-characterization-wire.test.ts index f8291ab47a..d39b2e39bc 100644 --- a/tests/fastwire-characterization-wire.test.ts +++ b/tests/fastwire-characterization-wire.test.ts @@ -14,11 +14,12 @@ afterEach(() => { async function driveResponses(args: { provider: OcxProviderConfig; + providerName?: string; model?: string; callerTier?: string; fastMode?: boolean; }): Promise<{ outboundBody: Record; logCtx: RequestLogContext }> { - const providerName = "fastwire-fixture"; + const providerName = args.providerName ?? "fastwire-fixture"; const model = args.model ?? "model"; const bodies: Record[] = []; globalThis.fetch = (async (_input: RequestInfo | URL, init?: RequestInit) => { @@ -99,6 +100,56 @@ describe("FastWire characterization: supported-route fastMode tri-state", () => }); expect(outboundBody.service_tier).toBe("flex"); }); + + test("a capability-without-wire warning is redacted and throttled per provider/model", async () => { + const providerName = `sk-ant-api03-${"A".repeat(40)}`; + const model = `model\n${"x".repeat(100)}`; + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + const provider: OcxProviderConfig = { + ...supportedResponsesProvider(), + fastWire: null, + }; + + try { + await driveResponses({ provider, providerName, model, callerTier: "flex" }); + await driveResponses({ provider, providerName, model, callerTier: "flex" }); + const fastWireWarnings = warnSpy.mock.calls + .map(call => String(call[0])) + .filter(message => message.includes("Fast policy")); + expect(fastWireWarnings).toHaveLength(1); + expect(fastWireWarnings[0]).not.toContain(providerName); + expect(fastWireWarnings[0]).not.toContain("\n"); + expect(fastWireWarnings[0]).not.toContain("x".repeat(65)); + } finally { + warnSpy.mockRestore(); + } + }); +}); + +describe("FastWire characterization: resolved model adapter controls fast override", () => { + test.each([ + { fastMode: true, expectedTier: "priority" }, + { fastMode: false, expectedTier: undefined }, + ])( + "anthropic provider overridden to openai-chat emits $expectedTier with fastMode=$fastMode", + async ({ fastMode, expectedTier }) => { + const { outboundBody } = await driveResponses({ + provider: { + adapter: "anthropic", + baseUrl: "https://mixed-wire.example.test/v1", + authMode: "key", + apiKey: "sk-test", + modelAdapters: { model: "openai-chat" }, + supportsServiceTier: true, + chatServiceTier: true, + }, + callerTier: "flex", + fastMode, + }); + if (expectedTier === undefined) expect(outboundBody).not.toHaveProperty("service_tier"); + else expect(outboundBody.service_tier).toBe(expectedTier); + }, + ); }); describe("FastWire characterization: unclassified support matrix", () => { @@ -163,18 +214,28 @@ describe("FastWire characterization: requestedServiceTier timing", () => { }); describe("FastWire characterization: rawBody observation point", () => { - test("fastMode injection is visible in parsed._rawBody when the adapter is invoked", async () => { + test("Responses writes the decision outbound without changing parsed._rawBody", async () => { let adapterRawBody: Record | undefined; - const adapterSpy = spyOn(adapterResolveModule, "resolveAdapter").mockReturnValue({ - name: "openai-responses", - passthrough: true, - async buildRequest(parsed) { - adapterRawBody = JSON.parse(JSON.stringify(parsed._rawBody)) as Record; - throw new Error("fastwire rawBody observation complete"); - }, - } as ReturnType); + let outboundBody: Record | undefined; + const actualResolveAdapter = adapterResolveModule.resolveAdapter; + const adapterSpy = spyOn(adapterResolveModule, "resolveAdapter").mockImplementation((provider, cacheRetention) => { + const actualAdapter = actualResolveAdapter(provider, cacheRetention); + return { + ...actualAdapter, + buildRequest(parsed, incoming) { + adapterRawBody = parsed._rawBody as Record; + const request = actualAdapter.buildRequest!(parsed, incoming); + outboundBody = JSON.parse(request.body) as Record; + return request; + }, + }; + }); try { + globalThis.fetch = (async () => new Response("data: [DONE]\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" }, + })) as typeof fetch; const providerName = "fastwire-raw-body"; const config = { port: 0, @@ -193,11 +254,9 @@ describe("FastWire characterization: rawBody observation point", () => { }), }); - await expect(handleResponses(request, config, { model: "", provider: "" }, {})) - .rejects.toThrow("fastwire rawBody observation complete"); - // A1 intentionally moves fast-mode injection out of `_rawBody`; update this - // characterization when that observation point changes. - expect(adapterRawBody?.service_tier).toBe("priority"); + await handleResponses(request, config, { model: "", provider: "" }, {}); + expect(outboundBody?.service_tier).toBe("priority"); + expect(adapterRawBody?.service_tier).toBe("flex"); } finally { adapterSpy.mockRestore(); } diff --git a/tests/fastwire-observability.test.ts b/tests/fastwire-observability.test.ts new file mode 100644 index 0000000000..453a363273 --- /dev/null +++ b/tests/fastwire-observability.test.ts @@ -0,0 +1,528 @@ +import { describe, expect, test } from "bun:test"; +import type { AdapterRequest } from "../src/adapters/base"; +import { createResponsesPassthroughAdapter } from "../src/adapters/openai-responses"; +import { buildBehaviorFingerprintV1 } from "../src/lab/subject/behavior-fingerprint"; +import { sanitizeLogMetadataString } from "../src/lib/redact"; +import { + createAdapterTierMetadata, + type ResolvedFastPolicy, +} from "../src/providers/fastwire"; +import { resolveProductionBehaviorValues } from "../src/routing/compatibility/behavior"; +import { + addFinalRequestLog, + applyResponseLogMetadata, + beginRequestAttempt, + inspectResponseLogJson, + inspectResponseLogSsePayloadParsed, + recordAdapterTier, + type RequestLogContext, + type RequestLogEntry, +} from "../src/server/request-log"; +import { applyServiceTierGate } from "../src/server/responses/core"; +import type { OcxConfig, OcxParsedRequest, TierObservationContext } from "../src/types"; +import { estimateComboCost, serviceTierContextFromOutcome } from "../src/usage/cost"; +import type { ExpectedPriceOverlay } from "../src/usage/expected-prices"; +import { normalizeUsageEntryForTest } from "../src/usage/log"; +import { withTestTranslatorBudget } from "./helpers/translator-budget"; + +const SERVICE_WIRE = { + kind: "service-tier" as const, + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "verbatim" as const, +}; + +function observation( + overrides: Partial = {}, +): TierObservationContext { + return { + capability: true, + eligibility: "eligible", + fastWire: SERVICE_WIRE, + demandDecision: "force-fast", + ...overrides, + }; +} + +describe("FastWire attempt outcomes", () => { + test("force-default suppresses caller Fast without classifying a downgrade", () => { + const tracker = createAdapterTierMetadata( + observation({ demandDecision: "force-default", callerTier: "priority" }), + { kind: "drop" }, + null, + null, + ); + expect(tracker?.outcome).toEqual({ + wireKind: null, + wireValue: null, + fastOutcome: "not-requested", + callerFastSuppressedByConfig: true, + confirmation: "unknown", + }); + }); + + test("unclassified passthrough stays unknown and ignores Fast config", () => { + const tracker = createAdapterTierMetadata( + observation({ + capability: undefined, + eligibility: "unclassified", + demandDecision: "force-default", + callerTier: "priority", + }), + { kind: "forward-caller" }, + "service-tier", + "priority", + ); + expect(tracker?.outcome).toEqual({ + wireKind: "service-tier", + wireValue: "priority", + fastOutcome: "unknown", + confirmation: "unknown", + }); + }); + + test.each([ + { + label: "route unsupported", + context: observation({ capability: false, eligibility: "capability-unsupported" }), + reason: "route-unsupported", + }, + { + label: "wire unavailable", + context: observation({ fastWire: null, eligibility: "wire-unavailable" }), + reason: "wire-unavailable", + }, + ] as const)("Fast demand records $label downgrade", ({ context, reason }) => { + const tracker = createAdapterTierMetadata(context, { kind: "drop" }, null, null); + expect(tracker?.outcome).toEqual({ + wireKind: null, + wireValue: null, + fastOutcome: "downgraded", + fastDowngradeReason: reason, + confirmation: "downgraded", + }); + }); + + test("foreign-tier drop records only callerTierDropped", () => { + const tracker = createAdapterTierMetadata( + observation({ demandDecision: "inherit", callerTier: "flex" }), + { kind: "drop" }, + null, + null, + ); + expect(tracker?.outcome).toEqual({ + wireKind: null, + wireValue: null, + fastOutcome: "not-requested", + callerTierDropped: true, + confirmation: "unknown", + }); + }); + + test("confirmation covers assumed, confirmed, downgraded, and unknown", () => { + const assumed = createAdapterTierMetadata( + observation(), + { kind: "set", value: "priority" }, + "service-tier", + "priority", + )!; + expect(assumed.outcome).toMatchObject({ + canonical: "priority", + fastOutcome: "applied", + confirmation: "assumed", + }); + + const confirmed = createAdapterTierMetadata( + observation({ + fastWire: { ...SERVICE_WIRE, canonicalToWire: { priority: "performance" } }, + }), + { kind: "set", value: "performance" }, + "service-tier", + "performance", + )!; + confirmed.observeResponseServiceTier("performance"); + expect(confirmed.outcome).toMatchObject({ + canonical: "priority", + fastOutcome: "applied", + confirmation: "confirmed", + responseServiceTier: "performance", + }); + expect(serviceTierContextFromOutcome(confirmed.outcome)).toEqual({ + responseServiceTier: "priority", + }); + + const declined = createAdapterTierMetadata( + observation(), + { kind: "set", value: "priority" }, + "service-tier", + "priority", + )!; + declined.observeResponseServiceTier("default"); + expect(declined.outcome).toMatchObject({ + fastOutcome: "downgraded", + fastDowngradeReason: "response-declined", + confirmation: "downgraded", + responseServiceTier: "default", + }); + expect(declined.outcome.canonical).toBeUndefined(); + + const unknown = createAdapterTierMetadata( + observation(), + { kind: "set", value: "priority" }, + "service-tier", + "priority", + )!; + unknown.markResponseUnparseable(); + expect(unknown.outcome).toMatchObject({ fastOutcome: "unknown", confirmation: "unknown" }); + expect(unknown.outcome.canonical).toBeUndefined(); + }); +}); + +describe("FastWire logging and persistence", () => { + test("the serializing adapter returns metadata for the exact emitted tier", () => { + const rawBody = { model: "gpt-5.6-sol", input: "ping", service_tier: "flex" }; + const parsed: OcxParsedRequest = { + modelId: "gpt-5.6-sol", + context: { messages: [] }, + stream: true, + options: { + serviceTier: "priority", + tierDecision: { kind: "set", value: "priority" }, + tierObservation: observation({ callerTier: "flex" }), + }, + _rawBody: rawBody, + }; + const adapter = withTestTranslatorBudget(createResponsesPassthroughAdapter({ + adapter: "openai-responses", + baseUrl: "https://example.test/v1", + authMode: "key", + apiKey: "sk-test", + })); + const request = adapter.buildRequest(parsed) as AdapterRequest; + + expect(JSON.parse(request.body).service_tier).toBe("priority"); + expect(request.tierLog?.outcome).toMatchObject({ + canonical: "priority", + wireKind: "service-tier", + wireValue: "priority", + fastOutcome: "applied", + confirmation: "assumed", + }); + expect(parsed._rawBody).toBe(rawBody); + expect(rawBody.service_tier).toBe("flex"); + }); + + test("adapter metadata lands on its attempt and final-attempt summary", () => { + const tracker = createAdapterTierMetadata( + observation(), + { kind: "set", value: "priority" }, + "service-tier", + "priority", + )!; + const attempt = beginRequestAttempt(1, "openai", "gpt-5.6-sol", "openai-responses"); + const logCtx: RequestLogContext = { + model: "gpt-5.6-sol", + provider: "openai", + activeAttempt: attempt, + activeAttemptStartedAt: Date.now(), + attempts: [attempt], + }; + recordAdapterTier(logCtx, { + url: "https://example.test/v1/responses", + method: "POST", + headers: {}, + body: "{}", + tierLog: tracker, + } satisfies AdapterRequest); + applyResponseLogMetadata(logCtx, { response: { service_tier: "priority" } }); + + let logged: RequestLogEntry | undefined; + addFinalRequestLog("ocx-tier", Date.now(), logCtx, 200, undefined, entry => { + logged = entry; + }); + expect(logged?.attempts?.[0]?.tierOutcome).toMatchObject({ + fastOutcome: "applied", + confirmation: "confirmed", + responseServiceTier: "priority", + }); + expect(logged?.tierOutcome).toEqual(logged?.attempts?.[0]?.tierOutcome); + }); + + test.each([ + { + label: "JSON", + inspect: (logCtx: RequestLogContext) => inspectResponseLogJson(logCtx, "not-json"), + }, + { + label: "SSE", + inspect: (logCtx: RequestLogContext) => { + inspectResponseLogSsePayloadParsed(logCtx, "not-json", undefined); + }, + }, + ])("$label inspection marks an unparseable response outcome unknown", ({ inspect }) => { + const tracker = createAdapterTierMetadata( + observation(), + { kind: "set", value: "priority" }, + "service-tier", + "priority", + )!; + const attempt = beginRequestAttempt(1, "openai", "gpt-5.6-sol", "openai-responses"); + const logCtx: RequestLogContext = { + model: "gpt-5.6-sol", + provider: "openai", + activeAttempt: attempt, + activeAttemptStartedAt: Date.now(), + attempts: [attempt], + }; + recordAdapterTier(logCtx, { + url: "https://example.test/v1/responses", + method: "POST", + headers: {}, + body: "{}", + tierLog: tracker, + } satisfies AdapterRequest); + expect(attempt.tierOutcome).toMatchObject({ + canonical: "priority", + fastOutcome: "applied", + confirmation: "assumed", + }); + + inspect(logCtx); + + expect(attempt.tierOutcome).toMatchObject({ + fastOutcome: "unknown", + confirmation: "unknown", + }); + expect(attempt.tierOutcome).not.toHaveProperty("canonical"); + }); + + test("old attempts remain valid and new outcomes survive normalization", () => { + const oldAttempt = { + ordinal: 1, + provider: "openai", + model: "gpt-5.6-sol", + adapter: "openai-responses", + status: 200, + durationMs: 1, + sendCount: 1, + recoveryKinds: [], + usageStatus: "reported" as const, + usage: { inputTokens: 10, outputTokens: 1 }, + }; + const normalized = normalizeUsageEntryForTest({ + requestId: "ocx-old", + timestamp: 1, + provider: "openai", + model: "gpt-5.6-sol", + status: 200, + durationMs: 1, + usageStatus: "reported", + attempts: [oldAttempt, { + ...oldAttempt, + ordinal: 2, + tierOutcome: { + canonical: "priority", + wireKind: "service-tier", + wireValue: "priority", + fastOutcome: "applied", + confirmation: "assumed", + }, + }], + }); + expect(normalized.attempts?.[0]).not.toHaveProperty("tierOutcome"); + expect(normalized.attempts?.[1]?.tierOutcome).toMatchObject({ + canonical: "priority", + fastOutcome: "applied", + confirmation: "assumed", + }); + }); + + test("callerServiceTier is trimmed, control-filtered, redacted, and capped", () => { + const secret = ["sk", "proj", "abcdefghijklmnopqrstuvwxyz0123456789"].join("-"); + const sanitized = sanitizeLogMetadataString( + ` \u0000authorization: Bearer ${secret}\n\u0085\u2028\u2029${"x".repeat(80)} `, + ); + expect(sanitized).not.toContain(secret); + expect(sanitized).not.toMatch(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/); + expect(sanitized?.length).toBeLessThanOrEqual(64); + + const normalized = normalizeUsageEntryForTest({ + requestId: "ocx-caller-tier", + timestamp: 1, + provider: "openai", + model: "gpt-5.6-sol", + callerServiceTier: ` priority\n${"y".repeat(100)} `, + status: 200, + durationMs: 1, + usageStatus: "unreported", + }); + expect(normalized.callerServiceTier).toBe(`priority${"y".repeat(56)}`); + }); +}); + +describe("FastWire per-attempt cost", () => { + const overlays: ExpectedPriceOverlay[] = [{ + provider: "openai", + modelId: "gpt-5.6-sol", + cost4: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 }, + source: "test", + verifiedAt: "2026-08-17", + status: "verified", + }]; + const usage = { inputTokens: 200_000, outputTokens: 20_000 }; + + test("an unknown unclassified outcome prices from the serialized caller tier", () => { + const outcome = { + wireKind: "service-tier" as const, + wireValue: "priority", + fastOutcome: "unknown" as const, + confirmation: "unknown" as const, + }; + expect(serviceTierContextFromOutcome(outcome)).toEqual({ + requestedServiceTier: "priority", + }); + + const estimate = estimateComboCost([ + { + ordinal: 1, + provider: "openai", + model: "gpt-5.6-sol", + usageStatus: "reported", + usage, + tierOutcome: outcome, + }, + ], overlays, { requestedServiceTier: "priority" })!; + expect(estimate.priorityMultiplier).toBe(2); + expect(estimate.cost.total).toBeCloseTo(3.2, 9); + }); + + test("combo prices each attempt from its own outcome before the top-level tier", () => { + const attempts = [ + { + ordinal: 1, + provider: "openai", + model: "gpt-5.6-sol", + usageStatus: "reported" as const, + usage, + tierOutcome: { + canonical: "priority" as const, + wireKind: "service-tier" as const, + wireValue: "priority", + fastOutcome: "applied" as const, + confirmation: "confirmed" as const, + responseServiceTier: "priority", + }, + }, + { + ordinal: 2, + provider: "openai", + model: "gpt-5.6-sol", + usageStatus: "reported" as const, + usage, + tierOutcome: { + wireKind: "service-tier" as const, + wireValue: "priority", + fastOutcome: "downgraded" as const, + fastDowngradeReason: "response-declined" as const, + confirmation: "downgraded" as const, + responseServiceTier: "default", + }, + }, + ]; + const estimate = estimateComboCost( + attempts, + overlays, + { requestedServiceTier: "priority" }, + )!; + expect(estimate.attempts?.[0]?.cost.total).toBeCloseTo(3.2, 9); + expect(estimate.attempts?.[1]?.cost.total).toBeCloseTo(1.6, 9); + expect(estimate.cost.total).toBeCloseTo(4.8, 9); + expect(estimate.cost.total).not.toBeCloseTo(6.4, 9); + }); + + test("old attempts without outcomes retain the top-level fallback", () => { + const estimate = estimateComboCost([ + { ordinal: 1, provider: "openai", model: "gpt-5.6-sol", usageStatus: "reported", usage }, + { ordinal: 2, provider: "openai", model: "gpt-5.6-sol", usageStatus: "reported", usage }, + ], overlays, { requestedServiceTier: "priority" })!; + expect(estimate.cost.total).toBeCloseTo(6.4, 9); + expect(estimate.attempts?.every(attempt => attempt.priorityMultiplier === 2)).toBe(true); + }); +}); + +describe("FastWire gate and compatibility fingerprint", () => { + test("foreignCallerTiers is value-aware while unclassified passthrough stays unchanged", () => { + const dropPolicy: ResolvedFastPolicy = { + capability: true, + eligibility: "eligible", + adapter: "openai-responses", + fastWire: { ...SERVICE_WIRE, foreignCallerTiers: "drop" }, + forwardCallerTier: true, + }; + const droppedBody: Record = { service_tier: "flex" }; + const droppedOptions = { serviceTier: "flex" }; + applyServiceTierGate( + { adapter: "openai-responses", baseUrl: "https://example.test", supportsServiceTier: true }, + droppedBody, + droppedOptions, + "model", + "fixture", + "responses", + dropPolicy, + ); + expect(droppedBody).not.toHaveProperty("service_tier"); + expect(droppedOptions.serviceTier).toBeUndefined(); + + const unknownPolicy: ResolvedFastPolicy = { ...dropPolicy, capability: undefined, eligibility: "unclassified" }; + const passthroughBody = { service_tier: "flex" }; + const passthroughOptions = { serviceTier: "flex" }; + applyServiceTierGate( + { adapter: "openai-responses", baseUrl: "https://example.test" }, + passthroughBody, + passthroughOptions, + "model", + "fixture", + "responses", + unknownPolicy, + ); + expect(passthroughBody.service_tier).toBe("flex"); + expect(passthroughOptions.serviceTier).toBe("flex"); + }); + + test("Fast wire projections change the digest without changing serviceTier projection", () => { + const config = { + port: 10100, + defaultProvider: "fixture", + providers: { + fixture: { + adapter: "openai-responses", + baseUrl: "https://example.test/v1", + supportsServiceTier: true, + }, + }, + } as OcxConfig; + const base = resolveProductionBehaviorValues( + config, + "fixture", + "model", + config.providers.fixture!, + "salt", + )!; + const performanceProvider = { + ...config.providers.fixture!, + fastWire: { ...SERVICE_WIRE, canonicalToWire: { priority: "performance" } }, + }; + const performance = resolveProductionBehaviorValues( + { ...config, providers: { fixture: performanceProvider } }, + "fixture", + "model", + performanceProvider, + "salt", + )!; + + expect(base["responses.serviceTier"]).toEqual(performance["responses.serviceTier"]); + expect(base["responses.fastWireKind"]?.value).toBe("service-tier"); + expect(base["responses.fastWireValue"]?.value).toBe("priority"); + expect(performance["responses.fastWireValue"]?.value).toBe("performance"); + expect(buildBehaviorFingerprintV1(base)).not.toBe(buildBehaviorFingerprintV1(performance)); + }); +}); diff --git a/tests/fastwire-policy.test.ts b/tests/fastwire-policy.test.ts new file mode 100644 index 0000000000..82aacb0b96 --- /dev/null +++ b/tests/fastwire-policy.test.ts @@ -0,0 +1,588 @@ +import { describe, expect, test } from "bun:test"; + +import { createResponsesPassthroughAdapter } from "../src/adapters/openai-responses"; +import { validateConfigCandidate } from "../src/config"; +import { + canonicalFastTierMarker, + cloneFastWire, + decideTier, + legacyChatEligibility, + resolveFastPolicy, + tierValueAfterDecision, + type FastPolicyAuthority, + type ResolvedFastPolicy, +} from "../src/providers/fastwire"; +import { captureFastPolicyAuthority, fastPolicyForModel } from "../src/providers/service-tier"; +import { PROVIDER_REGISTRY, providerRegistryFastWireError } from "../src/providers/registry"; +import { + captureWireAdapterHardPins, + isWirePinnedModel, + type FastWire, + type OcxConfig, + type OcxParsedRequest, + type TierDecision, +} from "../src/types"; +import { withTestTranslatorBudget } from "./helpers/translator-budget"; + +const MODEL = "model"; +const SERVICE_WIRE: FastWire = { + kind: "service-tier", + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "verbatim", +}; + +type AdapterSource = "hard-pin" | "override" | "registry-default" | "provider-adapter"; +type DeclarationState = "undefined" | "null" | "explicit"; +type CapabilityState = "false" | "undefined" | "true"; + +function authorityForMatrix(args: { + source: AdapterSource; + declaration: DeclarationState; + overrideAllowed: boolean; + capability: CapabilityState; + legacyChatEligible: boolean; +}): FastPolicyAuthority { + const providerAdapter = args.source === "provider-adapter" ? "openai-chat" : "openai-responses"; + return { + providerAdapter, + fastWireDeclaration: args.declaration === "undefined" + ? undefined + : args.declaration === "null" ? null : SERVICE_WIRE, + modelWireOverrideAllowed: args.overrideAllowed, + authTransport: "authorization_bearer", + capability: { + ...(args.capability === "undefined" ? {} : { provider: args.capability === "true" }), + models: {}, + chatServiceTier: args.legacyChatEligible, + }, + modelAdapters: args.source === "hard-pin" || args.source === "override" + ? { [MODEL]: args.source === "override" ? "openai-chat" : "openai-responses" } + : {}, + hardPins: args.source === "hard-pin" ? { [MODEL]: "openai-chat" } : {}, + registryWireDefaults: args.source === "hard-pin" || args.source === "override" + ? { [MODEL]: "openai-responses" } + : args.source === "registry-default" ? { [MODEL]: "openai-chat" } : {}, + }; +} + +const policyMatrix = (["undefined", "null", "explicit"] as const).flatMap(declaration => + ([false, true] as const).flatMap(overrideAllowed => + (["hard-pin", "override", "registry-default", "provider-adapter"] as const).flatMap(source => + (["false", "undefined", "true"] as const).flatMap(capability => + ([false, true] as const).map(legacyChatEligible => ({ + declaration, + overrideAllowed, + source, + capability, + legacyChatEligible, + })), + ), + ), + ), +); + +describe("resolveFastPolicy matrix", () => { + test.each(policyMatrix)( + "$declaration declaration, overrideAllowed=$overrideAllowed, $source, capability=$capability, legacy=$legacyChatEligible", + row => { + const authority = authorityForMatrix(row); + const policy = resolveFastPolicy(authority, MODEL); + const overrideCanWin = row.overrideAllowed && row.source !== "provider-adapter"; + const expectedAdapter = row.source === "hard-pin" + ? "openai-chat" + : overrideCanWin ? "openai-chat" + : row.source === "provider-adapter" ? "openai-chat" : "openai-responses"; + const capability = row.capability === "undefined" ? undefined : row.capability === "true"; + const chatEligible = expectedAdapter !== "openai-chat" || row.legacyChatEligible; + const wireAvailable = row.declaration !== "null"; + const expectedEligibility: ResolvedFastPolicy["eligibility"] = capability === false + ? "capability-unsupported" + : !wireAvailable + ? "wire-unavailable" + : !chatEligible + ? "capability-unsupported" + : capability === undefined ? "unclassified" : "eligible"; + + expect(policy.adapter).toBe(expectedAdapter); + expect(policy.capability).toBe(capability); + expect(policy.eligibility).toBe(expectedEligibility); + expect(policy.fastWire === null ? null : policy.fastWire?.kind).toBe( + row.declaration === "null" ? null : "service-tier", + ); + expect(policy.forwardCallerTier).toBe(capability !== false && chatEligible); + }, + ); + + test("registry defaults retain their inbound constraint", () => { + const authority: FastPolicyAuthority = { + ...authorityForMatrix({ + source: "registry-default", + declaration: "undefined", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + providerAdapter: "openai-responses", + registryWireDefaults: { [MODEL]: { wire: "openai-chat", inbound: ["chat"] } }, + }; + expect(resolveFastPolicy(authority, MODEL, "chat").adapter).toBe("openai-chat"); + expect(resolveFastPolicy(authority, MODEL, "responses").adapter).toBe("openai-responses"); + }); + + test("hard pins and configured overrides retain exact runtime model-key semantics", () => { + const authority: FastPolicyAuthority = { + ...authorityForMatrix({ + source: "provider-adapter", + declaration: "undefined", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + providerAdapter: "openai-responses", + modelAdapters: { Model: "openai-chat" }, + hardPins: { Pinned: "anthropic" }, + }; + expect(resolveFastPolicy(authority, "model").adapter).toBe("openai-responses"); + expect(resolveFastPolicy(authority, "Model").adapter).toBe("openai-chat"); + expect(resolveFastPolicy(authority, "pinned").adapter).toBe("openai-responses"); + expect(resolveFastPolicy(authority, "Pinned").adapter).toBe("anthropic"); + }); + + test("invalid configured overrides fall through to the captured registry default", () => { + const authority: FastPolicyAuthority = { + ...authorityForMatrix({ + source: "registry-default", + declaration: "undefined", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + modelAdapters: { [MODEL]: "anthropic" }, + registryWireDefaults: { [MODEL]: "openai-chat" }, + }; + expect(resolveFastPolicy(authority, MODEL).adapter).toBe("openai-chat"); + }); + + test("registry defaults do not move a provider outside the allowed base-wire family", () => { + const authority: FastPolicyAuthority = { + ...authorityForMatrix({ + source: "registry-default", + declaration: "undefined", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + providerAdapter: "anthropic", + registryWireDefaults: { [MODEL]: "openai-chat" }, + }; + expect(resolveFastPolicy(authority, MODEL).adapter).toBe("anthropic"); + }); + + test("anthropic-speed has no A1 adapter mapping", () => { + const policy = resolveFastPolicy({ + ...authorityForMatrix({ + source: "provider-adapter", + declaration: "explicit", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + fastWireDeclaration: { + kind: "anthropic-speed", + canonicalToWire: { priority: "fast" }, + foreignCallerTiers: "drop", + betas: ["fast-beta"], + }, + }, MODEL); + expect(policy).toMatchObject({ eligibility: "wire-unavailable", forwardCallerTier: false }); + }); + + test("an incompatible hard pin reports pin-unavailable", () => { + const policy = resolveFastPolicy({ + ...authorityForMatrix({ + source: "provider-adapter", + declaration: "explicit", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + hardPins: { [MODEL]: "anthropic" }, + }, MODEL); + expect(policy).toMatchObject({ adapter: "anthropic", eligibility: "pin-unavailable" }); + }); + + test("an explicitly disabled wire reports wire-unavailable even when hard pinned", () => { + const policy = resolveFastPolicy({ + ...authorityForMatrix({ + source: "provider-adapter", + declaration: "null", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }), + hardPins: { [MODEL]: "anthropic" }, + }, MODEL); + expect(policy).toMatchObject({ adapter: "anthropic", eligibility: "wire-unavailable" }); + }); + + test("mutable providers rebuild authority after a capture", () => { + const provider = { + adapter: "openai-responses", + baseUrl: "https://fixture.example/v1", + supportsServiceTier: true, + }; + expect(captureFastPolicyAuthority("fixture", provider, false).capability.provider).toBe(true); + provider.supportsServiceTier = false; + expect(fastPolicyForModel(provider, MODEL, "fixture").capability).toBe(false); + }); + + test("prototype-named providers and models use only own wire-policy rows", () => { + expect(captureWireAdapterHardPins("toString")).toEqual({}); + expect(isWirePinnedModel("toString", MODEL)).toBe(false); + const authority = authorityForMatrix({ + source: "provider-adapter", + declaration: "undefined", + overrideAllowed: true, + capability: "true", + legacyChatEligible: true, + }); + const responsesAuthority = { ...authority, providerAdapter: "openai-responses" }; + expect(resolveFastPolicy(responsesAuthority, "constructor")).toMatchObject({ + adapter: "openai-responses", + eligibility: "eligible", + }); + expect(resolveFastPolicy({ + ...responsesAuthority, + hardPins: Object.fromEntries([["constructor", "anthropic"]]), + }, "constructor")).toMatchObject({ + adapter: "anthropic", + eligibility: "pin-unavailable", + }); + }); + + test("a missing provider name preserves the legacy provider-adapter short circuit", () => { + const provider = { + adapter: "anthropic", + baseUrl: "https://fixture.example/v1", + modelAdapters: { [MODEL]: "openai-responses" }, + supportsServiceTier: true, + } as const; + expect(fastPolicyForModel(provider, MODEL)).toMatchObject({ + adapter: "anthropic", + capability: true, + eligibility: "wire-unavailable", + }); + expect(fastPolicyForModel(provider, MODEL, "fixture")).toMatchObject({ + adapter: "openai-responses", + capability: true, + eligibility: "eligible", + }); + }); +}); + +describe("legacyChatEligibility", () => { + test.each([ + { + label: "chatServiceTier opt-in", + provider: undefined, + models: {}, + chatServiceTier: true, + expected: true, + }, + { + label: "case-insensitive exact-model opt-in", + provider: undefined, + models: { MODEL: true }, + chatServiceTier: false, + expected: true, + }, + { + label: "provider false closes an exact-model opt-in", + provider: false, + models: { model: true }, + chatServiceTier: true, + expected: false, + }, + { + label: "exact false closes a provider Chat opt-in", + provider: true, + models: { model: false }, + chatServiceTier: true, + expected: false, + }, + ])("$label", ({ provider, models, chatServiceTier, expected }) => { + const authority = authorityForMatrix({ + source: "provider-adapter", + declaration: "undefined", + overrideAllowed: true, + capability: "undefined", + legacyChatEligible: false, + }); + expect(legacyChatEligibility({ + ...authority, + capability: { ...(provider === undefined ? {} : { provider }), models, chatServiceTier }, + }, MODEL)).toBe(expected); + }); +}); + +const tierGrid = ([false, undefined, true] as const).flatMap(support => + ([false, undefined, true] as const).flatMap(fastMode => + (["priority", "fast", "flex", undefined] as const).map(callerTier => ({ + support, + fastMode, + callerTier, + })), + ), +); + +function tierPolicy(support: boolean | undefined): ResolvedFastPolicy { + return { + capability: support, + eligibility: support === true ? "eligible" : support === false ? "capability-unsupported" : "unclassified", + adapter: "openai-responses", + fastWire: SERVICE_WIRE, + forwardCallerTier: support !== false, + }; +} + +describe("TierDecision state machine", () => { + test.each(tierGrid)( + "support=$support fastMode=$fastMode caller=$callerTier", + ({ support, fastMode, callerTier }) => { + const decision = decideTier(tierPolicy(support), fastMode, callerTier); + const expectedValue = support === false + ? undefined + : support === undefined ? callerTier + : fastMode === true ? "priority" : fastMode === false ? undefined : callerTier; + const expectedKind: TierDecision["kind"] = support === false || (support === true && fastMode === false) + ? "drop" + : support === true && fastMode === true ? "set" : "forward-caller"; + expect(decision.kind).toBe(expectedKind); + expect(tierValueAfterDecision(decision, callerTier)).toBe(expectedValue); + expect(canonicalFastTierMarker(callerTier)).toBe( + callerTier === "priority" || callerTier === "fast" ? "priority" : undefined, + ); + }, + ); + + test.each(([false, undefined, true] as const).flatMap(fastMode => + (["priority", "fast", "flex", undefined] as const).map(callerTier => ({ fastMode, callerTier })), + ))("true capability plus null wire preserves caller with fastMode=$fastMode caller=$callerTier", ({ fastMode, callerTier }) => { + const decision = decideTier({ + capability: true, + eligibility: "wire-unavailable", + adapter: "openai-responses", + fastWire: null, + forwardCallerTier: true, + }, fastMode, callerTier); + expect(decision).toEqual({ kind: "forward-caller" }); + expect(tierValueAfterDecision(decision, callerTier)).toBe(callerTier); + }); + + test.each(["Priority", "FAST", " fast "])("normalizes %s only into an internal marker", callerTier => { + expect(canonicalFastTierMarker(callerTier)).toBe("priority"); + expect(tierValueAfterDecision({ kind: "forward-caller" }, callerTier)).toBe(callerTier); + }); + + test.each([ + { callerTier: "priority", expected: { kind: "forward-caller" } }, + { callerTier: "fast", expected: { kind: "forward-caller" } }, + { callerTier: "flex", expected: { kind: "drop" } }, + { callerTier: undefined, expected: { kind: "forward-caller" } }, + ])("foreign-tier drop policy resolves caller=$callerTier to $expected.kind", ({ callerTier, expected }) => { + expect(decideTier({ + ...tierPolicy(true), + fastWire: { ...SERVICE_WIRE, foreignCallerTiers: "drop" }, + }, undefined, callerTier)).toEqual(expected); + }); + + test("unclassified capability keeps the full caller passthrough contract", () => { + expect(decideTier({ + ...tierPolicy(undefined), + fastWire: { ...SERVICE_WIRE, foreignCallerTiers: "drop" }, + }, true, "flex")).toEqual({ kind: "forward-caller" }); + }); +}); + +function configWithFastWire(fastWire: unknown, capability?: { provider?: boolean; exact?: boolean }): unknown { + return { + port: 10100, + defaultProvider: "fixture", + providers: { + fixture: { + adapter: "openai-responses", + baseUrl: "https://fixture.example/v1", + fastWire, + ...(capability?.provider === undefined ? {} : { supportsServiceTier: capability.provider }), + ...(capability?.exact === undefined ? {} : { modelSupportsServiceTier: { [MODEL]: capability.exact } }), + }, + }, + }; +} + +describe("FastWire config and registry validation", () => { + test("the shared clone detaches nested FastWire records and arrays", () => { + const canonicalToWire = { priority: "priority" }; + const betas = ["beta-one"]; + const original: FastWire = { + kind: "service-tier", + canonicalToWire, + foreignCallerTiers: "verbatim", + betas, + }; + const cloned = cloneFastWire(original)!; + canonicalToWire.priority = "performance"; + betas[0] = "changed"; + expect(cloned).toEqual({ + kind: "service-tier", + canonicalToWire: { priority: "priority" }, + foreignCallerTiers: "verbatim", + betas: ["beta-one"], + }); + }); + + test("accepts a complete declaration and trims its wire values", () => { + const result = validateConfigCandidate(configWithFastWire({ + kind: "service-tier", + canonicalToWire: { priority: " priority ", flex: " flex " }, + foreignCallerTiers: "verbatim", + betas: [" beta-one "], + })); + expect(result.ok).toBe(true); + if (result.ok) { + expect((result.config as OcxConfig).providers.fixture?.fastWire).toEqual({ + kind: "service-tier", + canonicalToWire: { priority: "priority", flex: "flex" }, + foreignCallerTiers: "verbatim", + betas: ["beta-one"], + }); + } + }); + + test.each([ + { label: "closed kind", value: { kind: "future", canonicalToWire: { priority: "priority" }, foreignCallerTiers: "verbatim" } }, + { label: "missing priority", value: { kind: "service-tier", canonicalToWire: { fast: "fast" }, foreignCallerTiers: "verbatim" } }, + { label: "blank priority", value: { kind: "service-tier", canonicalToWire: { priority: " " }, foreignCallerTiers: "verbatim" } }, + { label: "overlong wire value", value: { kind: "service-tier", canonicalToWire: { priority: "x".repeat(65) }, foreignCallerTiers: "verbatim" } }, + { label: "duplicate wire values", value: { kind: "service-tier", canonicalToWire: { priority: "fast", other: " fast " }, foreignCallerTiers: "verbatim" } }, + { label: "blank beta", value: { kind: "anthropic-speed", canonicalToWire: { priority: "fast" }, foreignCallerTiers: "drop", betas: [" "] } }, + { label: "duplicate betas", value: { kind: "anthropic-speed", canonicalToWire: { priority: "fast" }, foreignCallerTiers: "drop", betas: ["one", " one "] } }, + { label: "too many betas", value: { kind: "anthropic-speed", canonicalToWire: { priority: "fast" }, foreignCallerTiers: "drop", betas: Array.from({ length: 17 }, (_, index) => `b${index}`) } }, + { label: "unknown declaration key", value: { kind: "service-tier", canonicalToWire: { priority: "priority" }, foreignCallerTiers: "verbatim", future: true } }, + ])("rejects $label", ({ value }) => { + expect(validateConfigCandidate(configWithFastWire(value)).ok).toBe(false); + }); + + test.each([ + { label: "provider capability", capability: { provider: true } }, + { label: "exact-model capability", capability: { exact: true } }, + ])("rejects null against $label", ({ capability }) => { + expect(validateConfigCandidate(configWithFastWire(null, capability)).ok).toBe(false); + }); + + test("redacts a token-shaped provider name in a FastWire conflict path", () => { + const providerName = ["sk", "proj", "fastwire", "A".repeat(40)].join("-"); + const result = validateConfigCandidate({ + port: 10100, + defaultProvider: providerName, + providers: { + [providerName]: { + adapter: "openai-responses", + baseUrl: "https://fixture.example/v1", + supportsServiceTier: true, + fastWire: null, + }, + }, + }); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).not.toContain(providerName); + expect(result.error).toContain("providers.[REDACTED].fastWire"); + } + }); + + test("provider-level false keeps null valid even with an exact-model true", () => { + expect(validateConfigCandidate(configWithFastWire(null, { provider: false, exact: true })).ok) + .toBe(true); + }); + + test("accepts and preserves null against an inherited registry capability", () => { + expect(validateConfigCandidate({ + port: 10100, + defaultProvider: "openai-apikey", + providers: { + "openai-apikey": { + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + authMode: "key", + fastWire: null, + }, + }, + })).toMatchObject({ + ok: true, + config: { providers: { "openai-apikey": { fastWire: null } } }, + }); + }); + + test("provider-level false closes an inherited registry capability", () => { + expect(validateConfigCandidate({ + port: 10100, + defaultProvider: "openai-apikey", + providers: { + "openai-apikey": { + adapter: "openai-responses", + baseUrl: "https://api.openai.com/v1", + authMode: "key", + supportsServiceTier: false, + fastWire: null, + }, + }, + }).ok).toBe(true); + }); + + test("registry validation rejects the same null/capability conflict", () => { + expect(providerRegistryFastWireError({ fastWire: null, supportsServiceTier: true })) + .toContain("conflicts"); + expect(providerRegistryFastWireError({ fastWire: null, modelSupportsServiceTier: { [MODEL]: true } })) + .toContain("conflicts"); + expect(providerRegistryFastWireError({ + fastWire: null, + supportsServiceTier: false, + modelSupportsServiceTier: { [MODEL]: true }, + })).toBeNull(); + }); + + test("A1 adds no explicit registry FastWire declaration", () => { + expect(PROVIDER_REGISTRY.every(entry => entry.fastWire === undefined)).toBeTrue(); + }); +}); + +describe("Responses TierDecision immutability", () => { + test.each([ + { label: "set", decision: { kind: "set", value: "priority" } as TierDecision, expected: "priority" }, + { label: "drop", decision: { kind: "drop" } as TierDecision, expected: undefined }, + { label: "forward-caller", decision: { kind: "forward-caller" } as TierDecision, expected: "flex" }, + ])("$label preserves the caller-owned raw body", ({ decision, expected }) => { + const rawBody = { model: MODEL, input: "ping", service_tier: "flex" }; + const original = { ...rawBody }; + const parsed: OcxParsedRequest = { + modelId: MODEL, + context: { messages: [] }, + stream: true, + options: { serviceTier: expected, tierDecision: decision }, + _rawBody: rawBody, + }; + const adapter = withTestTranslatorBudget(createResponsesPassthroughAdapter({ + adapter: "openai-responses", + baseUrl: "https://fixture.example/v1", + authMode: "key", + apiKey: "sk-test", + })); + const outbound = JSON.parse(adapter.buildRequest(parsed).body) as Record; + + expect(parsed._rawBody).toBe(rawBody); + expect(rawBody).toEqual(original); + if (expected === undefined) expect(outbound).not.toHaveProperty("service_tier"); + else expect(outbound.service_tier).toBe(expected); + }); +});