pi-zentui 0.29.3 → 0.30.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/docs/configuration.md +178 -18
- package/docs/footer-format.md +55 -6
- package/extensions/zentui/average-token-rate.ts +146 -0
- package/extensions/zentui/component-colors.ts +17 -3
- package/extensions/zentui/component-settings.ts +146 -2
- package/extensions/zentui/config.ts +67 -4
- package/extensions/zentui/custom-value-colors.ts +93 -0
- package/extensions/zentui/custom-variable-format.ts +3 -2
- package/extensions/zentui/editor-metadata-format.ts +48 -17
- package/extensions/zentui/footer-format.ts +42 -13
- package/extensions/zentui/footer.ts +21 -21
- package/extensions/zentui/github-status.ts +575 -0
- package/extensions/zentui/host-project-changes.ts +25 -0
- package/extensions/zentui/host-template-values.ts +1 -0
- package/extensions/zentui/index.ts +138 -21
- package/extensions/zentui/interaction-summary.ts +70 -5
- package/extensions/zentui/live-metadata-demand.ts +72 -0
- package/extensions/zentui/live-metadata-display.ts +21 -0
- package/extensions/zentui/live-metadata.ts +255 -0
- package/extensions/zentui/minimalist-editor.ts +20 -7
- package/extensions/zentui/minimalist-template.ts +3 -2
- package/extensions/zentui/omp-template-lookups.ts +2 -191
- package/extensions/zentui/settings-command.ts +59 -4
- package/extensions/zentui/token-rate.ts +313 -0
- package/extensions/zentui/turn-summary-format.ts +59 -0
- package/extensions/zentui/turn-summary-rate.ts +52 -0
- package/extensions/zentui/working-line.ts +60 -4
- package/omp.ts +5 -1
- package/package.json +1 -1
package/docs/configuration.md
CHANGED
|
@@ -51,7 +51,7 @@ The interactive `/zentui` menu is split into seven component-oriented sections.
|
|
|
51
51
|
2. **Editor** — enablement, style, colors, Codex quota, model label, border behavior, viewport indicators, settings for the selected editor style, and a static synthetic preview.
|
|
52
52
|
3. **User messages** — enablement, style, colors, and a static synthetic Markdown preview.
|
|
53
53
|
4. **Thinking (Experimental)** — private Rail, Tree, or Streaming rendering; active Streaming can switch live to Rail or Tree, Rail and Tree can switch live between each other, and the private renderer may break after Pi updates.
|
|
54
|
-
5. **Working line** — ownership, settled Turn summary, spinner and text speeds, optional spinner-color motion, text animation, color source, custom messages, Tool/Elapsed/Thinking time/Tokens segments, and animated preview.
|
|
54
|
+
5. **Working line** — ownership, settled Turn summary, spinner and text speeds, optional spinner-color motion, text animation, color source, custom messages, Tool/Elapsed/Thinking time/Tokens/Token rate segments, and animated preview.
|
|
55
55
|
6. **Footer** — Native, Starship, or Hidden. Starship additionally exposes colors, Codex quota, model label, responsive layout, separator, context style, and path display.
|
|
56
56
|
- **Segments →** — visibility toggles for non-Git Starship segments.
|
|
57
57
|
- **Git →** — Starship Footer Git segment and probe controls, not Editor Git controls.
|
|
@@ -61,7 +61,7 @@ The two Footer child entries appear only with Starship selected. Child headings
|
|
|
61
61
|
|
|
62
62
|
Editor, User messages, Thinking (Experimental), and Working line retain independent configuration. Editor, User-message, and Thinking previews remain visible while their component is disabled. Only the Working-line preview owns an animation timer. Footer’s Starship-specific rows are shown only while Starship is selected; Extension statuses offers the same unprefixed placement/color rows in every Footer style. Native saves dormant Starship position/color preferences without changing Pi's layout. Footer Color overrides remain available for preconfiguration under every Footer style and say **Saved for Starship** when inactive. Native and Hidden hide the two child entries without changing their saved preferences. Other dormant choices explain their scope without rewriting values. Auto icons assume a Nerd Font without detecting one; ASCII replaces icons only, not all borders or UI glyphs.
|
|
63
63
|
|
|
64
|
-
Free-form values such as custom formats, Opencode metadata formats, and previously unseen, unsaved extension keys remain JSON-only. Component raw colors are editable through each component’s **Color overrides** action, with explicit **Reset / inherit**. Working-line speed accepts validated custom milliseconds in `/zentui`.
|
|
64
|
+
Free-form values such as custom formats, Opencode metadata formats, and previously unseen, unsaved extension keys remain JSON-only. Component raw colors are editable through each component’s **Color overrides** action, with explicit **Reset / inherit**. Editor and Starship Footer also offer **Individual custom value colors**, listing current publishers, valid alias targets, and saved publisher overrides. Working-line speed accepts validated custom milliseconds in `/zentui`.
|
|
65
65
|
|
|
66
66
|
Every section and Footer child page has a direct route and completion:
|
|
67
67
|
|
|
@@ -249,6 +249,7 @@ Reference only—not a starter file. Prefer the minimal overrides above. Optiona
|
|
|
249
249
|
"enabled": false,
|
|
250
250
|
"placement": "above",
|
|
251
251
|
"turnSummary": true,
|
|
252
|
+
"turnSummaryFormat": "Turn took $turn_duration$join_sep(thought for $thought_duration)$join_sep(↑$input_tokens ↓$output_tokens)$join_sep$token_rate",
|
|
252
253
|
"spinner": "star-bloom",
|
|
253
254
|
"spinnerIntervalMs": 100,
|
|
254
255
|
"animateSpinnerColor": false,
|
|
@@ -499,11 +500,14 @@ Empty strings and whitespace-only strings mean deliberately **unstyled**, not mi
|
|
|
499
500
|
| --- | --- | --- |
|
|
500
501
|
| `footer` | `cwd`, `sessionName`, `gitBranch`, `gitStatus`, `contextNormal`, `contextWarning`, `contextError`, `cost`, `sessionDuration`, `tokens`, `separator`, `runtimePrefix`, `extensionStatus`, `packageVersion`, `gitCommit`, `gitMetricsAdded`, `gitMetricsDeleted`, `username`, `time`, `os` | Same-named shared key |
|
|
501
502
|
| `editor` | `cwd`, `sessionName`, `gitStatus`, `contextNormal`, `contextWarning`, `contextError`, `cost`, `sessionDuration` | Same-named shared key; used by Minimalist metadata and quota |
|
|
503
|
+
| `editor` | `cacheHit` | Optional shared `cacheHit`, then Editor `contextNormal`; used only by Minimalist cache-hit metadata |
|
|
502
504
|
| `editor` | `gitBranch` | `editorGitBranch`, then explicitly configured shared `gitBranch` / `git`; never the generated Footer branch default |
|
|
503
505
|
| `editor` | `accent`, `border`, `prompt`, `rail`, `shellRail`, `model`, `provider`, `thinking`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `thinkingMax` | `editorAccent`, `editorBorder`, `editorPrompt`, `editorRail`, `editorShellRail`, `editorModel`, `editorProvider`, `editorThinking`, and matching `editorThinking*` level keys |
|
|
504
506
|
| `userMessages` | `accent`, `border` | `editorAccent`, `editorBorder` |
|
|
505
507
|
| `selectorBorders` | `border` | No shared raw key: defaults to theme `borderMuted` / terminal `bright-black`; never inherits `editorBorder` |
|
|
506
508
|
| `workingLine` | `low`, `mid`, `high` | `workingLineLow`, `workingLineMid`, `workingLineHigh` |
|
|
509
|
+
| `editor`, `footer` | `prNumber`, `prUrl`, `ci`, `tokenRate` | No shared raw keys; existing neutral border/extension-status fallback |
|
|
510
|
+
| `workingLine` | `tokenRate` | No shared raw key; Static-only override, otherwise inherits mid; Classic/KITT use whole-row tiers |
|
|
507
511
|
|
|
508
512
|
Shared aliases `cwdText → cwd` and `git → gitBranch` remain accepted. Footer model/provider are plain text and the detected runtime label uses its runtime module's style, not invented Footer color keys.
|
|
509
513
|
|
|
@@ -513,6 +517,7 @@ Role-specific defaults and chains remain intact:
|
|
|
513
517
|
- In shell-command mode (`!` or `!!`), Opencode's left rail and model label share `shellRail` → configured `accent` → theme `bashMode` / terminal `bright-cyan`. Normal model coloring is unchanged.
|
|
514
518
|
- Accent Rail uses only `rail` / `editorRail`, then warm theme `syntaxNumber` / terminal `215`; it does not inherit `accent`.
|
|
515
519
|
- Minimalist branch defaults to theme `bold syntaxKeyword` / terminal `bold blue` when no local or explicit shared branch style exists.
|
|
520
|
+
- Minimalist's built-in `Cache NN%` label and `$cache_hit` use Editor `cacheHit` → optional shared `colors.cacheHit` → Editor `contextNormal` (local, then shared/default). The normal tier stays stable even at warning/error context usage. Configure it through `/zentui` → Editor → **Color overrides** → `cacheHit`; **Reset / inherit** resumes the chain. The selected Editor color source applies throughout; no cache-hit palette is generated or saved, and other owners are unchanged.
|
|
516
521
|
- Thinking levels use their level key then generic `thinking`; Max uses `thinkingMax → thinkingXhigh → thinking`. Static metadata and adaptive borders retain their existing distinct fallback behavior; theme-adaptive borders still defer to Pi's thinking-border callback.
|
|
517
522
|
- Working-line defaults remain theme `dim`, `muted`, `bold accent`, or terminal `bright-black`, `cyan`, `bold cyan`. Both animated rows and summaries consume local overrides. New persisted Turn summaries snapshot the effective high style; when no safe SGR prefix exists (including an unstyled high override), they retain the safe bold-cyan substitute. Existing persisted summaries keep their recorded style; legacy version-1 summaries use current high styling.
|
|
518
523
|
|
|
@@ -543,8 +548,8 @@ Zentui's generated layout; an empty string hides that slot's configurable
|
|
|
543
548
|
metadata. No extra rows are added. Use **Editor → Metadata templates** in
|
|
544
549
|
`/zentui` to edit a slot or **Reset / inherit** to delete its override.
|
|
545
550
|
|
|
546
|
-
Templates use
|
|
547
|
-
|
|
551
|
+
Templates use `$variable`, `${variable}`, literal text, conditional `( ... )`
|
|
552
|
+
groups, and the additive `$join_sep` marker. Layout is selected by the six slot names, not
|
|
548
553
|
`$fill`. Viewport counts, Bash mode, and an embedded Working line remain
|
|
549
554
|
operational indicators outside the templates. Explicit templates may show
|
|
550
555
|
metadata whose ordinary visibility toggle is off, but can never bypass Editor
|
|
@@ -576,7 +581,7 @@ with `variables`, then reference those names in any slot:
|
|
|
576
581
|
**Editor → Custom variable aliases** edits the name-to-publisher-key JSON or
|
|
577
582
|
resets the aliases. Names use letters, digits, and underscores, start with a
|
|
578
583
|
letter or underscore, and cannot replace built-in/structural variables or
|
|
579
|
-
prototype names. At most 16 aliases are accepted; keys are nonempty, at most
|
|
584
|
+
prototype names, except that a valid explicit `ci` alias retains precedence over the new GitHub builtin. At most 16 aliases are accepted; keys are nonempty, at most
|
|
580
585
|
64 UTF-16 code units, and contain no whitespace or terminal controls. Reset
|
|
581
586
|
removes only the edited overrides. Unrelated owners/styles and unknown raw
|
|
582
587
|
configuration remain untouched. These settings never enable Footer or Working
|
|
@@ -588,10 +593,15 @@ Built-ins are `$model`, `$model_id`, `$model_name`, `$provider`, `$thinking`,
|
|
|
588
593
|
and `$output_tokens`. `$turn_duration` is current/completed interaction time,
|
|
589
594
|
not total session duration. Existing path, context, separator and color
|
|
590
595
|
preferences still apply. `$sep` (alias `$separator`) uses the configured
|
|
591
|
-
Minimalist dash/dot separator.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
596
|
+
Minimalist dash/dot separator. `$join_sep` (or `${join_sep}`) joins only populated
|
|
597
|
+
fields with that same styled separator. For example, set `bottomLeft` to
|
|
598
|
+
`"$session_name$join_sep($git_branch $git_status)"` for a session-only, Git-only,
|
|
599
|
+
combined, or empty label; explicit references work even with `showSessionName`
|
|
600
|
+
off. Do not add padding around the marker. Legacy `$sep` and optional-group
|
|
601
|
+
formatting remain unchanged; see [conditional joining](./footer-format.md#conditional-joining)
|
|
602
|
+
for chains, nested scopes, preserved ANSI/link styling, and boundaries.
|
|
603
|
+
|
|
604
|
+
Pi and OMP support `$pr_number`, `$pr_url`, `$ci`, and `$token_rate` (see [live metadata](#live-metadata)). OMP additionally supports the [host template data](#omp-template-data) built-ins
|
|
595
605
|
in all six slots and both Opencode metadata formats. These are separate from
|
|
596
606
|
publisher aliases and are not included in `$extensions`.
|
|
597
607
|
|
|
@@ -674,6 +684,15 @@ Tip: with `opencode-copy-friendly`, set Pi's `editorPaddingX` to `1` for a small
|
|
|
674
684
|
|
|
675
685
|
Each Opencode variant owns an independent `metadataFormat`:
|
|
676
686
|
|
|
687
|
+
Both support `$join_sep` / `${join_sep}` to join populated fields with neutral
|
|
688
|
+
border-styled ` · `, for example `$model$join_sep$provider$join_sep$thinking`.
|
|
689
|
+
Missing middle fields leave exactly one separator; single fields have none.
|
|
690
|
+
Do not pad the marker: it owns its spaces. Joins are local to groups and never
|
|
691
|
+
cross `$fill` zones. Legacy `$sep` / `$separator` still render empty in Opencode.
|
|
692
|
+
See [shared conditional-joining semantics](./footer-format.md#conditional-joining).
|
|
693
|
+
The marker is reserved (not a custom alias or data demand), requires a supporting
|
|
694
|
+
version, and does not rewrite defaults, saved templates, or other components.
|
|
695
|
+
|
|
677
696
|
```json
|
|
678
697
|
{
|
|
679
698
|
"components": {
|
|
@@ -712,6 +731,7 @@ The configured right zone and Pi's operational right status are right-aligned to
|
|
|
712
731
|
| `$tokens` | cumulative session input/output tokens only, for example `↑76k ↓1.6k` |
|
|
713
732
|
| `$cache_hit` | latest assistant prompt cache-hit rate to one decimal; `0.0%` when unavailable |
|
|
714
733
|
| `$codex_quota` | remaining 5-hour/weekly account quota; requires Editor quota consent and active `openai-codex` |
|
|
734
|
+
| `$join_sep` | conditional join with neutral border-styled ` · ` |
|
|
715
735
|
|
|
716
736
|
`$context` uses Pi's current context snapshot and the live assistant context override, refreshing on the existing 250 ms streaming render cadence. `$tokens` and `$cache_hit` use authoritative persisted session snapshots, so they update at normal session synchronization boundaries rather than estimating in-progress totals. These variables are independent of Footer visibility, style, color source, and configuration.
|
|
717
737
|
|
|
@@ -791,17 +811,158 @@ Streaming keeps the reviewed host-rendered behavior: while open it shows the lat
|
|
|
791
811
|
|
|
792
812
|
The exact all-mode private matrix covers Pi 0.85.0 and 0.87.1 under dark, light, and current themes, narrow/wide widths and resize; both versions also have fullscreen live-transition PTY coverage. Thinking (Experimental) never owns or writes the Working line, including its unchanged **Thinking time** option, and does not own Footer, Editor, widgets, statuses, or model behavior.
|
|
793
813
|
|
|
814
|
+
## Individual custom value colors
|
|
815
|
+
|
|
816
|
+
Sparse `components.editor.customValueColors` and `components.footer.customValueColors`
|
|
817
|
+
are keyed by **publisher ID**, not template alias. All aliases for that publisher
|
|
818
|
+
and Minimalist's supported `$extensions` aggregate share the override within the
|
|
819
|
+
owner, across its styles. Footer's `$extensions` remains the existing status
|
|
820
|
+
protocol and is not affected. Working-line publisher segments are a separate
|
|
821
|
+
protocol and do not consume these maps.
|
|
822
|
+
|
|
823
|
+
```json
|
|
824
|
+
{
|
|
825
|
+
"components": {
|
|
826
|
+
"editor": { "customValueColors": { "vendor.package/value": "bold fg:202" } },
|
|
827
|
+
"footer": { "customValueColors": { "vendor.package/value": "" } }
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
Missing overrides retain the style's Original/Zentui behavior exactly. Explicit
|
|
833
|
+
styles strip publisher SGR and hyperlinks before styling through the selected
|
|
834
|
+
owner color source; empty/whitespace deliberately means unstyled. Reset deletes
|
|
835
|
+
one leaf and restores inheritance. Theme tokens, hex/256-color values and
|
|
836
|
+
Starship-style modifiers are supported. Normalization accepts up to 1024 own
|
|
837
|
+
properties and styles up to 4096 UTF-16 units; invalid keys/styles inherit.
|
|
838
|
+
Saves preserve unrelated raw JSON, invalid/future leaves and the other owner;
|
|
839
|
+
ordinary selection saves and `/zentui migrate` never snapshot these maps or
|
|
840
|
+
rewrite templates.
|
|
841
|
+
|
|
842
|
+
## Live metadata
|
|
843
|
+
|
|
844
|
+
Minimalist's six slots, both Opencode metadata formats, and Starship wide and
|
|
845
|
+
responsive compact templates support these explicit opt-in references:
|
|
846
|
+
|
|
847
|
+
| Variable | Pi | OMP |
|
|
848
|
+
| --- | --- | --- |
|
|
849
|
+
| `$pr_number`, `$pr_url` | Open PR number and safe plain HTTPS URL for a non-default branch | Same shared collector |
|
|
850
|
+
| `$ci` | `CI passing`, `CI running`, `CI failed`, `CI no checks`, or `CI stale` | Same shared collector |
|
|
851
|
+
| `$token_rate` | Completed model-work average for the current interaction, e.g. `48 tok/s avg`; unknown is `— tok/s avg` | Existing native last-assistant / streaming worker rate unchanged |
|
|
852
|
+
|
|
853
|
+
No default or saved template is rewritten. For example:
|
|
854
|
+
|
|
855
|
+
```json
|
|
856
|
+
{
|
|
857
|
+
"components": {
|
|
858
|
+
"editor": {
|
|
859
|
+
"colors": { "ci": "bold blue", "tokenRate": "fg:202" },
|
|
860
|
+
"styles": { "opencode": { "metadataFormat": "$model( · PR #$pr_number)( · $ci)( · $token_rate)" } }
|
|
861
|
+
},
|
|
862
|
+
"footer": {
|
|
863
|
+
"styles": { "starship": {
|
|
864
|
+
"format": "$cwd( · PR #$pr_number)( · $ci)$fill$context",
|
|
865
|
+
"compactFormat": "$cwd$wrap(PR #$pr_number)$wrap($ci)$wrap($token_rate)"
|
|
866
|
+
} }
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
A valid owner/style-local `variables.ci` mapping **wins over the builtin** and
|
|
873
|
+
creates only publisher demand. Remove that mapping to opt into GitHub CI. Other
|
|
874
|
+
builtin reservations are unchanged. Builtin colors use independent owner-local
|
|
875
|
+
`prNumber`, `prUrl`, `ci`, and `tokenRate` roles, not publisher color maps; CI has
|
|
876
|
+
one role, not an automatic severity palette.
|
|
877
|
+
|
|
878
|
+
GitHub collection is read-only via `gh`, asynchronous and outside render. It
|
|
879
|
+
runs immediately, then refreshes at the completed result's 30-second cache
|
|
880
|
+
expiry, only while an owned, enabled, supported consumer references PR/CI.
|
|
881
|
+
Expiry repaints CI as stale before awaiting the next fetch; failures without a
|
|
882
|
+
cache deadline retry after 30 seconds. Editor and Footer share one neutral snapshot but keep
|
|
883
|
+
independent settings/appearance. Each refresh verifies repository, branch,
|
|
884
|
+
remotes and local HEAD identity, including cache hits; observed project/tool and
|
|
885
|
+
host branch changes invalidate immediately. External changes are discovered on
|
|
886
|
+
the next demanded refresh, not instantaneously. Valid repo metadata lasts five
|
|
887
|
+
minutes; PR successes and negative results last 30 seconds. Identity/context
|
|
888
|
+
changes, demand loss and shutdown abort outstanding work. No auth commands,
|
|
889
|
+
credential reads, forge mutations or other forges are supported.
|
|
890
|
+
|
|
891
|
+
CI is qualified by the PR head: a local commit different from the remote PR head
|
|
892
|
+
is **stale**, never green. Dirty files alone do not change commit identity.
|
|
893
|
+
Expired CI is stale while refreshing; errors clear earlier results. Zero checks
|
|
894
|
+
or only skipped/neutral checks are **no checks**, not passing. Unknown/malformed
|
|
895
|
+
checks are never proof of success. No PR, closed/default/detached branches,
|
|
896
|
+
missing `gh`/read access or lookup errors are empty; optional groups hide labels.
|
|
897
|
+
|
|
898
|
+
Pi Editor/Footer `$token_rate` is a **completed model-work average**: sum of
|
|
899
|
+
provider-reported final output tokens divided by the sum of observed model-call
|
|
900
|
+
durations in the current interaction. Each interval starts when Zentui observes
|
|
901
|
+
public `turn_start` and ends at its matching accepted assistant `message_end`,
|
|
902
|
+
using a monotonic clock. This includes initial wait **and client preparation
|
|
903
|
+
before the provider call**; it is not pure backend decode speed. Tool execution,
|
|
904
|
+
between-call gaps and idle are excluded. For example, 120 output tokens over 4s
|
|
905
|
+
(including 2s initial wait), then 180 over 2s after a 10s tool gap, gives
|
|
906
|
+
`300 / 6 = 50 tok/s avg`, not an average of call rates or `300 / 16`.
|
|
907
|
+
|
|
908
|
+
The slot shows `— tok/s avg` before a trustworthy completed sample, never a
|
|
909
|
+
live-rate substitute. After each completed call it shows **completed calls so
|
|
910
|
+
far**, retaining the previous aggregate while another call in the same interaction
|
|
911
|
+
is in flight, through tools, `agent_end` and settlement. Continuation/retry
|
|
912
|
+
`agent_start` does not reset it; a genuinely new interaction does. If non-idle
|
|
913
|
+
settlement partitions an already-started run into a new interaction, only that
|
|
914
|
+
surviving run's completed work and any open call's original start are retained;
|
|
915
|
+
unknown coverage from settled runs is discarded, but surviving unknown coverage
|
|
916
|
+
remains unknown. Only accepted,
|
|
917
|
+
deduplicated finals contribute per-message output, never cumulative interaction
|
|
918
|
+
totals, last streaming usage or character estimates. Successful reported zero
|
|
919
|
+
output is included with its duration. Positive provider-reported partial/error
|
|
920
|
+
usage is included with a matching interval; all-zero error/abort placeholders
|
|
921
|
+
are unknown. Missing/invalid final usage or timing for any included call makes
|
|
922
|
+
the aggregate unknown until reset rather than silently showing a partial exact
|
|
923
|
+
average. This reflects final usage visible to Zentui's event handler, not a
|
|
924
|
+
history scan or independent verification of provider accounting.
|
|
925
|
+
|
|
926
|
+
Working line keeps its separate **recent live rate**, with `~` for estimates
|
|
927
|
+
and `— tok/s` before a usable measurement. It uses a bounded three-second
|
|
928
|
+
observation window and needs advancing output spanning at least 500ms, excluding
|
|
929
|
+
pre-output silence. The measurement expires after two seconds without advancing
|
|
930
|
+
output, but its display holds the last observed window rate while the Working row
|
|
931
|
+
remains active: between chunks, through response end, tools, between-call pauses,
|
|
932
|
+
continuations/retries and subsequent model turns in the same interaction. A fresh
|
|
933
|
+
usable live sample replaces it; a retained value is historical, not ongoing generation.
|
|
934
|
+
Text, thinking and tool-call argument deltas use the existing Unicode estimator;
|
|
935
|
+
source/usage corrections rebaseline rather than producing spikes. Final usage
|
|
936
|
+
never creates an instantaneous live sample. Working rate is independently opt-in;
|
|
937
|
+
a genuinely new interaction starts at `— tok/s` until its first usable measurement.
|
|
938
|
+
If non-idle settlement promotes an already-started run into a new interaction,
|
|
939
|
+
only that surviving run's observation is retained; an older run's value becomes
|
|
940
|
+
`— tok/s`. The whole Working row still disappears normally at idle/settlement.
|
|
941
|
+
|
|
942
|
+
Compaction, session/model/tree changes and loss of all rate demand clear both
|
|
943
|
+
metrics. After a mid-run model change, the next properly observed turn can
|
|
944
|
+
establish a new current-model aggregate without another `agent_start`. Repaint
|
|
945
|
+
polling is active only while streaming and demanded; retained rates and averages
|
|
946
|
+
need no idle timer. These live metrics add no latency measurement; persisted summaries have their own snapshot schema.
|
|
947
|
+
OMP's native `$token_rate` semantics are unchanged.
|
|
948
|
+
|
|
794
949
|
## Working line
|
|
795
950
|
|
|
796
951
|
When enabled, Zentui owns Pi's complete working-row message and indicator. Five fixed-width spinner presets are available: Braille Orbit, Star Bloom, ASCII Pinwheel, Claude-inspired, and three-cell Pulse.
|
|
797
952
|
|
|
798
|
-
`messages.custom` defaults on and selects once per model turn from an editable, materialized 16-message list. Turning it off keeps the row owned and displays animated `Working…`; an empty or invalid list uses the same fallback. Optional segments show the latest active Tool, interaction-wide Elapsed time, cumulative wall-clock Thinking time, and whole-interaction Tokens.
|
|
953
|
+
`messages.custom` defaults on and selects once per model turn from an editable, materialized 16-message list. Turning it off keeps the row owned and displays animated `Working…`; an empty or invalid list uses the same fallback. Optional segments show the latest active Tool, interaction-wide Elapsed time, cumulative wall-clock Thinking time, and whole-interaction Tokens. **Token rate** is separately optional and default-off (`segments.tokenRate: false`); enable it without adopting Editor or Footer: `{"components":{"workingLine":{"enabled":true,"segments":{"tokenRate":true},"colors":{"tokenRate":"fg:202"}}}}`. It holds the last observed recent live Pi rate through tools and subsequent calls in the same interaction, not the completed average in Editor/Footer `$token_rate`. The row disappears normally at idle, and identity changes such as compaction clear the observation; see [live metadata](#live-metadata).
|
|
799
954
|
|
|
800
955
|
Committed totals stay provider-reported across tool loops, retries, compaction retries, and queued continuations. During a response, live output follows Pi's `↓N` convention whether usage is provider-reported or temporarily estimated. Final usage reconciles atomically; input is never estimated. Labels are sanitized and width-bounded.
|
|
801
956
|
|
|
802
|
-
When Pi settles, the default-on **Turn summary** appends a persistent context-free row such as `Turn took 56s · thought for 10s · ↑7.1k ↓779`. Thought is cumulative wall-clock time from Pi's public thinking stream; overlaps count once and zero is omitted. Output already includes reasoning tokens, so reasoning is not added separately.
|
|
957
|
+
When Pi settles, the default-on boolean **Turn summary** (`turnSummary`) appends a persistent context-free row such as `Turn took 56s · thought for 10s · ↑7.1k ↓779 · 42 tok/s avg`. Thought is cumulative wall-clock time from Pi's public thinking stream; overlaps count once and zero is omitted. Output already includes reasoning tokens, so reasoning is not added separately. The default template includes both token totals even when live Tokens or **Thinking time** is hidden or zero. Summaries are inactive while Working line is disabled.
|
|
958
|
+
|
|
959
|
+
`turnSummaryFormat` is a terminal-safe single-line template (maximum 2048 code units and eight nested optional groups). Missing, invalid, empty or whitespace-only values use the default shown above; use `turnSummary: false` to hide new summaries. Supported variables are `$turn_duration`, `$thought_duration`, `$input_tokens`, `$output_tokens`, and `$token_rate`. Braced variables and optional `(groups)` use the existing format grammar; unknown names are empty. `$sep`/`$separator` emit ` · `; `$join_sep` joins only nonempty fields. Zero thought is empty; known-zero counts remain `0`.
|
|
960
|
+
|
|
961
|
+
Summary `$token_rate` is a complete label such as `42 tok/s avg`, or empty when exact final usage/timing coverage is unavailable (never an unknown dash). It sums provider-reported final assistant output divided by summed observed `turn_start` → accepted `message_end` durations, including initial wait/client preparation and excluding tools/gaps, across the **whole settled interaction**, including model selections and continuations. Non-idle settlement includes only settled runs, not an already-running successor. Summary-only collection is passive: it does not enable live Token rate, Editor/Footer slots or sampling timers. Enabling collection late cannot claim a partial exact average.
|
|
962
|
+
|
|
963
|
+
`components.workingLine.colors.turnSummary` styles only new static transcript summaries. Missing/invalid values inherit effective Working **high** (local then shared fallback); explicit empty/whitespace means unstyled. `/zentui` → Working line offers **Turn summary format** Edit/Reset and **Color overrides** → `turnSummary`; Reset deletes only that override. Format and resolved style are snapshotted in new entries, so changes never rewrite history. Legacy v1/v2/v3 entries retain their original output without fabricated historical TPS. OMP remains native.
|
|
803
964
|
|
|
804
|
-
Classic and KITT
|
|
965
|
+
Classic and KITT sweep across the entire row: Message, Tool, Elapsed, Thought, Tokens, Token rate (including `— tok/s`), and extension segments. A saved `colors.tokenRate` override never creates a fixed-color segment in animated modes; it applies only in Static (`textAnimation: "disabled"`). **Animate spinner color** optionally includes spinner cells and separator. Static uses the mid tier except for an explicit Token rate override; an omitted or invalid override inherits mid, while an empty style deliberately leaves Token rate unstyled. Static ignores text speed/spinner-color participation without changing saved values. Spinner glyph motion always remains active.
|
|
805
966
|
|
|
806
967
|
| Setting | Default | Presets | Applies to |
|
|
807
968
|
| --- | ---: | --- | --- |
|
|
@@ -810,7 +971,7 @@ Classic and KITT move color across message and segments. **Animate spinner color
|
|
|
810
971
|
|
|
811
972
|
Both speeds accept `30..1000` ms. Classic/KITT combine both cadences through one Pi Loader interval; exact cycles are used within 1024-frame/512-KiB limits. Pathological custom pairs use a bounded evenly distributed schedule with at most half a spinner-cycle and half a text-step rounding. Legacy `intervalMs` is accepted only as migration input for `spinnerIntervalMs` when the canonical field is absent.
|
|
812
973
|
|
|
813
|
-
Content reserves the complete Tokens
|
|
974
|
+
Content reserves the complete Tokens and optional Token rate labels and active extension segments first, then Message, Thought, Elapsed, and Tool allocation, while preserving visual order **Message · Tool · Elapsed · Thought · Tokens · Token rate · Extensions** within the 80-column Loader-row contract. Active thought starts as `thinking 0s`; completed positive thought becomes `thought for Ns`. Rebuilds preserve spinner and visible color phase.
|
|
814
975
|
|
|
815
976
|
Pi's working-row APIs are global and unkeyed. While owning the row, Zentui reasserts its blank message on owned refreshes/reconciles (even with unchanged frames) and before a Border fallback reveals the Above row. A later external message write or reset can still win until the next such boundary; native spinner ticks alone do not repair it, and there is no added polling. Separate multiline widgets are unaffected; extensions sharing the working-message slot should use keyed segments below instead.
|
|
816
977
|
|
|
@@ -965,13 +1126,14 @@ OMP has no public editor-factory getter. Zentui observes public setter descripto
|
|
|
965
1126
|
The following explicit Zentui names are available in Opencode/copy-friendly
|
|
966
1127
|
`metadataFormat`, all Minimalist slots, and Starship `format`/`compactFormat`.
|
|
967
1128
|
OMP segment IDs are not automatically template variables. These names are
|
|
968
|
-
reserved built-ins,
|
|
969
|
-
Pi and
|
|
1129
|
+
reserved built-ins, except the explicit `ci` alias compatibility rule; `$extensions` is unchanged.
|
|
1130
|
+
Pi supplies the [shared PR/CI and live rate variables](#live-metadata); other host-only or unavailable capabilities render empty.
|
|
970
1131
|
|
|
971
1132
|
| Variable | Data and distinctions |
|
|
972
1133
|
| --- | --- |
|
|
973
1134
|
| `$session_id` | Current session's full ID, not its human-readable `$session_name`. |
|
|
974
1135
|
| `$pr_number`, `$pr_url` | Current non-default branch's GitHub PR number and safe plain HTTPS URL. |
|
|
1136
|
+
| `$ci` | Shared GitHub PR-head CI state; see [live metadata](#live-metadata). |
|
|
975
1137
|
| `$subagent_count` | Native active subagent badge count; background Bash/Eval jobs are not subagents. A known zero renders `0`. |
|
|
976
1138
|
| `$token_rate` | Output tokens/second, for example `42.5 tok/s`. Uses OMP's last-assistant average; observed live Vibe workers add the current streaming main rate, not an idle previous reply. Unknown timings remain empty. |
|
|
977
1139
|
| `$active_time` | Native accumulated active-processing time, including the open interval and excluding idle time, for example `1m 5s`. Not wall-clock `$session_duration` or current/completed `$turn_duration`; a known zero renders `0s`. |
|
|
@@ -1010,9 +1172,7 @@ with optional groups around labels:
|
|
|
1010
1172
|
}
|
|
1011
1173
|
```
|
|
1012
1174
|
|
|
1013
|
-
PR data
|
|
1014
|
-
capture and recheck repository/branch identity, cache for up to 30 seconds, and
|
|
1015
|
-
invalidate on native project/branch changes. No PR, default/detached branches,
|
|
1175
|
+
PR/CI data uses the [shared demand-controlled collector](#live-metadata), requiring a matching GitHub remote, `gh`, and read access. It captures and rechecks full repository/branch/remote/HEAD identity, caches PR results for 30 seconds and successful repo metadata for five minutes, and invalidates on observed project/branch changes. No PR, default/detached branches,
|
|
1016
1176
|
unavailable tooling, or lookup failure renders empty rather than a placeholder.
|
|
1017
1177
|
|
|
1018
1178
|
**`$usage_quota` is opt-in by explicit template reference.** It uses the current
|
package/docs/footer-format.md
CHANGED
|
@@ -7,6 +7,7 @@ Set `components.footer.styles.starship.format` for complete control over the Sta
|
|
|
7
7
|
- `$variable` and `${variable}` tokens
|
|
8
8
|
- literal text and spaces
|
|
9
9
|
- conditional groups `( ... )` that disappear when every nested variable is empty
|
|
10
|
+
- `$join_sep` conditional field joining
|
|
10
11
|
- `$fill` layout boundaries
|
|
11
12
|
|
|
12
13
|
A custom format overrides `components.footer.styles.starship.segments` for the wide layout. Empty or omitted format uses the segment layout. Responsive mode first reflows wide content, then uses the independent `compactFormat` template; compact variables do not follow built-in segment toggles. `compactMaxLines` limits compact rows, not segment selection. No settings toggle rewrites either template. `$codex_quota` is an exception to segment-toggle bypass: it always requires the independent Footer quota consent toggle and active `openai-codex` provider.
|
|
@@ -112,18 +113,21 @@ The released flat `footerFormat` and `footerSegments` keys remain accepted only
|
|
|
112
113
|
| `$subscription` | | `(sub)` in subscription mode; otherwise empty |
|
|
113
114
|
| `$auto_compaction` | | `(auto)` when automatic compaction is enabled |
|
|
114
115
|
| `$sep` | `$separator` | themed `|` using `colors.separator` |
|
|
116
|
+
| `$join_sep` | — | join populated sibling fields with the styled surface separator |
|
|
115
117
|
| `$fill` | — | wide or compact layout boundary |
|
|
116
118
|
|
|
117
119
|
Each variable renders its core value without prose prefixes such as `on` or `via`; add those words as literals.
|
|
118
120
|
|
|
119
|
-
### OMP host data
|
|
121
|
+
### Shared live metadata and OMP host data
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
Pi and OMP supply `$pr_number`, `$pr_url`, and `$ci` through one passive, demand-controlled GitHub snapshot. Pi also supplies `$token_rate`: the current interaction’s completed model-work average (`48 tok/s avg`, or `— tok/s avg` when unknown), retained until a new interaction. It uses final output divided by summed observed call durations, including initial wait and client preparation but excluding tools/gaps; an in-flight call keeps the completed-calls-so-far aggregate, never a live estimate. Working line retains its separate recent live rate. OMP retains its native rate semantics. See [live metadata](./configuration.md#live-metadata) for identity/head qualification, TTLs, empty/stale/no-checks behavior and lifecycle limits. Templates opt in without rewriting defaults; disabled/native/unowned surfaces do not poll.
|
|
124
|
+
|
|
125
|
+
OMP additionally supplies `$session_id`, `$subagent_count`,
|
|
122
126
|
`$token_rate`, `$active_time`, `$hostname`, `$usage_quota`, `$collaboration`,
|
|
123
127
|
`$stream_state`, `$vim_mode`, `$plan_mode`, `$prewalk_mode`, `$goal_mode`,
|
|
124
128
|
`$vibe_mode`, and `$loop_mode`. These explicit built-ins also work in Opencode
|
|
125
129
|
metadata and Minimalist slots; they are not aliases for OMP's segment IDs or
|
|
126
|
-
third-party `$extensions`. Pi and unsupported/inactive capabilities render empty.
|
|
130
|
+
third-party `$extensions`. Other Pi host-only and unsupported/inactive capabilities render empty.
|
|
127
131
|
See the [data/source reference](./configuration.md#omp-template-data).
|
|
128
132
|
|
|
129
133
|
For example:
|
|
@@ -217,7 +221,7 @@ template:
|
|
|
217
221
|
```
|
|
218
222
|
|
|
219
223
|
These JSON-only aliases cannot replace built-in variables, aliases such as
|
|
220
|
-
`branch`, structural tokens, or prototype names. Missing values are empty;
|
|
224
|
+
`branch`, structural tokens, or prototype names, except a valid explicit `ci` alias takes precedence over GitHub CI and does not demand GitHub. Missing values are empty;
|
|
221
225
|
conditional groups remove associated optional text. No custom value is appended
|
|
222
226
|
outside an explicit template. A compact-only reference creates demand only
|
|
223
227
|
while responsive mode is enabled. Native and Hidden never acquire this registry
|
|
@@ -227,7 +231,7 @@ Editor.
|
|
|
227
231
|
Original is the default color mode and preserves permitted publisher SGR and
|
|
228
232
|
HTTP(S) hyperlinks. Zentui strips incoming styling and uses Footer's own
|
|
229
233
|
`colors.extensionStatus` role and color source. These choices are independent of
|
|
230
|
-
Editor and of the existing `extensionStatuses.colorModes` preferences.
|
|
234
|
+
Editor and of the existing `extensionStatuses.colorModes` preferences. Sparse `components.footer.customValueColors` overrides style individual publisher IDs across aliases in both templates. Explicit styles replace publisher styling; empty is unstyled, Reset deletes one leaf. `/zentui` → Footer → **Individual custom value colors** offers current, aliased and saved keys. See [individual custom value colors](./configuration.md#individual-custom-value-colors).
|
|
231
235
|
|
|
232
236
|
Values are kept whole through wide alignment, reflow, and compact packing.
|
|
233
237
|
When a value would be partially cropped or split, Footer recomposes without it
|
|
@@ -270,6 +274,51 @@ $cwd( on $git_branch)($git_status)$fill($context)
|
|
|
270
274
|
|
|
271
275
|
If every variable inside a group is empty, the group and its literal text are dropped. `$session_name` is available whenever a custom format is set, independently of segment visibility; use a group such as `($sep$session_name)` so unnamed sessions leave no separator.
|
|
272
276
|
|
|
277
|
+
## Conditional joining
|
|
278
|
+
|
|
279
|
+
Use `$join_sep` (or `${join_sep}`) between fields to insert a separator only
|
|
280
|
+
between populated fields:
|
|
281
|
+
|
|
282
|
+
```text
|
|
283
|
+
$session_name$join_sep($git_branch $git_status)
|
|
284
|
+
$a$join_sep$b$join_sep$c
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The first example shows just the session, just the Git field, both with one
|
|
288
|
+
separator, or nothing. Git status can populate its field even without a branch.
|
|
289
|
+
The chain renders `a SEP c` when the middle field is missing, never two gaps.
|
|
290
|
+
Leading, trailing, or consecutive markers cannot create orphan separators.
|
|
291
|
+
Literal-only visible fields count; unknown/unsupported variables are empty.
|
|
292
|
+
Whitespace-only fields, SGR resets, and OSC hyperlink wrappers do not count as
|
|
293
|
+
content. Retained fields keep their ANSI styles, hyperlinks, Unicode and icons.
|
|
294
|
+
|
|
295
|
+
**Do not pad the marker with spaces:** the surface's already styled separator
|
|
296
|
+
owns its spacing. Retained field output is not implicitly trimmed; any literal
|
|
297
|
+
padding inside a populated field remains template-owned. Footer uses its existing
|
|
298
|
+
styled ` | `, Minimalist its configured ` – ` or ` · `, and both Opencode variants
|
|
299
|
+
use neutral border-styled ` · ` for this marker only.
|
|
300
|
+
|
|
301
|
+
Each parenthesized group is a local sibling scope. For example,
|
|
302
|
+
`$a$join_sep($b$join_sep$c)` joins the group as a whole to `a`, while
|
|
303
|
+
`$a($join_sep$b)` cannot borrow `a` to insert a separator inside the group.
|
|
304
|
+
Existing optional-group liveness rules still apply: the marker is not content,
|
|
305
|
+
and join-only or whitespace-only marker groups disappear. Top-level `$fill`
|
|
306
|
+
zones and compact `$wrap`/`$wrap_sep` chunks are hard boundaries: joins cannot
|
|
307
|
+
cross zones, chunks, or rows. Nested fills remain nonstructural. For example,
|
|
308
|
+
`$a$join_sep$fill$join_sep$b` renders separate `a` and `b` zones, and
|
|
309
|
+
`$a$join_sep$wrap_sep$join_sep$b` leaves any same-row separator to the compact
|
|
310
|
+
packer. `$wrap_sep` is not an alias for `$join_sep`.
|
|
311
|
+
|
|
312
|
+
This is additive: templates without the marker keep their old behavior,
|
|
313
|
+
including whitespace-only values, `$sep`/`$separator`, and Footer's existing
|
|
314
|
+
pipe-orphan cleanup. No defaults or saved templates are migrated. Joining happens
|
|
315
|
+
before width fitting; narrow displays still may truncate or omit fields.
|
|
316
|
+
`join_sep` is now reserved and cannot be a custom alias on any template owner;
|
|
317
|
+
rename a previously configured alias with that name. The marker does not resolve
|
|
318
|
+
publisher data or create host/Git/quota demand; the joined fields still do.
|
|
319
|
+
Older installed versions treat it as unknown/empty and cannot conditionally join;
|
|
320
|
+
load or upgrade to a version supporting the marker before using it.
|
|
321
|
+
|
|
273
322
|
## Formatting rules
|
|
274
323
|
|
|
275
324
|
- Literal text, pipes, and spaces render verbatim; the template owns spacing.
|
|
@@ -279,4 +328,4 @@ If every variable inside a group is empty, the group and its literal text are dr
|
|
|
279
328
|
- `DEFAULT_COMPACT_FOOTER_FORMAT` omits model/provider, thinking level, and atomic telemetry. Add variables to `components.footer.styles.starship.compactFormat` to opt in at narrow widths. The flat `compactFooterFormat` key remains migration-only input.
|
|
280
329
|
- Auto-compaction settings refresh at the next normal Footer synchronization. Unsupported Pi capabilities or read errors omit optional markers.
|
|
281
330
|
- Unknown variables render empty.
|
|
282
|
-
- `$fill`, `$wrap`, and `$wrap_sep` are structural and never render visible text.
|
|
331
|
+
- `$fill`, `$wrap`, and `$wrap_sep` are structural and never render visible text. `$join_sep` emits a separator only between populated sibling fields.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type MessageEndResult, parseAssistantMessageTokens } from "./interaction-summary";
|
|
2
|
+
import type { TokenRateMessage } from "./token-rate";
|
|
3
|
+
|
|
4
|
+
type FinalMessage = TokenRateMessage & { stopReason?: unknown };
|
|
5
|
+
type CompletedWork = { output: number; elapsedMs: number; unknown: boolean };
|
|
6
|
+
|
|
7
|
+
function emptyCompletedWork(): CompletedWork {
|
|
8
|
+
return { output: 0, elapsedMs: 0, unknown: false };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function combineCompletedWork(prior: CompletedWork, current: CompletedWork): CompletedWork {
|
|
12
|
+
const output = prior.output + current.output;
|
|
13
|
+
const elapsedMs = prior.elapsedMs + current.elapsedMs;
|
|
14
|
+
if (
|
|
15
|
+
!Number.isSafeInteger(output) ||
|
|
16
|
+
!Number.isFinite(elapsedMs) ||
|
|
17
|
+
elapsedMs > Number.MAX_SAFE_INTEGER
|
|
18
|
+
)
|
|
19
|
+
return { ...prior, unknown: true };
|
|
20
|
+
return { output, elapsedMs, unknown: prior.unknown || current.unknown };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function averageCompletedWork(completed: CompletedWork): number | undefined {
|
|
24
|
+
return !completed.unknown && completed.elapsedMs > 0
|
|
25
|
+
? (completed.output * 1000) / completed.elapsedMs
|
|
26
|
+
: undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Provider final output / summed turn_start -> accepted message_end observations.
|
|
30
|
+
* Includes initial wait and client preparation, not just backend decoding. No tool/gap time.
|
|
31
|
+
* Acceptance and response identity belong to InteractionMetricsTracker, not this accumulator.
|
|
32
|
+
*/
|
|
33
|
+
export class AverageTokenRateTracker {
|
|
34
|
+
/** Only the latest run can survive non-idle settlement; older runs fold in constant space. */
|
|
35
|
+
private priorRuns = emptyCompletedWork();
|
|
36
|
+
private currentRun = emptyCompletedWork();
|
|
37
|
+
private enabled = false;
|
|
38
|
+
private startedAt?: number;
|
|
39
|
+
private clock?: number;
|
|
40
|
+
private priorClock?: number;
|
|
41
|
+
|
|
42
|
+
constructor(private readonly now: () => number = () => performance.now()) {}
|
|
43
|
+
|
|
44
|
+
agentStart(interactionStarted: boolean): void {
|
|
45
|
+
if (interactionStarted) this.reset();
|
|
46
|
+
else {
|
|
47
|
+
this.suspend();
|
|
48
|
+
this.priorRuns = combineCompletedWork(this.priorRuns, this.currentRun);
|
|
49
|
+
this.currentRun = emptyCompletedWork();
|
|
50
|
+
this.priorClock = this.clock ?? this.priorClock;
|
|
51
|
+
this.clock = undefined;
|
|
52
|
+
}
|
|
53
|
+
this.enabled = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
turnStart(at = this.now()): void {
|
|
57
|
+
this.suspend();
|
|
58
|
+
this.enabled = true;
|
|
59
|
+
this.observeTime(at);
|
|
60
|
+
this.startedAt = at;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
observeTime(at = this.now()): void {
|
|
64
|
+
if (!this.enabled) return;
|
|
65
|
+
// Cross-run clock ordering is relevant only while those prior runs remain included.
|
|
66
|
+
if (this.clock === undefined && this.priorClock !== undefined && at < this.priorClock)
|
|
67
|
+
this.priorRuns.unknown = true;
|
|
68
|
+
if (
|
|
69
|
+
!Number.isFinite(at) ||
|
|
70
|
+
at < 0 ||
|
|
71
|
+
at > Number.MAX_SAFE_INTEGER ||
|
|
72
|
+
(this.clock !== undefined && at < this.clock)
|
|
73
|
+
)
|
|
74
|
+
this.currentRun.unknown = true;
|
|
75
|
+
this.clock = at;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
messageEnd(message: FinalMessage, result: MessageEndResult, at = this.now()): void {
|
|
79
|
+
if (result.status !== "accepted" || !this.enabled) return;
|
|
80
|
+
this.observeTime(at);
|
|
81
|
+
const elapsed = this.startedAt === undefined ? NaN : at - this.startedAt;
|
|
82
|
+
this.startedAt = undefined;
|
|
83
|
+
const usage = result.source === "final" ? parseAssistantMessageTokens(message) : undefined;
|
|
84
|
+
const failedPlaceholder =
|
|
85
|
+
(message.stopReason === "error" || message.stopReason === "aborted") &&
|
|
86
|
+
usage?.input === 0 &&
|
|
87
|
+
usage.output === 0;
|
|
88
|
+
if (
|
|
89
|
+
!usage ||
|
|
90
|
+
failedPlaceholder ||
|
|
91
|
+
!Number.isFinite(elapsed) ||
|
|
92
|
+
elapsed <= 0 ||
|
|
93
|
+
!Number.isSafeInteger(this.currentRun.output + usage.output) ||
|
|
94
|
+
!Number.isFinite(this.currentRun.elapsedMs + elapsed) ||
|
|
95
|
+
this.currentRun.elapsedMs + elapsed > Number.MAX_SAFE_INTEGER
|
|
96
|
+
) {
|
|
97
|
+
this.currentRun.unknown = true;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.currentRun.output += usage.output;
|
|
101
|
+
this.currentRun.elapsedMs += elapsed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** An unclosed observed call cannot be silently omitted from an exact aggregate. */
|
|
105
|
+
suspend(): void {
|
|
106
|
+
if (this.startedAt !== undefined) this.currentRun.unknown = true;
|
|
107
|
+
this.startedAt = undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Drop settled runs without disturbing surviving completed work or an open call's anchor. */
|
|
111
|
+
partition(): void {
|
|
112
|
+
this.priorRuns = emptyCompletedWork();
|
|
113
|
+
this.priorClock = undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Summary settlement reads the removed side before dropping it, never combined or surviving work. */
|
|
117
|
+
settle(idle: boolean): number | undefined {
|
|
118
|
+
if (idle) this.suspend();
|
|
119
|
+
const rate = idle ? this.snapshot() : averageCompletedWork(this.priorRuns);
|
|
120
|
+
if (idle) this.reset();
|
|
121
|
+
else this.partition();
|
|
122
|
+
return rate;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
markIncomplete(): void {
|
|
126
|
+
this.currentRun.unknown = true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
reset(): void {
|
|
130
|
+
this.priorRuns = emptyCompletedWork();
|
|
131
|
+
this.currentRun = emptyCompletedWork();
|
|
132
|
+
this.enabled = false;
|
|
133
|
+
this.startedAt = undefined;
|
|
134
|
+
this.clock = undefined;
|
|
135
|
+
this.priorClock = undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
snapshot(): number | undefined {
|
|
139
|
+
const completed = combineCompletedWork(this.priorRuns, this.currentRun);
|
|
140
|
+
return averageCompletedWork(completed);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function formatAverageTokenRate(rate: number | undefined): string {
|
|
145
|
+
return `${rate !== undefined && Number.isFinite(rate) && rate >= 0 ? Math.round(rate) : "—"} tok/s avg`;
|
|
146
|
+
}
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
|
|
8
8
|
export const componentColorKeys = {
|
|
9
9
|
footer: [
|
|
10
|
+
"prNumber",
|
|
11
|
+
"prUrl",
|
|
12
|
+
"ci",
|
|
13
|
+
"tokenRate",
|
|
10
14
|
"cwd",
|
|
11
15
|
"sessionName",
|
|
12
16
|
"gitBranch",
|
|
@@ -29,6 +33,10 @@ export const componentColorKeys = {
|
|
|
29
33
|
"os",
|
|
30
34
|
],
|
|
31
35
|
editor: [
|
|
36
|
+
"prNumber",
|
|
37
|
+
"prUrl",
|
|
38
|
+
"ci",
|
|
39
|
+
"tokenRate",
|
|
32
40
|
"cwd",
|
|
33
41
|
"sessionName",
|
|
34
42
|
"gitBranch",
|
|
@@ -36,6 +44,7 @@ export const componentColorKeys = {
|
|
|
36
44
|
"contextNormal",
|
|
37
45
|
"contextWarning",
|
|
38
46
|
"contextError",
|
|
47
|
+
"cacheHit",
|
|
39
48
|
"cost",
|
|
40
49
|
"sessionDuration",
|
|
41
50
|
"accent",
|
|
@@ -55,7 +64,7 @@ export const componentColorKeys = {
|
|
|
55
64
|
],
|
|
56
65
|
userMessages: ["accent", "border"],
|
|
57
66
|
selectorBorders: ["border"],
|
|
58
|
-
workingLine: ["low", "mid", "high"],
|
|
67
|
+
workingLine: ["low", "mid", "high", "tokenRate", "turnSummary"],
|
|
59
68
|
} as const;
|
|
60
69
|
export type ColorOwner = keyof typeof componentColorKeys;
|
|
61
70
|
export type ComponentColorKey<O extends ColorOwner> = (typeof componentColorKeys)[O][number];
|
|
@@ -103,7 +112,7 @@ export function componentColor(
|
|
|
103
112
|
export function componentColor(
|
|
104
113
|
config: ZentuiConfig,
|
|
105
114
|
owner: "editor",
|
|
106
|
-
key: Exclude<ComponentColorKey<"editor">, keyof typeof editorLegacyKeys>,
|
|
115
|
+
key: Exclude<ComponentColorKey<"editor">, keyof typeof editorLegacyKeys | "cacheHit">,
|
|
107
116
|
): string;
|
|
108
117
|
export function componentColor<O extends ColorOwner>(
|
|
109
118
|
config: ZentuiConfig,
|
|
@@ -119,7 +128,10 @@ export function componentColor<O extends ColorOwner>(
|
|
|
119
128
|
key
|
|
120
129
|
];
|
|
121
130
|
if (typeof local === "string" && isSupportedColorSpec(local)) return local;
|
|
122
|
-
if (owner === "
|
|
131
|
+
if (owner === "workingLine" && key === "turnSummary")
|
|
132
|
+
return componentColor(config, "workingLine", "high");
|
|
133
|
+
if (owner === "selectorBorders" || ["prNumber", "prUrl", "ci", "tokenRate"].includes(key))
|
|
134
|
+
return undefined;
|
|
123
135
|
const legacy =
|
|
124
136
|
owner === "editor"
|
|
125
137
|
? (editorLegacyKeys[key as keyof typeof editorLegacyKeys] ?? key)
|
|
@@ -140,6 +152,8 @@ export function workingLineColor(
|
|
|
140
152
|
): string | undefined {
|
|
141
153
|
const local = config.colors?.[tier];
|
|
142
154
|
if (typeof local === "string" && isSupportedColorSpec(local)) return local;
|
|
155
|
+
if (tier === "tokenRate") return undefined;
|
|
156
|
+
if (tier === "turnSummary") return workingLineColor(config, colors, "high");
|
|
143
157
|
return colors[
|
|
144
158
|
tier === "low" ? "workingLineLow" : tier === "mid" ? "workingLineMid" : "workingLineHigh"
|
|
145
159
|
];
|