@thehammer/danx-session-telemetry 0.1.14 → 0.1.15
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/activity-observer.d.ts +77 -0
- package/dist/activity-observer.js +73 -0
- package/dist/dispatch-usage.d.ts +209 -0
- package/dist/dispatch-usage.js +430 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +11 -1
- package/dist/internal-logger.js +5 -2
- package/dist/session-log-watcher.d.ts +230 -35
- package/dist/session-log-watcher.js +300 -45
- package/dist/stats-wire.d.ts +227 -0
- package/dist/stats-wire.js +307 -0
- package/dist/usage-ledger.js +1 -1
- package/dist/write-queue.d.ts +9 -0
- package/dist/write-queue.js +16 -0
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-2954 — the TRANSCRIPT-side producer of a dispatch's running set.
|
|
3
|
+
*
|
|
4
|
+
* `dispatch_activities` answers "what is this dispatch running RIGHT NOW" — one
|
|
5
|
+
* row per sub-agent or background script, with `finished_at` NULL while it is
|
|
6
|
+
* still going. This module produces the sub-agent half of that set from the one
|
|
7
|
+
* signal danxbot owns end-to-end: the session-log watcher discovering a
|
|
8
|
+
* sub-agent's transcript file and then tailing it.
|
|
9
|
+
*
|
|
10
|
+
* ## Why the transcript, not a hook
|
|
11
|
+
*
|
|
12
|
+
* Claude Code's `SubagentStart` / `SubagentStop` hooks carry strictly more (an
|
|
13
|
+
* `agent_type` before the sub-agent has written anything; and, for a background
|
|
14
|
+
* `Bash` shell, the ONLY finish signal that exists at all). But those hooks ship
|
|
15
|
+
* from the plugin, which lives in a repo danxbot has no board for — so they
|
|
16
|
+
* cannot land in this card (see DX-3284). Keying the running set solely on them
|
|
17
|
+
* would mean shipping a set that is permanently empty: `subagent_count` would read 0 on
|
|
18
|
+
* every dispatch, which is a REGRESSION against the transcript-derived counter
|
|
19
|
+
* this card replaced, not a deferral.
|
|
20
|
+
*
|
|
21
|
+
* So the transcript is the producer, and the hooks are an ENRICHMENT layered on
|
|
22
|
+
* the same rows:
|
|
23
|
+
*
|
|
24
|
+
* | Signal | Sub-agent start | Sub-agent finish | Background script |
|
|
25
|
+
* |---|---|---|---|
|
|
26
|
+
* | Transcript (here) | file discovered | dispatch terminal (`finishOpenActivities`) | not visible |
|
|
27
|
+
* | Hooks (DX-3284) | `SubagentStart` | `SubagentStop` | `PostToolUse` + its own stop |
|
|
28
|
+
*
|
|
29
|
+
* ## Why the two producers cannot double-count
|
|
30
|
+
*
|
|
31
|
+
* Every row is an upsert keyed `(dispatch_id, kind, activity_id)`, and BOTH
|
|
32
|
+
* producers derive `activity_id` from the same thing: the sub-agent's transcript
|
|
33
|
+
* file basename. The watcher has it directly; a hook has it as the basename of
|
|
34
|
+
* its `transcript_path`. That is also the key the ledger credits the sub-agent's
|
|
35
|
+
* tokens under, which is how per-sub-agent tokens join to the running set
|
|
36
|
+
* without this table carrying a token column.
|
|
37
|
+
*
|
|
38
|
+
* ## What this module deliberately does NOT do
|
|
39
|
+
*
|
|
40
|
+
* It never reports a FINISH. A transcript gives no "this sub-agent is done"
|
|
41
|
+
* signal — a finished sub-agent's file simply stops growing, which is
|
|
42
|
+
* indistinguishable from a slow one. Guessing a finish from an idle gap would
|
|
43
|
+
* mark a long-thinking sub-agent as complete. So open rows are closed exactly
|
|
44
|
+
* once, at the dispatch's own terminal transition (`finishOpenActivities`), and
|
|
45
|
+
* until DX-3284 lands the honest reading of a long-idle running row is what
|
|
46
|
+
* `quietest_running_at` is for.
|
|
47
|
+
*/
|
|
48
|
+
import type { ActivityChange } from "./stats-wire.js";
|
|
49
|
+
import type { EntryMeta, SourceDiscoveredInfo } from "./session-log-watcher.js";
|
|
50
|
+
import type { AgentLogEntry } from "./types.js";
|
|
51
|
+
/** What the observer needs from the dispatch's usage accumulator. */
|
|
52
|
+
export interface ActivityObserverDeps {
|
|
53
|
+
/**
|
|
54
|
+
* Queue activity changes onto the next stats write. Called with the full
|
|
55
|
+
* current state of each changed activity — the server's upsert is idempotent,
|
|
56
|
+
* so re-reporting an unchanged field is free and a dropped batch is
|
|
57
|
+
* self-healing on the next write.
|
|
58
|
+
*/
|
|
59
|
+
recordActivities: (changes: ActivityChange[]) => void;
|
|
60
|
+
/**
|
|
61
|
+
* DX-2954 — ignore every sub-agent file that already existed when the watcher
|
|
62
|
+
* attached. Set for a dispatch that continues an earlier one's session
|
|
63
|
+
* (`StartCursorPlan.inheritsPredecessorSubagents`): those files are the
|
|
64
|
+
* predecessor's sub-agents, which ended with its process, and reporting them
|
|
65
|
+
* would open a never-closing "running" row per predecessor sub-agent on the
|
|
66
|
+
* new dispatch — inflating `subagent_count` / `running_activity_count` and
|
|
67
|
+
* tripping the silent-too-long warning ten minutes later.
|
|
68
|
+
*/
|
|
69
|
+
ignorePreexisting?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface ActivityObserver {
|
|
72
|
+
/** Wire to the watcher's `onSourceDiscovered`. */
|
|
73
|
+
onSourceDiscovered: (info: SourceDiscoveredInfo) => void;
|
|
74
|
+
/** Wire to the watcher's `onEntry`. */
|
|
75
|
+
onEntry: (entry: AgentLogEntry, meta: EntryMeta) => void;
|
|
76
|
+
}
|
|
77
|
+
export declare function createActivityObserver(deps: ActivityObserverDeps): ActivityObserver;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export function createActivityObserver(deps) {
|
|
2
|
+
// No clock of its own, deliberately. Every stamp this module reports comes from
|
|
3
|
+
// the event that caused it — the watcher's `discoveredAt` for a start, the
|
|
4
|
+
// entry's own `timestamp` for an advance — so there is no wall-clock reading
|
|
5
|
+
// anywhere that a test would need to freeze or that a replay could skew.
|
|
6
|
+
/** Every sub-agent this observer has reported, by its ledger source key. */
|
|
7
|
+
const started = new Map();
|
|
8
|
+
return {
|
|
9
|
+
onSourceDiscovered(info) {
|
|
10
|
+
// Not this dispatch's sub-agent (see `ignorePreexisting`). Never added to
|
|
11
|
+
// `started` either, so its later lines do not stamp activity on a row
|
|
12
|
+
// that does not exist.
|
|
13
|
+
if (deps.ignorePreexisting === true && info.preexisting)
|
|
14
|
+
return;
|
|
15
|
+
const existing = started.get(info.source);
|
|
16
|
+
if (existing) {
|
|
17
|
+
// A second discovery of a source already reported can only ADD an
|
|
18
|
+
// `agentType` the first pass could not read yet (the sub-agent had not
|
|
19
|
+
// written a line naming one). Never re-stamp `startedAt`: the server
|
|
20
|
+
// takes LEAST(startedAt) anyway, but reporting a later start for a
|
|
21
|
+
// sub-agent that has been running since before this watcher attached
|
|
22
|
+
// would be wrong in the payload itself.
|
|
23
|
+
if (existing.agentType === null && info.agentType !== null) {
|
|
24
|
+
existing.agentType = info.agentType;
|
|
25
|
+
deps.recordActivities([{ ...existing }]);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const change = {
|
|
30
|
+
kind: "agent",
|
|
31
|
+
activityId: info.source,
|
|
32
|
+
agentType: info.agentType,
|
|
33
|
+
// A sub-agent has no Bash `description`; the field exists for the
|
|
34
|
+
// background-script kind and stays null here rather than being filled
|
|
35
|
+
// with a stand-in a reader would have to distinguish from a real one.
|
|
36
|
+
description: null,
|
|
37
|
+
startedAt: info.discoveredAt,
|
|
38
|
+
lastActivityAt: info.discoveredAt,
|
|
39
|
+
// Never a finish from the transcript — see the module docblock.
|
|
40
|
+
finishedAt: null,
|
|
41
|
+
};
|
|
42
|
+
started.set(info.source, change);
|
|
43
|
+
deps.recordActivities([{ ...change }]);
|
|
44
|
+
},
|
|
45
|
+
onEntry(entry, meta) {
|
|
46
|
+
// DX-2954 — a REPLAY counts as activity here, unlike the stall detector's
|
|
47
|
+
// liveness signal. The two ask different questions: the stall detector asks
|
|
48
|
+
// "is the agent alive NOW" (a re-read proves nothing), while this stamp
|
|
49
|
+
// answers "when did this sub-agent last actually say something", which a
|
|
50
|
+
// re-read line genuinely reports.
|
|
51
|
+
//
|
|
52
|
+
// Which is exactly why the stamp is the ENTRY's OWN timestamp and never a
|
|
53
|
+
// wall clock. Stamping `Date.now()` would make a reattach reset every
|
|
54
|
+
// pre-existing sub-agent's last activity to the moment the collector
|
|
55
|
+
// restarted: the whole replay window would report as fresh, and a sub-agent
|
|
56
|
+
// silent for half an hour would come out of it looking live, so
|
|
57
|
+
// `RUNNING_ACTIVITY_SILENT_AFTER_MS` could never fire for it. That is the
|
|
58
|
+
// one signal this feature has for "ended or stuck" (DX-3246 fixed the same
|
|
59
|
+
// wall-clock-for-entry-time confusion in the boot mark). Even with no
|
|
60
|
+
// replay involved, a wall clock records when the 5s poll NOTICED the line,
|
|
61
|
+
// not when the agent wrote it.
|
|
62
|
+
const change = started.get(meta.source);
|
|
63
|
+
if (!change)
|
|
64
|
+
return;
|
|
65
|
+
// GREATEST, mirroring the server: a line read out of order, or an older
|
|
66
|
+
// line inside a replay window, never drags liveness backwards.
|
|
67
|
+
if (entry.timestamp <= change.lastActivityAt)
|
|
68
|
+
return;
|
|
69
|
+
change.lastActivityAt = entry.timestamp;
|
|
70
|
+
deps.recordActivities([{ ...change }]);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-2963 — a running dispatch's usage, worker side.
|
|
3
|
+
*
|
|
4
|
+
* The worker no longer sums anything. It feeds every transcript line into
|
|
5
|
+
* the ONE shared counter (`UsageLedgerCounter`, per-field maximum per reply)
|
|
6
|
+
* and ships the entries that are new or changed since the last acknowledged
|
|
7
|
+
* write to the server (`applyStats`), which derives every total from the
|
|
8
|
+
* ledger and hands them back. The worker's own view of the totals is what the
|
|
9
|
+
* server last returned — plus, for the token ceiling only, a local estimate of
|
|
10
|
+
* what it has read but the server has not acknowledged yet.
|
|
11
|
+
*
|
|
12
|
+
* Totals are taken from the server ONLY at process start (the first write,
|
|
13
|
+
* sent with no entries if there are none) and from each write's response —
|
|
14
|
+
* never seeded by hand (the reattach hand seeds this replaces).
|
|
15
|
+
*
|
|
16
|
+
* DB-free: runs in the DB-less worker. The sink decides the transport
|
|
17
|
+
* (HTTP on a registered worker, getPool-direct on the dashboard).
|
|
18
|
+
*/
|
|
19
|
+
import { UsageLedgerCounter } from "./usage-ledger.js";
|
|
20
|
+
import type { CursorPastEofInfo } from "./session-log-watcher.js";
|
|
21
|
+
import type { AgentLogEntry } from "./types.js";
|
|
22
|
+
import { type ActivityChange, type SourceCursors, type StatsPayload, type StatsResponse, type UsageTotals } from "./stats-wire.js";
|
|
23
|
+
/** Where stats writes go — both dispatch-row sinks implement it. */
|
|
24
|
+
export interface StatsSink {
|
|
25
|
+
applyStats(dispatchId: string, payload: StatsPayload): Promise<StatsResponse>;
|
|
26
|
+
}
|
|
27
|
+
export declare function zeroUsageTotals(): UsageTotals;
|
|
28
|
+
export type FlushOutcome = "ok" | "failed" | "refused";
|
|
29
|
+
export declare class DispatchUsage {
|
|
30
|
+
readonly counter: UsageLedgerCounter;
|
|
31
|
+
private serverTotals;
|
|
32
|
+
private readonly acked;
|
|
33
|
+
private healthy;
|
|
34
|
+
private refused;
|
|
35
|
+
private started;
|
|
36
|
+
private readonly queue;
|
|
37
|
+
/** How to read the watcher's live per-source cursor state. */
|
|
38
|
+
private readCursorState;
|
|
39
|
+
/**
|
|
40
|
+
* Per source, the `delivered` offset as of the last ACKNOWLEDGED write — which
|
|
41
|
+
* is precisely what `acked` means. Seeded from the cursors this process
|
|
42
|
+
* resumed from, so a restart that writes before reading anything re-reports
|
|
43
|
+
* its own resume point instead of regressing it.
|
|
44
|
+
*/
|
|
45
|
+
private readonly ackedCursor;
|
|
46
|
+
/** Past-EOF resets to report once, then forget. */
|
|
47
|
+
private pastEof;
|
|
48
|
+
/**
|
|
49
|
+
* DX-2954 — replies this process read as REPLAYS. The server already counted
|
|
50
|
+
* them for THIS dispatch (they are below its stored `acked`), so they must not
|
|
51
|
+
* enter the unacknowledged excess the token-ceiling estimate adds on top of the
|
|
52
|
+
* server totals.
|
|
53
|
+
*/
|
|
54
|
+
private readonly replayedMessageIds;
|
|
55
|
+
/** Running-set changes to report on the next write. */
|
|
56
|
+
private pendingActivities;
|
|
57
|
+
/** The session transcript path — the ONE path field the wire carries. */
|
|
58
|
+
private transcriptPath;
|
|
59
|
+
private transcriptPathSent;
|
|
60
|
+
/**
|
|
61
|
+
* DX-2954 — wire the cursor half up at attach.
|
|
62
|
+
*
|
|
63
|
+
* `startCursors` are the cursors this process RESUMED from
|
|
64
|
+
* (`resolveStartCursors`); their `acked` values seed this collector's own
|
|
65
|
+
* acknowledged floor. `readCursorState` reads the watcher's live offsets at
|
|
66
|
+
* each write.
|
|
67
|
+
*/
|
|
68
|
+
attachCursors(readCursorState: () => Record<string, {
|
|
69
|
+
startOffset: number;
|
|
70
|
+
delivered: number;
|
|
71
|
+
}>, startCursors: SourceCursors): void;
|
|
72
|
+
/**
|
|
73
|
+
* DX-2954 — a stored cursor was past its file's end; report the reset on the
|
|
74
|
+
* next write so the server records a `transcript_cursor_past_eof` event and
|
|
75
|
+
* permits the one sanctioned backwards move of `acked`.
|
|
76
|
+
*/
|
|
77
|
+
recordPastEof(info: CursorPastEofInfo): void;
|
|
78
|
+
/**
|
|
79
|
+
* DX-2954 — queue running-set changes for the next write.
|
|
80
|
+
*
|
|
81
|
+
* Kept COLLAPSED (one entry per `(kind, activityId)`) rather than appended
|
|
82
|
+
* raw, so the queue is bounded by how many activities the dispatch has, not by
|
|
83
|
+
* how much they moved since the last write. A producer reports an activity's
|
|
84
|
+
* full current state every time anything about it changes, and a chatty
|
|
85
|
+
* sub-agent advances its `lastActivityAt` continuously — appending those would
|
|
86
|
+
* grow without bound, eventually exceed `ACTIVITY_BATCH_CAP` and take the
|
|
87
|
+
* WHOLE stats write down (not just its activity part) to describe a handful of
|
|
88
|
+
* activities.
|
|
89
|
+
*
|
|
90
|
+
* `collapseActivityChanges` applies the server's own merge rule, so collapsing
|
|
91
|
+
* here reaches the identical stored row; it is not a lossy summarisation.
|
|
92
|
+
*/
|
|
93
|
+
recordActivities(changes: readonly ActivityChange[]): void;
|
|
94
|
+
/**
|
|
95
|
+
* DX-2954 — the session's own transcript path, recorded on the dispatch's
|
|
96
|
+
* `jsonl_path`. Sent once, with the first stats write that carries it; the
|
|
97
|
+
* server also keeps only the first path it is given, so a repeat is harmless.
|
|
98
|
+
*/
|
|
99
|
+
setTranscriptPath(path: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* DX-2954 — this write's cursor reports, plus the `delivered` value to promote
|
|
102
|
+
* to `acked` per source once the write is acknowledged.
|
|
103
|
+
*
|
|
104
|
+
* **`acked` deliberately LAGS `delivered` by one write.** A write reports the
|
|
105
|
+
* position it has read to (`delivered`) and the position whose entries a
|
|
106
|
+
* PREVIOUS write already had acknowledged (`acked`); only when THIS write
|
|
107
|
+
* succeeds does its own `delivered` become the next write's `acked`.
|
|
108
|
+
*
|
|
109
|
+
* The lag is not slack — it is what makes the pair safe. A reply's ledger entry
|
|
110
|
+
* can be RAISED after it was first reported (its usage grows as the reply
|
|
111
|
+
* streams), and a delivered line need not produce an entry at all (a
|
|
112
|
+
* `tool_result`), so "the server committed my last write" is NOT the same as
|
|
113
|
+
* "every line I have read is finally accounted for". Reporting `acked` one
|
|
114
|
+
* write behind is the conservative claim that is always true.
|
|
115
|
+
*
|
|
116
|
+
* What it costs: a restart re-reads the last write's window. That is exactly
|
|
117
|
+
* the window `EntryMeta.replay` covers — the ledger re-credits nothing (its
|
|
118
|
+
* excess rule) and every side-effecting subscriber skips it — so the cost is
|
|
119
|
+
* one window of re-read, never a double count or a duplicate side effect.
|
|
120
|
+
*
|
|
121
|
+
* ## KNOWN GAP: the process-start write carries no cursors
|
|
122
|
+
*
|
|
123
|
+
* `attachMonitoringStack` deliberately does NOT await `watcher.start()` —
|
|
124
|
+
* file discovery polls for up to 180 s and must not block the spawn — and the
|
|
125
|
+
* process-start stats write fires immediately after it. At that moment the
|
|
126
|
+
* watcher has opened no file, so `cursorState()` is empty and this returns no
|
|
127
|
+
* reports. Cursors first reach the server on the write AFTER discovery, one
|
|
128
|
+
* stats tick (15-30 s) later.
|
|
129
|
+
*
|
|
130
|
+
* The bound this puts on the card's "every attach persists its starting
|
|
131
|
+
* cursors in its first stats write": a dispatch that dies inside its first
|
|
132
|
+
* stats interval stores no cursor at all, so a later dispatch continuing its
|
|
133
|
+
* session resolves start-order rule 4 (existing files at EOF) and does not
|
|
134
|
+
* count that first interval's usage. The ledger keeps this from ever
|
|
135
|
+
* DOUBLE-counting; the cost is the same bounded loss the card already accepts
|
|
136
|
+
* for the deploy transition. Closing it properly means persisting the starting
|
|
137
|
+
* cursor on discovery rather than on the next tick — a watcher-discovery
|
|
138
|
+
* callback wired into the ordered stats channel — which is a separate change,
|
|
139
|
+
* not something to bolt onto the flush path.
|
|
140
|
+
*/
|
|
141
|
+
private buildCursorReports;
|
|
142
|
+
/**
|
|
143
|
+
* DX-2954 — whether the cursor half has anything new to say. An IDLE dispatch
|
|
144
|
+
* (no new lines, no hook events) must still cost zero writes per tick, which
|
|
145
|
+
* is why a write is not forced merely because cursors EXIST.
|
|
146
|
+
*/
|
|
147
|
+
private cursorsMoved;
|
|
148
|
+
/**
|
|
149
|
+
* Feed one watcher entry. Only assistant lines carry usage / tool ids.
|
|
150
|
+
*
|
|
151
|
+
* `replay` (DX-2954) marks a line re-read after a restart. The line STILL goes
|
|
152
|
+
* to the counter — the ledger is idempotent and dropping it would lose usage a
|
|
153
|
+
* restart is meant to recover — but its id is remembered so the local ceiling
|
|
154
|
+
* estimate does not count it twice (see {@link DispatchUsage.unacknowledged}).
|
|
155
|
+
*/
|
|
156
|
+
observeEntry(entry: AgentLogEntry, replay?: boolean): void;
|
|
157
|
+
/**
|
|
158
|
+
* Ship every changed entry in capped batches, one after another. The first
|
|
159
|
+
* call always writes (even with nothing to send) so the process takes the
|
|
160
|
+
* server totals at start. A failed batch re-queues itself and every later
|
|
161
|
+
* batch; a refusal (the dispatch went terminal) stops all later writes.
|
|
162
|
+
*
|
|
163
|
+
* DX-2954 — the write also carries the cursor half. A write happens when there
|
|
164
|
+
* are entries, OR the cursors moved, OR there are activity changes, OR it is
|
|
165
|
+
* the process-start write; an idle dispatch still writes nothing. Cursors,
|
|
166
|
+
* activity changes and the transcript path ride the LAST batch only, so a
|
|
167
|
+
* multi-batch backlog does not repeat them and the cursor can never be
|
|
168
|
+
* acknowledged ahead of the entries it covers.
|
|
169
|
+
*/
|
|
170
|
+
flush(sink: StatsSink, dispatchId: string): Promise<FlushOutcome>;
|
|
171
|
+
/** The dispatch's totals as the server last returned them. */
|
|
172
|
+
totals(): UsageTotals;
|
|
173
|
+
/** Window tokens (input + output + cache writes) of the server totals. */
|
|
174
|
+
serverWindowTokens(): number;
|
|
175
|
+
/** Whether the most recent stats write succeeded. */
|
|
176
|
+
statsHealthy(): boolean;
|
|
177
|
+
/**
|
|
178
|
+
* The per-field excess of each local reply's maximum over what the server
|
|
179
|
+
* last acknowledged for it.
|
|
180
|
+
*
|
|
181
|
+
* DX-2954 — REPLAYED replies are excluded. The pre-DX-2954 comment here
|
|
182
|
+
* asserted that "every id this process observed was read at or after its
|
|
183
|
+
* starting cursor … so lines re-read on a restart never enter the estimate",
|
|
184
|
+
* which stopped being true the moment replays became a deliberate input: a
|
|
185
|
+
* replayed reply has no entry in `this.acked` at process start, so its FULL
|
|
186
|
+
* usage would be added on top of `serverTotals` that already contain it. That
|
|
187
|
+
* feeds a fabricated total to the token ceiling (`agent-status.ts` uses this
|
|
188
|
+
* estimate precisely when a write did NOT land) and to the context-overflow
|
|
189
|
+
* decision — either can stop a healthy dispatch.
|
|
190
|
+
*/
|
|
191
|
+
private unacknowledged;
|
|
192
|
+
/**
|
|
193
|
+
* The dispatch's usage as far as this process knows it: the server totals
|
|
194
|
+
* plus the per-field excess this process has read but the server has not
|
|
195
|
+
* acknowledged. A replay block adds nothing (its excess over the
|
|
196
|
+
* acknowledged total is zero). For a job with no dispatch row (nothing is
|
|
197
|
+
* ever acknowledged) this is exactly the local counter's total.
|
|
198
|
+
*/
|
|
199
|
+
estimate(): {
|
|
200
|
+
tokensIn: number;
|
|
201
|
+
tokensOut: number;
|
|
202
|
+
cacheRead: number;
|
|
203
|
+
cacheWrite: number;
|
|
204
|
+
};
|
|
205
|
+
/** Token-ceiling input: window tokens (input + output + cache writes) of {@link estimate}. */
|
|
206
|
+
windowTokensEstimate(): number;
|
|
207
|
+
/** All-field total of {@link estimate}. */
|
|
208
|
+
totalTokensEstimate(): number;
|
|
209
|
+
}
|