@vincemakes/kiso-tui 0.32.2 → 0.33.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/status.d.ts +55 -6
- package/dist/status.js +119 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { Container, foldLine, foldWords, visibleWidth, SPINNER, type Component,
|
|
|
12
12
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
13
13
|
export { bannerLines, COLOR_OFF, COLOR_ON, currentGround, setGround, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderResumeList, renderSessionLine, renderStatusLine, relativeTime, renderTerminalGap, renderToolSummary, TAGLINE, toolTarget, truncateRow, type Palette, type PathResolver, type RecapStats, type ResumeMeta, type RenderInput, type RenderResult, type RunUsage, } from "./lines.js";
|
|
14
14
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
|
15
|
-
export { STATUS_GLYPHS, cacheHitPct, idleStatus, runningStatus, type StatusMeter } from "./status.js";
|
|
15
|
+
export { STATUS_GLYPHS, cacheHitPct, decodeRate, idleStatus, runningStatus, type StatusMeter } from "./status.js";
|
|
16
16
|
export { contextRows, contextUnavailableRows, type ContextLedger } from "./context-ledger.js";
|
|
17
17
|
export { interactivePrompt, projectTrustRows, projectTrustView, projectUntrustedNote, uncertainView, verifyOfferView, type TrustArtifact } from "./strings.js";
|
|
18
18
|
export { AT_CAP, AT_SKIP, AT_VISIBLE, atEmbed, atFilter, atPanelRows, atWindow, bandHeader, longestRun, type AtItem, type AtMatch } from "./at-picker.js";
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ export { bannerLines, COLOR_OFF, COLOR_ON, currentGround, setGround, escapeTermi
|
|
|
19
19
|
export { editFileDiff, truncateDiff, writeFileDiff } from "./diff.js";
|
|
20
20
|
// KC2 §5: the status rows' formatters — the CLI keeps the state and the
|
|
21
21
|
// repaint, the terminal layer owns what the row says.
|
|
22
|
-
export { STATUS_GLYPHS, cacheHitPct, idleStatus, runningStatus } from "./status.js";
|
|
22
|
+
export { STATUS_GLYPHS, cacheHitPct, decodeRate, idleStatus, runningStatus } from "./status.js";
|
|
23
23
|
// TUI2-R1 (E): /context's attribution rows — a pure function of the
|
|
24
24
|
// counts the trace sidecar already records (the CLI reads, this renders).
|
|
25
25
|
export { contextRows, contextUnavailableRows } from "./context-ledger.js";
|
package/dist/status.d.ts
CHANGED
|
@@ -36,6 +36,25 @@
|
|
|
36
36
|
* Glyphs only, no colour: intact under NO_COLOR and on any ground.
|
|
37
37
|
*/
|
|
38
38
|
export declare const STATUS_GLYPHS: readonly ["✧", "✦", "✶", "✸", "✺", "✸", "✦"];
|
|
39
|
+
/**
|
|
40
|
+
* TPS-1 — the settled DECODE rate of one model call: its output tokens
|
|
41
|
+
* over the seconds from the call's first streamed event to its usage
|
|
42
|
+
* event. TTFT is excluded on purpose; this is the speed of the text
|
|
43
|
+
* arriving, which is what "tokens per second" means to the person
|
|
44
|
+
* watching it.
|
|
45
|
+
*
|
|
46
|
+
* The null rule, and every branch of it is the same principle: a number
|
|
47
|
+
* on this row is a MEASUREMENT or it is absent. No output count (the
|
|
48
|
+
* provider reported no usage) is not a zero. Under half a second of
|
|
49
|
+
* decoding is a sample too short to divide by. A non-positive elapsed is
|
|
50
|
+
* a clock, not a rate. And a call that decoded NOTHING has no rate to
|
|
51
|
+
* report: `0 tok/s` would read as a measured speed and it is not one — it
|
|
52
|
+
* is the absence of output, which the transcript already shows. The
|
|
53
|
+
* condition is on the RENDERED INTEGER rather than on the token count, so
|
|
54
|
+
* a slow trickle that rounds to the same `0` reaches the same absence for
|
|
55
|
+
* the same reason.
|
|
56
|
+
*/
|
|
57
|
+
export declare function decodeRate(outputTokens: number | null, elapsedMs: number): number | null;
|
|
39
58
|
/**
|
|
40
59
|
* The RUNNING row: the rotating glyph, the wall seconds since `since`
|
|
41
60
|
* (never below 1 — a run that just started still reads "1s", so the row
|
|
@@ -46,7 +65,7 @@ export declare const STATUS_GLYPHS: readonly ["✧", "✦", "✶", "✸", "✺",
|
|
|
46
65
|
* — stop, and do THIS instead. The row is where the gesture is taught,
|
|
47
66
|
* because it is on screen exactly when the gesture is useful.
|
|
48
67
|
*/
|
|
49
|
-
export declare function runningStatus(glyph: string, since: number, outTokens: number | null, ctxRatio: number): string;
|
|
68
|
+
export declare function runningStatus(glyph: string, since: number, outTokens: number | null, ctxRatio: number, tokPerSec?: number | null): string;
|
|
50
69
|
/**
|
|
51
70
|
* TUI2-R1 (E) — the idle row's meter: what the session has SPENT, next
|
|
52
71
|
* to what it has left.
|
|
@@ -71,12 +90,42 @@ export interface StatusMeter {
|
|
|
71
90
|
* (trace ledger, /context); the field is kept so callers need not
|
|
72
91
|
* change shape, and it renders NOTHING. */
|
|
73
92
|
readonly costUsd: number | null;
|
|
93
|
+
/** TPS-1 — the decode rate of the LAST settled call, carried into the
|
|
94
|
+
* idle row so the figure a person watched during the turn is still
|
|
95
|
+
* there when the turn ends. Null renders nothing (see `decodeRate`);
|
|
96
|
+
* a new model binding starts with none, exactly as the cache figure
|
|
97
|
+
* does (DF-0311-F1: an unmeasured binding has no measurement). */
|
|
98
|
+
readonly tokPerSec: number | null;
|
|
74
99
|
}
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
|
|
100
|
+
/**
|
|
101
|
+
* The IDLE row: the approval tier as the CALLER names it, the /mode hint,
|
|
102
|
+
* the model driving the session, the TUI2-R1 meter when there is one, and
|
|
103
|
+
* the ctx estimate. Called without a meter — or with one that knows
|
|
104
|
+
* nothing — the row is byte-identical to the pre-round row.
|
|
105
|
+
*
|
|
106
|
+
* DF-0330-F1 — THE DROP ORDER. `W` is the row's budget; given one, the row
|
|
107
|
+
* gives ground in a fixed order rather than letting invariant ① cut its
|
|
108
|
+
* end off. Found the hard way: at 100 columns the ` · N tok/s` segment
|
|
109
|
+
* never appeared and at 140 it did, because the row was 102 columns wide
|
|
110
|
+
* and the segment TPS-1 added sat last.
|
|
111
|
+
*
|
|
112
|
+
* 1. the MODEL ID is elided in its middle. It is the only segment that
|
|
113
|
+
* varies, it is the one that grew (the owner's is 35 columns of a
|
|
114
|
+
* 90-column row), and eliding it gives the row back a budget instead
|
|
115
|
+
* of re-allocating a deficit;
|
|
116
|
+
* 2. `/mode to switch` is dropped. It teaches a gesture; `/mode` and `?`
|
|
117
|
+
* still exist and the row is not the only place they are taught;
|
|
118
|
+
* 3. the FACTS are never dropped and never cut — the tier, CH, the ctx
|
|
119
|
+
* estimate and the rate. A row that silently drops a measurement is
|
|
120
|
+
* the defect this rule exists to prevent.
|
|
121
|
+
*
|
|
122
|
+
* The elision is ON THE ROW only. `/model`, the session log and the trace
|
|
123
|
+
* ledger all keep the id whole — the row is a view, never the record.
|
|
124
|
+
*
|
|
125
|
+
* No `W` means no dropping, which is what the callers that do not know
|
|
126
|
+
* their width should get: today's row, unchanged.
|
|
127
|
+
*/
|
|
128
|
+
export declare function idleStatus(tier: string, model: string, ctxRatio: number, meter?: StatusMeter, W?: number): string;
|
|
80
129
|
/** TUI2-R1 (E) — the cache hit rate the status row shows, from the usage
|
|
81
130
|
* the CLI already tracks. The denominator is the TOTAL the model was
|
|
82
131
|
* given (fresh + cacheRead), which is the E2 ruling's own: cacheRead
|
package/dist/status.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { kUnit } from "./lines.js";
|
|
25
25
|
import { TWINKLE } from "@vincemakes/kiso-tui-cells/render";
|
|
26
|
+
import { displayWidth } from "@vincemakes/kiso-tui-cells/width";
|
|
26
27
|
/**
|
|
27
28
|
* R3 (design §5.2) — the working glyph family is the TWINKLE, and the
|
|
28
29
|
* CLI's 200ms spinner walks it exactly as it walked the four quadrant
|
|
@@ -45,6 +46,32 @@ export const STATUS_GLYPHS = TWINKLE;
|
|
|
45
46
|
function ctxLeft(ratio) {
|
|
46
47
|
return Number.isFinite(ratio) ? Math.round((1 - ratio) * 100) : null;
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* TPS-1 — the settled DECODE rate of one model call: its output tokens
|
|
51
|
+
* over the seconds from the call's first streamed event to its usage
|
|
52
|
+
* event. TTFT is excluded on purpose; this is the speed of the text
|
|
53
|
+
* arriving, which is what "tokens per second" means to the person
|
|
54
|
+
* watching it.
|
|
55
|
+
*
|
|
56
|
+
* The null rule, and every branch of it is the same principle: a number
|
|
57
|
+
* on this row is a MEASUREMENT or it is absent. No output count (the
|
|
58
|
+
* provider reported no usage) is not a zero. Under half a second of
|
|
59
|
+
* decoding is a sample too short to divide by. A non-positive elapsed is
|
|
60
|
+
* a clock, not a rate. And a call that decoded NOTHING has no rate to
|
|
61
|
+
* report: `0 tok/s` would read as a measured speed and it is not one — it
|
|
62
|
+
* is the absence of output, which the transcript already shows. The
|
|
63
|
+
* condition is on the RENDERED INTEGER rather than on the token count, so
|
|
64
|
+
* a slow trickle that rounds to the same `0` reaches the same absence for
|
|
65
|
+
* the same reason.
|
|
66
|
+
*/
|
|
67
|
+
export function decodeRate(outputTokens, elapsedMs) {
|
|
68
|
+
if (outputTokens === null)
|
|
69
|
+
return null;
|
|
70
|
+
if (!Number.isFinite(elapsedMs) || elapsedMs < 500)
|
|
71
|
+
return null;
|
|
72
|
+
const rate = Math.round(outputTokens / (elapsedMs / 1000));
|
|
73
|
+
return rate > 0 ? rate : null;
|
|
74
|
+
}
|
|
48
75
|
/**
|
|
49
76
|
* The RUNNING row: the rotating glyph, the wall seconds since `since`
|
|
50
77
|
* (never below 1 — a run that just started still reads "1s", so the row
|
|
@@ -55,22 +82,101 @@ function ctxLeft(ratio) {
|
|
|
55
82
|
* — stop, and do THIS instead. The row is where the gesture is taught,
|
|
56
83
|
* because it is on screen exactly when the gesture is useful.
|
|
57
84
|
*/
|
|
58
|
-
export function runningStatus(glyph, since, outTokens, ctxRatio) {
|
|
85
|
+
export function runningStatus(glyph, since, outTokens, ctxRatio, tokPerSec = null) {
|
|
59
86
|
const out = outTokens !== null ? ` ↓ ${kUnit(outTokens)} tokens` : "";
|
|
87
|
+
// TPS-1: after each call SETTLES within the turn, between the tokens
|
|
88
|
+
// segment and the stop hint. The default is null and that is the honest
|
|
89
|
+
// rule spelled as a default — the recovery flow has no per-call timing
|
|
90
|
+
// state, so its row says nothing rather than guessing.
|
|
91
|
+
const rate = tokPerSec !== null ? ` · ${tokPerSec} tok/s` : "";
|
|
60
92
|
const seconds = Math.max(1, Math.round((Date.now() - since) / 1000));
|
|
61
|
-
return `${glyph} working ${seconds}s${out} · esc stop · alt+⏎ redirect · ctx left ~${ctxLeft(ctxRatio)}%`;
|
|
93
|
+
return `${glyph} working ${seconds}s${out}${rate} · esc stop · alt+⏎ redirect · ctx left ~${ctxLeft(ctxRatio)}%`;
|
|
94
|
+
}
|
|
95
|
+
/** DF-0330-F1 — how far the model id may be squeezed on the ROW. Twenty
|
|
96
|
+
* visible columns keeps a head and a tail: `deepseek-v…s-on-0910` still
|
|
97
|
+
* says which binding is driving, and the tail is where the parts that
|
|
98
|
+
* distinguish one id from its neighbours live (`-flash`, `-0910`). */
|
|
99
|
+
const MODEL_ON_ROW = 20;
|
|
100
|
+
/** Elide in the MIDDLE, keeping the head and the tail. A string already
|
|
101
|
+
* within budget is returned untouched, so this is a no-op for every
|
|
102
|
+
* ordinary model name.
|
|
103
|
+
*
|
|
104
|
+
* Budgeted in DISPLAY COLUMNS, not code points. A first version sliced
|
|
105
|
+
* code points and a wide-character id came out at 28 columns while the
|
|
106
|
+
* function claimed 20 — which would have put the row straight back over
|
|
107
|
+
* its budget and handed it to invariant ①'s cut, the exact defect this
|
|
108
|
+
* whole change exists to prevent. No such model id exists today; the
|
|
109
|
+
* guarantee should not depend on that staying true. */
|
|
110
|
+
function elideMiddle(text, max) {
|
|
111
|
+
if (displayWidth(text) <= max)
|
|
112
|
+
return text;
|
|
113
|
+
const chars = [...text];
|
|
114
|
+
const room = max - 1; // the ellipsis costs one column
|
|
115
|
+
const headBudget = Math.ceil(room / 2);
|
|
116
|
+
let head = "";
|
|
117
|
+
for (const c of chars) {
|
|
118
|
+
if (displayWidth(head + c) > headBudget)
|
|
119
|
+
break;
|
|
120
|
+
head += c;
|
|
121
|
+
}
|
|
122
|
+
let tail = "";
|
|
123
|
+
const tailBudget = room - displayWidth(head);
|
|
124
|
+
for (let i = chars.length - 1; i >= 0; i -= 1) {
|
|
125
|
+
if (displayWidth(chars[i] + tail) > tailBudget)
|
|
126
|
+
break;
|
|
127
|
+
tail = chars[i] + tail;
|
|
128
|
+
}
|
|
129
|
+
return `${head}…${tail}`;
|
|
62
130
|
}
|
|
63
|
-
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
131
|
+
/**
|
|
132
|
+
* The IDLE row: the approval tier as the CALLER names it, the /mode hint,
|
|
133
|
+
* the model driving the session, the TUI2-R1 meter when there is one, and
|
|
134
|
+
* the ctx estimate. Called without a meter — or with one that knows
|
|
135
|
+
* nothing — the row is byte-identical to the pre-round row.
|
|
136
|
+
*
|
|
137
|
+
* DF-0330-F1 — THE DROP ORDER. `W` is the row's budget; given one, the row
|
|
138
|
+
* gives ground in a fixed order rather than letting invariant ① cut its
|
|
139
|
+
* end off. Found the hard way: at 100 columns the ` · N tok/s` segment
|
|
140
|
+
* never appeared and at 140 it did, because the row was 102 columns wide
|
|
141
|
+
* and the segment TPS-1 added sat last.
|
|
142
|
+
*
|
|
143
|
+
* 1. the MODEL ID is elided in its middle. It is the only segment that
|
|
144
|
+
* varies, it is the one that grew (the owner's is 35 columns of a
|
|
145
|
+
* 90-column row), and eliding it gives the row back a budget instead
|
|
146
|
+
* of re-allocating a deficit;
|
|
147
|
+
* 2. `/mode to switch` is dropped. It teaches a gesture; `/mode` and `?`
|
|
148
|
+
* still exist and the row is not the only place they are taught;
|
|
149
|
+
* 3. the FACTS are never dropped and never cut — the tier, CH, the ctx
|
|
150
|
+
* estimate and the rate. A row that silently drops a measurement is
|
|
151
|
+
* the defect this rule exists to prevent.
|
|
152
|
+
*
|
|
153
|
+
* The elision is ON THE ROW only. `/model`, the session log and the trace
|
|
154
|
+
* ledger all keep the id whole — the row is a view, never the record.
|
|
155
|
+
*
|
|
156
|
+
* No `W` means no dropping, which is what the callers that do not know
|
|
157
|
+
* their width should get: today's row, unchanged.
|
|
158
|
+
*/
|
|
159
|
+
export function idleStatus(tier, model, ctxRatio, meter, W) {
|
|
160
|
+
const compose = (label, hint) => {
|
|
161
|
+
const parts = [`▸ ${tier}`];
|
|
162
|
+
if (hint)
|
|
163
|
+
parts.push("/mode to switch");
|
|
164
|
+
parts.push(label);
|
|
165
|
+
if (meter?.cacheHitPct != null)
|
|
166
|
+
parts.push(`CH ${Math.round(meter.cacheHitPct)}%`);
|
|
167
|
+
// costUsd deliberately NOT rendered — see StatusMeter.costUsd.
|
|
168
|
+
parts.push(`ctx left ~${ctxLeft(ctxRatio)}%`);
|
|
169
|
+
if (meter?.tokPerSec != null)
|
|
170
|
+
parts.push(`${meter.tokPerSec} tok/s`); // TPS-1: last, after the ctx estimate
|
|
171
|
+
return parts.join(" · ");
|
|
172
|
+
};
|
|
173
|
+
const full = compose(model, true);
|
|
174
|
+
if (W === undefined || displayWidth(full) <= W)
|
|
175
|
+
return full;
|
|
176
|
+
const squeezed = compose(elideMiddle(model, MODEL_ON_ROW), true);
|
|
177
|
+
if (displayWidth(squeezed) <= W)
|
|
178
|
+
return squeezed;
|
|
179
|
+
return compose(elideMiddle(model, MODEL_ON_ROW), false);
|
|
74
180
|
}
|
|
75
181
|
/** TUI2-R1 (E) — the cache hit rate the status row shows, from the usage
|
|
76
182
|
* the CLI already tracks. The denominator is the TOTAL the model was
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"description": "kiso tui — the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://github.com/vincemakes/kiso/tree/main/packages/tui#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@vincemakes/kiso-tui-cells": "0.
|
|
38
|
+
"@vincemakes/kiso-tui-cells": "0.33.0"
|
|
39
39
|
}
|
|
40
40
|
}
|