@adonis-agora/agent 0.79.0 → 0.80.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/assets/spa/assets/{index-DVnnBvfn.css → index-BxmGk7Ik.css} +1 -1
  3. package/dist/assets/spa/assets/index-Csk8lqYx.js +9 -0
  4. package/dist/assets/spa/index.html +2 -2
  5. package/dist/providers/agent_provider.d.ts.map +1 -1
  6. package/dist/providers/agent_provider.js +14 -0
  7. package/dist/providers/agent_provider.js.map +1 -1
  8. package/dist/src/agent-loop.d.ts.map +1 -1
  9. package/dist/src/agent-loop.js +47 -1
  10. package/dist/src/agent-loop.js.map +1 -1
  11. package/dist/src/genui/catalog.d.ts +14 -0
  12. package/dist/src/genui/catalog.d.ts.map +1 -1
  13. package/dist/src/genui/catalog.js +2 -0
  14. package/dist/src/genui/catalog.js.map +1 -1
  15. package/dist/src/genui/public.d.ts +4 -3
  16. package/dist/src/genui/public.d.ts.map +1 -1
  17. package/dist/src/genui/public.js +2 -1
  18. package/dist/src/genui/public.js.map +1 -1
  19. package/dist/src/genui/sandbox-validate.d.ts +42 -0
  20. package/dist/src/genui/sandbox-validate.d.ts.map +1 -0
  21. package/dist/src/genui/sandbox-validate.js +557 -0
  22. package/dist/src/genui/sandbox-validate.js.map +1 -0
  23. package/dist/src/genui/sandbox.d.ts +6 -0
  24. package/dist/src/genui/sandbox.d.ts.map +1 -1
  25. package/dist/src/genui/sandbox.js +54 -0
  26. package/dist/src/genui/sandbox.js.map +1 -1
  27. package/dist/src/genui/tools.d.ts +16 -1
  28. package/dist/src/genui/tools.d.ts.map +1 -1
  29. package/dist/src/genui/tools.js +76 -9
  30. package/dist/src/genui/tools.js.map +1 -1
  31. package/dist/src/genui-telemetry.d.ts +232 -0
  32. package/dist/src/genui-telemetry.d.ts.map +1 -0
  33. package/dist/src/genui-telemetry.js +426 -0
  34. package/dist/src/genui-telemetry.js.map +1 -0
  35. package/dist/src/index.d.ts +3 -1
  36. package/dist/src/index.d.ts.map +1 -1
  37. package/dist/src/index.js +2 -1
  38. package/dist/src/index.js.map +1 -1
  39. package/dist/src/opencode/mcp.d.ts +2 -0
  40. package/dist/src/opencode/mcp.d.ts.map +1 -1
  41. package/dist/src/opencode/mcp.js +16 -1
  42. package/dist/src/opencode/mcp.js.map +1 -1
  43. package/dist/src/spi/agent-store.d.ts +8 -0
  44. package/dist/src/spi/agent-store.d.ts.map +1 -1
  45. package/dist/src/spi/agent-store.js.map +1 -1
  46. package/dist/src/spi/governance-queries.d.ts +7 -0
  47. package/dist/src/spi/governance-queries.d.ts.map +1 -1
  48. package/dist/src/spi/governance-queries.js +0 -13
  49. package/dist/src/spi/governance-queries.js.map +1 -1
  50. package/dist/src/stores/lucid-governance-queries.d.ts +2 -0
  51. package/dist/src/stores/lucid-governance-queries.d.ts.map +1 -1
  52. package/dist/src/stores/lucid-governance-queries.js +56 -0
  53. package/dist/src/stores/lucid-governance-queries.js.map +1 -1
  54. package/dist/src/stores/lucid-schema.d.ts +11 -6
  55. package/dist/src/stores/lucid-schema.d.ts.map +1 -1
  56. package/dist/src/stores/lucid-schema.js +41 -6
  57. package/dist/src/stores/lucid-schema.js.map +1 -1
  58. package/dist/src/stores/lucid.d.ts +2 -0
  59. package/dist/src/stores/lucid.d.ts.map +1 -1
  60. package/dist/src/stores/lucid.js +38 -0
  61. package/dist/src/stores/lucid.js.map +1 -1
  62. package/dist/src/testing/in-memory-governance-queries.d.ts +2 -0
  63. package/dist/src/testing/in-memory-governance-queries.d.ts.map +1 -1
  64. package/dist/src/testing/in-memory-governance-queries.js +4 -0
  65. package/dist/src/testing/in-memory-governance-queries.js.map +1 -1
  66. package/dist/src/testing/in-memory-store.d.ts +5 -0
  67. package/dist/src/testing/in-memory-store.d.ts.map +1 -1
  68. package/dist/src/testing/in-memory-store.js +14 -0
  69. package/dist/src/testing/in-memory-store.js.map +1 -1
  70. package/dist/src/types.d.ts +17 -0
  71. package/dist/src/types.d.ts.map +1 -1
  72. package/package.json +3 -1
  73. package/skills/agent-governance/SKILL.md +2 -2
  74. package/skills/agent-setup/SKILL.md +1 -1
  75. package/dist/assets/spa/assets/index-Be6OClIm.js +0 -9
