@yansigit/opencodex 2.31.3 → 2.32.0
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/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-BG43zwVe.js +102 -0
- package/gui/dist/assets/index-CiSI-jrP.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +6 -8
- package/src/adapters/base.ts +2 -0
- package/src/adapters/command-code.ts +2 -3
- package/src/adapters/cursor/live-models.ts +8 -0
- package/src/adapters/cursor/native-exec-desktop.ts +16 -0
- package/src/adapters/cursor/tool-definitions.ts +1 -1
- package/src/adapters/google.ts +6 -7
- package/src/adapters/kiro.ts +0 -3
- package/src/adapters/openai-responses.ts +3 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-web-search.ts +7 -2
- package/src/bridge.ts +21 -15
- package/src/cli/dispatch.ts +50 -2
- package/src/cli/doctor.ts +24 -11
- package/src/cli/help.ts +4 -3
- package/src/cli/index.ts +11 -4
- package/src/cli/models.ts +13 -3
- package/src/cli/observe.ts +20 -5
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +7 -5
- package/src/cli/status.ts +2 -1
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +134 -0
- package/src/codex/app-server-processes.ts +3 -1
- package/src/codex/catalog/aggregation.ts +13 -1
- package/src/codex/catalog/effort.ts +24 -9
- package/src/codex/catalog/model-metadata.ts +566 -0
- package/src/codex/catalog/parsing.ts +33 -0
- package/src/codex/catalog/provider-fetch.ts +123 -32
- package/src/codex/catalog/sync.ts +5 -4
- package/src/codex/desktop-app-restart.ts +342 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject.ts +49 -21
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/combos/failover.ts +27 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +81 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +308 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config.ts +75 -812
- package/src/generated/compatibility-version.json +145 -85
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +141 -0
- package/src/integrations/ownership.ts +10 -0
- package/src/integrations/state.ts +44 -5
- package/src/integrations/writer.ts +6 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/process-control.ts +2 -1
- package/src/lib/state-store-registrations.ts +2 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/health.ts +1 -1
- package/src/providers/registry.ts +1 -1
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/custom-tool-compat.ts +132 -38
- package/src/responses/parser.ts +3 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/server/auth-cors.ts +9 -7
- package/src/server/index.ts +102 -21
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +1 -1
- package/src/server/management/config-routes.ts +4 -1
- package/src/server/management/context.ts +1 -1
- package/src/server/management/logs-usage-routes.ts +27 -6
- package/src/server/management/model-routes.ts +8 -4
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +2 -1
- package/src/server/request-log-conversation.ts +30 -0
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/combo-stream-preflight.ts +171 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +225 -94
- package/src/server/responses/fetch-helpers.ts +2 -97
- package/src/server/responses-custom-tool-repair.ts +41 -5
- package/src/server/responses-undeclared-tool-guard.ts +156 -15
- package/src/service.ts +8 -4
- package/src/types/request.ts +6 -1
- package/src/types/tools.ts +87 -11
- package/src/types.ts +1 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/log.ts +16 -8
- package/src/usage/summary.ts +201 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/executor.ts +10 -3
- package/gui/dist/assets/index-CGoDO3uO.css +0 -1
- package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human rendering for `ocx usage`.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of `observe.ts` and away from the shared `summaryLines()` helper on
|
|
5
|
+
* purpose. `summaryLines()` is a generic depth-1 flattener shared with
|
|
6
|
+
* storage/memory/debug/claude-inbound/injection; it renders any array as
|
|
7
|
+
* "N item(s)", which is why every per-model and per-provider cost the server
|
|
8
|
+
* computes used to vanish before reaching the terminal. Deepening it would
|
|
9
|
+
* change five unrelated commands.
|
|
10
|
+
*
|
|
11
|
+
* Formatting follows the existing CLI house style: dynamic `padEnd` columns
|
|
12
|
+
* (as in `formatAccountTable`), plain text, no ANSI colour.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface CostRow {
|
|
16
|
+
provider: string;
|
|
17
|
+
model?: string;
|
|
18
|
+
requests: number;
|
|
19
|
+
totalTokens: number;
|
|
20
|
+
estimatedCostUsd?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UsageReportInput {
|
|
24
|
+
range?: string;
|
|
25
|
+
surface?: string;
|
|
26
|
+
since?: number | null;
|
|
27
|
+
summary?: {
|
|
28
|
+
requests?: number;
|
|
29
|
+
totalTokens?: number;
|
|
30
|
+
inputTokens?: number;
|
|
31
|
+
outputTokens?: number;
|
|
32
|
+
cachedInputTokens?: number;
|
|
33
|
+
estimatedCostUsd?: number;
|
|
34
|
+
unpricedRequests?: number;
|
|
35
|
+
unmeteredRequests?: number;
|
|
36
|
+
};
|
|
37
|
+
models?: CostRow[];
|
|
38
|
+
providers?: CostRow[];
|
|
39
|
+
days?: { date: string; requests: number; totalTokens: number; estimatedCostUsd?: number }[];
|
|
40
|
+
filter?: { provider: string | null; model: string | null; matched: boolean; comboOverlap: boolean };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const MAX_MODEL_ROWS = 10;
|
|
44
|
+
|
|
45
|
+
function count(value: number | undefined): string {
|
|
46
|
+
return (value ?? 0).toLocaleString("en-US");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Matches the dashboard's `~$` with four fraction digits. Estimates below a
|
|
51
|
+
* hundredth of a cent still read as a number rather than collapsing to $0.00,
|
|
52
|
+
* which matters when a single request is being inspected.
|
|
53
|
+
*/
|
|
54
|
+
function usd(value: number | undefined): string {
|
|
55
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return "—";
|
|
56
|
+
return `~$${value.toFixed(4)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function table(header: string[], rows: string[][]): string[] {
|
|
60
|
+
if (rows.length === 0) return [];
|
|
61
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map(r => (r[i] ?? "").length)));
|
|
62
|
+
const line = (cols: string[]): string => cols.map((c, i) => (c ?? "").padEnd(widths[i]!)).join(" ").trimEnd();
|
|
63
|
+
return [line(header), ...rows.map(line)];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function describeScope(data: UsageReportInput): string {
|
|
67
|
+
const parts = [`Usage — ${data.range ?? "?"}`];
|
|
68
|
+
if (data.surface && data.surface !== "all") parts.push(`surface=${data.surface}`);
|
|
69
|
+
if (data.filter?.provider) parts.push(`provider=${data.filter.provider}`);
|
|
70
|
+
if (data.filter?.model) parts.push(`model=${data.filter.model}`);
|
|
71
|
+
return parts.join(", ");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatUsageReport(data: UsageReportInput): string[] {
|
|
75
|
+
const summary = data.summary ?? {};
|
|
76
|
+
const lines: string[] = [describeScope(data), ""];
|
|
77
|
+
|
|
78
|
+
if (data.filter && !data.filter.matched) {
|
|
79
|
+
const what = [data.filter.provider && `provider "${data.filter.provider}"`, data.filter.model && `model "${data.filter.model}"`]
|
|
80
|
+
.filter(Boolean).join(" and ");
|
|
81
|
+
lines.push(`No usage recorded for ${what} in this range.`);
|
|
82
|
+
lines.push("Check the spelling against `ocx usage --json`, or widen --range.");
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const tokenSplit = [
|
|
87
|
+
summary.inputTokens !== undefined ? `in ${count(summary.inputTokens)}` : null,
|
|
88
|
+
summary.outputTokens !== undefined ? `out ${count(summary.outputTokens)}` : null,
|
|
89
|
+
summary.cachedInputTokens ? `cached ${count(summary.cachedInputTokens)}` : null,
|
|
90
|
+
].filter(Boolean).join(" / ");
|
|
91
|
+
|
|
92
|
+
lines.push(`Requests ${count(summary.requests)}`);
|
|
93
|
+
lines.push(`Tokens ${count(summary.totalTokens)}${tokenSplit ? ` (${tokenSplit})` : ""}`);
|
|
94
|
+
lines.push(`Est. cost ${usd(summary.estimatedCostUsd)} API list-price equivalent (this range)`);
|
|
95
|
+
|
|
96
|
+
const unpriced = summary.unpricedRequests ?? 0;
|
|
97
|
+
const unmetered = summary.unmeteredRequests ?? 0;
|
|
98
|
+
if (unpriced > 0 || unmetered > 0) {
|
|
99
|
+
// Spelled out because a $0 total is ambiguous otherwise: it can mean "no
|
|
100
|
+
// spend" or "no price row matched", and those are very different answers.
|
|
101
|
+
lines.push(` ${count(unpriced)} unpriced, ${count(unmetered)} unmetered excluded from ~$`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const providers = (data.providers ?? []).filter(row => row.requests > 0);
|
|
105
|
+
if (providers.length > 0) {
|
|
106
|
+
lines.push("");
|
|
107
|
+
lines.push(...table(
|
|
108
|
+
["PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
|
|
109
|
+
providers.map(row => [row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const models = (data.models ?? []).filter(row => row.requests > 0);
|
|
114
|
+
if (models.length > 0) {
|
|
115
|
+
lines.push("");
|
|
116
|
+
const shown = models.slice(0, MAX_MODEL_ROWS);
|
|
117
|
+
lines.push(...table(
|
|
118
|
+
["MODEL", "PROVIDER", "REQUESTS", "TOKENS", "EST. COST"],
|
|
119
|
+
shown.map(row => [row.model ?? "-", row.provider, count(row.requests), count(row.totalTokens), usd(row.estimatedCostUsd)]),
|
|
120
|
+
));
|
|
121
|
+
if (models.length > shown.length) {
|
|
122
|
+
lines.push(`... ${models.length - shown.length} more (use --json)`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (data.filter?.comboOverlap) {
|
|
127
|
+
lines.push("");
|
|
128
|
+
lines.push("Some requests ran as combos, so per-model request counts can overlap. Cost does not.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lines.push("");
|
|
132
|
+
lines.push("Not a billing receipt. Subscription usage or provider credits may apply instead.");
|
|
133
|
+
return lines;
|
|
134
|
+
}
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
import { readCodexCatalogPath } from "./catalog/parsing";
|
|
18
18
|
|
|
19
19
|
export const STALE_CODEX_APP_SERVER_HINT =
|
|
20
|
-
"If Codex still shows an older model list, restart its long-lived app-server process after sync (ocx sync --restart-codex)."
|
|
20
|
+
"If Codex still shows an older model list, restart its long-lived app-server process after sync (ocx sync --restart-codex). "
|
|
21
|
+
+ "On Windows the desktop app itself may also need a full restart (ocx sync --restart-desktop-app).";
|
|
21
22
|
|
|
22
23
|
/** Attach the shared dashboard hint only after a catalog or models_cache write. */
|
|
23
24
|
export function attachStaleAppServerHint<T extends {
|
|
@@ -505,6 +506,7 @@ export function formatStaleCodexAppServerWarning(
|
|
|
505
506
|
`WARNING: ${processes.length} Codex app-server process(es) still running (PID${processes.length === 1 ? "" : "s"}: ${pids}). `
|
|
506
507
|
+ "Disk catalog/cache were updated, but Codex may keep showing the old model list until those processes restart. "
|
|
507
508
|
+ "Re-run with `ocx sync --restart-codex` (or `ocx sync-cache --restart-codex`) to send SIGTERM only to matching app-server processes. "
|
|
509
|
+
+ "On Windows the desktop app itself may also need a full restart (`ocx sync --restart-desktop-app`). "
|
|
508
510
|
+ "Active turns may be interrupted."
|
|
509
511
|
);
|
|
510
512
|
}
|
|
@@ -152,10 +152,20 @@ export function deriveComboCatalogModel(
|
|
|
152
152
|
// A combo is cap-limited only when every member defining its effective minimum was
|
|
153
153
|
// itself reduced by a provider cap. An uncapped member at the same minimum means the
|
|
154
154
|
// combo would have the same window even without the cap.
|
|
155
|
-
const contextCapped = limitingMembers.every(member =>
|
|
155
|
+
const contextCapped = limitingMembers.every(member => (
|
|
156
|
+
member.contextCapped === true
|
|
157
|
+
|| (
|
|
158
|
+
typeof member.detectedContextWindow === "number"
|
|
159
|
+
&& typeof member.contextWindow === "number"
|
|
160
|
+
&& member.detectedContextWindow > member.contextWindow
|
|
161
|
+
)
|
|
162
|
+
));
|
|
156
163
|
const maxInputTokens = Math.min(
|
|
157
164
|
...members.map(member => member.maxInputTokens ?? member.contextWindow!),
|
|
158
165
|
);
|
|
166
|
+
const detectedContextWindow = Math.min(
|
|
167
|
+
...limitingMembers.map(member => member.detectedContextWindow ?? member.contextWindow!),
|
|
168
|
+
);
|
|
159
169
|
const defaultReasoningEffort = effectiveComboDefault(
|
|
160
170
|
combo.defaultEffort,
|
|
161
171
|
reasoningEfforts,
|
|
@@ -167,6 +177,8 @@ export function deriveComboCatalogModel(
|
|
|
167
177
|
owned_by: COMBO_NAMESPACE,
|
|
168
178
|
contextWindow,
|
|
169
179
|
maxInputTokens,
|
|
180
|
+
metadataSource: "derived",
|
|
181
|
+
detectedContextWindow,
|
|
170
182
|
...(hasLimitingContextCapMetadata ? { contextCapped } : {}),
|
|
171
183
|
inputModalities,
|
|
172
184
|
reasoningEfforts,
|
|
@@ -33,6 +33,7 @@ import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
|
33
33
|
|
|
34
34
|
import { generatedModelMetadata, readCatalog, readCodexCatalogPath } from "./parsing";
|
|
35
35
|
import type { CatalogModel, RawEntry } from "./parsing";
|
|
36
|
+
import { shouldStampContextProvenance } from "./model-metadata";
|
|
36
37
|
import { UPSTREAM_NATIVE_ENTRIES } from "./metadata";
|
|
37
38
|
import { nativeOpenAiCapabilitySourceSlug } from "./native-models";
|
|
38
39
|
import { loadBundledCodexCatalog } from "./bundled";
|
|
@@ -171,33 +172,47 @@ export function applyCatalogModelMetadata(entry: RawEntry, model?: CatalogModel)
|
|
|
171
172
|
* live in that table.
|
|
172
173
|
*/
|
|
173
174
|
function stampCapabilityProvenance(entry: RawEntry, model: CatalogModel): void {
|
|
174
|
-
// Virtual combo rows
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
if (model.provider === COMBO_NAMESPACE) return;
|
|
175
|
+
// Virtual combo rows without derived member evidence are placeholders
|
|
176
|
+
// (generic 128k / `["text"]`). Stamping those would reintroduce the
|
|
177
|
+
// false-evidence defect this block exists to prevent. Combos that actually
|
|
178
|
+
// min() real member windows carry `metadataSource: "derived"`.
|
|
179
|
+
if (model.provider === COMBO_NAMESPACE && model.metadataSource !== "derived") return;
|
|
179
180
|
|
|
180
181
|
const meta = generatedModelMetadata(model.provider, model.id);
|
|
181
182
|
const metaContext = typeof meta?.contextWindow === "number" && meta.contextWindow > 0
|
|
182
|
-
// The generated context is capped before it reaches the entry, so provenance
|
|
183
|
-
// must apply the same cap or routing would advertise a window the cap refused.
|
|
184
183
|
? applyProviderContextCap(meta.contextWindow, model.contextCap) ?? meta.contextWindow
|
|
185
184
|
: undefined;
|
|
186
|
-
const
|
|
185
|
+
const stampModelContext = shouldStampContextProvenance(model);
|
|
186
|
+
const contextWindow = stampModelContext
|
|
187
187
|
? model.contextWindow
|
|
188
|
-
: metaContext;
|
|
188
|
+
: (model.metadataSource === undefined ? metaContext : undefined);
|
|
189
189
|
const inputModalities = Array.isArray(model.inputModalities) && model.inputModalities.length > 0
|
|
190
190
|
? model.inputModalities
|
|
191
191
|
: (Array.isArray(meta?.input) && meta.input.length > 0 ? meta.input : undefined);
|
|
192
|
+
const maxInputTokens = typeof model.maxInputTokens === "number" && model.maxInputTokens > 0
|
|
193
|
+
? model.maxInputTokens
|
|
194
|
+
: undefined;
|
|
195
|
+
const source = model.metadataSource;
|
|
196
|
+
const stampSource = source === "live" || source === "registry" || source === "snapshot" || source === "derived";
|
|
197
|
+
|
|
198
|
+
if (contextWindow === undefined && inputModalities === undefined
|
|
199
|
+
&& !(Array.isArray(model.capabilities) && model.capabilities.length > 0)) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
192
202
|
|
|
193
203
|
entry.opencodex_capability_provenance = {
|
|
194
204
|
provider: model.provider,
|
|
195
205
|
model_id: model.id,
|
|
196
206
|
...(contextWindow !== undefined ? { context_window: contextWindow } : {}),
|
|
207
|
+
...(maxInputTokens !== undefined && stampModelContext ? { max_input_tokens: maxInputTokens } : {}),
|
|
197
208
|
...(inputModalities !== undefined ? { input_modalities: [...inputModalities] } : {}),
|
|
198
209
|
...(Array.isArray(model.capabilities) && model.capabilities.length > 0
|
|
199
210
|
? { capabilities: [...model.capabilities] }
|
|
200
211
|
: {}),
|
|
212
|
+
...(stampSource ? { source } : {}),
|
|
213
|
+
...(typeof model.metadataObservedAt === "string" && stampSource
|
|
214
|
+
? { observed_at: model.metadataObservedAt }
|
|
215
|
+
: {}),
|
|
201
216
|
};
|
|
202
217
|
}
|
|
203
218
|
|