@bitkyc08/opencodex 2.7.35 → 2.7.36
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.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +4 -2
- package/README.ru.md +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/ocx.mjs +52 -0
- package/gui/dist/assets/index-BpX-hoSd.css +1 -0
- package/gui/dist/assets/index-ZmFopEYw.js +52 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/cursor/cursor-errors.ts +38 -1
- package/src/adapters/cursor/discovery.ts +1 -0
- package/src/adapters/cursor/effort-map.ts +1 -0
- package/src/adapters/cursor/live-models.ts +22 -5
- package/src/adapters/cursor/live-transport.ts +82 -7
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +5 -2
- package/src/adapters/openai-responses.ts +64 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/help.ts +10 -0
- package/src/cli/index.ts +88 -9
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/status.ts +15 -4
- package/src/cli/tray-proxy.ts +52 -0
- package/src/codex/auth-api.ts +46 -5
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +268 -0
- package/src/codex/catalog/bundled.ts +188 -0
- package/src/codex/catalog/effort.ts +263 -0
- package/src/codex/catalog/metadata.ts +176 -0
- package/src/codex/catalog/parsing.ts +399 -0
- package/src/codex/catalog/provider-fetch.ts +609 -0
- package/src/codex/catalog/sync.ts +540 -0
- package/src/codex/catalog.ts +11 -2426
- package/src/codex/inject.ts +165 -3
- package/src/codex/shim.ts +141 -8
- package/src/codex/sync.ts +17 -2
- package/src/config.ts +23 -0
- package/src/lib/errors.ts +11 -0
- package/src/providers/antigravity-models.ts +33 -0
- package/src/providers/kiro-models.ts +2 -0
- package/src/providers/registry.ts +2 -2
- package/src/responses/state.ts +69 -6
- package/src/server/auth-cors.ts +3 -0
- package/src/server/management/agent-settings-routes.ts +536 -0
- package/src/server/management/combo-routes.ts +210 -0
- package/src/server/management/config-routes.ts +302 -0
- package/src/server/management/context.ts +21 -0
- package/src/server/management/logs-usage-routes.ts +176 -0
- package/src/server/management/model-routes.ts +253 -0
- package/src/server/management/oauth-account-routes.ts +301 -0
- package/src/server/management/provider-routes.ts +408 -0
- package/src/server/management/shared.ts +186 -0
- package/src/server/management-api.ts +23 -1806
- package/src/server/responses/collaboration.ts +300 -0
- package/src/server/responses/compact.ts +342 -0
- package/src/server/responses/core.ts +1498 -0
- package/src/server/responses/encrypted-payload.ts +231 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses.ts +9 -2172
- package/src/server/startup-action-control.ts +41 -0
- package/src/server/startup-health-cache.ts +100 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/service.ts +171 -19
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +290 -0
- package/src/tray/windows.ts +628 -0
- package/src/types.ts +5 -0
- package/src/update/index.ts +43 -0
- package/src/update/job.ts +46 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +9 -2
- package/src/usage/summary.ts +42 -7
- package/gui/dist/assets/index-BunUANVE.js +0 -52
- package/gui/dist/assets/index-Sg-7L_oZ.css +0 -1
package/src/update/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, readdirSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { getConfigDir, loadConfig, readPid, readRuntimePort } from "../config";
|
|
6
|
+
import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "./tray-update-plan.mjs";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* A `codex-history-backup-*.json` surviving a stop means the native-history restore was
|
|
@@ -170,6 +171,26 @@ export async function runUpdate(): Promise<void> {
|
|
|
170
171
|
const { isServiceInstalled } = await import("../service");
|
|
171
172
|
serviceWasInstalled = isServiceInstalled();
|
|
172
173
|
} catch { /* best-effort */ }
|
|
174
|
+
let trayWasInstalled = false;
|
|
175
|
+
let trayWasRunning = false;
|
|
176
|
+
if (process.platform === "win32") {
|
|
177
|
+
try {
|
|
178
|
+
const { getWindowsTrayStatus, startWindowsTray, stopWindowsTray } = await import("../tray/windows");
|
|
179
|
+
const tray = getWindowsTrayStatus();
|
|
180
|
+
const trayPlan = handoffWindowsTrayForUpdate(tray, {
|
|
181
|
+
stop: () => {
|
|
182
|
+
const stopped = stopWindowsTray();
|
|
183
|
+
return { exitStatus: 0, running: stopped.running };
|
|
184
|
+
},
|
|
185
|
+
start: () => startWindowsTray(),
|
|
186
|
+
});
|
|
187
|
+
trayWasInstalled = trayPlan.refreshAfterReplacement;
|
|
188
|
+
trayWasRunning = trayPlan.restoreOnFailure;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`⚠️ Could not stop the Windows tray; aborting before package replacement: ${error instanceof Error ? error.message : String(error)}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
173
194
|
|
|
174
195
|
// Capture listen target before stop clears runtime state (same contract as GUI update worker).
|
|
175
196
|
// Prefer a live runtime record; a stale crashed leftover must not override config.port.
|
|
@@ -200,6 +221,12 @@ export async function runUpdate(): Promise<void> {
|
|
|
200
221
|
});
|
|
201
222
|
if (stopStdio === "pipe") logSpawnOutput("", stop);
|
|
202
223
|
if (stop.status !== 0 || readPid() || readRuntimePort()) {
|
|
224
|
+
if (trayWasRunning) {
|
|
225
|
+
try {
|
|
226
|
+
const { startWindowsTray } = await import("../tray/windows");
|
|
227
|
+
startWindowsTray();
|
|
228
|
+
} catch { /* preserve the proxy stop failure */ }
|
|
229
|
+
}
|
|
203
230
|
console.error("⚠️ Could not stop the running proxy; aborting the update. Run 'ocx stop' and retry.");
|
|
204
231
|
process.exit(1);
|
|
205
232
|
}
|
|
@@ -238,6 +265,16 @@ export async function runUpdate(): Promise<void> {
|
|
|
238
265
|
} catch (e) {
|
|
239
266
|
console.warn(`⚠️ Shim repair skipped: ${e instanceof Error ? e.message : e}`);
|
|
240
267
|
}
|
|
268
|
+
if (trayWasInstalled) {
|
|
269
|
+
const trayArgs = [process.argv[1], ...planWindowsTrayUpdate({ installed: trayWasInstalled, running: trayWasRunning }).installArgs];
|
|
270
|
+
const tray = spawnSync(process.execPath, trayArgs, { stdio: "inherit", windowsHide: true });
|
|
271
|
+
if (tray.status === 0) {
|
|
272
|
+
console.log("🔧 Refreshed Windows tray startup paths.");
|
|
273
|
+
} else {
|
|
274
|
+
console.warn("⚠️ Windows tray refresh failed. Run 'ocx tray install'.");
|
|
275
|
+
if (trayWasRunning) spawnSync(process.execPath, [process.argv[1], "tray", "start"], { stdio: "ignore", windowsHide: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
241
278
|
// The stop above unloaded any managed service; reinstall it with the NEW files
|
|
242
279
|
// (spawn the fresh cli.ts so updated code writes the baked paths) so a
|
|
243
280
|
// launchd/schtasks/systemd user isn't left with the background proxy down.
|
|
@@ -286,6 +323,12 @@ export async function runUpdate(): Promise<void> {
|
|
|
286
323
|
console.log("Restart the proxy: ocx start");
|
|
287
324
|
}
|
|
288
325
|
} else {
|
|
326
|
+
if (trayWasRunning) {
|
|
327
|
+
try {
|
|
328
|
+
const { startWindowsTray } = await import("../tray/windows");
|
|
329
|
+
startWindowsTray();
|
|
330
|
+
} catch { /* keep the primary update failure */ }
|
|
331
|
+
}
|
|
289
332
|
console.error(`\n⚠️ Update failed (${bin} exit ${r.status ?? "?"}). Try manually: ${bin} ${cmdArgs.join(" ")}`);
|
|
290
333
|
process.exit(1);
|
|
291
334
|
}
|
package/src/update/job.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
updateCommandStr,
|
|
21
21
|
} from "./index";
|
|
22
22
|
import { isNewer } from "./notify";
|
|
23
|
+
import { handoffWindowsTrayForUpdate, planWindowsTrayUpdate } from "./tray-update-plan.mjs";
|
|
23
24
|
|
|
24
25
|
const RELEASE_NOTES_URL = "https://github.com/lidge-jun/opencodex/releases/latest";
|
|
25
26
|
const UPDATE_JOB_FILENAME = "update-job.json";
|
|
@@ -474,6 +475,8 @@ export async function runGuiUpdateWorker(jobId: string, channel: Channel, restar
|
|
|
474
475
|
port: runtimeTrusted ? rt.port : configPort,
|
|
475
476
|
hostname: (runtimeTrusted ? rt.hostname : undefined) ?? preUpdateConfig.hostname ?? "127.0.0.1",
|
|
476
477
|
};
|
|
478
|
+
let trayWasInstalled = false;
|
|
479
|
+
let trayWasRunning = false;
|
|
477
480
|
if (!job) {
|
|
478
481
|
job = {
|
|
479
482
|
id: jobId,
|
|
@@ -517,6 +520,28 @@ export async function runGuiUpdateWorker(jobId: string, channel: Channel, restar
|
|
|
517
520
|
command: cmd.display,
|
|
518
521
|
}, integrityLine);
|
|
519
522
|
|
|
523
|
+
if (process.platform === "win32") {
|
|
524
|
+
try {
|
|
525
|
+
const { getWindowsTrayStatus, startWindowsTray, stopWindowsTray } = await import("../tray/windows");
|
|
526
|
+
const tray = getWindowsTrayStatus();
|
|
527
|
+
const trayPlan = handoffWindowsTrayForUpdate(tray, {
|
|
528
|
+
stop: () => {
|
|
529
|
+
const stopped = stopWindowsTray();
|
|
530
|
+
return { exitStatus: 0, running: stopped.running };
|
|
531
|
+
},
|
|
532
|
+
start: () => startWindowsTray(),
|
|
533
|
+
});
|
|
534
|
+
trayWasInstalled = trayPlan.refreshAfterReplacement;
|
|
535
|
+
trayWasRunning = trayPlan.restoreOnFailure;
|
|
536
|
+
} catch (error) {
|
|
537
|
+
updateJob(job, {
|
|
538
|
+
status: "failed",
|
|
539
|
+
error: `Could not stop the Windows tray; aborting before package replacement: ${error instanceof Error ? error.message : String(error)}`,
|
|
540
|
+
});
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
520
545
|
/* [Decision Log]
|
|
521
546
|
- 목적: GUI 요청 처리 프로세스가 자신이 실행 중인 패키지를 직접 덮어쓰지 않도록 업데이트를 별도 worker에서 수행한다.
|
|
522
547
|
- 대안 분석: (1) 서버에서 runUpdate 직접 호출: process.exit/stdio/실행 파일 교체 위험. (2) GUI에서 CLI 명령 안내만 제공: 자동 업데이트 UX 부족. (3) 숨은 worker가 Node launcher/Bun 전역 명령을 실행: 상태 추적과 안전한 재시작이 가능.
|
|
@@ -524,6 +549,12 @@ export async function runGuiUpdateWorker(jobId: string, channel: Channel, restar
|
|
|
524
549
|
*/
|
|
525
550
|
const result = runLoggedCommand(job, cmd.bin, cmd.args, UPDATE_TIMEOUT_MS);
|
|
526
551
|
if (result.status !== 0) {
|
|
552
|
+
if (trayWasRunning) {
|
|
553
|
+
try {
|
|
554
|
+
const { startWindowsTray } = await import("../tray/windows");
|
|
555
|
+
startWindowsTray();
|
|
556
|
+
} catch { /* retain the primary update failure */ }
|
|
557
|
+
}
|
|
527
558
|
updateJob(job, {
|
|
528
559
|
status: "failed",
|
|
529
560
|
exitCode: result.status,
|
|
@@ -533,6 +564,15 @@ export async function runGuiUpdateWorker(jobId: string, channel: Channel, restar
|
|
|
533
564
|
return;
|
|
534
565
|
}
|
|
535
566
|
|
|
567
|
+
if (trayWasInstalled) {
|
|
568
|
+
const trayArgs = [process.argv[1], ...planWindowsTrayUpdate({ installed: trayWasInstalled, running: trayWasRunning }).installArgs];
|
|
569
|
+
const tray = runLoggedCommand(job, process.execPath, trayArgs, 20_000);
|
|
570
|
+
if (tray.status !== 0) {
|
|
571
|
+
updateJob(job, {}, "Windows tray refresh failed; run 'ocx tray install'.");
|
|
572
|
+
if (trayWasRunning) runLoggedCommand(job, process.execPath, [process.argv[1], "tray", "start"], 15_000);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
536
576
|
if (restart) {
|
|
537
577
|
job = updateJob(job, { status: "restarting" }, "Update installed. Restarting proxy...");
|
|
538
578
|
await restartAfterUpdate(job, captured);
|
|
@@ -543,6 +583,12 @@ export async function runGuiUpdateWorker(jobId: string, channel: Channel, restar
|
|
|
543
583
|
|
|
544
584
|
updateJob(job, { status: "succeeded", restarted: false }, "Update installed. Restart the proxy to use the new version.");
|
|
545
585
|
} catch (err) {
|
|
586
|
+
if (trayWasRunning) {
|
|
587
|
+
try {
|
|
588
|
+
const { startWindowsTray } = await import("../tray/windows");
|
|
589
|
+
startWindowsTray();
|
|
590
|
+
} catch { /* retain the primary worker failure */ }
|
|
591
|
+
}
|
|
546
592
|
updateJob(job, {
|
|
547
593
|
status: "failed",
|
|
548
594
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface WindowsTrayUpdatePlan {
|
|
2
|
+
installed: boolean;
|
|
3
|
+
running: boolean;
|
|
4
|
+
stopBeforeReplacement: boolean;
|
|
5
|
+
restoreOnFailure: boolean;
|
|
6
|
+
refreshAfterReplacement: boolean;
|
|
7
|
+
installArgs: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export declare function planWindowsTrayUpdate(status: { installed?: boolean; running?: boolean }): WindowsTrayUpdatePlan;
|
|
11
|
+
export declare function windowsTrayStopConfirmed(exitStatus: number | null, stillRunning: boolean): boolean;
|
|
12
|
+
export declare function handoffWindowsTrayForUpdate(
|
|
13
|
+
status: { installed?: boolean; running?: boolean },
|
|
14
|
+
io: {
|
|
15
|
+
stop: () => { exitStatus: number | null; running: boolean };
|
|
16
|
+
start: () => unknown;
|
|
17
|
+
},
|
|
18
|
+
): WindowsTrayUpdatePlan;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared, side-effect-free contract for preserving the Windows tray across all
|
|
3
|
+
* updater entry points (npm launcher, CLI updater, and GUI worker).
|
|
4
|
+
*/
|
|
5
|
+
export function planWindowsTrayUpdate(status) {
|
|
6
|
+
const installed = status?.installed === true;
|
|
7
|
+
const running = status?.running === true;
|
|
8
|
+
return {
|
|
9
|
+
installed,
|
|
10
|
+
running,
|
|
11
|
+
stopBeforeReplacement: running,
|
|
12
|
+
restoreOnFailure: running,
|
|
13
|
+
refreshAfterReplacement: installed,
|
|
14
|
+
installArgs: ["tray", "install", ...(!running ? ["--no-start"] : [])],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function windowsTrayStopConfirmed(exitStatus, stillRunning) {
|
|
19
|
+
return exitStatus === 0 && !stillRunning;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Stop before replacement and restore best-effort when the handoff cannot be confirmed. */
|
|
23
|
+
export function handoffWindowsTrayForUpdate(status, io) {
|
|
24
|
+
const plan = planWindowsTrayUpdate(status);
|
|
25
|
+
if (!plan.stopBeforeReplacement) return plan;
|
|
26
|
+
try {
|
|
27
|
+
const stopped = io.stop();
|
|
28
|
+
if (!windowsTrayStopConfirmed(stopped.exitStatus, stopped.running)) {
|
|
29
|
+
throw new Error("the tray still reports running after shutdown");
|
|
30
|
+
}
|
|
31
|
+
return plan;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (plan.restoreOnFailure) {
|
|
34
|
+
try { io.start(); } catch { /* preserve the handoff failure */ }
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/usage/cost.ts
CHANGED
|
Binary file
|
|
@@ -38,6 +38,10 @@ const KIMI_K27_CODE_HIGHSPEED: Cost4 = { input: 1.9, output: 8, cacheRead: 0.38,
|
|
|
38
38
|
const KIMI_K26: Cost4 = { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0.95 };
|
|
39
39
|
const KIMI_K25: Cost4 = { input: 0.6, output: 3, cacheRead: 0.1, cacheWrite: 0.6 };
|
|
40
40
|
const QWEN38_ROUTEWAY_TEMPORARY: Cost4 = { input: 1.5, output: 5, cacheRead: 0.15, cacheWrite: 0 };
|
|
41
|
+
// Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate.
|
|
42
|
+
const CLAUDE_SONNET_46: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
43
|
+
const CLAUDE_OPUS_46: Cost4 = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
44
|
+
const ANTHROPIC_PRICING = "https://platform.claude.com/docs/en/about-claude/pricing (official; 5m cache-write tier)";
|
|
41
45
|
|
|
42
46
|
const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
|
|
43
47
|
const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
|
|
@@ -63,6 +67,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
63
67
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro", cost4: GEMINI_31_PRO, source: `collapsed base ID ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified" },
|
|
64
68
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
65
69
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-high", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
70
|
+
{ provider: "google-antigravity", modelId: "gemini-pro-agent", cost4: GEMINI_31_PRO, source: `wire id for gemini-3.1-pro high ${GEMINI_PRICING}`, verifiedAt: "2026-07-23", status: "verified-derived" },
|
|
66
71
|
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-low", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
67
72
|
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-medium", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
68
73
|
{ provider: "google-antigravity", modelId: "gemini-3.6-flash-high", cost4: GEMINI_36_FLASH, source: `derived: gemini-3.6-flash ${GEMINI_PRICING}`, verifiedAt: "2026-07-22", status: "verified-derived" },
|
|
@@ -76,8 +81,10 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
76
81
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
|
|
77
82
|
// Antigravity-bundled third-party models — derived from the underlying vendor's
|
|
78
83
|
// official API price (Antigravity itself bills via subscription quota).
|
|
79
|
-
{ provider: "google-antigravity", modelId: "claude-sonnet-4-6", cost4:
|
|
80
|
-
{ provider: "google-antigravity", modelId: "claude-opus-4-6-thinking", cost4:
|
|
84
|
+
{ provider: "google-antigravity", modelId: "claude-sonnet-4-6", cost4: CLAUDE_SONNET_46, source: `anthropic official Claude Sonnet 4.6 ${ANTHROPIC_PRICING}`, verifiedAt: "2026-07-23", status: "verified" },
|
|
85
|
+
{ provider: "google-antigravity", modelId: "claude-opus-4-6-thinking", cost4: CLAUDE_OPUS_46, source: `anthropic official Claude Opus 4.6 ${ANTHROPIC_PRICING}`, verifiedAt: "2026-07-23", status: "verified" },
|
|
86
|
+
// Alias without the Antigravity "-thinking" suffix (if logs/UI ever surface it).
|
|
87
|
+
{ provider: "google-antigravity", modelId: "claude-opus-4-6", cost4: CLAUDE_OPUS_46, source: `anthropic official Claude Opus 4.6 ${ANTHROPIC_PRICING}`, verifiedAt: "2026-07-23", status: "verified" },
|
|
81
88
|
{ provider: "google-antigravity", modelId: "gpt-oss-120b-medium", cost4: { input: 0.03, output: 0.15, cacheRead: 0, cacheWrite: 0 }, source: "derived: gpt-oss-120b open-weights — OpenRouter advertised lowest https://openrouter.ai/openai/gpt-oss-120b/providers", verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
82
89
|
// Kimi / Moonshot — official price tables are now published (2026-07-20 re-check;
|
|
83
90
|
// previously empty). kimi = Kimi Code OAuth surface, moonshot = CN key surface,
|
package/src/usage/summary.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { baseProviderLabel } from "../providers/label";
|
|
2
|
+
import { canonicalAntigravityUsageModel } from "../providers/antigravity-models";
|
|
2
3
|
import { usageDisplayTotalTokens } from "./totals";
|
|
3
4
|
import type { PersistedUsageEntry, UsageStatus } from "./log";
|
|
4
5
|
import { estimateComboCost, estimateRequestCost } from "./cost";
|
|
@@ -160,13 +161,47 @@ interface UsageAttribution {
|
|
|
160
161
|
totalTokens?: number;
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Usage row identity for model breakdowns.
|
|
167
|
+
* Google Antigravity collapses wire/compat/suffix ids to picker/call base models so
|
|
168
|
+
* historical effort-variant logs merge with current base-model invocations.
|
|
169
|
+
*/
|
|
170
|
+
function usageModelIdentity(
|
|
171
|
+
provider: string,
|
|
172
|
+
model: string,
|
|
173
|
+
resolvedModel?: string,
|
|
174
|
+
): { model: string; resolvedModel?: string } {
|
|
175
|
+
if (baseProviderLabel(provider) !== "google-antigravity") {
|
|
176
|
+
return resolvedModel ? { model, resolvedModel } : { model };
|
|
177
|
+
}
|
|
178
|
+
const fromModel = canonicalAntigravityUsageModel(model);
|
|
179
|
+
const fromResolved = resolvedModel
|
|
180
|
+
? canonicalAntigravityUsageModel(resolvedModel)
|
|
181
|
+
: undefined;
|
|
182
|
+
// Prefer an explicit base mapping from model; if model is unknown but resolved maps
|
|
183
|
+
// to a known base, use that (covers base call + upstream wire resolvedModel pairs).
|
|
184
|
+
const canonical = fromModel !== model
|
|
185
|
+
? fromModel
|
|
186
|
+
: (fromResolved && fromResolved !== resolvedModel ? fromResolved : fromModel);
|
|
187
|
+
return { model: canonical };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function usageModelKey(providerKey: string, model: string): string {
|
|
191
|
+
return `${providerKey}/${model}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function antigravityUsageModel(provider: string, model: string): string {
|
|
195
|
+
if (baseProviderLabel(provider) !== "google-antigravity") return model;
|
|
196
|
+
return canonicalAntigravityUsageModel(model);
|
|
197
|
+
}
|
|
198
|
+
|
|
163
199
|
function usageAttributions(entry: PersistedUsageEntry): UsageAttribution[] {
|
|
164
200
|
if (!entry.attempts?.length) {
|
|
165
201
|
return [{
|
|
166
202
|
requestId: entry.requestId,
|
|
167
203
|
provider: entry.provider,
|
|
168
|
-
|
|
169
|
-
...(entry.resolvedModel ? { resolvedModel: entry.resolvedModel } : {}),
|
|
204
|
+
...usageModelIdentity(entry.provider, entry.model, entry.resolvedModel),
|
|
170
205
|
usageStatus: entry.usageStatus,
|
|
171
206
|
...(entry.usage ? { usage: entry.usage } : {}),
|
|
172
207
|
...(entry.totalTokens !== undefined ? { totalTokens: entry.totalTokens } : {}),
|
|
@@ -175,7 +210,7 @@ function usageAttributions(entry: PersistedUsageEntry): UsageAttribution[] {
|
|
|
175
210
|
return entry.attempts.map(attempt => ({
|
|
176
211
|
requestId: entry.requestId,
|
|
177
212
|
provider: attempt.provider,
|
|
178
|
-
|
|
213
|
+
...usageModelIdentity(attempt.provider, attempt.model),
|
|
179
214
|
usageStatus: attempt.usageStatus,
|
|
180
215
|
...(attempt.usage ? { usage: attempt.usage } : {}),
|
|
181
216
|
...(attempt.totalTokens !== undefined ? { totalTokens: attempt.totalTokens } : {}),
|
|
@@ -263,7 +298,7 @@ function buildDayGrid(range: UsageRange, since: number | null, now: number, entr
|
|
|
263
298
|
let models = dayModels.get(dayKey);
|
|
264
299
|
if (!models) { models = new Map(); dayModels.set(dayKey, models); }
|
|
265
300
|
const providerKey = baseProviderLabel(attribution.provider);
|
|
266
|
-
const mKey =
|
|
301
|
+
const mKey = usageModelKey(providerKey, attribution.model);
|
|
267
302
|
let m = models.get(mKey);
|
|
268
303
|
if (!m) {
|
|
269
304
|
m = { model: attribution.model, provider: providerKey, requests: 0, attemptCount: 0, totalTokens: 0 };
|
|
@@ -310,7 +345,7 @@ function buildModels(entries: PersistedUsageEntry[], totalTokens: number): Usage
|
|
|
310
345
|
for (const attribution of usageAttributions(entry)) {
|
|
311
346
|
const providerKey = baseProviderLabel(attribution.provider);
|
|
312
347
|
// resolvedModel is a routing detail, not a row identity.
|
|
313
|
-
const key =
|
|
348
|
+
const key = usageModelKey(providerKey, attribution.model);
|
|
314
349
|
let model = byKey.get(key);
|
|
315
350
|
if (!model) {
|
|
316
351
|
model = {
|
|
@@ -363,14 +398,14 @@ function buildModels(entries: PersistedUsageEntry[], totalTokens: number): Usage
|
|
|
363
398
|
// Combo: attribute each attempt's cost to its own model
|
|
364
399
|
for (const attemptEst of estimate.attempts) {
|
|
365
400
|
const aProviderKey = baseProviderLabel(attemptEst.provider);
|
|
366
|
-
const aKey =
|
|
401
|
+
const aKey = usageModelKey(aProviderKey, antigravityUsageModel(attemptEst.provider, attemptEst.model));
|
|
367
402
|
const m = byKey.get(aKey);
|
|
368
403
|
if (m) m.estimatedCostUsd = (m.estimatedCostUsd ?? 0) + attemptEst.cost.total;
|
|
369
404
|
}
|
|
370
405
|
} else {
|
|
371
406
|
// Single-target: attribute to the entry's model
|
|
372
407
|
const providerKey = baseProviderLabel(entry.provider);
|
|
373
|
-
const key =
|
|
408
|
+
const key = usageModelKey(providerKey, antigravityUsageModel(entry.provider, entry.model));
|
|
374
409
|
const m = byKey.get(key);
|
|
375
410
|
if (m) m.estimatedCostUsd = (m.estimatedCostUsd ?? 0) + estimate.cost.total;
|
|
376
411
|
}
|