@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
|
+
}
|
package/extensions/render.ts
CHANGED
|
@@ -314,7 +314,7 @@ function renderPreviewText(
|
|
|
314
314
|
const label =
|
|
315
315
|
preview.kind === 'agent'
|
|
316
316
|
? options?.partial
|
|
317
|
-
? (options.partialLabel ?? '
|
|
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,
|
|
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
|
-
|
|
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
|
|
89
|
-
expandedLines: events,
|
|
89
|
+
summary: `${snapshot.status} | ${snapshot.totalEvents} ${eventLabel}`,
|
|
90
|
+
lines: snapshot.events,
|
|
90
91
|
};
|
|
91
92
|
|
|
92
93
|
onUpdate?.({
|
|
93
|
-
content: [
|
|
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
|
|
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
|
-
|
|
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
|
|