pi-advisor-flow 0.3.6 → 0.5.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/src/usage.ts ADDED
@@ -0,0 +1,199 @@
1
+ import type { Usage } from "@earendil-works/pi-ai/compat";
2
+
3
+ /** Normalized usage returned by an Advisor or Scout provider response. */
4
+ export interface AdvisorUsageSnapshot {
5
+ cacheRead?: number;
6
+ cacheWrite?: number;
7
+ cost?: number;
8
+ input?: number;
9
+ output?: number;
10
+ totalTokens?: number;
11
+ }
12
+
13
+ /** Session-local totals for direct Advisor model requests. */
14
+ export interface AdvisorUsageTotals {
15
+ cacheRead?: number;
16
+ cacheWrite?: number;
17
+ calls: number;
18
+ cost?: number;
19
+ costCalls: number;
20
+ input?: number;
21
+ knownCalls: number;
22
+ output?: number;
23
+ totalTokens?: number;
24
+ }
25
+
26
+ const finite = (value: unknown) =>
27
+ typeof value === "number" && Number.isFinite(value) && value >= 0
28
+ ? value
29
+ : undefined;
30
+
31
+ const add = (left: number | undefined, right: number | undefined) =>
32
+ left === undefined || right === undefined ? (left ?? right) : left + right;
33
+
34
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
35
+ Boolean(value) && typeof value === "object" && !Array.isArray(value);
36
+
37
+ const costFields = ["input", "output", "cacheRead", "cacheWrite", "total"];
38
+
39
+ /** Extracts provider-agnostic usage fields without trusting provider metadata. */
40
+ export const snapshotAdvisorUsage = (
41
+ usage: unknown
42
+ ): AdvisorUsageSnapshot | undefined => {
43
+ if (!isRecord(usage)) {
44
+ return undefined;
45
+ }
46
+ const cost = isRecord(usage.cost) ? usage.cost : undefined;
47
+ const snapshot = {
48
+ cacheRead: finite(usage.cacheRead),
49
+ cacheWrite: finite(usage.cacheWrite),
50
+ cost: finite(cost?.total) ?? finite(usage.totalCost) ?? finite(usage.cost),
51
+ input: finite(usage.input),
52
+ output: finite(usage.output),
53
+ totalTokens: finite(usage.totalTokens),
54
+ } satisfies AdvisorUsageSnapshot;
55
+ const hasCostField = costFields.some(
56
+ (field) => finite(cost?.[field]) !== undefined
57
+ );
58
+ return Object.values(snapshot).some((value) => value !== undefined) ||
59
+ hasCostField
60
+ ? snapshot
61
+ : undefined;
62
+ };
63
+
64
+ /** Returns the reported provider cost, when the response includes one. */
65
+ export const advisorUsageCost = (usage: unknown): number | undefined =>
66
+ snapshotAdvisorUsage(usage)?.cost;
67
+
68
+ /**
69
+ * Converts supported provider usage to Pi's complete nested-tool usage shape.
70
+ * Missing fields become zero only at this Pi API boundary; absent usage remains
71
+ * undefined so an unavailable request is never presented as a zero-cost call.
72
+ */
73
+ export const advisorUsageForPi = (usage: unknown): Usage | undefined => {
74
+ const snapshot = snapshotAdvisorUsage(usage);
75
+ if (!(snapshot && isRecord(usage))) {
76
+ return undefined;
77
+ }
78
+ const cost = isRecord(usage.cost) ? usage.cost : undefined;
79
+ const input = snapshot.input ?? 0;
80
+ const output = snapshot.output ?? 0;
81
+ const cacheRead = snapshot.cacheRead ?? 0;
82
+ const cacheWrite = snapshot.cacheWrite ?? 0;
83
+ const cacheWrite1h = finite(usage.cacheWrite1h);
84
+ const reasoning = finite(usage.reasoning);
85
+ return {
86
+ cacheRead,
87
+ cacheWrite,
88
+ ...(cacheWrite1h === undefined ? {} : { cacheWrite1h }),
89
+ ...(reasoning === undefined ? {} : { reasoning }),
90
+ cost: {
91
+ cacheRead: finite(cost?.cacheRead) ?? 0,
92
+ cacheWrite: finite(cost?.cacheWrite) ?? 0,
93
+ input: finite(cost?.input) ?? 0,
94
+ output: finite(cost?.output) ?? 0,
95
+ total: snapshot.cost ?? 0,
96
+ },
97
+ input,
98
+ output,
99
+ totalTokens:
100
+ snapshot.totalTokens ?? input + output + cacheRead + cacheWrite,
101
+ };
102
+ };
103
+
104
+ /** Creates empty totals without treating absent usage as zero usage. */
105
+ export const emptyAdvisorUsageTotals = (): AdvisorUsageTotals => ({
106
+ calls: 0,
107
+ costCalls: 0,
108
+ knownCalls: 0,
109
+ });
110
+
111
+ /** Adds one direct Advisor response to session-local usage totals. */
112
+ export const addAdvisorUsage = (totals: AdvisorUsageTotals, usage: unknown) => {
113
+ totals.calls += 1;
114
+ const snapshot = snapshotAdvisorUsage(usage);
115
+ if (!snapshot) {
116
+ return;
117
+ }
118
+ totals.knownCalls += 1;
119
+ totals.cacheRead = add(totals.cacheRead, snapshot.cacheRead);
120
+ totals.cacheWrite = add(totals.cacheWrite, snapshot.cacheWrite);
121
+ totals.input = add(totals.input, snapshot.input);
122
+ totals.output = add(totals.output, snapshot.output);
123
+ totals.totalTokens = add(totals.totalTokens, snapshot.totalTokens);
124
+ if (snapshot.cost !== undefined) {
125
+ totals.cost = add(totals.cost, snapshot.cost);
126
+ totals.costCalls += 1;
127
+ }
128
+ };
129
+
130
+ const formatTokens = (value: number) => {
131
+ if (value < 1000) {
132
+ return String(value);
133
+ }
134
+ if (value < 10_000) {
135
+ return `${(value / 1000).toFixed(1)}k`;
136
+ }
137
+ if (value < 1_000_000) {
138
+ return `${Math.round(value / 1000)}k`;
139
+ }
140
+ if (value < 10_000_000) {
141
+ return `${(value / 1_000_000).toFixed(1)}M`;
142
+ }
143
+ return `${Math.round(value / 1_000_000)}M`;
144
+ };
145
+
146
+ const formatCost = (value: number) => `$${value.toFixed(4)}`;
147
+
148
+ const formatUsageFields = (usage: AdvisorUsageSnapshot): string | undefined => {
149
+ const tokens = [
150
+ usage.input === undefined ? undefined : `↑${formatTokens(usage.input)}`,
151
+ usage.output === undefined ? undefined : `↓${formatTokens(usage.output)}`,
152
+ usage.cacheRead === undefined
153
+ ? undefined
154
+ : `cr:${formatTokens(usage.cacheRead)}`,
155
+ usage.cacheWrite === undefined
156
+ ? undefined
157
+ : `cw:${formatTokens(usage.cacheWrite)}`,
158
+ ].filter((value): value is string => value !== undefined);
159
+ if (tokens.length === 0 && usage.totalTokens !== undefined) {
160
+ tokens.push(`tokens:${formatTokens(usage.totalTokens)}`);
161
+ }
162
+ if (usage.cost !== undefined) {
163
+ tokens.push(formatCost(usage.cost));
164
+ }
165
+ return tokens.join(" · ") || undefined;
166
+ };
167
+
168
+ /** Formats one response for an Advisor or Scout result renderer. */
169
+ export const formatAdvisorUsage = (usage: unknown): string | undefined => {
170
+ const snapshot = snapshotAdvisorUsage(usage);
171
+ return snapshot ? formatUsageFields(snapshot) : undefined;
172
+ };
173
+
174
+ /** Formats cumulative direct Advisor usage for a footer or session summary. */
175
+ export const formatAdvisorUsageTotals = (
176
+ totals: AdvisorUsageTotals
177
+ ): string => {
178
+ const usage = formatUsageFields(totals);
179
+ const missing = totals.calls - totals.knownCalls;
180
+ const parts = [
181
+ usage,
182
+ missing > 0 ? `${missing} without usage data` : undefined,
183
+ ];
184
+ return (
185
+ parts.filter((value): value is string => value !== undefined).join(" · ") ||
186
+ "unavailable"
187
+ );
188
+ };
189
+
190
+ /** Formats the current direct Advisor usage status for the Pi footer. */
191
+ export const formatAdvisorUsageStatus = (
192
+ totals: AdvisorUsageTotals
193
+ ): string | undefined => {
194
+ if (totals.calls === 0) {
195
+ return undefined;
196
+ }
197
+ const label = `Advisor: ${totals.calls} call${totals.calls === 1 ? "" : "s"}`;
198
+ return `${label} · ${formatAdvisorUsageTotals(totals)}`;
199
+ };
package/src/telemetry.ts DELETED
@@ -1,270 +0,0 @@
1
- import type { EventBus } from "@earendil-works/pi-coding-agent";
2
- import { redactAndCapText } from "./conversation.js";
3
-
4
- export const BENCHMARK_TELEMETRY_CHANNEL = "pi-advisor:benchmark";
5
- const BENCHMARK_CONTEXT = "PI_ADVISOR_BENCHMARK_CONTEXT";
6
- const BENCHMARK_RUN_ID = "PI_ADVISOR_BENCHMARK_RUN_ID";
7
- const BENCHMARK_TOKEN = "PI_ADVISOR_BENCHMARK_TOKEN";
8
- const MAX_TEXT_BYTES = 2000;
9
- const MAX_LABELS = 32;
10
- const MAX_LABEL_BYTES = 160;
11
-
12
- export interface BenchmarkAdvisorStart {
13
- model: string;
14
- question?: string;
15
- trigger?: string;
16
- }
17
-
18
- export interface BenchmarkAdvisorEnd extends BenchmarkAdvisorStart {
19
- outcome?: string;
20
- response?: string;
21
- usage?: unknown;
22
- }
23
-
24
- export interface BenchmarkAdvisorError extends BenchmarkAdvisorStart {
25
- category: "provider-error" | "empty-response" | "cancelled" | "unknown";
26
- }
27
-
28
- export type BenchmarkScoutEvent =
29
- | { model: string; type: "call" }
30
- | {
31
- availableCount?: number;
32
- latencyMs?: number;
33
- model: string;
34
- omittedBeforeScout?: number;
35
- selectedCount?: number;
36
- selectedLabels?: string[];
37
- synthesis?: string;
38
- type: "success";
39
- usage?: unknown;
40
- }
41
- | {
42
- availableCount?: number;
43
- fallback?: string;
44
- latencyMs?: number;
45
- model: string;
46
- omittedBeforeScout?: number;
47
- selectedCount?: number;
48
- type: "fallback";
49
- usage?: unknown;
50
- }
51
- | { type: "cancelled" };
52
-
53
- export type BenchmarkTelemetryEvent =
54
- | {
55
- type: "advisor:start";
56
- runId: string;
57
- model: string;
58
- question?: string;
59
- trigger?: string;
60
- timestamp: string;
61
- }
62
- | {
63
- type: "advisor:end";
64
- runId: string;
65
- model: string;
66
- outcome?: string;
67
- response?: string;
68
- timestamp: string;
69
- usage?: Record<string, unknown>;
70
- }
71
- | {
72
- type: "advisor:error";
73
- category: BenchmarkAdvisorError["category"];
74
- runId: string;
75
- model: string;
76
- timestamp: string;
77
- }
78
- | {
79
- event: BenchmarkScoutEvent;
80
- runId: string;
81
- timestamp: string;
82
- type: "scout";
83
- }
84
- | {
85
- fields: Record<string, unknown>;
86
- runId: string;
87
- timestamp: string;
88
- type: "provider-request";
89
- };
90
-
91
- type BenchmarkTelemetryPayload = {
92
- [K in BenchmarkTelemetryEvent["type"]]: Omit<
93
- Extract<BenchmarkTelemetryEvent, { type: K }>,
94
- "runId" | "timestamp"
95
- >;
96
- }[BenchmarkTelemetryEvent["type"]];
97
-
98
- export interface BenchmarkTelemetry {
99
- advisorEnd: (event: BenchmarkAdvisorEnd) => void;
100
- advisorError: (event: BenchmarkAdvisorError) => void;
101
- advisorStart: (event: BenchmarkAdvisorStart) => void;
102
- providerRequest: (payload: unknown) => void;
103
- scout: (event: BenchmarkScoutEvent) => void;
104
- }
105
-
106
- const finite = (value: unknown) =>
107
- typeof value === "number" && Number.isFinite(value) ? value : undefined;
108
-
109
- const usageSnapshot = (usage: unknown): Record<string, unknown> | undefined => {
110
- if (!usage || typeof usage !== "object") {
111
- return undefined;
112
- }
113
- const source = usage as Record<string, unknown>;
114
- const { cost } = source;
115
- const costSource =
116
- cost && typeof cost === "object"
117
- ? (cost as Record<string, unknown>)
118
- : undefined;
119
- const result = Object.fromEntries(
120
- ["input", "output", "cacheRead", "cacheWrite", "totalTokens"]
121
- .map((key) => [key, finite(source[key])] as const)
122
- .filter(([, value]) => value !== undefined)
123
- ) as Record<string, unknown>;
124
- const providerCost = finite(costSource?.total);
125
- if (providerCost !== undefined) {
126
- result.cost = { total: providerCost };
127
- }
128
- return Object.keys(result).length > 0 ? result : undefined;
129
- };
130
-
131
- const text = (value: string | undefined, maxBytes = MAX_TEXT_BYTES) =>
132
- value ? redactAndCapText(value, maxBytes, true) : undefined;
133
-
134
- const labels = (values: string[] | undefined) =>
135
- values
136
- ?.slice(0, MAX_LABELS)
137
- .map((value) => text(value, MAX_LABEL_BYTES))
138
- .filter((value): value is string => Boolean(value));
139
-
140
- const PROVIDER_SENSITIVE_KEY = /message|prompt|content|input|system/i;
141
- const SAFE_PROVIDER_FIELDS = new Set([
142
- "max_completion_tokens",
143
- "max_tokens",
144
- "parallel_tool_calls",
145
- "reasoning_effort",
146
- "temperature",
147
- "top_p",
148
- "tool_choice",
149
- ]);
150
- const sanitizeProviderValue = (value: unknown, depth = 0): unknown => {
151
- if (depth > 3 || value === null) {
152
- return value;
153
- }
154
- if (
155
- typeof value === "string" ||
156
- typeof value === "number" ||
157
- typeof value === "boolean"
158
- ) {
159
- return typeof value === "string" ? text(value, 320) : value;
160
- }
161
- if (Array.isArray(value)) {
162
- return value
163
- .slice(0, 16)
164
- .map((item) => sanitizeProviderValue(item, depth + 1));
165
- }
166
- if (typeof value === "object") {
167
- return Object.fromEntries(
168
- Object.entries(value as Record<string, unknown>)
169
- .filter(([key]) => !PROVIDER_SENSITIVE_KEY.test(key))
170
- .slice(0, 32)
171
- .map(([key, item]) => [key, sanitizeProviderValue(item, depth + 1)])
172
- );
173
- }
174
- return undefined;
175
- };
176
-
177
- const providerFields = (payload: unknown): Record<string, unknown> => {
178
- const value =
179
- payload && typeof payload === "object"
180
- ? (payload as Record<string, unknown>)
181
- : {};
182
- return Object.fromEntries(
183
- [...SAFE_PROVIDER_FIELDS]
184
- .filter((key) => key in value)
185
- .map((key) => [key, sanitizeProviderValue(value[key])])
186
- );
187
- };
188
-
189
- const enabledCapability = () => {
190
- const context = process.env[BENCHMARK_CONTEXT] === "1";
191
- const runId = process.env[BENCHMARK_RUN_ID];
192
- const token = process.env[BENCHMARK_TOKEN];
193
- if (!(context && runId && token && token.length >= 32)) {
194
- return;
195
- }
196
- return { runId, token };
197
- };
198
-
199
- export const createBenchmarkTelemetry = (
200
- events: EventBus
201
- ): BenchmarkTelemetry | undefined => {
202
- const capability = enabledCapability();
203
- if (!capability) {
204
- return undefined;
205
- }
206
- const emit = (event: BenchmarkTelemetryPayload) => {
207
- try {
208
- events.emit(BENCHMARK_TELEMETRY_CHANNEL, {
209
- ...event,
210
- runId: capability.runId,
211
- timestamp: new Date().toISOString(),
212
- } satisfies BenchmarkTelemetryEvent);
213
- } catch {
214
- // Benchmark diagnostics must never change Advisor/Scout behavior.
215
- }
216
- };
217
- return {
218
- advisorEnd: (event) =>
219
- emit({
220
- model: event.model,
221
- outcome: text(event.outcome, 160),
222
- response: text(event.response),
223
- type: "advisor:end",
224
- usage: usageSnapshot(event.usage),
225
- }),
226
- advisorError: (event) =>
227
- emit({
228
- category: event.category,
229
- model: event.model,
230
- type: "advisor:error",
231
- }),
232
- advisorStart: (event) =>
233
- emit({
234
- model: event.model,
235
- question: text(event.question),
236
- trigger: text(event.trigger, 160),
237
- type: "advisor:start",
238
- }),
239
- providerRequest: (payload) => {
240
- emit({
241
- fields: providerFields(payload),
242
- type: "provider-request",
243
- });
244
- },
245
- scout: (event) => {
246
- if (event.type === "call" || event.type === "cancelled") {
247
- emit({ event, type: "scout" });
248
- return;
249
- }
250
- emit({
251
- event: {
252
- ...(event.type === "success"
253
- ? {
254
- selectedLabels: labels(event.selectedLabels),
255
- synthesis: text(event.synthesis),
256
- }
257
- : { fallback: text(event.fallback, 320) }),
258
- availableCount: finite(event.availableCount),
259
- latencyMs: finite(event.latencyMs),
260
- model: event.model,
261
- omittedBeforeScout: finite(event.omittedBeforeScout),
262
- selectedCount: finite(event.selectedCount),
263
- type: event.type,
264
- usage: usageSnapshot(event.usage),
265
- },
266
- type: "scout",
267
- });
268
- },
269
- };
270
- };