@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/src/errors.ts ADDED
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Typed error hierarchy for the Prompteryx SDK.
3
+ *
4
+ * Every error from the SDK is an instance of `PrompteryxError`. Subclass
5
+ * by HTTP status family so callers can `if (err instanceof QuotaError)`
6
+ * without parsing strings. Network/parse errors get their own classes
7
+ * too so retries can fork on category.
8
+ */
9
+
10
+ /** Base class — every SDK error inherits from this. */
11
+ export class PrompteryxError extends Error {
12
+ public readonly status?: number
13
+ public readonly code?: string
14
+ public readonly requestId?: string
15
+ public readonly raw?: unknown
16
+
17
+ constructor(message: string, opts: { status?: number; code?: string; requestId?: string; raw?: unknown } = {}) {
18
+ super(message)
19
+ this.name = 'PrompteryxError'
20
+ this.status = opts.status
21
+ this.code = opts.code
22
+ this.requestId = opts.requestId
23
+ this.raw = opts.raw
24
+ // Restore prototype chain for `instanceof` checks across realms.
25
+ Object.setPrototypeOf(this, new.target.prototype)
26
+ }
27
+ }
28
+
29
+ /** 401 / 403 — bad or revoked API key, or insufficient scope. */
30
+ export class AuthError extends PrompteryxError {
31
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
32
+ super(message, opts)
33
+ this.name = 'AuthError'
34
+ Object.setPrototypeOf(this, AuthError.prototype)
35
+ }
36
+ }
37
+
38
+ /** 402 — plan allowance exhausted (AI Credits, cloud minutes, etc.). */
39
+ export class QuotaError extends PrompteryxError {
40
+ /** Which resources are exhausted, when known. */
41
+ public readonly resources?: string[]
42
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] & { resources?: string[] } = {}) {
43
+ super(message, opts)
44
+ this.name = 'QuotaError'
45
+ this.resources = opts.resources
46
+ Object.setPrototypeOf(this, QuotaError.prototype)
47
+ }
48
+ }
49
+
50
+ /** 404 — resource doesn't exist (workflow id, execution id, session id). */
51
+ export class NotFoundError extends PrompteryxError {
52
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
53
+ super(message, opts)
54
+ this.name = 'NotFoundError'
55
+ Object.setPrototypeOf(this, NotFoundError.prototype)
56
+ }
57
+ }
58
+
59
+ /** 422 — request body shape was wrong. */
60
+ export class ValidationError extends PrompteryxError {
61
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
62
+ super(message, opts)
63
+ this.name = 'ValidationError'
64
+ Object.setPrototypeOf(this, ValidationError.prototype)
65
+ }
66
+ }
67
+
68
+ /** 429 — rate-limited. Caller can retry after `retryAfterSeconds`. */
69
+ export class RateLimitError extends PrompteryxError {
70
+ public readonly retryAfterSeconds?: number
71
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] & { retryAfterSeconds?: number } = {}) {
72
+ super(message, opts)
73
+ this.name = 'RateLimitError'
74
+ this.retryAfterSeconds = opts.retryAfterSeconds
75
+ Object.setPrototypeOf(this, RateLimitError.prototype)
76
+ }
77
+ }
78
+
79
+ /** 5xx — server-side failure. SDK retries these by default for idempotent ops. */
80
+ export class ServerError extends PrompteryxError {
81
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
82
+ super(message, opts)
83
+ this.name = 'ServerError'
84
+ Object.setPrototypeOf(this, ServerError.prototype)
85
+ }
86
+ }
87
+
88
+ /** Network / DNS / socket / aborted — never reached the server. */
89
+ export class NetworkError extends PrompteryxError {
90
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
91
+ super(message, opts)
92
+ this.name = 'NetworkError'
93
+ Object.setPrototypeOf(this, NetworkError.prototype)
94
+ }
95
+ }
96
+
97
+ /** Response body parse failure (server returned a non-JSON 500 page, etc.). */
98
+ export class ParseError extends PrompteryxError {
99
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
100
+ super(message, opts)
101
+ this.name = 'ParseError'
102
+ Object.setPrototypeOf(this, ParseError.prototype)
103
+ }
104
+ }
105
+
106
+ /** Timed-out waiting for a long-running op (execution polling, agent run). */
107
+ export class TimeoutError extends PrompteryxError {
108
+ constructor(message: string, opts: ConstructorParameters<typeof PrompteryxError>[1] = {}) {
109
+ super(message, opts)
110
+ this.name = 'TimeoutError'
111
+ Object.setPrototypeOf(this, TimeoutError.prototype)
112
+ }
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * @prompteryx/sdk
3
+ *
4
+ * Official TypeScript SDK for the Prompteryx platform.
5
+ *
6
+ * Two AI surfaces:
7
+ *
8
+ * • **Copilot** — helps with ONE step you describe in plain English.
9
+ * Your code drives Playwright; copilot just figures out the
10
+ * selector to click / the data to pull / what's on the page.
11
+ *
12
+ * ```ts
13
+ * await px.copilot.do(page, 'click the Sign up button')
14
+ * const product = await px.copilot.read(page, productSchema)
15
+ * const actions = await px.copilot.scan(page, 'checkout buttons')
16
+ * ```
17
+ *
18
+ * • **Autopilot** — runs an autonomous multi-step task end-to-end
19
+ * with no per-action involvement from you.
20
+ *
21
+ * ```ts
22
+ * const result = await px.autopilot.run({
23
+ * goal: 'Apply for the Senior Engineer role at OpenAI',
24
+ * saveAsWorkflow: true, // permanent zero-AI-cost replay
25
+ * })
26
+ * ```
27
+ *
28
+ * Plus workflows, executions, cloud browser sessions / fetch / search,
29
+ * schedules, profiles, and subscription telemetry.
30
+ *
31
+ * TWO KEY FAMILIES:
32
+ * • `apiKey` (`px_live_…`) — the platform API key; sent as
33
+ * `Authorization: Bearer`. 60 requests/minute, 10,000/day.
34
+ * • `cloudBrowserKey` (`pcb_live_…`) — the Cloud Browser key; required
35
+ * only for `px.cloudBrowser.*`, sent as `x-api-key`.
36
+ *
37
+ * Quick start:
38
+ *
39
+ * ```ts
40
+ * import { Prompteryx } from '@prompteryx/sdk'
41
+ * import { chromium } from 'playwright-core'
42
+ *
43
+ * const px = new Prompteryx({
44
+ * apiKey: process.env.PROMPTERYX_API_KEY!, // px_live_…
45
+ * cloudBrowserKey: process.env.PROMPTERYX_CLOUD_BROWSER_KEY, // pcb_live_…
46
+ * })
47
+ *
48
+ * // 1. Cloud browser session
49
+ * const session = await px.cloudBrowser.sessions.create({
50
+ * useProxy: true, proxyLocation: 'us',
51
+ * })
52
+ * const browser = await chromium.connectOverCDP(session.connectUrl)
53
+ * const page = browser.contexts()[0].pages()[0]
54
+ *
55
+ * // 2. Copilot on top of Playwright
56
+ * await page.goto('https://news.ycombinator.com')
57
+ * const top = await px.copilot.read(page, z.object({
58
+ * stories: z.array(z.object({ title: z.string(), url: z.string() })),
59
+ * }))
60
+ * ```
61
+ *
62
+ * See PROMPTERYX_SDK.md in docs/ for the full design + reference.
63
+ */
64
+
65
+ import { HttpClient } from './client'
66
+ import { AutopilotResource } from './resources/autopilot'
67
+ import { CloudBrowserResource } from './resources/cloudBrowser'
68
+ import { ExecutionsResource } from './resources/executions'
69
+ import { ProfilesResource } from './resources/profiles'
70
+ import { SchedulesResource } from './resources/schedules'
71
+ import { SubscriptionResource } from './resources/subscription'
72
+ import { WorkflowsResource } from './resources/workflows'
73
+ import { CopilotHelpers, type PageLike } from './page'
74
+ import type {
75
+ CopilotDoResult,
76
+ DiscoveredAction,
77
+ PrompteryxClientOptions,
78
+ } from './types'
79
+
80
+ // Public re-exports
81
+ export * from './errors'
82
+ export * from './types'
83
+ export * from './models'
84
+ export type { PageLike }
85
+
86
+ // v0.4.0 (2026-09-03): the connectHub, customNodes, templates, apiKeys and
87
+ // recordings namespaces plus subscription.usage() were REMOVED from the
88
+ // public surface — the routes they target don't exist on the live API (or,
89
+ // for api-keys, can never accept API-key auth). The source files remain in
90
+ // src/resources/ with dated NOT-SHIPPED headers for a future release.
91
+
92
+ /**
93
+ * Copilot surface — bundles the three on-page primitives behind a
94
+ * single namespace so calling code reads as
95
+ * `px.copilot.do(...)` / `px.copilot.read(...)` / `px.copilot.scan(...)`.
96
+ * Used internally by the Prompteryx class.
97
+ */
98
+ class Copilot {
99
+ constructor(private readonly helpers: CopilotHelpers) {}
100
+ /** Execute a natural-language action on a connected Playwright page. */
101
+ do(page: PageLike, instruction: string, opts?: { timeout?: number }): Promise<CopilotDoResult> {
102
+ return this.helpers.do(page, instruction, opts)
103
+ }
104
+ /** Pull typed data from the page (Zod schema or raw JSON Schema). */
105
+ read<T>(page: PageLike, schema: { parse(input: unknown): T } | { jsonSchema: unknown }): Promise<T> {
106
+ return this.helpers.read<T>(page, schema as any)
107
+ }
108
+ /** Discover available actions on the page; useful pre-`do` step. */
109
+ scan(page: PageLike, hint?: string): Promise<DiscoveredAction[]> {
110
+ return this.helpers.scan(page, hint)
111
+ }
112
+ }
113
+
114
+ export class Prompteryx {
115
+ private readonly http: HttpClient
116
+
117
+ // Core resources — the verified live surface.
118
+ public readonly workflows: WorkflowsResource
119
+ public readonly executions: ExecutionsResource
120
+ public readonly cloudBrowser: CloudBrowserResource
121
+ public readonly autopilot: AutopilotResource
122
+ public readonly copilot: Copilot
123
+ public readonly schedules: SchedulesResource
124
+ public readonly profiles: ProfilesResource
125
+ public readonly subscription: SubscriptionResource
126
+
127
+ constructor(opts: PrompteryxClientOptions) {
128
+ this.http = new HttpClient(opts)
129
+ this.workflows = new WorkflowsResource(this.http)
130
+ this.executions = new ExecutionsResource(this.http)
131
+ this.cloudBrowser = new CloudBrowserResource(this.http)
132
+ this.autopilot = new AutopilotResource(this.http)
133
+ this.copilot = new Copilot(new CopilotHelpers(this.http))
134
+ this.schedules = new SchedulesResource(this.http)
135
+ this.profiles = new ProfilesResource(this.http)
136
+ this.subscription = new SubscriptionResource(this.http)
137
+ }
138
+ }
139
+
140
+ export default Prompteryx
package/src/models.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Browser-agent model catalog — the ids `px.autopilot.run({ model })` accepts.
3
+ *
4
+ * ⚠️ MUST MIRROR `components/unified-chat-v2/browser-models.ts`
5
+ * `V2_BROWSER_MODELS` in the platform repo — that file is the canonical
6
+ * catalog the server validates against (an unknown id hard-400s with
7
+ * `UNSUPPORTED_MODEL` under strict validation). When that list changes,
8
+ * regenerate this one. Last synced: 2026-09-03 — 32 ids.
9
+ *
10
+ * The type stays open (`| (string & {})`) so a model the server adds
11
+ * tomorrow works without an SDK release, while editors still autocomplete
12
+ * the known catalog.
13
+ */
14
+
15
+ export const AUTOPILOT_MODELS = [
16
+ // ── Standard (Gemini native Computer Use) ────────────────────────────────
17
+ 'gemini-3.5-flash', // Recommended — fast and cheap. THE DEFAULT.
18
+ 'gemini-3.7-flash', // Newest GA Flash — Google-recommended for computer use
19
+ 'gemini-3.6-flash', // Newest Flash (Computer Use preview)
20
+ 'gemini-default', // Gemini 2.5 Computer Use (legacy)
21
+ // ── Experimental (Gemini) ────────────────────────────────────────────────
22
+ 'gemini-3-flash-preview',
23
+ 'gemini-3.5-flash-lite',
24
+ // Harness aliases — resolved server-side to an underlying brain + prompt.
25
+ 'model-a',
26
+ 'model-a1',
27
+ 'model-b',
28
+ 'model-j',
29
+ 'model-k',
30
+ 'model-k37',
31
+ // ── Anthropic / OpenAI native Computer Use ───────────────────────────────
32
+ 'claude-sonnet-4-6',
33
+ 'claude-opus-4-8',
34
+ 'gpt-5.6-terra',
35
+ 'gpt-5.6-sol',
36
+ 'gpt-5.5',
37
+ // ── Generic Vision Loop (standard chat models on screenshots) ────────────
38
+ 'claude-fable-5-vision',
39
+ 'claude-opus-5-vision',
40
+ 'claude-sonnet-5-vision',
41
+ 'claude-sonnet-4-6-vision',
42
+ 'gpt-5.6-luna-vision',
43
+ 'gpt-5.4-vision',
44
+ 'gpt-4o-vision',
45
+ 'kimi-k3-vision',
46
+ // ── Experimental server-side harness engines ─────────────────────────────
47
+ 'modelc',
48
+ 'model-d',
49
+ 'model-d1',
50
+ 'model-e',
51
+ 'model-f',
52
+ 'model-h',
53
+ 'model-i',
54
+ ] as const
55
+
56
+ /** A model id from the known catalog. */
57
+ export type AutopilotModelId = (typeof AUTOPILOT_MODELS)[number]
58
+
59
+ /** Open union: known ids autocomplete, forward-compatible strings still pass. */
60
+ export type AutopilotModel = AutopilotModelId | (string & {})
61
+
62
+ /** The platform default when `model` is omitted. */
63
+ export const DEFAULT_AUTOPILOT_MODEL = 'gemini-3.5-flash'
package/src/page.ts ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Copilot primitives — `do`, `read`, `scan` — operating on a Playwright
3
+ * page.
4
+ *
5
+ * Two namespaces in the Prompteryx SDK:
6
+ * • Copilot → SDK helps with ONE step you describe in plain English.
7
+ * Your code drives Playwright; copilot just figures out
8
+ * which selector to click / what data to pull / what's
9
+ * discoverable on the current page.
10
+ * • Autopilot → SDK runs an autonomous multi-step task with no per-
11
+ * action involvement from you. See ./resources/autopilot.ts.
12
+ *
13
+ * The copilot primitives:
14
+ * • `do(page, instruction)` — execute a single natural-language
15
+ * action ("click the Sign up button", "fill the email field with
16
+ * hello@example.com"). Returns a structured result describing
17
+ * what was done.
18
+ * • `read(page, schema)` — pull typed data from the page. Pass a
19
+ * Zod schema or a raw JSON Schema; the SDK returns the populated
20
+ * object validated against your schema.
21
+ * • `scan(page, hint?)` — list discoverable actions on the page.
22
+ * Useful as a pre-step to `do()` for resilient automations:
23
+ * scan → pick the action whose description matches your intent →
24
+ * do() against it.
25
+ *
26
+ * Architecturally the SDK never drives the browser server-side. The
27
+ * server returns a structured plan and the SDK executes it locally
28
+ * against your Playwright page, so your trace, debugger, and any
29
+ * custom event handlers continue to work normally.
30
+ */
31
+
32
+ import type { HttpClient } from './client'
33
+ import { ValidationError } from './errors'
34
+ import type { CopilotDoResult, DiscoveredAction } from './types'
35
+
36
+ /** Minimal Page surface — anything satisfying this works (Playwright's
37
+ * Page does, by structural typing, without an explicit import). */
38
+ export interface PageLike {
39
+ url(): string
40
+ title(): Promise<string>
41
+ screenshot(opts?: { type?: 'png' | 'jpeg'; quality?: number; fullPage?: boolean }): Promise<Buffer | Uint8Array>
42
+ evaluate<T>(fn: (...args: unknown[]) => T): Promise<T>
43
+ click(selector: string, opts?: { timeout?: number }): Promise<void>
44
+ fill(selector: string, value: string, opts?: { timeout?: number }): Promise<void>
45
+ selectOption(selector: string, value: string | string[], opts?: { timeout?: number }): Promise<unknown>
46
+ goto(url: string, opts?: { timeout?: number }): Promise<unknown>
47
+ hover(selector: string, opts?: { timeout?: number }): Promise<void>
48
+ keyboard: { press(key: string): Promise<void>; type(text: string, opts?: { delay?: number }): Promise<void> }
49
+ // Used by the AI-vision fallback (when all ranked selectors fail). Playwright's
50
+ // Page provides both; optional so a minimal page can still satisfy PageLike.
51
+ mouse?: { click(x: number, y: number): Promise<void>; move?(x: number, y: number): Promise<void> }
52
+ viewportSize?(): { width: number; height: number } | null
53
+ locator(selector: string): {
54
+ first(): { isVisible(opts?: { timeout?: number }): Promise<boolean>; textContent(opts?: { timeout?: number }): Promise<string | null> }
55
+ }
56
+ }
57
+
58
+ interface ZodLikeSchema<T> {
59
+ parse(input: unknown): T
60
+ _def?: unknown
61
+ }
62
+
63
+ export class CopilotHelpers {
64
+ constructor(private readonly http: HttpClient) {}
65
+
66
+ /**
67
+ * Execute a single natural-language action against the page.
68
+ *
69
+ * ```ts
70
+ * await px.copilot.do(page, 'click the Sign up button')
71
+ * await px.copilot.do(page, 'fill the email field with hello@example.com')
72
+ * ```
73
+ *
74
+ * Internally: snapshot the page → POST `/api/v1/copilot/do` → server
75
+ * returns a structured action plan (ranked selector + alternatives +
76
+ * value + a normalised vision point) → SDK executes locally, trying the
77
+ * ranked selectors in order, and ONLY if every selector fails, falling
78
+ * back to an AI-vision coordinate click. Costs 1 AI Credit per call.
79
+ *
80
+ * This is the key resilience advantage over a pure-LLM `act()`: the cheap,
81
+ * deterministic ranked selectors are tried first (no flakiness, no re-asking
82
+ * the model); the vision fallback is a safety net, not the default path. Pass
83
+ * `{ visionFallback: false }` to disable the fallback (selectors-only).
84
+ */
85
+ async do(page: PageLike, instruction: string, opts: { timeout?: number; visionFallback?: boolean } = {}): Promise<CopilotDoResult> {
86
+ const start = Date.now()
87
+ const snapshot = await this.snapshotPage(page)
88
+ const plan = await this.http.request<{
89
+ type: 'click' | 'fill' | 'select' | 'hover' | 'press_key' | 'goto'
90
+ selector?: string
91
+ alternativeSelectors?: string[]
92
+ point?: { x: number; y: number }
93
+ value?: string
94
+ url?: string
95
+ description: string
96
+ }>('/api/v1/copilot/do', {
97
+ method: 'POST',
98
+ body: { instruction, snapshot },
99
+ timeoutMs: 45_000,
100
+ })
101
+ const allowVision = opts.visionFallback !== false
102
+ try {
103
+ const usedVisionFallback = await this.runAction(page, plan, opts.timeout ?? 15_000, allowVision)
104
+ return {
105
+ success: true,
106
+ action: plan.description,
107
+ selector: plan.selector,
108
+ usedVisionFallback,
109
+ durationMs: Date.now() - start,
110
+ }
111
+ } catch (err) {
112
+ return {
113
+ success: false,
114
+ action: plan.description,
115
+ selector: plan.selector,
116
+ durationMs: Date.now() - start,
117
+ error: err instanceof Error ? err.message : String(err),
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Pull typed structured data from the page conforming to a schema.
124
+ *
125
+ * ```ts
126
+ * import { z } from 'zod'
127
+ * const product = await px.copilot.read(page, z.object({
128
+ * name: z.string(),
129
+ * pricePerMonth: z.number(),
130
+ * features: z.array(z.string()),
131
+ * }))
132
+ * // product is fully typed; ValidationError is thrown if the model
133
+ * // returns data that doesn't match the schema.
134
+ * ```
135
+ *
136
+ * Accepts a Zod schema (preferred — gives you compile-time types)
137
+ * OR a raw JSON Schema via `{ jsonSchema: ... }` if you don't want
138
+ * a `zod` peer dep.
139
+ */
140
+ async read<T>(page: PageLike, schema: ZodLikeSchema<T> | { jsonSchema: unknown }): Promise<T> {
141
+ const snapshot = await this.snapshotPage(page)
142
+ const jsonSchema = 'jsonSchema' in schema
143
+ ? (schema as { jsonSchema: unknown }).jsonSchema
144
+ : this.zodToJsonSchema(schema as ZodLikeSchema<T>)
145
+ const raw = await this.http.request<{ data: unknown }>(
146
+ '/api/v1/copilot/read',
147
+ { method: 'POST', body: { snapshot, jsonSchema }, timeoutMs: 60_000 },
148
+ )
149
+ if ('parse' in (schema as object) && typeof (schema as ZodLikeSchema<T>).parse === 'function') {
150
+ try {
151
+ return (schema as ZodLikeSchema<T>).parse(raw.data)
152
+ } catch (e) {
153
+ throw new ValidationError(
154
+ `Extracted data didn't match the schema: ${e instanceof Error ? e.message : String(e)}`,
155
+ { raw: raw.data },
156
+ )
157
+ }
158
+ }
159
+ return raw.data as T
160
+ }
161
+
162
+ /**
163
+ * Scan the page for available actions. Returns a ranked list of
164
+ * actions a user / agent could take next, with selectors + multi-
165
+ * option fallbacks + human-readable descriptions.
166
+ *
167
+ * Useful as a pre-step to `do()` for resilient automations:
168
+ * const actions = await px.copilot.scan(page, 'sign up flow')
169
+ * const target = actions.find(a => a.description.includes('Sign up'))
170
+ * if (target) await px.copilot.do(page, target.example ?? `click ${target.description}`)
171
+ */
172
+ async scan(page: PageLike, hint?: string): Promise<DiscoveredAction[]> {
173
+ const snapshot = await this.snapshotPage(page)
174
+ const res = await this.http.request<{ actions: DiscoveredAction[] }>(
175
+ '/api/v1/copilot/scan',
176
+ { method: 'POST', body: { snapshot, hint }, timeoutMs: 45_000 },
177
+ )
178
+ return res.actions ?? []
179
+ }
180
+
181
+ // ── internals ────────────────────────────────────────────────────────
182
+
183
+ private async snapshotPage(page: PageLike): Promise<{ url: string; title: string; screenshot: string }> {
184
+ const buf = await page.screenshot({ type: 'jpeg', quality: 60, fullPage: false })
185
+ const screenshot = bufferToBase64(buf)
186
+ return {
187
+ url: page.url(),
188
+ title: await page.title().catch(() => ''),
189
+ screenshot,
190
+ }
191
+ }
192
+
193
+ /** Execute the plan. Returns true if the AI-vision fallback was used. */
194
+ private async runAction(
195
+ page: PageLike,
196
+ plan: {
197
+ type: 'click' | 'fill' | 'select' | 'hover' | 'press_key' | 'goto'
198
+ selector?: string
199
+ alternativeSelectors?: string[]
200
+ point?: { x: number; y: number }
201
+ value?: string
202
+ url?: string
203
+ },
204
+ timeout: number,
205
+ allowVision: boolean,
206
+ ): Promise<boolean> {
207
+ if (plan.type === 'press_key' && plan.value) {
208
+ await page.keyboard.press(plan.value)
209
+ return false
210
+ }
211
+ if (plan.type === 'goto' && plan.url) {
212
+ await page.goto(plan.url, { timeout })
213
+ return false
214
+ }
215
+ const candidates = [plan.selector, ...(plan.alternativeSelectors ?? [])].filter(
216
+ (s): s is string => typeof s === 'string' && s.length > 0,
217
+ )
218
+ // 1) Try the ranked selectors in order (cheap + deterministic).
219
+ let lastErr: unknown
220
+ for (const sel of candidates) {
221
+ try {
222
+ switch (plan.type) {
223
+ case 'click': await page.click(sel, { timeout }); return false
224
+ case 'fill': await page.fill(sel, plan.value ?? '', { timeout }); return false
225
+ case 'select': await page.selectOption(sel, plan.value ?? '', { timeout }); return false
226
+ case 'hover': await page.hover(sel, { timeout }); return false
227
+ default: throw new Error(`Unsupported action type: ${plan.type}`)
228
+ }
229
+ } catch (e) {
230
+ lastErr = e
231
+ }
232
+ }
233
+ // 2) AI-vision fallback: every selector failed → click the model's
234
+ // normalised point. <select> can't be operated by a coordinate, so it's
235
+ // selectors-only. Requires page.mouse (Playwright provides it).
236
+ if (allowVision && plan.point && plan.type !== 'select' && page.mouse) {
237
+ const vp = page.viewportSize?.() || { width: 1280, height: 800 }
238
+ const x = Math.round((plan.point.x / 1000) * vp.width)
239
+ const y = Math.round((plan.point.y / 1000) * vp.height)
240
+ await page.mouse.click(x, y)
241
+ if (plan.type === 'fill' && plan.value) await page.keyboard.type(plan.value, { delay: 20 })
242
+ // hover via mouse.move when available, else the click above is close enough.
243
+ if (plan.type === 'hover' && page.mouse.move) await page.mouse.move(x, y)
244
+ return true
245
+ }
246
+ if (candidates.length === 0) throw new Error(`No selector or vision point returned for action ${plan.type}`)
247
+ throw lastErr ?? new Error(`No selector worked for ${plan.type} (and vision fallback unavailable)`)
248
+ }
249
+
250
+ private zodToJsonSchema<T>(schema: ZodLikeSchema<T>): unknown {
251
+ return { type: 'object', _zodHint: String(schema) }
252
+ }
253
+ }
254
+
255
+ function bufferToBase64(buf: Buffer | Uint8Array): string {
256
+ if (typeof Buffer !== 'undefined' && buf instanceof Buffer) {
257
+ return buf.toString('base64')
258
+ }
259
+ let binary = ''
260
+ const bytes = buf as Uint8Array
261
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i])
262
+ const g = globalThis as any
263
+ return g.btoa ? g.btoa(binary) : Buffer.from(binary, 'binary').toString('base64')
264
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * ⚠️ NOT SHIPPED in @prompteryx/sdk 0.4.0 (2026-09-03) — not exported from
3
+ * src/index.ts. The live /api/v1/api-keys route requires a Firebase session
4
+ * ID token — an API key can NEVER manage API keys (401 by design), so this
5
+ * resource cannot work from an SDK authenticated with an API key. Verified
6
+ * in docs/API_SDK_DEVELOPER_PLAN_GOLIVE_AUDIT.md. Kept as source in case a
7
+ * session-token auth mode ships; do not re-export before then.
8
+ *
9
+ * `px.apiKeys.*` — programmatically manage your own API keys.
10
+ *
11
+ * Useful for orgs that rotate keys via CI, or for embedding "create a
12
+ * temporary key for this sub-account" flows.
13
+ */
14
+
15
+ import type { HttpClient } from '../client'
16
+ import type { ApiKeySummary } from '../types'
17
+
18
+ export class ApiKeysResource {
19
+ constructor(private readonly http: HttpClient) {}
20
+
21
+ async list(): Promise<ApiKeySummary[]> {
22
+ const res = await this.http.request<{ data: { apiKeys: ApiKeySummary[] } }>(
23
+ '/api/v1/api-keys',
24
+ )
25
+ return res.data?.apiKeys ?? []
26
+ }
27
+
28
+ /**
29
+ * Create a new API key. The full key is returned ONCE in `apiKey` —
30
+ * store it immediately, you can never see it again.
31
+ */
32
+ async create(opts: {
33
+ name: string
34
+ /** Days until the key expires. `null` = never. Default 90. */
35
+ expiresInDays?: number | null
36
+ }): Promise<{ apiKey: string; keyId: string; keyPrefix: string }> {
37
+ const res = await this.http.request<{ data: { apiKey: string; keyId: string; keyPrefix: string } }>(
38
+ '/api/v1/api-keys',
39
+ {
40
+ method: 'POST',
41
+ body: { name: opts.name, expiresInDays: opts.expiresInDays ?? 90 },
42
+ },
43
+ )
44
+ return res.data
45
+ }
46
+
47
+ /** Revoke a key by id. Immediate; no soft delete. */
48
+ async delete(keyId: string): Promise<{ ok: true }> {
49
+ return this.http.request(`/api/v1/api-keys/${encodeURIComponent(keyId)}`, {
50
+ method: 'DELETE',
51
+ })
52
+ }
53
+ }