@alasano/pi-exa 0.0.1

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,263 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
4
+ import { getAgentDir, getSettingsListTheme } from '@earendil-works/pi-coding-agent';
5
+ import { type SettingItem, SettingsList } from '@earendil-works/pi-tui';
6
+ import { isRecord } from './util';
7
+
8
+ const SETTINGS_PATH = join(getAgentDir(), 'state', 'extensions', 'pi-exa', 'tool-settings.json');
9
+ const OVERLAY_MAX_INNER = 60;
10
+ const GOLD_FG = '\x1b[38;2;212;162;46m';
11
+ const RESET_FG = '\x1b[39m';
12
+ const ANSI_RE = new RegExp(String.fromCharCode(0x1b) + '\\[[0-9;]*m', 'g');
13
+
14
+ const PRIMARY_TOOL_ITEMS = [
15
+ { id: 'web_search_exa', label: 'web_search_exa' },
16
+ { id: 'web_search_advanced_exa', label: 'web_search_advanced_exa' },
17
+ { id: 'web_fetch_exa', label: 'web_fetch_exa' },
18
+ { id: 'web_answer_exa', label: 'web_answer_exa' },
19
+ ] as const;
20
+
21
+ export const AGENT_TOOL_NAMES = [
22
+ 'web_agent_exa',
23
+ 'web_agent_get_exa',
24
+ 'web_agent_list_exa',
25
+ 'web_agent_cancel_exa',
26
+ 'web_agent_delete_exa',
27
+ 'web_agent_events_exa',
28
+ ] as const;
29
+
30
+ export const ALL_EXA_TOOLS = [
31
+ ...PRIMARY_TOOL_ITEMS.map((item) => item.id),
32
+ ...AGENT_TOOL_NAMES,
33
+ ] as const;
34
+
35
+ type ToolSettings = {
36
+ disabledTools: string[];
37
+ };
38
+
39
+ function checkboxValue(enabled: boolean): string {
40
+ return enabled ? '[x]' : '[ ]';
41
+ }
42
+
43
+ function gold(text: string): string {
44
+ return `${GOLD_FG}${text}${RESET_FG}`;
45
+ }
46
+
47
+ function isKnownExaTool(tool: string): tool is (typeof ALL_EXA_TOOLS)[number] {
48
+ return (ALL_EXA_TOOLS as readonly string[]).includes(tool);
49
+ }
50
+
51
+ export function normalizeExaToolSettings(raw: unknown): ToolSettings {
52
+ if (!isRecord(raw) || !Array.isArray(raw.disabledTools)) return { disabledTools: [] };
53
+
54
+ const disabled = raw.disabledTools.filter(
55
+ (tool: unknown): tool is string => typeof tool === 'string' && isKnownExaTool(tool),
56
+ );
57
+ const agentDisabled = disabled.some((tool) =>
58
+ (AGENT_TOOL_NAMES as readonly string[]).includes(tool),
59
+ );
60
+
61
+ return {
62
+ disabledTools: [
63
+ ...PRIMARY_TOOL_ITEMS.flatMap((item) => (disabled.includes(item.id) ? [item.id] : [])),
64
+ ...(agentDisabled ? [...AGENT_TOOL_NAMES] : []),
65
+ ],
66
+ };
67
+ }
68
+
69
+ function createDefaultSettings(): ToolSettings {
70
+ return { disabledTools: [] };
71
+ }
72
+
73
+ async function loadSettings(): Promise<ToolSettings> {
74
+ try {
75
+ return normalizeExaToolSettings(JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf8')));
76
+ } catch {
77
+ return createDefaultSettings();
78
+ }
79
+ }
80
+
81
+ async function saveSettings(settings: ToolSettings): Promise<boolean> {
82
+ try {
83
+ await fs.mkdir(dirname(SETTINGS_PATH), { recursive: true });
84
+ await fs.writeFile(SETTINGS_PATH, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ function applySettings(pi: ExtensionAPI, settings: ToolSettings): void {
92
+ const currentTools = pi.getActiveTools();
93
+ const nonExaTools = currentTools.filter((tool) => !isKnownExaTool(tool));
94
+ const enabledExaTools = ALL_EXA_TOOLS.filter((tool) => !settings.disabledTools.includes(tool));
95
+ pi.setActiveTools([...nonExaTools, ...enabledExaTools]);
96
+ }
97
+
98
+ function isToolEnabled(settings: ToolSettings, tool: string): boolean {
99
+ return !settings.disabledTools.includes(tool);
100
+ }
101
+
102
+ function areAgentToolsEnabled(settings: ToolSettings): boolean {
103
+ return AGENT_TOOL_NAMES.every((tool) => isToolEnabled(settings, tool));
104
+ }
105
+
106
+ function buildItems(settings: ToolSettings): SettingItem[] {
107
+ return [
108
+ ...PRIMARY_TOOL_ITEMS.map((tool) => ({
109
+ id: tool.id,
110
+ label: tool.label,
111
+ currentValue: checkboxValue(isToolEnabled(settings, tool.id)),
112
+ values: ['[x]', '[ ]'],
113
+ })),
114
+ {
115
+ id: 'agent-tools',
116
+ label: `web_agent_exa + ${AGENT_TOOL_NAMES.length - 1} other tools`,
117
+ currentValue: checkboxValue(areAgentToolsEnabled(settings)),
118
+ values: ['[x]', '[ ]'],
119
+ },
120
+ ];
121
+ }
122
+
123
+ function stripAnsi(text: string): string {
124
+ return text.replace(ANSI_RE, '');
125
+ }
126
+
127
+ function visibleWidth(text: string): number {
128
+ return stripAnsi(text).length;
129
+ }
130
+
131
+ function padVisible(text: string, width: number): string {
132
+ const deficit = width - visibleWidth(text);
133
+ if (deficit <= 0) return text;
134
+ return `${text}${' '.repeat(deficit)}`;
135
+ }
136
+
137
+ function computeOverlayInner(bodyLines: string[], availableWidth: number): number {
138
+ const maxInner = Math.max(24, Math.min(availableWidth - 2, OVERLAY_MAX_INNER));
139
+ return Math.max(
140
+ 24,
141
+ Math.min(
142
+ maxInner,
143
+ Math.max(...bodyLines.map((line) => visibleWidth(line)), visibleWidth('─ EXA TOOLS ')) + 2,
144
+ ),
145
+ );
146
+ }
147
+
148
+ function frameBody(title: string, bodyLines: string[], inner: number): string[] {
149
+ const leftHeader = `─ ${title} `;
150
+ const fill = Math.max(1, inner - leftHeader.length);
151
+ const top = gold('╭') + gold(leftHeader) + gold('─'.repeat(fill)) + gold('╮');
152
+ const bottom = gold('╰') + gold('─'.repeat(inner)) + gold('╯');
153
+ const contentWidth = Math.max(8, inner - 2);
154
+ const framedBody = bodyLines.map(
155
+ (line) => gold('│ ') + padVisible(line, contentWidth) + gold(' │'),
156
+ );
157
+ return [top, ...framedBody, bottom];
158
+ }
159
+
160
+ async function showToolSettingsOverlay(
161
+ pi: ExtensionAPI,
162
+ ctx: ExtensionContext,
163
+ settings: ToolSettings,
164
+ ): Promise<void> {
165
+ const items = buildItems(settings);
166
+ const settingsTheme = getSettingsListTheme();
167
+ const maxVisibleItems = Math.min(items.length + 2, 12);
168
+
169
+ const probeList = new SettingsList(
170
+ items,
171
+ maxVisibleItems,
172
+ settingsTheme,
173
+ () => {},
174
+ () => {},
175
+ );
176
+ const probeLines = probeList.render(Math.max(8, OVERLAY_MAX_INNER - 2));
177
+ const overlayBodyLines = ['Configure Exa tools', '', ...probeLines];
178
+ const overlayWidth = computeOverlayInner(overlayBodyLines, OVERLAY_MAX_INNER + 2) + 2;
179
+
180
+ await ctx.ui.custom(
181
+ (_tui, theme, _kb, done) => {
182
+ const settingsList = new SettingsList(
183
+ items,
184
+ maxVisibleItems,
185
+ settingsTheme,
186
+ async (id, newValue) => {
187
+ const nextEnabled = newValue === '[x]';
188
+
189
+ if (id === 'agent-tools') {
190
+ settings.disabledTools = nextEnabled
191
+ ? settings.disabledTools.filter(
192
+ (tool) => !(AGENT_TOOL_NAMES as readonly string[]).includes(tool),
193
+ )
194
+ : [
195
+ ...settings.disabledTools.filter(
196
+ (tool) => !(AGENT_TOOL_NAMES as readonly string[]).includes(tool),
197
+ ),
198
+ ...AGENT_TOOL_NAMES,
199
+ ];
200
+ } else if (nextEnabled) {
201
+ settings.disabledTools = settings.disabledTools.filter((tool) => tool !== id);
202
+ } else {
203
+ settings.disabledTools = [...settings.disabledTools.filter((tool) => tool !== id), id];
204
+ }
205
+
206
+ const normalized = normalizeExaToolSettings(settings);
207
+ settings.disabledTools = normalized.disabledTools;
208
+ settingsList.updateValue('agent-tools', checkboxValue(areAgentToolsEnabled(settings)));
209
+
210
+ await saveSettings(settings);
211
+ applySettings(pi, settings);
212
+ },
213
+ () => done(undefined),
214
+ );
215
+
216
+ return {
217
+ render(width: number) {
218
+ const safeWidth = Math.max(24, width);
219
+ const provisionalInner = Math.max(24, Math.min(safeWidth - 2, OVERLAY_MAX_INNER));
220
+ const listLines = settingsList.render(Math.max(8, provisionalInner - 2));
221
+ const bodyLines = [theme.fg('muted', 'Configure Exa tools'), '', ...listLines];
222
+ const naturalInner = computeOverlayInner(bodyLines, safeWidth);
223
+ return frameBody('EXA TOOLS', bodyLines, naturalInner);
224
+ },
225
+ invalidate() {
226
+ settingsList.invalidate();
227
+ },
228
+ handleInput(data: string) {
229
+ settingsList.handleInput?.(data);
230
+ },
231
+ };
232
+ },
233
+ {
234
+ overlay: true,
235
+ overlayOptions: {
236
+ anchor: 'center',
237
+ width: overlayWidth,
238
+ },
239
+ },
240
+ );
241
+ }
242
+
243
+ export async function registerExaSettings(pi: ExtensionAPI): Promise<void> {
244
+ let settings = await loadSettings();
245
+
246
+ pi.registerCommand('exa-settings', {
247
+ description: 'Open Exa tool settings',
248
+ handler: async (_args, ctx) => {
249
+ settings = await loadSettings();
250
+ await showToolSettingsOverlay(pi, ctx, settings);
251
+ },
252
+ });
253
+
254
+ pi.on('session_start', async (_event, _ctx) => {
255
+ settings = await loadSettings();
256
+ applySettings(pi, settings);
257
+ });
258
+
259
+ pi.on('session_before_switch', async (_event, _ctx) => {
260
+ settings = await loadSettings();
261
+ applySettings(pi, settings);
262
+ });
263
+ }
@@ -0,0 +1,26 @@
1
+ import type { ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';
2
+ import { formatToolError } from '../client';
3
+ import { resolveApiKey } from '../auth';
4
+
5
+ export async function withExaApiKey<T>(
6
+ ctx: ExtensionContext,
7
+ callback: (apiKey: string) => Promise<T>,
8
+ ): Promise<T> {
9
+ const { apiKey } = await resolveApiKey(ctx, { promptIfMissing: true });
10
+ if (!apiKey) {
11
+ throw new Error(
12
+ 'No Exa API key configured. Set EXA_API_KEY before starting pi or run /exa-auth set.',
13
+ );
14
+ }
15
+ return callback(apiKey);
16
+ }
17
+
18
+ export function errorResult(error: unknown) {
19
+ return {
20
+ content: [{ type: 'text' as const, text: formatToolError(error) }],
21
+ details: undefined,
22
+ isError: true,
23
+ };
24
+ }
25
+
26
+ export type AnyExaTool = ToolDefinition<any, any, any>;
@@ -0,0 +1,28 @@
1
+ import type { AgentRunTracker } from '../agent-tracker';
2
+ import { createWebAnswerTool } from './web-answer';
3
+ import { createWebAgentTool } from './web-agent';
4
+ import {
5
+ createWebAgentCancelTool,
6
+ createWebAgentDeleteTool,
7
+ createWebAgentEventsTool,
8
+ createWebAgentGetTool,
9
+ createWebAgentListTool,
10
+ } from './web-agent-runs';
11
+ import { createWebFetchTool } from './web-fetch';
12
+ import { createWebSearchTool } from './web-search';
13
+ import { createWebSearchAdvancedTool } from './web-search-advanced';
14
+
15
+ export function createExaTools(tracker: AgentRunTracker) {
16
+ return [
17
+ createWebSearchTool(),
18
+ createWebSearchAdvancedTool(),
19
+ createWebFetchTool(),
20
+ createWebAnswerTool(),
21
+ createWebAgentTool(tracker),
22
+ createWebAgentGetTool(),
23
+ createWebAgentListTool(),
24
+ createWebAgentCancelTool(),
25
+ createWebAgentDeleteTool(),
26
+ createWebAgentEventsTool(),
27
+ ];
28
+ }
@@ -0,0 +1,377 @@
1
+ import { defineTool } from '@earendil-works/pi-coding-agent';
2
+ import type { Static } from '@sinclair/typebox';
3
+ import {
4
+ cancelAgentRun,
5
+ deleteAgentRun,
6
+ getAgentRun,
7
+ listAgentRunEvents,
8
+ listAgentRuns,
9
+ replayAgentRunEvents,
10
+ } from '../agent';
11
+ import {
12
+ countAgentSources,
13
+ formatAgentEventListResponse,
14
+ formatAgentRunListResponse,
15
+ formatAgentRunResponse,
16
+ formatDeletedAgentRun,
17
+ } from '../format';
18
+ import { truncateToolOutput } from '../output';
19
+ import {
20
+ buildAgentEventListPreview,
21
+ buildAgentRunListPreview,
22
+ buildAgentRunPreview,
23
+ buildDeletedAgentRunPreview,
24
+ metadataFromArgs,
25
+ renderExaCall,
26
+ renderExaResult,
27
+ sendProgress,
28
+ } from '../render';
29
+ import {
30
+ WebAgentEventsParamsSchema,
31
+ WebAgentListParamsSchema,
32
+ WebAgentRunParamsSchema,
33
+ } from '../schemas';
34
+ import type {
35
+ ExaAgentEventListResponse,
36
+ ExaAgentRun,
37
+ ExaAgentRunListResponse,
38
+ ExaDeletedAgentRun,
39
+ ExaToolDetails,
40
+ } from '../types';
41
+ import { compactObject } from '../util';
42
+ import { errorResult, withExaApiKey } from './helpers';
43
+
44
+ type WebAgentRunParams = Static<typeof WebAgentRunParamsSchema>;
45
+ type WebAgentListParams = Static<typeof WebAgentListParamsSchema>;
46
+ type WebAgentEventsParams = Static<typeof WebAgentEventsParamsSchema>;
47
+
48
+ export interface AgentPaginationRequest {
49
+ limit?: number;
50
+ cursor?: string;
51
+ [key: string]: string | number | undefined;
52
+ }
53
+
54
+ export function buildAgentPaginationRequest(
55
+ params: WebAgentListParams | WebAgentEventsParams,
56
+ ): AgentPaginationRequest {
57
+ return compactObject({
58
+ limit: params.limit,
59
+ cursor: params.cursor,
60
+ }) as AgentPaginationRequest;
61
+ }
62
+
63
+ export interface AgentEventReplayRequest {
64
+ lastEventId?: string;
65
+ }
66
+
67
+ export function buildAgentEventReplayRequest(
68
+ params: WebAgentEventsParams,
69
+ ): AgentEventReplayRequest {
70
+ return compactObject({
71
+ lastEventId: params.lastEventId,
72
+ }) as AgentEventReplayRequest;
73
+ }
74
+
75
+ async function collectAsync<T>(items: AsyncIterable<T>): Promise<T[]> {
76
+ const output: T[] = [];
77
+ for await (const item of items) output.push(item);
78
+ return output;
79
+ }
80
+
81
+ export function createWebAgentGetTool() {
82
+ return defineTool<
83
+ typeof WebAgentRunParamsSchema,
84
+ ExaToolDetails<ExaAgentRun, WebAgentRunParams> | undefined
85
+ >({
86
+ name: 'web_agent_get_exa',
87
+ label: 'Exa Agent Get Run',
88
+ description:
89
+ 'Retrieve a stored Exa Agent run by ID, including output, sources, usage, and cost.',
90
+ promptSnippet: 'Get an Exa Agent run by ID',
91
+ promptGuidelines: [
92
+ 'Use web_agent_get_exa to inspect a known Agent run ID, including completed background runs. It returns the full Agent result payload, including structured output and grounding.',
93
+ 'If a run is still queued or running, inspect events only when the user asked for progress/history; otherwise wait for background completion or user direction.',
94
+ 'Do not repeatedly call web_agent_get_exa for a background run started by web_agent_exa; pi-exa tracks background completion and sends a follow-up.',
95
+ ],
96
+ parameters: WebAgentRunParamsSchema,
97
+ async execute(_toolCallId, params, signal, _onUpdate, ctx) {
98
+ try {
99
+ return await withExaApiKey(ctx, async (apiKey) => {
100
+ const response = await getAgentRun(apiKey, params.runId, signal);
101
+ const output = await truncateToolOutput(
102
+ formatAgentRunResponse(response),
103
+ 'web-agent-get-exa',
104
+ 'Use a narrower Agent output schema for future runs, or inspect only events with web_agent_events_exa. ',
105
+ { maxLines: Number.MAX_SAFE_INTEGER, maxBytes: Number.MAX_SAFE_INTEGER },
106
+ );
107
+
108
+ return {
109
+ content: [{ type: 'text', text: output.text }],
110
+ details: {
111
+ endpoint: `/agent/runs/${params.runId}`,
112
+ request: params,
113
+ response,
114
+ requestId: response.id,
115
+ count: countAgentSources(response.output?.grounding),
116
+ costDollars: response.costDollars,
117
+ preview: buildAgentRunPreview(response, output),
118
+ truncated: output.truncation.truncated,
119
+ truncation: output.truncation,
120
+ fullOutputPath: output.fullOutputPath,
121
+ },
122
+ isError: response.status === 'failed',
123
+ };
124
+ });
125
+ } catch (error) {
126
+ return errorResult(error);
127
+ }
128
+ },
129
+ renderCall(args, theme) {
130
+ return renderExaCall('web_agent_get_exa', args.runId, theme);
131
+ },
132
+ renderResult(result, options, theme) {
133
+ return renderExaResult(result, options, theme, 'Getting Exa Agent run...');
134
+ },
135
+ });
136
+ }
137
+
138
+ export function createWebAgentListTool() {
139
+ return defineTool<
140
+ typeof WebAgentListParamsSchema,
141
+ ExaToolDetails<ExaAgentRunListResponse, AgentPaginationRequest> | undefined
142
+ >({
143
+ name: 'web_agent_list_exa',
144
+ label: 'Exa Agent List Runs',
145
+ description: 'List recent Exa Agent runs, with pagination support.',
146
+ promptSnippet: 'List recent Exa Agent runs',
147
+ promptGuidelines: [
148
+ 'Use web_agent_list_exa when the user needs to find a run ID or inspect recent Agent run statuses.',
149
+ 'Use cursor for pagination only when the first page does not contain the needed run.',
150
+ 'Do not use web_agent_list_exa as a polling loop for a background run started in this session.',
151
+ ],
152
+ parameters: WebAgentListParamsSchema,
153
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
154
+ const request = buildAgentPaginationRequest(params);
155
+ try {
156
+ return await withExaApiKey(ctx, async (apiKey) => {
157
+ sendProgress(onUpdate, 'Listing Exa Agent runs...');
158
+ const response = await listAgentRuns(apiKey, request, signal);
159
+ const output = await truncateToolOutput(
160
+ formatAgentRunListResponse(response),
161
+ 'web-agent-list-exa',
162
+ 'Use a smaller limit, cursor pagination, or web_agent_get_exa for one run. ',
163
+ );
164
+
165
+ return {
166
+ content: [{ type: 'text', text: output.text }],
167
+ details: {
168
+ endpoint: '/agent/runs',
169
+ request,
170
+ response,
171
+ count: response.data?.length ?? 0,
172
+ preview: buildAgentRunListPreview(response, output),
173
+ truncated: output.truncation.truncated,
174
+ truncation: output.truncation,
175
+ fullOutputPath: output.fullOutputPath,
176
+ },
177
+ };
178
+ });
179
+ } catch (error) {
180
+ return errorResult(error);
181
+ }
182
+ },
183
+ renderCall(args, theme) {
184
+ return renderExaCall(
185
+ 'web_agent_list_exa',
186
+ 'recent runs',
187
+ theme,
188
+ metadataFromArgs(args, ['limit', 'cursor']),
189
+ );
190
+ },
191
+ renderResult(result, options, theme) {
192
+ return renderExaResult(result, options, theme, 'Listing Exa Agent runs...');
193
+ },
194
+ });
195
+ }
196
+
197
+ export function createWebAgentCancelTool() {
198
+ return defineTool<
199
+ typeof WebAgentRunParamsSchema,
200
+ ExaToolDetails<ExaAgentRun, WebAgentRunParams> | undefined
201
+ >({
202
+ name: 'web_agent_cancel_exa',
203
+ label: 'Exa Agent Cancel Run',
204
+ description: 'Cancel a queued or running Exa Agent run.',
205
+ promptSnippet: 'Cancel an Exa Agent run',
206
+ promptGuidelines: [
207
+ 'Use web_agent_cancel_exa only when the user wants to stop a known queued or running Agent run.',
208
+ 'After cancellation, use web_agent_get_exa if the user wants the stored final status and cost.',
209
+ ],
210
+ parameters: WebAgentRunParamsSchema,
211
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
212
+ try {
213
+ return await withExaApiKey(ctx, async (apiKey) => {
214
+ sendProgress(onUpdate, `Cancelling Exa Agent run ${params.runId}...`);
215
+ const response = await cancelAgentRun(apiKey, params.runId, signal);
216
+ const output = await truncateToolOutput(
217
+ formatAgentRunResponse(response),
218
+ 'web-agent-cancel-exa',
219
+ 'Use web_agent_get_exa for the run ID if more detail is needed. ',
220
+ );
221
+
222
+ return {
223
+ content: [{ type: 'text', text: output.text }],
224
+ details: {
225
+ endpoint: `/agent/runs/${params.runId}/cancel`,
226
+ request: params,
227
+ response,
228
+ requestId: response.id,
229
+ costDollars: response.costDollars,
230
+ preview: buildAgentRunPreview(response, output),
231
+ truncated: output.truncation.truncated,
232
+ truncation: output.truncation,
233
+ fullOutputPath: output.fullOutputPath,
234
+ },
235
+ isError: response.status === 'failed',
236
+ };
237
+ });
238
+ } catch (error) {
239
+ return errorResult(error);
240
+ }
241
+ },
242
+ renderCall(args, theme) {
243
+ return renderExaCall('web_agent_cancel_exa', args.runId, theme);
244
+ },
245
+ renderResult(result, options, theme) {
246
+ return renderExaResult(result, options, theme, 'Cancelling Exa Agent run...');
247
+ },
248
+ });
249
+ }
250
+
251
+ export function createWebAgentDeleteTool() {
252
+ return defineTool<
253
+ typeof WebAgentRunParamsSchema,
254
+ ExaToolDetails<ExaDeletedAgentRun, WebAgentRunParams> | undefined
255
+ >({
256
+ name: 'web_agent_delete_exa',
257
+ label: 'Exa Agent Delete Run',
258
+ description: 'Delete a stored Exa Agent run by ID.',
259
+ promptSnippet: 'Delete a stored Exa Agent run',
260
+ promptGuidelines: [
261
+ 'Use web_agent_delete_exa only when the user explicitly wants to delete a stored Agent run.',
262
+ 'Deletion removes the stored run from Exa; prefer web_agent_cancel_exa for stopping active work.',
263
+ ],
264
+ parameters: WebAgentRunParamsSchema,
265
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
266
+ try {
267
+ return await withExaApiKey(ctx, async (apiKey) => {
268
+ sendProgress(onUpdate, `Deleting Exa Agent run ${params.runId}...`);
269
+ const response = await deleteAgentRun(apiKey, params.runId, signal);
270
+ const output = await truncateToolOutput(
271
+ formatDeletedAgentRun(response),
272
+ 'web-agent-delete-exa',
273
+ 'List runs with web_agent_list_exa if you need another run ID. ',
274
+ );
275
+
276
+ return {
277
+ content: [{ type: 'text', text: output.text }],
278
+ details: {
279
+ endpoint: `/agent/runs/${params.runId}`,
280
+ request: params,
281
+ response,
282
+ requestId: response.id,
283
+ preview: buildDeletedAgentRunPreview(response, output),
284
+ truncated: output.truncation.truncated,
285
+ truncation: output.truncation,
286
+ fullOutputPath: output.fullOutputPath,
287
+ },
288
+ isError: !response.deleted,
289
+ };
290
+ });
291
+ } catch (error) {
292
+ return errorResult(error);
293
+ }
294
+ },
295
+ renderCall(args, theme) {
296
+ return renderExaCall('web_agent_delete_exa', args.runId, theme);
297
+ },
298
+ renderResult(result, options, theme) {
299
+ return renderExaResult(result, options, theme, 'Deleting Exa Agent run...');
300
+ },
301
+ });
302
+ }
303
+
304
+ export function createWebAgentEventsTool() {
305
+ return defineTool<
306
+ typeof WebAgentEventsParamsSchema,
307
+ ExaToolDetails<ExaAgentEventListResponse, WebAgentEventsParams> | undefined
308
+ >({
309
+ name: 'web_agent_events_exa',
310
+ label: 'Exa Agent Run Events',
311
+ description: 'List stored lifecycle events for an Exa Agent run.',
312
+ promptSnippet: 'List events for an Exa Agent run',
313
+ promptGuidelines: [
314
+ 'Use web_agent_events_exa to inspect lifecycle progress or replay stored events for a known Agent run.',
315
+ 'Use web_agent_get_exa when the user needs the run output rather than event history.',
316
+ 'Do not call web_agent_events_exa repeatedly for background runs; the extension tracks background completion and sends a follow-up.',
317
+ ],
318
+ parameters: WebAgentEventsParamsSchema,
319
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
320
+ const request = params;
321
+ const pagination = buildAgentPaginationRequest(params);
322
+ const replay = buildAgentEventReplayRequest(params);
323
+ try {
324
+ return await withExaApiKey(ctx, async (apiKey) => {
325
+ sendProgress(
326
+ onUpdate,
327
+ params.replay
328
+ ? `Replaying events for Exa Agent run ${params.runId}...`
329
+ : `Listing events for Exa Agent run ${params.runId}...`,
330
+ );
331
+ const response = params.replay
332
+ ? {
333
+ object: 'list',
334
+ data: await collectAsync(
335
+ replayAgentRunEvents(apiKey, params.runId, replay, signal),
336
+ ),
337
+ hasMore: false,
338
+ nextCursor: null,
339
+ }
340
+ : await listAgentRunEvents(apiKey, params.runId, pagination, signal);
341
+ const output = await truncateToolOutput(
342
+ formatAgentEventListResponse(response),
343
+ 'web-agent-events-exa',
344
+ 'Use a smaller limit, cursor pagination, replay with lastEventId, or web_agent_get_exa for run output. ',
345
+ );
346
+
347
+ return {
348
+ content: [{ type: 'text', text: output.text }],
349
+ details: {
350
+ endpoint: `/agent/runs/${params.runId}/events`,
351
+ request,
352
+ response,
353
+ count: response.data?.length ?? 0,
354
+ preview: buildAgentEventListPreview(response, output),
355
+ truncated: output.truncation.truncated,
356
+ truncation: output.truncation,
357
+ fullOutputPath: output.fullOutputPath,
358
+ },
359
+ };
360
+ });
361
+ } catch (error) {
362
+ return errorResult(error);
363
+ }
364
+ },
365
+ renderCall(args, theme) {
366
+ return renderExaCall(
367
+ 'web_agent_events_exa',
368
+ args.runId,
369
+ theme,
370
+ metadataFromArgs(args, ['limit', 'cursor', 'replay', 'lastEventId']),
371
+ );
372
+ },
373
+ renderResult(result, options, theme) {
374
+ return renderExaResult(result, options, theme, 'Listing Exa Agent run events...');
375
+ },
376
+ });
377
+ }