@prompteryx/sdk 0.4.0

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 ADDED
@@ -0,0 +1,1258 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ AUTOPILOT_MODELS: () => AUTOPILOT_MODELS,
24
+ AuthError: () => AuthError,
25
+ DEFAULT_AUTOPILOT_MODEL: () => DEFAULT_AUTOPILOT_MODEL,
26
+ NetworkError: () => NetworkError,
27
+ NotFoundError: () => NotFoundError,
28
+ ParseError: () => ParseError,
29
+ Prompteryx: () => Prompteryx,
30
+ PrompteryxError: () => PrompteryxError,
31
+ QuotaError: () => QuotaError,
32
+ RateLimitError: () => RateLimitError,
33
+ ServerError: () => ServerError,
34
+ TimeoutError: () => TimeoutError,
35
+ ValidationError: () => ValidationError,
36
+ default: () => src_default
37
+ });
38
+ module.exports = __toCommonJS(src_exports);
39
+
40
+ // src/errors.ts
41
+ var PrompteryxError = class extends Error {
42
+ constructor(message, opts = {}) {
43
+ super(message);
44
+ this.name = "PrompteryxError";
45
+ this.status = opts.status;
46
+ this.code = opts.code;
47
+ this.requestId = opts.requestId;
48
+ this.raw = opts.raw;
49
+ Object.setPrototypeOf(this, new.target.prototype);
50
+ }
51
+ };
52
+ var AuthError = class _AuthError extends PrompteryxError {
53
+ constructor(message, opts = {}) {
54
+ super(message, opts);
55
+ this.name = "AuthError";
56
+ Object.setPrototypeOf(this, _AuthError.prototype);
57
+ }
58
+ };
59
+ var QuotaError = class _QuotaError extends PrompteryxError {
60
+ constructor(message, opts = {}) {
61
+ super(message, opts);
62
+ this.name = "QuotaError";
63
+ this.resources = opts.resources;
64
+ Object.setPrototypeOf(this, _QuotaError.prototype);
65
+ }
66
+ };
67
+ var NotFoundError = class _NotFoundError extends PrompteryxError {
68
+ constructor(message, opts = {}) {
69
+ super(message, opts);
70
+ this.name = "NotFoundError";
71
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
72
+ }
73
+ };
74
+ var ValidationError = class _ValidationError extends PrompteryxError {
75
+ constructor(message, opts = {}) {
76
+ super(message, opts);
77
+ this.name = "ValidationError";
78
+ Object.setPrototypeOf(this, _ValidationError.prototype);
79
+ }
80
+ };
81
+ var RateLimitError = class _RateLimitError extends PrompteryxError {
82
+ constructor(message, opts = {}) {
83
+ super(message, opts);
84
+ this.name = "RateLimitError";
85
+ this.retryAfterSeconds = opts.retryAfterSeconds;
86
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
87
+ }
88
+ };
89
+ var ServerError = class _ServerError extends PrompteryxError {
90
+ constructor(message, opts = {}) {
91
+ super(message, opts);
92
+ this.name = "ServerError";
93
+ Object.setPrototypeOf(this, _ServerError.prototype);
94
+ }
95
+ };
96
+ var NetworkError = class _NetworkError extends PrompteryxError {
97
+ constructor(message, opts = {}) {
98
+ super(message, opts);
99
+ this.name = "NetworkError";
100
+ Object.setPrototypeOf(this, _NetworkError.prototype);
101
+ }
102
+ };
103
+ var ParseError = class _ParseError extends PrompteryxError {
104
+ constructor(message, opts = {}) {
105
+ super(message, opts);
106
+ this.name = "ParseError";
107
+ Object.setPrototypeOf(this, _ParseError.prototype);
108
+ }
109
+ };
110
+ var TimeoutError = class _TimeoutError extends PrompteryxError {
111
+ constructor(message, opts = {}) {
112
+ super(message, opts);
113
+ this.name = "TimeoutError";
114
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
115
+ }
116
+ };
117
+
118
+ // src/client.ts
119
+ var DEFAULT_BASE_URL = "https://prompteryx.com";
120
+ var DEFAULT_TIMEOUT_MS = 6e4;
121
+ var DEFAULT_MAX_RETRIES = 2;
122
+ var HttpClient = class {
123
+ constructor(opts) {
124
+ if (!opts.apiKey) {
125
+ throw new PrompteryxError("apiKey is required to construct a Prompteryx client");
126
+ }
127
+ this.apiKey = opts.apiKey;
128
+ this.cloudBrowserKey = opts.cloudBrowserKey;
129
+ this.baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
130
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
131
+ this.maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
132
+ this.defaultHeaders = opts.defaultHeaders ?? {};
133
+ const f = opts.fetch ?? globalThis.fetch;
134
+ if (!f) {
135
+ throw new PrompteryxError(
136
+ "No fetch implementation found. Provide options.fetch or run on a runtime with global fetch (Node 18+, browser, Cloudflare Workers)."
137
+ );
138
+ }
139
+ this.fetchImpl = f.bind(globalThis);
140
+ }
141
+ /** Returns the JSON-parsed body typed as T. Throws typed errors on non-2xx. */
142
+ async request(path, opts = {}) {
143
+ const res = await this.rawRequest(path, opts);
144
+ const text = await res.text();
145
+ if (!text) return void 0;
146
+ try {
147
+ return JSON.parse(text);
148
+ } catch {
149
+ throw new ParseError(`Failed to parse JSON response from ${path}`, {
150
+ status: res.status,
151
+ raw: text.slice(0, 500)
152
+ });
153
+ }
154
+ }
155
+ /** Returns the raw Response. Throws typed errors on non-2xx, but does
156
+ * not attempt to read the body. Useful for downloading binaries. */
157
+ async rawRequest(path, opts = {}) {
158
+ const method = opts.method ?? "GET";
159
+ const url = this.buildUrl(path, opts.query);
160
+ const shouldRetry = opts.retry ?? method === "GET";
161
+ const maxAttempts = shouldRetry ? this.maxRetries + 1 : 1;
162
+ let lastErr;
163
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
164
+ try {
165
+ const res = await this.sendOnce(url, method, opts);
166
+ if (res.ok) return res;
167
+ const err = await this.errorFromResponse(res);
168
+ if (this.isRetryable(err) && attempt < maxAttempts - 1) {
169
+ await this.backoff(attempt, err);
170
+ lastErr = err;
171
+ continue;
172
+ }
173
+ throw err;
174
+ } catch (e) {
175
+ if (e instanceof PrompteryxError) throw e;
176
+ const wrapped = this.wrapTransport(e);
177
+ if (this.isRetryable(wrapped) && attempt < maxAttempts - 1) {
178
+ await this.backoff(attempt, wrapped);
179
+ lastErr = wrapped;
180
+ continue;
181
+ }
182
+ throw wrapped;
183
+ }
184
+ }
185
+ throw lastErr ?? new PrompteryxError("Unknown error after retries exhausted");
186
+ }
187
+ /**
188
+ * Stream a Server-Sent Events response line-by-line as JSON-parsed
189
+ * payloads. Yields each event's `data:` payload parsed as JSON. The
190
+ * caller is responsible for breaking the loop / cancelling the
191
+ * AbortSignal when done.
192
+ */
193
+ async *streamSse(path, opts = {}) {
194
+ const res = await this.rawRequest(path, {
195
+ ...opts,
196
+ headers: { ...opts.headers, Accept: "text/event-stream" }
197
+ });
198
+ if (!res.body) return;
199
+ const reader = res.body.getReader();
200
+ const decoder = new TextDecoder();
201
+ let buf = "";
202
+ while (true) {
203
+ const { value, done } = await reader.read();
204
+ if (done) break;
205
+ buf += decoder.decode(value, { stream: true });
206
+ let idx;
207
+ while ((idx = buf.indexOf("\n\n")) !== -1) {
208
+ const raw = buf.slice(0, idx);
209
+ buf = buf.slice(idx + 2);
210
+ const dataLines = raw.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).trim());
211
+ if (dataLines.length === 0) continue;
212
+ const payload = dataLines.join("\n");
213
+ if (!payload) continue;
214
+ try {
215
+ yield JSON.parse(payload);
216
+ } catch {
217
+ }
218
+ }
219
+ }
220
+ }
221
+ // ── internals ────────────────────────────────────────────────────────
222
+ async sendOnce(url, method, opts) {
223
+ const timeoutMs = opts.timeoutMs ?? this.timeoutMs;
224
+ const ctl = new AbortController();
225
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
226
+ if (opts.signal) {
227
+ if (opts.signal.aborted) ctl.abort();
228
+ else opts.signal.addEventListener("abort", () => ctl.abort(), { once: true });
229
+ }
230
+ try {
231
+ const headers = {
232
+ Authorization: `Bearer ${this.apiKey}`,
233
+ Accept: "application/json",
234
+ ...this.defaultHeaders,
235
+ ...opts.headers ?? {}
236
+ };
237
+ let body;
238
+ if (opts.body !== void 0) {
239
+ headers["Content-Type"] = "application/json";
240
+ body = JSON.stringify(opts.body);
241
+ }
242
+ return await this.fetchImpl(url, {
243
+ method,
244
+ headers,
245
+ body,
246
+ signal: ctl.signal
247
+ });
248
+ } finally {
249
+ clearTimeout(t);
250
+ }
251
+ }
252
+ buildUrl(path, query) {
253
+ const base = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
254
+ if (!query) return base;
255
+ const params = new URLSearchParams();
256
+ for (const [k, v] of Object.entries(query)) {
257
+ if (v === void 0 || v === null) continue;
258
+ params.set(k, String(v));
259
+ }
260
+ const qs = params.toString();
261
+ return qs ? `${base}${base.includes("?") ? "&" : "?"}${qs}` : base;
262
+ }
263
+ async errorFromResponse(res) {
264
+ let parsed = null;
265
+ try {
266
+ const text = await res.text();
267
+ parsed = text ? JSON.parse(text) : null;
268
+ } catch {
269
+ }
270
+ const message = parsed?.error?.message ?? parsed?.message ?? parsed?.error ?? `Request failed with ${res.status}`;
271
+ const code = parsed?.error?.code ?? parsed?.code;
272
+ const requestId = res.headers.get("x-request-id") ?? void 0;
273
+ const opts = { status: res.status, code, requestId, raw: parsed };
274
+ switch (res.status) {
275
+ case 401:
276
+ case 403:
277
+ return new AuthError(message, opts);
278
+ case 402:
279
+ return new QuotaError(message, { ...opts, resources: parsed?.error?.resources ?? parsed?.resources });
280
+ case 404:
281
+ return new NotFoundError(message, opts);
282
+ case 422:
283
+ return new ValidationError(message, opts);
284
+ case 429: {
285
+ const retryAfter = parseInt(res.headers.get("retry-after") ?? "0", 10);
286
+ return new RateLimitError(message, { ...opts, retryAfterSeconds: Number.isFinite(retryAfter) ? retryAfter : void 0 });
287
+ }
288
+ default:
289
+ if (res.status >= 500) return new ServerError(message, opts);
290
+ return new PrompteryxError(message, opts);
291
+ }
292
+ }
293
+ wrapTransport(e) {
294
+ const msg = e instanceof Error ? e.message : String(e);
295
+ if (e instanceof Error && (e.name === "AbortError" || msg.includes("aborted"))) {
296
+ return new TimeoutError(`Request timed out: ${msg}`);
297
+ }
298
+ return new NetworkError(`Network error: ${msg}`);
299
+ }
300
+ isRetryable(err) {
301
+ if (err instanceof ServerError) return true;
302
+ if (err instanceof RateLimitError) return true;
303
+ if (err instanceof NetworkError) return true;
304
+ if (err instanceof TimeoutError) return true;
305
+ return false;
306
+ }
307
+ async backoff(attempt, err) {
308
+ const retryAfter = err.retryAfterSeconds;
309
+ if (retryAfter && retryAfter > 0) {
310
+ return new Promise((r) => setTimeout(r, Math.min(retryAfter, 30) * 1e3));
311
+ }
312
+ const base = 250 * Math.pow(2, attempt);
313
+ const jitter = Math.random() * 100;
314
+ return new Promise((r) => setTimeout(r, base + jitter));
315
+ }
316
+ };
317
+
318
+ // src/resources/autopilot.ts
319
+ var AutopilotResource = class {
320
+ constructor(http) {
321
+ this.http = http;
322
+ }
323
+ /**
324
+ * Run the autopilot. Blocks until the task finishes (success, step
325
+ * limit, or error). Returns the trace plus optionally the saved
326
+ * workflow id.
327
+ *
328
+ * ```ts
329
+ * const result = await px.autopilot.run({
330
+ * goal: 'Apply for the Senior Engineer role at OpenAI',
331
+ * startUrl: 'https://openai.com/careers',
332
+ * maxSteps: 40,
333
+ * saveAsWorkflow: true, // Replay-forever, zero AI cost
334
+ * session: { useProxy: true, useCaptcha: true },
335
+ * })
336
+ * if (result.savedWorkflowId) {
337
+ * console.log('Saved as workflow:', result.savedWorkflowId)
338
+ * // Run it later for free:
339
+ * await px.workflows.run(result.savedWorkflowId)
340
+ * }
341
+ * ```
342
+ */
343
+ async run(opts) {
344
+ if (opts.target === "local") {
345
+ return this.runLocal(opts);
346
+ }
347
+ if (opts.agents && opts.agents > 1) {
348
+ return this.runSwarm(opts);
349
+ }
350
+ return this.runAsync(opts);
351
+ }
352
+ /** The task params shared by the sync, swarm, and async request bodies. */
353
+ cloudTaskBody(opts) {
354
+ return {
355
+ instruction: opts.goal,
356
+ startUrl: opts.startUrl,
357
+ maxSteps: opts.maxSteps,
358
+ maxCredits: opts.maxCredits,
359
+ model: opts.model,
360
+ aiVision: opts.aiVision,
361
+ finalStepVision: opts.finalStepVision,
362
+ outputSchema: opts.outputSchema,
363
+ costSaving: opts.costSaving,
364
+ costSavingMaxBatch: opts.costSavingMaxBatch,
365
+ carefulBatching: opts.carefulBatching,
366
+ saveAsWorkflow: opts.saveAsWorkflow,
367
+ savedWorkflowName: opts.savedWorkflowName,
368
+ sessionId: opts.sessionId,
369
+ systemPromptOverride: opts.systemPromptOverride,
370
+ allowedTools: opts.allowedTools,
371
+ viewport: opts.viewport,
372
+ session: opts.session,
373
+ // Settings parity (2026-07-15) — mirror the AI Browser Agent settings dialog.
374
+ safetyConsent: opts.safetyConsent,
375
+ confirmUnclear: opts.confirmUnclear,
376
+ enableContextCompression: opts.enableContextCompression,
377
+ compressionThreshold: opts.compressionThreshold,
378
+ enableSessionReset: opts.enableSessionReset,
379
+ sessionResetThreshold: opts.sessionResetThreshold,
380
+ ...opts.passthrough
381
+ };
382
+ }
383
+ /** Multi-agent swarm — one synchronous request; the server merges all lanes. */
384
+ async runSwarm(opts) {
385
+ const env = await this.http.request("/api/v1/ai-browser/execute", {
386
+ method: "POST",
387
+ timeoutMs: opts.timeoutMs ?? 10 * 6e4,
388
+ body: {
389
+ ...this.cloudTaskBody(opts),
390
+ agents: opts.agents,
391
+ collaborate: opts.collaborate,
392
+ attachmentContext: opts.attachmentContext,
393
+ maxRunCredits: opts.maxRunCredits
394
+ }
395
+ });
396
+ return this.mapResult(env?.data ?? env);
397
+ }
398
+ /**
399
+ * Async single-agent run: POST { mode:'start' } to set up the job (returns a
400
+ * jobId immediately), then POST { mode:'run' } in a loop — each advances the job
401
+ * for up to ~230s server-side and returns the current status — until the job is
402
+ * terminal or `opts.timeoutMs` elapses. No single request is long, so the gateway
403
+ * timeout is never hit. Same `AutopilotRunResult` shape as before.
404
+ */
405
+ async runAsync(opts) {
406
+ const timeoutMs = opts.timeoutMs ?? 10 * 6e4;
407
+ const deadline = Date.now() + timeoutMs;
408
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
409
+ const startEnv = await this.http.request("/api/v1/ai-browser/execute", {
410
+ method: "POST",
411
+ timeoutMs: 6e4,
412
+ body: { ...this.cloudTaskBody(opts), mode: "start" }
413
+ });
414
+ const jobId = startEnv?.data?.jobId;
415
+ if (!jobId) {
416
+ return this.mapResult(startEnv?.data ?? startEnv);
417
+ }
418
+ let view = null;
419
+ while (Date.now() < deadline) {
420
+ const runEnv = await this.http.request("/api/v1/ai-browser/execute", {
421
+ method: "POST",
422
+ timeoutMs: 25e4,
423
+ retry: true,
424
+ body: { jobId, mode: "run", maxSteps: opts.maxSteps }
425
+ });
426
+ view = runEnv?.data ?? null;
427
+ if (!view || view.terminal || view.status !== "running") break;
428
+ await sleep(500);
429
+ }
430
+ if (view && view.status === "running") {
431
+ try {
432
+ const statusEnv = await this.http.request("/api/v1/ai-browser/execute", {
433
+ query: { jobId },
434
+ timeoutMs: 3e4
435
+ });
436
+ if (statusEnv?.data) view = statusEnv.data;
437
+ } catch {
438
+ }
439
+ }
440
+ return this.mapResult(view);
441
+ }
442
+ /** Map an execute-endpoint payload (sync result, swarm result, or async status
443
+ * view — they share finalAnswer / steps / usage / savedWorkflowId / status) into
444
+ * the public AutopilotRunResult shape. */
445
+ mapResult(data) {
446
+ const d = data || {};
447
+ const status = d.status;
448
+ const success = status === "completed" || status === "max_steps_reached";
449
+ const steps = Array.isArray(d.steps) ? d.steps.map((s, i) => ({
450
+ step: i + 1,
451
+ action: typeof s?.action === "string" ? s.action : s?.action?.name || "action",
452
+ result: s?.details ?? s?.result
453
+ })) : [];
454
+ return {
455
+ success,
456
+ finalAnswer: d.finalAnswer,
457
+ steps,
458
+ savedWorkflowId: d.savedWorkflowId,
459
+ usage: d.usage,
460
+ swarm: d.swarm
461
+ };
462
+ }
463
+ /**
464
+ * Local Chrome autopilot — talks DIRECTLY to the Prompteryx desktop app on
465
+ * this machine (localhost:61337): opens your local Chrome, runs the agent
466
+ * loop there, polls until done. Same options as `run` (model, aiVision
467
+ * preset, maxSteps, maxCredits, costSaving). Credits still apply; cloud
468
+ * minutes do not. The tab stays open after the run for follow-ups.
469
+ */
470
+ async runLocal(opts) {
471
+ const fetchImpl = globalThis.fetch;
472
+ if (!fetchImpl) {
473
+ throw new Error("[Prompteryx SDK] Local target needs a global fetch (Node 18+ / browser).");
474
+ }
475
+ const base = (opts.runnerUrl || "http://localhost:61337").replace(/\/$/, "");
476
+ const sessionId = `sdk-local-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
477
+ const goal = opts.startUrl ? `First, go to ${opts.startUrl}. Then: ${opts.goal}` : opts.goal;
478
+ const post = async (path, body) => {
479
+ let r;
480
+ try {
481
+ r = await fetchImpl(`${base}${path}`, {
482
+ method: "POST",
483
+ headers: { "Content-Type": "application/json" },
484
+ body: JSON.stringify(body)
485
+ });
486
+ } catch (e) {
487
+ throw new Error(
488
+ `[Prompteryx SDK] Couldn't reach the desktop app at ${base}. Make sure the Prompteryx desktop app is running and signed in on this machine. (${e.message})`
489
+ );
490
+ }
491
+ if (!r.ok) {
492
+ let msg = `${path} failed (${r.status})`;
493
+ try {
494
+ const j = await r.json();
495
+ if (j?.error) msg = j.error;
496
+ } catch {
497
+ }
498
+ throw new Error(`[Prompteryx SDK] ${msg}`);
499
+ }
500
+ return r.json().catch(() => ({}));
501
+ };
502
+ await post("/launch-gemini-browser", {
503
+ sessionId,
504
+ profileId: opts.session?.profileId,
505
+ enableRecording: false
506
+ });
507
+ await post("/local-agent/start", {
508
+ sessionId,
509
+ task: goal,
510
+ model: opts.model,
511
+ aiVision: opts.aiVision,
512
+ // preset slug — resolved app-side
513
+ maxTurns: opts.maxSteps,
514
+ maxCredits: opts.maxCredits,
515
+ costSaving: opts.passthrough?.costSaving === true,
516
+ costSavingMaxBatch: opts.passthrough?.costSavingMaxBatch
517
+ });
518
+ const deadline = Date.now() + (opts.timeoutMs ?? 10 * 6e4);
519
+ const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
520
+ let state = null;
521
+ while (Date.now() < deadline) {
522
+ await sleep(1200);
523
+ const r = await fetchImpl(`${base}/local-agent/state/${encodeURIComponent(sessionId)}`).catch(() => null);
524
+ if (!r || !r.ok) continue;
525
+ state = await r.json().catch(() => null);
526
+ if (!state || state.found === false) continue;
527
+ if (state.status && state.status !== "running") break;
528
+ }
529
+ const status = state?.status;
530
+ return {
531
+ success: status === "done" || status === "awaiting_input",
532
+ finalAnswer: state?.answer,
533
+ steps: (state?.steps || []).map((s, i) => ({
534
+ step: i + 1,
535
+ action: s?.name || s?.action?.name || "action",
536
+ result: s?.args || s?.action?.args
537
+ })),
538
+ usage: {
539
+ aiCredits: Math.max(0, Math.round((state?.costUSD || 0) / 0.01)),
540
+ tokensIn: state?.inTokens || 0,
541
+ tokensOut: state?.outTokens || 0,
542
+ costUSD: state?.costUSD || 0,
543
+ turns: state?.turns || 0
544
+ }
545
+ };
546
+ }
547
+ /**
548
+ * Ask a running async job to stop at its next step boundary
549
+ * (`POST { jobId, mode: 'stop' }`). Use it to wind down a job you
550
+ * started via the raw API (or a run you're abandoning) instead of
551
+ * leaving it stepping against a dead browser session until the step
552
+ * cap — an abandoned job burns a model call + timeout per step.
553
+ *
554
+ * ```ts
555
+ * const { stopRequested, status } = await px.autopilot.stop(jobId)
556
+ * ```
557
+ */
558
+ async stop(jobId) {
559
+ const env = await this.http.request("/api/v1/ai-browser/execute", {
560
+ method: "POST",
561
+ timeoutMs: 3e4,
562
+ body: { jobId, mode: "stop" }
563
+ });
564
+ const d = env?.data ?? env ?? {};
565
+ return { jobId: d.jobId ?? jobId, stopRequested: d.stopRequested === true, status: d.status };
566
+ }
567
+ /**
568
+ * Run the autopilot over the keep-alive stream endpoint and yield the
569
+ * step trace. Ends with a `{ step: -1, action: 'done' }` sentinel whose
570
+ * `result` field carries the full `AutopilotRunResult`.
571
+ *
572
+ * ```ts
573
+ * for await (const step of px.autopilot.stream({ goal: 'Buy a ticket' })) {
574
+ * console.log('Step', step.step, '→', step.action)
575
+ * if (step.action === 'done') break
576
+ * }
577
+ * ```
578
+ *
579
+ * PROTOCOL (matches /api/v1/ai-browser/execute-stream — it is NOT SSE):
580
+ * the server emits a 1-space heartbeat every 15s while the run executes,
581
+ * then the complete execute-route JSON as the final chunk, i.e. the body
582
+ * is `<heartbeats>\n<json>`. The heartbeats exist to defeat the ~300s
583
+ * infra idle timeout on long synchronous runs; per-step live events are
584
+ * not available on this route, so steps arrive together when the run
585
+ * finishes. Prefer `run()` unless you specifically want the keep-alive
586
+ * transport for a long single-request run.
587
+ */
588
+ async *stream(opts) {
589
+ if (opts.target === "local") {
590
+ throw new Error(
591
+ '[Prompteryx SDK] Streaming is cloud-only. For local Chrome use autopilot.run({ target: "local" }) \u2014 its result contains all steps.'
592
+ );
593
+ }
594
+ const res = await this.http.rawRequest("/api/v1/ai-browser/execute-stream", {
595
+ method: "POST",
596
+ timeoutMs: opts.timeoutMs ?? 15 * 6e4,
597
+ body: this.cloudTaskBody(opts),
598
+ signal: opts.signal
599
+ });
600
+ let text = "";
601
+ if (res.body) {
602
+ const reader = res.body.getReader();
603
+ const decoder = new TextDecoder();
604
+ while (true) {
605
+ const { value, done } = await reader.read();
606
+ if (done) break;
607
+ if (value) text += decoder.decode(value, { stream: true });
608
+ }
609
+ text += decoder.decode();
610
+ } else {
611
+ text = await res.text();
612
+ }
613
+ const payload = text.trim();
614
+ let parsed;
615
+ try {
616
+ parsed = JSON.parse(payload);
617
+ } catch {
618
+ throw new ParseError("execute-stream returned a non-JSON payload", {
619
+ raw: payload.slice(0, 500)
620
+ });
621
+ }
622
+ if (parsed && parsed.success === false) {
623
+ const msg = parsed?.error?.message ?? parsed?.error ?? "AI Browser Agent run failed";
624
+ throw new PrompteryxError(String(msg), { code: parsed?.error?.code, raw: parsed });
625
+ }
626
+ const result = this.mapResult(parsed?.data ?? parsed);
627
+ for (const step of result.steps) yield step;
628
+ yield { step: -1, action: "done", result };
629
+ }
630
+ };
631
+
632
+ // src/resources/cloudBrowser.ts
633
+ var MISSING_CB_KEY_MESSAGE = "px.cloudBrowser.* uses a Cloud Browser API key (pcb_live_\u2026), which is a separate key family from the platform px_live_\u2026 key. Create one under Cloud Platform \u2192 API Keys and pass it as new Prompteryx({ apiKey, cloudBrowserKey }).";
634
+ var SessionsResource = class {
635
+ constructor(http) {
636
+ this.http = http;
637
+ }
638
+ /** Headers for the pcb_live_ key family (throws a clear error if absent). */
639
+ cbAuth() {
640
+ const key = this.http.cloudBrowserKey;
641
+ if (!key) throw new AuthError(MISSING_CB_KEY_MESSAGE);
642
+ return { "x-api-key": key };
643
+ }
644
+ cbRequest(path, opts = {}) {
645
+ return this.http.request(path, {
646
+ ...opts,
647
+ headers: { ...this.cbAuth(), ...opts.headers ?? {} }
648
+ });
649
+ }
650
+ /** Create a new browser session.
651
+ *
652
+ * Cloud (default):
653
+ * ```ts
654
+ * const s = await px.cloudBrowser.sessions.create()
655
+ * // s.connectUrl → Prompteryx Cloud CDP. Bills cloud-browser minutes.
656
+ * ```
657
+ *
658
+ * Local — uses YOUR machine's Chrome via the Prompteryx plugin +
659
+ * Electron runner. ZERO cloud-browser minutes. Requires the plugin
660
+ * to be running on the same machine as the SDK consumer; the call
661
+ * short-circuits to localhost and never reaches the API.
662
+ * ```ts
663
+ * const s = await px.cloudBrowser.sessions.create({ target: 'local' })
664
+ * // s.connectUrl → http://localhost:9222 (your Chrome's debug port)
665
+ * ```
666
+ *
667
+ * When `target: 'local'` is set, cloud-only fields (recordSession,
668
+ * proxy, profileId) are ignored — you're driving your own Chrome
669
+ * with whatever cookies/extensions you've already installed.
670
+ */
671
+ async create(opts = {}) {
672
+ if (opts.target === "local") {
673
+ const localCdp = "http://127.0.0.1:9222";
674
+ const localId = `local_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
675
+ return {
676
+ id: localId,
677
+ connectUrl: localCdp,
678
+ status: "active",
679
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
680
+ recordSession: false
681
+ };
682
+ }
683
+ return this.cbRequest("/api/v1/cloud-browser/sessions", {
684
+ method: "POST",
685
+ body: {
686
+ recordSession: opts.recordSession,
687
+ captureDownloads: opts.captureDownloads,
688
+ extensions: opts.extensions,
689
+ proxy: opts.useProxy,
690
+ country: opts.proxyLocation,
691
+ sessionTimeoutMinutes: opts.sessionTimeoutMinutes,
692
+ profileId: opts.profileId,
693
+ persistContext: opts.persistContext,
694
+ viewport: opts.viewport,
695
+ ...opts.passthrough || {}
696
+ }
697
+ });
698
+ }
699
+ /** Retrieve a session's history record (status/duration/recording flag).
700
+ * Note: this is durable history, not a live handle — it has no
701
+ * `connectUrl`. Keep the `create()` response for connecting. */
702
+ async get(sessionId) {
703
+ return this.cbRequest(
704
+ `/api/v1/cloud-browser/sessions/${encodeURIComponent(sessionId)}`
705
+ );
706
+ }
707
+ /** List recent sessions for your account, newest first. */
708
+ async list(opts = {}) {
709
+ const res = await this.cbRequest(
710
+ "/api/v1/cloud-browser/sessions",
711
+ { query: { limit: opts.limit } }
712
+ );
713
+ return res.sessions ?? [];
714
+ }
715
+ /** Close a session, finalising the recording (if any) + releasing the
716
+ * cloud-browser slot. Idempotent. No-op for local sessions
717
+ * (target: 'local') — those don't have a slot to release. */
718
+ async close(sessionId) {
719
+ if (sessionId.startsWith("local_")) return { ok: true };
720
+ return this.cbRequest(
721
+ `/api/v1/cloud-browser/sessions/${encodeURIComponent(sessionId)}`,
722
+ { method: "DELETE" }
723
+ );
724
+ }
725
+ };
726
+ var CloudBrowserResource = class {
727
+ constructor(http) {
728
+ this.http = http;
729
+ this.sessions = new SessionsResource(http);
730
+ }
731
+ cbAuth() {
732
+ const key = this.http.cloudBrowserKey;
733
+ if (!key) throw new AuthError(MISSING_CB_KEY_MESSAGE);
734
+ return { "x-api-key": key };
735
+ }
736
+ /**
737
+ * One-shot fetch through the cloud browser. Spins up a short-lived
738
+ * session, loads the page in real Chromium (so JS-rendered sites work),
739
+ * extracts the content, and tears down. Use this when you only need ONE
740
+ * page and don't want to manage Playwright yourself.
741
+ *
742
+ * ```ts
743
+ * const page = await px.cloudBrowser.fetch({
744
+ * url: 'https://example.com/pricing',
745
+ * format: 'markdown', // 'text' (default) | 'markdown' | 'html' | 'links'
746
+ * waitForSelector: '.pricing-table', // for JS-rendered content
747
+ * selectors: ['.pricing-table .plan'], // deterministic CSS extraction
748
+ * })
749
+ * // page.content, page.extracted, page.title, page.finalUrl …
750
+ * ```
751
+ */
752
+ async fetch(opts) {
753
+ return this.http.request("/api/v1/cloud-browser/fetch", {
754
+ method: "POST",
755
+ body: opts,
756
+ headers: this.cbAuth(),
757
+ timeoutMs: Math.max(9e4, (opts.timeoutMs ?? 3e4) + 3e4)
758
+ });
759
+ }
760
+ /**
761
+ * Search the web through the cloud browser and get structured results
762
+ * (title/url/snippet). Runs the query against DuckDuckGo's server-rendered
763
+ * HTML endpoint in a real browser — there is no engine choice today.
764
+ */
765
+ async search(opts) {
766
+ const res = await this.http.request(
767
+ "/api/v1/cloud-browser/search",
768
+ { method: "POST", body: opts, headers: this.cbAuth(), timeoutMs: 9e4 }
769
+ );
770
+ return res.results ?? [];
771
+ }
772
+ };
773
+
774
+ // src/resources/executions.ts
775
+ var TERMINAL_STATES = [
776
+ "completed",
777
+ "failed",
778
+ "cancelled",
779
+ "timed_out"
780
+ ];
781
+ var ExecutionsResource = class {
782
+ constructor(http) {
783
+ this.http = http;
784
+ }
785
+ /** Get current execution record. */
786
+ async get(executionId) {
787
+ return this.http.request(`/api/v1/executions/${encodeURIComponent(executionId)}`);
788
+ }
789
+ /**
790
+ * Block until the execution reaches a terminal state. Polls every
791
+ * `pollIntervalMs` (default 2s) until it's `completed`/`failed`/
792
+ * `cancelled`/`timed_out`, OR until `timeoutMs` elapses (default 5min).
793
+ *
794
+ * Throws `TimeoutError` on timeout; otherwise returns the final record.
795
+ */
796
+ async wait(executionId, opts = {}) {
797
+ const timeoutMs = opts.timeoutMs ?? 5 * 6e4;
798
+ const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
799
+ const deadline = Date.now() + timeoutMs;
800
+ while (true) {
801
+ if (opts.signal?.aborted) throw new TimeoutError(`Wait cancelled for ${executionId}`);
802
+ const exec = await this.get(executionId);
803
+ if (TERMINAL_STATES.includes(exec.status)) return exec;
804
+ if (Date.now() > deadline) {
805
+ throw new TimeoutError(`Execution ${executionId} did not finish within ${timeoutMs}ms`);
806
+ }
807
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
808
+ }
809
+ }
810
+ /**
811
+ * Get the logs for a finished (or in-progress) execution as a one-shot
812
+ * fetch. For real-time streaming use `stream(executionId)` instead.
813
+ */
814
+ async logs(executionId) {
815
+ const res = await this.http.request(
816
+ `/api/v1/executions/${encodeURIComponent(executionId)}/logs`
817
+ );
818
+ return res.logs ?? [];
819
+ }
820
+ /**
821
+ * Stream log events as they arrive. Async iterable:
822
+ *
823
+ * for await (const ev of px.executions.stream(execId)) {
824
+ * console.log(ev.message)
825
+ * if (ev.type === 'done') break
826
+ * }
827
+ */
828
+ async *stream(executionId, opts = {}) {
829
+ yield* this.http.streamSse(
830
+ `/api/v1/executions/${encodeURIComponent(executionId)}/logs/stream`,
831
+ { signal: opts.signal }
832
+ );
833
+ }
834
+ /** Get a single node's output from a finished execution. */
835
+ async getNodeOutput(executionId, nodeId) {
836
+ return this.http.request(
837
+ `/api/v1/executions/${encodeURIComponent(executionId)}/nodes/${encodeURIComponent(nodeId)}/output`
838
+ );
839
+ }
840
+ };
841
+
842
+ // src/resources/profiles.ts
843
+ var ProfilesResource = class {
844
+ constructor(http) {
845
+ this.http = http;
846
+ }
847
+ async list(opts = {}) {
848
+ const res = await this.http.request(
849
+ "/api/v1/profiles",
850
+ { query: { kind: opts.kind } }
851
+ );
852
+ return res.profiles ?? [];
853
+ }
854
+ async get(profileId) {
855
+ return this.http.request(`/api/v1/profiles/${encodeURIComponent(profileId)}`);
856
+ }
857
+ /** Create a new CLOUD profile. Local profiles are managed by the
858
+ * plugin and cannot be created via the API. */
859
+ async create(opts) {
860
+ return this.http.request("/api/v1/profiles", {
861
+ method: "POST",
862
+ body: { name: opts.name, kind: "cloud" }
863
+ });
864
+ }
865
+ async delete(profileId) {
866
+ return this.http.request(`/api/v1/profiles/${encodeURIComponent(profileId)}`, {
867
+ method: "DELETE"
868
+ });
869
+ }
870
+ };
871
+
872
+ // src/resources/schedules.ts
873
+ var SchedulesResource = class {
874
+ constructor(http) {
875
+ this.http = http;
876
+ }
877
+ async list(opts = {}) {
878
+ const res = await this.http.request(
879
+ "/api/v1/schedules",
880
+ { query: { active: opts.active, workflowId: opts.workflowId } }
881
+ );
882
+ return res.schedules ?? [];
883
+ }
884
+ async get(scheduleId) {
885
+ return this.http.request(`/api/v1/schedules/${encodeURIComponent(scheduleId)}`);
886
+ }
887
+ /** Create a new schedule. Returns the created record. */
888
+ async create(opts) {
889
+ return this.http.request("/api/v1/schedules", {
890
+ method: "POST",
891
+ body: opts
892
+ });
893
+ }
894
+ /** Pause / resume / change cron / timezone. */
895
+ async update(scheduleId, patch) {
896
+ return this.http.request(`/api/v1/schedules/${encodeURIComponent(scheduleId)}`, {
897
+ method: "PATCH",
898
+ body: patch
899
+ });
900
+ }
901
+ async delete(scheduleId) {
902
+ return this.http.request(`/api/v1/schedules/${encodeURIComponent(scheduleId)}`, {
903
+ method: "DELETE"
904
+ });
905
+ }
906
+ };
907
+
908
+ // src/resources/subscription.ts
909
+ var SubscriptionResource = class {
910
+ constructor(http) {
911
+ this.http = http;
912
+ }
913
+ /** Get the current plan + balances + usage. */
914
+ async get() {
915
+ return this.http.request("/api/v1/subscription");
916
+ }
917
+ // NOT SHIPPED (2026-09-03, v0.4.0): usage() was removed — the route it
918
+ // targeted (/api/v1/subscription/usage) does not exist on the live API
919
+ // (the only usage route is /api/v1/usage, with a different shape).
920
+ // Re-add once a real time-series endpoint ships.
921
+ };
922
+
923
+ // src/resources/workflows.ts
924
+ var WorkflowsResource = class {
925
+ constructor(http) {
926
+ this.http = http;
927
+ }
928
+ async list(opts = {}) {
929
+ const res = await this.http.request(
930
+ "/api/v1/workflows",
931
+ { query: { limit: opts.limit, q: opts.search, tag: opts.tag } }
932
+ );
933
+ return res.workflows ?? [];
934
+ }
935
+ async get(workflowId) {
936
+ return this.http.request(`/api/v1/workflows/${encodeURIComponent(workflowId)}`);
937
+ }
938
+ /**
939
+ * Trigger a workflow. Returns immediately with `executionId`. Use
940
+ * `px.executions.wait(id)` to block until completion or
941
+ * `px.executions.stream(id)` to follow log events live.
942
+ *
943
+ * The `execution` options override the workflow's saved settings
944
+ * for this one run — you don't have to edit the workflow in VS to
945
+ * change the proxy, region, profile, etc.
946
+ */
947
+ async run(workflowId, opts = {}) {
948
+ return this.http.request(
949
+ `/api/v1/workflows/${encodeURIComponent(workflowId)}/execute`,
950
+ {
951
+ method: "POST",
952
+ body: {
953
+ variables: opts.input,
954
+ executionOptions: opts.execution
955
+ }
956
+ }
957
+ );
958
+ }
959
+ /**
960
+ * Run + block. Returns the final ExecutionRecord. Throws
961
+ * `TimeoutError` if the run takes longer than `timeoutMs`
962
+ * (default 5 minutes).
963
+ */
964
+ async runAndWait(workflowId, opts = {}) {
965
+ const started = await this.run(workflowId, opts);
966
+ return this.waitInternal(started.executionId, opts.timeoutMs ?? 5 * 6e4, opts.pollIntervalMs ?? 2e3);
967
+ }
968
+ async waitInternal(executionId, timeoutMs, pollIntervalMs) {
969
+ const deadline = Date.now() + timeoutMs;
970
+ while (true) {
971
+ const exec = await this.http.request(
972
+ `/api/v1/executions/${encodeURIComponent(executionId)}`
973
+ );
974
+ if (["completed", "failed", "cancelled", "timed_out"].includes(exec.status)) return exec;
975
+ if (Date.now() > deadline) {
976
+ throw new TimeoutError(`Execution ${executionId} did not finish within ${timeoutMs}ms`);
977
+ }
978
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
979
+ }
980
+ }
981
+ };
982
+
983
+ // src/page.ts
984
+ var CopilotHelpers = class {
985
+ constructor(http) {
986
+ this.http = http;
987
+ }
988
+ /**
989
+ * Execute a single natural-language action against the page.
990
+ *
991
+ * ```ts
992
+ * await px.copilot.do(page, 'click the Sign up button')
993
+ * await px.copilot.do(page, 'fill the email field with hello@example.com')
994
+ * ```
995
+ *
996
+ * Internally: snapshot the page → POST `/api/v1/copilot/do` → server
997
+ * returns a structured action plan (ranked selector + alternatives +
998
+ * value + a normalised vision point) → SDK executes locally, trying the
999
+ * ranked selectors in order, and ONLY if every selector fails, falling
1000
+ * back to an AI-vision coordinate click. Costs 1 AI Credit per call.
1001
+ *
1002
+ * This is the key resilience advantage over a pure-LLM `act()`: the cheap,
1003
+ * deterministic ranked selectors are tried first (no flakiness, no re-asking
1004
+ * the model); the vision fallback is a safety net, not the default path. Pass
1005
+ * `{ visionFallback: false }` to disable the fallback (selectors-only).
1006
+ */
1007
+ async do(page, instruction, opts = {}) {
1008
+ const start = Date.now();
1009
+ const snapshot = await this.snapshotPage(page);
1010
+ const plan = await this.http.request("/api/v1/copilot/do", {
1011
+ method: "POST",
1012
+ body: { instruction, snapshot },
1013
+ timeoutMs: 45e3
1014
+ });
1015
+ const allowVision = opts.visionFallback !== false;
1016
+ try {
1017
+ const usedVisionFallback = await this.runAction(page, plan, opts.timeout ?? 15e3, allowVision);
1018
+ return {
1019
+ success: true,
1020
+ action: plan.description,
1021
+ selector: plan.selector,
1022
+ usedVisionFallback,
1023
+ durationMs: Date.now() - start
1024
+ };
1025
+ } catch (err) {
1026
+ return {
1027
+ success: false,
1028
+ action: plan.description,
1029
+ selector: plan.selector,
1030
+ durationMs: Date.now() - start,
1031
+ error: err instanceof Error ? err.message : String(err)
1032
+ };
1033
+ }
1034
+ }
1035
+ /**
1036
+ * Pull typed structured data from the page conforming to a schema.
1037
+ *
1038
+ * ```ts
1039
+ * import { z } from 'zod'
1040
+ * const product = await px.copilot.read(page, z.object({
1041
+ * name: z.string(),
1042
+ * pricePerMonth: z.number(),
1043
+ * features: z.array(z.string()),
1044
+ * }))
1045
+ * // product is fully typed; ValidationError is thrown if the model
1046
+ * // returns data that doesn't match the schema.
1047
+ * ```
1048
+ *
1049
+ * Accepts a Zod schema (preferred — gives you compile-time types)
1050
+ * OR a raw JSON Schema via `{ jsonSchema: ... }` if you don't want
1051
+ * a `zod` peer dep.
1052
+ */
1053
+ async read(page, schema) {
1054
+ const snapshot = await this.snapshotPage(page);
1055
+ const jsonSchema = "jsonSchema" in schema ? schema.jsonSchema : this.zodToJsonSchema(schema);
1056
+ const raw = await this.http.request(
1057
+ "/api/v1/copilot/read",
1058
+ { method: "POST", body: { snapshot, jsonSchema }, timeoutMs: 6e4 }
1059
+ );
1060
+ if ("parse" in schema && typeof schema.parse === "function") {
1061
+ try {
1062
+ return schema.parse(raw.data);
1063
+ } catch (e) {
1064
+ throw new ValidationError(
1065
+ `Extracted data didn't match the schema: ${e instanceof Error ? e.message : String(e)}`,
1066
+ { raw: raw.data }
1067
+ );
1068
+ }
1069
+ }
1070
+ return raw.data;
1071
+ }
1072
+ /**
1073
+ * Scan the page for available actions. Returns a ranked list of
1074
+ * actions a user / agent could take next, with selectors + multi-
1075
+ * option fallbacks + human-readable descriptions.
1076
+ *
1077
+ * Useful as a pre-step to `do()` for resilient automations:
1078
+ * const actions = await px.copilot.scan(page, 'sign up flow')
1079
+ * const target = actions.find(a => a.description.includes('Sign up'))
1080
+ * if (target) await px.copilot.do(page, target.example ?? `click ${target.description}`)
1081
+ */
1082
+ async scan(page, hint) {
1083
+ const snapshot = await this.snapshotPage(page);
1084
+ const res = await this.http.request(
1085
+ "/api/v1/copilot/scan",
1086
+ { method: "POST", body: { snapshot, hint }, timeoutMs: 45e3 }
1087
+ );
1088
+ return res.actions ?? [];
1089
+ }
1090
+ // ── internals ────────────────────────────────────────────────────────
1091
+ async snapshotPage(page) {
1092
+ const buf = await page.screenshot({ type: "jpeg", quality: 60, fullPage: false });
1093
+ const screenshot = bufferToBase64(buf);
1094
+ return {
1095
+ url: page.url(),
1096
+ title: await page.title().catch(() => ""),
1097
+ screenshot
1098
+ };
1099
+ }
1100
+ /** Execute the plan. Returns true if the AI-vision fallback was used. */
1101
+ async runAction(page, plan, timeout, allowVision) {
1102
+ if (plan.type === "press_key" && plan.value) {
1103
+ await page.keyboard.press(plan.value);
1104
+ return false;
1105
+ }
1106
+ if (plan.type === "goto" && plan.url) {
1107
+ await page.goto(plan.url, { timeout });
1108
+ return false;
1109
+ }
1110
+ const candidates = [plan.selector, ...plan.alternativeSelectors ?? []].filter(
1111
+ (s) => typeof s === "string" && s.length > 0
1112
+ );
1113
+ let lastErr;
1114
+ for (const sel of candidates) {
1115
+ try {
1116
+ switch (plan.type) {
1117
+ case "click":
1118
+ await page.click(sel, { timeout });
1119
+ return false;
1120
+ case "fill":
1121
+ await page.fill(sel, plan.value ?? "", { timeout });
1122
+ return false;
1123
+ case "select":
1124
+ await page.selectOption(sel, plan.value ?? "", { timeout });
1125
+ return false;
1126
+ case "hover":
1127
+ await page.hover(sel, { timeout });
1128
+ return false;
1129
+ default:
1130
+ throw new Error(`Unsupported action type: ${plan.type}`);
1131
+ }
1132
+ } catch (e) {
1133
+ lastErr = e;
1134
+ }
1135
+ }
1136
+ if (allowVision && plan.point && plan.type !== "select" && page.mouse) {
1137
+ const vp = page.viewportSize?.() || { width: 1280, height: 800 };
1138
+ const x = Math.round(plan.point.x / 1e3 * vp.width);
1139
+ const y = Math.round(plan.point.y / 1e3 * vp.height);
1140
+ await page.mouse.click(x, y);
1141
+ if (plan.type === "fill" && plan.value) await page.keyboard.type(plan.value, { delay: 20 });
1142
+ if (plan.type === "hover" && page.mouse.move) await page.mouse.move(x, y);
1143
+ return true;
1144
+ }
1145
+ if (candidates.length === 0) throw new Error(`No selector or vision point returned for action ${plan.type}`);
1146
+ throw lastErr ?? new Error(`No selector worked for ${plan.type} (and vision fallback unavailable)`);
1147
+ }
1148
+ zodToJsonSchema(schema) {
1149
+ return { type: "object", _zodHint: String(schema) };
1150
+ }
1151
+ };
1152
+ function bufferToBase64(buf) {
1153
+ if (typeof Buffer !== "undefined" && buf instanceof Buffer) {
1154
+ return buf.toString("base64");
1155
+ }
1156
+ let binary = "";
1157
+ const bytes = buf;
1158
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
1159
+ const g = globalThis;
1160
+ return g.btoa ? g.btoa(binary) : Buffer.from(binary, "binary").toString("base64");
1161
+ }
1162
+
1163
+ // src/models.ts
1164
+ var AUTOPILOT_MODELS = [
1165
+ // ── Standard (Gemini native Computer Use) ────────────────────────────────
1166
+ "gemini-3.5-flash",
1167
+ // Recommended — fast and cheap. THE DEFAULT.
1168
+ "gemini-3.7-flash",
1169
+ // Newest GA Flash — Google-recommended for computer use
1170
+ "gemini-3.6-flash",
1171
+ // Newest Flash (Computer Use preview)
1172
+ "gemini-default",
1173
+ // Gemini 2.5 Computer Use (legacy)
1174
+ // ── Experimental (Gemini) ────────────────────────────────────────────────
1175
+ "gemini-3-flash-preview",
1176
+ "gemini-3.5-flash-lite",
1177
+ // Harness aliases — resolved server-side to an underlying brain + prompt.
1178
+ "model-a",
1179
+ "model-a1",
1180
+ "model-b",
1181
+ "model-j",
1182
+ "model-k",
1183
+ "model-k37",
1184
+ // ── Anthropic / OpenAI native Computer Use ───────────────────────────────
1185
+ "claude-sonnet-4-6",
1186
+ "claude-opus-4-8",
1187
+ "gpt-5.6-terra",
1188
+ "gpt-5.6-sol",
1189
+ "gpt-5.5",
1190
+ // ── Generic Vision Loop (standard chat models on screenshots) ────────────
1191
+ "claude-fable-5-vision",
1192
+ "claude-opus-5-vision",
1193
+ "claude-sonnet-5-vision",
1194
+ "claude-sonnet-4-6-vision",
1195
+ "gpt-5.6-luna-vision",
1196
+ "gpt-5.4-vision",
1197
+ "gpt-4o-vision",
1198
+ "kimi-k3-vision",
1199
+ // ── Experimental server-side harness engines ─────────────────────────────
1200
+ "modelc",
1201
+ "model-d",
1202
+ "model-d1",
1203
+ "model-e",
1204
+ "model-f",
1205
+ "model-h",
1206
+ "model-i"
1207
+ ];
1208
+ var DEFAULT_AUTOPILOT_MODEL = "gemini-3.5-flash";
1209
+
1210
+ // src/index.ts
1211
+ var Copilot = class {
1212
+ constructor(helpers) {
1213
+ this.helpers = helpers;
1214
+ }
1215
+ /** Execute a natural-language action on a connected Playwright page. */
1216
+ do(page, instruction, opts) {
1217
+ return this.helpers.do(page, instruction, opts);
1218
+ }
1219
+ /** Pull typed data from the page (Zod schema or raw JSON Schema). */
1220
+ read(page, schema) {
1221
+ return this.helpers.read(page, schema);
1222
+ }
1223
+ /** Discover available actions on the page; useful pre-`do` step. */
1224
+ scan(page, hint) {
1225
+ return this.helpers.scan(page, hint);
1226
+ }
1227
+ };
1228
+ var Prompteryx = class {
1229
+ constructor(opts) {
1230
+ this.http = new HttpClient(opts);
1231
+ this.workflows = new WorkflowsResource(this.http);
1232
+ this.executions = new ExecutionsResource(this.http);
1233
+ this.cloudBrowser = new CloudBrowserResource(this.http);
1234
+ this.autopilot = new AutopilotResource(this.http);
1235
+ this.copilot = new Copilot(new CopilotHelpers(this.http));
1236
+ this.schedules = new SchedulesResource(this.http);
1237
+ this.profiles = new ProfilesResource(this.http);
1238
+ this.subscription = new SubscriptionResource(this.http);
1239
+ }
1240
+ };
1241
+ var src_default = Prompteryx;
1242
+ // Annotate the CommonJS export names for ESM import in node:
1243
+ 0 && (module.exports = {
1244
+ AUTOPILOT_MODELS,
1245
+ AuthError,
1246
+ DEFAULT_AUTOPILOT_MODEL,
1247
+ NetworkError,
1248
+ NotFoundError,
1249
+ ParseError,
1250
+ Prompteryx,
1251
+ PrompteryxError,
1252
+ QuotaError,
1253
+ RateLimitError,
1254
+ ServerError,
1255
+ TimeoutError,
1256
+ ValidationError
1257
+ });
1258
+ //# sourceMappingURL=index.js.map