@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/LICENSE +21 -0
- package/README.md +336 -0
- package/dist/chunk-ODLNHNQT.mjs +272 -0
- package/dist/chunk-ODLNHNQT.mjs.map +1 -0
- package/dist/index.d.mts +541 -0
- package/dist/index.d.ts +541 -0
- package/dist/index.js +1258 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +975 -0
- package/dist/index.mjs.map +1 -0
- package/dist/page-8LsjwpEo.d.mts +879 -0
- package/dist/page-8LsjwpEo.d.ts +879 -0
- package/dist/page.d.mts +1 -0
- package/dist/page.d.ts +1 -0
- package/dist/page.js +230 -0
- package/dist/page.js.map +1 -0
- package/dist/page.mjs +7 -0
- package/dist/page.mjs.map +1 -0
- package/package.json +86 -0
- package/src/client.ts +286 -0
- package/src/errors.ts +113 -0
- package/src/index.ts +140 -0
- package/src/models.ts +63 -0
- package/src/page.ts +264 -0
- package/src/resources/apiKeys.ts +53 -0
- package/src/resources/autopilot.ts +385 -0
- package/src/resources/cloudBrowser.ts +197 -0
- package/src/resources/connectHub.ts +93 -0
- package/src/resources/customNodes.ts +62 -0
- package/src/resources/executions.ts +84 -0
- package/src/resources/profiles.ts +48 -0
- package/src/resources/recordings.ts +32 -0
- package/src/resources/schedules.ts +49 -0
- package/src/resources/subscription.ts +27 -0
- package/src/resources/templates.ts +46 -0
- package/src/resources/workflows.ts +84 -0
- package/src/types.ts +680 -0
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
|
+
}
|