@oya-ai/browser 1.0.86 → 1.0.87

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/README.md CHANGED
@@ -150,6 +150,28 @@ Use `{{name}}` references in prompts instead of interpolating values into the pr
150
150
 
151
151
  For prompt runs, pass credentials in `secrets`. For replay, pass all variables in `data`: the saved playbook tracks which variables are secret, including during healing. Placeholder-based inputs remain variables in the saved flow. This is not a blanket redaction guarantee for page content, screenshots, agent replies, or application logs; inspect exported code before sharing it.
152
152
 
153
+ ### Files
154
+
155
+ `file()` puts a file in `data`. The agent attaches it with its upload tool: hand it the element id of whatever you can see — the "Choose file" button, the drop zone, the field itself — and the real `<input type="file">` is found from there, including the hidden ones most upload widgets use.
156
+
157
+ ```js
158
+ import { Oya, file } from "@oya-ai/browser";
159
+
160
+ await browser.ask("Attach my resume to the application and submit it", {
161
+ data: { name: "Ada Lovelace", resume: await file("./cv.pdf") },
162
+ });
163
+ ```
164
+
165
+ A string argument is a path on disk (Node only); a `Blob`, a `File`, or a `Uint8Array` works anywhere. `name` sets the filename the site sees and `type` overrides the MIME guessed from the extension. The ceiling is 10MB per file, and the bytes travel inline with the run — nothing is stored server-side after it ends.
166
+
167
+ Files work in `data` for `ask()`, `submit()`, and `play()`; `secrets` rejects them, because a file is never typed through a placeholder. A run recorded with `toPlaybook()` keeps the upload as a variable, so the replay takes a different file:
168
+
169
+ ```js
170
+ await browser.play("job-application", { name: "Ada Lovelace", resume: await file("./other.pdf") });
171
+ ```
172
+
173
+ The generated Playwright module calls `setInputFiles`, where the same variable is a plain path rather than a `file()` value.
174
+
153
175
  ### Review a repaired playbook
154
176
 
155
177
  Replay normally runs recorded steps without an LLM. With `autoHeal: true` (the default), a broken step can hand over to the agent, which saves a repair as `<name>:draft`. Promotion replaces the saved playbook with that draft.
@@ -179,12 +201,28 @@ const modelKey = process.env.GEMINI_API_KEY;
179
201
  if (!modelKey) throw new Error("Set GEMINI_API_KEY first.");
180
202
  const oya = new Oya();
181
203
  await oya.config.set({
182
- llm_provider: "gemini", // "openai" | "anthropic" | "gemini" | "vertex"
204
+ llm_provider: "gemini", // LlmProvider: "openai" | "anthropic" | "gemini" | "vertex"
183
205
  openai_api_key: modelKey, // Shared field name for every supported provider.
184
206
  // chat_model: process.env.OYA_CHAT_MODEL, // Optional provider model override.
185
207
  });
