agentfootprint-lens 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,901 @@
1
+ // src/AgentLens.tsx
2
+ import { useMemo, useState as useState2 } from "react";
3
+
4
+ // src/adapters/fromAgentSnapshot.ts
5
+ function fromAgentSnapshot(runtime) {
6
+ const shared = runtime?.sharedState ?? {};
7
+ const rawMessages = shared.messages ?? [];
8
+ const messages = rawMessages.map(normalizeMessage);
9
+ const commitLog = Array.isArray(runtime?.commitLog) ? runtime.commitLog : [];
10
+ const llmCalls = extractLLMCalls(commitLog);
11
+ const toolExecs = extractToolExecutions(commitLog);
12
+ const instructionEvals = extractInstructionEvals(commitLog);
13
+ const toolResolves = extractToolResolves(commitLog);
14
+ const turns = assembleTurns(messages, llmCalls, toolExecs, instructionEvals, toolResolves);
15
+ const allTools = turns.flatMap((t) => t.iterations.flatMap((i) => i.toolCalls));
16
+ return {
17
+ turns,
18
+ messages,
19
+ tools: allTools,
20
+ finalDecision: shared.decision ?? {},
21
+ rawSnapshot: runtime
22
+ };
23
+ }
24
+ function normalizeMessage(m) {
25
+ const role = (() => {
26
+ if (m.role === "system" || m.role === "user" || m.role === "assistant" || m.role === "tool")
27
+ return m.role;
28
+ return "user";
29
+ })();
30
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
31
+ return {
32
+ role,
33
+ content,
34
+ ...m.toolCalls?.length ? { toolCalls: m.toolCalls } : {},
35
+ ...m.toolCallId ? { toolCallId: m.toolCallId } : {}
36
+ };
37
+ }
38
+ function extractLLMCalls(commitLog) {
39
+ const out = [];
40
+ let iter = 0;
41
+ for (const entry of commitLog) {
42
+ const id = entry.stageId ?? "";
43
+ if (id !== "call-llm" && id !== "streaming-call-llm") continue;
44
+ iter++;
45
+ const u = entry.updates ?? {};
46
+ const rawResponse = u["adapterRawResponse"] ?? u["llmResponse"];
47
+ const usage = rawResponse?.usage;
48
+ out.push({
49
+ iterationIndex: iter,
50
+ model: rawResponse?.model,
51
+ inputTokens: usage?.inputTokens,
52
+ outputTokens: usage?.outputTokens,
53
+ durationMs: u["callDurationMs"] ?? void 0,
54
+ stopReason: rawResponse?.stopReason,
55
+ assistantContent: rawResponse?.content ?? "",
56
+ toolCallsRequested: rawResponse?.toolCalls ?? []
57
+ });
58
+ }
59
+ return out;
60
+ }
61
+ function extractToolExecutions(commitLog) {
62
+ const out = [];
63
+ let iter = 0;
64
+ for (const entry of commitLog) {
65
+ const id = entry.stageId ?? "";
66
+ if (id !== "execute-tool-calls") continue;
67
+ iter++;
68
+ const u = entry.updates ?? {};
69
+ const rawCalls = u["toolCalls"];
70
+ const stubs = (rawCalls ?? []).map((c) => ({
71
+ id: c.id,
72
+ name: c.name,
73
+ arguments: c.arguments ?? {},
74
+ result: "",
75
+ // filled in during assembly
76
+ iterationIndex: iter,
77
+ turnIndex: 0,
78
+ // reassigned during assembly
79
+ decisionUpdate: u["updatedDecision"]
80
+ }));
81
+ out.push({ iterationIndex: iter, toolCalls: stubs });
82
+ }
83
+ return out;
84
+ }
85
+ function extractInstructionEvals(commitLog) {
86
+ const out = [];
87
+ let iter = 0;
88
+ for (const entry of commitLog) {
89
+ const id = entry.stageId ?? "";
90
+ if (id !== "evaluate-instructions") continue;
91
+ iter++;
92
+ const u = entry.updates ?? {};
93
+ const matchedRaw = u["matchedInstructions"];
94
+ const matched = parseMatched(matchedRaw);
95
+ const promptInj = Array.isArray(u["promptInjections"]) ? u["promptInjections"].length : 0;
96
+ const toolInj = Array.isArray(u["toolInjections"]) ? u["toolInjections"].length : 0;
97
+ out.push({
98
+ iterationIndex: iter,
99
+ ...matched.length > 0 && { matchedInstructions: matched },
100
+ promptInjectionCount: promptInj,
101
+ toolInjectionCount: toolInj
102
+ });
103
+ }
104
+ return out;
105
+ }
106
+ function parseMatched(v) {
107
+ if (!v) return [];
108
+ if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
109
+ if (typeof v === "string") {
110
+ const m = v.match(/:\s*(.+)/);
111
+ if (!m) return [];
112
+ return m[1].split(",").map((s) => s.trim().replace(/\.{3}$/, "")).filter(Boolean);
113
+ }
114
+ return [];
115
+ }
116
+ function extractToolResolves(commitLog) {
117
+ const out = [];
118
+ let iter = 0;
119
+ for (const entry of commitLog) {
120
+ const id = entry.stageId ?? "";
121
+ if (id !== "resolve-tools") continue;
122
+ iter++;
123
+ const u = entry.updates ?? {};
124
+ const descs = u["toolDescriptions"];
125
+ out.push({
126
+ iterationIndex: iter,
127
+ visibleTools: (descs ?? []).map((d) => d.name)
128
+ });
129
+ }
130
+ return out;
131
+ }
132
+ function assembleTurns(messages, llmCalls, toolExecs, instructionEvals, toolResolves) {
133
+ const turns = [];
134
+ let currentTurn = null;
135
+ let llmIdx = 0;
136
+ const toolExecByIter = new Map(toolExecs.map((t) => [t.iterationIndex, t]));
137
+ const instrByIter = new Map(instructionEvals.map((i) => [i.iterationIndex, i]));
138
+ const visibleByIter = new Map(toolResolves.map((t) => [t.iterationIndex, t]));
139
+ for (const msg of messages) {
140
+ if (msg.role === "user") {
141
+ if (currentTurn) turns.push(finalizeTurn(currentTurn));
142
+ currentTurn = {
143
+ index: turns.length,
144
+ userPrompt: msg.content,
145
+ iterations: [],
146
+ finalContent: ""
147
+ };
148
+ continue;
149
+ }
150
+ if (!currentTurn) continue;
151
+ if (msg.role === "assistant") {
152
+ const call = llmCalls[llmIdx];
153
+ llmIdx++;
154
+ const iterIndex = call?.iterationIndex ?? currentTurn.iterations.length + 1;
155
+ const exec = toolExecByIter.get(iterIndex);
156
+ const instr = instrByIter.get(iterIndex);
157
+ const visible = visibleByIter.get(iterIndex);
158
+ const toolCalls = (exec?.toolCalls ?? []).map((tc) => ({
159
+ ...tc,
160
+ turnIndex: currentTurn.index
161
+ }));
162
+ const iteration = {
163
+ index: iterIndex,
164
+ ...call?.model && { model: call.model },
165
+ ...call?.inputTokens !== void 0 && { inputTokens: call.inputTokens },
166
+ ...call?.outputTokens !== void 0 && { outputTokens: call.outputTokens },
167
+ ...call?.durationMs !== void 0 && { durationMs: call.durationMs },
168
+ ...call?.stopReason && { stopReason: call.stopReason },
169
+ assistantContent: call?.assistantContent ?? msg.content,
170
+ toolCalls,
171
+ decisionAtStart: {},
172
+ // TODO(phase-2): derive from pre-iter commit
173
+ ...instr?.matchedInstructions && { matchedInstructions: instr.matchedInstructions },
174
+ visibleTools: visible?.visibleTools ?? []
175
+ };
176
+ currentTurn.iterations.push(iteration);
177
+ if (!toolCalls.length) currentTurn.finalContent = iteration.assistantContent;
178
+ continue;
179
+ }
180
+ if (msg.role === "tool" && msg.toolCallId) {
181
+ const iter = currentTurn.iterations[currentTurn.iterations.length - 1];
182
+ if (!iter) continue;
183
+ const idx = iter.toolCalls.findIndex((tc) => tc.id === msg.toolCallId);
184
+ if (idx < 0) continue;
185
+ const updated = { ...iter.toolCalls[idx], result: msg.content };
186
+ iter.toolCalls[idx] = updated;
187
+ }
188
+ }
189
+ if (currentTurn) turns.push(finalizeTurn(currentTurn));
190
+ return turns;
191
+ }
192
+ function finalizeTurn(t) {
193
+ const totalInputTokens = t.iterations.reduce((s, i) => s + (i.inputTokens ?? 0), 0);
194
+ const totalOutputTokens = t.iterations.reduce((s, i) => s + (i.outputTokens ?? 0), 0);
195
+ const totalDurationMs = t.iterations.reduce((s, i) => s + (i.durationMs ?? 0), 0);
196
+ return {
197
+ index: t.index,
198
+ userPrompt: t.userPrompt,
199
+ iterations: t.iterations,
200
+ finalContent: t.finalContent,
201
+ totalInputTokens,
202
+ totalOutputTokens,
203
+ totalDurationMs
204
+ };
205
+ }
206
+
207
+ // src/panels/MessagesPanel.tsx
208
+ import { useState } from "react";
209
+
210
+ // src/theme/useLensTheme.ts
211
+ import { useFootprintTheme, coolDark } from "footprint-explainable-ui";
212
+ function useLensTheme() {
213
+ const ctx = useFootprintTheme();
214
+ return resolve(ctx);
215
+ }
216
+ function resolve(tokens) {
217
+ const t = tokens ?? {};
218
+ const c = t.colors ?? coolDark.colors ?? {};
219
+ const fallback = coolDark.colors ?? {};
220
+ return {
221
+ bg: c.bgPrimary ?? fallback.bgPrimary ?? "#0f172a",
222
+ bgElev: c.bgSecondary ?? fallback.bgSecondary ?? "#1e293b",
223
+ bgHover: c.bgTertiary ?? fallback.bgTertiary ?? "#334155",
224
+ border: c.border ?? fallback.border ?? "#334155",
225
+ borderStrong: c.bgTertiary ?? fallback.bgTertiary ?? "#334155",
226
+ text: c.textPrimary ?? fallback.textPrimary ?? "#f8fafc",
227
+ textMuted: c.textSecondary ?? fallback.textSecondary ?? "#94a3b8",
228
+ textSubtle: c.textMuted ?? fallback.textMuted ?? "#64748b",
229
+ accent: c.primary ?? fallback.primary ?? "#6366f1",
230
+ success: c.success ?? fallback.success ?? "#22c55e",
231
+ warning: c.warning ?? fallback.warning ?? "#f59e0b",
232
+ error: c.error ?? fallback.error ?? "#ef4444",
233
+ fontSans: t.fontFamily?.sans ?? coolDark.fontFamily?.sans ?? "system-ui, sans-serif",
234
+ fontMono: t.fontFamily?.mono ?? coolDark.fontFamily?.mono ?? "ui-monospace, monospace",
235
+ radius: t.radius ?? coolDark.radius ?? "8px"
236
+ };
237
+ }
238
+
239
+ // src/panels/MessagesPanel.tsx
240
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
241
+ function MessagesPanel({
242
+ timeline,
243
+ onToolCallClick,
244
+ systemPrompt
245
+ }) {
246
+ const t = useLensTheme();
247
+ return /* @__PURE__ */ jsxs(
248
+ "div",
249
+ {
250
+ "data-fp-lens": "messages-panel",
251
+ style: {
252
+ display: "flex",
253
+ flexDirection: "column",
254
+ gap: 16,
255
+ padding: "16px 24px",
256
+ background: t.bg,
257
+ color: t.text,
258
+ fontFamily: t.fontSans,
259
+ fontSize: 14,
260
+ lineHeight: 1.55,
261
+ minHeight: 0,
262
+ overflow: "auto"
263
+ },
264
+ children: [
265
+ systemPrompt && /* @__PURE__ */ jsx(SystemBubble, { text: systemPrompt }),
266
+ timeline.turns.map((turn) => /* @__PURE__ */ jsx(TurnBlock, { turn, onToolCallClick }, turn.index))
267
+ ]
268
+ }
269
+ );
270
+ }
271
+ function SystemBubble({ text }) {
272
+ const t = useLensTheme();
273
+ const [open, setOpen] = useState(false);
274
+ const preview = text.slice(0, 140).replace(/\s+/g, " ") + (text.length > 140 ? "\u2026" : "");
275
+ return /* @__PURE__ */ jsxs(
276
+ "div",
277
+ {
278
+ style: {
279
+ alignSelf: "center",
280
+ width: "100%",
281
+ maxWidth: 880,
282
+ border: `1px dashed ${t.border}`,
283
+ borderRadius: t.radius,
284
+ padding: "8px 12px",
285
+ background: t.bgElev,
286
+ fontSize: 12,
287
+ color: t.textMuted
288
+ },
289
+ children: [
290
+ /* @__PURE__ */ jsxs(
291
+ "button",
292
+ {
293
+ onClick: () => setOpen((v) => !v),
294
+ style: {
295
+ background: "transparent",
296
+ border: "none",
297
+ color: t.textMuted,
298
+ cursor: "pointer",
299
+ padding: 0,
300
+ font: "inherit"
301
+ },
302
+ children: [
303
+ /* @__PURE__ */ jsx("strong", { children: "SYSTEM" }),
304
+ " ",
305
+ open ? "\u25BE" : "\u25B8",
306
+ " ",
307
+ open ? "" : preview
308
+ ]
309
+ }
310
+ ),
311
+ open && /* @__PURE__ */ jsx(
312
+ "pre",
313
+ {
314
+ style: {
315
+ marginTop: 8,
316
+ whiteSpace: "pre-wrap",
317
+ fontFamily: t.fontMono,
318
+ fontSize: 11,
319
+ color: t.text
320
+ },
321
+ children: text
322
+ }
323
+ )
324
+ ]
325
+ }
326
+ );
327
+ }
328
+ function TurnBlock({
329
+ turn,
330
+ onToolCallClick
331
+ }) {
332
+ const t = useLensTheme();
333
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
334
+ /* @__PURE__ */ jsx(TurnHeader, { turn }),
335
+ /* @__PURE__ */ jsx(UserBubble, { text: turn.userPrompt }),
336
+ turn.iterations.map((iter) => /* @__PURE__ */ jsx(IterationBlock, { iter, onToolCallClick }, iter.index)),
337
+ turn.finalContent && turn.iterations.length > 0 && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: t.textSubtle, textAlign: "center" }, children: [
338
+ "turn ",
339
+ turn.index + 1,
340
+ " final \xB7 ",
341
+ turn.iterations.length,
342
+ " iter \xB7 ",
343
+ turn.totalInputTokens,
344
+ "\u2192",
345
+ turn.totalOutputTokens,
346
+ " tok \xB7 ",
347
+ (turn.totalDurationMs / 1e3).toFixed(1),
348
+ "s"
349
+ ] })
350
+ ] });
351
+ }
352
+ function TurnHeader({ turn }) {
353
+ const t = useLensTheme();
354
+ return /* @__PURE__ */ jsxs(
355
+ "div",
356
+ {
357
+ style: {
358
+ display: "flex",
359
+ alignItems: "center",
360
+ gap: 8,
361
+ color: t.textSubtle,
362
+ fontSize: 11,
363
+ textTransform: "uppercase",
364
+ letterSpacing: "0.08em",
365
+ fontWeight: 600
366
+ },
367
+ children: [
368
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: t.border } }),
369
+ /* @__PURE__ */ jsxs("span", { children: [
370
+ "Turn ",
371
+ turn.index + 1
372
+ ] }),
373
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, height: 1, background: t.border } })
374
+ ]
375
+ }
376
+ );
377
+ }
378
+ function UserBubble({ text }) {
379
+ const t = useLensTheme();
380
+ return /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx(
381
+ "div",
382
+ {
383
+ style: {
384
+ background: `color-mix(in srgb, ${t.accent} 18%, ${t.bgElev})`,
385
+ border: `1px solid ${t.border}`,
386
+ color: t.text,
387
+ maxWidth: 720,
388
+ padding: "10px 14px",
389
+ borderRadius: `${t.radius} ${t.radius} 2px ${t.radius}`,
390
+ whiteSpace: "pre-wrap"
391
+ },
392
+ children: text
393
+ }
394
+ ) });
395
+ }
396
+ function IterationBlock({
397
+ iter,
398
+ onToolCallClick
399
+ }) {
400
+ const t = useLensTheme();
401
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
402
+ /* @__PURE__ */ jsx(IterationBadge, { iter }),
403
+ iter.assistantContent && /* @__PURE__ */ jsx(
404
+ "div",
405
+ {
406
+ style: {
407
+ background: t.bgElev,
408
+ border: `1px solid ${t.border}`,
409
+ borderRadius: `2px ${t.radius} ${t.radius} ${t.radius}`,
410
+ padding: "10px 14px",
411
+ maxWidth: 820,
412
+ whiteSpace: "pre-wrap"
413
+ },
414
+ children: iter.assistantContent
415
+ }
416
+ ),
417
+ iter.toolCalls.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 6, paddingLeft: 12 }, children: iter.toolCalls.map((tc) => /* @__PURE__ */ jsx(ToolCallCard, { invocation: tc, onClick: onToolCallClick }, tc.id)) })
418
+ ] });
419
+ }
420
+ function IterationBadge({ iter }) {
421
+ const t = useLensTheme();
422
+ const bits = [`iter ${iter.index}`];
423
+ if (iter.model) bits.push(iter.model);
424
+ if (iter.inputTokens !== void 0)
425
+ bits.push(`${iter.inputTokens}\u2192${iter.outputTokens ?? "?"} tok`);
426
+ if (iter.durationMs !== void 0) bits.push(`${(iter.durationMs / 1e3).toFixed(2)}s`);
427
+ if (iter.stopReason) bits.push(iter.stopReason);
428
+ return /* @__PURE__ */ jsx(
429
+ "div",
430
+ {
431
+ style: {
432
+ alignSelf: "flex-start",
433
+ fontSize: 10,
434
+ color: t.textSubtle,
435
+ textTransform: "uppercase",
436
+ letterSpacing: "0.08em",
437
+ fontWeight: 600,
438
+ fontFamily: t.fontMono
439
+ },
440
+ children: bits.join(" \xB7 ")
441
+ }
442
+ );
443
+ }
444
+ function ToolCallCard({
445
+ invocation,
446
+ onClick
447
+ }) {
448
+ const t = useLensTheme();
449
+ const [open, setOpen] = useState(false);
450
+ const preview = shortArgs(invocation.arguments);
451
+ const errored = invocation.error === true;
452
+ return /* @__PURE__ */ jsxs(
453
+ "div",
454
+ {
455
+ style: {
456
+ border: `1px solid ${errored ? t.error : t.border}`,
457
+ borderLeft: `3px solid ${errored ? t.error : t.accent}`,
458
+ borderRadius: 6,
459
+ background: t.bg,
460
+ overflow: "hidden"
461
+ },
462
+ children: [
463
+ /* @__PURE__ */ jsxs(
464
+ "div",
465
+ {
466
+ onClick: () => {
467
+ setOpen((v) => !v);
468
+ onClick?.(invocation);
469
+ },
470
+ style: {
471
+ padding: "8px 12px",
472
+ cursor: "pointer",
473
+ display: "flex",
474
+ alignItems: "center",
475
+ gap: 10,
476
+ fontSize: 12,
477
+ fontFamily: t.fontMono
478
+ },
479
+ children: [
480
+ /* @__PURE__ */ jsx("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: invocation.name }),
481
+ /* @__PURE__ */ jsxs("span", { style: { color: t.textMuted }, children: [
482
+ "(",
483
+ preview,
484
+ ")"
485
+ ] }),
486
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
487
+ invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ jsx(
488
+ "span",
489
+ {
490
+ style: {
491
+ fontSize: 10,
492
+ padding: "1px 6px",
493
+ borderRadius: 3,
494
+ background: `color-mix(in srgb, ${t.warning} 20%, transparent)`,
495
+ color: t.warning,
496
+ fontFamily: t.fontSans,
497
+ fontWeight: 600,
498
+ textTransform: "uppercase"
499
+ },
500
+ children: "decisionUpdate"
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsx("span", { style: { color: t.textSubtle }, children: open ? "\u25BE" : "\u25B8" })
504
+ ]
505
+ }
506
+ ),
507
+ open && /* @__PURE__ */ jsxs("div", { style: { padding: "8px 12px", borderTop: `1px solid ${t.border}` }, children: [
508
+ /* @__PURE__ */ jsx(Label, { t, children: "args" }),
509
+ /* @__PURE__ */ jsx(JsonBlock, { value: invocation.arguments }),
510
+ invocation.result && /* @__PURE__ */ jsxs(Fragment, { children: [
511
+ /* @__PURE__ */ jsx(Label, { t, style: { marginTop: 10 }, children: "result" }),
512
+ /* @__PURE__ */ jsx(
513
+ "pre",
514
+ {
515
+ style: {
516
+ margin: 0,
517
+ padding: "8px 10px",
518
+ background: t.bgElev,
519
+ borderRadius: 4,
520
+ fontSize: 11,
521
+ fontFamily: t.fontMono,
522
+ color: errored ? t.error : t.text,
523
+ maxHeight: 280,
524
+ overflow: "auto",
525
+ whiteSpace: "pre-wrap"
526
+ },
527
+ children: invocation.result
528
+ }
529
+ )
530
+ ] }),
531
+ invocation.decisionUpdate && Object.keys(invocation.decisionUpdate).length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
532
+ /* @__PURE__ */ jsx(Label, { t, style: { marginTop: 10 }, children: "decisionUpdate" }),
533
+ /* @__PURE__ */ jsx(JsonBlock, { value: invocation.decisionUpdate })
534
+ ] })
535
+ ] })
536
+ ]
537
+ }
538
+ );
539
+ }
540
+ function Label({
541
+ t,
542
+ children,
543
+ style
544
+ }) {
545
+ return /* @__PURE__ */ jsx(
546
+ "div",
547
+ {
548
+ style: {
549
+ fontSize: 10,
550
+ color: t.textSubtle,
551
+ textTransform: "uppercase",
552
+ letterSpacing: "0.08em",
553
+ fontWeight: 600,
554
+ marginBottom: 4,
555
+ ...style
556
+ },
557
+ children
558
+ }
559
+ );
560
+ }
561
+ function JsonBlock({ value }) {
562
+ const t = useLensTheme();
563
+ return /* @__PURE__ */ jsx(
564
+ "pre",
565
+ {
566
+ style: {
567
+ margin: 0,
568
+ padding: "8px 10px",
569
+ background: t.bgElev,
570
+ borderRadius: 4,
571
+ fontSize: 11,
572
+ fontFamily: t.fontMono,
573
+ color: t.text,
574
+ maxHeight: 200,
575
+ overflow: "auto"
576
+ },
577
+ children: JSON.stringify(value, null, 2)
578
+ }
579
+ );
580
+ }
581
+ function shortArgs(args) {
582
+ const keys = Object.keys(args);
583
+ if (keys.length === 0) return "";
584
+ if (keys.length === 1) {
585
+ const v = args[keys[0]];
586
+ if (typeof v === "string" && v.length < 40) return `${keys[0]}: "${v}"`;
587
+ }
588
+ return keys.join(", ");
589
+ }
590
+
591
+ // src/panels/IterationStrip.tsx
592
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
593
+ function IterationStrip({ timeline, selectedKey, onSelect }) {
594
+ const t = useLensTheme();
595
+ const chips = timeline.turns.flatMap(
596
+ (turn) => turn.iterations.map((iter) => ({
597
+ key: `${turn.index}.${iter.index}`,
598
+ turn: turn.index + 1,
599
+ iter: iter.index,
600
+ label: chipLabel(iter),
601
+ tools: iter.toolCalls.length,
602
+ durationMs: iter.durationMs ?? 0,
603
+ stopReason: iter.stopReason
604
+ }))
605
+ );
606
+ return /* @__PURE__ */ jsxs2(
607
+ "div",
608
+ {
609
+ "data-fp-lens": "iteration-strip",
610
+ style: {
611
+ display: "flex",
612
+ gap: 4,
613
+ padding: "8px 12px",
614
+ overflowX: "auto",
615
+ borderBottom: `1px solid ${t.border}`,
616
+ background: t.bgElev
617
+ },
618
+ children: [
619
+ chips.length === 0 && /* @__PURE__ */ jsx2("span", { style: { color: t.textSubtle, fontSize: 11 }, children: "No iterations yet." }),
620
+ chips.map((c) => {
621
+ const active = c.key === selectedKey;
622
+ const isFinal = c.stopReason === "stop" || c.stopReason === "end_turn";
623
+ return /* @__PURE__ */ jsxs2(
624
+ "button",
625
+ {
626
+ onClick: () => onSelect?.(c.key),
627
+ style: {
628
+ background: active ? t.accent : "transparent",
629
+ color: active ? "#fff" : t.textMuted,
630
+ border: `1px solid ${active ? t.accent : t.border}`,
631
+ borderRadius: 4,
632
+ padding: "4px 8px",
633
+ fontSize: 11,
634
+ fontFamily: "ui-monospace, monospace",
635
+ cursor: "pointer",
636
+ whiteSpace: "nowrap",
637
+ flexShrink: 0
638
+ },
639
+ title: `turn ${c.turn} \xB7 iter ${c.iter} \xB7 ${c.tools} tool call${c.tools === 1 ? "" : "s"}${c.stopReason ? ` \xB7 ${c.stopReason}` : ""}`,
640
+ children: [
641
+ "t",
642
+ c.turn,
643
+ ".i",
644
+ c.iter,
645
+ " \xB7 ",
646
+ c.label,
647
+ " ",
648
+ isFinal ? "\u2713" : ""
649
+ ]
650
+ },
651
+ c.key
652
+ );
653
+ })
654
+ ]
655
+ }
656
+ );
657
+ }
658
+ function chipLabel(iter) {
659
+ const d = iter.durationMs ?? 0;
660
+ const secs = d >= 1e3 ? `${(d / 1e3).toFixed(1)}s` : `${Math.round(d)}ms`;
661
+ const toolBit = iter.toolCalls.length > 0 ? `${iter.toolCalls.length}t` : "final";
662
+ return `${secs} \xB7 ${toolBit}`;
663
+ }
664
+
665
+ // src/panels/ToolCallInspector.tsx
666
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
667
+ function ToolCallInspector({
668
+ timeline,
669
+ selectedId,
670
+ onSelect
671
+ }) {
672
+ const t = useLensTheme();
673
+ return /* @__PURE__ */ jsxs3(
674
+ "div",
675
+ {
676
+ "data-fp-lens": "tool-call-inspector",
677
+ style: {
678
+ background: t.bg,
679
+ color: t.text,
680
+ fontFamily: t.fontSans,
681
+ display: "flex",
682
+ flexDirection: "column",
683
+ minHeight: 0,
684
+ overflow: "hidden"
685
+ },
686
+ children: [
687
+ /* @__PURE__ */ jsxs3(
688
+ "div",
689
+ {
690
+ style: {
691
+ padding: "10px 14px",
692
+ borderBottom: `1px solid ${t.border}`,
693
+ fontSize: 11,
694
+ color: t.textSubtle,
695
+ textTransform: "uppercase",
696
+ letterSpacing: "0.08em",
697
+ fontWeight: 600,
698
+ background: t.bgElev
699
+ },
700
+ children: [
701
+ "Tool calls \xB7 ",
702
+ timeline.tools.length
703
+ ]
704
+ }
705
+ ),
706
+ /* @__PURE__ */ jsxs3("div", { style: { overflow: "auto", flex: 1 }, children: [
707
+ timeline.tools.length === 0 && /* @__PURE__ */ jsx3("div", { style: { padding: 14, color: t.textSubtle, fontSize: 12 }, children: "No tool calls yet." }),
708
+ timeline.tools.map((tc) => {
709
+ const active = tc.id === selectedId;
710
+ const errored = tc.error === true;
711
+ return /* @__PURE__ */ jsxs3(
712
+ "button",
713
+ {
714
+ onClick: () => onSelect?.(tc),
715
+ style: {
716
+ display: "flex",
717
+ flexDirection: "column",
718
+ alignItems: "stretch",
719
+ width: "100%",
720
+ textAlign: "left",
721
+ padding: "8px 12px",
722
+ background: active ? t.bgHover : "transparent",
723
+ border: "none",
724
+ borderLeft: `3px solid ${active ? t.accent : errored ? t.error : "transparent"}`,
725
+ borderBottom: `1px solid ${t.border}`,
726
+ color: t.text,
727
+ cursor: "pointer",
728
+ fontFamily: "inherit"
729
+ },
730
+ children: [
731
+ /* @__PURE__ */ jsxs3(
732
+ "div",
733
+ {
734
+ style: {
735
+ display: "flex",
736
+ gap: 8,
737
+ alignItems: "baseline",
738
+ fontSize: 12,
739
+ fontFamily: t.fontMono
740
+ },
741
+ children: [
742
+ /* @__PURE__ */ jsx3("span", { style: { color: errored ? t.error : t.accent, fontWeight: 600 }, children: tc.name }),
743
+ /* @__PURE__ */ jsxs3(
744
+ "span",
745
+ {
746
+ style: { color: t.textSubtle, fontSize: 10, marginLeft: "auto" },
747
+ children: [
748
+ "t",
749
+ tc.turnIndex + 1,
750
+ ".i",
751
+ tc.iterationIndex,
752
+ tc.durationMs !== void 0 && ` \xB7 ${Math.round(tc.durationMs)}ms`
753
+ ]
754
+ }
755
+ )
756
+ ]
757
+ }
758
+ ),
759
+ /* @__PURE__ */ jsx3(
760
+ "div",
761
+ {
762
+ style: {
763
+ fontSize: 10,
764
+ color: t.textMuted,
765
+ marginTop: 2,
766
+ fontFamily: t.fontMono,
767
+ overflow: "hidden",
768
+ textOverflow: "ellipsis",
769
+ whiteSpace: "nowrap"
770
+ },
771
+ children: shortArgs2(tc.arguments)
772
+ }
773
+ )
774
+ ]
775
+ },
776
+ tc.id
777
+ );
778
+ })
779
+ ] })
780
+ ]
781
+ }
782
+ );
783
+ }
784
+ function shortArgs2(args) {
785
+ const keys = Object.keys(args);
786
+ if (keys.length === 0) return "\u2014 no args \u2014";
787
+ return keys.map((k) => {
788
+ const v = args[k];
789
+ if (typeof v === "string") return `${k}: "${v.length > 20 ? v.slice(0, 20) + "\u2026" : v}"`;
790
+ if (typeof v === "number" || typeof v === "boolean") return `${k}: ${v}`;
791
+ return `${k}: \u2026`;
792
+ }).join(", ");
793
+ }
794
+
795
+ // src/AgentLens.tsx
796
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
797
+ function AgentLens({
798
+ runtimeSnapshot,
799
+ timeline: providedTimeline,
800
+ systemPrompt,
801
+ onToolCallClick
802
+ }) {
803
+ const t = useLensTheme();
804
+ const timeline = useMemo(() => {
805
+ if (providedTimeline) return providedTimeline;
806
+ if (!runtimeSnapshot) return null;
807
+ return fromAgentSnapshot(runtimeSnapshot);
808
+ }, [providedTimeline, runtimeSnapshot]);
809
+ const [selectedToolId, setSelectedToolId] = useState2(null);
810
+ const [selectedIterKey, setSelectedIterKey] = useState2(null);
811
+ function handleToolClick(inv) {
812
+ setSelectedToolId(inv.id);
813
+ setSelectedIterKey(`${inv.turnIndex}.${inv.iterationIndex}`);
814
+ onToolCallClick?.(inv);
815
+ }
816
+ if (!timeline) {
817
+ return /* @__PURE__ */ jsxs4(
818
+ "div",
819
+ {
820
+ "data-fp-lens": "empty",
821
+ style: {
822
+ padding: 32,
823
+ color: t.textMuted,
824
+ fontFamily: t.fontSans,
825
+ textAlign: "center",
826
+ background: t.bg
827
+ },
828
+ children: [
829
+ "No agent run to show yet. Pass ",
830
+ /* @__PURE__ */ jsx4("code", { children: "runtimeSnapshot" }),
831
+ " after the agent runs."
832
+ ]
833
+ }
834
+ );
835
+ }
836
+ const derivedSystemPrompt = systemPrompt ?? (typeof timeline.rawSnapshot?.sharedState?.systemPrompt === "string" ? timeline.rawSnapshot.sharedState.systemPrompt : void 0);
837
+ return /* @__PURE__ */ jsxs4(
838
+ "div",
839
+ {
840
+ "data-fp-lens": "shell",
841
+ style: {
842
+ display: "grid",
843
+ gridTemplateColumns: "1fr 320px",
844
+ gridTemplateRows: "auto 1fr",
845
+ gridTemplateAreas: '"strip strip" "messages inspector"',
846
+ height: "100%",
847
+ minHeight: 0,
848
+ background: t.bg,
849
+ color: t.text,
850
+ fontFamily: t.fontSans
851
+ },
852
+ children: [
853
+ /* @__PURE__ */ jsx4("div", { style: { gridArea: "strip" }, children: /* @__PURE__ */ jsx4(
854
+ IterationStrip,
855
+ {
856
+ timeline,
857
+ selectedKey: selectedIterKey,
858
+ onSelect: setSelectedIterKey
859
+ }
860
+ ) }),
861
+ /* @__PURE__ */ jsx4("div", { style: { gridArea: "messages", minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsx4(
862
+ MessagesPanel,
863
+ {
864
+ timeline,
865
+ onToolCallClick: handleToolClick,
866
+ ...derivedSystemPrompt && { systemPrompt: derivedSystemPrompt }
867
+ }
868
+ ) }),
869
+ /* @__PURE__ */ jsx4(
870
+ "div",
871
+ {
872
+ style: {
873
+ gridArea: "inspector",
874
+ minHeight: 0,
875
+ overflow: "hidden",
876
+ borderLeft: `1px solid ${t.border}`
877
+ },
878
+ children: /* @__PURE__ */ jsx4(
879
+ ToolCallInspector,
880
+ {
881
+ timeline,
882
+ selectedId: selectedToolId,
883
+ onSelect: handleToolClick
884
+ }
885
+ )
886
+ }
887
+ )
888
+ ]
889
+ }
890
+ );
891
+ }
892
+ export {
893
+ AgentLens,
894
+ IterationStrip,
895
+ MessagesPanel,
896
+ ToolCallInspector,
897
+ fromAgentSnapshot,
898
+ resolve as resolveLensTheme,
899
+ useLensTheme
900
+ };
901
+ //# sourceMappingURL=index.js.map