@bitkyc08/opencodex 2.29.0 → 2.31.0-preview.20260822
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/gui/dist/assets/index-DyWYnr-t.js +102 -0
- package/gui/dist/index.html +1 -1
- package/package.json +3 -3
- package/src/adapters/cursor/cursor-errors.ts +65 -6
- package/src/adapters/cursor/discovery.ts +29 -2
- package/src/adapters/cursor/effort-map.ts +6 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +21 -26
- package/src/adapters/cursor/live-transport.ts +239 -8
- package/src/adapters/cursor/native-exec-common.ts +17 -0
- package/src/adapters/cursor/native-exec.ts +9 -4
- package/src/adapters/cursor/protobuf-events.ts +5 -1
- package/src/adapters/cursor/protobuf-request.ts +46 -9
- package/src/adapters/cursor/request-builder.ts +29 -14
- package/src/adapters/cursor/tool-definitions.ts +20 -0
- package/src/adapters/cursor/transport.ts +10 -0
- package/src/adapters/cursor/types.ts +8 -1
- package/src/adapters/cursor.ts +23 -5
- package/src/adapters/google.ts +16 -3
- package/src/adapters/openai-responses.ts +66 -20
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/cli/agent.ts +2 -1
- package/src/cli/dispatch.ts +2 -2
- package/src/cli/doctor.ts +89 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/registry.ts +7 -2
- package/src/codex/auth-context.ts +41 -2
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-fetch.ts +20 -5
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/features.ts +58 -0
- package/src/codex/inject-coordination.ts +39 -6
- package/src/codex/transition-state.ts +12 -12
- package/src/generated/compatibility-version.json +86 -58
- package/src/lib/bun-stream-caps.ts +7 -4
- package/src/lib/errors.ts +8 -2
- package/src/oauth/cursor.ts +21 -0
- package/src/providers/command-code-efforts.ts +7 -0
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +3 -0
- package/src/providers/fastwire.ts +12 -1
- package/src/providers/openai-sidecar.ts +1 -0
- package/src/providers/quota.ts +98 -25
- package/src/providers/registry.ts +115 -10
- package/src/providers/service-tier.ts +22 -7
- package/src/responses/custom-tool-compat.ts +24 -8
- package/src/responses/namespace-tool-compat.ts +2 -3
- package/src/router.ts +3 -0
- package/src/server/chat-completions.ts +4 -0
- package/src/server/chat-native.ts +20 -0
- package/src/server/management/agent-settings-routes.ts +16 -5
- package/src/server/management/config-routes.ts +25 -5
- package/src/server/management/vision-sidecar-options.ts +54 -19
- package/src/server/responses/compact.ts +1 -2
- package/src/server/responses/core.ts +54 -13
- package/src/service.ts +122 -14
- package/src/types/config.ts +9 -3
- package/src/types/provider.ts +6 -0
- package/src/usage/cost.ts +52 -38
- package/src/usage/expected-prices.ts +79 -9
- package/src/vision/backends.ts +97 -0
- package/src/vision/eligibility.ts +43 -22
- package/src/vision/index.ts +73 -5
- package/src/vision/routed-describe.ts +175 -0
- package/gui/dist/assets/index-BNESwCzn.js +0 -102
package/src/service.ts
CHANGED
|
@@ -3274,6 +3274,7 @@ export async function serviceStatusReport(
|
|
|
3274
3274
|
}
|
|
3275
3275
|
|
|
3276
3276
|
export function normalizeServiceSubcommand(sub?: string): string {
|
|
3277
|
+
if (sub === "restart") return "repair";
|
|
3277
3278
|
return sub ?? "install";
|
|
3278
3279
|
}
|
|
3279
3280
|
|
|
@@ -3283,6 +3284,119 @@ export interface ParsedServiceArgs {
|
|
|
3283
3284
|
invalid: string[];
|
|
3284
3285
|
}
|
|
3285
3286
|
|
|
3287
|
+
export type ServiceInstallationState = "installed" | "absent" | "unknown";
|
|
3288
|
+
|
|
3289
|
+
export interface ServiceInstallationProbe {
|
|
3290
|
+
state: ServiceInstallationState;
|
|
3291
|
+
detail?: string;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
export interface ServiceInstallationProbeHooks {
|
|
3295
|
+
platform?: NodeJS.Platform;
|
|
3296
|
+
exists?: (path: string) => boolean;
|
|
3297
|
+
probeWindowsTask?: () => WindowsSchedulerTaskProbe;
|
|
3298
|
+
nativeStatus?: () => WinswStatus;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
/**
|
|
3302
|
+
* Read only enough registration state to choose between install and repair.
|
|
3303
|
+
* Windows must keep query failure distinct from proven absence: treating an
|
|
3304
|
+
* unreadable scheduler/SCM as absent would send a bare command into the
|
|
3305
|
+
* elevated registration path and recreate the original #2287 failure.
|
|
3306
|
+
*/
|
|
3307
|
+
export function probeServiceInstallation(
|
|
3308
|
+
hooks: ServiceInstallationProbeHooks = {},
|
|
3309
|
+
): ServiceInstallationProbe {
|
|
3310
|
+
const platform = hooks.platform ?? process.platform;
|
|
3311
|
+
const exists = hooks.exists ?? existsSync;
|
|
3312
|
+
if (platform === "darwin") {
|
|
3313
|
+
return { state: exists(plistPath()) ? "installed" : "absent" };
|
|
3314
|
+
}
|
|
3315
|
+
if (platform === "linux") {
|
|
3316
|
+
return { state: exists(unitPath()) ? "installed" : "absent" };
|
|
3317
|
+
}
|
|
3318
|
+
if (platform !== "win32") return { state: "absent" };
|
|
3319
|
+
|
|
3320
|
+
let scheduler: WindowsSchedulerTaskProbe;
|
|
3321
|
+
try {
|
|
3322
|
+
scheduler = (hooks.probeWindowsTask ?? probeWindowsSchedulerTask)();
|
|
3323
|
+
} catch (cause) {
|
|
3324
|
+
scheduler = { status: "unknown", detail: schtasksErrorDetail(cause) };
|
|
3325
|
+
}
|
|
3326
|
+
let native: WinswStatus;
|
|
3327
|
+
try {
|
|
3328
|
+
native = (hooks.nativeStatus ?? statusWinswRaw)();
|
|
3329
|
+
} catch {
|
|
3330
|
+
native = "unknown";
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
if (scheduler.status === "present" || native === "started" || native === "stopped") {
|
|
3334
|
+
return { state: "installed" };
|
|
3335
|
+
}
|
|
3336
|
+
if (scheduler.status === "unknown" || native === "unknown") {
|
|
3337
|
+
const parts = [
|
|
3338
|
+
scheduler.status === "unknown" ? `Task Scheduler: ${scheduler.detail}` : null,
|
|
3339
|
+
native === "unknown" ? "WinSW status could not be determined" : null,
|
|
3340
|
+
].filter((part): part is string => Boolean(part));
|
|
3341
|
+
return { state: "unknown", detail: parts.join("; ") };
|
|
3342
|
+
}
|
|
3343
|
+
return { state: "absent" };
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
/**
|
|
3347
|
+
* A bare invocation is an idempotent "make the installed service current"
|
|
3348
|
+
* operation. First-time setup still installs, but an existing registration must
|
|
3349
|
+
* use the repair path so Windows does not re-run the elevated `schtasks /create`.
|
|
3350
|
+
* Backend flags remain an explicit install request because they select which
|
|
3351
|
+
* registration mechanism to create.
|
|
3352
|
+
*/
|
|
3353
|
+
export function selectServiceSubcommand(
|
|
3354
|
+
parsed: ParsedServiceArgs,
|
|
3355
|
+
options: { hasExplicitSubcommand: boolean; installed: boolean },
|
|
3356
|
+
): string {
|
|
3357
|
+
if (!options.hasExplicitSubcommand && parsed.backend === null && options.installed) return "repair";
|
|
3358
|
+
return parsed.sub;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
export type ServiceCommandPlan =
|
|
3362
|
+
| { ok: true; parsed: ParsedServiceArgs; command: string }
|
|
3363
|
+
| { ok: false; message: string };
|
|
3364
|
+
|
|
3365
|
+
export function planServiceCommand(
|
|
3366
|
+
args: string[],
|
|
3367
|
+
options: { platform?: NodeJS.Platform; probeInstallation?: () => ServiceInstallationProbe } = {},
|
|
3368
|
+
): ServiceCommandPlan {
|
|
3369
|
+
const parsed = parseServiceArgs(args);
|
|
3370
|
+
if (parsed.invalid.length > 0) {
|
|
3371
|
+
return { ok: false, message: `Unknown service option: ${parsed.invalid.join(" ")}` };
|
|
3372
|
+
}
|
|
3373
|
+
if (parsed.backend && parsed.sub !== "install") {
|
|
3374
|
+
return { ok: false, message: "--native/--scheduler apply to `ocx service install` only; other subcommands use the installed backend." };
|
|
3375
|
+
}
|
|
3376
|
+
if (parsed.backend === "native" && (options.platform ?? process.platform) !== "win32") {
|
|
3377
|
+
return { ok: false, message: "--native (WinSW) is Windows-only." };
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
const hasExplicitSubcommand = args.some(arg => !arg.startsWith("--"));
|
|
3381
|
+
let installed = false;
|
|
3382
|
+
if (!hasExplicitSubcommand && parsed.backend === null) {
|
|
3383
|
+
const probe = (options.probeInstallation ?? probeServiceInstallation)();
|
|
3384
|
+
if (probe.state === "unknown") {
|
|
3385
|
+
const suffix = probe.detail ? ` (${probe.detail})` : "";
|
|
3386
|
+
return {
|
|
3387
|
+
ok: false,
|
|
3388
|
+
message: `Could not safely determine whether the service is installed${suffix}. Run 'ocx service status' and retry; use explicit 'ocx service install' only after confirming it is absent.`,
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
installed = probe.state === "installed";
|
|
3392
|
+
}
|
|
3393
|
+
return {
|
|
3394
|
+
ok: true,
|
|
3395
|
+
parsed,
|
|
3396
|
+
command: selectServiceSubcommand(parsed, { hasExplicitSubcommand, installed }),
|
|
3397
|
+
};
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3286
3400
|
/**
|
|
3287
3401
|
* `ocx service [sub] [--native|--scheduler]`. The first non-flag token is the
|
|
3288
3402
|
* subcommand; backend flags are only meaningful for `install` (validated by the caller).
|
|
@@ -3308,20 +3422,13 @@ export function parseServiceArgs(args: string[]): ParsedServiceArgs {
|
|
|
3308
3422
|
}
|
|
3309
3423
|
|
|
3310
3424
|
export async function serviceCommand(...args: (string | undefined)[]): Promise<void> {
|
|
3311
|
-
const
|
|
3312
|
-
const
|
|
3313
|
-
if (
|
|
3314
|
-
console.error(
|
|
3315
|
-
process.exit(1);
|
|
3316
|
-
}
|
|
3317
|
-
if (parsed.backend && command !== "install") {
|
|
3318
|
-
console.error("--native/--scheduler apply to `ocx service install` only; other subcommands use the installed backend.");
|
|
3319
|
-
process.exit(1);
|
|
3320
|
-
}
|
|
3321
|
-
if (parsed.backend === "native" && process.platform !== "win32") {
|
|
3322
|
-
console.error("--native (WinSW) is Windows-only.");
|
|
3425
|
+
const filteredArgs = args.filter((a): a is string => Boolean(a));
|
|
3426
|
+
const plan = planServiceCommand(filteredArgs);
|
|
3427
|
+
if (!plan.ok) {
|
|
3428
|
+
console.error(plan.message);
|
|
3323
3429
|
process.exit(1);
|
|
3324
3430
|
}
|
|
3431
|
+
const { parsed, command } = plan;
|
|
3325
3432
|
if (command === "repair") {
|
|
3326
3433
|
assertServiceEnvironmentMatchesInstall();
|
|
3327
3434
|
assertServiceAuthEnvironment();
|
|
@@ -3458,9 +3565,10 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
3458
3565
|
console.log("✅ service uninstalled.");
|
|
3459
3566
|
break;
|
|
3460
3567
|
default:
|
|
3461
|
-
console.error("Usage: ocx service [install|repair|start|stop|status|uninstall|remove] [--native|--scheduler]");
|
|
3462
|
-
console.error(" With no subcommand, installs
|
|
3568
|
+
console.error("Usage: ocx service [install|repair|restart|start|stop|status|uninstall|remove] [--native|--scheduler]");
|
|
3569
|
+
console.error(" With no subcommand, installs when absent or repairs/restarts an existing service.");
|
|
3463
3570
|
console.error(" repair: refresh assets and restart an already-installed service (no admin re-prompt).");
|
|
3571
|
+
console.error(" restart: alias of repair.");
|
|
3464
3572
|
console.error(" --native (Windows only): register a real SCM service via WinSW instead of Task Scheduler.");
|
|
3465
3573
|
process.exit(1);
|
|
3466
3574
|
}
|
package/src/types/config.ts
CHANGED
|
@@ -130,7 +130,7 @@ export interface OcxClaudeCodeConfig {
|
|
|
130
130
|
/** Claude-originated web-search override. Unset fields inherit the global sidecar settings. */
|
|
131
131
|
webSearchSidecar?: { backend?: "openai" | "anthropic" | "xai" | "gemini" | "exa"; model?: string };
|
|
132
132
|
/** Claude-originated vision override. Unset fields inherit the global sidecar settings. */
|
|
133
|
-
visionSidecar?: { backend?: "openai" | "anthropic"; model?: string };
|
|
133
|
+
visionSidecar?: { backend?: "openai" | "anthropic" | "routed"; model?: string };
|
|
134
134
|
/** Persisted Claude Desktop four-family routing profile. */
|
|
135
135
|
desktopProfile?: OcxClaudeDesktopProfile;
|
|
136
136
|
/** Auto-reconcile Desktop 3P config when provider catalog changes. Default: enabled. */
|
|
@@ -774,8 +774,14 @@ export interface OcxSearchConfig {
|
|
|
774
774
|
export interface OcxVisionSidecarConfig {
|
|
775
775
|
/** Master switch. Default: enabled when the selected backend has a usable credential. */
|
|
776
776
|
enabled?: boolean;
|
|
777
|
-
/**
|
|
778
|
-
|
|
777
|
+
/**
|
|
778
|
+
* Description backend. Unset prefers a usable stored Anthropic OAuth credential, else OpenAI —
|
|
779
|
+
* the historical default order, deliberately unchanged by the union widening (#2188 roadmap
|
|
780
|
+
* 170/180 revised): "routed" describes through the proxy's OWN routing (loopback
|
|
781
|
+
* /v1/chat/completions) with a NAMESPACED "provider/model" describer, is explicit-only, and is
|
|
782
|
+
* never auto-selected from credential availability.
|
|
783
|
+
*/
|
|
784
|
+
backend?: "openai" | "anthropic" | "routed";
|
|
779
785
|
/** Vision model that describes images. */
|
|
780
786
|
model?: string;
|
|
781
787
|
/** Max description cache misses admitted in one main-model turn. Zero disables description calls. */
|
package/src/types/provider.ts
CHANGED
|
@@ -352,6 +352,12 @@ export interface OcxProviderConfig {
|
|
|
352
352
|
* passthrough compatibility for OpenAI and unclassified gateways.
|
|
353
353
|
*/
|
|
354
354
|
supportsOpenAiWebSearchToolFields?: boolean;
|
|
355
|
+
/**
|
|
356
|
+
* Whether the Responses upstream accepts native custom tools and custom_tool_call items.
|
|
357
|
+
* Set false only for a provider whose native contract rejects them; absence preserves
|
|
358
|
+
* apply_patch passthrough compatibility for OpenAI and unclassified gateways.
|
|
359
|
+
*/
|
|
360
|
+
supportsResponsesCustomTools?: boolean;
|
|
355
361
|
/**
|
|
356
362
|
* Provider-local repair for Responses gateways whose lifecycle snapshots omit canonical
|
|
357
363
|
* fields or closing events (#893). Disabled by default and applied only to client-facing
|
package/src/usage/cost.ts
CHANGED
|
@@ -22,7 +22,8 @@ import { activeConfiguredProviders, activeUserCostOverlays, userCostOverlayVersi
|
|
|
22
22
|
import {
|
|
23
23
|
EXPECTED_PRICE_OVERLAYS,
|
|
24
24
|
findExpectedPriceOverlay,
|
|
25
|
-
|
|
25
|
+
findVerifiedPriceOverride,
|
|
26
|
+
findPriorityPricingRule,
|
|
26
27
|
findContextTier,
|
|
27
28
|
isLongContext,
|
|
28
29
|
type Cost4,
|
|
@@ -81,12 +82,12 @@ export interface AttemptCostEstimate {
|
|
|
81
82
|
price: MatchedPrice;
|
|
82
83
|
cost: CostBreakdown;
|
|
83
84
|
estimated: boolean;
|
|
84
|
-
/** Applied
|
|
85
|
+
/** Applied provider priority-tier multiplier (undefined or 1 = standard). */
|
|
85
86
|
priorityMultiplier?: number;
|
|
86
|
-
/** Standard-price estimate is a known floor for a confirmed, unpriced priority endpoint. */
|
|
87
|
-
priorityLowerBound?: boolean;
|
|
88
87
|
/** Set when the published long-context rate was applied (#908). */
|
|
89
88
|
contextTier?: ContextTierName;
|
|
89
|
+
/** The numeric estimate is a known floor because the exact Priority price is unavailable. */
|
|
90
|
+
priorityLowerBound?: boolean;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
export interface CostEstimate {
|
|
@@ -95,12 +96,12 @@ export interface CostEstimate {
|
|
|
95
96
|
estimated: boolean;
|
|
96
97
|
attempts?: AttemptCostEstimate[];
|
|
97
98
|
price?: MatchedPrice;
|
|
98
|
-
/** Applied
|
|
99
|
+
/** Applied provider priority-tier multiplier (undefined or 1 = standard). */
|
|
99
100
|
priorityMultiplier?: number;
|
|
100
|
-
/** Standard-price estimate is a known floor for a confirmed, unpriced priority endpoint. */
|
|
101
|
-
priorityLowerBound?: boolean;
|
|
102
101
|
/** Set when any priced attempt used the published long-context rate (#908). */
|
|
103
102
|
contextTier?: ContextTierName;
|
|
103
|
+
/** The aggregate is a known floor because every priced attempt is a lower bound. */
|
|
104
|
+
priorityLowerBound?: boolean;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function finiteNonNegative(value: number): boolean {
|
|
@@ -236,7 +237,7 @@ function resolveMatchedPriceInner(
|
|
|
236
237
|
|
|
237
238
|
/**
|
|
238
239
|
* Exact provider/model price lookup: user-configured `modelCosts` first, then
|
|
239
|
-
* the jawcode provider bundle,
|
|
240
|
+
* an exact official correction, the jawcode provider bundle, the expected-price overlay, then the
|
|
240
241
|
* model-level vendor fallback. All-zero rows fall through ("not billable").
|
|
241
242
|
*/
|
|
242
243
|
function resolveMatchedPriceExact(
|
|
@@ -249,6 +250,20 @@ function resolveMatchedPriceExact(
|
|
|
249
250
|
// operator's explicit price is authoritative for the ~$ estimate.
|
|
250
251
|
const userOverlay = userOverlayMatch(provider, modelId, userOverlays);
|
|
251
252
|
if (userOverlay) return userOverlay;
|
|
253
|
+
const verifiedOverride = overlays === EXPECTED_PRICE_OVERLAYS
|
|
254
|
+
? findVerifiedPriceOverride(provider, modelId)
|
|
255
|
+
: undefined;
|
|
256
|
+
if (verifiedOverride && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
|
|
257
|
+
return {
|
|
258
|
+
provider,
|
|
259
|
+
modelId,
|
|
260
|
+
cost4: verifiedOverride.cost4,
|
|
261
|
+
source: "expected",
|
|
262
|
+
sourceRef: verifiedOverride.source,
|
|
263
|
+
verifiedAt: verifiedOverride.verifiedAt,
|
|
264
|
+
status: "verified",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
252
267
|
const metadataProvider = resolveMetadataProvider(provider);
|
|
253
268
|
const bundled = metadataProvider
|
|
254
269
|
? getModelMetadata(metadataProvider, modelId)
|
|
@@ -320,13 +335,6 @@ function isEstimated(usage: OcxUsage, usageStatus: UsageStatus, priceStatus: Exp
|
|
|
320
335
|
return usage.estimated === true || usageStatus === "estimated" || priceStatus === "verified-derived";
|
|
321
336
|
}
|
|
322
337
|
|
|
323
|
-
/**
|
|
324
|
-
* OpenAI provider ids eligible for service_tier "priority" price multipliers.
|
|
325
|
-
* Only canonical OpenAI forward providers use the priority tier; routed providers
|
|
326
|
-
* (OpenRouter, Cursor, etc.) may share model slugs but have independent pricing.
|
|
327
|
-
*/
|
|
328
|
-
const OPENAI_TIER_PROVIDER_IDS = new Set(["openai", "openai-apikey"]);
|
|
329
|
-
|
|
330
338
|
/**
|
|
331
339
|
* Resolve the effective service tier from persisted log fields.
|
|
332
340
|
* Priority: responseServiceTier (server-confirmed) > requestedServiceTier
|
|
@@ -405,9 +413,9 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
|
|
|
405
413
|
* normalized billable input — normalization subtracts cache read/write, so a
|
|
406
414
|
* cache-heavy long prompt would fall below the boundary and under-bill.
|
|
407
415
|
*
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
416
|
+
* A provider's declaration decides how a response-confirmed priority tier relates to this band.
|
|
417
|
+
* OpenAI declares the bands exclusive. xAI publishes neither a combined rate nor an exclusion,
|
|
418
|
+
* so its long-context rate remains the known lower bound instead of inventing a stacked multiplier.
|
|
411
419
|
*/
|
|
412
420
|
function applyContextTier(
|
|
413
421
|
cost4: Cost4,
|
|
@@ -415,25 +423,27 @@ function applyContextTier(
|
|
|
415
423
|
modelId: string,
|
|
416
424
|
rawInputTokens: number | undefined,
|
|
417
425
|
tier?: ServiceTierInput,
|
|
418
|
-
): [Cost4, ContextTierName | undefined] {
|
|
419
|
-
if (rawInputTokens === undefined) return [cost4, undefined];
|
|
420
|
-
if (isConfirmedFast(tier)) return [cost4, undefined];
|
|
426
|
+
): [Cost4, ContextTierName | undefined, boolean] {
|
|
427
|
+
if (rawInputTokens === undefined) return [cost4, undefined, false];
|
|
421
428
|
const rule = findContextTier(baseProviderLabel(provider), modelId);
|
|
422
|
-
if (!rule || !isLongContext(rule, rawInputTokens)) return [cost4, undefined];
|
|
429
|
+
if (!rule || !isLongContext(rule, rawInputTokens)) return [cost4, undefined, false];
|
|
430
|
+
const confirmedFast = isConfirmedFast(tier);
|
|
431
|
+
if (confirmedFast && rule.confirmedPriorityRelation === "exclusive") {
|
|
432
|
+
return [cost4, undefined, false];
|
|
433
|
+
}
|
|
423
434
|
return [{
|
|
424
435
|
input: cost4.input * rule.multiplier.input,
|
|
425
436
|
output: cost4.output * rule.multiplier.output,
|
|
426
437
|
cacheRead: cost4.cacheRead * rule.multiplier.cacheRead,
|
|
427
438
|
cacheWrite: cost4.cacheWrite * rule.multiplier.cacheWrite,
|
|
428
|
-
}, "long"];
|
|
439
|
+
}, "long", confirmedFast && rule.confirmedPriorityRelation === "lower-bound"];
|
|
429
440
|
}
|
|
430
441
|
|
|
431
442
|
/**
|
|
432
|
-
* Apply
|
|
443
|
+
* Apply a declared provider/model priority-tier multiplier to a Cost4 when applicable.
|
|
433
444
|
* Returns [effectiveCost4, multiplier]. Multiplier is 1 (no-op) when:
|
|
434
445
|
* - serviceTier is not "priority"
|
|
435
|
-
* -
|
|
436
|
-
* - model is not in PRIORITY_MULTIPLIERS
|
|
446
|
+
* - no exact provider/model rule exists
|
|
437
447
|
*/
|
|
438
448
|
function applyPriorityMultiplier(
|
|
439
449
|
cost4: Cost4,
|
|
@@ -443,8 +453,9 @@ function applyPriorityMultiplier(
|
|
|
443
453
|
): [Cost4, number] {
|
|
444
454
|
if (tierScalar(serviceTier) !== "priority") return [cost4, 1];
|
|
445
455
|
const base = baseProviderLabel(provider);
|
|
446
|
-
|
|
447
|
-
|
|
456
|
+
const rule = findPriorityPricingRule(base, modelId);
|
|
457
|
+
if (rule?.requiresResponseConfirmation && !isConfirmedFast(serviceTier)) return [cost4, 1];
|
|
458
|
+
const multiplier = rule?.multiplier ?? 1;
|
|
448
459
|
if (multiplier === 1) return [cost4, 1];
|
|
449
460
|
return [{
|
|
450
461
|
input: cost4.input * multiplier,
|
|
@@ -495,16 +506,17 @@ export function estimateAttemptCost(
|
|
|
495
506
|
const attemptServiceTier = attempt.tierOutcome
|
|
496
507
|
? serviceTierContextFromOutcome(attempt.tierOutcome)
|
|
497
508
|
: serviceTier;
|
|
498
|
-
const [tieredCost4, contextTier] = applyContextTier(
|
|
509
|
+
const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
|
|
499
510
|
price.cost4, attempt.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier,
|
|
500
511
|
);
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
512
|
+
// A published long-context row owns the numeric estimate. OpenAI declares that band
|
|
513
|
+
// exclusive with Fast; xAI's confirmed combination is deliberately left unmultiplied
|
|
514
|
+
// and marked as a lower bound because no combined price has been published.
|
|
504
515
|
const [effectiveCost4, multiplier] = contextTier
|
|
505
516
|
? [tieredCost4, 1] as const
|
|
506
517
|
: applyPriorityMultiplier(tieredCost4, attempt.provider, attempt.model, attemptServiceTier);
|
|
507
|
-
const priorityLowerBound =
|
|
518
|
+
const priorityLowerBound = contextPriorityLowerBound
|
|
519
|
+
|| isOpenRouterPriorityLowerBound(attempt.provider, attempt.tierOutcome);
|
|
508
520
|
return {
|
|
509
521
|
ordinal: attempt.ordinal,
|
|
510
522
|
provider: attempt.provider,
|
|
@@ -514,8 +526,8 @@ export function estimateAttemptCost(
|
|
|
514
526
|
cost: calculateCost(tokens, effectiveCost4),
|
|
515
527
|
estimated: isEstimated(attempt.usage, attempt.usageStatus, price.status),
|
|
516
528
|
...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
|
|
517
|
-
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
518
529
|
...(contextTier ? { contextTier } : {}),
|
|
530
|
+
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
519
531
|
};
|
|
520
532
|
}
|
|
521
533
|
|
|
@@ -557,8 +569,10 @@ export function estimateComboCost(
|
|
|
557
569
|
...(estimates.some(est => est.priorityMultiplier && est.priorityMultiplier !== 1)
|
|
558
570
|
? { priorityMultiplier: estimates.find(est => est.priorityMultiplier)?.priorityMultiplier }
|
|
559
571
|
: {}),
|
|
560
|
-
...(estimates.some(est => est.priorityLowerBound) ? { priorityLowerBound: true } : {}),
|
|
561
572
|
...(estimates.some(est => est.contextTier) ? { contextTier: "long" as const } : {}),
|
|
573
|
+
...(estimates.every(est => est.priorityLowerBound === true)
|
|
574
|
+
? { priorityLowerBound: true as const }
|
|
575
|
+
: {}),
|
|
562
576
|
};
|
|
563
577
|
}
|
|
564
578
|
|
|
@@ -579,13 +593,13 @@ export function estimateRequestCost(
|
|
|
579
593
|
if (!tokens) return null;
|
|
580
594
|
const price = resolveMatchedPrice(input.provider, input.model, overlays, userOverlays);
|
|
581
595
|
if (!price) return null;
|
|
582
|
-
const [tieredCost4, contextTier] = applyContextTier(
|
|
596
|
+
const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
|
|
583
597
|
price.cost4, input.provider, input.model, input.usage.inputTokens, input.serviceTier,
|
|
584
598
|
);
|
|
585
599
|
const [effectiveCost4, multiplier] = contextTier
|
|
586
600
|
? [tieredCost4, 1] as const
|
|
587
601
|
: applyPriorityMultiplier(tieredCost4, input.provider, input.model, input.serviceTier);
|
|
588
|
-
const priorityLowerBound = isOpenRouterPriorityLowerBound(
|
|
602
|
+
const priorityLowerBound = contextPriorityLowerBound || isOpenRouterPriorityLowerBound(
|
|
589
603
|
input.provider,
|
|
590
604
|
typeof input.serviceTier === "object" ? input.serviceTier.tierOutcome : undefined,
|
|
591
605
|
);
|
|
@@ -595,8 +609,8 @@ export function estimateRequestCost(
|
|
|
595
609
|
cost: calculateCost(tokens, effectiveCost4),
|
|
596
610
|
estimated: isEstimated(input.usage, input.usageStatus, price.status),
|
|
597
611
|
...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
|
|
598
|
-
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
599
612
|
...(contextTier ? { contextTier } : {}),
|
|
613
|
+
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
600
614
|
};
|
|
601
615
|
}
|
|
602
616
|
|
|
@@ -181,6 +181,30 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
181
181
|
{ provider: "cursor", modelId: "auto", cost4: { input: 1.25, output: 6, cacheRead: 0.25, cacheWrite: 1.25 }, source: "https://docs.cursor.com/account/pricing + https://cursor.com/blog/aug-2025-pricing", verifiedAt: "2026-07-20", status: "verified" },
|
|
182
182
|
];
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Exact official corrections for stale nonzero catalog rows. These are intentionally separate
|
|
186
|
+
* from fallback overlays: they win over the bundled row only for the declared provider/model and
|
|
187
|
+
* therefore cannot reprice routed resellers that reuse the same model slug.
|
|
188
|
+
*/
|
|
189
|
+
export const VERIFIED_PRICE_OVERRIDES: readonly ExpectedPriceOverlay[] = [
|
|
190
|
+
{
|
|
191
|
+
provider: "xai",
|
|
192
|
+
modelId: "grok-4.6",
|
|
193
|
+
cost4: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 },
|
|
194
|
+
source: "https://docs.x.ai/developers/pricing",
|
|
195
|
+
verifiedAt: "2026-08-18",
|
|
196
|
+
status: "verified",
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
export function findVerifiedPriceOverride(
|
|
201
|
+
provider: string,
|
|
202
|
+
modelId: string,
|
|
203
|
+
overrides: readonly ExpectedPriceOverlay[] = VERIFIED_PRICE_OVERRIDES,
|
|
204
|
+
): ExpectedPriceOverlay | undefined {
|
|
205
|
+
return overrides.find(row => row.provider === provider && row.modelId === modelId);
|
|
206
|
+
}
|
|
207
|
+
|
|
184
208
|
/**
|
|
185
209
|
* Exact-key overlay lookup. Returns verified first, then verified-derived.
|
|
186
210
|
* NEVER returns "unverified" rows — fail-closed is enforced in code, not just docs.
|
|
@@ -196,12 +220,7 @@ export function findExpectedPriceOverlay(
|
|
|
196
220
|
?? exact.find(row => row.status === "verified-derived");
|
|
197
221
|
}
|
|
198
222
|
|
|
199
|
-
/**
|
|
200
|
-
* OpenAI Fast mode (`service_tier=priority`) price multipliers by model slug.
|
|
201
|
-
* Source: https://openai.com/api-fast-mode/ (2026-07-31).
|
|
202
|
-
* Fast pricing applies uniformly to all token types (input, output, cache).
|
|
203
|
-
* Models not listed here fall back to 1× (no multiplier).
|
|
204
|
-
*/
|
|
223
|
+
/** OpenAI Fast price multipliers retained as a compatibility export. */
|
|
205
224
|
export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
|
|
206
225
|
"gpt-5.6-sol": 2,
|
|
207
226
|
// Post-price-cut Fast tables (https://openai.com/api-fast-mode/, 2026-08-05):
|
|
@@ -219,6 +238,52 @@ export function resolvePriorityMultiplier(modelId: string): number {
|
|
|
219
238
|
return PRIORITY_MULTIPLIERS[modelId] ?? 1;
|
|
220
239
|
}
|
|
221
240
|
|
|
241
|
+
export interface PriorityPricingRule {
|
|
242
|
+
provider: string;
|
|
243
|
+
modelId: string;
|
|
244
|
+
multiplier: number;
|
|
245
|
+
/** Apply the premium only after the upstream response confirms this tier. */
|
|
246
|
+
requiresResponseConfirmation?: true;
|
|
247
|
+
source: string;
|
|
248
|
+
verifiedAt: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const OPENAI_FAST_PRICING = "https://openai.com/api-fast-mode/";
|
|
252
|
+
const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/priority-processing";
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Exact provider/model priority premiums. Routed resellers never inherit a vendor rule merely
|
|
256
|
+
* because they reuse its model slug. Multipliers apply uniformly after cache discounts.
|
|
257
|
+
*/
|
|
258
|
+
export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
|
|
259
|
+
...["openai", "openai-apikey"].flatMap(provider =>
|
|
260
|
+
Object.entries(PRIORITY_MULTIPLIERS).map(([modelId, multiplier]): PriorityPricingRule => ({
|
|
261
|
+
provider,
|
|
262
|
+
modelId,
|
|
263
|
+
multiplier,
|
|
264
|
+
source: OPENAI_FAST_PRICING,
|
|
265
|
+
verifiedAt: "2026-08-05",
|
|
266
|
+
})),
|
|
267
|
+
),
|
|
268
|
+
...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
|
|
269
|
+
provider: "xai",
|
|
270
|
+
modelId,
|
|
271
|
+
multiplier: 2,
|
|
272
|
+
requiresResponseConfirmation: true,
|
|
273
|
+
source: XAI_PRIORITY_PRICING,
|
|
274
|
+
verifiedAt: "2026-08-18",
|
|
275
|
+
})),
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
/** Exact provider/model priority-pricing lookup. */
|
|
279
|
+
export function findPriorityPricingRule(
|
|
280
|
+
provider: string,
|
|
281
|
+
modelId: string,
|
|
282
|
+
rules: readonly PriorityPricingRule[] = PRIORITY_PRICING_RULES,
|
|
283
|
+
): PriorityPricingRule | undefined {
|
|
284
|
+
return rules.find(rule => rule.provider === provider && rule.modelId === modelId);
|
|
285
|
+
}
|
|
286
|
+
|
|
222
287
|
/**
|
|
223
288
|
* Long-context pricing tiers (#908). Several vendors reprice the ENTIRE request
|
|
224
289
|
* once the prompt crosses a published input-token threshold, so a flat Cost4
|
|
@@ -244,6 +309,8 @@ export interface ContextTier {
|
|
|
244
309
|
inclusive: boolean;
|
|
245
310
|
/** Per-field factor from the short rate to the published long rate. */
|
|
246
311
|
multiplier: Cost4;
|
|
312
|
+
/** Published relationship between confirmed priority and long-context bands. */
|
|
313
|
+
confirmedPriorityRelation?: "exclusive" | "lower-bound";
|
|
247
314
|
source: string;
|
|
248
315
|
verifiedAt: string;
|
|
249
316
|
}
|
|
@@ -277,6 +344,7 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
277
344
|
thresholdInputTokens: 272_000,
|
|
278
345
|
inclusive: false,
|
|
279
346
|
multiplier: OPENAI_LONG_CONTEXT,
|
|
347
|
+
confirmedPriorityRelation: "exclusive",
|
|
280
348
|
source: OPENAI_PRICING_DOC,
|
|
281
349
|
verifiedAt: "2026-08-03",
|
|
282
350
|
})),
|
|
@@ -287,19 +355,21 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
287
355
|
thresholdInputTokens: 200_000,
|
|
288
356
|
inclusive: true,
|
|
289
357
|
multiplier: UNIFORM_DOUBLE,
|
|
358
|
+
confirmedPriorityRelation: "lower-bound",
|
|
290
359
|
source: "https://docs.x.ai/developers/pricing",
|
|
291
360
|
verifiedAt: "2026-08-03",
|
|
292
361
|
},
|
|
293
362
|
{
|
|
294
|
-
//
|
|
295
|
-
//
|
|
363
|
+
// xAI publishes the whole-request >=200k band for grok-4.6. Its combination with
|
|
364
|
+
// Priority Processing is not published, so confirmed priority uses this row as a lower bound.
|
|
296
365
|
provider: "xai",
|
|
297
366
|
modelId: "grok-4.6",
|
|
298
367
|
thresholdInputTokens: 200_000,
|
|
299
368
|
inclusive: true,
|
|
300
369
|
multiplier: UNIFORM_DOUBLE,
|
|
370
|
+
confirmedPriorityRelation: "lower-bound",
|
|
301
371
|
source: "https://docs.x.ai/developers/pricing",
|
|
302
|
-
verifiedAt: "2026-08-
|
|
372
|
+
verifiedAt: "2026-08-18",
|
|
303
373
|
},
|
|
304
374
|
{
|
|
305
375
|
// daybreak-blue-latest aliases gpt-5.6-sol, which publishes the full long-context row
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which backends may DESCRIBE images for the vision sidecar, and which
|
|
3
|
+
* candidate rows each can describe through (#2188 vision rules; roadmap 170
|
|
4
|
+
* REVISED: the "routed" backend).
|
|
5
|
+
*
|
|
6
|
+
* A SIBLING of WEB_SEARCH_BACKENDS, not a shared table: vision has no
|
|
7
|
+
* per-model probe (rule 2 is "− provably text-only", enforced by
|
|
8
|
+
* modelAcceptsImageInput, not here), carries per-side baseline models, and
|
|
9
|
+
* excludes non-LLM backends like exa.
|
|
10
|
+
*
|
|
11
|
+
* Three backends, not one per provider: "openai" and "anthropic" carry auth
|
|
12
|
+
* semantics loopback routing cannot replicate (forwarded ChatGPT headers,
|
|
13
|
+
* OAuth beta fences) and their defaults must not drift. Every OTHER
|
|
14
|
+
* picker-visible provider row reaches the describer through "routed" — a
|
|
15
|
+
* loopback self-fetch of the proxy's own /v1/chat/completions, where the
|
|
16
|
+
* router and adapters already speak each provider's wire. That is what makes
|
|
17
|
+
* this table closed under provider growth: a new provider needs no new
|
|
18
|
+
* describe executor.
|
|
19
|
+
*/
|
|
20
|
+
import type { OcxConfig } from "../types";
|
|
21
|
+
import type { SidecarAuthState } from "../sidecar/auth";
|
|
22
|
+
import { listOpenAiForwardSidecarCandidates } from "../providers/openai-sidecar";
|
|
23
|
+
import type { VisionCandidateModel, VisionSidecarBackend } from "./eligibility";
|
|
24
|
+
|
|
25
|
+
export interface VisionBackendDescriptor {
|
|
26
|
+
backend: VisionSidecarBackend;
|
|
27
|
+
/** Liveness signal for this backend. */
|
|
28
|
+
isActive(auth: SidecarAuthState, config: OcxConfig): boolean;
|
|
29
|
+
/** Which candidate rows this backend's describe executor can actually run. */
|
|
30
|
+
candidateMatch(candidate: VisionCandidateModel, auth: SidecarAuthState): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Default entry for this side: cheap, image-capable, present in every
|
|
33
|
+
* deployment. Only the two universal sides carry one — "routed" has no
|
|
34
|
+
* universal model to name.
|
|
35
|
+
*/
|
|
36
|
+
baseline?: string;
|
|
37
|
+
/** Stable option ordering (baselines first within a side). */
|
|
38
|
+
rank: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const VISION_BACKENDS: readonly VisionBackendDescriptor[] = [
|
|
42
|
+
{
|
|
43
|
+
backend: "openai",
|
|
44
|
+
// The OpenAI describer needs a CANONICAL ChatGPT forward provider, not
|
|
45
|
+
// merely a provider keyed "openai" — same predicate the runtime sidecar
|
|
46
|
+
// resolver uses. Deliberately NOT auth.isCodexAuth: tightening to a live
|
|
47
|
+
// credential here would change which options a fresh install sees, and
|
|
48
|
+
// the options list is a suggestion surface, not the write gate.
|
|
49
|
+
isActive: (_auth, config) => listOpenAiForwardSidecarCandidates(config).length > 0,
|
|
50
|
+
candidateMatch: candidate => candidate.native === true || candidate.provider === "openai",
|
|
51
|
+
baseline: "gpt-5.6-luna",
|
|
52
|
+
rank: 0,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
backend: "anthropic",
|
|
56
|
+
isActive: auth => auth.isAnthropicAuth,
|
|
57
|
+
// The runtime dispatches through exactly ONE Anthropic provider — the
|
|
58
|
+
// resolved OAuth row. Same-adapter keyed rows are unreachable (see
|
|
59
|
+
// visionBackendForCandidate's original stance).
|
|
60
|
+
candidateMatch: (candidate, auth) => candidate.provider === auth.anthropicProviderName,
|
|
61
|
+
baseline: "claude-haiku-4-5",
|
|
62
|
+
rank: 1,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
backend: "routed",
|
|
66
|
+
// Always offered: options only materialize when a matching picker row
|
|
67
|
+
// exists, and the row's own provider config is the liveness signal — the
|
|
68
|
+
// loopback request fails closed through ordinary routing errors.
|
|
69
|
+
isActive: () => true,
|
|
70
|
+
// Any row the other two executors do NOT own. Auth-slot rows are
|
|
71
|
+
// entitlements of the openai/anthropic sides and never route here.
|
|
72
|
+
candidateMatch: (candidate, auth) =>
|
|
73
|
+
candidate.native !== true
|
|
74
|
+
&& candidate.provider !== "openai"
|
|
75
|
+
&& candidate.provider !== auth.anthropicProviderName,
|
|
76
|
+
rank: 2,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export function visionBackendDescriptor(backend: VisionSidecarBackend): VisionBackendDescriptor {
|
|
81
|
+
const descriptor = VISION_BACKENDS.find(entry => entry.backend === backend);
|
|
82
|
+
if (!descriptor) throw new Error(`unknown vision backend "${backend}"`);
|
|
83
|
+
return descriptor;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The active backend set for option generation. Falls back to the two
|
|
88
|
+
* UNIVERSAL sides when neither is active (fresh install: picker stays
|
|
89
|
+
* populated, permissive-unknown rule); "routed" is active by construction.
|
|
90
|
+
*/
|
|
91
|
+
export function activeVisionBackends(auth: SidecarAuthState, config: OcxConfig): VisionSidecarBackend[] {
|
|
92
|
+
const active = VISION_BACKENDS.filter(entry => entry.isActive(auth, config)).map(entry => entry.backend);
|
|
93
|
+
return active.includes("openai") || active.includes("anthropic")
|
|
94
|
+
? active
|
|
95
|
+
: ["openai", "anthropic", ...active.filter(backend => backend === "routed")];
|
|
96
|
+
}
|
|
97
|
+
|