@oya-ai/browser 1.0.73 → 1.0.75
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 +39 -0
- package/dist/index.cjs +123 -4
- package/dist/index.d.cts +124 -3
- package/dist/index.d.ts +124 -3
- package/dist/index.js +121 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,6 +44,45 @@ const answer = await browser.ask("What are the top 3 stories and their points?")
|
|
|
44
44
|
console.log(answer);
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### Data pass-through: the model never sees your values
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
await browser.ask("Search for order {{orderNumber}} and download its invoice", {
|
|
51
|
+
data: { orderNumber: "1042" }, // typed into the page; the LLM only reads {{orderNumber}}
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Playbooks: ask once, replay without the LLM
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const pb = await browser.toPlaybook("download-invoice");
|
|
59
|
+
console.log(pb.variables); // ["orderNumber"]
|
|
60
|
+
console.log(pb.code); // the same flow as Playwright
|
|
61
|
+
|
|
62
|
+
// Later, on any browser. If the site changed, the agent finishes the task
|
|
63
|
+
// and saves its fix as a draft ("download-invoice:draft").
|
|
64
|
+
const result = await browser.play("download-invoice", { orderNumber: "2077" });
|
|
65
|
+
if (result.healed) await oya.playbooks.promote("download-invoice"); // after reviewing it
|
|
66
|
+
// autoHeal: false throws at the broken step instead.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Submit and get called back
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const run = await browser.submit({ playbook: "download-invoice" }, {
|
|
73
|
+
data: { orderNumber: "2077" },
|
|
74
|
+
onSuccess: (result) => console.log("done", result),
|
|
75
|
+
onFailure: (error) => console.error("failed", error.message),
|
|
76
|
+
// CAPTCHA or MFA it could not clear, the agent asking a question, or a replay it could not heal.
|
|
77
|
+
onHumanAttention: async (req) => {
|
|
78
|
+
console.log(req.reason, req.message, req.liveViewUrl);
|
|
79
|
+
await req.respond("done"); // or your answer, when req.reason === "agent"
|
|
80
|
+
},
|
|
81
|
+
onHealed: (result) => console.log("fix saved as", result.draft),
|
|
82
|
+
});
|
|
83
|
+
await run.done;
|
|
84
|
+
```
|
|
85
|
+
|
|
47
86
|
> **Universal Lifecycle:** If you are not using `await using`, manage lifecycle with `try / finally`:
|
|
48
87
|
> ```ts
|
|
49
88
|
> const browser = await oya.browser.start();
|
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
Browser: () => Browser,
|
|
24
24
|
Oya: () => Oya,
|
|
25
25
|
OyaError: () => OyaError,
|
|
26
|
+
Run: () => Run,
|
|
26
27
|
default: () => index_default
|
|
27
28
|
});
|
|
28
29
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -185,16 +186,57 @@ var Browser = class {
|
|
|
185
186
|
if (result.liveViewUrl) result.liveViewUrl = new URL(result.liveViewUrl, this.http.baseUrl).href;
|
|
186
187
|
return result;
|
|
187
188
|
}
|
|
188
|
-
/**
|
|
189
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Natural-language control, using this key's configured model. Refer to `data`
|
|
191
|
+
* as `{{name}}` in the prompt: the agent types the placeholder, the page gets the
|
|
192
|
+
* value, and the model never sees it.
|
|
193
|
+
*/
|
|
194
|
+
async ask(prompt, { data } = {}) {
|
|
190
195
|
const res = await this.http.request(
|
|
191
196
|
"POST",
|
|
192
197
|
`/api/browsers/${this.id}/chat`,
|
|
193
|
-
{ messages: [{ role: "user", content: prompt }] },
|
|
198
|
+
{ messages: [{ role: "user", content: prompt }], data },
|
|
194
199
|
6e5
|
|
195
200
|
);
|
|
201
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
196
202
|
return res.text;
|
|
197
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Save the last `ask()` on this browser as a named playbook. Values that came
|
|
206
|
+
* from the prompt (names, IDs, dates) become variables; `code` is the same
|
|
207
|
+
* flow as a Playwright module, to read or run yourself.
|
|
208
|
+
*/
|
|
209
|
+
toPlaybook(name) {
|
|
210
|
+
return this.http.request("POST", `/api/browsers/${this.id}/playbooks`, { name }, 12e4);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Replay a playbook with no LLM in the loop. Variables left out reuse the
|
|
214
|
+
* recorded values where there are any. If a step no longer fits the page and
|
|
215
|
+
* `autoHeal` is on (the default), the agent finishes the task and its fix is
|
|
216
|
+
* saved as a draft (`healed`, `draft`); off, the step's error is thrown.
|
|
217
|
+
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
218
|
+
*/
|
|
219
|
+
async play(name, data = {}, { autoHeal = true } = {}) {
|
|
220
|
+
const res = await this.http.request(
|
|
221
|
+
"POST",
|
|
222
|
+
`/api/browsers/${this.id}/playbooks/${encodeURIComponent(name)}/play`,
|
|
223
|
+
{ variables: data, autoHeal },
|
|
224
|
+
6e5
|
|
225
|
+
);
|
|
226
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
227
|
+
return res;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Start a prompt or playbook in the background and hear back through callbacks.
|
|
231
|
+
* `onHumanAttention` fires for an unsolved CAPTCHA, an unfinished MFA, the agent
|
|
232
|
+
* asking for help, or a replay the agent could not heal; the run waits (up to 30
|
|
233
|
+
* minutes) until you call `respond()`.
|
|
234
|
+
*/
|
|
235
|
+
async submit(task, options = {}) {
|
|
236
|
+
const { onSuccess, onFailure, onHumanAttention, onHealed, pollMs, ...body } = options;
|
|
237
|
+
const started = await this.http.request("POST", `/api/browsers/${this.id}/runs`, { ...task, ...body });
|
|
238
|
+
return new Run(this.http, started.id, { onSuccess, onFailure, onHumanAttention, onHealed }, pollMs);
|
|
239
|
+
}
|
|
198
240
|
/**
|
|
199
241
|
* Watch it work: the console, opened on this browser.
|
|
200
242
|
*
|
|
@@ -266,6 +308,72 @@ var Browser = class {
|
|
|
266
308
|
await this.stop();
|
|
267
309
|
}
|
|
268
310
|
};
|
|
311
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
312
|
+
var Run = class {
|
|
313
|
+
constructor(http, id, callbacks, pollMs = 2e3) {
|
|
314
|
+
this.http = http;
|
|
315
|
+
this.id = id;
|
|
316
|
+
this.done = this.watch(callbacks, pollMs);
|
|
317
|
+
this.done.catch(() => {
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
http;
|
|
321
|
+
id;
|
|
322
|
+
done;
|
|
323
|
+
status() {
|
|
324
|
+
return this.http.request("GET", `/api/runs/${encodeURIComponent(this.id)}`);
|
|
325
|
+
}
|
|
326
|
+
/** Answer the open attention request: `'done'` after handling it by hand, or your reply to the agent. */
|
|
327
|
+
async respond(response = "done") {
|
|
328
|
+
await this.http.request("POST", `/api/runs/${encodeURIComponent(this.id)}/respond`, { response });
|
|
329
|
+
}
|
|
330
|
+
async watch(cb, pollMs) {
|
|
331
|
+
const call = async (fn) => {
|
|
332
|
+
try {
|
|
333
|
+
await fn();
|
|
334
|
+
} catch (err) {
|
|
335
|
+
console.error("[oya] run callback threw:", err);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
let seen;
|
|
339
|
+
for (let errors = 0; ; ) {
|
|
340
|
+
let run;
|
|
341
|
+
try {
|
|
342
|
+
run = await this.status();
|
|
343
|
+
errors = 0;
|
|
344
|
+
} catch (err) {
|
|
345
|
+
if (++errors < 5) {
|
|
346
|
+
await sleep(pollMs);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const failure = err instanceof OyaError ? err : new OyaError(String(err), 0, null);
|
|
350
|
+
await call(() => cb.onFailure?.(failure));
|
|
351
|
+
throw failure;
|
|
352
|
+
}
|
|
353
|
+
if (run.status === "needs_attention" && run.attention && run.attention.id !== seen) {
|
|
354
|
+
seen = run.attention.id;
|
|
355
|
+
const request = {
|
|
356
|
+
...run.attention,
|
|
357
|
+
liveViewUrl: run.attention.liveViewUrl && new URL(run.attention.liveViewUrl, this.http.baseUrl).href,
|
|
358
|
+
respond: (response) => this.respond(response)
|
|
359
|
+
};
|
|
360
|
+
void call(() => cb.onHumanAttention?.(request));
|
|
361
|
+
}
|
|
362
|
+
if (run.status === "succeeded") {
|
|
363
|
+
const result = run.result || {};
|
|
364
|
+
if (result.healed) await call(() => cb.onHealed?.(result));
|
|
365
|
+
await call(() => cb.onSuccess?.(result));
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
if (run.status === "failed") {
|
|
369
|
+
const failure = new OyaError(run.error || "Run failed", 500, run);
|
|
370
|
+
await call(() => cb.onFailure?.(failure));
|
|
371
|
+
throw failure;
|
|
372
|
+
}
|
|
373
|
+
await sleep(pollMs);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
};
|
|
269
377
|
|
|
270
378
|
// src/index.ts
|
|
271
379
|
var DEFAULT_BASE_URL = "https://browser.getoya.ai";
|
|
@@ -342,6 +450,16 @@ var Oya = class {
|
|
|
342
450
|
removeWebhook: (id) => this.http.request("DELETE", `/api/control/webhooks/${encodeURIComponent(id)}`),
|
|
343
451
|
replayDelivery: (id) => this.http.request("POST", `/api/control/deliveries/${encodeURIComponent(id)}/replay`, {})
|
|
344
452
|
};
|
|
453
|
+
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
454
|
+
playbooks = {
|
|
455
|
+
list: async () => (await this.http.request("GET", "/api/playbooks")).playbooks,
|
|
456
|
+
/** Delete a playbook and its draft, or only the draft with `'<name>:draft'`. */
|
|
457
|
+
remove: async (name) => {
|
|
458
|
+
await this.http.request("DELETE", `/api/playbooks/${encodeURIComponent(name)}`);
|
|
459
|
+
},
|
|
460
|
+
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
461
|
+
promote: (name) => this.http.request("POST", `/api/playbooks/${encodeURIComponent(name)}/promote`, {})
|
|
462
|
+
};
|
|
345
463
|
/**
|
|
346
464
|
* Proxy exits for your personas. A persona takes one at first connect (by its
|
|
347
465
|
* geo hint) or by `personas.pinProxy`, and keeps it.
|
|
@@ -413,5 +531,6 @@ var index_default = Oya;
|
|
|
413
531
|
0 && (module.exports = {
|
|
414
532
|
Browser,
|
|
415
533
|
Oya,
|
|
416
|
-
OyaError
|
|
534
|
+
OyaError,
|
|
535
|
+
Run
|
|
417
536
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -54,6 +54,74 @@ interface StartResult {
|
|
|
54
54
|
cdpUrl?: string;
|
|
55
55
|
note?: string;
|
|
56
56
|
}
|
|
57
|
+
interface Playbook {
|
|
58
|
+
name: string;
|
|
59
|
+
/** Inputs `play()` accepts; any left out reuse the recorded value. */
|
|
60
|
+
variables: string[];
|
|
61
|
+
steps: number;
|
|
62
|
+
/** The same flow as a Playwright module: `export default async function run(page, vars)`. */
|
|
63
|
+
code: string;
|
|
64
|
+
}
|
|
65
|
+
interface PlayResult {
|
|
66
|
+
/** Steps replayed before finishing or handing over to the agent. */
|
|
67
|
+
steps: number;
|
|
68
|
+
total: number;
|
|
69
|
+
/** A step no longer fit the page and the agent finished the task. */
|
|
70
|
+
fellBack: boolean;
|
|
71
|
+
/** The agent's fix was saved as `draft`; promote it with `oya.playbooks.promote(name)`. */
|
|
72
|
+
healed?: boolean;
|
|
73
|
+
draft?: string;
|
|
74
|
+
/** The agent's summary, when it fell back. */
|
|
75
|
+
text?: string;
|
|
76
|
+
}
|
|
77
|
+
interface PlaybookSummary extends Playbook {
|
|
78
|
+
createdAt: string | null;
|
|
79
|
+
promotedAt: string | null;
|
|
80
|
+
/** A healed replay's fix, waiting for `promote()` or `remove('<name>:draft')`. */
|
|
81
|
+
draft: (Playbook & {
|
|
82
|
+
healedAt: string;
|
|
83
|
+
healedFrom: number;
|
|
84
|
+
}) | null;
|
|
85
|
+
}
|
|
86
|
+
/** Values passed through to the page as `{{name}}` placeholders; the model never sees them. */
|
|
87
|
+
type RunData = Record<string, string | number>;
|
|
88
|
+
interface AttentionRequest {
|
|
89
|
+
id: string;
|
|
90
|
+
/** captcha / mfa: finish it in the live view. agent: the agent's question. heal_failed: replay and the agent both gave up. */
|
|
91
|
+
reason: 'captcha' | 'mfa' | 'agent' | 'heal_failed';
|
|
92
|
+
message: string;
|
|
93
|
+
liveViewUrl?: string;
|
|
94
|
+
at: number;
|
|
95
|
+
}
|
|
96
|
+
type RunResult = Partial<PlayResult> & {
|
|
97
|
+
text?: string;
|
|
98
|
+
};
|
|
99
|
+
interface RunInfo {
|
|
100
|
+
id: string;
|
|
101
|
+
browserId: string;
|
|
102
|
+
status: 'running' | 'needs_attention' | 'succeeded' | 'failed';
|
|
103
|
+
createdAt: number;
|
|
104
|
+
endedAt?: number;
|
|
105
|
+
attention: AttentionRequest | null;
|
|
106
|
+
result?: RunResult;
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
interface SubmitOptions {
|
|
110
|
+
/** Passed through as `{{name}}` placeholders; for a playbook, its variables. */
|
|
111
|
+
data?: RunData;
|
|
112
|
+
/** Playbooks only: let the agent finish a broken replay and save its fix as a draft. Default true. */
|
|
113
|
+
autoHeal?: boolean;
|
|
114
|
+
onSuccess?: (result: RunResult) => unknown;
|
|
115
|
+
onFailure?: (error: OyaError) => unknown;
|
|
116
|
+
/** Call `respond()` once it is handled: `'done'` after finishing by hand, or your answer to the agent. */
|
|
117
|
+
onHumanAttention?: (request: AttentionRequest & {
|
|
118
|
+
respond(response?: string): Promise<void>;
|
|
119
|
+
}) => unknown;
|
|
120
|
+
/** Fires before onSuccess when a replay was healed; `result.draft` names the draft. */
|
|
121
|
+
onHealed?: (result: RunResult) => unknown;
|
|
122
|
+
/** How often to check on the run. Default 2000. */
|
|
123
|
+
pollMs?: number;
|
|
124
|
+
}
|
|
57
125
|
interface Element {
|
|
58
126
|
id: number;
|
|
59
127
|
type: string;
|
|
@@ -357,8 +425,41 @@ declare class Browser {
|
|
|
357
425
|
* answer it, `liveViewUrl` is where a person finishes by hand.
|
|
358
426
|
*/
|
|
359
427
|
completeMfa(): Promise<MfaResult>;
|
|
360
|
-
/**
|
|
361
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Natural-language control, using this key's configured model. Refer to `data`
|
|
430
|
+
* as `{{name}}` in the prompt: the agent types the placeholder, the page gets the
|
|
431
|
+
* value, and the model never sees it.
|
|
432
|
+
*/
|
|
433
|
+
ask(prompt: string, { data }?: {
|
|
434
|
+
data?: RunData;
|
|
435
|
+
}): Promise<string>;
|
|
436
|
+
/**
|
|
437
|
+
* Save the last `ask()` on this browser as a named playbook. Values that came
|
|
438
|
+
* from the prompt (names, IDs, dates) become variables; `code` is the same
|
|
439
|
+
* flow as a Playwright module, to read or run yourself.
|
|
440
|
+
*/
|
|
441
|
+
toPlaybook(name: string): Promise<Playbook>;
|
|
442
|
+
/**
|
|
443
|
+
* Replay a playbook with no LLM in the loop. Variables left out reuse the
|
|
444
|
+
* recorded values where there are any. If a step no longer fits the page and
|
|
445
|
+
* `autoHeal` is on (the default), the agent finishes the task and its fix is
|
|
446
|
+
* saved as a draft (`healed`, `draft`); off, the step's error is thrown.
|
|
447
|
+
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
448
|
+
*/
|
|
449
|
+
play(name: string, data?: RunData, { autoHeal }?: {
|
|
450
|
+
autoHeal?: boolean;
|
|
451
|
+
}): Promise<PlayResult>;
|
|
452
|
+
/**
|
|
453
|
+
* Start a prompt or playbook in the background and hear back through callbacks.
|
|
454
|
+
* `onHumanAttention` fires for an unsolved CAPTCHA, an unfinished MFA, the agent
|
|
455
|
+
* asking for help, or a replay the agent could not heal; the run waits (up to 30
|
|
456
|
+
* minutes) until you call `respond()`.
|
|
457
|
+
*/
|
|
458
|
+
submit(task: {
|
|
459
|
+
prompt: string;
|
|
460
|
+
} | {
|
|
461
|
+
playbook: string;
|
|
462
|
+
}, options?: SubmitOptions): Promise<Run>;
|
|
362
463
|
/**
|
|
363
464
|
* Watch it work: the console, opened on this browser.
|
|
364
465
|
*
|
|
@@ -411,6 +512,18 @@ declare class Browser {
|
|
|
411
512
|
/** @deprecated use stop() — close() only dropped the socket, and a cloud browser redialled. */
|
|
412
513
|
close(): Promise<void>;
|
|
413
514
|
}
|
|
515
|
+
type RunCallbacks = Pick<SubmitOptions, 'onSuccess' | 'onFailure' | 'onHumanAttention' | 'onHealed'>;
|
|
516
|
+
/** A submitted task. Callbacks fire as it changes; `done` settles when it ends. */
|
|
517
|
+
declare class Run {
|
|
518
|
+
private readonly http;
|
|
519
|
+
readonly id: string;
|
|
520
|
+
readonly done: Promise<RunResult>;
|
|
521
|
+
constructor(http: Http, id: string, callbacks: RunCallbacks, pollMs?: number);
|
|
522
|
+
status(): Promise<RunInfo>;
|
|
523
|
+
/** Answer the open attention request: `'done'` after handling it by hand, or your reply to the agent. */
|
|
524
|
+
respond(response?: string): Promise<void>;
|
|
525
|
+
private watch;
|
|
526
|
+
}
|
|
414
527
|
|
|
415
528
|
/**
|
|
416
529
|
* @oya-ai/browser — thousands of browsers, one API.
|
|
@@ -496,6 +609,14 @@ declare class Oya {
|
|
|
496
609
|
ok: boolean;
|
|
497
610
|
}>;
|
|
498
611
|
};
|
|
612
|
+
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
613
|
+
readonly playbooks: {
|
|
614
|
+
list: () => Promise<PlaybookSummary[]>;
|
|
615
|
+
/** Delete a playbook and its draft, or only the draft with `'<name>:draft'`. */
|
|
616
|
+
remove: (name: string) => Promise<void>;
|
|
617
|
+
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
618
|
+
promote: (name: string) => Promise<Playbook>;
|
|
619
|
+
};
|
|
499
620
|
/**
|
|
500
621
|
* Proxy exits for your personas. A persona takes one at first connect (by its
|
|
501
622
|
* geo hint) or by `personas.pinProxy`, and keeps it.
|
|
@@ -624,4 +745,4 @@ declare class Oya {
|
|
|
624
745
|
private waitUntilConnected;
|
|
625
746
|
}
|
|
626
747
|
|
|
627
|
-
export { type Activity, type Analysis, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type StartOptions, type StartResult, type StopResult, Oya as default };
|
|
748
|
+
export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,74 @@ interface StartResult {
|
|
|
54
54
|
cdpUrl?: string;
|
|
55
55
|
note?: string;
|
|
56
56
|
}
|
|
57
|
+
interface Playbook {
|
|
58
|
+
name: string;
|
|
59
|
+
/** Inputs `play()` accepts; any left out reuse the recorded value. */
|
|
60
|
+
variables: string[];
|
|
61
|
+
steps: number;
|
|
62
|
+
/** The same flow as a Playwright module: `export default async function run(page, vars)`. */
|
|
63
|
+
code: string;
|
|
64
|
+
}
|
|
65
|
+
interface PlayResult {
|
|
66
|
+
/** Steps replayed before finishing or handing over to the agent. */
|
|
67
|
+
steps: number;
|
|
68
|
+
total: number;
|
|
69
|
+
/** A step no longer fit the page and the agent finished the task. */
|
|
70
|
+
fellBack: boolean;
|
|
71
|
+
/** The agent's fix was saved as `draft`; promote it with `oya.playbooks.promote(name)`. */
|
|
72
|
+
healed?: boolean;
|
|
73
|
+
draft?: string;
|
|
74
|
+
/** The agent's summary, when it fell back. */
|
|
75
|
+
text?: string;
|
|
76
|
+
}
|
|
77
|
+
interface PlaybookSummary extends Playbook {
|
|
78
|
+
createdAt: string | null;
|
|
79
|
+
promotedAt: string | null;
|
|
80
|
+
/** A healed replay's fix, waiting for `promote()` or `remove('<name>:draft')`. */
|
|
81
|
+
draft: (Playbook & {
|
|
82
|
+
healedAt: string;
|
|
83
|
+
healedFrom: number;
|
|
84
|
+
}) | null;
|
|
85
|
+
}
|
|
86
|
+
/** Values passed through to the page as `{{name}}` placeholders; the model never sees them. */
|
|
87
|
+
type RunData = Record<string, string | number>;
|
|
88
|
+
interface AttentionRequest {
|
|
89
|
+
id: string;
|
|
90
|
+
/** captcha / mfa: finish it in the live view. agent: the agent's question. heal_failed: replay and the agent both gave up. */
|
|
91
|
+
reason: 'captcha' | 'mfa' | 'agent' | 'heal_failed';
|
|
92
|
+
message: string;
|
|
93
|
+
liveViewUrl?: string;
|
|
94
|
+
at: number;
|
|
95
|
+
}
|
|
96
|
+
type RunResult = Partial<PlayResult> & {
|
|
97
|
+
text?: string;
|
|
98
|
+
};
|
|
99
|
+
interface RunInfo {
|
|
100
|
+
id: string;
|
|
101
|
+
browserId: string;
|
|
102
|
+
status: 'running' | 'needs_attention' | 'succeeded' | 'failed';
|
|
103
|
+
createdAt: number;
|
|
104
|
+
endedAt?: number;
|
|
105
|
+
attention: AttentionRequest | null;
|
|
106
|
+
result?: RunResult;
|
|
107
|
+
error?: string;
|
|
108
|
+
}
|
|
109
|
+
interface SubmitOptions {
|
|
110
|
+
/** Passed through as `{{name}}` placeholders; for a playbook, its variables. */
|
|
111
|
+
data?: RunData;
|
|
112
|
+
/** Playbooks only: let the agent finish a broken replay and save its fix as a draft. Default true. */
|
|
113
|
+
autoHeal?: boolean;
|
|
114
|
+
onSuccess?: (result: RunResult) => unknown;
|
|
115
|
+
onFailure?: (error: OyaError) => unknown;
|
|
116
|
+
/** Call `respond()` once it is handled: `'done'` after finishing by hand, or your answer to the agent. */
|
|
117
|
+
onHumanAttention?: (request: AttentionRequest & {
|
|
118
|
+
respond(response?: string): Promise<void>;
|
|
119
|
+
}) => unknown;
|
|
120
|
+
/** Fires before onSuccess when a replay was healed; `result.draft` names the draft. */
|
|
121
|
+
onHealed?: (result: RunResult) => unknown;
|
|
122
|
+
/** How often to check on the run. Default 2000. */
|
|
123
|
+
pollMs?: number;
|
|
124
|
+
}
|
|
57
125
|
interface Element {
|
|
58
126
|
id: number;
|
|
59
127
|
type: string;
|
|
@@ -357,8 +425,41 @@ declare class Browser {
|
|
|
357
425
|
* answer it, `liveViewUrl` is where a person finishes by hand.
|
|
358
426
|
*/
|
|
359
427
|
completeMfa(): Promise<MfaResult>;
|
|
360
|
-
/**
|
|
361
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Natural-language control, using this key's configured model. Refer to `data`
|
|
430
|
+
* as `{{name}}` in the prompt: the agent types the placeholder, the page gets the
|
|
431
|
+
* value, and the model never sees it.
|
|
432
|
+
*/
|
|
433
|
+
ask(prompt: string, { data }?: {
|
|
434
|
+
data?: RunData;
|
|
435
|
+
}): Promise<string>;
|
|
436
|
+
/**
|
|
437
|
+
* Save the last `ask()` on this browser as a named playbook. Values that came
|
|
438
|
+
* from the prompt (names, IDs, dates) become variables; `code` is the same
|
|
439
|
+
* flow as a Playwright module, to read or run yourself.
|
|
440
|
+
*/
|
|
441
|
+
toPlaybook(name: string): Promise<Playbook>;
|
|
442
|
+
/**
|
|
443
|
+
* Replay a playbook with no LLM in the loop. Variables left out reuse the
|
|
444
|
+
* recorded values where there are any. If a step no longer fits the page and
|
|
445
|
+
* `autoHeal` is on (the default), the agent finishes the task and its fix is
|
|
446
|
+
* saved as a draft (`healed`, `draft`); off, the step's error is thrown.
|
|
447
|
+
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
448
|
+
*/
|
|
449
|
+
play(name: string, data?: RunData, { autoHeal }?: {
|
|
450
|
+
autoHeal?: boolean;
|
|
451
|
+
}): Promise<PlayResult>;
|
|
452
|
+
/**
|
|
453
|
+
* Start a prompt or playbook in the background and hear back through callbacks.
|
|
454
|
+
* `onHumanAttention` fires for an unsolved CAPTCHA, an unfinished MFA, the agent
|
|
455
|
+
* asking for help, or a replay the agent could not heal; the run waits (up to 30
|
|
456
|
+
* minutes) until you call `respond()`.
|
|
457
|
+
*/
|
|
458
|
+
submit(task: {
|
|
459
|
+
prompt: string;
|
|
460
|
+
} | {
|
|
461
|
+
playbook: string;
|
|
462
|
+
}, options?: SubmitOptions): Promise<Run>;
|
|
362
463
|
/**
|
|
363
464
|
* Watch it work: the console, opened on this browser.
|
|
364
465
|
*
|
|
@@ -411,6 +512,18 @@ declare class Browser {
|
|
|
411
512
|
/** @deprecated use stop() — close() only dropped the socket, and a cloud browser redialled. */
|
|
412
513
|
close(): Promise<void>;
|
|
413
514
|
}
|
|
515
|
+
type RunCallbacks = Pick<SubmitOptions, 'onSuccess' | 'onFailure' | 'onHumanAttention' | 'onHealed'>;
|
|
516
|
+
/** A submitted task. Callbacks fire as it changes; `done` settles when it ends. */
|
|
517
|
+
declare class Run {
|
|
518
|
+
private readonly http;
|
|
519
|
+
readonly id: string;
|
|
520
|
+
readonly done: Promise<RunResult>;
|
|
521
|
+
constructor(http: Http, id: string, callbacks: RunCallbacks, pollMs?: number);
|
|
522
|
+
status(): Promise<RunInfo>;
|
|
523
|
+
/** Answer the open attention request: `'done'` after handling it by hand, or your reply to the agent. */
|
|
524
|
+
respond(response?: string): Promise<void>;
|
|
525
|
+
private watch;
|
|
526
|
+
}
|
|
414
527
|
|
|
415
528
|
/**
|
|
416
529
|
* @oya-ai/browser — thousands of browsers, one API.
|
|
@@ -496,6 +609,14 @@ declare class Oya {
|
|
|
496
609
|
ok: boolean;
|
|
497
610
|
}>;
|
|
498
611
|
};
|
|
612
|
+
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
613
|
+
readonly playbooks: {
|
|
614
|
+
list: () => Promise<PlaybookSummary[]>;
|
|
615
|
+
/** Delete a playbook and its draft, or only the draft with `'<name>:draft'`. */
|
|
616
|
+
remove: (name: string) => Promise<void>;
|
|
617
|
+
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
618
|
+
promote: (name: string) => Promise<Playbook>;
|
|
619
|
+
};
|
|
499
620
|
/**
|
|
500
621
|
* Proxy exits for your personas. A persona takes one at first connect (by its
|
|
501
622
|
* geo hint) or by `personas.pinProxy`, and keeps it.
|
|
@@ -624,4 +745,4 @@ declare class Oya {
|
|
|
624
745
|
private waitUntilConnected;
|
|
625
746
|
}
|
|
626
747
|
|
|
627
|
-
export { type Activity, type Analysis, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, type StartOptions, type StartResult, type StopResult, Oya as default };
|
|
748
|
+
export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default };
|
package/dist/index.js
CHANGED
|
@@ -156,16 +156,57 @@ var Browser = class {
|
|
|
156
156
|
if (result.liveViewUrl) result.liveViewUrl = new URL(result.liveViewUrl, this.http.baseUrl).href;
|
|
157
157
|
return result;
|
|
158
158
|
}
|
|
159
|
-
/**
|
|
160
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Natural-language control, using this key's configured model. Refer to `data`
|
|
161
|
+
* as `{{name}}` in the prompt: the agent types the placeholder, the page gets the
|
|
162
|
+
* value, and the model never sees it.
|
|
163
|
+
*/
|
|
164
|
+
async ask(prompt, { data } = {}) {
|
|
161
165
|
const res = await this.http.request(
|
|
162
166
|
"POST",
|
|
163
167
|
`/api/browsers/${this.id}/chat`,
|
|
164
|
-
{ messages: [{ role: "user", content: prompt }] },
|
|
168
|
+
{ messages: [{ role: "user", content: prompt }], data },
|
|
165
169
|
6e5
|
|
166
170
|
);
|
|
171
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
167
172
|
return res.text;
|
|
168
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Save the last `ask()` on this browser as a named playbook. Values that came
|
|
176
|
+
* from the prompt (names, IDs, dates) become variables; `code` is the same
|
|
177
|
+
* flow as a Playwright module, to read or run yourself.
|
|
178
|
+
*/
|
|
179
|
+
toPlaybook(name) {
|
|
180
|
+
return this.http.request("POST", `/api/browsers/${this.id}/playbooks`, { name }, 12e4);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Replay a playbook with no LLM in the loop. Variables left out reuse the
|
|
184
|
+
* recorded values where there are any. If a step no longer fits the page and
|
|
185
|
+
* `autoHeal` is on (the default), the agent finishes the task and its fix is
|
|
186
|
+
* saved as a draft (`healed`, `draft`); off, the step's error is thrown.
|
|
187
|
+
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
188
|
+
*/
|
|
189
|
+
async play(name, data = {}, { autoHeal = true } = {}) {
|
|
190
|
+
const res = await this.http.request(
|
|
191
|
+
"POST",
|
|
192
|
+
`/api/browsers/${this.id}/playbooks/${encodeURIComponent(name)}/play`,
|
|
193
|
+
{ variables: data, autoHeal },
|
|
194
|
+
6e5
|
|
195
|
+
);
|
|
196
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
197
|
+
return res;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Start a prompt or playbook in the background and hear back through callbacks.
|
|
201
|
+
* `onHumanAttention` fires for an unsolved CAPTCHA, an unfinished MFA, the agent
|
|
202
|
+
* asking for help, or a replay the agent could not heal; the run waits (up to 30
|
|
203
|
+
* minutes) until you call `respond()`.
|
|
204
|
+
*/
|
|
205
|
+
async submit(task, options = {}) {
|
|
206
|
+
const { onSuccess, onFailure, onHumanAttention, onHealed, pollMs, ...body } = options;
|
|
207
|
+
const started = await this.http.request("POST", `/api/browsers/${this.id}/runs`, { ...task, ...body });
|
|
208
|
+
return new Run(this.http, started.id, { onSuccess, onFailure, onHumanAttention, onHealed }, pollMs);
|
|
209
|
+
}
|
|
169
210
|
/**
|
|
170
211
|
* Watch it work: the console, opened on this browser.
|
|
171
212
|
*
|
|
@@ -237,6 +278,72 @@ var Browser = class {
|
|
|
237
278
|
await this.stop();
|
|
238
279
|
}
|
|
239
280
|
};
|
|
281
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
282
|
+
var Run = class {
|
|
283
|
+
constructor(http, id, callbacks, pollMs = 2e3) {
|
|
284
|
+
this.http = http;
|
|
285
|
+
this.id = id;
|
|
286
|
+
this.done = this.watch(callbacks, pollMs);
|
|
287
|
+
this.done.catch(() => {
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
http;
|
|
291
|
+
id;
|
|
292
|
+
done;
|
|
293
|
+
status() {
|
|
294
|
+
return this.http.request("GET", `/api/runs/${encodeURIComponent(this.id)}`);
|
|
295
|
+
}
|
|
296
|
+
/** Answer the open attention request: `'done'` after handling it by hand, or your reply to the agent. */
|
|
297
|
+
async respond(response = "done") {
|
|
298
|
+
await this.http.request("POST", `/api/runs/${encodeURIComponent(this.id)}/respond`, { response });
|
|
299
|
+
}
|
|
300
|
+
async watch(cb, pollMs) {
|
|
301
|
+
const call = async (fn) => {
|
|
302
|
+
try {
|
|
303
|
+
await fn();
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error("[oya] run callback threw:", err);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
let seen;
|
|
309
|
+
for (let errors = 0; ; ) {
|
|
310
|
+
let run;
|
|
311
|
+
try {
|
|
312
|
+
run = await this.status();
|
|
313
|
+
errors = 0;
|
|
314
|
+
} catch (err) {
|
|
315
|
+
if (++errors < 5) {
|
|
316
|
+
await sleep(pollMs);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const failure = err instanceof OyaError ? err : new OyaError(String(err), 0, null);
|
|
320
|
+
await call(() => cb.onFailure?.(failure));
|
|
321
|
+
throw failure;
|
|
322
|
+
}
|
|
323
|
+
if (run.status === "needs_attention" && run.attention && run.attention.id !== seen) {
|
|
324
|
+
seen = run.attention.id;
|
|
325
|
+
const request = {
|
|
326
|
+
...run.attention,
|
|
327
|
+
liveViewUrl: run.attention.liveViewUrl && new URL(run.attention.liveViewUrl, this.http.baseUrl).href,
|
|
328
|
+
respond: (response) => this.respond(response)
|
|
329
|
+
};
|
|
330
|
+
void call(() => cb.onHumanAttention?.(request));
|
|
331
|
+
}
|
|
332
|
+
if (run.status === "succeeded") {
|
|
333
|
+
const result = run.result || {};
|
|
334
|
+
if (result.healed) await call(() => cb.onHealed?.(result));
|
|
335
|
+
await call(() => cb.onSuccess?.(result));
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
if (run.status === "failed") {
|
|
339
|
+
const failure = new OyaError(run.error || "Run failed", 500, run);
|
|
340
|
+
await call(() => cb.onFailure?.(failure));
|
|
341
|
+
throw failure;
|
|
342
|
+
}
|
|
343
|
+
await sleep(pollMs);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
240
347
|
|
|
241
348
|
// src/index.ts
|
|
242
349
|
var DEFAULT_BASE_URL = "https://browser.getoya.ai";
|
|
@@ -313,6 +420,16 @@ var Oya = class {
|
|
|
313
420
|
removeWebhook: (id) => this.http.request("DELETE", `/api/control/webhooks/${encodeURIComponent(id)}`),
|
|
314
421
|
replayDelivery: (id) => this.http.request("POST", `/api/control/deliveries/${encodeURIComponent(id)}/replay`, {})
|
|
315
422
|
};
|
|
423
|
+
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
424
|
+
playbooks = {
|
|
425
|
+
list: async () => (await this.http.request("GET", "/api/playbooks")).playbooks,
|
|
426
|
+
/** Delete a playbook and its draft, or only the draft with `'<name>:draft'`. */
|
|
427
|
+
remove: async (name) => {
|
|
428
|
+
await this.http.request("DELETE", `/api/playbooks/${encodeURIComponent(name)}`);
|
|
429
|
+
},
|
|
430
|
+
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
431
|
+
promote: (name) => this.http.request("POST", `/api/playbooks/${encodeURIComponent(name)}/promote`, {})
|
|
432
|
+
};
|
|
316
433
|
/**
|
|
317
434
|
* Proxy exits for your personas. A persona takes one at first connect (by its
|
|
318
435
|
* geo hint) or by `personas.pinProxy`, and keeps it.
|
|
@@ -384,5 +501,6 @@ export {
|
|
|
384
501
|
Browser,
|
|
385
502
|
Oya,
|
|
386
503
|
OyaError,
|
|
504
|
+
Run,
|
|
387
505
|
index_default as default
|
|
388
506
|
};
|
package/package.json
CHANGED