@oya-ai/browser 1.0.82 → 1.0.85
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 +185 -61
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,81 +26,191 @@ Get an API key at [browser.getoya.ai](https://browser.getoya.ai) or self-host yo
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Quickstart
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Requires Node.js 18+; examples use ES modules. Set `OYA_API_KEY` in your environment. `OYA_BASE_URL` optionally points to a self-hosted control plane.
|
|
32
32
|
|
|
33
|
-
```
|
|
33
|
+
```js
|
|
34
34
|
import { Oya } from "@oya-ai/browser";
|
|
35
35
|
|
|
36
|
-
const oya = new Oya();
|
|
36
|
+
const oya = new Oya();
|
|
37
|
+
const browser = await oya.browser.start({ captcha: "auto" });
|
|
38
|
+
try {
|
|
39
|
+
await browser.goto("https://example.com");
|
|
40
|
+
console.log(await browser.ask("What is the main heading on this page?"));
|
|
41
|
+
} finally {
|
|
42
|
+
await browser.stop();
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
On Node.js 24+, `await using browser = await oya.browser.start()` also stops the browser when its scope exits, including on error.
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
// Stops the browser automatically when the scope exits, even on error
|
|
40
|
-
await using browser = await oya.browser.start({ persona: "auto", captcha: "auto" });
|
|
48
|
+
## Portal automation: record once, replay with new inputs
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
const answer = await browser.ask("What are the top 3 stories and their points?");
|
|
44
|
-
console.log(answer);
|
|
45
|
-
```
|
|
50
|
+
This example adapts the portal-automation project's workflow: reuse a persona, attach to an existing browser or start one, run a prompt the first time, then replay its saved playbook. The portal, workflow names, and request values below are fictional. Supply your own test portal and credentials through environment variables; adapt the task to its actual pages.
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
Save as `portal.mjs` and run `node portal.mjs` after setting `OYA_API_KEY`, `PORTAL_URL`, `PORTAL_USERNAME`, and `PORTAL_PASSWORD`. Set `OYA_BROWSER_ID` only to reuse an already running browser.
|
|
48
53
|
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
```js
|
|
55
|
+
import { createInterface } from "node:readline/promises";
|
|
56
|
+
import { Oya } from "@oya-ai/browser";
|
|
57
|
+
|
|
58
|
+
function requiredEnv(name) {
|
|
59
|
+
const value = process.env[name];
|
|
60
|
+
if (!value) throw new Error(`Set ${name} before running this example.`);
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const oya = new Oya({ apiKey: requiredEnv("OYA_API_KEY") });
|
|
65
|
+
const portalUrl = requiredEnv("PORTAL_URL");
|
|
66
|
+
const playbookName = "portal-request-review";
|
|
67
|
+
const secrets = {
|
|
68
|
+
username: requiredEnv("PORTAL_USERNAME"),
|
|
69
|
+
password: requiredEnv("PORTAL_PASSWORD"),
|
|
70
|
+
};
|
|
71
|
+
// Fictional test inputs. data is visible to the agent.
|
|
72
|
+
const data = {
|
|
73
|
+
customerName: "Alex Example",
|
|
74
|
+
requestId: "DEMO-0001",
|
|
75
|
+
requestedDate: "2030-01-15",
|
|
76
|
+
};
|
|
77
|
+
const task = [
|
|
78
|
+
"If not logged in, log in with {{username}} and {{password}}.",
|
|
79
|
+
"Open New Request and enter {{requestId}} as the reference.",
|
|
80
|
+
"Fill first name {{customerName|first}} and last name {{customerName|last}}.",
|
|
81
|
+
"Set the requested date to {{requestedDate|date:MM/DD/YYYY}}.",
|
|
82
|
+
"If a field is already correct, do not type its value again.",
|
|
83
|
+
"If an action times out, inspect the page before retrying it.",
|
|
84
|
+
"If information is missing, ask the person instead of guessing.",
|
|
85
|
+
"Stop on the review page. Do not submit the request.",
|
|
86
|
+
].join("\n");
|
|
87
|
+
|
|
88
|
+
const existingId = process.env.OYA_BROWSER_ID;
|
|
89
|
+
let browser;
|
|
90
|
+
if (existingId) {
|
|
91
|
+
browser = await oya.browser.get(existingId);
|
|
92
|
+
} else {
|
|
93
|
+
const persona = (await oya.personas.list())
|
|
94
|
+
.find((p) => p.name === "portal-demo")
|
|
95
|
+
?? await oya.personas.create({ name: "portal-demo" });
|
|
96
|
+
browser = await oya.browser.start({ persona: persona.id, captcha: "auto" });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
console.log("Watch in your Oya dashboard:", browser.liveViewUrl());
|
|
101
|
+
await browser.goto(portalUrl);
|
|
102
|
+
const exists = (await oya.playbooks.list()).some((p) => p.name === playbookName);
|
|
103
|
+
const run = await browser.submit(
|
|
104
|
+
exists ? { playbook: playbookName } : { prompt: task },
|
|
105
|
+
{
|
|
106
|
+
// Replay accepts all variables in data; the playbook remembers secret names.
|
|
107
|
+
...(exists ? { data: { ...data, ...secrets } } : { data, secrets }),
|
|
108
|
+
onSuccess: () => console.log("Run succeeded."),
|
|
109
|
+
onFailure: (error) => console.error("Run failed with status:", error.status),
|
|
110
|
+
onHealed: (result) => {
|
|
111
|
+
console.log("A repair draft is ready for review:", result.draft);
|
|
112
|
+
},
|
|
113
|
+
onHumanAttention: async (request) => {
|
|
114
|
+
console.log("Attention needed:", request.reason);
|
|
115
|
+
console.log(request.message);
|
|
116
|
+
console.log("Open:", request.liveViewUrl ?? browser.liveViewUrl());
|
|
117
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
118
|
+
try {
|
|
119
|
+
const answer = await terminal.question(request.reason === "agent"
|
|
120
|
+
? "Answer the agent: "
|
|
121
|
+
: "Handle this in the live view, then press Enter: ");
|
|
122
|
+
await request.respond(answer || "done");
|
|
123
|
+
} finally {
|
|
124
|
+
terminal.close();
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
await run.done; // Rejects on failure; do not save a failed run as a playbook.
|
|
131
|
+
const info = await run.status();
|
|
132
|
+
console.log("Run status:", info.status);
|
|
133
|
+
if (!exists) {
|
|
134
|
+
const playbook = await browser.toPlaybook(playbookName);
|
|
135
|
+
console.log("Saved:", playbook.name, "Steps:", playbook.steps);
|
|
136
|
+
console.log("Variables:", playbook.variables);
|
|
137
|
+
// playbook.code contains the flow as an exported Playwright module.
|
|
138
|
+
}
|
|
139
|
+
} finally {
|
|
140
|
+
// Leave an attached browser open; stop only the browser this script started.
|
|
141
|
+
if (!existingId) await browser.stop();
|
|
142
|
+
}
|
|
54
143
|
```
|
|
55
144
|
|
|
56
|
-
|
|
145
|
+
`submit()` starts a background run and returns a `Run` handle. The SDK polls every two seconds by default (`pollMs` overrides this). `run.done` resolves with the result or rejects with an error; `run.status()` reads the run record. Attention reasons are `captcha`, `mfa`, `agent`, and `heal_failed`. A run waits up to 30 minutes for `request.respond()` or `run.respond()`. Run records are held in server memory for one hour after completion; they do not survive a server restart.
|
|
57
146
|
|
|
58
|
-
###
|
|
147
|
+
### Data, secrets, and reusable placeholders
|
|
59
148
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
149
|
+
Use `{{name}}` references in prompts instead of interpolating values into the prompt text. `data` is available to the agent for reasoning; `secrets` supplies values for typing without including them as readable task inputs. Filters include `first`, `last`, `digits`, and `date:MM/DD/YYYY`.
|
|
150
|
+
|
|
151
|
+
For prompt runs, pass credentials in `secrets`. For replay, pass all variables in `data`: the saved playbook tracks which variables are secret, including during healing. Placeholder-based inputs remain variables in the saved flow. This is not a blanket redaction guarantee for page content, screenshots, agent replies, or application logs; inspect exported code before sharing it.
|
|
152
|
+
|
|
153
|
+
### Review a repaired playbook
|
|
154
|
+
|
|
155
|
+
Replay normally runs recorded steps without an LLM. With `autoHeal: true` (the default), a broken step can hand over to the agent, which saves a repair as `<name>:draft`. Promotion replaces the saved playbook with that draft.
|
|
156
|
+
|
|
157
|
+
The following continues with an active `browser` and the inputs above. Replaying a draft performs its actions, so review its code and use test inputs before promoting it.
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
const saved = (await oya.playbooks.list())
|
|
161
|
+
.find((p) => p.name === playbookName);
|
|
162
|
+
if (saved?.draft) {
|
|
163
|
+
// Review saved.draft.code before executing it.
|
|
164
|
+
await browser.play(`${playbookName}:draft`, { ...data, ...secrets }, { autoHeal: false });
|
|
165
|
+
await oya.playbooks.promote(playbookName);
|
|
166
|
+
}
|
|
70
167
|
```
|
|
71
168
|
|
|
72
|
-
|
|
169
|
+
Use `autoHeal: false` with `play()` or `submit({ playbook: name }, options)` to fail at a broken step without agent repair. `oya.playbooks.remove(name)` removes a playbook and its draft; `remove("<name>:draft")` removes only the draft.
|
|
73
170
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
171
|
+
## Use your own model key
|
|
172
|
+
|
|
173
|
+
As in the portal-automation project's model setup script, configure the model on your Oya API key once for subsequent agent runs:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
import { Oya } from "@oya-ai/browser";
|
|
177
|
+
|
|
178
|
+
const modelKey = process.env.GEMINI_API_KEY;
|
|
179
|
+
if (!modelKey) throw new Error("Set GEMINI_API_KEY first.");
|
|
180
|
+
const oya = new Oya();
|
|
181
|
+
await oya.config.set({
|
|
182
|
+
llm_provider: "gemini", // "openai" | "anthropic" | "gemini"
|
|
183
|
+
openai_api_key: modelKey, // Shared field name for every supported provider.
|
|
184
|
+
// chat_model: process.env.OYA_CHAT_MODEL, // Optional provider model override.
|
|
85
185
|
});
|
|
86
|
-
await run.done;
|
|
87
186
|
```
|
|
88
187
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
188
|
+
Omit `chat_model` to use the configured provider's default. `oya.config.get()` reads configuration. To return to the shared model configuration:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
await oya.config.set({ llm_provider: null, openai_api_key: null, chat_model: null });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Live view, sharing, and embedded streams
|
|
195
|
+
|
|
196
|
+
`browser.liveViewUrl()` returns a dashboard link without an API key; the viewer signs into Oya. For a scoped handoff to someone else, create an expiring share link:
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
const share = await browser.shareUrl({ control: true, expiresInSeconds: 900 });
|
|
200
|
+
// Deliver share.url privately to the intended operator.
|
|
201
|
+
// Once the handoff is finished:
|
|
202
|
+
await browser.revokeShare(share.id);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Share links are view-only by default. `control: true` permits browser interaction. Anyone holding the link has its access until expiry or revocation.
|
|
206
|
+
|
|
207
|
+
For an embedded SSE stream of JPEG frames, use `await browser.liveStreamUrl()`. It mints a single-use connection ticket valid for 60 seconds; request a new URL for each connection.
|
|
98
208
|
|
|
99
209
|
---
|
|
100
210
|
|
|
101
211
|
## 🛡️ Deterministic Personas (Anti-Ban Identity)
|
|
102
212
|
|
|
103
|
-
A persona
|
|
213
|
+
A persona groups a stable device fingerprint, saved login cookies, and a proxy assignment for reuse across browser sessions.
|
|
104
214
|
|
|
105
215
|
```ts
|
|
106
216
|
import { Oya } from "@oya-ai/browser";
|
|
@@ -112,7 +222,7 @@ const persona = await oya.personas.create({
|
|
|
112
222
|
name: "us-shopper",
|
|
113
223
|
prefs: { platform: "MacIntel", timezone: "America/New_York", locale: "en-US" },
|
|
114
224
|
proxy: { geo: "US" },
|
|
115
|
-
maxConcurrent: 2, //
|
|
225
|
+
maxConcurrent: 2, // Limit simultaneous sessions for this identity
|
|
116
226
|
});
|
|
117
227
|
|
|
118
228
|
// Launch a browser with this persona (or persona: 'auto' for least-recently-used)
|
|
@@ -208,7 +318,7 @@ console.log("Page Title:", await page.title());
|
|
|
208
318
|
|
|
209
319
|
---
|
|
210
320
|
|
|
211
|
-
##
|
|
321
|
+
## API reference
|
|
212
322
|
|
|
213
323
|
### Initialization
|
|
214
324
|
|
|
@@ -243,14 +353,18 @@ const oya = new Oya({
|
|
|
243
353
|
- `queueMs?: number` — Wait duration for fleet capacity (ms)
|
|
244
354
|
- `budgetUsd?: number` — Enforce budget limit for session
|
|
245
355
|
- `idempotencyKey?: string` — Safe retry token
|
|
246
|
-
- `governed?: boolean` —
|
|
356
|
+
- `governed?: boolean` — Enable governed session controls
|
|
357
|
+
- `profile?: string` — Saved login profile (takes precedence over `persona`)
|
|
358
|
+
- `priority?: 'low' | 'normal' | 'high'` — Queue priority
|
|
359
|
+
- `policy?: { allowedHosts?, humanHosts?, region?, redactRecording? }` — Session policy
|
|
360
|
+
- `readyTimeoutMs?: number` — Wait budget for a starting browser to connect
|
|
247
361
|
|
|
248
362
|
### Browser Instance Methods (`browser.*`)
|
|
249
363
|
|
|
250
364
|
| Method | Returns | Description |
|
|
251
365
|
|:---|:---|:---|
|
|
252
366
|
| `goto(url)` | `Promise<void>` | Navigate to URL (with optional auto-CAPTCHA) |
|
|
253
|
-
| `ask(prompt)` | `Promise<string>` | Natural-language AI driving using key's configured model |
|
|
367
|
+
| `ask(prompt, { data?, secrets? }?)` | `Promise<string>` | Natural-language AI driving using key's configured model |
|
|
254
368
|
| `analyze()` | `Promise<Analysis>` | Returns markdown representation and numbered elements |
|
|
255
369
|
| `elements()` | `Promise<Element[]>` | Returns only visible interactable elements |
|
|
256
370
|
| `click(elementId)` | `Promise<void>` | Click element by numeric ID from `analyze()` |
|
|
@@ -266,11 +380,19 @@ const oya = new Oya({
|
|
|
266
380
|
| `closeTab(tabId)` | `Promise<void>` | Close target tab |
|
|
267
381
|
| `solveCaptcha()` | `Promise<CaptchaResult>` | Detect and solve on-screen CAPTCHA |
|
|
268
382
|
| `completeMfa()` | `Promise<MfaResult>` | Resolve TOTP/SMS MFA or return `liveViewUrl` |
|
|
269
|
-
| `liveViewUrl()` | `string` |
|
|
383
|
+
| `liveViewUrl()` | `string` | Dashboard link for this browser |
|
|
384
|
+
| `liveStreamUrl()` | `Promise<string>` | SSE frame stream URL with a single-use ticket |
|
|
385
|
+
| `shareUrl(options?)` | `Promise<{ url, id, expiresAt }>` | Expiring browser share link; optional control access |
|
|
386
|
+
| `revokeShare(id)` | `Promise<void>` | Revoke a share link |
|
|
387
|
+
| `submit(task, options?)` | `Promise<Run>` | Background prompt or playbook with callbacks |
|
|
388
|
+
| `toPlaybook(name)` | `Promise<Playbook>` | Save the latest agent flow and export Playwright code |
|
|
389
|
+
| `play(name, data?, { autoHeal? }?)` | `Promise<PlayResult>` | Replay a saved flow |
|
|
270
390
|
| `status()` | `Promise<BrowserDetail>` | Instance metrics, health, and recent activity log |
|
|
271
391
|
| `stop()` | `Promise<StopResult>` | Tear down sandbox and release CDP session |
|
|
272
392
|
|
|
273
|
-
###
|
|
393
|
+
### Profile and persona management (`oya.profiles`, `oya.personas`)
|
|
394
|
+
|
|
395
|
+
`oya.profiles` exposes the same methods as `oya.personas`; the persona name remains available for existing integrations.
|
|
274
396
|
|
|
275
397
|
| Method | Description |
|
|
276
398
|
|:---|:---|
|
|
@@ -312,7 +434,7 @@ await oya.personas.pinProxy(persona.id, proxy.id);
|
|
|
312
434
|
| `session(id)` | Get detailed session execution state |
|
|
313
435
|
| `takeover(id, 'acquire' \| 'release' \| 'resume')` | Manage human control leases |
|
|
314
436
|
| `ticket(id)` | Generate single-use connection ticket for secure handoff |
|
|
315
|
-
| `events(after?)` |
|
|
437
|
+
| `events(after?)` | Read audit events and a pagination cursor |
|
|
316
438
|
| `createCredential(options)` | Mint scoped service credential (`viewer` / `operator` / `administrator`) |
|
|
317
439
|
| `createWebhook(url, types)` | Register HMAC-signed webhook for fleet lifecycle events |
|
|
318
440
|
|
|
@@ -320,7 +442,7 @@ await oya.personas.pinProxy(persona.id, proxy.id);
|
|
|
320
442
|
|
|
321
443
|
## 🚨 Error Handling
|
|
322
444
|
|
|
323
|
-
|
|
445
|
+
API error responses and failed browser commands throw `OyaError`. Network failures, request timeouts, and configuration errors may throw other error types:
|
|
324
446
|
|
|
325
447
|
```ts
|
|
326
448
|
import { Oya, OyaError } from "@oya-ai/browser";
|
|
@@ -331,7 +453,9 @@ try {
|
|
|
331
453
|
} catch (err) {
|
|
332
454
|
if (err instanceof OyaError) {
|
|
333
455
|
console.error(`Oya API Error (${err.status}):`, err.message);
|
|
334
|
-
|
|
456
|
+
// err.body contains response details; inspect privately if needed.
|
|
457
|
+
} else {
|
|
458
|
+
throw err;
|
|
335
459
|
}
|
|
336
460
|
}
|
|
337
461
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -171,7 +171,7 @@ var Browser = class {
|
|
|
171
171
|
await this.command("close_tab", { tab_id: tabId });
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
|
-
|
|
174
|
+
x * Detect and clear a CAPTCHA. Providers that solve natively are left to do
|
|
175
175
|
* it; everything else goes to the configured solver.
|
|
176
176
|
*/
|
|
177
177
|
solveCaptcha() {
|
package/dist/index.d.cts
CHANGED
|
@@ -424,7 +424,7 @@ declare class Browser {
|
|
|
424
424
|
switchTab(tabId: string): Promise<void>;
|
|
425
425
|
closeTab(tabId: string): Promise<void>;
|
|
426
426
|
/**
|
|
427
|
-
|
|
427
|
+
x * Detect and clear a CAPTCHA. Providers that solve natively are left to do
|
|
428
428
|
* it; everything else goes to the configured solver.
|
|
429
429
|
*/
|
|
430
430
|
solveCaptcha(): Promise<CaptchaResult>;
|
package/dist/index.d.ts
CHANGED
|
@@ -424,7 +424,7 @@ declare class Browser {
|
|
|
424
424
|
switchTab(tabId: string): Promise<void>;
|
|
425
425
|
closeTab(tabId: string): Promise<void>;
|
|
426
426
|
/**
|
|
427
|
-
|
|
427
|
+
x * Detect and clear a CAPTCHA. Providers that solve natively are left to do
|
|
428
428
|
* it; everything else goes to the configured solver.
|
|
429
429
|
*/
|
|
430
430
|
solveCaptcha(): Promise<CaptchaResult>;
|
package/dist/index.js
CHANGED
|
@@ -141,7 +141,7 @@ var Browser = class {
|
|
|
141
141
|
await this.command("close_tab", { tab_id: tabId });
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
|
-
|
|
144
|
+
x * Detect and clear a CAPTCHA. Providers that solve natively are left to do
|
|
145
145
|
* it; everything else goes to the configured solver.
|
|
146
146
|
*/
|
|
147
147
|
solveCaptcha() {
|
package/package.json
CHANGED