@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.
- package/README.md +109 -0
- package/assets/screenshot.png +0 -0
- package/assets/web_search_exa.png +0 -0
- package/extensions/agent-tracker.ts +258 -0
- package/extensions/agent-widget.ts +136 -0
- package/extensions/agent.ts +289 -0
- package/extensions/auth.ts +66 -0
- package/extensions/client.ts +146 -0
- package/extensions/format.ts +436 -0
- package/extensions/index.ts +69 -0
- package/extensions/output.ts +52 -0
- package/extensions/render.ts +394 -0
- package/extensions/schemas.ts +239 -0
- package/extensions/settings.ts +263 -0
- package/extensions/tools/helpers.ts +26 -0
- package/extensions/tools/index.ts +28 -0
- package/extensions/tools/web-agent-runs.ts +377 -0
- package/extensions/tools/web-agent.ts +328 -0
- package/extensions/tools/web-answer.ts +92 -0
- package/extensions/tools/web-fetch.ts +94 -0
- package/extensions/tools/web-search-advanced.ts +164 -0
- package/extensions/tools/web-search.ts +112 -0
- package/extensions/types.ts +238 -0
- package/extensions/util.ts +26 -0
- package/package.json +46 -0
- package/skills/exa-search/SKILL.md +81 -0
- package/skills/exa-search/references/web-agent-exa.md +277 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExaAgentCostDollars,
|
|
3
|
+
ExaAgentEvent,
|
|
4
|
+
ExaAgentEventListResponse,
|
|
5
|
+
ExaAgentGroundingEntry,
|
|
6
|
+
ExaAgentRun,
|
|
7
|
+
ExaAgentRunListResponse,
|
|
8
|
+
ExaAgentUsage,
|
|
9
|
+
ExaAnswerResponse,
|
|
10
|
+
ExaContentsResponse,
|
|
11
|
+
ExaContentsStatus,
|
|
12
|
+
ExaDeletedAgentRun,
|
|
13
|
+
ExaLink,
|
|
14
|
+
ExaSearchResponse,
|
|
15
|
+
ExaSearchResult,
|
|
16
|
+
} from './types';
|
|
17
|
+
import { asString, isRecord, truncateText } from './util';
|
|
18
|
+
|
|
19
|
+
const CITATION_TEXT_MAX = 500;
|
|
20
|
+
const MAX_EXTRA_LINKS = 10;
|
|
21
|
+
const AGENT_SOURCE_MAX = 10;
|
|
22
|
+
|
|
23
|
+
function stringifyValue(value: unknown): string | undefined {
|
|
24
|
+
if (typeof value === 'string') return value.trim() || undefined;
|
|
25
|
+
if (value === undefined || value === null) return undefined;
|
|
26
|
+
try {
|
|
27
|
+
return JSON.stringify(value, null, 2);
|
|
28
|
+
} catch {
|
|
29
|
+
return String(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatMetadataLine(result: {
|
|
34
|
+
publishedDate?: string | null;
|
|
35
|
+
author?: string | null;
|
|
36
|
+
score?: number;
|
|
37
|
+
}): string | undefined {
|
|
38
|
+
const parts: string[] = [];
|
|
39
|
+
if (result.publishedDate) parts.push(`Published: ${result.publishedDate}`);
|
|
40
|
+
if (result.author) parts.push(`Author: ${result.author}`);
|
|
41
|
+
if (result.score !== undefined) parts.push(`Score: ${result.score}`);
|
|
42
|
+
return parts.length ? parts.join(' | ') : undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function formatMetadata(result: ExaSearchResult): string[] {
|
|
46
|
+
const lines = [`Title: ${result.title || 'N/A'}`, `URL: ${result.url || result.id || 'N/A'}`];
|
|
47
|
+
if (result.id && result.id !== result.url) lines.push(`ID: ${result.id}`);
|
|
48
|
+
const metadata = formatMetadataLine(result);
|
|
49
|
+
if (metadata) lines.push(metadata);
|
|
50
|
+
if (result.image) lines.push(`Image: ${result.image}`);
|
|
51
|
+
if (result.favicon) lines.push(`Favicon: ${result.favicon}`);
|
|
52
|
+
return lines;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatLinkEntry(link: string | ExaLink): string {
|
|
56
|
+
if (typeof link === 'string') return link;
|
|
57
|
+
const label = asString(link.title) || asString(link.altText) || 'Link';
|
|
58
|
+
return `${label}${link.url ? ` - ${link.url}` : ''}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function appendExtras(lines: string[], result: ExaSearchResult, indent = '') {
|
|
62
|
+
const links = [...(result.links || []), ...(result.extras?.links || [])];
|
|
63
|
+
if (links.length > 0) {
|
|
64
|
+
lines.push(`${indent}Links:`);
|
|
65
|
+
for (const link of links.slice(0, MAX_EXTRA_LINKS)) {
|
|
66
|
+
lines.push(`${indent}- ${formatLinkEntry(link)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const imageLinks = [...(result.imageLinks || []), ...(result.extras?.imageLinks || [])];
|
|
71
|
+
if (imageLinks.length > 0) {
|
|
72
|
+
lines.push(`${indent}Image links:`);
|
|
73
|
+
for (const imageLink of imageLinks.slice(0, MAX_EXTRA_LINKS)) {
|
|
74
|
+
lines.push(`${indent}- ${formatLinkEntry(imageLink)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatSearchResult(result: ExaSearchResult): string {
|
|
80
|
+
const lines = formatMetadata(result);
|
|
81
|
+
|
|
82
|
+
const summary = stringifyValue(result.summary);
|
|
83
|
+
if (summary) {
|
|
84
|
+
lines.push('Summary:');
|
|
85
|
+
lines.push(summary);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (Array.isArray(result.highlights) && result.highlights.length > 0) {
|
|
89
|
+
lines.push('Highlights:');
|
|
90
|
+
lines.push(...result.highlights.filter(Boolean).map((highlight) => `- ${highlight}`));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result.text) {
|
|
94
|
+
lines.push('Text:');
|
|
95
|
+
lines.push(result.text);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
appendExtras(lines, result);
|
|
99
|
+
|
|
100
|
+
if (Array.isArray(result.subpages) && result.subpages.length > 0) {
|
|
101
|
+
lines.push('Subpages:');
|
|
102
|
+
for (const [index, subpage] of result.subpages.entries()) {
|
|
103
|
+
lines.push(formatContentResult(subpage, index + 1, 1));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function formatSearchResponse(response: ExaSearchResponse): string {
|
|
111
|
+
const results = Array.isArray(response.results) ? response.results : [];
|
|
112
|
+
const sections: string[] = [];
|
|
113
|
+
|
|
114
|
+
if (response.context) {
|
|
115
|
+
sections.push(`Context:\n${response.context}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (response.output) {
|
|
119
|
+
const output = stringifyValue(response.output.content);
|
|
120
|
+
if (output) sections.push(`Synthesized output:\n${output}`);
|
|
121
|
+
|
|
122
|
+
const grounding = response.output.grounding || [];
|
|
123
|
+
if (grounding.length > 0) {
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
const lines = ['Sources:'];
|
|
126
|
+
for (const item of grounding) {
|
|
127
|
+
for (const citation of item.citations || []) {
|
|
128
|
+
const key = citation.url || citation.title;
|
|
129
|
+
if (!key || seen.has(key)) continue;
|
|
130
|
+
seen.add(key);
|
|
131
|
+
lines.push(`- ${citation.title || 'Source'}${citation.url ? ` - ${citation.url}` : ''}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (lines.length > 1) sections.push(lines.join('\n'));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (results.length > 0) {
|
|
139
|
+
sections.push(results.map(formatSearchResult).join('\n\n---\n\n'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const errors = Array.isArray(response.statuses)
|
|
143
|
+
? response.statuses.filter((status) => status.status === 'error')
|
|
144
|
+
: [];
|
|
145
|
+
if (errors.length > 0) sections.push(errors.map(formatStatusError).join('\n'));
|
|
146
|
+
|
|
147
|
+
return sections.length > 0 ? sections.join('\n\n---\n\n') : 'No search results found.';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatContentResult(result: ExaSearchResult, index?: number, depth = 0): string {
|
|
151
|
+
const indent = ' '.repeat(depth);
|
|
152
|
+
const title = result.title || result.url || result.id || '(no title)';
|
|
153
|
+
const lines = [index === undefined ? `# ${title}` : `${indent}## Item ${index}: ${title}`];
|
|
154
|
+
if (result.url || result.id) lines.push(`${indent}URL: ${result.url || result.id || 'N/A'}`);
|
|
155
|
+
if (result.id && result.id !== result.url) lines.push(`${indent}ID: ${result.id}`);
|
|
156
|
+
const metadata = formatMetadataLine(result);
|
|
157
|
+
if (metadata) lines.push(`${indent}${metadata}`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
160
|
+
const summary = stringifyValue(result.summary);
|
|
161
|
+
if (summary) {
|
|
162
|
+
lines.push(`${indent}Summary:`);
|
|
163
|
+
lines.push(summary);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(result.highlights) && result.highlights.length > 0) {
|
|
167
|
+
lines.push(`${indent}Highlights:`);
|
|
168
|
+
lines.push(...result.highlights.filter(Boolean).map((highlight) => `${indent}- ${highlight}`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (result.text) {
|
|
172
|
+
lines.push(`${indent}Text:`);
|
|
173
|
+
lines.push(result.text);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
appendExtras(lines, result, indent);
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(result.subpages) && result.subpages.length > 0) {
|
|
179
|
+
lines.push(`${indent}Subpages:`);
|
|
180
|
+
for (const [subpageIndex, subpage] of result.subpages.entries()) {
|
|
181
|
+
lines.push(formatContentResult(subpage, subpageIndex + 1, depth + 1));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return lines.join('\n').trim();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function formatStatusError(status: ExaContentsStatus): string {
|
|
189
|
+
const target = status.url || status.id || 'unknown URL';
|
|
190
|
+
const error = status.error;
|
|
191
|
+
if (typeof error === 'string') return `Error fetching ${target}: ${error}`;
|
|
192
|
+
if (isRecord(error)) {
|
|
193
|
+
return `Error fetching ${target}: ${error.message || error.tag || 'unknown error'}`;
|
|
194
|
+
}
|
|
195
|
+
return `Error fetching ${target}: unknown error`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function formatContentsResponse(response: ExaContentsResponse): string {
|
|
199
|
+
const results = Array.isArray(response.results) ? response.results : [];
|
|
200
|
+
const errors = Array.isArray(response.statuses)
|
|
201
|
+
? response.statuses.filter((status) => status.status === 'error')
|
|
202
|
+
: [];
|
|
203
|
+
|
|
204
|
+
const sections = [
|
|
205
|
+
...results.map((result, index) => formatContentResult(result, index + 1)),
|
|
206
|
+
...errors.map(formatStatusError),
|
|
207
|
+
];
|
|
208
|
+
return sections.length > 0
|
|
209
|
+
? sections.join('\n\n---\n\n')
|
|
210
|
+
: 'No content found for the provided URL(s).';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function hasContentsErrors(response: ExaContentsResponse): boolean {
|
|
214
|
+
return Boolean(response.statuses?.some((status) => status.status === 'error'));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formatAnswerValue(answer: ExaAnswerResponse['answer']): string {
|
|
218
|
+
if (answer === undefined) return 'No answer returned.';
|
|
219
|
+
if (typeof answer === 'string') return answer;
|
|
220
|
+
return JSON.stringify(answer, null, 2);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function formatCitation(citation: ExaSearchResult, index: number): string {
|
|
224
|
+
const lines = [
|
|
225
|
+
`${index + 1}. ${citation.title || citation.url || citation.id || 'Untitled source'}`,
|
|
226
|
+
];
|
|
227
|
+
if (citation.url) lines.push(` URL: ${citation.url}`);
|
|
228
|
+
const metadata = formatMetadataLine(citation);
|
|
229
|
+
if (metadata) lines.push(` ${metadata}`);
|
|
230
|
+
if (citation.text) lines.push(` Text: ${truncateText(citation.text, CITATION_TEXT_MAX)}`);
|
|
231
|
+
return lines.join('\n');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function formatAnswerResponse(response: ExaAnswerResponse): string {
|
|
235
|
+
const lines = [`Answer:\n${formatAnswerValue(response.answer)}`];
|
|
236
|
+
const citations = Array.isArray(response.citations) ? response.citations : [];
|
|
237
|
+
|
|
238
|
+
if (citations.length > 0) {
|
|
239
|
+
lines.push(`Sources:\n${citations.map(formatCitation).join('\n\n')}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return lines.join('\n\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function formatJsonBlock(value: unknown): string {
|
|
246
|
+
if (value === undefined || value === null) return '';
|
|
247
|
+
if (typeof value === 'string') return value;
|
|
248
|
+
return JSON.stringify(value, null, 2);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function formatKeyValues(
|
|
252
|
+
value: ExaAgentUsage | ExaAgentCostDollars | undefined,
|
|
253
|
+
keys: string[],
|
|
254
|
+
): string | undefined {
|
|
255
|
+
if (!value) return undefined;
|
|
256
|
+
const parts = keys.flatMap((key) => {
|
|
257
|
+
const item = value[key];
|
|
258
|
+
return item === undefined || item === null ? [] : [`${key}: ${item}`];
|
|
259
|
+
});
|
|
260
|
+
return parts.length ? parts.join(' | ') : undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function countAgentSources(grounding: ExaAgentGroundingEntry[] | null | undefined): number {
|
|
264
|
+
if (!grounding?.length) return 0;
|
|
265
|
+
|
|
266
|
+
const seen = new Set<string>();
|
|
267
|
+
for (const entry of grounding) {
|
|
268
|
+
for (const citation of entry.citations || []) {
|
|
269
|
+
const key = citation.url || citation.title;
|
|
270
|
+
if (!key) continue;
|
|
271
|
+
seen.add(key);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return seen.size;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function formatAgentSources(grounding: ExaAgentGroundingEntry[] | null | undefined): string[] {
|
|
279
|
+
if (!grounding?.length) return [];
|
|
280
|
+
|
|
281
|
+
const seen = new Set<string>();
|
|
282
|
+
const sources: string[] = [];
|
|
283
|
+
|
|
284
|
+
for (const entry of grounding) {
|
|
285
|
+
for (const citation of entry.citations || []) {
|
|
286
|
+
const key = citation.url || citation.title;
|
|
287
|
+
if (!key || seen.has(key)) continue;
|
|
288
|
+
seen.add(key);
|
|
289
|
+
sources.push(`${citation.title || 'Source'}${citation.url ? ` - ${citation.url}` : ''}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (sources.length === 0) return [];
|
|
294
|
+
|
|
295
|
+
const lines = ['Sources:'];
|
|
296
|
+
lines.push(...sources.slice(0, AGENT_SOURCE_MAX).map((source) => `- ${source}`));
|
|
297
|
+
|
|
298
|
+
if (sources.length > AGENT_SOURCE_MAX) {
|
|
299
|
+
lines.push(`...${sources.length - AGENT_SOURCE_MAX} more source(s) omitted.`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return lines;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function formatAgentRunResponse(
|
|
306
|
+
run: ExaAgentRun,
|
|
307
|
+
options?: { timedOut?: boolean; background?: boolean },
|
|
308
|
+
): string {
|
|
309
|
+
const lines: string[] = [`# Exa Agent Run: ${run.id}`];
|
|
310
|
+
|
|
311
|
+
const status = [`Status: ${run.status}`];
|
|
312
|
+
if (run.stopReason) status.push(`Stop reason: ${run.stopReason}`);
|
|
313
|
+
if (options?.timedOut) status.push('Wait timed out');
|
|
314
|
+
if (options?.background) status.push('Background tracking enabled; do not poll unless needed');
|
|
315
|
+
lines.push(status.join(' | '));
|
|
316
|
+
|
|
317
|
+
const dates = [];
|
|
318
|
+
if (run.createdAt) dates.push(`Created: ${run.createdAt}`);
|
|
319
|
+
if (run.completedAt) dates.push(`Completed: ${run.completedAt}`);
|
|
320
|
+
if (dates.length > 0) lines.push(dates.join(' | '));
|
|
321
|
+
|
|
322
|
+
if (run.request?.query) {
|
|
323
|
+
lines.push('');
|
|
324
|
+
lines.push('Query:');
|
|
325
|
+
lines.push(run.request.query);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (run.output?.text) {
|
|
329
|
+
lines.push('');
|
|
330
|
+
lines.push('Answer:');
|
|
331
|
+
lines.push(run.output.text);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (run.output?.structured !== undefined && run.output.structured !== null) {
|
|
335
|
+
lines.push('');
|
|
336
|
+
lines.push('Structured output:');
|
|
337
|
+
lines.push(formatJsonBlock(run.output.structured));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const sources = formatAgentSources(run.output?.grounding);
|
|
341
|
+
if (sources.length > 0) {
|
|
342
|
+
lines.push('');
|
|
343
|
+
lines.push(...sources);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (run.output?.grounding?.length) {
|
|
347
|
+
lines.push('');
|
|
348
|
+
lines.push('Citation grounding:');
|
|
349
|
+
lines.push(formatJsonBlock(run.output.grounding));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (run.error) {
|
|
353
|
+
lines.push('');
|
|
354
|
+
lines.push('Error:');
|
|
355
|
+
lines.push(formatJsonBlock(run.error));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const usage = formatKeyValues(run.usage, [
|
|
359
|
+
'agentComputeUnits',
|
|
360
|
+
'searches',
|
|
361
|
+
'emails',
|
|
362
|
+
'phoneNumbers',
|
|
363
|
+
]);
|
|
364
|
+
if (usage) {
|
|
365
|
+
lines.push('');
|
|
366
|
+
lines.push(`Usage: ${usage}`);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const cost = formatKeyValues(run.costDollars, [
|
|
370
|
+
'total',
|
|
371
|
+
'agentCompute',
|
|
372
|
+
'search',
|
|
373
|
+
'emails',
|
|
374
|
+
'phoneNumbers',
|
|
375
|
+
]);
|
|
376
|
+
if (cost) lines.push(`Cost: ${cost}`);
|
|
377
|
+
|
|
378
|
+
return lines.join('\n');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function formatAgentRunListEntry(run: ExaAgentRun, index: number): string {
|
|
382
|
+
const lines = [`${index + 1}. ${run.id} | ${run.status}`];
|
|
383
|
+
if (run.createdAt) lines.push(` Created: ${run.createdAt}`);
|
|
384
|
+
if (run.completedAt) lines.push(` Completed: ${run.completedAt}`);
|
|
385
|
+
if (run.stopReason) lines.push(` Stop reason: ${run.stopReason}`);
|
|
386
|
+
if (run.request?.query) lines.push(` Query: ${truncateText(run.request.query, 240)}`);
|
|
387
|
+
const cost = formatKeyValues(run.costDollars, ['total', 'agentCompute', 'search']);
|
|
388
|
+
if (cost) lines.push(` Cost: ${cost}`);
|
|
389
|
+
return lines.join('\n');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function formatAgentRunListResponse(response: ExaAgentRunListResponse): string {
|
|
393
|
+
const runs = Array.isArray(response.data) ? response.data : [];
|
|
394
|
+
const lines = [`Agent runs: ${runs.length}`];
|
|
395
|
+
|
|
396
|
+
if (runs.length > 0) {
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push(runs.map(formatAgentRunListEntry).join('\n\n'));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (response.hasMore) {
|
|
402
|
+
lines.push('');
|
|
403
|
+
lines.push(`More runs available. nextCursor: ${response.nextCursor || '(none returned)'}`);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return lines.join('\n');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function formatAgentEvent(event: ExaAgentEvent, index: number): string {
|
|
410
|
+
const lines = [`${index + 1}. ${event.event}`];
|
|
411
|
+
if (event.id) lines.push(` Event ID: ${event.id}`);
|
|
412
|
+
if (event.createdAt) lines.push(` Created: ${event.createdAt}`);
|
|
413
|
+
lines.push(` Data: ${formatJsonBlock(event.data).replace(/\n/g, '\n ')}`);
|
|
414
|
+
return lines.join('\n');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function formatAgentEventListResponse(response: ExaAgentEventListResponse): string {
|
|
418
|
+
const events = Array.isArray(response.data) ? response.data : [];
|
|
419
|
+
const lines = [`Agent run events: ${events.length}`];
|
|
420
|
+
|
|
421
|
+
if (events.length > 0) {
|
|
422
|
+
lines.push('');
|
|
423
|
+
lines.push(events.map(formatAgentEvent).join('\n\n'));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (response.hasMore) {
|
|
427
|
+
lines.push('');
|
|
428
|
+
lines.push(`More events available. nextCursor: ${response.nextCursor || '(none returned)'}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return lines.join('\n');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function formatDeletedAgentRun(response: ExaDeletedAgentRun): string {
|
|
435
|
+
return `Deleted Agent run: ${response.id}\nDeleted: ${response.deleted ? 'yes' : 'no'}`;
|
|
436
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { AgentRunTracker } from './agent-tracker';
|
|
3
|
+
import { clearCredentials, readCredentials, resolveApiKey, writeCredentials } from './auth';
|
|
4
|
+
import { registerExaSettings } from './settings';
|
|
5
|
+
import { createExaTools } from './tools';
|
|
6
|
+
import { asString } from './util';
|
|
7
|
+
|
|
8
|
+
export default async function exaExtension(pi: ExtensionAPI) {
|
|
9
|
+
const tracker = new AgentRunTracker(pi);
|
|
10
|
+
|
|
11
|
+
pi.registerCommand('exa-auth', {
|
|
12
|
+
description: 'Manage Exa API auth (usage: /exa-auth [set|clear|status])',
|
|
13
|
+
handler: async (args, ctx) => {
|
|
14
|
+
const subcommand = args.trim().split(/\s+/)[0]?.toLowerCase() || 'status';
|
|
15
|
+
|
|
16
|
+
switch (subcommand) {
|
|
17
|
+
case 'set': {
|
|
18
|
+
if (!ctx.hasUI) {
|
|
19
|
+
ctx.ui.notify('Cannot prompt for an Exa API key without UI support', 'warning');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keyInput = await ctx.ui.input('Exa API key', 'exa_...');
|
|
24
|
+
const apiKey = asString(keyInput);
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
ctx.ui.notify('No Exa API key provided', 'warning');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await writeCredentials(apiKey);
|
|
31
|
+
ctx.ui.notify('Exa API key saved for pi-exa', 'info');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
case 'clear': {
|
|
36
|
+
await clearCredentials();
|
|
37
|
+
ctx.ui.notify('Cleared pi-exa stored credentials', 'info');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'status':
|
|
42
|
+
case '': {
|
|
43
|
+
const { source } = await resolveApiKey(ctx, { promptIfMissing: false });
|
|
44
|
+
const credentials = await readCredentials();
|
|
45
|
+
const stored = credentials.apiKey ? 'yes' : 'no';
|
|
46
|
+
ctx.ui.notify(`Auth source: ${source}\nStored pi-exa credential: ${stored}`, 'info');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
default:
|
|
51
|
+
ctx.ui.notify('Usage: /exa-auth [set|clear|status]', 'warning');
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
for (const tool of createExaTools(tracker)) {
|
|
57
|
+
pi.registerTool(tool);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await registerExaSettings(pi);
|
|
61
|
+
|
|
62
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
63
|
+
await tracker.resume(ctx);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
pi.on('session_shutdown', async (_event, ctx) => {
|
|
67
|
+
tracker.shutdown(ctx);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_MAX_BYTES,
|
|
6
|
+
DEFAULT_MAX_LINES,
|
|
7
|
+
formatSize,
|
|
8
|
+
truncateHead,
|
|
9
|
+
type TruncationResult,
|
|
10
|
+
} from '@earendil-works/pi-coding-agent';
|
|
11
|
+
|
|
12
|
+
const TEMP_FILE_PREFIX = 'pi-exa-';
|
|
13
|
+
|
|
14
|
+
export interface TruncatedToolOutput {
|
|
15
|
+
text: string;
|
|
16
|
+
truncation: TruncationResult;
|
|
17
|
+
fullOutputPath?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function truncateToolOutput(
|
|
21
|
+
text: string,
|
|
22
|
+
fileStem: string,
|
|
23
|
+
refineHint: string,
|
|
24
|
+
options?: { maxLines?: number; maxBytes?: number },
|
|
25
|
+
): Promise<TruncatedToolOutput> {
|
|
26
|
+
const truncation = truncateHead(text, {
|
|
27
|
+
maxLines: options?.maxLines ?? DEFAULT_MAX_LINES,
|
|
28
|
+
maxBytes: options?.maxBytes ?? DEFAULT_MAX_BYTES,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!truncation.truncated) {
|
|
32
|
+
return { text: truncation.content, truncation };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const tempDir = await mkdtemp(join(tmpdir(), TEMP_FILE_PREFIX));
|
|
36
|
+
const fullOutputPath = join(tempDir, `${fileStem}.txt`);
|
|
37
|
+
await writeFile(fullOutputPath, text, 'utf8');
|
|
38
|
+
|
|
39
|
+
const truncatedLines = truncation.totalLines - truncation.outputLines;
|
|
40
|
+
const truncatedBytes = truncation.totalBytes - truncation.outputBytes;
|
|
41
|
+
const notice =
|
|
42
|
+
`\n\n[Output truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines ` +
|
|
43
|
+
`(${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}). ` +
|
|
44
|
+
`${truncatedLines} lines (${formatSize(truncatedBytes)}) omitted. ` +
|
|
45
|
+
`${refineHint}Full output saved to: ${fullOutputPath}]`;
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
text: truncation.content + notice,
|
|
49
|
+
truncation,
|
|
50
|
+
fullOutputPath,
|
|
51
|
+
};
|
|
52
|
+
}
|