@@ -0,0 +1,232 @@
1
+ import type { GenuiErrorCategory } from './genui/sandbox-validate.js';
2
+ /**
3
+ * Generative-UI telemetry: one row per UI tool call (`ui__render`, `ui__sandbox`, `ui__show_*`,
4
+ * `ui__show`), recorded by the loop through `AgentStore.recordGenuiCalls` and read back by the
5
+ * dashboard's "Generative UI" page through `AgentGovernanceQueries.genuiStats`.
6
+ *
7
+ * PRIVACY: a row is metadata — names, counts, timings, an error's category and a scrubbed message.
8
+ * The generated HTML/JSX and props are stored only when the tool was built with
9
+ * `telemetry: { samplePayloads: true }` (off by default).
10
+ */
11
+ import type { AgentStore, CostSource } from './spi/agent-store.js';
12
+ import type { GovernanceRange } from './spi/governance-queries.js';
13
+ import type { SinkWriter } from './spi/token-stream-sink.js';
14
+ import type { GenuiToolMarker } from './types.js';
15
+ export type { GenuiErrorCategory } from './genui/sandbox-validate.js';
16
+ /**
17
+ * How a call fared: `first_try` — accepted on the turn's first UI attempt; `retried` — accepted
18
+ * after one or more refused attempts; `failed` — refused (the model was handed the error).
19
+ */
20
+ export type GenuiValidity = 'first_try' | 'retried' | 'failed';
21
+ /** One UI tool call, as the loop records it. */
22
+ export interface RecordGenuiCallInput {
23
+ /** The tool call's id — the row's key (a replayed record overwrites, never duplicates). */
24
+ toolCallId: string;
25
+ runId: string;
26
+ threadId: string;
27
+ actorRef: string;
28
+ agentName?: string;
29
+ /** The model step (0-based) that wrote the call. */
30
+ step: number;
31
+ toolName: string;
32
+ /** The turn's channel (`web`, `whatsapp`, …). */
33
+ channel: string;
34
+ mode: GenuiToolMarker['mode'];
35
+ modelId: string;
36
+ /** 1 + the UI calls refused since the turn's last accepted one. */
37
+ attempt: number;
38
+ outcome: 'ok' | 'error';
39
+ validity: GenuiValidity;
40
+ errorCategory?: GenuiErrorCategory;
41
+ /** The error, with quoted values replaced by `"…"` (verbatim with `samplePayloads`). At most 300 chars. */
42
+ errorMessage?: string;
43
+ /**
44
+ * The call's share of the step that wrote it — the step's tokens and cost split by the characters
45
+ * each tool call's arguments (and the step's text) took.
46
+ */
47
+ inputTokens: number;
48
+ outputTokens: number;
49
+ costUsd?: number;
50
+ costSource?: CostSource;
51
+ /** Turn start → the call's first `ui` frame (a streaming preview or the pushed component), in ms. */
52
+ firstFrameMs?: number;
53
+ /** Turn start → the call's final (non-partial) `ui` frame, in ms. Absent for a refused call. */
54
+ completeFrameMs?: number;
55
+ /** Size of the call's arguments, as JSON. */
56
+ payloadBytes: number;
57
+ /** The call's arguments as JSON — ONLY with `telemetry.samplePayloads`. */
58
+ payload?: string;
59
+ }
60
+ /** A stored {@link RecordGenuiCallInput}. */
61
+ export interface GenuiCallRow extends RecordGenuiCallInput {
62
+ createdAt: Date;
63
+ }
64
+ /** First-try success for one tool and model. */
65
+ export interface GenuiToolModelRow {
66
+ toolName: string;
67
+ modelId: string;
68
+ mode: string;
69
+ calls: number;
70
+ /** Turns whose first UI attempt used this tool. */
71
+ firstAttempts: number;
72
+ firstTrySuccesses: number;
73
+ /** `firstTrySuccesses / firstAttempts`; null when there is none. */
74
+ firstTryRate: number | null;
75
+ retried: number;
76
+ failed: number;
77
+ costUsd: number | null;
78
+ }
79
+ export interface GenuiErrorRow {
80
+ category: string;
81
+ /** The (scrubbed) message, as recorded. */
82
+ message: string;
83
+ toolName: string;
84
+ count: number;
85
+ lastAt: string;
86
+ }
87
+ export interface GenuiDayRow {
88
+ day: string;
89
+ calls: number;
90
+ turns: number;
91
+ inputTokens: number;
92
+ outputTokens: number;
93
+ costUsd: number | null;
94
+ firstTryRate: number | null;
95
+ }
96
+ export interface GenuiLatency {
97
+ samples: number;
98
+ p50Ms: number | null;
99
+ p95Ms: number | null;
100
+ }
101
+ /** What the dashboard's Generative UI page shows. */
102
+ export interface GenuiStats {
103
+ totals: {
104
+ calls: number;
105
+ turns: number;
106
+ firstAttempts: number;
107
+ firstTrySuccesses: number;
108
+ firstTryRate: number | null;
109
+ retried: number;
110
+ failed: number;
111
+ inputTokens: number;
112
+ outputTokens: number;
113
+ costUsd: number | null;
114
+ };
115
+ byToolModel: GenuiToolModelRow[];
116
+ /** Most frequent first, at most 20. */
117
+ errors: GenuiErrorRow[];
118
+ byDay: GenuiDayRow[];
119
+ /** UI cost per turn (turns with at least one UI call). */
120
+ costPerTurn: {
121
+ turns: number;
122
+ avgUsd: number | null;
123
+ p50Usd: number | null;
124
+ p95Usd: number | null;
125
+ };
126
+ /**
127
+ * Per turn: turn start → its first UI frame, and → the first complete UI frame of an accepted call.
128
+ */
129
+ latency: {
130
+ firstFrame: GenuiLatency;
131
+ complete: GenuiLatency;
132
+ };
133
+ }
134
+ /** Which kind of mistake a UI tool's error message is (what the genui tools and the registry throw). */
135
+ export declare function classifyGenuiError(message: string): GenuiErrorCategory;
136
+ /** An error message without the values it quotes (a schema's "received 'Jane'"), at most 300 chars. */
137
+ export declare function scrubGenuiError(message: string): string;
138
+ /** Rows created within the (inclusive, UTC) day range. */
139
+ export declare function genuiRowsInRange<T extends {
140
+ createdAt: Date;
141
+ }>(rows: readonly T[], range: GovernanceRange): T[];
142
+ /** {@link GenuiStats} from the rows of a range — the same numbers whichever store holds them. */
143
+ export declare function summarizeGenuiCalls(rows: readonly GenuiCallRow[]): GenuiStats;
144
+ /** When each tool call's first and final `ui` frames went out on the turn's stream. */
145
+ export interface GenuiFrameTimes {
146
+ first?: number;
147
+ complete?: number;
148
+ }
149
+ /**
150
+ * Wraps the turn's stream writer to note when each tool call's UI frames (`component`) are written —
151
+ * what the user sees, so the time to first frame counts a streaming preview, and a frame a gate
152
+ * held back counts when it was released.
153
+ */
154
+ export declare function observeGenuiFrames(writer: SinkWriter, now?: () => number): {
155
+ writer: SinkWriter;
156
+ times: Map<string, GenuiFrameTimes>;
157
+ };
158
+ /** What the loop knows about one step, to turn its UI calls into rows. */
159
+ export interface GenuiStepFacts {
160
+ runId: string;
161
+ threadId: string;
162
+ actorRef: string;
163
+ agentName?: string;
164
+ step: number;
165
+ channel: string;
166
+ modelId: string;
167
+ /** When the turn started; absent → no latency is recorded (an engine that cannot tell). */
168
+ startedAt?: number;
169
+ /** The step's model usage and cost (the whole step: shared out by characters written). */
170
+ usage: {
171
+ inputTokens: number;
172
+ outputTokens: number;
173
+ };
174
+ costUsd: number | null;
175
+ costSource?: CostSource;
176
+ text: string;
177
+ calls: readonly {
178
+ id: string;
179
+ name: string;
180
+ input: unknown;
181
+ }[];
182
+ /** Each call's result: an `error` when it was refused or failed. */
183
+ results: readonly {
184
+ id: string;
185
+ error?: string;
186
+ output?: unknown;
187
+ }[];
188
+ /** A call's genui marker, or undefined for a tool that is not a UI tool. */
189
+ markerOf(name: string): GenuiToolMarker | undefined;
190
+ frames: Map<string, GenuiFrameTimes>;
191
+ /** When the step's results settled (a call whose frames went out unobserved completes here). */
192
+ now: number;
193
+ }
194
+ /**
195
+ * Tracks the turn's UI attempts across steps: a refused UI call followed by another is a retry,
196
+ * whichever UI tool the second one uses.
197
+ */
198
+ export declare class GenuiTurnTelemetry {
199
+ private refusedSinceAccepted;
200
+ rows(facts: GenuiStepFacts): RecordGenuiCallInput[];
201
+ }
202
+ /** One UI call made outside the agent loop, for {@link GenuiRunRecorder.track}. */
203
+ export interface GenuiOutOfLoopCall {
204
+ store: Pick<AgentStore, 'recordGenuiCalls'>;
205
+ /** The tool's `spec.genui`; not a UI tool (undefined) or telemetry off → nothing is recorded. */
206
+ marker: GenuiToolMarker | undefined;
207
+ runId: string;
208
+ threadId: string;
209
+ actorRef: string;
210
+ agentName?: string;
211
+ channel: string;
212
+ modelId: string;
213
+ call: {
214
+ id: string;
215
+ name: string;
216
+ input: unknown;
217
+ };
218
+ }
219
+ /**
220
+ * Genui telemetry for an engine whose tools are called OUTSIDE the agent loop (OpenCode's MCP
221
+ * endpoint, which its code mode calls): one row per UI call, attempts counted per run. There the
222
+ * model's step is not split per call (tokens 0, no cost) and the frames are not timed (no latency).
223
+ * Recording is best-effort and never changes what the call returns or throws.
224
+ */
225
+ export declare class GenuiRunRecorder {
226
+ private readonly maxRuns;
227
+ private readonly runs;
228
+ constructor(maxRuns?: number);
229
+ track<T>(input: GenuiOutOfLoopCall, invoke: () => Promise<T>): Promise<T>;
230
+ private turn;
231
+ }
232
+ //# sourceMappingURL=genui-telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"genui-telemetry.d.ts","sourceRoot":"","sources":["../../src/genui-telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,426 @@
1
+ /** Which kind of mistake a UI tool's error message is (what the genui tools and the registry throw). */
2
+ export function classifyGenuiError(message) {
3
+ const code = /^invalid .+? code: (.*)$/s.exec(message);
4
+ if (code !== null) {
5
+ const first = code[1] ?? '';
6
+ if (/the bridge (is|has one method)/.test(first))
7
+ return 'bridge';
8
+ if (/: no `App`/.test(first))
9
+ return 'missing-app';
10
+ if (/is not a kit component|does not exist here/.test(first))
11
+ return 'unknown-component';
12
+ return 'syntax';
13
+ }
14
+ if (/is not available here|not available (on this channel|in this conversation)/i.test(message))
15
+ return 'unavailable';
16
+ if (/^invalid (UI tree|.+ props|.+ call): /.test(message))
17
+ return 'shape';
18
+ if (/^Invalid input for tool "/.test(message))
19
+ return 'shape';
20
+ return 'other';
21
+ }
22
+ /** An error message without the values it quotes (a schema's "received 'Jane'"), at most 300 chars. */
23
+ export function scrubGenuiError(message) {
24
+ const scrubbed = message
25
+ .replace(/"(?:[^"\\\n]|\\.)*"/g, '"…"')
26
+ .replace(/'(?:[^'\\\n]|\\.)*'/g, "'…'");
27
+ return scrubbed.length > 300 ? `${scrubbed.slice(0, 299)}…` : scrubbed;
28
+ }
29
+ // ─── Aggregation (every store's `genuiStats`) ───────────────────────────────────────────────────
30
+ /** Rows created within the (inclusive, UTC) day range. */
31
+ export function genuiRowsInRange(rows, range) {
32
+ const start = new Date(`${range.fromDay}T00:00:00.000Z`);
33
+ const end = new Date(`${range.toDay}T23:59:59.999Z`);
34
+ return rows.filter((row) => row.createdAt >= start && row.createdAt <= end);
35
+ }
36
+ function percentile(sorted, p) {
37
+ if (sorted.length === 0)
38
+ return null;
39
+ return sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))] ?? null;
40
+ }
41
+ function rate(successes, attempts) {
42
+ return attempts === 0 ? null : successes / attempts;
43
+ }
44
+ function addCost(total, cost) {
45
+ if (cost === undefined || cost === null)
46
+ return total;
47
+ return (total ?? 0) + cost;
48
+ }
49
+ /** {@link GenuiStats} from the rows of a range — the same numbers whichever store holds them. */
50
+ export function summarizeGenuiCalls(rows) {
51
+ const byRun = new Map();
52
+ for (const row of rows) {
53
+ const list = byRun.get(row.runId) ?? [];
54
+ list.push(row);
55
+ byRun.set(row.runId, list);
56
+ }
57
+ const toolModel = new Map();
58
+ const keyOf = (row) => `${row.toolName}\u0000${row.modelId}`;
59
+ const groupOf = (row) => {
60
+ let group = toolModel.get(keyOf(row));
61
+ if (group === undefined) {
62
+ group = {
63
+ toolName: row.toolName,
64
+ modelId: row.modelId,
65
+ mode: row.mode,
66
+ calls: 0,
67
+ firstAttempts: 0,
68
+ firstTrySuccesses: 0,
69
+ firstTryRate: null,
70
+ retried: 0,
71
+ failed: 0,
72
+ costUsd: null,
73
+ };
74
+ toolModel.set(keyOf(row), group);
75
+ }
76
+ return group;
77
+ };
78
+ const totals = {
79
+ calls: rows.length,
80
+ turns: byRun.size,
81
+ firstAttempts: 0,
82
+ firstTrySuccesses: 0,
83
+ firstTryRate: null,
84
+ retried: 0,
85
+ failed: 0,
86
+ inputTokens: 0,
87
+ outputTokens: 0,
88
+ costUsd: null,
89
+ };
90
+ const errors = new Map();
91
+ const days = new Map();
92
+ for (const row of rows) {
93
+ const group = groupOf(row);
94
+ group.calls += 1;
95
+ group.costUsd = addCost(group.costUsd, row.costUsd);
96
+ totals.inputTokens += row.inputTokens;
97
+ totals.outputTokens += row.outputTokens;
98
+ totals.costUsd = addCost(totals.costUsd, row.costUsd);
99
+ const first = row.attempt === 1;
100
+ if (first) {
101
+ group.firstAttempts += 1;
102
+ totals.firstAttempts += 1;
103
+ }
104
+ if (row.validity === 'first_try') {
105
+ group.firstTrySuccesses += 1;
106
+ totals.firstTrySuccesses += 1;
107
+ }
108
+ else if (row.validity === 'retried') {
109
+ group.retried += 1;
110
+ totals.retried += 1;
111
+ }
112
+ else {
113
+ group.failed += 1;
114
+ totals.failed += 1;
115
+ const category = row.errorCategory ?? 'other';
116
+ const message = row.errorMessage ?? '';
117
+ const key = `${category}\u0000${row.toolName}\u0000${message}`;
118
+ const at = row.createdAt.toISOString();
119
+ const entry = errors.get(key) ?? {
120
+ category,
121
+ message,
122
+ toolName: row.toolName,
123
+ count: 0,
124
+ lastAt: at,
125
+ };
126
+ entry.count += 1;
127
+ if (at > entry.lastAt)
128
+ entry.lastAt = at;
129
+ errors.set(key, entry);
130
+ }
131
+ const day = row.createdAt.toISOString().slice(0, 10);
132
+ const bucket = days.get(day) ?? {
133
+ day,
134
+ calls: 0,
135
+ turns: 0,
136
+ inputTokens: 0,
137
+ outputTokens: 0,
138
+ costUsd: null,
139
+ firstTryRate: null,
140
+ runs: new Set(),
141
+ attempts: 0,
142
+ successes: 0,
143
+ };
144
+ bucket.calls += 1;
145
+ bucket.runs.add(row.runId);
146
+ bucket.inputTokens += row.inputTokens;
147
+ bucket.outputTokens += row.outputTokens;
148
+ bucket.costUsd = addCost(bucket.costUsd, row.costUsd);
149
+ if (first)
150
+ bucket.attempts += 1;
151
+ if (row.validity === 'first_try')
152
+ bucket.successes += 1;
153
+ days.set(day, bucket);
154
+ }
155
+ totals.firstTryRate = rate(totals.firstTrySuccesses, totals.firstAttempts);
156
+ for (const group of toolModel.values()) {
157
+ group.firstTryRate = rate(group.firstTrySuccesses, group.firstAttempts);
158
+ }
159
+ const turnCosts = [];
160
+ const firstFrames = [];
161
+ const completes = [];
162
+ for (const list of byRun.values()) {
163
+ let cost = null;
164
+ let firstFrame;
165
+ let complete;
166
+ for (const row of list) {
167
+ cost = addCost(cost, row.costUsd);
168
+ if (row.firstFrameMs !== undefined && row.firstFrameMs !== null) {
169
+ firstFrame = Math.min(firstFrame ?? row.firstFrameMs, row.firstFrameMs);
170
+ }
171
+ if (row.outcome === 'ok' &&
172
+ row.completeFrameMs !== undefined &&
173
+ row.completeFrameMs !== null) {
174
+ complete = Math.min(complete ?? row.completeFrameMs, row.completeFrameMs);
175
+ }
176
+ }
177
+ if (cost !== null)
178
+ turnCosts.push(cost);
179
+ if (firstFrame !== undefined)
180
+ firstFrames.push(firstFrame);
181
+ if (complete !== undefined)
182
+ completes.push(complete);
183
+ }
184
+ const ascending = (values) => [...values].sort((a, b) => a - b);
185
+ const costs = ascending(turnCosts);
186
+ const firstSorted = ascending(firstFrames);
187
+ const completeSorted = ascending(completes);
188
+ return {
189
+ totals,
190
+ byToolModel: [...toolModel.values()].sort((a, b) => b.calls - a.calls || a.toolName.localeCompare(b.toolName)),
191
+ errors: [...errors.values()]
192
+ .sort((a, b) => b.count - a.count || b.lastAt.localeCompare(a.lastAt))
193
+ .slice(0, 20),
194
+ byDay: [...days.values()]
195
+ .sort((a, b) => a.day.localeCompare(b.day))
196
+ .map(({ runs, attempts, successes, ...day }) => ({
197
+ ...day,
198
+ turns: runs.size,
199
+ firstTryRate: rate(successes, attempts),
200
+ })),
201
+ costPerTurn: {
202
+ turns: byRun.size,
203
+ avgUsd: costs.length === 0 ? null : costs.reduce((a, b) => a + b, 0) / costs.length,
204
+ p50Usd: percentile(costs, 0.5),
205
+ p95Usd: percentile(costs, 0.95),
206
+ },
207
+ latency: {
208
+ firstFrame: {
209
+ samples: firstSorted.length,
210
+ p50Ms: percentile(firstSorted, 0.5),
211
+ p95Ms: percentile(firstSorted, 0.95),
212
+ },
213
+ complete: {
214
+ samples: completeSorted.length,
215
+ p50Ms: percentile(completeSorted, 0.5),
216
+ p95Ms: percentile(completeSorted, 0.95),
217
+ },
218
+ },
219
+ };
220
+ }
221
+ /**
222
+ * Wraps the turn's stream writer to note when each tool call's UI frames (`component`) are written —
223
+ * what the user sees, so the time to first frame counts a streaming preview, and a frame a gate
224
+ * held back counts when it was released.
225
+ */
226
+ export function observeGenuiFrames(writer, now = Date.now) {
227
+ const times = new Map();
228
+ const note = (frame) => {
229
+ let toolCallId;
230
+ let partial;
231
+ if (frame.t === 'component') {
232
+ toolCallId = frame.toolCallId;
233
+ partial = frame.partial;
234
+ }
235
+ else if (frame.t === 'event' && frame.event.kind === 'ui') {
236
+ toolCallId = frame.event.toolCallId;
237
+ partial = frame.event.partial;
238
+ }
239
+ else {
240
+ return;
241
+ }
242
+ if (typeof toolCallId !== 'string')
243
+ return;
244
+ const at = now();
245
+ const entry = times.get(toolCallId) ?? {};
246
+ entry.first ??= at;
247
+ if (partial !== true)
248
+ entry.complete ??= at;
249
+ times.set(toolCallId, entry);
250
+ };
251
+ const wrapped = Object.create(writer);
252
+ wrapped.write = (frame) => {
253
+ try {
254
+ note(frame);
255
+ }
256
+ catch {
257
+ // Telemetry never stands in the stream's way.
258
+ }
259
+ return writer.write(frame);
260
+ };
261
+ wrapped.end = () => writer.end();
262
+ if (typeof writer.flush === 'function') {
263
+ wrapped.flush = () => writer.flush.call(writer);
264
+ }
265
+ return { writer: wrapped, times };
266
+ }
267
+ /**
268
+ * Tracks the turn's UI attempts across steps: a refused UI call followed by another is a retry,
269
+ * whichever UI tool the second one uses.
270
+ */
271
+ export class GenuiTurnTelemetry {
272
+ refusedSinceAccepted = 0;
273
+ rows(facts) {
274
+ const sizes = facts.calls.map((call) => jsonSize(call.input));
275
+ const written = facts.text.length + sizes.reduce((a, b) => a + b, 0);
276
+ const rows = [];
277
+ facts.calls.forEach((call, index) => {
278
+ const marker = facts.markerOf(call.name);
279
+ if (marker === undefined || marker.telemetry === false)
280
+ return;
281
+ const result = facts.results.find((each) => each.id === call.id);
282
+ if (result === undefined)
283
+ return;
284
+ const share = written === 0 ? 1 / facts.calls.length : (sizes[index] ?? 0) / written;
285
+ const failed = result.error !== undefined;
286
+ const attempt = this.refusedSinceAccepted + 1;
287
+ if (failed)
288
+ this.refusedSinceAccepted += 1;
289
+ else
290
+ this.refusedSinceAccepted = 0;
291
+ const sample = marker.telemetry !== undefined && marker.telemetry.samplePayloads === true;
292
+ const times = facts.frames.get(call.id);
293
+ const pushed = !failed && hasUi(result.output);
294
+ const complete = times?.complete ?? (pushed ? facts.now : undefined);
295
+ rows.push({
296
+ toolCallId: call.id,
297
+ runId: facts.runId,
298
+ threadId: facts.threadId,
299
+ actorRef: facts.actorRef,
300
+ ...(facts.agentName !== undefined ? { agentName: facts.agentName } : {}),
301
+ step: facts.step,
302
+ toolName: call.name,
303
+ channel: facts.channel,
304
+ mode: marker.mode,
305
+ modelId: facts.modelId,
306
+ attempt,
307
+ outcome: failed ? 'error' : 'ok',
308
+ validity: failed ? 'failed' : attempt === 1 ? 'first_try' : 'retried',
309
+ ...(failed
310
+ ? {
311
+ errorCategory: classifyGenuiError(result.error ?? ''),
312
+ errorMessage: sample
313
+ ? (result.error ?? '').slice(0, 300)
314
+ : scrubGenuiError(result.error ?? ''),
315
+ }
316
+ : {}),
317
+ inputTokens: Math.round(facts.usage.inputTokens * share),
318
+ outputTokens: Math.round(facts.usage.outputTokens * share),
319
+ ...(facts.costUsd !== null ? { costUsd: facts.costUsd * share } : {}),
320
+ ...(facts.costUsd !== null && facts.costSource !== undefined
321
+ ? { costSource: facts.costSource }
322
+ : {}),
323
+ ...latency(facts.startedAt, times?.first ?? complete, failed ? undefined : complete),
324
+ payloadBytes: sizes[index] ?? 0,
325
+ ...(sample ? { payload: JSON.stringify(call.input ?? null).slice(0, 200_000) } : {}),
326
+ });
327
+ });
328
+ return rows;
329
+ }
330
+ }
331
+ /** The two latencies from the turn's start, when it is known. */
332
+ function latency(startedAt, first, complete) {
333
+ if (startedAt === undefined)
334
+ return {};
335
+ return {
336
+ ...(first !== undefined ? { firstFrameMs: Math.max(0, first - startedAt) } : {}),
337
+ ...(complete !== undefined ? { completeFrameMs: Math.max(0, complete - startedAt) } : {}),
338
+ };
339
+ }
340
+ /**
341
+ * Genui telemetry for an engine whose tools are called OUTSIDE the agent loop (OpenCode's MCP
342
+ * endpoint, which its code mode calls): one row per UI call, attempts counted per run. There the
343
+ * model's step is not split per call (tokens 0, no cost) and the frames are not timed (no latency).
344
+ * Recording is best-effort and never changes what the call returns or throws.
345
+ */
346
+ export class GenuiRunRecorder {
347
+ maxRuns;
348
+ runs = new Map();
349
+ constructor(maxRuns = 1000) {
350
+ this.maxRuns = maxRuns;
351
+ }
352
+ async track(input, invoke) {
353
+ const { marker, store } = input;
354
+ if (marker === undefined ||
355
+ marker.telemetry === false ||
356
+ store.recordGenuiCalls === undefined) {
357
+ return invoke();
358
+ }
359
+ let output;
360
+ let error;
361
+ try {
362
+ output = await invoke();
363
+ return output;
364
+ }
365
+ catch (thrown) {
366
+ error = thrown instanceof Error ? thrown.message : String(thrown);
367
+ throw thrown;
368
+ }
369
+ finally {
370
+ try {
371
+ const rows = this.turn(input.runId).rows({
372
+ runId: input.runId,
373
+ threadId: input.threadId,
374
+ actorRef: input.actorRef,
375
+ ...(input.agentName !== undefined ? { agentName: input.agentName } : {}),
376
+ step: 0,
377
+ channel: input.channel,
378
+ modelId: input.modelId,
379
+ usage: { inputTokens: 0, outputTokens: 0 },
380
+ costUsd: null,
381
+ text: '',
382
+ calls: [input.call],
383
+ results: [
384
+ error !== undefined ? { id: input.call.id, error } : { id: input.call.id, output },
385
+ ],
386
+ markerOf: () => marker,
387
+ frames: new Map(),
388
+ now: Date.now(),
389
+ });
390
+ if (rows.length > 0)
391
+ await store.recordGenuiCalls(rows);
392
+ }
393
+ catch {
394
+ // Telemetry is best-effort.
395
+ }
396
+ }
397
+ }
398
+ turn(runId) {
399
+ let turn = this.runs.get(runId);
400
+ if (turn === undefined) {
401
+ turn = new GenuiTurnTelemetry();
402
+ this.runs.set(runId, turn);
403
+ if (this.runs.size > this.maxRuns) {
404
+ const oldest = this.runs.keys().next().value;
405
+ if (oldest !== undefined)
406
+ this.runs.delete(oldest);
407
+ }
408
+ }
409
+ return turn;
410
+ }
411
+ }
412
+ /** A genui tool's output (`{ shown, id }`) says it pushed a frame. */
413
+ function hasUi(output) {
414
+ return (typeof output === 'object' &&
415
+ output !== null &&
416
+ typeof output.id === 'string');
417
+ }
418
+ function jsonSize(value) {
419
+ try {
420
+ return JSON.stringify(value ?? null).length;
421
+ }
422
+ catch {
423
+ return 0;
424
+ }
425
+ }
426
+ //# sourceMappingURL=genui-telemetry.js.map