@alasano/pi-exa 0.1.1 → 0.1.2

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.
@@ -0,0 +1,83 @@
1
+ export const AGENT_TIMELINE_MAX_EVENTS = 6;
2
+ export const AGENT_TIMELINE_UPDATE_THROTTLE_MS = 100;
3
+
4
+ export interface AgentTimelineSnapshot {
5
+ events: string[];
6
+ totalEvents: number;
7
+ status: string;
8
+ }
9
+
10
+ interface AgentTimelineOptions {
11
+ maxEvents?: number;
12
+ throttleMs?: number;
13
+ }
14
+
15
+ export class AgentTimeline {
16
+ private readonly events: string[] = [];
17
+ private readonly maxEvents: number;
18
+ private readonly throttleMs: number;
19
+ private totalEvents = 0;
20
+ private status = 'running';
21
+ private updateTimer: ReturnType<typeof setTimeout> | undefined;
22
+ private updateDirty = false;
23
+ private lastUpdateAt = 0;
24
+
25
+ constructor(
26
+ private readonly onUpdate: (snapshot: AgentTimelineSnapshot) => void,
27
+ options: AgentTimelineOptions = {},
28
+ ) {
29
+ this.maxEvents = Math.max(1, options.maxEvents ?? AGENT_TIMELINE_MAX_EVENTS);
30
+ this.throttleMs = Math.max(0, options.throttleMs ?? AGENT_TIMELINE_UPDATE_THROTTLE_MS);
31
+ }
32
+
33
+ append(event: string, status = 'running'): void {
34
+ this.totalEvents += 1;
35
+ this.status = status;
36
+ this.events.push(event);
37
+ if (this.events.length > this.maxEvents) {
38
+ this.events.splice(0, this.events.length - this.maxEvents);
39
+ }
40
+ this.scheduleUpdate();
41
+ }
42
+
43
+ flush(): void {
44
+ this.clearUpdateTimer();
45
+ this.emitUpdate();
46
+ }
47
+
48
+ dispose(): void {
49
+ this.clearUpdateTimer();
50
+ }
51
+
52
+ private scheduleUpdate(): void {
53
+ this.updateDirty = true;
54
+ const delay = this.throttleMs - (Date.now() - this.lastUpdateAt);
55
+ if (delay <= 0) {
56
+ this.clearUpdateTimer();
57
+ this.emitUpdate();
58
+ return;
59
+ }
60
+
61
+ this.updateTimer ??= setTimeout(() => {
62
+ this.updateTimer = undefined;
63
+ this.emitUpdate();
64
+ }, delay);
65
+ }
66
+
67
+ private emitUpdate(): void {
68
+ if (!this.updateDirty) return;
69
+ this.updateDirty = false;
70
+ this.lastUpdateAt = Date.now();
71
+ this.onUpdate({
72
+ events: [...this.events],
73
+ totalEvents: this.totalEvents,
74
+ status: this.status,
75
+ });
76
+ }
77
+
78
+ private clearUpdateTimer(): void {
79
+ if (!this.updateTimer) return;
80
+ clearTimeout(this.updateTimer);
81
+ this.updateTimer = undefined;
82
+ }
83
+ }
@@ -314,7 +314,7 @@ function renderPreviewText(
314
314
  const label =
315
315
  preview.kind === 'agent'
316
316
  ? options?.partial
317
- ? (options.partialLabel ?? 'Streaming events:')
317
+ ? (options.partialLabel ?? 'Latest streaming events:')
318
318
  : expanded
319
319
  ? 'Expanded UI preview; Ctrl+O to hide:'
320
320
  : 'Preview only; Ctrl+O to show full response:'
@@ -363,7 +363,10 @@ export function renderExaResult<TDetails extends { preview?: PreviewDetails } |
363
363
  ? 'Polling status:'
364
364
  : undefined;
365
365
  return new Text(
366
- renderPreviewText(result.details.preview, true, theme, { partial: true, partialLabel }),
366
+ renderPreviewText(result.details.preview, options.expanded, theme, {
367
+ partial: true,
368
+ partialLabel,
369
+ }),
367
370
  0,
368
371
  0,
369
372
  );
@@ -10,6 +10,7 @@ import {
10
10
  streamAgentRunEvents,
11
11
  type ExaAgentCreateRequest,
12
12
  } from '../agent';
13
+ import { AgentTimeline, type AgentTimelineSnapshot } from '../agent-timeline';
13
14
  import type { AgentRunTracker } from '../agent-tracker';
14
15
  import { countAgentSources, formatAgentRunResponse } from '../format';
15
16
  import { truncateToolOutput } from '../output';
@@ -38,6 +39,7 @@ type WebAgentDetails =
38
39
  monitor?: 'stream' | 'poll';
39
40
  timedOut?: boolean;
40
41
  events?: string[];
42
+ eventCount?: number;
41
43
  })