186
208
  ```
187
209
 
210
+ `config.set` takes `ConfigUpdate` and `config.get()` returns `Config`, so an editor offers the valid providers and a typo fails to compile rather than silently falling back:
211
+
212
+ ```ts
213
+ import { Oya, type LlmProvider } from "@oya-ai/browser";
214
+
215
+ await oya.config.set({ llm_provider: "vertx" });
216
+ // ~~~~~~~ Type '"vertx"' is not assignable to type
217
+ // 'LlmProvider'. Did you mean '"vertex"'?
218
+
219
+ const provider: LlmProvider = "vertex"; // for your own config plumbing
220
+ const { effective } = await oya.config.get();
221
+ console.log(effective.baseUrl, effective.model, effective.hasLlmKey);
222
+ ```
223
+
224
+ `browser_provider` is typed as `Provider` and `captcha_solver` as `CaptchaSolver` the same way. The server enforces the same sets, so a non-TypeScript caller gets a 400 listing the valid values instead of a silent fallback. Pass `null` to clear a field.
225
+
188
226
  ### Gemini Enterprise (ex-Vertex AI)
189
227
 
190
228
  `llm_provider: "vertex"` targets express mode, whose API keys work against a global endpoint with no GCP project or location:
package/dist/index.cjs CHANGED
@@ -21,10 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Browser: () => Browser,
24
+ MAX_FILE_BYTES: () => MAX_FILE_BYTES,
24
25
  Oya: () => Oya,
25
26
  OyaError: () => OyaError,
26
27
  Run: () => Run,
27
- default: () => index_default
28
+ default: () => index_default,
29
+ file: () => file
28
30
  });
29
31
  module.exports = __toCommonJS(index_exports);
30
32
 
@@ -376,6 +378,63 @@ var Run = class {
376
378
  }
377
379
  };
378
380
 
381
+ // src/file.ts
382
+ var MAX_FILE_BYTES = 10 * 1024 * 1024;
383
+ var NODE_FS = "node:fs/promises";
384
+ var MIME = {
385
+ pdf: "application/pdf",
386
+ png: "image/png",
387
+ jpg: "image/jpeg",
388
+ jpeg: "image/jpeg",
389
+ gif: "image/gif",
390
+ webp: "image/webp",
391
+ svg: "image/svg+xml",
392
+ heic: "image/heic",
393
+ txt: "text/plain",
394
+ csv: "text/csv",
395
+ json: "application/json",
396
+ xml: "application/xml",
397
+ html: "text/html",
398
+ doc: "application/msword",
399
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
400
+ xls: "application/vnd.ms-excel",
401
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
402
+ ppt: "application/vnd.ms-powerpoint",
403
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
404
+ zip: "application/zip"
405
+ };
406
+ var base64 = (bytes) => {
407
+ const buffer = globalThis.Buffer;
408
+ if (buffer) return buffer.from(bytes).toString("base64");
409
+ let binary = "";
410
+ for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
411
+ return btoa(binary);
412
+ };
413
+ async function file(source, options = {}) {
414
+ let bytes;
415
+ let name = options.name;
416
+ if (typeof source === "string") {
417
+ const fs = await import(
418
+ /* webpackIgnore: true */
419
+ /* @vite-ignore */
420
+ NODE_FS
421
+ );
422
+ bytes = new Uint8Array(await fs.readFile(source));
423
+ name ||= source.split(/[\\/]/).pop() || "file";
424
+ } else if (source instanceof Uint8Array) {
425
+ bytes = source;
426
+ } else {
427
+ bytes = new Uint8Array(await source.arrayBuffer());
428
+ name ||= source.name;
429
+ }
430
+ name ||= "file";
431
+ if (bytes.length > MAX_FILE_BYTES) {
432
+ throw new Error(`${name} is ${Math.round(bytes.length / 1024 / 1024)}MB; the limit for a task file is ${MAX_FILE_BYTES / 1024 / 1024}MB.`);
433
+ }
434
+ const ext = name.includes(".") ? name.split(".").pop().toLowerCase() : "";
435
+ return { file: name, type: options.type || MIME[ext] || "application/octet-stream", b64: base64(bytes) };
436
+ }
437
+
379
438
  // src/index.ts
380
439
  var DEFAULT_BASE_URL = "https://browser.getoya.ai";
381
440
  var READY_POLL_MS = 2e3;
@@ -506,10 +565,11 @@ var Oya = class {
506
565
  *
507
566
  * Bring your own LLM key (it pays for its own tokens, so no hourly chat quota applies):
508
567
  * await oya.config.set({ llm_provider: 'gemini', openai_api_key: process.env.GEMINI_API_KEY });
509
- * `llm_provider` is 'openai' | 'anthropic' | 'gemini' | 'vertex'; `chat_model` overrides
510
- * its default model. 'vertex' is Gemini Enterprise (ex-Vertex AI) in express mode, which
511
- * needs no GCP project; for a project-scoped endpoint, set `openai_base_url` to
512
- * `.../endpoints/openapi` and pass an OAuth access token as `openai_api_key`.
568
+ * `llm_provider` is the {@link LlmProvider} union, so an editor offers the choices and a
569
+ * typo is a compile error; `chat_model` overrides its default model. 'vertex' is Gemini
570
+ * Enterprise (ex-Vertex AI) in express mode, which needs no GCP project; for a
571
+ * project-scoped endpoint, set `openai_base_url` to `.../endpoints/openapi` and pass an
572
+ * OAuth access token as `openai_api_key`. See {@link ConfigUpdate} for every field.
513
573
  */
514
574
  config = {
515
575
  get: () => this.http.request("GET", "/api/config"),
@@ -540,7 +600,9 @@ var index_default = Oya;
540
600
  // Annotate the CommonJS export names for ESM import in node:
541
601
  0 && (module.exports = {
542
602
  Browser,
603
+ MAX_FILE_BYTES,
543
604
  Oya,
544
605
  OyaError,
545
- Run
606
+ Run,
607
+ file
546
608
  });
package/dist/index.d.cts CHANGED
@@ -9,6 +9,60 @@ declare class Http {
9
9
  }
10
10
 
11
11
  /** Everything the API returns or accepts, in one place. */
12
+ /** Which model drives `ask()` and the chat API. */
13
+ type LlmProvider = 'openai' | 'anthropic'
14
+ /** Gemini via AI Studio. */
15
+ | 'gemini'
16
+ /** Gemini Enterprise, ex-Vertex AI. Express mode by default; set `openai_base_url` to a
17
+ * project-scoped `.../endpoints/openapi` endpoint to use an enterprise project. */
18
+ | 'vertex';
19
+ /** Empty disables solving. */
20
+ type CaptchaSolver = 'capsolver' | '2captcha' | '';
21
+ /**
22
+ * What a key can configure. Mirrors the server's field allowlist exactly: a field not
23
+ * listed here is ignored rather than stored, so the type is the whole surface.
24
+ * `null` clears a field and falls back to the deployment default.
25
+ */
26
+ interface ConfigUpdate {
27
+ llm_provider?: LlmProvider | null;
28
+ /** The credential for whichever `llm_provider` is set — the field name is shared. */
29
+ openai_api_key?: string | null;
30
+ /** Only honoured alongside this key's own `openai_api_key`. */
31
+ openai_base_url?: string | null;
32
+ /** Overrides the provider's default model. */
33
+ chat_model?: string | null;
34
+ browser_provider?: Provider | null;
35
+ anchor_api_key?: string | null;
36
+ browserbase_api_key?: string | null;
37
+ browserbase_project_id?: string | null;
38
+ steel_api_key?: string | null;
39
+ browseruse_api_key?: string | null;
40
+ cdp_ws_url?: string | null;
41
+ captcha_solver?: CaptchaSolver | null;
42
+ captcha_api_key?: string | null;
43
+ onboarded?: string | null;
44
+ }
45
+ /** What `config.get()` returns. Secrets read back masked, never in full. */
46
+ interface Config extends Omit<ConfigUpdate, 'llm_provider' | 'browser_provider' | 'captcha_solver'> {
47
+ llm_provider?: LlmProvider | '';
48
+ browser_provider?: Provider | '';
49
+ captcha_solver?: CaptchaSolver;
50
+ /** What this key would actually use right now, deployment defaults included. */
51
+ effective: {
52
+ baseUrl: string;
53
+ model: string;
54
+ hasLlmKey: boolean;
55
+ };
56
+ /** True when the LLM key in play belongs to the deployment, not this key. */
57
+ inherited: boolean;
58
+ has_openai_key: boolean;
59
+ providers: Array<{
60
+ id: Provider;
61
+ label: string;
62
+ needs: string[];
63
+ configured: boolean;
64
+ }>;
65
+ }
12
66
  /** Where a browser comes from. Configuration, not something a caller must know. */
13
67
  type Provider = 'oya-cloud' | 'oya-selfhosted' | 'browseruse' | 'browserbase' | 'steel' | 'anchor' | 'cdp';
14
68
  interface StartOptions {
@@ -83,12 +137,28 @@ interface PlaybookSummary extends Playbook {
83
137
  healedFrom: number;
84
138
  }) | null;
85
139
  }
140
+ /**
141
+ * A file attached to a task value. Build it with `file()`, never by hand. Only `data`
142
+ * takes one: a file is not typed through a placeholder, so `secrets` has nothing to hide
143
+ * and rejects it.
144
+ */
145
+ interface FileValue {
146
+ /** The filename the site sees. */
147
+ file: string;
148
+ /** MIME type, guessed from the extension unless you pass one. */
149
+ type: string;
150
+ /** The bytes, base64. 10MB ceiling. */
151
+ b64: string;
152
+ }
86
153
  /**
87
154
  * Task values, referred to as `{{name}}` in prompts. As `data` the agent can read them
88
155
  * (to split a name or pick the right option); as `secrets` it never sees them. Either
89
156
  * way they are typed through placeholders, so playbooks store no values.
157
+ *
158
+ * A {@link FileValue} from `file()` is the exception: the agent attaches it with its
159
+ * upload tool rather than typing it.
90
160
  */
91
- type RunData = Record<string, string | number>;
161
+ type RunData = Record<string, string | number | FileValue>;
92
162
  interface AttentionRequest {
93
163
  id: string;
94
164
  /** captcha / mfa: finish it in the live view. agent: the agent's question. heal_failed: replay and the agent both gave up. */
@@ -535,6 +605,28 @@ declare class Run {
535
605
  private watch;
536
606
  }
537
607
 
608
+ /**
609
+ * Files as task values.
610
+ *
611
+ * await browser.ask('Attach my resume', { data: { resume: await file('./cv.pdf') } });
612
+ *
613
+ * The bytes ride inline in the run request, so the agent's `upload_file` tool can put
614
+ * them into a page's file input. `secrets` cannot hold one — a file is never typed
615
+ * through a placeholder, so there is nothing to hide.
616
+ */
617
+
618
+ /** Bigger than this and the server would refuse the body anyway. */
619
+ declare const MAX_FILE_BYTES: number;
620
+ /**
621
+ * A file for `data`. A string is a path on disk (Node only); a Blob, a File or raw bytes
622
+ * work anywhere. `name` is what the site sees, and `type` overrides the MIME guessed
623
+ * from the extension.
624
+ */
625
+ declare function file(source: string | Uint8Array | Blob, options?: {
626
+ name?: string;
627
+ type?: string;
628
+ }): Promise<FileValue>;
629
+
538
630
  /**
539
631
  * @oya-ai/browser — thousands of browsers, one API.
540
632
  *
@@ -699,14 +791,15 @@ declare class Oya {
699
791
  *
700
792
  * Bring your own LLM key (it pays for its own tokens, so no hourly chat quota applies):
701
793
  * await oya.config.set({ llm_provider: 'gemini', openai_api_key: process.env.GEMINI_API_KEY });
702
- * `llm_provider` is 'openai' | 'anthropic' | 'gemini' | 'vertex'; `chat_model` overrides
703
- * its default model. 'vertex' is Gemini Enterprise (ex-Vertex AI) in express mode, which
704
- * needs no GCP project; for a project-scoped endpoint, set `openai_base_url` to
705
- * `.../endpoints/openapi` and pass an OAuth access token as `openai_api_key`.
794
+ * `llm_provider` is the {@link LlmProvider} union, so an editor offers the choices and a
795
+ * typo is a compile error; `chat_model` overrides its default model. 'vertex' is Gemini
796
+ * Enterprise (ex-Vertex AI) in express mode, which needs no GCP project; for a
797
+ * project-scoped endpoint, set `openai_base_url` to `.../endpoints/openapi` and pass an
798
+ * OAuth access token as `openai_api_key`. See {@link ConfigUpdate} for every field.
706
799
  */
707
800
  readonly config: {
708
- get: <T = Record<string, unknown>>() => Promise<T>;
709
- set: <T = Record<string, unknown>>(values: Record<string, unknown>) => Promise<T>;
801
+ get: <T = Config>() => Promise<T>;
802
+ set: <T = Config>(values: ConfigUpdate) => Promise<T>;
710
803
  };
711
804
  /** Saved profiles. `personas` is retained as an alias for existing clients. */
712
805
  readonly profiles: {
@@ -764,4 +857,4 @@ declare class Oya {
764
857
  private waitUntilConnected;
765
858
  }
766
859
 
767
- export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default };
860
+ export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
package/dist/index.d.ts CHANGED
@@ -9,6 +9,60 @@ declare class Http {
9
9
  }
10
10
 
11
11
  /** Everything the API returns or accepts, in one place. */
12
+ /** Which model drives `ask()` and the chat API. */
13
+ type LlmProvider = 'openai' | 'anthropic'
14
+ /** Gemini via AI Studio. */
15
+ | 'gemini'
16
+ /** Gemini Enterprise, ex-Vertex AI. Express mode by default; set `openai_base_url` to a
17
+ * project-scoped `.../endpoints/openapi` endpoint to use an enterprise project. */
18
+ | 'vertex';
19
+ /** Empty disables solving. */
20
+ type CaptchaSolver = 'capsolver' | '2captcha' | '';
21
+ /**
22
+ * What a key can configure. Mirrors the server's field allowlist exactly: a field not
23
+ * listed here is ignored rather than stored, so the type is the whole surface.
24
+ * `null` clears a field and falls back to the deployment default.
25
+ */
26
+ interface ConfigUpdate {
27
+ llm_provider?: LlmProvider | null;
28
+ /** The credential for whichever `llm_provider` is set — the field name is shared. */
29
+ openai_api_key?: string | null;
30
+ /** Only honoured alongside this key's own `openai_api_key`. */
31
+ openai_base_url?: string | null;
32
+ /** Overrides the provider's default model. */
33
+ chat_model?: string | null;
34
+ browser_provider?: Provider | null;
35
+ anchor_api_key?: string | null;
36
+ browserbase_api_key?: string | null;
37
+ browserbase_project_id?: string | null;
38
+ steel_api_key?: string | null;
39
+ browseruse_api_key?: string | null;
40
+ cdp_ws_url?: string | null;
41
+ captcha_solver?: CaptchaSolver | null;
42
+ captcha_api_key?: string | null;
43
+ onboarded?: string | null;
44
+ }
45
+ /** What `config.get()` returns. Secrets read back masked, never in full. */
46
+ interface Config extends Omit<ConfigUpdate, 'llm_provider' | 'browser_provider' | 'captcha_solver'> {
47
+ llm_provider?: LlmProvider | '';
48
+ browser_provider?: Provider | '';
49
+ captcha_solver?: CaptchaSolver;
50
+ /** What this key would actually use right now, deployment defaults included. */
51
+ effective: {
52
+ baseUrl: string;
53
+ model: string;
54
+ hasLlmKey: boolean;
55
+ };
56
+ /** True when the LLM key in play belongs to the deployment, not this key. */
57
+ inherited: boolean;
58
+ has_openai_key: boolean;
59
+ providers: Array<{
60
+ id: Provider;
61
+ label: string;
62
+ needs: string[];
63
+ configured: boolean;
64
+ }>;
65
+ }
12
66
  /** Where a browser comes from. Configuration, not something a caller must know. */
13
67
  type Provider = 'oya-cloud' | 'oya-selfhosted' | 'browseruse' | 'browserbase' | 'steel' | 'anchor' | 'cdp';
14
68
  interface StartOptions {
@@ -83,12 +137,28 @@ interface PlaybookSummary extends Playbook {
83
137
  healedFrom: number;
84
138
  }) | null;
85
139
  }
140
+ /**
141
+ * A file attached to a task value. Build it with `file()`, never by hand. Only `data`
142
+ * takes one: a file is not typed through a placeholder, so `secrets` has nothing to hide
143
+ * and rejects it.
144
+ */
145
+ interface FileValue {
146
+ /** The filename the site sees. */
147
+ file: string;
148
+ /** MIME type, guessed from the extension unless you pass one. */
149
+ type: string;
150
+ /** The bytes, base64. 10MB ceiling. */
151
+ b64: string;
152
+ }
86
153
  /**
87
154
  * Task values, referred to as `{{name}}` in prompts. As `data` the agent can read them
88
155
  * (to split a name or pick the right option); as `secrets` it never sees them. Either
89
156
  * way they are typed through placeholders, so playbooks store no values.
157
+ *
158
+ * A {@link FileValue} from `file()` is the exception: the agent attaches it with its
159
+ * upload tool rather than typing it.
90
160
  */
91
- type RunData = Record<string, string | number>;
161
+ type RunData = Record<string, string | number | FileValue>;
92
162
  interface AttentionRequest {
93
163
  id: string;
94
164
  /** captcha / mfa: finish it in the live view. agent: the agent's question. heal_failed: replay and the agent both gave up. */
@@ -535,6 +605,28 @@ declare class Run {
535
605
  private watch;
536
606
  }
537
607
 
608
+ /**
609
+ * Files as task values.
610
+ *
611
+ * await browser.ask('Attach my resume', { data: { resume: await file('./cv.pdf') } });
612
+ *
613
+ * The bytes ride inline in the run request, so the agent's `upload_file` tool can put
614
+ * them into a page's file input. `secrets` cannot hold one — a file is never typed
615
+ * through a placeholder, so there is nothing to hide.
616
+ */
617
+
618
+ /** Bigger than this and the server would refuse the body anyway. */
619
+ declare const MAX_FILE_BYTES: number;
620
+ /**
621
+ * A file for `data`. A string is a path on disk (Node only); a Blob, a File or raw bytes
622
+ * work anywhere. `name` is what the site sees, and `type` overrides the MIME guessed
623
+ * from the extension.
624
+ */
625
+ declare function file(source: string | Uint8Array | Blob, options?: {
626
+ name?: string;
627
+ type?: string;
628
+ }): Promise<FileValue>;
629
+
538
630
  /**
539
631
  * @oya-ai/browser — thousands of browsers, one API.
540
632
  *
@@ -699,14 +791,15 @@ declare class Oya {
699
791
  *
700
792
  * Bring your own LLM key (it pays for its own tokens, so no hourly chat quota applies):
701
793
  * await oya.config.set({ llm_provider: 'gemini', openai_api_key: process.env.GEMINI_API_KEY });
702
- * `llm_provider` is 'openai' | 'anthropic' | 'gemini' | 'vertex'; `chat_model` overrides
703
- * its default model. 'vertex' is Gemini Enterprise (ex-Vertex AI) in express mode, which
704
- * needs no GCP project; for a project-scoped endpoint, set `openai_base_url` to
705
- * `.../endpoints/openapi` and pass an OAuth access token as `openai_api_key`.
794
+ * `llm_provider` is the {@link LlmProvider} union, so an editor offers the choices and a
795
+ * typo is a compile error; `chat_model` overrides its default model. 'vertex' is Gemini
796
+ * Enterprise (ex-Vertex AI) in express mode, which needs no GCP project; for a
797
+ * project-scoped endpoint, set `openai_base_url` to `.../endpoints/openapi` and pass an
798
+ * OAuth access token as `openai_api_key`. See {@link ConfigUpdate} for every field.
706
799
  */
707
800
  readonly config: {
708
- get: <T = Record<string, unknown>>() => Promise<T>;
709
- set: <T = Record<string, unknown>>(values: Record<string, unknown>) => Promise<T>;
801
+ get: <T = Config>() => Promise<T>;
802
+ set: <T = Config>(values: ConfigUpdate) => Promise<T>;
710
803
  };
711
804
  /** Saved profiles. `personas` is retained as an alias for existing clients. */
712
805
  readonly profiles: {
@@ -764,4 +857,4 @@ declare class Oya {
764
857
  private waitUntilConnected;
765
858
  }
766
859
 
767
- export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type Fingerprint, type Health, type HumanInputAction, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default };
860
+ export { type Activity, type Analysis, type AttentionRequest, Browser, type BrowserDetail, type BrowserInfo, type CaptchaResult, type CaptchaSolver, type Config, type ConfigUpdate, type ControlCredential, type ControlEvent, type ControlOverview, type ControlRole, type ControlSession, type Element, type FileValue, type Fingerprint, type Health, type HumanInputAction, type LlmProvider, MAX_FILE_BYTES, type MfaConfig, type MfaResult, Oya, OyaError, type OyaOptions, type PersonaInfo, type PersonaPrefs, type PlayResult, type Playbook, type PlaybookSummary, type ProjectSettings, type Provider, type ProxyCreate, type ProxyInfo, Run, type RunData, type RunInfo, type RunResult, type StartOptions, type StartResult, type StopResult, type SubmitOptions, Oya as default, file };
package/dist/index.js CHANGED
@@ -346,6 +346,63 @@ var Run = class {
346
346
  }
347
347
  };
348
348
 
349
+ // src/file.ts
350
+ var MAX_FILE_BYTES = 10 * 1024 * 1024;
351
+ var NODE_FS = "node:fs/promises";
352
+ var MIME = {
353
+ pdf: "application/pdf",
354
+ png: "image/png",
355
+ jpg: "image/jpeg",
356
+ jpeg: "image/jpeg",
357
+ gif: "image/gif",
358
+ webp: "image/webp",
359
+ svg: "image/svg+xml",
360
+ heic: "image/heic",
361
+ txt: "text/plain",
362
+ csv: "text/csv",
363
+ json: "application/json",
364
+ xml: "application/xml",
365
+ html: "text/html",
366
+ doc: "application/msword",
367
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
368
+ xls: "application/vnd.ms-excel",
369
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
370
+ ppt: "application/vnd.ms-powerpoint",
371
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
372
+ zip: "application/zip"
373
+ };
374
+ var base64 = (bytes) => {
375
+ const buffer = globalThis.Buffer;
376
+ if (buffer) return buffer.from(bytes).toString("base64");
377
+ let binary = "";
378
+ for (let i = 0; i < bytes.length; i += 8192) binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
379
+ return btoa(binary);
380
+ };
381
+ async function file(source, options = {}) {
382
+ let bytes;
383
+ let name = options.name;
384
+ if (typeof source === "string") {
385
+ const fs = await import(
386
+ /* webpackIgnore: true */
387
+ /* @vite-ignore */
388
+ NODE_FS
389
+ );
390
+ bytes = new Uint8Array(await fs.readFile(source));
391
+ name ||= source.split(/[\\/]/).pop() || "file";
392
+ } else if (source instanceof Uint8Array) {
393
+ bytes = source;
394
+ } else {
395
+ bytes = new Uint8Array(await source.arrayBuffer());
396
+ name ||= source.name;
397
+ }
398
+ name ||= "file";
399
+ if (bytes.length > MAX_FILE_BYTES) {
400
+ throw new Error(`${name} is ${Math.round(bytes.length / 1024 / 1024)}MB; the limit for a task file is ${MAX_FILE_BYTES / 1024 / 1024}MB.`);
401
+ }
402
+ const ext = name.includes(".") ? name.split(".").pop().toLowerCase() : "";
403
+ return { file: name, type: options.type || MIME[ext] || "application/octet-stream", b64: base64(bytes) };
404
+ }
405
+
349
406
  // src/index.ts
350
407
  var DEFAULT_BASE_URL = "https://browser.getoya.ai";
351
408
  var READY_POLL_MS = 2e3;
@@ -476,10 +533,11 @@ var Oya = class {
476
533
  *
477
534
  * Bring your own LLM key (it pays for its own tokens, so no hourly chat quota applies):
478
535
  * await oya.config.set({ llm_provider: 'gemini', openai_api_key: process.env.GEMINI_API_KEY });
479
- * `llm_provider` is 'openai' | 'anthropic' | 'gemini' | 'vertex'; `chat_model` overrides
480
- * its default model. 'vertex' is Gemini Enterprise (ex-Vertex AI) in express mode, which
481
- * needs no GCP project; for a project-scoped endpoint, set `openai_base_url` to
482
- * `.../endpoints/openapi` and pass an OAuth access token as `openai_api_key`.
536
+ * `llm_provider` is the {@link LlmProvider} union, so an editor offers the choices and a
537
+ * typo is a compile error; `chat_model` overrides its default model. 'vertex' is Gemini
538
+ * Enterprise (ex-Vertex AI) in express mode, which needs no GCP project; for a
539
+ * project-scoped endpoint, set `openai_base_url` to `.../endpoints/openapi` and pass an
540
+ * OAuth access token as `openai_api_key`. See {@link ConfigUpdate} for every field.
483
541
  */
484
542
  config = {
485
543
  get: () => this.http.request("GET", "/api/config"),
@@ -509,8 +567,10 @@ var Oya = class {
509
567
  var index_default = Oya;
510
568
  export {
511
569
  Browser,
570
+ MAX_FILE_BYTES,
512
571
  Oya,
513
572
  OyaError,
514
573
  Run,
515
- index_default as default
574
+ index_default as default,
575
+ file
516
576
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oya-ai/browser",
3
- "version": "1.0.86",
3
+ "version": "1.0.87",
4
4
  "description": "Rotate thousands of browsers behind one API — personas, proxies, stealth, CAPTCHA and MFA.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://browser.getoya.ai",