@oya-ai/cli 1.0.74 → 1.0.76
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/index.js +120 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -157,16 +157,57 @@ var Browser = class {
|
|
|
157
157
|
if (result.liveViewUrl) result.liveViewUrl = new URL(result.liveViewUrl, this.http.baseUrl).href;
|
|
158
158
|
return result;
|
|
159
159
|
}
|
|
160
|
-
/**
|
|
161
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Natural-language control, using this key's configured model. Refer to `data`
|
|
162
|
+
* as `{{name}}` in the prompt: the agent types the placeholder, the page gets the
|
|
163
|
+
* value, and the model never sees it.
|
|
164
|
+
*/
|
|
165
|
+
async ask(prompt, { data } = {}) {
|
|
162
166
|
const res = await this.http.request(
|
|
163
167
|
"POST",
|
|
164
168
|
`/api/browsers/${this.id}/chat`,
|
|
165
|
-
{ messages: [{ role: "user", content: prompt }] },
|
|
169
|
+
{ messages: [{ role: "user", content: prompt }], data },
|
|
166
170
|
6e5
|
|
167
171
|
);
|
|
172
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
168
173
|
return res.text;
|
|
169
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Save the last `ask()` on this browser as a named playbook. Values that came
|
|
177
|
+
* from the prompt (names, IDs, dates) become variables; `code` is the same
|
|
178
|
+
* flow as a Playwright module, to read or run yourself.
|
|
179
|
+
*/
|
|
180
|
+
toPlaybook(name) {
|
|
181
|
+
return this.http.request("POST", `/api/browsers/${this.id}/playbooks`, { name }, 12e4);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Replay a playbook with no LLM in the loop. Variables left out reuse the
|
|
185
|
+
* recorded values where there are any. If a step no longer fits the page and
|
|
186
|
+
* `autoHeal` is on (the default), the agent finishes the task and its fix is
|
|
187
|
+
* saved as a draft (`healed`, `draft`); off, the step's error is thrown.
|
|
188
|
+
* Play `'<name>:draft'` to try a draft before promoting it.
|
|
189
|
+
*/
|
|
190
|
+
async play(name, data = {}, { autoHeal = true } = {}) {
|
|
191
|
+
const res = await this.http.request(
|
|
192
|
+
"POST",
|
|
193
|
+
`/api/browsers/${this.id}/playbooks/${encodeURIComponent(name)}/play`,
|
|
194
|
+
{ variables: data, autoHeal },
|
|
195
|
+
6e5
|
|
196
|
+
);
|
|
197
|
+
if (res.error) throw new OyaError(res.error, 500, res);
|
|
198
|
+
return res;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Start a prompt or playbook in the background and hear back through callbacks.
|
|
202
|
+
* `onHumanAttention` fires for an unsolved CAPTCHA, an unfinished MFA, the agent
|
|
203
|
+
* asking for help, or a replay the agent could not heal; the run waits (up to 30
|
|
204
|
+
* minutes) until you call `respond()`.
|
|
205
|
+
*/
|
|
206
|
+
async submit(task, options = {}) {
|
|
207
|
+
const { onSuccess, onFailure, onHumanAttention, onHealed, pollMs, ...body } = options;
|
|
208
|
+
const started = await this.http.request("POST", `/api/browsers/${this.id}/runs`, { ...task, ...body });
|
|
209
|
+
return new Run(this.http, started.id, { onSuccess, onFailure, onHumanAttention, onHealed }, pollMs);
|
|
210
|
+
}
|
|
170
211
|
/**
|
|
171
212
|
* Watch it work: the console, opened on this browser.
|
|
172
213
|
*
|
|
@@ -238,6 +279,72 @@ var Browser = class {
|
|
|
238
279
|
await this.stop();
|
|
239
280
|
}
|
|
240
281
|
};
|
|
282
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
283
|
+
var Run = class {
|
|
284
|
+
constructor(http, id, callbacks, pollMs = 2e3) {
|
|
285
|
+
this.http = http;
|
|
286
|
+
this.id = id;
|
|
287
|
+
this.done = this.watch(callbacks, pollMs);
|
|
288
|
+
this.done.catch(() => {
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
http;
|
|
292
|
+
id;
|
|
293
|
+
done;
|
|
294
|
+
status() {
|
|
295
|
+
return this.http.request("GET", `/api/runs/${encodeURIComponent(this.id)}`);
|
|
296
|
+
}
|
|
297
|
+
/** Answer the open attention request: `'done'` after handling it by hand, or your reply to the agent. */
|
|
298
|
+
async respond(response = "done") {
|
|
299
|
+
await this.http.request("POST", `/api/runs/${encodeURIComponent(this.id)}/respond`, { response });
|
|
300
|
+
}
|
|
301
|
+
async watch(cb, pollMs) {
|
|
302
|
+
const call = async (fn) => {
|
|
303
|
+
try {
|
|
304
|
+
await fn();
|
|
305
|
+
} catch (err) {
|
|
306
|
+
console.error("[oya] run callback threw:", err);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
let seen;
|
|
310
|
+
for (let errors = 0; ; ) {
|
|
311
|
+
let run2;
|
|
312
|
+
try {
|
|
313
|
+
run2 = await this.status();
|
|
314
|
+
errors = 0;
|
|
315
|
+
} catch (err) {
|
|
316
|
+
if (++errors < 5) {
|
|
317
|
+
await sleep(pollMs);
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const failure = err instanceof OyaError ? err : new OyaError(String(err), 0, null);
|
|
321
|
+
await call(() => cb.onFailure?.(failure));
|
|
322
|
+
throw failure;
|
|
323
|
+
}
|
|
324
|
+
if (run2.status === "needs_attention" && run2.attention && run2.attention.id !== seen) {
|
|
325
|
+
seen = run2.attention.id;
|
|
326
|
+
const request = {
|
|
327
|
+
...run2.attention,
|
|
328
|
+
liveViewUrl: run2.attention.liveViewUrl && new URL(run2.attention.liveViewUrl, this.http.baseUrl).href,
|
|
329
|
+
respond: (response) => this.respond(response)
|
|
330
|
+
};
|
|
331
|
+
void call(() => cb.onHumanAttention?.(request));
|
|
332
|
+
}
|
|
333
|
+
if (run2.status === "succeeded") {
|
|
334
|
+
const result = run2.result || {};
|
|
335
|
+
if (result.healed) await call(() => cb.onHealed?.(result));
|
|
336
|
+
await call(() => cb.onSuccess?.(result));
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
if (run2.status === "failed") {
|
|
340
|
+
const failure = new OyaError(run2.error || "Run failed", 500, run2);
|
|
341
|
+
await call(() => cb.onFailure?.(failure));
|
|
342
|
+
throw failure;
|
|
343
|
+
}
|
|
344
|
+
await sleep(pollMs);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
};
|
|
241
348
|
var DEFAULT_BASE_URL = "https://browser.getoya.ai";
|
|
242
349
|
var READY_POLL_MS = 2e3;
|
|
243
350
|
var env = (name) => globalThis.process?.env?.[name];
|
|
@@ -312,6 +419,16 @@ var Oya = class {
|
|
|
312
419
|
removeWebhook: (id) => this.http.request("DELETE", `/api/control/webhooks/${encodeURIComponent(id)}`),
|
|
313
420
|
replayDelivery: (id) => this.http.request("POST", `/api/control/deliveries/${encodeURIComponent(id)}/replay`, {})
|
|
314
421
|
};
|
|
422
|
+
/** Playbooks saved with `browser.toPlaybook()`. */
|
|
423
|
+
playbooks = {
|
|
424
|
+
list: async () => (await this.http.request("GET", "/api/playbooks")).playbooks,
|
|
425
|
+
/** Delete a playbook and its draft, or only the draft with `'<name>:draft'`. */
|
|
426
|
+
remove: async (name) => {
|
|
427
|
+
await this.http.request("DELETE", `/api/playbooks/${encodeURIComponent(name)}`);
|
|
428
|
+
},
|
|
429
|
+
/** Replace a playbook with the draft a healed replay saved. Try it first with `browser.play('<name>:draft')`. */
|
|
430
|
+
promote: (name) => this.http.request("POST", `/api/playbooks/${encodeURIComponent(name)}/promote`, {})
|
|
431
|
+
};
|
|
315
432
|
/**
|
|
316
433
|
* Proxy exits for your personas. A persona takes one at first connect (by its
|
|
317
434
|
* geo hint) or by `personas.pinProxy`, and keeps it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oya-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.76",
|
|
4
4
|
"description": "Command line for the Oya browser control plane.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://browser.getoya.ai",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@oya-ai/browser": "1.0.
|
|
42
|
+
"@oya-ai/browser": "1.0.76",
|
|
43
43
|
"@types/node": "^20.19.43",
|
|
44
44
|
"tsup": "^8.0.0",
|
|
45
45
|
"typescript": "^5.4.0"
|