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