@oagi/oagi 0.1.4 → 0.1.5
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/agent_observer.ts +123 -0
- package/dist/{chunk-JVNTVY6W.js → chunk-SRTB44IH.js} +1021 -76
- package/dist/chunk-SRTB44IH.js.map +1 -0
- package/dist/cli.cjs +952 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/events.ts +21 -0
- package/dist/exporters.ts +432 -0
- package/dist/index.cjs +1334 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -18
- package/dist/index.d.ts +165 -18
- package/dist/index.js +7 -1
- package/dist/index.ts +23 -0
- package/dist/protocol.ts +14 -0
- package/dist/report_template.html +474 -0
- package/package.json +4 -2
- package/README.md +0 -154
- package/dist/chunk-JVNTVY6W.js.map +0 -1
package/dist/cli.js
CHANGED
package/dist/events.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* -----------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) OpenAGI Foundation
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* This file is part of the official API project.
|
|
7
|
+
* Licensed under the MIT License.
|
|
8
|
+
* -----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Re-export from types for convenience
|
|
12
|
+
export type {
|
|
13
|
+
ActionEvent,
|
|
14
|
+
BaseEvent,
|
|
15
|
+
ImageEvent,
|
|
16
|
+
LogEvent,
|
|
17
|
+
ObserverEvent,
|
|
18
|
+
SplitEvent,
|
|
19
|
+
StepEvent,
|
|
20
|
+
PlanEvent,
|
|
21
|
+
} from '../../types/index.js';
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* -----------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) OpenAGI Foundation
|
|
4
|
+
* All rights reserved.
|
|
5
|
+
*
|
|
6
|
+
* This file is part of the official API project.
|
|
7
|
+
* Licensed under the MIT License.
|
|
8
|
+
* -----------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
14
|
+
|
|
15
|
+
import type { Action, ActionType, ObserverEvent } from '../../types/index.js';
|
|
16
|
+
import {
|
|
17
|
+
parseCoords,
|
|
18
|
+
parseDragCoords,
|
|
19
|
+
parseScroll,
|
|
20
|
+
} from '../../types/models/action.js';
|
|
21
|
+
|
|
22
|
+
const ensureDir = (dirPath: string) => {
|
|
23
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type ParsedActionCoords =
|
|
27
|
+
| { type: 'click'; x: number; y: number }
|
|
28
|
+
| { type: 'drag'; x1: number; y1: number; x2: number; y2: number }
|
|
29
|
+
| { type: 'scroll'; x: number; y: number; direction: string };
|
|
30
|
+
|
|
31
|
+
const parseActionCoords = (action: Action): ParsedActionCoords | null => {
|
|
32
|
+
/**
|
|
33
|
+
* Parse coordinates from action argument for cursor indicators.
|
|
34
|
+
*/
|
|
35
|
+
const arg = action.argument.replace(/^\(|\)$/g, '');
|
|
36
|
+
|
|
37
|
+
switch (action.type as ActionType) {
|
|
38
|
+
case 'click':
|
|
39
|
+
case 'left_double':
|
|
40
|
+
case 'left_triple':
|
|
41
|
+
case 'right_single': {
|
|
42
|
+
const coords = parseCoords(arg);
|
|
43
|
+
if (coords) {
|
|
44
|
+
return { type: 'click', x: coords[0], y: coords[1] };
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
case 'drag': {
|
|
50
|
+
const coords = parseDragCoords(arg);
|
|
51
|
+
if (coords) {
|
|
52
|
+
return {
|
|
53
|
+
type: 'drag',
|
|
54
|
+
x1: coords[0],
|
|
55
|
+
y1: coords[1],
|
|
56
|
+
x2: coords[2],
|
|
57
|
+
y2: coords[3],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'scroll': {
|
|
64
|
+
const result = parseScroll(arg);
|
|
65
|
+
if (result) {
|
|
66
|
+
return {
|
|
67
|
+
type: 'scroll',
|
|
68
|
+
x: result[0],
|
|
69
|
+
y: result[1],
|
|
70
|
+
direction: result[2],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
default:
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const exportToMarkdown = (
|
|
82
|
+
events: ObserverEvent[],
|
|
83
|
+
filePath: string,
|
|
84
|
+
imagesDir?: string | null,
|
|
85
|
+
) => {
|
|
86
|
+
/**
|
|
87
|
+
* Export events to a Markdown file.
|
|
88
|
+
*/
|
|
89
|
+
const outputDir = path.dirname(filePath);
|
|
90
|
+
ensureDir(outputDir);
|
|
91
|
+
|
|
92
|
+
if (imagesDir) {
|
|
93
|
+
ensureDir(imagesDir);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const lines: string[] = ['# Agent Execution Report\n'];
|
|
97
|
+
|
|
98
|
+
for (const event of events) {
|
|
99
|
+
const d =
|
|
100
|
+
event.timestamp instanceof Date
|
|
101
|
+
? event.timestamp
|
|
102
|
+
: new Date(event.timestamp);
|
|
103
|
+
const timestamp = d.toTimeString().slice(0, 8);
|
|
104
|
+
|
|
105
|
+
switch (event.type) {
|
|
106
|
+
case 'step':
|
|
107
|
+
lines.push(`\n## Step ${event.step_num}\n`);
|
|
108
|
+
lines.push(`**Time:** ${timestamp}\n`);
|
|
109
|
+
if (event.task_id) {
|
|
110
|
+
lines.push(`**Task ID:** \`${event.task_id}\`\n`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (typeof event.image !== 'string') {
|
|
114
|
+
if (imagesDir) {
|
|
115
|
+
const imageFilename = `step_${event.step_num}.png`;
|
|
116
|
+
const imagePath = path.join(imagesDir, imageFilename);
|
|
117
|
+
fs.writeFileSync(imagePath, Buffer.from(event.image));
|
|
118
|
+
const relPath = path.join(path.basename(imagesDir), imageFilename);
|
|
119
|
+
lines.push(`\n\n`);
|
|
120
|
+
} else {
|
|
121
|
+
lines.push(
|
|
122
|
+
`\n*[Screenshot captured - ${event.image.byteLength} bytes]*\n`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
lines.push(`\n**Screenshot URL:** ${event.image}\n`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (event.step.reason) {
|
|
130
|
+
lines.push(`\n**Reasoning:**\n> ${event.step.reason}\n`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (event.step.actions?.length) {
|
|
134
|
+
lines.push('\n**Planned Actions:**\n');
|
|
135
|
+
for (const action of event.step.actions) {
|
|
136
|
+
const countStr =
|
|
137
|
+
action.count && action.count > 1 ? ` (x${action.count})` : '';
|
|
138
|
+
lines.push(`- \`${action.type}\`: ${action.argument}${countStr}\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (event.step.stop) {
|
|
143
|
+
lines.push('\n**Status:** Task Complete\n');
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'action':
|
|
148
|
+
lines.push(`\n### Actions Executed (${timestamp})\n`);
|
|
149
|
+
if (event.error) {
|
|
150
|
+
lines.push(`\n**Error:** ${event.error}\n`);
|
|
151
|
+
} else {
|
|
152
|
+
lines.push('\n**Result:** Success\n');
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
case 'log':
|
|
157
|
+
lines.push(`\n> **Log (${timestamp}):** ${event.message}\n`);
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'split':
|
|
161
|
+
if (event.label) {
|
|
162
|
+
lines.push(`\n---\n\n### ${event.label}\n`);
|
|
163
|
+
} else {
|
|
164
|
+
lines.push('\n---\n');
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case 'image':
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
case 'plan': {
|
|
172
|
+
const phaseTitles: Record<string, string> = {
|
|
173
|
+
initial: 'Initial Planning',
|
|
174
|
+
reflection: 'Reflection',
|
|
175
|
+
summary: 'Summary',
|
|
176
|
+
};
|
|
177
|
+
const phaseTitle = phaseTitles[event.phase] ?? event.phase;
|
|
178
|
+
|
|
179
|
+
lines.push(`\n### ${phaseTitle} (${timestamp})\n`);
|
|
180
|
+
if (event.request_id) {
|
|
181
|
+
lines.push(`**Request ID:** \`${event.request_id}\`\n`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (event.image) {
|
|
185
|
+
if (typeof event.image !== 'string') {
|
|
186
|
+
if (imagesDir) {
|
|
187
|
+
const imageFilename = `plan_${event.phase}_${Date.now()}.png`;
|
|
188
|
+
const imagePath = path.join(imagesDir, imageFilename);
|
|
189
|
+
fs.writeFileSync(imagePath, Buffer.from(event.image));
|
|
190
|
+
const relPath = path.join(
|
|
191
|
+
path.basename(imagesDir),
|
|
192
|
+
imageFilename,
|
|
193
|
+
);
|
|
194
|
+
lines.push(`\n\n`);
|
|
195
|
+
} else {
|
|
196
|
+
lines.push(
|
|
197
|
+
`\n*[Screenshot captured - ${event.image.byteLength} bytes]*\n`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
lines.push(`\n**Screenshot URL:** ${event.image}\n`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (event.reasoning) {
|
|
206
|
+
lines.push(`\n**Reasoning:**\n> ${event.reasoning}\n`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (event.result) {
|
|
210
|
+
lines.push(`\n**Result:** ${event.result}\n`);
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fs.writeFileSync(filePath, lines.join(''), 'utf-8');
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
type HtmlStepEvent = {
|
|
221
|
+
event_type: 'step';
|
|
222
|
+
timestamp: string;
|
|
223
|
+
step_num: number;
|
|
224
|
+
image: string | null;
|
|
225
|
+
action_coords: ParsedActionCoords[];
|
|
226
|
+
reason?: string;
|
|
227
|
+
actions: { type: string; argument: string; count: number }[];
|
|
228
|
+
stop: boolean;
|
|
229
|
+
task_id?: string | null;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
type HtmlActionEvent = {
|
|
233
|
+
event_type: 'action';
|
|
234
|
+
timestamp: string;
|
|
235
|
+
error?: string | null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
type HtmlLogEvent = {
|
|
239
|
+
event_type: 'log';
|
|
240
|
+
timestamp: string;
|
|
241
|
+
message: string;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
type HtmlSplitEvent = {
|
|
245
|
+
event_type: 'split';
|
|
246
|
+
timestamp: string;
|
|
247
|
+
label: string;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
type HtmlPlanEvent = {
|
|
251
|
+
event_type: 'plan';
|
|
252
|
+
timestamp: string;
|
|
253
|
+
phase: string;
|
|
254
|
+
image: string | null;
|
|
255
|
+
reasoning: string;
|
|
256
|
+
result?: string | null;
|
|
257
|
+
request_id?: string | null;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
type HtmlEvent =
|
|
261
|
+
| HtmlStepEvent
|
|
262
|
+
| HtmlActionEvent
|
|
263
|
+
| HtmlLogEvent
|
|
264
|
+
| HtmlSplitEvent
|
|
265
|
+
| HtmlPlanEvent;
|
|
266
|
+
|
|
267
|
+
const convertEventsForHtml = (events: ObserverEvent[]): HtmlEvent[] => {
|
|
268
|
+
/** Convert events to JSON-serializable format for HTML template. */
|
|
269
|
+
const result: HtmlEvent[] = [];
|
|
270
|
+
|
|
271
|
+
for (const event of events) {
|
|
272
|
+
const d =
|
|
273
|
+
event.timestamp instanceof Date
|
|
274
|
+
? event.timestamp
|
|
275
|
+
: new Date(event.timestamp);
|
|
276
|
+
const timestamp = d.toTimeString().slice(0, 8);
|
|
277
|
+
|
|
278
|
+
switch (event.type) {
|
|
279
|
+
case 'step': {
|
|
280
|
+
const action_coords: ParsedActionCoords[] = [];
|
|
281
|
+
const actions: { type: string; argument: string; count: number }[] = [];
|
|
282
|
+
|
|
283
|
+
if (event.step.actions?.length) {
|
|
284
|
+
for (const action of event.step.actions) {
|
|
285
|
+
const coords = parseActionCoords(action);
|
|
286
|
+
if (coords) {
|
|
287
|
+
action_coords.push(coords);
|
|
288
|
+
}
|
|
289
|
+
actions.push({
|
|
290
|
+
type: action.type,
|
|
291
|
+
argument: action.argument,
|
|
292
|
+
count: action.count ?? 1,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let image: string | null = null;
|
|
298
|
+
if (typeof event.image !== 'string') {
|
|
299
|
+
image = Buffer.from(event.image).toString('base64');
|
|
300
|
+
} else {
|
|
301
|
+
image = event.image;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
result.push({
|
|
305
|
+
event_type: 'step',
|
|
306
|
+
timestamp,
|
|
307
|
+
step_num: event.step_num,
|
|
308
|
+
image,
|
|
309
|
+
action_coords,
|
|
310
|
+
reason: event.step.reason,
|
|
311
|
+
actions,
|
|
312
|
+
stop: event.step.stop,
|
|
313
|
+
task_id: event.task_id,
|
|
314
|
+
});
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case 'action':
|
|
319
|
+
result.push({
|
|
320
|
+
event_type: 'action',
|
|
321
|
+
timestamp,
|
|
322
|
+
error: event.error ?? null,
|
|
323
|
+
});
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
case 'log':
|
|
327
|
+
result.push({ event_type: 'log', timestamp, message: event.message });
|
|
328
|
+
break;
|
|
329
|
+
|
|
330
|
+
case 'split':
|
|
331
|
+
result.push({ event_type: 'split', timestamp, label: event.label! });
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case 'image':
|
|
335
|
+
break;
|
|
336
|
+
|
|
337
|
+
case 'plan': {
|
|
338
|
+
let image: string | null = null;
|
|
339
|
+
if (event.image) {
|
|
340
|
+
if (typeof event.image !== 'string') {
|
|
341
|
+
image = Buffer.from(event.image).toString('base64');
|
|
342
|
+
} else {
|
|
343
|
+
image = event.image;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
result.push({
|
|
348
|
+
event_type: 'plan',
|
|
349
|
+
timestamp,
|
|
350
|
+
phase: event.phase,
|
|
351
|
+
image,
|
|
352
|
+
reasoning: event.reasoning,
|
|
353
|
+
result: event.result ?? null,
|
|
354
|
+
request_id: event.request_id ?? null,
|
|
355
|
+
});
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return result;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export const exportToHtml = (events: ObserverEvent[], filePath: string) => {
|
|
365
|
+
/**
|
|
366
|
+
* Export events to a self-contained HTML file.
|
|
367
|
+
*/
|
|
368
|
+
const outputDir = path.dirname(filePath);
|
|
369
|
+
ensureDir(outputDir);
|
|
370
|
+
|
|
371
|
+
const moduleUrl = (import.meta as any)?.url
|
|
372
|
+
? (import.meta as any).url
|
|
373
|
+
: pathToFileURL(__filename).href;
|
|
374
|
+
const moduleDir = path.dirname(fileURLToPath(moduleUrl));
|
|
375
|
+
const primaryTemplate = path.join(moduleDir, 'report_template.html');
|
|
376
|
+
const fallbackTemplate = path.resolve(
|
|
377
|
+
moduleDir,
|
|
378
|
+
'..',
|
|
379
|
+
'src',
|
|
380
|
+
'agent',
|
|
381
|
+
'observer',
|
|
382
|
+
'report_template.html',
|
|
383
|
+
);
|
|
384
|
+
const templatePath = fs.existsSync(primaryTemplate)
|
|
385
|
+
? primaryTemplate
|
|
386
|
+
: fallbackTemplate;
|
|
387
|
+
|
|
388
|
+
if (!fs.existsSync(templatePath)) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Report template not found at ${primaryTemplate} or ${fallbackTemplate}`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const template = fs.readFileSync(templatePath, 'utf-8');
|
|
395
|
+
|
|
396
|
+
const eventsData = convertEventsForHtml(events);
|
|
397
|
+
const eventsJson = JSON.stringify(eventsData);
|
|
398
|
+
|
|
399
|
+
const htmlContent = template.replace('{EVENTS_DATA}', eventsJson);
|
|
400
|
+
fs.writeFileSync(filePath, htmlContent, 'utf-8');
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
export const exportToJson = (events: ObserverEvent[], filePath: string) => {
|
|
404
|
+
/**
|
|
405
|
+
* Export events to a JSON file.
|
|
406
|
+
*/
|
|
407
|
+
const outputDir = path.dirname(filePath);
|
|
408
|
+
ensureDir(outputDir);
|
|
409
|
+
|
|
410
|
+
const jsonEvents = events.map(event => {
|
|
411
|
+
const timestamp =
|
|
412
|
+
event.timestamp instanceof Date
|
|
413
|
+
? event.timestamp.toISOString()
|
|
414
|
+
: new Date(event.timestamp).toISOString();
|
|
415
|
+
// Handle ArrayBuffer images before JSON to avoid binary output
|
|
416
|
+
if ('image' in event && event.image instanceof ArrayBuffer) {
|
|
417
|
+
return {
|
|
418
|
+
...event,
|
|
419
|
+
timestamp,
|
|
420
|
+
image: Buffer.from(event.image).toString('base64'),
|
|
421
|
+
image_encoding: 'base64',
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
...event,
|
|
427
|
+
timestamp,
|
|
428
|
+
};
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
fs.writeFileSync(filePath, JSON.stringify(jsonEvents, null, 2), 'utf-8');
|
|
432
|
+
};
|