42
44
  | undefined;
43
45
 
@@ -79,23 +81,28 @@ function agentEventProgress(event: ExaAgentEvent): string {
79
81
 
80
82
  function sendAgentTimelineUpdate(
81
83
  onUpdate: AgentToolUpdateCallback<WebAgentDetails> | undefined,
82
- events: string[],
83
- summary = 'running',
84
+ snapshot: AgentTimelineSnapshot,
84
85
  ) {
86
+ const eventLabel = snapshot.totalEvents === 1 ? 'event' : 'events';
85
87
  const preview: PreviewDetails = {
86
88
  kind: 'agent',
87
- summary,
88
- lines: events.slice(-6),
89
- expandedLines: events,
89
+ summary: `${snapshot.status} | ${snapshot.totalEvents} ${eventLabel}`,
90
+ lines: snapshot.events,
90
91
  };
91
92
 
92
93
  onUpdate?.({
93
- content: [{ type: 'text', text: events[events.length - 1] || 'Running Exa Agent...' }],
94
+ content: [
95
+ {
96
+ type: 'text',
97
+ text: snapshot.events[snapshot.events.length - 1] || 'Running Exa Agent...',
98
+ },
99
+ ],
94
100
  details: {
95
101
  endpoint: '/agent/runs',
96
102
  mode: 'wait',
97
103
  monitor: 'stream',
98
- events,
104
+ events: snapshot.events,
105
+ eventCount: snapshot.totalEvents,
99
106
  preview,
100
107
  },
101
108
  });
@@ -192,7 +199,7 @@ async function waitWithStreaming(
192
199
  const timeoutSignal = createTimeoutSignal(signal, timeoutMs);
193
200
  let runId: string | undefined;
194
201
  let terminalFromStream = false;
195
- const events: string[] = [];
202
+ const timeline = new AgentTimeline((snapshot) => sendAgentTimelineUpdate(onUpdate, snapshot));
196
203
 
197
204
  try {
198
205
  sendProgress(onUpdate, 'Starting Exa Agent run with streaming events...');
@@ -200,11 +207,10 @@ async function waitWithStreaming(
200
207
  runId = getRunIdFromAgentEvent(event) ?? runId;
201
208
  const status = typeof event.data.status === 'string' ? event.data.status : undefined;
202
209
  terminalFromStream = terminalFromStream || isTerminalAgentStatus(status);
203
- const line = agentEventProgress(event);
204
- events.push(line);
205
- sendAgentTimelineUpdate(onUpdate, events, status || 'running');
210
+ timeline.append(agentEventProgress(event), status || 'running');
206
211
  }
207
212
  } catch (error) {
213
+ timeline.flush();
208
214
  if (!runId || (!timeoutSignal.isTimedOut() && signal?.aborted)) throw error;
209
215
  if (timeoutSignal.isTimedOut()) {
210
216
  const run = await getAgentRun(apiKey, runId, signal);
@@ -218,6 +224,8 @@ async function waitWithStreaming(
218
224
  signal,
219
225
  });
220
226
  } finally {
227
+ timeline.flush();
228
+ timeline.dispose();
221
229
  timeoutSignal.dispose();
222
230
  }
223
231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alasano/pi-exa",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Exa-powered web search, content retrieval, answers, and agentic search tools for pi",
5
5
  "keywords": [
6
6
  "pi-package"