@huseynsnmz/pi-suite 0.0.0-stage → 0.2.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { hostname } from "node:os";
|
|
2
|
+
|
|
3
|
+
import type { AccountState, Board, PeerMark, RoomMark, WritingMark } from "../kit/index.ts";
|
|
4
|
+
import { VERSION, type ExtensionContext, type SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
import { plainText } from "../render/safe.ts";
|
|
7
|
+
|
|
8
|
+
import type { GitState } from "./git.ts";
|
|
9
|
+
import type { RequestView, RunView } from "./telemetry.ts";
|
|
10
|
+
import {
|
|
11
|
+
type Averages,
|
|
12
|
+
averagesOf,
|
|
13
|
+
cacheHitPercent,
|
|
14
|
+
cacheWarming,
|
|
15
|
+
emptyTotals,
|
|
16
|
+
type Totals,
|
|
17
|
+
totalsOf,
|
|
18
|
+
} from "./totals.ts";
|
|
19
|
+
|
|
20
|
+
interface ModelInfo {
|
|
21
|
+
name: string;
|
|
22
|
+
provider: string;
|
|
23
|
+
reasoning: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ContextInfo {
|
|
27
|
+
percent: number;
|
|
28
|
+
tokens: number;
|
|
29
|
+
window: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** pi-sandbox sets this while it's on, and empties it when it goes off. */
|
|
33
|
+
export const SANDBOX_STATUS = "sandbox";
|
|
34
|
+
|
|
35
|
+
export interface Snapshot {
|
|
36
|
+
now: number;
|
|
37
|
+
cwd: string;
|
|
38
|
+
home: string | undefined;
|
|
39
|
+
branch: string | null;
|
|
40
|
+
git: GitState | undefined;
|
|
41
|
+
host: string | undefined;
|
|
42
|
+
sessionName: string | undefined;
|
|
43
|
+
/** The word the room calls this session by, in its color once it is in a room. */
|
|
44
|
+
me: { handle: string; color?: string } | undefined;
|
|
45
|
+
version: string;
|
|
46
|
+
model: ModelInfo | undefined;
|
|
47
|
+
account: AccountState | undefined;
|
|
48
|
+
thinking: string | undefined;
|
|
49
|
+
context: ContextInfo | undefined;
|
|
50
|
+
totals: Totals;
|
|
51
|
+
cacheHit: number | undefined;
|
|
52
|
+
cacheWarming: boolean;
|
|
53
|
+
subscription: boolean;
|
|
54
|
+
request: RequestView | undefined;
|
|
55
|
+
last: RequestView | undefined;
|
|
56
|
+
run: RunView | undefined;
|
|
57
|
+
averages: Averages;
|
|
58
|
+
statuses: ReadonlyMap<string, string>;
|
|
59
|
+
/** Whether pi-sandbox is confining commands. Undefined when it isn't installed. */
|
|
60
|
+
sandbox: "on" | "off" | undefined;
|
|
61
|
+
/** When a board feature keeps it. */
|
|
62
|
+
board: Board | undefined;
|
|
63
|
+
/** When a peers feature keeps them. */
|
|
64
|
+
peers: PeerMark[];
|
|
65
|
+
/** Empty when the peers feature publishes only `peers`. */
|
|
66
|
+
peersRooms: RoomMark[];
|
|
67
|
+
peersUnread: number;
|
|
68
|
+
peersWriting: WritingMark[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function emptySnapshot(): Snapshot {
|
|
72
|
+
return {
|
|
73
|
+
now: 0,
|
|
74
|
+
cwd: "",
|
|
75
|
+
home: undefined,
|
|
76
|
+
branch: null,
|
|
77
|
+
git: undefined,
|
|
78
|
+
host: undefined,
|
|
79
|
+
sessionName: undefined,
|
|
80
|
+
me: undefined,
|
|
81
|
+
version: VERSION,
|
|
82
|
+
model: undefined,
|
|
83
|
+
account: undefined,
|
|
84
|
+
thinking: undefined,
|
|
85
|
+
context: undefined,
|
|
86
|
+
totals: emptyTotals(),
|
|
87
|
+
cacheHit: undefined,
|
|
88
|
+
cacheWarming: false,
|
|
89
|
+
subscription: false,
|
|
90
|
+
request: undefined,
|
|
91
|
+
last: undefined,
|
|
92
|
+
run: undefined,
|
|
93
|
+
averages: { decode: undefined, prefill: undefined },
|
|
94
|
+
statuses: new Map(),
|
|
95
|
+
sandbox: undefined,
|
|
96
|
+
board: undefined,
|
|
97
|
+
peers: [],
|
|
98
|
+
peersRooms: [],
|
|
99
|
+
peersUnread: 0,
|
|
100
|
+
peersWriting: [],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface LiveInputs {
|
|
105
|
+
branch: string | null;
|
|
106
|
+
git: GitState | undefined;
|
|
107
|
+
account?: AccountState | undefined;
|
|
108
|
+
request: RequestView | undefined;
|
|
109
|
+
last: RequestView | undefined;
|
|
110
|
+
run: RunView | undefined;
|
|
111
|
+
statuses: ReadonlyMap<string, string>;
|
|
112
|
+
sandboxInstalled?: boolean;
|
|
113
|
+
board?: Board | undefined;
|
|
114
|
+
me?: { handle: string; color?: string } | undefined;
|
|
115
|
+
peers?: PeerMark[] | undefined;
|
|
116
|
+
peersRooms?: RoomMark[] | undefined;
|
|
117
|
+
peersUnread?: number | undefined;
|
|
118
|
+
peersWriting?: WritingMark[] | undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface Derived {
|
|
122
|
+
totals: Totals;
|
|
123
|
+
cacheHit: number | undefined;
|
|
124
|
+
cacheWarming: boolean;
|
|
125
|
+
averages: Averages;
|
|
126
|
+
context: ContextInfo | undefined;
|
|
127
|
+
answeredBy: string | undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const VIRTUAL_API = "pi-virtual";
|
|
131
|
+
|
|
132
|
+
export class SnapshotReader {
|
|
133
|
+
#key: string | undefined;
|
|
134
|
+
#derived: Derived | undefined;
|
|
135
|
+
readonly #host = hostname().split(".")[0];
|
|
136
|
+
|
|
137
|
+
read(ctx: ExtensionContext, live: LiveInputs): Snapshot {
|
|
138
|
+
const model = ctx.model;
|
|
139
|
+
const derived = this.#derive(ctx);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
now: Date.now(),
|
|
143
|
+
cwd: ctx.cwd,
|
|
144
|
+
home: process.env.HOME ?? process.env.USERPROFILE,
|
|
145
|
+
branch: live.branch,
|
|
146
|
+
git: live.git,
|
|
147
|
+
host: this.#host,
|
|
148
|
+
sessionName: ctx.sessionManager.getSessionName(),
|
|
149
|
+
me: live.me,
|
|
150
|
+
version: VERSION,
|
|
151
|
+
model:
|
|
152
|
+
model === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: {
|
|
155
|
+
name:
|
|
156
|
+
model.api === VIRTUAL_API && derived.answeredBy !== undefined
|
|
157
|
+
? `${model.name} \u2192 ${derived.answeredBy}`
|
|
158
|
+
: (model.name ?? model.id),
|
|
159
|
+
provider: ctx.modelRegistry.getProviderDisplayName(model.provider),
|
|
160
|
+
reasoning: model.reasoning,
|
|
161
|
+
},
|
|
162
|
+
thinking: ctx.thinkingLevel,
|
|
163
|
+
account: live.account,
|
|
164
|
+
...derived,
|
|
165
|
+
subscription: model === undefined ? false : ctx.modelRegistry.isUsingOAuth(model),
|
|
166
|
+
request: live.request,
|
|
167
|
+
last: live.last,
|
|
168
|
+
run: live.run,
|
|
169
|
+
statuses: new Map(
|
|
170
|
+
[...live.statuses.entries()]
|
|
171
|
+
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
172
|
+
.map(([key, text]): [string, string] => [key, sanitize(text)])
|
|
173
|
+
.filter(([, text]) => text !== ""),
|
|
174
|
+
),
|
|
175
|
+
sandbox: !live.sandboxInstalled ? undefined : live.statuses.get(SANDBOX_STATUS) ? "on" : "off",
|
|
176
|
+
board: live.board,
|
|
177
|
+
peers: live.peers ?? [],
|
|
178
|
+
peersRooms: live.peersRooms ?? [],
|
|
179
|
+
peersUnread: live.peersUnread ?? 0,
|
|
180
|
+
peersWriting: live.peersWriting ?? [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#derive(ctx: ExtensionContext): Derived {
|
|
185
|
+
// Entries are appended at the leaf, so the sums change only when the leaf or the model does.
|
|
186
|
+
const key = `${ctx.sessionManager.getLeafId() ?? ""}\u0000${ctx.model?.provider}/${ctx.model?.id}`;
|
|
187
|
+
if (this.#derived !== undefined && key === this.#key) return this.#derived;
|
|
188
|
+
|
|
189
|
+
const entries: readonly SessionEntry[] = ctx.sessionManager.getBranch();
|
|
190
|
+
this.#key = key;
|
|
191
|
+
this.#derived = {
|
|
192
|
+
totals: totalsOf(entries),
|
|
193
|
+
cacheHit: cacheHitPercent(entries),
|
|
194
|
+
cacheWarming: cacheWarming(entries),
|
|
195
|
+
averages: averagesOf(entries),
|
|
196
|
+
context: contextOf(ctx),
|
|
197
|
+
answeredBy: answeredBy(ctx, entries),
|
|
198
|
+
};
|
|
199
|
+
return this.#derived;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** A virtual model only says what was selected. The latest answer says which model it chose. */
|
|
204
|
+
function answeredBy(ctx: ExtensionContext, entries: readonly SessionEntry[]): string | undefined {
|
|
205
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
206
|
+
const entry = entries[index];
|
|
207
|
+
if (entry?.type !== "message" || entry.message.role !== "assistant") continue;
|
|
208
|
+
if (entry.message.stopReason === "error" || entry.message.stopReason === "aborted") continue;
|
|
209
|
+
const { provider, model } = entry.message;
|
|
210
|
+
return plainText(ctx.modelRegistry.find(provider, model)?.name ?? model).replace(/\s+/g, " ");
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function contextOf(ctx: ExtensionContext): ContextInfo | undefined {
|
|
216
|
+
const usage = ctx.getContextUsage();
|
|
217
|
+
if (usage === undefined || usage.percent === null || usage.tokens === null) return undefined;
|
|
218
|
+
return { percent: usage.percent, tokens: usage.tokens, window: usage.contextWindow };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function sanitize(text: string): string {
|
|
222
|
+
return text.replace(/[\r\n\t]+/g, " ").trim();
|
|
223
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Under the folder and the model, only what changed or waits on you since the last session here.
|
|
2
|
+
import { formatClock, GLYPH } from "../kit/index.ts";
|
|
3
|
+
|
|
4
|
+
import type { GithubState } from "./github.ts";
|
|
5
|
+
import type { Snapshot } from "./snapshot.ts";
|
|
6
|
+
|
|
7
|
+
const PLAN_LIMIT = 70;
|
|
8
|
+
|
|
9
|
+
export interface LastVisit {
|
|
10
|
+
at: number;
|
|
11
|
+
version: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StartNote {
|
|
15
|
+
mark: string;
|
|
16
|
+
color: string;
|
|
17
|
+
text: string;
|
|
18
|
+
detail?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function startNotes(
|
|
22
|
+
snapshot: Pick<Snapshot, "peers" | "account" | "git" | "branch">,
|
|
23
|
+
version: string,
|
|
24
|
+
last: LastVisit | undefined,
|
|
25
|
+
github?: GithubState,
|
|
26
|
+
): StartNote[] {
|
|
27
|
+
const notes: StartNote[] = [];
|
|
28
|
+
const { account, git } = snapshot;
|
|
29
|
+
|
|
30
|
+
if (account?.needsLogin === true)
|
|
31
|
+
notes.push({
|
|
32
|
+
mark: GLYPH.cross,
|
|
33
|
+
color: "error",
|
|
34
|
+
text: `${account.label} needs a sign-in`,
|
|
35
|
+
detail: "/accounts",
|
|
36
|
+
});
|
|
37
|
+
if (git !== undefined && git.conflicted > 0)
|
|
38
|
+
notes.push({ mark: GLYPH.cross, color: "error", text: `${git.conflicted} files in conflict` });
|
|
39
|
+
if (git !== undefined && git.behind > 0)
|
|
40
|
+
notes.push({
|
|
41
|
+
mark: GLYPH.down,
|
|
42
|
+
color: "warning",
|
|
43
|
+
text: `${snapshot.branch ?? "this branch"} is ${git.behind} behind`,
|
|
44
|
+
detail: "pull before you start",
|
|
45
|
+
});
|
|
46
|
+
if (github?.ci !== undefined)
|
|
47
|
+
notes.push({
|
|
48
|
+
mark: GLYPH.cross,
|
|
49
|
+
color: "error",
|
|
50
|
+
text: `CI failed on ${github.ci.branch}`,
|
|
51
|
+
detail: `${github.ci.sha} ${github.ci.name}`,
|
|
52
|
+
});
|
|
53
|
+
if (github?.pr !== undefined)
|
|
54
|
+
notes.push({
|
|
55
|
+
mark: GLYPH.flag,
|
|
56
|
+
color: "warning",
|
|
57
|
+
text: `${github.pr.ref ?? `PR #${github.pr.number}`} ${github.pr.title}`,
|
|
58
|
+
detail: github.pr.waits,
|
|
59
|
+
});
|
|
60
|
+
for (const peer of snapshot.peers)
|
|
61
|
+
notes.push({
|
|
62
|
+
mark: GLYPH.diamond,
|
|
63
|
+
color: peer.color,
|
|
64
|
+
text: `@${peer.handle}`,
|
|
65
|
+
detail: peerState(peer.state),
|
|
66
|
+
});
|
|
67
|
+
for (const window of account?.windows ?? []) {
|
|
68
|
+
if (window.used < PLAN_LIMIT) continue;
|
|
69
|
+
notes.push({
|
|
70
|
+
mark: GLYPH.up,
|
|
71
|
+
color: window.used >= 90 ? "error" : "warning",
|
|
72
|
+
text: `${window.name} at ${Math.round(window.used)}%`,
|
|
73
|
+
detail: window.resetsIn === undefined ? undefined : `resets in ${window.resetsIn}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (last !== undefined && last.version !== version)
|
|
77
|
+
notes.push({
|
|
78
|
+
mark: GLYPH.up,
|
|
79
|
+
color: "accent",
|
|
80
|
+
text: `${GLYPH.pi}-frame ${version} is new`,
|
|
81
|
+
detail: `was ${last.version}`,
|
|
82
|
+
});
|
|
83
|
+
return notes;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function peerState(state: "running" | "idle" | "waiting"): string {
|
|
87
|
+
switch (state) {
|
|
88
|
+
case "running":
|
|
89
|
+
return "works here";
|
|
90
|
+
case "waiting":
|
|
91
|
+
return "waits for you";
|
|
92
|
+
case "idle":
|
|
93
|
+
return "is open here";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Local time, or the day when it was not today. */
|
|
98
|
+
export function since(at: number, now: number): string {
|
|
99
|
+
const then = new Date(at);
|
|
100
|
+
const time = formatClock(at);
|
|
101
|
+
if (new Date(now).toDateString() === then.toDateString()) return time;
|
|
102
|
+
const days = Math.round((now - at) / 86_400_000);
|
|
103
|
+
return days <= 1 ? `yesterday ${time}` : `${days} days ago`;
|
|
104
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type { SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { STALL_MS } from "../render/timing.ts";
|
|
4
|
+
import {
|
|
5
|
+
emptyTotals,
|
|
6
|
+
perSecond,
|
|
7
|
+
promptOf,
|
|
8
|
+
REQUEST_CHANGES,
|
|
9
|
+
type RequestRecord,
|
|
10
|
+
type Totals,
|
|
11
|
+
usageTotals,
|
|
12
|
+
} from "./totals.ts";
|
|
13
|
+
|
|
14
|
+
const MIN_SAMPLE_MS = 250;
|
|
15
|
+
/** Measured over 16,889 assistant messages: 2.88 characters per output token. */
|
|
16
|
+
const DEFAULT_CHARS_PER_TOKEN = 2.9;
|
|
17
|
+
|
|
18
|
+
/** So the wait for the first thought can be told from the first word. */
|
|
19
|
+
export type DeltaKind = "thinking" | "writing";
|
|
20
|
+
|
|
21
|
+
export interface RequestView {
|
|
22
|
+
streaming: boolean;
|
|
23
|
+
waiting: boolean;
|
|
24
|
+
elapsedMs: number;
|
|
25
|
+
/** From the request leaving to the first token of any kind. */
|
|
26
|
+
waitMs: number | undefined;
|
|
27
|
+
/** The same wait while it runs. */
|
|
28
|
+
waitingMs: number;
|
|
29
|
+
/** To the response headers: the provider's own latency. */
|
|
30
|
+
serverMs: number | undefined;
|
|
31
|
+
/** From the headers to the first token: the model reading the prompt. */
|
|
32
|
+
prefillMs: number | undefined;
|
|
33
|
+
/** Unset when it wrote without thinking. */
|
|
34
|
+
thoughtMs: number | undefined;
|
|
35
|
+
decode: number | undefined;
|
|
36
|
+
prefill: number | undefined;
|
|
37
|
+
usage: Totals;
|
|
38
|
+
estimated: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** One call to the model, for the line under its response. */
|
|
42
|
+
export interface Timing {
|
|
43
|
+
/** When it left. */
|
|
44
|
+
at: number;
|
|
45
|
+
streaming: boolean;
|
|
46
|
+
waitMs: number | undefined;
|
|
47
|
+
waitingMs: number;
|
|
48
|
+
serverMs: number | undefined;
|
|
49
|
+
thoughtMs: number | undefined;
|
|
50
|
+
totalMs: number;
|
|
51
|
+
writingMs: number;
|
|
52
|
+
/** Pauses of `STALL_MS` or more while it wrote. */
|
|
53
|
+
stallMs: number;
|
|
54
|
+
output: number;
|
|
55
|
+
estimated: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface RunView {
|
|
59
|
+
running: boolean;
|
|
60
|
+
elapsedMs: number;
|
|
61
|
+
requests: number;
|
|
62
|
+
cost: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface Request {
|
|
66
|
+
startedAt: number;
|
|
67
|
+
sentAt: number | undefined;
|
|
68
|
+
headersAt: number | undefined;
|
|
69
|
+
firstTokenAt: number | undefined;
|
|
70
|
+
firstWritingAt: number | undefined;
|
|
71
|
+
endedAt: number | undefined;
|
|
72
|
+
messageAt?: number;
|
|
73
|
+
usage: Totals;
|
|
74
|
+
characters: number;
|
|
75
|
+
lastDeltaAt: number | undefined;
|
|
76
|
+
stallMs: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface Run {
|
|
80
|
+
startedAt: number;
|
|
81
|
+
endedAt: number | undefined;
|
|
82
|
+
requests: number;
|
|
83
|
+
cost: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface UsageLike {
|
|
87
|
+
input: number;
|
|
88
|
+
output: number;
|
|
89
|
+
cacheRead: number;
|
|
90
|
+
cacheWrite: number;
|
|
91
|
+
cost: { total: number };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function span(from: number | undefined, to: number | undefined): number | undefined {
|
|
95
|
+
return from === undefined || to === undefined ? undefined : to - from;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class Telemetry {
|
|
99
|
+
readonly #now: () => number;
|
|
100
|
+
#run: Run | undefined;
|
|
101
|
+
#request: Request | undefined;
|
|
102
|
+
#last: RequestView | undefined;
|
|
103
|
+
/** By the response's timestamp, which stays the same while it's written. */
|
|
104
|
+
#byMessage = new Map<number, Request>();
|
|
105
|
+
#restored = new Map<number, Timing>();
|
|
106
|
+
/** They arrive before the pi message they belong to. */
|
|
107
|
+
#sentAt: number | undefined;
|
|
108
|
+
#headersAt: number | undefined;
|
|
109
|
+
/** So the live estimate uses a real ratio. */
|
|
110
|
+
#sampled = { characters: 0, output: 0 };
|
|
111
|
+
|
|
112
|
+
constructor(now: () => number = Date.now) {
|
|
113
|
+
this.#now = now;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
runStarted(): void {
|
|
117
|
+
this.#run = { startedAt: this.#now(), endedAt: undefined, requests: 0, cost: 0 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
runEnded(): void {
|
|
121
|
+
if (this.#run !== undefined && this.#run.endedAt === undefined) this.#run.endedAt = this.#now();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Before every call to the model: the start of the wait. */
|
|
125
|
+
providerRequestSent(): void {
|
|
126
|
+
this.#sentAt = this.#now();
|
|
127
|
+
this.#headersAt = undefined;
|
|
128
|
+
this.#attach();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Before the stream is read. */
|
|
132
|
+
providerResponseArrived(): void {
|
|
133
|
+
this.#headersAt = this.#now();
|
|
134
|
+
this.#attach();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
requestStarted(): void {
|
|
138
|
+
this.#sentAt = undefined;
|
|
139
|
+
this.#headersAt = undefined;
|
|
140
|
+
this.#begin();
|
|
141
|
+
if (this.#run === undefined || this.#run.endedAt !== undefined) this.runStarted();
|
|
142
|
+
if (this.#run !== undefined) this.#run.requests++;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Later requests of a turn: the next call after a tool ran. */
|
|
146
|
+
answerStarted(messageAt?: number): void {
|
|
147
|
+
if (this.#request === undefined || this.#request.endedAt !== undefined) this.#begin();
|
|
148
|
+
if (messageAt !== undefined && this.#request !== undefined) {
|
|
149
|
+
this.#byMessage.set(messageAt, this.#request);
|
|
150
|
+
this.#request.messageAt = messageAt;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
answerGrew(delta: string, usage: UsageLike, kind: DeltaKind): void {
|
|
155
|
+
const request = this.#request;
|
|
156
|
+
if (request === undefined || request.endedAt !== undefined) return;
|
|
157
|
+
request.usage = usageTotals(usage);
|
|
158
|
+
if (delta.length === 0) return;
|
|
159
|
+
const grewAt = this.#now();
|
|
160
|
+
const gap = grewAt - (request.lastDeltaAt ?? grewAt);
|
|
161
|
+
if (gap >= STALL_MS) request.stallMs += gap;
|
|
162
|
+
request.lastDeltaAt = grewAt;
|
|
163
|
+
request.characters += delta.length;
|
|
164
|
+
request.firstTokenAt ??= this.#now();
|
|
165
|
+
if (kind === "writing") request.firstWritingAt ??= this.#now();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
answerEnded(usage: UsageLike): RequestRecord | undefined {
|
|
169
|
+
const request = this.#request;
|
|
170
|
+
if (request === undefined || request.endedAt !== undefined) return undefined;
|
|
171
|
+
|
|
172
|
+
const now = this.#now();
|
|
173
|
+
request.endedAt = now;
|
|
174
|
+
request.usage = usageTotals(usage);
|
|
175
|
+
if (this.#run !== undefined) this.#run.cost += usage.cost.total;
|
|
176
|
+
if (request.usage.output === 0 && request.firstTokenAt === undefined) return undefined;
|
|
177
|
+
|
|
178
|
+
if (request.characters > 0 && request.usage.output > 0) {
|
|
179
|
+
this.#sampled.characters += request.characters;
|
|
180
|
+
this.#sampled.output += request.usage.output;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const firstTokenAt = request.firstTokenAt ?? now;
|
|
184
|
+
const from = request.sentAt ?? request.startedAt;
|
|
185
|
+
this.#last = this.#view(request);
|
|
186
|
+
const timing = this.#timing(request);
|
|
187
|
+
return {
|
|
188
|
+
output: request.usage.output,
|
|
189
|
+
prompt: promptOf(request.usage),
|
|
190
|
+
firstTokenMs: firstTokenAt - from,
|
|
191
|
+
generationMs: now - firstTokenAt,
|
|
192
|
+
at: from,
|
|
193
|
+
messageAt: request.messageAt,
|
|
194
|
+
serverMs: span(request.sentAt, request.headersAt),
|
|
195
|
+
thoughtMs: timing.thoughtMs,
|
|
196
|
+
totalMs: now - from,
|
|
197
|
+
stallMs: request.stallMs,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
timingOf(messageAt: number): Timing | undefined {
|
|
202
|
+
const request = this.#byMessage.get(messageAt);
|
|
203
|
+
return request === undefined ? this.#restored.get(messageAt) : this.#timing(request);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The calls an earlier run of this session recorded. */
|
|
207
|
+
restore(entries: readonly SessionEntry[]): void {
|
|
208
|
+
for (const entry of entries) {
|
|
209
|
+
if (entry.type !== "custom" || entry.customType !== REQUEST_CHANGES) continue;
|
|
210
|
+
const record = entry.data as Partial<RequestRecord> | undefined;
|
|
211
|
+
if (record?.messageAt === undefined || record.at === undefined) continue;
|
|
212
|
+
this.#restored.set(record.messageAt, {
|
|
213
|
+
at: record.at,
|
|
214
|
+
streaming: false,
|
|
215
|
+
waitMs: record.firstTokenMs,
|
|
216
|
+
waitingMs: record.firstTokenMs ?? 0,
|
|
217
|
+
serverMs: record.serverMs,
|
|
218
|
+
thoughtMs: record.thoughtMs,
|
|
219
|
+
totalMs: record.totalMs ?? 0,
|
|
220
|
+
writingMs: record.generationMs ?? 0,
|
|
221
|
+
stallMs: record.stallMs ?? 0,
|
|
222
|
+
output: record.output ?? 0,
|
|
223
|
+
estimated: false,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
request(): RequestView | undefined {
|
|
229
|
+
const request = this.#request;
|
|
230
|
+
return request === undefined ? undefined : this.#view(request);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
last(): RequestView | undefined {
|
|
234
|
+
return this.#last;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
run(): RunView | undefined {
|
|
238
|
+
const run = this.#run;
|
|
239
|
+
if (run === undefined) return undefined;
|
|
240
|
+
const running = run.endedAt === undefined;
|
|
241
|
+
return {
|
|
242
|
+
running,
|
|
243
|
+
elapsedMs: (run.endedAt ?? this.#now()) - run.startedAt,
|
|
244
|
+
requests: run.requests,
|
|
245
|
+
cost: run.cost,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
reset(): void {
|
|
250
|
+
this.#run = undefined;
|
|
251
|
+
this.#request = undefined;
|
|
252
|
+
this.#last = undefined;
|
|
253
|
+
this.#byMessage.clear();
|
|
254
|
+
this.#restored.clear();
|
|
255
|
+
this.#sentAt = undefined;
|
|
256
|
+
this.#headersAt = undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#begin(): void {
|
|
260
|
+
this.#request = {
|
|
261
|
+
startedAt: this.#now(),
|
|
262
|
+
sentAt: this.#sentAt,
|
|
263
|
+
headersAt: this.#headersAt,
|
|
264
|
+
firstTokenAt: undefined,
|
|
265
|
+
firstWritingAt: undefined,
|
|
266
|
+
endedAt: undefined,
|
|
267
|
+
usage: emptyTotals(),
|
|
268
|
+
characters: 0,
|
|
269
|
+
lastDeltaAt: undefined,
|
|
270
|
+
stallMs: 0,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Pi sends before it opens the message, so the marks may arrive first. */
|
|
275
|
+
#attach(): void {
|
|
276
|
+
const request = this.#request;
|
|
277
|
+
if (request === undefined || request.endedAt !== undefined) return;
|
|
278
|
+
request.sentAt = this.#sentAt;
|
|
279
|
+
request.headersAt = this.#headersAt;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
#timing(request: Request): Timing {
|
|
283
|
+
const view = this.#view(request);
|
|
284
|
+
const end = request.endedAt ?? this.#now();
|
|
285
|
+
const from = request.sentAt ?? request.startedAt;
|
|
286
|
+
// While it's paused, the pause so far counts too.
|
|
287
|
+
const pause = request.endedAt === undefined ? end - (request.lastDeltaAt ?? end) : 0;
|
|
288
|
+
return {
|
|
289
|
+
at: from,
|
|
290
|
+
streaming: view.streaming,
|
|
291
|
+
waitMs: view.waitMs,
|
|
292
|
+
waitingMs: view.waitingMs,
|
|
293
|
+
serverMs: view.serverMs,
|
|
294
|
+
thoughtMs: view.thoughtMs,
|
|
295
|
+
totalMs: end - from,
|
|
296
|
+
writingMs: request.firstTokenAt === undefined ? 0 : end - request.firstTokenAt,
|
|
297
|
+
stallMs: request.stallMs + (pause >= STALL_MS ? pause : 0),
|
|
298
|
+
output: view.usage.output,
|
|
299
|
+
estimated: view.estimated,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
#view(request: Request): RequestView {
|
|
304
|
+
const now = this.#now();
|
|
305
|
+
const streaming = request.endedAt === undefined;
|
|
306
|
+
const from = request.sentAt ?? request.startedAt;
|
|
307
|
+
const waitMs = span(from, request.firstTokenAt);
|
|
308
|
+
const estimated = streaming && request.usage.output === 0;
|
|
309
|
+
const output = estimated
|
|
310
|
+
? Math.round(request.characters / this.#charsPerToken())
|
|
311
|
+
: request.usage.output;
|
|
312
|
+
const writingMs =
|
|
313
|
+
request.firstTokenAt === undefined ? 0 : (request.endedAt ?? now) - request.firstTokenAt;
|
|
314
|
+
const thoughtMs = span(request.firstTokenAt, request.firstWritingAt);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
streaming,
|
|
318
|
+
waiting: streaming && request.firstTokenAt === undefined,
|
|
319
|
+
elapsedMs: (request.endedAt ?? now) - request.startedAt,
|
|
320
|
+
waitMs,
|
|
321
|
+
waitingMs: (request.endedAt ?? now) - from,
|
|
322
|
+
serverMs: span(request.sentAt, request.headersAt),
|
|
323
|
+
prefillMs: span(request.headersAt ?? request.sentAt, request.firstTokenAt),
|
|
324
|
+
thoughtMs: thoughtMs !== undefined && thoughtMs > 0 ? thoughtMs : undefined,
|
|
325
|
+
decode: writingMs < MIN_SAMPLE_MS && streaming ? undefined : perSecond(output, writingMs),
|
|
326
|
+
prefill: waitMs === undefined ? undefined : perSecond(promptOf(request.usage), waitMs),
|
|
327
|
+
usage: { ...request.usage, output },
|
|
328
|
+
estimated,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** This session's ratio, or the measured default. */
|
|
333
|
+
#charsPerToken(): number {
|
|
334
|
+
const { characters, output } = this.#sampled;
|
|
335
|
+
return output > 0 ? characters / output : DEFAULT_CHARS_PER_TOKEN;
|
|
336
|
+
}
|
|
337
|
+
}
|