@songsid/agend 2.3.2-beta.3 → 2.3.2-beta.5
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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/agent-cli.js +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +5 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +45 -0
- package/dist/backend/kiro-identity.js +74 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +36 -1
- package/dist/backend/kiro.js +131 -6
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +7 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +7 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +5 -0
- package/dist/classic-channel-manager.js +14 -1
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +22 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +31 -1
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.js +22 -7
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +20 -6
- package/dist/dashboard-permissions.js +58 -9
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-manager.d.ts +51 -1
- package/dist/fleet-manager.js +416 -32
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +24 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +6 -3
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +12 -5
- package/dist/instance-config-impact.js +10 -5
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +17 -0
- package/dist/instance-lifecycle.js +32 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +49 -0
- package/dist/kiro-migrate.js +105 -0
- package/dist/kiro-migrate.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +72 -8
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -2
- package/dist/needs-you.js +6 -0
- package/dist/needs-you.js.map +1 -1
- package/dist/notice-delivery.d.ts +15 -0
- package/dist/notice-delivery.js +37 -0
- package/dist/notice-delivery.js.map +1 -0
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +24 -0
- package/dist/outbound-schemas.js +21 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +1 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +4 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-triggers.d.ts +2 -0
- package/dist/reply-triggers.js +3 -0
- package/dist/reply-triggers.js.map +1 -0
- package/dist/settings-api.js +27 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +17 -14
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +7 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-instance-patch.js +4 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +7 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +126 -6
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +4 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +12 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +6 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +26 -2
- package/dist/transcript-sources.js +87 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/turn-reply-guard.d.ts +1 -1
- package/dist/turn-reply-guard.js +1 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +19 -8
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/fleet-office-editor.js +10 -6
- package/dist/ui/fleet-office-layout.js +34 -28
- package/dist/ui/fleet-office-model.js +6 -3
- package/dist/ui/fleet-office-overview.js +2 -2
- package/dist/ui/fleet-office-pack.d.ts +3 -1
- package/dist/ui/fleet-office-pack.js +58 -7
- package/dist/ui/fleet-office-skin.js +13 -4
- package/dist/ui/fleet-office-store.js +27 -1
- package/dist/ui/fleet-office-view.js +5 -4
- package/dist/ui/fleet-office-zones.js +155 -6
- package/dist/ui/fleet-office.js +169 -41
- package/dist/ui/fleet-usage.js +19 -15
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/panel-settings.js +32 -10
- package/dist/ui/settings-dialogs.js +8 -1
- package/dist/ui/settings-model.js +14 -6
- package/dist/ui/settings-registry.js +4 -0
- package/dist/ui/settings-strings.js +14 -0
- package/dist/ui/shared/app-i18n.js +4 -2
- package/dist/ui/shared/app-shell.js +14 -6
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +18 -2
- package/dist/ui/shared/side-panel.js +95 -0
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +4 -0
- package/dist/web-api.js +110 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-office-acts.d.ts +173 -0
- package/dist/web-office-acts.js +285 -0
- package/dist/web-office-acts.js.map +1 -0
- package/dist/web-office-skins.d.ts +1 -1
- package/dist/web-office-skins.js +1 -1
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/package.json +2 -1
- package/templates/windows-shortcuts.ps1 +18 -7
|
@@ -16,7 +16,8 @@ import { onPrefsChanged, savePref } from "./app-prefs.js";
|
|
|
16
16
|
import { applyPageTheme, pageNow, resolvedMode, themesStore } from "./app-themes.js";
|
|
17
17
|
import { ErrorState, Skeleton } from "./ui-states.js";
|
|
18
18
|
import { InstanceNav, RosterNav, namesStore, setNamesPrimary } from "./instance-nav.js";
|
|
19
|
-
import {
|
|
19
|
+
import { usageRailStore } from "./header-tools.js";
|
|
20
|
+
import { RightRail, sidePanelStore } from "./side-panel.js";
|
|
20
21
|
import { OwnerCssKeepBar } from "./owner-css.js";
|
|
21
22
|
import { AgentWidget, fleetBinds, sidebarWidget, sidebarWidgetStore } from "./agent-widget.js";
|
|
22
23
|
|
|
@@ -89,14 +90,17 @@ export function openSearch(preset = {}) {
|
|
|
89
90
|
}
|
|
90
91
|
/**
|
|
91
92
|
* Ctrl+K (⌘K) anywhere opens search; Ctrl+F (⌘F) in an agent's chat opens it narrowed to that agent — elsewhere the
|
|
92
|
-
* browser's own find stays. Not while another dialog is open (search, open already, has its input focused)
|
|
93
|
+
* browser's own find stays. Not while another dialog is open (search, open already, has its input focused); a docked
|
|
94
|
+
* side panel (#2585) is not one.
|
|
93
95
|
*/
|
|
94
96
|
export function handleSearchKey(e) {
|
|
95
97
|
if (!searchOffered(true)) return;
|
|
96
98
|
if (e.isComposing || e.altKey || e.shiftKey || !(isMacLike() ? e.metaKey : e.ctrlKey)) return;
|
|
97
99
|
const key = (e.key || "").toLowerCase();
|
|
98
100
|
if (key !== "k" && key !== "f") return;
|
|
99
|
-
|
|
101
|
+
// #2585: a docked side panel is not modal — the page beside it is in use, so search is too (a result that leaves
|
|
102
|
+
// the page asks first when the panel holds changes: side-panel.js).
|
|
103
|
+
if (typeof document !== "undefined" && document.querySelector("dialog[open]:not(.side-panel)")) return;
|
|
100
104
|
const route = navStore.get().route;
|
|
101
105
|
if (key === "f" && !(route && route.panel === "chat" && route.instance)) return;
|
|
102
106
|
e.preventDefault();
|
|
@@ -127,7 +131,7 @@ export const sidebarWidthStore = createStore({ width: null });
|
|
|
127
131
|
function mainBudget() {
|
|
128
132
|
const vw = viewportWidth();
|
|
129
133
|
if (vw < 1200 || typeof document === "undefined") return vw;
|
|
130
|
-
const rail = document.querySelector(".u-rail");
|
|
134
|
+
const rail = document.querySelector(".shell > .u-rail, .shell > .side-panel"); // #2585: a docked panel in its place
|
|
131
135
|
if (!rail) return vw;
|
|
132
136
|
const w = Math.round(rail.getBoundingClientRect ? rail.getBoundingClientRect().width : 0) || RAIL_W;
|
|
133
137
|
return vw - w;
|
|
@@ -181,11 +185,15 @@ function SidebarResize() {
|
|
|
181
185
|
// once the page has drawn its change.
|
|
182
186
|
const again = () => afterPaint(applyKeptSidebarWidth);
|
|
183
187
|
const offRail = usageRailStore.subscribe(again);
|
|
188
|
+
// #2585: a docked panel in the rail's place (it is published on every render of its editor: only its coming and
|
|
189
|
+
// going changes the room).
|
|
190
|
+
let docked = !!sidePanelStore.get().panel;
|
|
191
|
+
const offPanel = sidePanelStore.subscribe(() => { const now = !!sidePanelStore.get().panel; if (now !== docked) { docked = now; again(); } });
|
|
184
192
|
const offNav = navStore.subscribe(again);
|
|
185
193
|
const w = win();
|
|
186
194
|
if (w && typeof w.addEventListener === "function") w.addEventListener("resize", applyKeptSidebarWidth);
|
|
187
195
|
return () => {
|
|
188
|
-
offRail(); offNav();
|
|
196
|
+
offRail(); offPanel(); offNav();
|
|
189
197
|
if (w && typeof w.removeEventListener === "function") w.removeEventListener("resize", applyKeptSidebarWidth);
|
|
190
198
|
if (activeDrag) activeDrag.end(false); // unmounted mid-drag: nothing kept, nothing left behind
|
|
191
199
|
};
|
|
@@ -787,7 +795,7 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
|
|
|
787
795
|
${viewOnly || guest || !shell.banner ? null : html`<${shell.banner} />`}
|
|
788
796
|
<${Outlet} route=${nav.route} seq=${nav.seq} panels=${panels} />
|
|
789
797
|
</main>
|
|
790
|
-
${guest ? null : html`<${
|
|
798
|
+
${guest ? null : html`<${RightRail} route=${nav.route} />`}
|
|
791
799
|
<${BottomTabs} route=${nav.route} viewOnly=${viewOnly} />
|
|
792
800
|
${shell.dialog ? html`<${shell.dialog.Component} ...${shell.dialog.props} onClose=${closeDialog} />` : null}
|
|
793
801
|
${viewOnly || guest || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
// tasks — the task board changed: the card numbers a write touched (SSE only: the board reads again when it is shown).
|
|
37
37
|
// offices — the stored offices changed ({ rev }; SSE only: the Office view reads /ui/offices again).
|
|
38
38
|
// office_skins — a skin pack was uploaded or deleted ({ rev }; SSE only: the Office view reads /ui/office-skins again).
|
|
39
|
-
|
|
39
|
+
// office_acts — the agents' live acts in the Office view changed ({ acts }, #2578; a poll page reads /ui/office-acts).
|
|
40
|
+
const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks", "offices", "office_skins", "office_acts"];
|
|
40
41
|
const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
|
|
41
42
|
const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
|
|
42
43
|
const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -1348,6 +1348,18 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
|
|
|
1348
1348
|
@media (prefers-reduced-motion: no-preference) { .u-crow { transition: background-color 0.15s var(--ease); } }
|
|
1349
1349
|
.u-rail-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); }
|
|
1350
1350
|
@media (max-width: 1199px) { .u-rail { display: none; } }
|
|
1351
|
+
/* #2585: the docked side panel (side-panel.js), in the usage rail's place beside the page — a non-modal dialog, so the
|
|
1352
|
+
shared dialog's head, body and actions, laid out as a full-height column. Below the rail's width it is never drawn
|
|
1353
|
+
(the editor is the centred Dialog there). */
|
|
1354
|
+
.dlg.side-panel { position: static; inset: auto; margin: 0; width: 400px; max-width: 400px; height: auto; max-height: none; flex-shrink: 0;
|
|
1355
|
+
display: flex; flex-direction: column; min-height: 0; border-radius: 0; box-shadow: none; border-left: 1px solid var(--border); background: var(--bg); color: var(--text); }
|
|
1356
|
+
.side-panel .dlg-box { flex: 1; min-height: 0; max-height: none; }
|
|
1357
|
+
.side-panel .dlg-head { padding: var(--s-3) var(--s-3) var(--s-3) var(--s-5); border-bottom: 1px solid var(--border); }
|
|
1358
|
+
.side-panel .dlg-body { flex: 1; min-height: 0; padding-top: var(--s-4); }
|
|
1359
|
+
.side-panel .dlg-actions { padding: var(--s-3) var(--s-5); border-top: 1px solid var(--border); }
|
|
1360
|
+
@media (max-width: 1199px) { .dlg.side-panel { display: none; } }
|
|
1361
|
+
/* A toast (Offices saved.) beside a docked panel, not over its Cancel and Save. */
|
|
1362
|
+
@media (min-width: 1200px) { .shell:has(> .side-panel) .toasts { right: calc(400px + var(--s-6)); } }
|
|
1351
1363
|
@media (max-width: 899px) {
|
|
1352
1364
|
.hide-narrow { display: none; }
|
|
1353
1365
|
.v-id { padding-left: var(--s-3); padding-right: var(--s-3); }
|
|
@@ -1731,7 +1743,7 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
|
|
|
1731
1743
|
.oc-office > .oc-bar { margin: 0; padding: var(--s-3) var(--s-5) var(--s-2); }
|
|
1732
1744
|
.oc-office > .oc-legend { margin: 0 var(--s-5) var(--s-2); }
|
|
1733
1745
|
.oo { flex: 1 0 auto; display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; width: 100%; }
|
|
1734
|
-
.oo > :not(.oo-stage) { margin-left: var(--s-5); margin-right: var(--s-5); }
|
|
1746
|
+
.oo > :not(.oo-stage):not(dialog) { margin-left: var(--s-5); margin-right: var(--s-5); } /* not the editors' dialogs: they centre themselves (#2585) */
|
|
1735
1747
|
.oo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
|
|
1736
1748
|
.oo-pick { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-2); }
|
|
1737
1749
|
.oo-pick .tb-select { max-width: 200px; }
|
|
@@ -1839,13 +1851,16 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
|
|
|
1839
1851
|
.ol-bar { position: absolute; left: 0; right: 0; bottom: 0; z-index: 8; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); padding: var(--s-2);
|
|
1840
1852
|
background: var(--surface); border-top: 1px solid var(--border); }
|
|
1841
1853
|
.ol-actions { display: inline-flex; gap: var(--s-2); margin-left: auto; }
|
|
1854
|
+
/* #2585: Edit layout's controls docked in the side panel (a wide desk): one column, the waypoint's own row wrapping. */
|
|
1855
|
+
.ol-side { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); }
|
|
1856
|
+
.ol-side .ol-corridors { display: flex; flex-wrap: wrap; gap: var(--s-2); }
|
|
1842
1857
|
.ol-corridors { display: inline-flex; flex-wrap: wrap; gap: 4px; }
|
|
1843
1858
|
.ol-msg { flex-basis: 100%; margin: 0; }
|
|
1844
1859
|
@media (max-width: 640px) {
|
|
1845
1860
|
/* A phone: the bars at the column's narrower padding; the stage at least most of a screen; no overview (as the canvas). */
|
|
1846
1861
|
.oc-office > .oc-bar { padding: var(--s-2) var(--s-3); }
|
|
1847
1862
|
.oc-office > .oc-legend { margin: 0 var(--s-3) var(--s-2); }
|
|
1848
|
-
.oo > :not(.oo-stage) { margin-left: var(--s-3); margin-right: var(--s-3); }
|
|
1863
|
+
.oo > :not(.oo-stage):not(dialog) { margin-left: var(--s-3); margin-right: var(--s-3); }
|
|
1849
1864
|
.oo-stage { min-height: max(300px, calc(100dvh - 300px)); }
|
|
1850
1865
|
.oo-pick .tb-select { max-width: 150px; }
|
|
1851
1866
|
.oo-mini { display: none; }
|
|
@@ -1921,6 +1936,7 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
|
|
|
1921
1936
|
.us-bar { display: block; width: 100%; height: 6px; margin: 4px 0; }
|
|
1922
1937
|
.us-bar-bg { fill: var(--surface-2); } .us-bar-fg { fill: var(--us-0); }
|
|
1923
1938
|
.us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
|
|
1939
|
+
.us-est { display: block; font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-3); }
|
|
1924
1940
|
.us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
|
|
1925
1941
|
.us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
|
|
1926
1942
|
.us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// #2585 (the user): settings-like editors docked on the right. On a screen wide enough for the usage rail (RAIL_MEDIA,
|
|
2
|
+
// #1673) an editor opens in the rail's place, beside the page — not modal: the page stays in view and in use (the
|
|
3
|
+
// Office re-fits beside it). While one is open the rail steps aside; it comes back as it was when the panel closes (its
|
|
4
|
+
// shown/hidden choice is never touched). Narrower, the same editor is the shared Dialog (centred; a phone's sheet).
|
|
5
|
+
//
|
|
6
|
+
// <${SidePanel} title=… onClose=… actions=… busy=…>…body…</${SidePanel}>: rendered = open, the owner unmounts it to
|
|
7
|
+
// close (like Dialog). Docked, the owner renders nothing itself: it publishes its title, body and actions to the shell's
|
|
8
|
+
// host (sidePanelStore) on every render, so the body keeps the owner's state and closures; the owner going (a close, a
|
|
9
|
+
// navigation) takes the panel with it. Esc in the panel and its × close it (not while `busy`); focus moves into it when
|
|
10
|
+
// it opens and goes back to what opened it when it closes. Docked, the body renders under the shell, not under its
|
|
11
|
+
// owner: a context provided above the owner does not reach it (pass what it needs as props, or read a store).
|
|
12
|
+
// `dirty`: the editor holds unsaved changes. Docked, the page beside it is in use (its links, search): leaving the page
|
|
13
|
+
// then closes the panel as Cancel does — and with changes, asks first (the app's leave guard; one at a time).
|
|
14
|
+
// `dock` false keeps it from docking (the Office in fullscreen, where the shell is out of view); `fallback`, when given,
|
|
15
|
+
// is what it is below the rail's width (or not docked) instead of the Dialog — Edit layout's bar over the canvas.
|
|
16
|
+
import { html, useEffect, useLayoutEffect, useRef } from "./app-html.js";
|
|
17
|
+
import { createStore, useStore } from "./app-store.js";
|
|
18
|
+
import { t } from "./app-i18n.js";
|
|
19
|
+
import { Icon } from "./ui-icons.js";
|
|
20
|
+
import { Dialog } from "./ui-dialog.js";
|
|
21
|
+
import { confirmDialog } from "./ui-confirm.js";
|
|
22
|
+
import { navigate, setLeaveGuard } from "./app-nav.js";
|
|
23
|
+
import { UsageRail, useRailWidth } from "./header-tools.js";
|
|
24
|
+
|
|
25
|
+
/** { panel: { id, title, label, body, actions, busy, onClose } | null } — the one docked panel. */
|
|
26
|
+
export const sidePanelStore = createStore({ panel: null });
|
|
27
|
+
let seq = 0;
|
|
28
|
+
const clear = (id) => { if (sidePanelStore.get().panel?.id === id) sidePanelStore.set({ panel: null }); };
|
|
29
|
+
|
|
30
|
+
/** An editor: docked on a wide screen, the shared Dialog (`wide`) elsewhere. */
|
|
31
|
+
export function SidePanel({ title, label, onClose, actions, busy = false, dirty = false, dock = true, fallback, children }) {
|
|
32
|
+
const docked = useRailWidth() && dock;
|
|
33
|
+
const id = useRef(0);
|
|
34
|
+
if (!id.current) id.current = ++seq;
|
|
35
|
+
const opener = useRef(undefined);
|
|
36
|
+
if (opener.current === undefined) opener.current = typeof document !== "undefined" ? document.activeElement : null;
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
if (docked) sidePanelStore.set({ panel: { id: id.current, title, label, body: children, actions, busy, onClose } });
|
|
39
|
+
else clear(id.current);
|
|
40
|
+
});
|
|
41
|
+
useLayoutEffect(() => () => {
|
|
42
|
+
const host = typeof document !== "undefined" ? document.querySelector(".side-panel") : null;
|
|
43
|
+
const inside = !!host && (host.contains(document.activeElement) || document.activeElement === document.body);
|
|
44
|
+
clear(id.current);
|
|
45
|
+
// Focus back to what opened it — when it was in the panel (or nowhere): a person who moved on keeps their place.
|
|
46
|
+
const back = opener.current;
|
|
47
|
+
if ((inside || !host) && back && typeof back.focus === "function" && document.contains?.(back)) back.focus();
|
|
48
|
+
}, []);
|
|
49
|
+
// Leaving the page with changes in the docked panel: asked in the app's dialog; yes = Cancel, then on to there.
|
|
50
|
+
const live = useRef(null);
|
|
51
|
+
live.current = { title, onClose };
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!docked || !dirty) return undefined;
|
|
54
|
+
let asking = false;
|
|
55
|
+
return setLeaveGuard((_to, path) => {
|
|
56
|
+
if (!asking) {
|
|
57
|
+
asking = true;
|
|
58
|
+
confirmDialog({ message: t("app.sidePanelLeave", live.current.title), confirmLabel: t("app.sidePanelDiscard"), danger: true }).then((yes) => {
|
|
59
|
+
asking = false;
|
|
60
|
+
if (!yes) return;
|
|
61
|
+
live.current.onClose();
|
|
62
|
+
navigate(path, { force: true });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
});
|
|
67
|
+
}, [docked, dirty]);
|
|
68
|
+
if (docked) return null;
|
|
69
|
+
if (fallback !== undefined) return fallback;
|
|
70
|
+
return html`<${Dialog} title=${title} label=${label} onClose=${onClose} actions=${actions} busy=${busy} wide=${true}>${children}</${Dialog}>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The docked panel itself: a non-modal dialog in the rail's place. */
|
|
74
|
+
function SidePanelHost({ panel }) {
|
|
75
|
+
const box = useRef(null);
|
|
76
|
+
// Into the panel as it opens (its box: no ring; Tab moves on), once per panel.
|
|
77
|
+
useLayoutEffect(() => { if (box.current && typeof box.current.focus === "function") box.current.focus({ preventScroll: true }); }, [panel.id]);
|
|
78
|
+
const close = () => { if (!panel.busy) panel.onClose(); };
|
|
79
|
+
const onKeyDown = (e) => { if (e.key === "Escape" && !e.defaultPrevented) { e.preventDefault(); close(); } };
|
|
80
|
+
return html`<dialog open data-trust="dialog" class="dlg side-panel" aria-label=${panel.label || panel.title} onKeyDown=${onKeyDown}>
|
|
81
|
+
<div class="dlg-box" tabindex="-1" ref=${box}>
|
|
82
|
+
<div class="dlg-head"><h2 class="dlg-title">${panel.title}</h2>
|
|
83
|
+
<button type="button" class="icon-btn dlg-x" aria-label=${t("app.close")} title=${t("app.close")} onClick=${close}><${Icon} name="close" /></button></div>
|
|
84
|
+
<div class="dlg-body">${panel.body}</div>
|
|
85
|
+
${panel.actions ? html`<div class="dlg-actions">${panel.actions}</div>` : null}
|
|
86
|
+
</div>
|
|
87
|
+
</dialog>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The shell's right-hand column: a docked panel when one is open (wide screens), else the usage rail. */
|
|
91
|
+
export function RightRail({ route }) {
|
|
92
|
+
const { panel } = useStore(sidePanelStore);
|
|
93
|
+
const wide = useRailWidth();
|
|
94
|
+
return panel && wide ? html`<${SidePanelHost} panel=${panel} />` : html`<${UsageRail} route=${route} />`;
|
|
95
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Shared browser controls. No terminal relay, provider output or secrets in diagnostics. */
|
|
2
|
+
const NAMES = { "kiro-cli": "Kiro", "claude-code": "Claude Code", codex: "Codex", antigravity: "Antigravity (console fallback)" };
|
|
3
|
+
const CHOICES = { builder_id: "Builder ID", google: "Google", github: "GitHub", organization: "Your organization", subscription: "Claude subscription", console: "Anthropic Console" };
|
|
4
|
+
const ERRORS = {
|
|
5
|
+
unsupported_prompt: "This CLI screen is not supported. Cancel and use the console fallback.",
|
|
6
|
+
cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
|
|
7
|
+
signin_cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
|
|
8
|
+
signin_action_stale: "That prompt changed or was already answered. Refresh its status; do not submit it again.",
|
|
9
|
+
signin_console_only: "This backend uses the explicit console fallback.",
|
|
10
|
+
backend_signin_local_only: "Backend sign-in is available only on this computer. Use the local setup page, or sign in later.",
|
|
11
|
+
signin_session_expired: "This setup session has ended. Reopen setup on the host.",
|
|
12
|
+
expired: "This login attempt expired. Start a new attempt.",
|
|
13
|
+
auth_unconfirmed: "The CLI exited, but credentials could not be verified. Retry or sign in later.",
|
|
14
|
+
provider_rejected: "The provider rejected this sign-in. Retry or sign in later.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function mountStructuredSignIns(root, { endpoint, fetcher = fetch, changed = () => {} } = {}) {
|
|
18
|
+
const doc = root.ownerDocument;
|
|
19
|
+
let view = null, busy = false, disposed = false, timer = null, generation = 0, message = "", rendered = "";
|
|
20
|
+
const el = (tag, text, parent = root) => { const node = doc.createElement(tag); if (text) node.textContent = text; parent.append(node); return node; };
|
|
21
|
+
const errorText = code => ERRORS[code] || "Could not complete sign-in. Check status, retry, or sign in later.";
|
|
22
|
+
const button = (parent, label, action) => { const b = el("button", label, parent); b.type = "button"; b.disabled = busy; b.onclick = action; return b; };
|
|
23
|
+
const request = async data => {
|
|
24
|
+
const response = await fetcher(endpoint, { method: data ? "POST" : "GET", cache: "no-store", headers: { "Content-Type": "application/json" }, ...(data ? { body: JSON.stringify(data) } : {}) });
|
|
25
|
+
const body = await response.json().catch(() => null);
|
|
26
|
+
if (!response.ok) throw new Error(body?.error || "signin_failed");
|
|
27
|
+
return body;
|
|
28
|
+
};
|
|
29
|
+
const accept = next => { view = next; root.dataset.ready = String(view.ready); changed(view); };
|
|
30
|
+
async function perform(data) {
|
|
31
|
+
if (busy || disposed) return;
|
|
32
|
+
busy = true; generation++; message = ""; render();
|
|
33
|
+
try { const next = await request(data); if (!disposed) accept(next); }
|
|
34
|
+
catch (error) { if (!disposed) message = errorText(error?.message); }
|
|
35
|
+
finally { busy = false; if (!disposed) render(); }
|
|
36
|
+
}
|
|
37
|
+
function render() {
|
|
38
|
+
const signature = JSON.stringify([view, busy, message]);
|
|
39
|
+
if (disposed || rendered === signature) return;
|
|
40
|
+
rendered = signature; root.replaceChildren();
|
|
41
|
+
el("h2", "Sign in your AI backends");
|
|
42
|
+
el("p", "Use the controls here; no terminal typing is needed for Kiro, Claude Code or Codex. You can sign in later from the dashboard.");
|
|
43
|
+
if (message) { const alert = el("p", message); alert.setAttribute("role", "alert"); }
|
|
44
|
+
if (!view) { button(root, "Check sign-ins", () => perform({ operation: "refresh" })); return; }
|
|
45
|
+
for (const backend of view.selected) {
|
|
46
|
+
const card = el("section"); card.dataset.backend = backend;
|
|
47
|
+
el("h3", NAMES[backend] || backend, card);
|
|
48
|
+
const state = view.states[backend], pending = view.pending.includes(backend), skipped = view.skipped.includes(backend);
|
|
49
|
+
el("p", skipped ? "Sign in later · remains in Needs you" : state === "valid" ? "Signed in ✓" : state === "not_installed" ? "Not installed on this host" : state === "console_only" ? "Use the explicit console fallback, or sign in later." : "Sign-in needed", card);
|
|
50
|
+
const login = view.login?.backend === backend ? view.login : null;
|
|
51
|
+
const active = login && login.phase !== "done" && !(login.phase === "failed" && login.reason);
|
|
52
|
+
if (active) {
|
|
53
|
+
el("p", login.phase === "waiting" || login.phase === "starting" ? "Waiting for the CLI…" : "Login: " + login.phase.replaceAll("_", " "), card);
|
|
54
|
+
if (login.phase === "method" && login.canAct) for (const choice of login.choices || []) {
|
|
55
|
+
if (CHOICES[choice]) button(card, CHOICES[choice], () => perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: "method", choice } }));
|
|
56
|
+
}
|
|
57
|
+
if (login.url) {
|
|
58
|
+
// The host already allowlists these; still never turn a non-HTTPS URL into a link.
|
|
59
|
+
let url; try { url = new URL(login.url); } catch {}
|
|
60
|
+
if (url?.protocol === "https:" && !url.username && !url.password) {
|
|
61
|
+
const link = el("a", "Open provider sign-in", card); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (login.code) { el("p", "Device code:", card); el("code", login.code, card); }
|
|
65
|
+
if (login.canAct && ["code", "start_url", "region"].includes(login.phase)) {
|
|
66
|
+
const label = el("label", { code: "Paste the returned code", start_url: "Organization Start URL", region: "AWS region" }[login.phase], card);
|
|
67
|
+
const input = el("input", null, label); input.type = login.phase === "code" ? "password" : "text"; input.autocomplete = "off"; input.spellcheck = false;
|
|
68
|
+
input.setAttribute("aria-label", label.textContent); input.disabled = busy;
|
|
69
|
+
button(card, "Submit " + login.phase.replaceAll("_", " "), () => { const value = input.value; input.value = ""; void perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: login.phase, value } }); });
|
|
70
|
+
}
|
|
71
|
+
if (["method", "code", "start_url", "region"].includes(login.phase) && !login.canAct) el("p", "Answer sent. Waiting for the next screen; it will not be sent twice.", card);
|
|
72
|
+
button(card, "Cancel sign-in", () => perform({ operation: "cancel" }));
|
|
73
|
+
} else {
|
|
74
|
+
if (login?.reason && login.reason !== "cancelled") el("p", errorText(login.reason), card);
|
|
75
|
+
const usable = !["antigravity"].includes(backend) && state !== "not_installed";
|
|
76
|
+
if (usable) button(card, state === "valid" ? "Sign in again" : "Sign in", () => {
|
|
77
|
+
const relogin = state === "valid";
|
|
78
|
+
if (relogin && !globalThis.confirm("Sign in again? Kiro will sign out its existing account first.")) return;
|
|
79
|
+
void perform({ operation: "start", backend, relogin });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (pending && !skipped) button(card, "Sign in later", () => perform({ operation: "skip", backend }));
|
|
83
|
+
}
|
|
84
|
+
button(root, "Check sign-ins", () => perform({ operation: "refresh" }));
|
|
85
|
+
if (view.ready) el("p", "You can continue setup.");
|
|
86
|
+
}
|
|
87
|
+
async function poll() {
|
|
88
|
+
if (disposed) return;
|
|
89
|
+
const before = generation;
|
|
90
|
+
if (!busy) try { const next = await request(); if (!disposed && before === generation) { accept(next); render(); } }
|
|
91
|
+
catch (error) { if (!disposed && before === generation) { message = errorText(error?.message); render(); } }
|
|
92
|
+
if (!disposed) timer = setTimeout(poll, 1000);
|
|
93
|
+
}
|
|
94
|
+
render(); void poll();
|
|
95
|
+
return {
|
|
96
|
+
addBackends(backends) {
|
|
97
|
+
if (view && Array.isArray(backends) && backends.every(b => NAMES[b]) && backends.some(b => !view.selected.includes(b)))
|
|
98
|
+
return perform({ operation: "select", backends: [...new Set([...view.selected, ...backends])] });
|
|
99
|
+
},
|
|
100
|
+
addBackend(backend) { if (view && NAMES[backend] && !view.selected.includes(backend)) return perform({ operation: "select", backends: [...view.selected, backend] }); },
|
|
101
|
+
dispose() { disposed = true; generation++; clearTimeout(timer); root.replaceChildren(); },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const setupRoot = typeof document === "undefined" ? null : document.getElementById("setupSignins");
|
|
106
|
+
if (setupRoot) {
|
|
107
|
+
const cards = mountStructuredSignIns(setupRoot, { endpoint: "setup/signins" });
|
|
108
|
+
const backend = document.getElementById("backend");
|
|
109
|
+
const select = () => cards.addBackend(backend?.value);
|
|
110
|
+
backend?.addEventListener("change", select);
|
|
111
|
+
const observer = new MutationObserver(select); if (backend) observer.observe(backend, { childList: true });
|
|
112
|
+
// Initial GET may finish after the environment list, so also observe our readiness result.
|
|
113
|
+
const ready = new MutationObserver(select); ready.observe(setupRoot, { attributes: true, attributeFilter: ["data-ready"] });
|
|
114
|
+
addEventListener("pagehide", () => { observer.disconnect(); ready.disconnect(); cards.dispose(); }, { once: true });
|
|
115
|
+
}
|
package/dist/usage-report.d.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* Honest by construction: an amount is "API-equivalent at list price (checked <date>) — not money spent"; an unpriced
|
|
8
8
|
* model's tokens are shown and its amount is not; Codex's cache writes are "not reported" (every CLI seen records 0),
|
|
9
9
|
* never priced as uncached; an instance whose backend AgEnD cannot measure is listed with its reason, never as $0.
|
|
10
|
+
* Kiro records no token counts: its rows are ESTIMATED from text size (kiro-usage.ts, design §10.2) and say so — on the
|
|
11
|
+
* row (`estimated`), on any total that includes one (`includes_estimates`, with the estimated part of the amount), in
|
|
12
|
+
* the API, on the page and in the CSV. An estimate never mixes silently into a measured number.
|
|
10
13
|
*/
|
|
11
14
|
import type { Ledger } from "./cache-ledger.js";
|
|
12
15
|
import { type UsageDays } from "./usage-rollup.js";
|
|
@@ -27,6 +30,12 @@ export declare const usageBasis: (checked?: string) => string;
|
|
|
27
30
|
* latest whole days and says from which date (`dailyFrom`); the rows and totals always cover the whole range.
|
|
28
31
|
*/
|
|
29
32
|
export declare const USAGE_DAILY_MAX = 20000;
|
|
33
|
+
/** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
|
|
34
|
+
export declare const ESTIMATED_BACKENDS: ReadonlySet<string>;
|
|
35
|
+
/** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
|
|
36
|
+
export declare const REPORTS_REASONING: ReadonlySet<string>;
|
|
37
|
+
/** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
|
|
38
|
+
export declare const WRITES_UNREPORTED: ReadonlySet<string>;
|
|
30
39
|
export interface UsageInstanceInput {
|
|
31
40
|
name: string;
|
|
32
41
|
/** Its backend now: what it is collected with from here on (each slice of its history carries its own). */
|
|
@@ -63,7 +72,10 @@ export interface UsageInput {
|
|
|
63
72
|
export interface UsageTotals {
|
|
64
73
|
/** Uncached input. */
|
|
65
74
|
input: number;
|
|
75
|
+
/** Output without its reasoning part — the units of tokscale's daily rows (design §10.3). */
|
|
66
76
|
output: number;
|
|
77
|
+
/** The part of the output a CLI reports as reasoning (Codex); 0 where none is reported (Claude). */
|
|
78
|
+
reasoning: number;
|
|
67
79
|
cacheRead: number;
|
|
68
80
|
cacheWrite5m: number;
|
|
69
81
|
cacheWrite1h: number;
|
|
@@ -73,6 +85,12 @@ export interface UsageTotals {
|
|
|
73
85
|
}
|
|
74
86
|
export interface UsageRow extends UsageTotals {
|
|
75
87
|
key: string;
|
|
88
|
+
/** Whether any of it comes from a CLI that reports reasoning (REPORTS_REASONING): false → its 0 is "not reported". */
|
|
89
|
+
reasoningReported: boolean;
|
|
90
|
+
/** Some of it is estimated (ESTIMATED_BACKENDS); flagged "estimated" (a row) or "includes_estimates" (the totals). */
|
|
91
|
+
estimated: boolean;
|
|
92
|
+
/** The estimated part of `amount` (null: nothing estimated is priced). */
|
|
93
|
+
amountEstimated: number | null;
|
|
76
94
|
total: number;
|
|
77
95
|
/** Delivered messages; null where a message has no such key (by model, by profile). */
|
|
78
96
|
messages: number | null;
|
|
@@ -92,6 +110,8 @@ export interface UsageRow extends UsageTotals {
|
|
|
92
110
|
}
|
|
93
111
|
export interface UsageDaily extends UsageTotals {
|
|
94
112
|
date: string;
|
|
113
|
+
reasoningReported: boolean;
|
|
114
|
+
estimated: boolean;
|
|
95
115
|
zone: string;
|
|
96
116
|
instance: string;
|
|
97
117
|
backend: string;
|
package/dist/usage-report.js
CHANGED
|
@@ -10,6 +10,12 @@ export const usageBasis = (checked = PRICES_CHECKED) => `API-equivalent at list
|
|
|
10
10
|
*/
|
|
11
11
|
export const USAGE_DAILY_MAX = 20_000;
|
|
12
12
|
const M = 1_000_000;
|
|
13
|
+
/** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
|
|
14
|
+
export const ESTIMATED_BACKENDS = new Set(["kiro-cli"]);
|
|
15
|
+
/** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
|
|
16
|
+
export const REPORTS_REASONING = new Set(["codex", "grok", "muse", "opencode", "antigravity"]);
|
|
17
|
+
/** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
|
|
18
|
+
export const WRITES_UNREPORTED = new Set(["codex", "muse"]);
|
|
13
19
|
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
14
20
|
/** A range as asked (query string or tool argument); anything else is the last 7 days. */
|
|
15
21
|
export function usageRange(kind, from, to) {
|
|
@@ -31,19 +37,23 @@ export function rangeDates(range, now, zone) {
|
|
|
31
37
|
case "custom": return { from: range.from, to: range.to };
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
|
-
const zero = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
|
|
35
|
-
|
|
40
|
+
const zero = () => ({ input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
|
|
41
|
+
/** One slice into a row: `reasoning` is the part of the slice's output reported as reasoning (shown beside the rest). */
|
|
42
|
+
const addTotals = (into, [n, uncached, read, w5, w1h, w, out], reasoning) => {
|
|
43
|
+
const r = Math.min(Math.max(0, reasoning), out);
|
|
36
44
|
into.requests += n;
|
|
37
45
|
into.input += uncached;
|
|
38
46
|
into.cacheRead += read;
|
|
39
47
|
into.cacheWrite5m += w5;
|
|
40
48
|
into.cacheWrite1h += w1h;
|
|
41
49
|
into.cacheWrite += w;
|
|
42
|
-
into.output += out;
|
|
50
|
+
into.output += out - r;
|
|
51
|
+
into.reasoning += r;
|
|
43
52
|
};
|
|
44
53
|
const splitKey = (mk) => { const bar = mk.lastIndexOf("|"); return { model: mk.slice(0, bar), long: mk.slice(bar + 1) === "1" }; };
|
|
45
54
|
/**
|
|
46
|
-
* The list-price amount of one slice, or null when its model is not in the table.
|
|
55
|
+
* The list-price amount of one slice, or null when its model is not in the table. Reasoning is a part of the slice's
|
|
56
|
+
* output, so the whole output is priced at the output rate once. Codex writes not reported are 0 in the
|
|
47
57
|
* ledger (it marks writesSeen on the first one above 0), so they add nothing and are never priced as uncached input.
|
|
48
58
|
*/
|
|
49
59
|
function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
|
|
@@ -52,6 +62,7 @@ function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
|
|
|
52
62
|
return null;
|
|
53
63
|
return (uncached * r.input + read * r.read + w5 * r.write5m + w1h * r.write1h + w * r.write + out * r.output) / M;
|
|
54
64
|
}
|
|
65
|
+
const estimatedSlice = (s) => ESTIMATED_BACKENDS.has(s.backend);
|
|
55
66
|
const unknownSource = (s) => s.backend === "unknown" || s.profile === "unknown";
|
|
56
67
|
export function buildUsageReport(input) {
|
|
57
68
|
const { from, to } = rangeDates(input.range, input.now, input.zone);
|
|
@@ -70,14 +81,14 @@ export function buildUsageReport(input) {
|
|
|
70
81
|
if (!i.ledger || !i.days)
|
|
71
82
|
continue;
|
|
72
83
|
coverage.push({ name: i.name, intervals: i.days.coverage.map(([a, b]) => [a, b]) });
|
|
73
|
-
// Codex: a write is reported only once a real one has been seen (every CLI so far records 0).
|
|
84
|
+
// Codex and Muse: a write is reported only once a real one has been seen (every CLI so far records 0).
|
|
74
85
|
const writesSeen = i.ledger.writesSeen === true;
|
|
75
86
|
const base = { instance: i.name, team: i.team };
|
|
76
87
|
for (const row of i.days.rows) {
|
|
77
88
|
if (!inRange(row.date))
|
|
78
89
|
continue;
|
|
79
90
|
for (const [mk, tot] of Object.entries(row.m))
|
|
80
|
-
slices.push({ ...base, backend: row.be, writesReported: row.be
|
|
91
|
+
slices.push({ ...base, backend: row.be, writesReported: !WRITES_UNREPORTED.has(row.be) || writesSeen, date: row.date, zone: row.zone, profile: row.pf, ...splitKey(mk), tot, reasoning: row.r?.[mk] ?? 0, cost: row.c?.[mk] ?? 0 });
|
|
81
92
|
}
|
|
82
93
|
for (const [hk, hour] of Object.entries(i.ledger.hours)) {
|
|
83
94
|
const h = Number(hk);
|
|
@@ -88,7 +99,7 @@ export function buildUsageReport(input) {
|
|
|
88
99
|
continue;
|
|
89
100
|
for (const src of hourSources(hour)) {
|
|
90
101
|
for (const [mk, tot] of Object.entries(src.m))
|
|
91
|
-
slices.push({ ...base, backend: src.backend, writesReported: src.backend
|
|
102
|
+
slices.push({ ...base, backend: src.backend, writesReported: !WRITES_UNREPORTED.has(src.backend) || writesSeen, date, zone: input.zone, profile: src.profile, ...splitKey(mk), tot, reasoning: src.r[mk] ?? 0, cost: src.c[mk] ?? 0 });
|
|
92
103
|
}
|
|
93
104
|
}
|
|
94
105
|
}
|
|
@@ -102,7 +113,7 @@ export function buildUsageReport(input) {
|
|
|
102
113
|
}
|
|
103
114
|
};
|
|
104
115
|
const rows = new Map();
|
|
105
|
-
const newRow = (key) => ({ key, ...zero(), total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
|
|
116
|
+
const newRow = (key) => ({ key, ...zero(), reasoningReported: false, estimated: false, amountEstimated: null, _estAmount: 0, _estPriced: false, total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
|
|
106
117
|
const uniq = (list, v) => { if (!list.includes(v))
|
|
107
118
|
list.push(v); };
|
|
108
119
|
// Daily rows: one per day, zone, agent, backend, model, tier and profile (an hour's slices summed into their day).
|
|
@@ -110,12 +121,18 @@ export function buildUsageReport(input) {
|
|
|
110
121
|
for (const s of slices) {
|
|
111
122
|
const key = keyOf(s);
|
|
112
123
|
const row = rows.get(key) ?? (rows.set(key, newRow(key)), rows.get(key));
|
|
113
|
-
addTotals(row, s.tot);
|
|
124
|
+
addTotals(row, s.tot, s.reasoning);
|
|
125
|
+
if (REPORTS_REASONING.has(s.backend))
|
|
126
|
+
row.reasoningReported = true;
|
|
114
127
|
uniq(row.instances, s.instance);
|
|
115
128
|
uniq(row.backends, s.backend);
|
|
116
129
|
uniq(row.models, s.model);
|
|
117
130
|
uniq(row.profiles, s.profile);
|
|
118
|
-
|
|
131
|
+
// List price first; a model the table lacks takes the cost its CLI reported (Grok), said so (design §10.2).
|
|
132
|
+
const listed = amountOf(s.model, s.long, s.tot), reported = listed === null && s.cost > 0;
|
|
133
|
+
const amount = listed ?? (reported ? s.cost : null);
|
|
134
|
+
if (reported)
|
|
135
|
+
uniq(row.flags, "cost_reported");
|
|
119
136
|
if (amount === null) {
|
|
120
137
|
row._unpricedAny = true;
|
|
121
138
|
uniq(row.unpriced, s.model);
|
|
@@ -124,6 +141,14 @@ export function buildUsageReport(input) {
|
|
|
124
141
|
row._pricedAny = true;
|
|
125
142
|
row._amount += amount;
|
|
126
143
|
}
|
|
144
|
+
if (estimatedSlice(s)) {
|
|
145
|
+
row.estimated = true;
|
|
146
|
+
uniq(row.flags, "estimated");
|
|
147
|
+
if (amount !== null) {
|
|
148
|
+
row._estPriced = true;
|
|
149
|
+
row._estAmount += amount;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
127
152
|
if (!s.writesReported)
|
|
128
153
|
uniq(row.flags, "cache_write_not_reported");
|
|
129
154
|
if (unknownSource(s))
|
|
@@ -132,11 +157,13 @@ export function buildUsageReport(input) {
|
|
|
132
157
|
const dk = [s.date, s.zone, s.instance, s.backend, s.model, tier, s.profile].join("\u0000");
|
|
133
158
|
let d = dailyBy.get(dk);
|
|
134
159
|
if (!d) {
|
|
135
|
-
d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), amount: amount === null ? null : 0,
|
|
136
|
-
flags: [...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
|
|
160
|
+
d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), reasoningReported: REPORTS_REASONING.has(s.backend), estimated: estimatedSlice(s), amount: amount === null ? null : 0,
|
|
161
|
+
flags: [...(estimatedSlice(s) ? ["estimated"] : []), ...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
|
|
137
162
|
dailyBy.set(dk, d);
|
|
138
163
|
}
|
|
139
|
-
addTotals(d, s.tot);
|
|
164
|
+
addTotals(d, s.tot, s.reasoning);
|
|
165
|
+
if (reported && !d.flags.includes("cost_reported"))
|
|
166
|
+
d.flags.push("cost_reported");
|
|
140
167
|
if (amount !== null && d.amount !== null)
|
|
141
168
|
d.amount += amount;
|
|
142
169
|
}
|
|
@@ -188,8 +215,9 @@ export function buildUsageReport(input) {
|
|
|
188
215
|
row.messages ??= 0;
|
|
189
216
|
}
|
|
190
217
|
const finish = (row) => {
|
|
191
|
-
const { _amount, _pricedAny, _unpricedAny, ...rest } = row;
|
|
192
|
-
rest.
|
|
218
|
+
const { _amount, _pricedAny, _unpricedAny, _estAmount, _estPriced, ...rest } = row;
|
|
219
|
+
rest.amountEstimated = _estPriced ? _estAmount : null;
|
|
220
|
+
rest.total = rest.input + rest.output + rest.reasoning + rest.cacheRead + rest.cacheWrite5m + rest.cacheWrite1h + rest.cacheWrite;
|
|
193
221
|
rest.amount = _pricedAny ? _amount : null;
|
|
194
222
|
rest.priced = _pricedAny ? (_unpricedAny ? "partial" : "all") : "none";
|
|
195
223
|
if (_unpricedAny)
|
|
@@ -200,15 +228,24 @@ export function buildUsageReport(input) {
|
|
|
200
228
|
.sort((a, b) => (b.amount ?? -1) - (a.amount ?? -1) || b.total - a.total || a.key.localeCompare(b.key));
|
|
201
229
|
const totals = finish(newRow("all"));
|
|
202
230
|
for (const r of out) {
|
|
203
|
-
for (const k of ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
|
|
231
|
+
for (const k of ["input", "output", "reasoning", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
|
|
204
232
|
totals[k] += r[k];
|
|
205
233
|
totals.total += r.total;
|
|
206
234
|
if (r.messages !== null)
|
|
207
235
|
totals.messages = (totals.messages ?? 0) + r.messages;
|
|
208
236
|
if (r.amount !== null)
|
|
209
237
|
totals.amount = (totals.amount ?? 0) + r.amount;
|
|
238
|
+
if (r.reasoningReported)
|
|
239
|
+
totals.reasoningReported = true;
|
|
240
|
+
if (r.estimated) {
|
|
241
|
+
totals.estimated = true;
|
|
242
|
+
uniq(totals.flags, "includes_estimates");
|
|
243
|
+
}
|
|
244
|
+
if (r.amountEstimated !== null)
|
|
245
|
+
totals.amountEstimated = (totals.amountEstimated ?? 0) + r.amountEstimated;
|
|
210
246
|
for (const f of r.flags)
|
|
211
|
-
|
|
247
|
+
if (f !== "estimated")
|
|
248
|
+
uniq(totals.flags, f);
|
|
212
249
|
for (const m of r.unpriced)
|
|
213
250
|
uniq(totals.unpriced, m);
|
|
214
251
|
}
|