notte-sdk 0.0.21 → 1.9.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/README.md CHANGED
@@ -4,8 +4,85 @@
4
4
 
5
5
  The official TypeScript SDK for Notte API, providing cloud-hosted browser sessions with precise control, LLM-powered web agents for automated tasks, serverless functions, and secure credential management.
6
6
 
7
+ ## Contributing
8
+
9
+ This directory is a standalone npm package. Node.js 22 and 24 are tested in CI;
10
+ Python tooling is not required to build or test it.
11
+
12
+ ```bash
13
+ cd node-sdk
14
+ npm ci
15
+ npm run typecheck
16
+ npm run test:unit -- --run
17
+ npx vitest run test/function-stream.local.test.ts # also part of npm run test:unit
18
+ npm run build
19
+ node scripts/check-package.js
20
+ npm pack --dry-run
21
+ ```
22
+
23
+ `npm run build` compiles the checked-in client without contacting the API.
24
+ To update the generated client, run `npm run generate:staging`, review the diff,
25
+ and commit it. `npm run check:staging` checks for drift without editing your files.
26
+
27
+ For the isolated live test, set `NOTTE_API_KEY` and
28
+ `NOTTE_API_URL=https://us-staging.notte.cc`, then run:
29
+
30
+ ```bash
31
+ npx vitest run test/function-create-run.integration.test.ts
32
+ ```
33
+
34
+ This test creates its own function and deletes it afterward. Other live suites
35
+ may require pre-existing fixtures; see [test documentation](test/README.md).
36
+ The `node sdk cicd` workflow runs on every PR touching `node-sdk/`:
37
+
38
+ - `tests` runs unit tests, local HTTP integration tests, type checks, and package
39
+ checks on Node.js 22 and 24, including for fork PRs.
40
+ - `integration-tests` runs the isolated live function lifecycle tests against
41
+ staging on same-repository PRs. Fork PRs skip this credentialed job.
42
+
43
+ Both jobs also run on main pushes and manual workflow runs on main. API drift
44
+ checks remain limited to main pushes and manual workflow runs on main.
45
+
46
+ ### Releases
47
+
48
+ The npm package remains `notte-sdk`. Its MIT license is in [LICENSE](LICENSE);
49
+ the repository root license does not replace this package's license.
50
+
51
+ The Node SDK is released together with the Python SDK, from the same tag and at
52
+ the same version. Cutting a release means creating a GitHub release tagged
53
+ `vX.Y.Z` on [nottelabs/notte](https://github.com/nottelabs/notte/releases):
54
+
55
+ - `pypi-release.yml` publishes the Python packages to PyPI as `X.Y.Z`.
56
+ - `node-sdk-publish.yml` sets this package's version to `X.Y.Z`, runs the
57
+ typecheck, unit tests, streaming test and export smoke test, publishes to npm
58
+ with provenance, then installs the published version back from npm to verify it.
59
+
60
+ `package.json` stays at `0.0.0-dev` on `main`; the version is only set in CI
61
+ from the tag, like the `.dev` placeholder in the Python `pyproject.toml` files.
62
+ Publishing uses npm trusted publishing (GitHub OIDC) bound to this repository,
63
+ workflow `node-sdk-publish.yml` and environment `npm`, so no npm token is stored
64
+ in the repository. To dry-run a release build locally, run `make release X.Y.Z`
65
+ in this directory; it never publishes.
66
+
67
+ Importing the SDK does not contact the npm registry. Constructing a `NotteClient`
68
+ performs a one-time, non-blocking check per process and warns when a newer
69
+ release exists, like the Python SDK. Set `NOTTE_SDK_DISABLE_VERSION_CHECK=1` to
70
+ disable it. To explicitly check for an update, call `await checkForLatestVersion()`
71
+ from `notte-sdk`.
72
+ API URLs must use HTTPS, except HTTP loopback addresses for local development.
73
+
7
74
  ## Features
8
75
 
76
+ ### Server-side proxy security
77
+
78
+ `createNotteProxy` from `notte-sdk/next` requires an `authenticate` callback at
79
+ runtime. Without one, requests return 401 even when a server API key is configured.
80
+ The callback must verify caller identity and authorize access to requested
81
+ resources. Returning a user-specific Notte API key isolates accounts; returning
82
+ void uses the shared server key and requires application-level ownership checks.
83
+ An endpoint allowlist is not a substitute for authorization. Relative HTTP proxy
84
+ clients poll agent status for completion because they cannot use backend WebSockets.
85
+
9
86
  - 🌐 **Cloud Browser Sessions** - Access remote browsers with full control
10
87
  - 🤖 **LLM-Powered Agents** - Intelligent web automation with natural language
11
88
  - 🔒 **Secret Vaults** - Enterprise-grade credential management with end-to-end encryption
@@ -14,6 +91,7 @@ The official TypeScript SDK for Notte API, providing cloud-hosted browser sessio
14
91
  - 📡 **Real-time Updates** - WebSocket support for live agent monitoring
15
92
  - 📝 **Type Safety** - Full TypeScript support with generated types
16
93
  - ⚡ **Serverless Functions** - Run pre-built automation workflows with simple API calls
94
+ - 🔍 **Web Search, Secrets and Usage** - Search the web, manage workspace secrets and read billing usage
17
95
  - 🔄 **Auto-sync** - Generated from OpenAPI spec, always up-to-date
18
96
 
19
97
  ## Installation
@@ -35,7 +113,7 @@ const notte = new NotteClient({
35
113
  });
36
114
 
37
115
  // Use session with automatic cleanup (mirrors Python's context manager)
38
- await notte.Session({ timeoutMinutes: 30 }).use(async (session) => {
116
+ await notte.Session({ idle_timeout_minutes: 5 }).use(async (session) => {
39
117
  const status = await session.status();
40
118
  console.log('Session status:', status);
41
119
  });
@@ -52,13 +130,13 @@ const notte = new NotteClient();
52
130
  await notte.Session().use(async (session) => {
53
131
  const agent = notte.Agent({ session, max_steps: 10 });
54
132
 
55
- const response = await agent.run(
56
- "Find the best Italian restaurant in San Francisco and book a table for 2 at 7pm today",
57
- (update) => {
133
+ const response = await agent.run({
134
+ task: "Find the best Italian restaurant in San Francisco and book a table for 2 at 7pm today",
135
+ updateHandler: (update) => {
58
136
  // Receive real-time updates via WebSocket
59
- console.log(`Step ${update.data.currentStep}: ${update.data.message}`);
137
+ console.log(`[${update.type}]`, update.data);
60
138
  }
61
- );
139
+ });
62
140
 
63
141
  console.log(`Agent completed: ${response.success ? 'Success' : 'Failed'}`);
64
142
  console.log(`Answer: ${response.answer}`);
@@ -74,10 +152,15 @@ The main client for interacting with the Notte API.
74
152
  ```typescript
75
153
  import { NotteClient } from 'notte-sdk';
76
154
 
77
- const notte = new NotteClient();
155
+ const notte = new NotteClient({
156
+ apiKey: process.env.NOTTE_API_KEY, // defaults to NOTTE_API_KEY
157
+ baseUrl: 'https://api.notte.cc', // defaults to NOTTE_API_URL or https://api.notte.cc
158
+ timeoutMs: 60_000, // per-request timeout, default 60 s
159
+ verbose: false, // log every request
160
+ });
78
161
 
79
162
  // Create sessions
80
- const session = notte.Session({ timeoutMinutes: 30 });
163
+ const session = notte.Session({ idle_timeout_minutes: 5, max_duration_minutes: 30 });
81
164
 
82
165
  // Create agents
83
166
  const agent = notte.Agent({ session, max_steps: 15 });
@@ -87,8 +170,32 @@ const vault = notte.Vault({ name: 'My Vault' });
87
170
 
88
171
  // Create personas
89
172
  const persona = notte.Persona({ create_vault: true });
173
+
174
+ // Check that the API is reachable (rejects with NotteAPIError otherwise)
175
+ await notte.healthCheck();
176
+ ```
177
+
178
+ #### Listing resources
179
+
180
+ Every resource has a `list()` namespace, mirroring `client.sessions.list()` and
181
+ friends in Python. Options are the generated query parameters of the matching
182
+ endpoint (`SessionListOptions`, `AgentListOptions`, `VaultListOptions`,
183
+ `FunctionListOptions`, `PersonaListOptions`).
184
+
185
+ ```typescript
186
+ const sessions = await notte.sessions.list({ only_active: true });
187
+ const agents = await notte.agents.list({ page_size: 10 });
188
+ const vaults = await notte.vaults.list();
189
+ const functions = await notte.functions.list();
190
+ const personas = await notte.personas.list({ only_active: true });
90
191
  ```
91
192
 
193
+ #### Database preview branches
194
+
195
+ Set `NOTTE_DB_PREVIEW_BRANCH` (or pass `dbPreview` to the client) to route every
196
+ request and websocket handshake to a database preview branch. This is an internal
197
+ option used when testing API changes.
198
+
92
199
  ### Session
93
200
 
94
201
  Manages browser sessions with automatic lifecycle management.
@@ -99,7 +206,7 @@ import { NotteClient } from 'notte-sdk';
99
206
  const notte = new NotteClient();
100
207
 
101
208
  // Context manager pattern (automatic start/stop)
102
- await notte.Session({ timeoutMinutes: 30 }).use(async (session) => {
209
+ await notte.Session({ idle_timeout_minutes: 5 }).use(async (session) => {
103
210
  // Session is automatically started
104
211
  console.log('Session ID:', session.getId());
105
212
 
@@ -120,7 +227,7 @@ try {
120
227
  }
121
228
 
122
229
  // Async iterator pattern
123
- for await (const session of notte.Session({ timeoutMinutes: 15 })) {
230
+ for await (const session of notte.Session({ max_duration_minutes: 15 })) {
124
231
  // Use session here
125
232
  const status = await session.status();
126
233
  console.log('Session active:', status);
@@ -128,15 +235,102 @@ for await (const session of notte.Session({ timeoutMinutes: 15 })) {
128
235
  }
129
236
  ```
130
237
 
238
+ #### Browser control
239
+
240
+ Drive the page with typed actions, exactly like `session.execute(...)` in Python.
241
+ Element ids (`B1`, `I2`, ...) must come from a live `observe()` call.
242
+
243
+ ```typescript
244
+ import { NotteClient, actions, ActionExecutionError } from 'notte-sdk';
245
+
246
+ const notte = new NotteClient();
247
+
248
+ await notte.Session().use(async (session) => {
249
+ await session.execute(actions.goto({ url: 'https://www.notte.cc' }));
250
+
251
+ const observation = await session.observe({ perception_type: 'deep' });
252
+ console.log(observation.space);
253
+
254
+ // Plain objects work too; the `type` field is a discriminated union
255
+ await session.execute({ type: 'click', id: 'B1' });
256
+ await session.execute(actions.fill({ selector: 'internal:text="Email"', value: 'me@example.com' }));
257
+
258
+ // A failed action throws ActionExecutionError with the server-side error type
259
+ try {
260
+ await session.execute(actions.click({ id: 'B999' }));
261
+ } catch (error) {
262
+ if (error instanceof ActionExecutionError) console.log(error.errorType, error.userMessage);
263
+ }
264
+
265
+ // Or inspect the result yourself
266
+ const result = await session.execute(actions.click({ id: 'B999' }), { raiseOnFailure: false });
267
+ console.log(result.success, result.message);
268
+
269
+ // Run JavaScript in the page and fetch through the page (cookies, proxy, fingerprint).
270
+ // Relative URLs resolve against the page; plaintext http targets are refused unless allowInsecure is set.
271
+ const title = await session.evaluateJs('document.title');
272
+ const summary = await session.fetch('/api/rest_v1/page/summary/Main_Page').then(r => r.json());
273
+
274
+ // Scrape the current page
275
+ const markdown = await session.scrape({ only_main_content: true });
276
+
277
+ // Cookies
278
+ const cookies = await session.getCookies();
279
+ await session.setCookies(cookies);
280
+ });
281
+ ```
282
+
283
+ #### Playwright access
284
+
285
+ `playwright-core` is an optional peer dependency (`npm install playwright-core`),
286
+ the counterpart of `pip install notte-sdk[playwright]`:
287
+
288
+ ```typescript
289
+ await notte.Session().use(async (session) => {
290
+ const page = await session.page(); // connected over CDP, cached
291
+ await page.goto('https://www.notte.cc');
292
+ await page.screenshot({ path: 'screenshot.png' });
293
+
294
+ // Or connect your own tooling
295
+ const cdpUrl = await session.cdpUrl();
296
+ });
297
+ ```
298
+
299
+ #### Replays and cookie files
300
+
301
+ ```typescript
302
+ // Persist login state between runs: loaded on start, appended on stop
303
+ const session = notte.Session({ cookie_file: './cookies.json' });
304
+
305
+ await session.use(async () => { /* ... */ });
306
+
307
+ // Replays are generated after stop(); replay() polls until ready (240 s by default)
308
+ const replay = await session.replay();
309
+ await session.downloadReplay('./session.mp4');
310
+ ```
311
+
131
312
  #### Session Methods
132
313
 
133
- - `start()` - Start the session
134
- - `stop()` - Stop the session
314
+ - `start()` - Start the session (retries 5xx errors 3 times, waits 30 s on cluster overload)
315
+ - `stop(closeReason?)` - Stop the session
135
316
  - `status()` - Get session status
136
- - `viewer()` - Open the live session viewer in the local default browser
137
317
  - `use(callback)` - Context manager pattern
138
318
  - `getId()` - Get session ID
139
319
  - `isSessionActive()` - Check if session is running
320
+ - `execute(action, options?)` - Execute a typed action; throws `ActionExecutionError` on failure unless `raiseOnFailure: false`
321
+ - `observe(options?)` - Observe the page (`perception_type`, `min_nb_actions`, `max_nb_actions`)
322
+ - `scrape(options?)` - Scrape the page as markdown, images, or structured data (see [Scraping](#scraping))
323
+ - `evaluateJs(code, options?)` - Evaluate JavaScript and return the stringified result
324
+ - `fetch(url, options?)` - Issue an HTTP request from inside the page
325
+ - `setCookies(cookies)` / `setCookiesFromFile(path)` / `getCookies()` - Manage cookies
326
+ - `cdpUrl()` - Chrome DevTools Protocol websocket URL
327
+ - `page()` - Playwright `Page` connected over CDP (requires `playwright-core`)
328
+ - `debugInfo()` / `debugTabInfo(tabIdx?)` - Debug URLs and tab metadata
329
+ - `replay(options?)` / `downloadReplay(path?)` - Session recording
330
+ - `getScript(options?)` - Python code reproducing the session
331
+ - `viewer()` - Open the live session viewer in the local default browser
332
+ - `storage` - The `RemoteFileStorage` attached with the `storage` option
333
+ - `client.sessions.list(options?)` - List sessions
140
334
 
141
335
  ### Agent
142
336
 
@@ -153,25 +347,25 @@ await notte.Session().use(async (session) => {
153
347
  max_steps: 10
154
348
  });
155
349
 
156
- // Non-blocking: start agent and get ID
157
- const agentId = await agent.start("Navigate to Google and search for 'TypeScript'");
158
- console.log('Agent started:', agentId);
350
+ // Non-blocking: start agent and get its ID
351
+ await agent.start({ task: "Navigate to Google and search for 'TypeScript'" });
352
+ console.log('Agent started:', agent.agentId);
159
353
 
160
354
  // Check agent status
161
355
  const status = await agent.status();
162
356
  console.log('Agent status:', status);
163
357
 
164
358
  // Blocking: run agent and wait for completion with live updates
165
- const response = await agent.run(
166
- "Find the latest TypeScript documentation",
167
- (update) => {
359
+ const response = await agent.run({
360
+ task: "Find the latest TypeScript documentation",
361
+ updateHandler: (update) => {
168
362
  if (update.type === 'step') {
169
363
  console.log(`Step update:`, update.data);
170
364
  } else if (update.type === 'completion') {
171
365
  console.log(`Completed:`, update.data);
172
366
  }
173
367
  }
174
- );
368
+ });
175
369
 
176
370
  console.log('Final result:', response);
177
371
  });
@@ -179,12 +373,15 @@ await notte.Session().use(async (session) => {
179
373
 
180
374
  #### Agent Methods
181
375
 
182
- - `start(task)` - Start agent with task (non-blocking)
183
- - `run(task, onUpdate?)` - Start agent and wait for completion (blocking)
376
+ - `start(request)` - Start agent with `{ task, url?, ... }` (non-blocking)
377
+ - `run(request)` - Start agent and wait for completion (blocking); pass `updateHandler` for live updates
184
378
  - `status()` - Get agent status
185
379
  - `stop()` - Stop the agent
186
- - `getId()` - Get agent ID
187
- - `isRunning()` - Check if agent is running
380
+ - `agentId` - The agent ID (read-only property, available after `start()` / `run()`)
381
+ - `sessionId` - The session ID the agent runs in (read-only property)
382
+ - `hasStarted()` - Whether `start()`/`run()` was called on this instance (local check)
383
+ - `isActive()` - Whether the agent is still active on the server (fetches the status)
384
+ - `isRunning()` - Deprecated alias of `hasStarted()`; it does not contact the API
188
385
 
189
386
  #### Structured answers with Zod
190
387
 
@@ -231,15 +428,6 @@ await vault.addCredentials('https://github.com/', {
231
428
  const strongPassword = vault.generatePassword(20, true); // 20 chars with special chars
232
429
  const simplePassword = vault.generatePassword(12, false); // 12 chars, no special chars
233
430
 
234
- // Add credit card information
235
- await vault.setCreditCard({
236
- card_holder_name: 'John Doe',
237
- card_number: '4111111111111111',
238
- card_cvv: '123',
239
- expiry_month: '12',
240
- expiry_year: '2025'
241
- });
242
-
243
431
  // Use with agents for automatic credential management
244
432
  await notte.Session().use(async (session) => {
245
433
  const agent = notte.Agent({
@@ -259,22 +447,21 @@ const credentials = await existingVault.listCredentials();
259
447
 
260
448
  // Cleanup
261
449
  await vault.deleteCredentials('https://github.com/');
262
- await vault.deleteCreditCard();
263
450
  await vault.stop(); // Deletes entire vault
264
451
  ```
265
452
 
266
453
  #### Vault Methods
267
454
 
268
- - `addCredentials(url, credentials)` - Store credentials for a URL
269
- - `getCredentials(url)` - Retrieve credentials for a URL
455
+ - `addCredentials(url, credentials)` - Store credentials for a URL (validated client-side like Python: root domain, exactly one of `email`/`username`, base32 `mfa_secret`)
456
+ - `addCredentialsFromEnv(url)` - Read `{DOMAIN}_EMAIL` / `{DOMAIN}_USERNAME` / `{DOMAIN}_PASSWORD` / `{DOMAIN}_MFA_SECRET` from the environment
457
+ - `getCredentials(url)` - Retrieve credentials for a URL; throws `NotteAPIError` when none exist
458
+ - `hasCredential(url)` - Whether credentials exist for a URL
270
459
  - `deleteCredentials(url)` - Delete credentials for a URL
271
460
  - `listCredentials()` - List all stored credentials
272
- - `setCreditCard(card)` - Store credit card information
273
- - `getCreditCard()` - Retrieve credit card information
274
- - `deleteCreditCard()` - Delete credit card information
275
461
  - `generatePassword(length?, includeSpecialChars?)` - Generate secure passwords
276
462
  - `delete()` - Delete the entire vault
277
463
  - `stop()` - Stop and delete the vault
464
+ - `client.vaults.list(options?)` - List vaults
278
465
 
279
466
  #### Security Features
280
467
 
@@ -292,15 +479,12 @@ import { NotteClient } from 'notte-sdk';
292
479
 
293
480
  const notte = new NotteClient();
294
481
 
295
- // Create a new persona with vault and phone number
296
- const persona = notte.Persona({
297
- create_vault: true,
298
- create_phone_number: true
299
- });
482
+ // Create a new persona with a vault
483
+ const persona = notte.Persona({ create_vault: true });
300
484
 
301
- // Get persona information
302
- console.log(`Email: ${persona.info.email}`);
303
- console.log(`Phone: ${persona.info.phone_number}`);
485
+ // Get persona information (the first call initializes the persona)
486
+ const info = await persona.get();
487
+ console.log(`Email: ${info.email}`);
304
488
 
305
489
  // Read emails sent to the persona
306
490
  const emails = await persona.emails({
@@ -323,9 +507,9 @@ await notte.Session().use(async (session) => {
323
507
  vault_id: persona.vault.vaultId // Agent will use persona's vault
324
508
  });
325
509
 
326
- const result = await agent.run(
327
- `Create an account on GitHub using the persona credentials`
328
- );
510
+ const result = await agent.run({
511
+ task: `Create an account on GitHub using the persona credentials`
512
+ });
329
513
  console.log(`Account created: ${result.success}`);
330
514
  });
331
515
 
@@ -345,10 +529,9 @@ await persona.stop(); // Deletes persona and all associated data
345
529
 
346
530
  #### Persona Methods
347
531
 
532
+ - `get()` - Fetch (and initialize) the persona information
348
533
  - `emails(options?)` - Read emails sent to the persona
349
534
  - `sms(options?)` - Read SMS messages sent to the persona
350
- - `createNumber(options?)` - Create a phone number for the persona
351
- - `deleteNumber()` - Delete the persona's phone number
352
535
  - `addCredentials(url)` - Add auto-generated credentials to the persona's vault
353
536
  - `delete()` - Delete the persona
354
537
  - `stop()` - Stop and delete the persona
@@ -368,7 +551,7 @@ const sms = await persona.sms(options);
368
551
 
369
552
  #### Persona Features
370
553
 
371
- - **Complete Digital Identity** - Unique email address and phone number
554
+ - **Complete Digital Identity** - Unique email address
372
555
  - **2FA Support** - Receive and read SMS verification codes automatically
373
556
  - **Automated Account Creation** - Seamless integration with agents for signup flows
374
557
  - **Vault Integration** - Optional secure credential storage
@@ -390,42 +573,95 @@ const fn = notte.NotteFunction({
390
573
  decryption_key: 'optional-decryption-key' // optional, for encrypted outputs
391
574
  });
392
575
 
393
- // Run the function with input variables
576
+ // Stream logs to console.log and wait for the final result (default)
394
577
  const result = await fn.run({
395
578
  url: 'https://example.com',
396
579
  query: 'extract pricing information'
397
580
  });
398
581
 
399
582
  console.log('Run ID:', result.function_run_id);
583
+ console.log('Result:', result.result);
400
584
 
401
585
  // Retrieve the run result / metadata later
402
- const metadata = await fn.retrieve(result.function_run_id);
586
+ const metadata = await fn.getRun(result.function_run_id);
403
587
  console.log('Status:', metadata.status); // 'active' | 'closed' | 'failed'
404
588
  console.log('Result:', metadata);
405
589
  ```
406
590
 
407
- #### Polling for completion
591
+ #### Selecting the runtime
592
+
593
+ `run` defaults to the standard Lambda runtime. Where the extended runtime pilot
594
+ is enabled, select AgentCore per invocation with the second argument:
595
+
596
+ ```typescript
597
+ const result = await fn.run(
598
+ { url: 'https://example.com' },
599
+ { runtime: 'extended' }
600
+ );
601
+
602
+ // The default is equivalent to an explicit standard selection.
603
+ await fn.run({ url: 'https://example.com' }, { runtime: 'standard' });
604
+ ```
605
+
606
+ The runtime option stays separate from script input variables. Extended requests
607
+ fail explicitly if that runtime is unavailable or at capacity; they do not fall
608
+ back to Lambda. Browser sessions keep their own duration limits. Existing
609
+ schedules continue to use the standard runtime during the pilot.
408
610
 
409
- Since functions run asynchronously, you can poll for the result:
611
+ #### Create a run before execution
612
+
613
+ `createRun()` is optional: use it when you need a run ID before execution starts.
614
+ It only creates the cloud run record. Pass the returned ID as `functionRunId` to
615
+ execute that record; omitting `functionRunId` makes `run()` create a new run.
410
616
 
411
617
  ```typescript
412
618
  const fn = notte.NotteFunction({ function_id: 'your-function-id' });
413
- const { function_run_id } = await fn.run({ url: 'https://example.com' });
414
-
415
- // Poll until complete
416
- let metadata;
417
- do {
418
- await new Promise(r => setTimeout(r, 2000)); // wait 2s between checks
419
- metadata = await fn.retrieve(function_run_id);
420
- } while (metadata.status === 'active');
421
-
422
- if (metadata.status === 'closed') {
423
- console.log('Function completed successfully:', metadata);
424
- } else {
425
- console.error('Function failed:', metadata);
426
- }
619
+ const { function_run_id } = await fn.createRun();
620
+ console.log('Run ID before execution:', function_run_id);
621
+
622
+ const pending = fn.run(
623
+ { url: 'https://example.com' },
624
+ { functionRunId: function_run_id },
625
+ );
626
+
627
+ // Do other work here, then wait for completion.
628
+ const result = await pending;
629
+ console.log(result.result);
630
+
631
+ const metadata = await fn.getRun(function_run_id);
632
+ console.log(metadata.status);
633
+ ```
634
+
635
+ `getRun()` fetches the current status and stored result without waiting for execution
636
+ to finish. `retrieve()` remains a compatible alias. Execution results contain the
637
+ function's structured output; the stored `metadata.result` is serialized JSON.
638
+
639
+ Keeping the promise lets your JavaScript continue while the HTTP request remains
640
+ open. Keep the process running until it completes. This is not a detached job.
641
+ With `{ stream: false }`, the SDK returns JSON without live logs, but standard
642
+ execution still waits for completion.
643
+
644
+ #### Streaming options and migration
645
+
646
+ **Breaking change:** `run()` now defaults to `stream: true`, matching the Python SDK.
647
+ It consumes the event stream, prints logs as they arrive, and resolves with the
648
+ final response, including `status` and `result`. Existing callers that require a
649
+ JSON response without live logs must add `{ stream: false }` as the second argument.
650
+
651
+ Failed executions throw by default. Set `raiseOnFailure: false` to inspect the
652
+ failed response yourself. Transport and malformed-stream errors still throw.
653
+ Use `onLog` to route logs to your application (or `onLog: () => {}` to silence them):
654
+
655
+ ```typescript
656
+ const result = await fn.run({ url: 'https://example.com' }, {
657
+ onLog: message => console.log('[function]', message),
658
+ raiseOnFailure: false,
659
+ });
427
660
  ```
428
661
 
662
+ Options belong in the second argument; a `stream` field in the first argument
663
+ is a script input and does not change streaming behavior.
664
+
429
665
  #### Downloading the function code
430
666
 
431
667
  ```typescript
@@ -452,24 +688,153 @@ const fn = notte.NotteFunction({
452
688
  const code = await fn.download();
453
689
  ```
454
690
 
691
+ #### Creating and managing functions
692
+
693
+ Upload a Python script to create a function, like `NotteFunction(path=...)` in Python.
694
+ Creation is lazy: the upload happens on the first call that needs the function id.
695
+
696
+ ```typescript
697
+ const fn = notte.NotteFunction({ path: './my_function.py', name: 'price-monitor', description: 'Checks prices' });
698
+
699
+ const details = await fn.get(); // metadata, versions and download link
700
+ await fn.update({ path: './my_function_v2.py' }); // upload a new version
701
+ await fn.updateMetadata({ name: 'price-monitor-v2' });
702
+ await fn.rollback({ version: details.latest_version });
703
+
704
+ await fn.setSchedule({ cron: '0 9 * * *', variables: { url: 'https://example.com' } });
705
+ await fn.deleteSchedule();
706
+
707
+ const runs = await fn.runs(); // all runs, including completed ones
708
+ const functions = await notte.functions.list();
709
+
710
+ await fn.delete();
711
+ ```
712
+
713
+ `run()` waits at most 300 s by default; pass `timeoutMs` to change it. A failed
714
+ run throws `FailedToRunCloudFunctionError` unless `raiseOnFailure: false`.
715
+
455
716
  #### Function Methods
456
717
 
457
- - `run(variables?)` - Start a function run with optional input variables
458
- - `retrieve(functionRunId)` - Get metadata/result for a specific run
718
+ - `run(variables?, options?)` - Stream logs and wait for completion; use `stream: false` for the backend's JSON response without live logs. Options: `runtime`, `functionRunId`, `onLog`, `raiseOnFailure`, `timeoutMs`
719
+ - `get(options?)` - Function metadata, versions and download link
720
+ - `update(options)` / `updateMetadata(body)` - Upload a new version / change name, description
721
+ - `delete()` - Delete the function
722
+ - `setSchedule(body)` / `deleteSchedule()` - Manage the cron schedule
723
+ - `rollback(options)` - Roll back to a previous version
724
+ - `runs(options?)` - List runs (all runs by default, `only_active: true` for active ones)
725
+ - `createRun()` - Create a cloud run record without executing it
726
+ - `getRun(functionRunId)` - Get current metadata/result for a specific run
727
+ - `retrieve(functionRunId)` - Compatibility alias for `getRun()`
459
728
  - `download(options?)` - Download the function code, optionally writing it to `options.path`
460
729
  - `getUrl(options?)` - Get the (decrypted) download url of the function code
461
730
  - `getFunctionId()` - Get the function ID
462
731
  - `functionId` - The function ID (read-only property)
463
732
  - `decryptionKey` - The decryption key, if provided (read-only property)
464
733
 
734
+ ### File Storage
735
+
736
+ Upload files for a session to use and download files the session produced,
737
+ the counterpart of `client.FileStorage()` in Python.
738
+
739
+ ```typescript
740
+ import { NotteClient, FileExistsError } from 'notte-sdk';
741
+
742
+ const notte = new NotteClient();
743
+ const storage = notte.FileStorage();
744
+ let sessionId = '';
745
+ let fileId = '';
746
+
747
+ await notte.Session({ storage }).use(async (session) => {
748
+ sessionId = session.getId()!;
749
+
750
+ // Upload from a path, a Blob or a Uint8Array
751
+ const uploaded = await storage.upload('./invoice.pdf');
752
+ fileId = uploaded.id;
753
+
754
+ // ... let the session / an agent download something ...
755
+
756
+ const downloads = await storage.list({ source: 'session_download' });
757
+ for (const file of downloads.files) {
758
+ const path = await storage.download(file.id, './downloads');
759
+ console.log('saved', path);
760
+ }
761
+
762
+ const info = await storage.metadata(uploaded.id);
763
+ await storage.delete(uploaded.id);
764
+ });
765
+
766
+ // Files of a closed session
767
+ const files = notte.FileStorage(sessionId);
768
+ await files.download(fileId, './downloads', { force: true }); // overwrite instead of FileExistsError
769
+ ```
770
+
771
+ `notte.Files(sessionId)` exposes the same operations returning `Blob`s instead of writing to disk.
772
+
773
+ ### Web Search
774
+
775
+ Search the public web with `POST /search`. The default `outputType` returns
776
+ ranked results; `sourcedAnswer` returns a written answer with its sources.
777
+
778
+ ```typescript
779
+ const { results } = await notte.search('notte browser automation', { depth: 'fast' });
780
+ results.forEach(item => console.log(item.name, item.url));
781
+
782
+ const { answer, sources } = await notte.search('what is notte?', { outputType: 'sourcedAnswer' });
783
+ console.log(answer, sources.map(s => s.url));
784
+ ```
785
+
786
+ Extra fields such as `maxResults` or `includeDomains` are forwarded to the search
787
+ provider. `SearchOptions` and the `SearchResultsResponse` /
788
+ `SearchSourcedAnswerResponse` shapes are exported.
789
+
790
+ ### Anything API
791
+
792
+ `notte.anything.start({ task })` (`POST /anything/start`) turns a plain-English
793
+ description of a web task into a deployed, reusable function. Building is slow and
794
+ billable; prefer `notte.functions.list()` when a ready-made function already fits.
795
+ The API response is not typed by the OpenAPI spec and is exposed as an opaque
796
+ `AnythingStartResponse` record (it references the built function, e.g. `function_id`).
797
+
798
+ ```typescript
799
+ const started = await notte.anything.start({ task: 'fetch the top 3 hacker news posts' });
800
+ console.log(started);
801
+ ```
802
+
803
+ ### Secrets
804
+
805
+ Workspace secrets (`/secrets`) are scoped by namespace: `llm_provider` keys are
806
+ used by agents and scraping, `function_env` values are injected into function runs.
807
+ Listing never returns values, only metadata with a `key_hint`.
808
+
809
+ ```typescript
810
+ const meta = await notte.secrets.store({ namespace: 'function_env', name: 'API_TOKEN', value: 'xyz' });
811
+ const { value } = await notte.secrets.get('API_TOKEN', 'function_env');
812
+ const secrets = await notte.secrets.list({ namespace: 'function_env' });
813
+ await notte.secrets.delete(meta.id);
814
+ ```
815
+
816
+ ### Usage
817
+
818
+ Read billing usage for the current (or a given) monthly period, and page through
819
+ per-request usage logs.
820
+
821
+ ```typescript
822
+ const usage = await notte.usage.get();
823
+ console.log(`${usage.session_count} sessions, $${usage.total_cost} this period`);
824
+
825
+ const page = await notte.usage.logs({ endpoint: 'sessions.start', page: 1, page_size: 50 });
826
+ page.items.forEach(log => console.log(log.created_at, log.endpoint, log.duration_ms));
827
+ ```
828
+
465
829
  ## Advanced Usage
466
830
 
467
831
  ### Session Configuration
468
832
 
469
833
  ```typescript
470
834
  const session = notte.Session({
471
- timeoutMinutes: 45, // Session timeout (default: 30)
472
- // Add other session options as supported by the API
835
+ idle_timeout_minutes: 5, // Close the session after this much inactivity
836
+ max_duration_minutes: 45, // Hard limit on the session lifetime
837
+ // Add other session options as supported by the API (SessionOptions)
473
838
  });
474
839
  ```
475
840
 
@@ -484,36 +849,79 @@ const agent = notte.Agent({
484
849
 
485
850
  ### Error Handling
486
851
 
852
+ Every error thrown by the SDK extends `NotteError`, mirroring `notte_sdk.errors`
853
+ in Python. Generated API calls reject with a `NotteAPIError` carrying the request
854
+ `path`, the HTTP `statusCode` and the parsed response body in `error`; the API can
855
+ flag failures as `NotteAPIExecutionError`. Client-side problems use dedicated
856
+ classes: `AuthenticationError` (missing key), `InvalidRequestError`,
857
+ `NotteTimeoutError` (request or wait deadline exceeded),
858
+ `FailedToRunCloudFunctionError`, `ScrapeFailedError` and `ActionExecutionError`.
859
+
487
860
  ```typescript
861
+ import { NotteAPIError, NotteError, NotteTimeoutError } from 'notte-sdk';
862
+
488
863
  try {
489
864
  await notte.Session().use(async (session) => {
490
865
  const agent = notte.Agent({ session });
491
- const response = await agent.run("Complete a complex task");
866
+ const response = await agent.run({ task: "Complete a complex task" });
492
867
 
493
868
  if (!response.success) {
494
- console.error('Agent failed:', response.error);
869
+ console.error('Agent failed:', response.answer);
495
870
  }
496
871
  });
497
872
  } catch (error) {
498
- console.error('Session error:', error);
873
+ if (error instanceof NotteAPIError) {
874
+ console.error(`API ${error.statusCode} on ${error.path}:`, error.apiMessage ?? error.error);
875
+ } else if (error instanceof NotteTimeoutError) {
876
+ console.error('Timed out:', error.message);
877
+ } else if (error instanceof NotteError) {
878
+ console.error('SDK error:', error.message);
879
+ } else {
880
+ throw error;
881
+ }
499
882
  }
500
883
  ```
501
884
 
885
+ ### Timeouts and retries
886
+
887
+ The client applies a per-request timeout (`timeoutMs`, default 60 000 ms; `0`
888
+ disables it) through response-body consumption and rejects with `NotteTimeoutError`
889
+ when it elapses. Explicit streams use the caller's signal for body cancellation;
890
+ function runs supply their own execution deadline. `retry()` wraps an async function so it is re-attempted on failure, like the Python
891
+ `@retry` decorator:
892
+
893
+ ```typescript
894
+ import { NotteClient, retry } from 'notte-sdk';
895
+
896
+ const notte = new NotteClient({ timeoutMs: 30_000 });
897
+
898
+ const start = retry(() => notte.Session().start(), {
899
+ maxTries: 3,
900
+ delayMs: 1_000,
901
+ shouldRetry: error => error instanceof NotteTimeoutError,
902
+ });
903
+ await start();
904
+ ```
905
+
502
906
  ### WebSocket Updates
503
907
 
504
- The `agent.run()` method provides real-time updates via WebSocket:
908
+ The `agent.run()` method provides real-time updates via WebSocket through the
909
+ `updateHandler` option:
505
910
 
506
911
  ```typescript
507
- const response = await agent.run("Your task", (update) => {
508
- switch (update.type) {
509
- case 'step':
510
- console.log(`Step ${update.data.currentStep}:`, update.data);
511
- break;
512
- case 'completion':
513
- console.log('Task completed:', update.data);
514
- break;
515
- default:
516
- console.log('Update:', update);
912
+ const response = await agent.run({
913
+ task: "Your task",
914
+ updateHandler: (update) => {
915
+ switch (update.type) {
916
+ case 'step':
917
+ console.log('Step:', update.data);
918
+ break;
919
+ case 'completion':
920
+ console.log('Task completed:', update.data);
921
+ break;
922
+ default:
923
+ console.log('Update:', update);
924
+ }
517
925
  }
518
926
  });
519
927
  ```
@@ -551,7 +959,7 @@ from notte_sdk import NotteClient
551
959
  notte = NotteClient()
552
960
 
553
961
  # Session context manager
554
- with notte.Session(timeout_minutes=2) as session:
962
+ with notte.Session(idle_timeout_minutes=2) as session:
555
963
  status = session.status()
556
964
  print(status)
557
965
 
@@ -569,7 +977,7 @@ import { NotteClient } from 'notte-sdk';
569
977
  const notte = new NotteClient();
570
978
 
571
979
  // Session context manager equivalent
572
- await notte.Session({ timeoutMinutes: 2 }).use(async (session) => {
980
+ await notte.Session({ idle_timeout_minutes: 2 }).use(async (session) => {
573
981
  const status = await session.status();
574
982
  console.log(status);
575
983
  });
@@ -577,7 +985,7 @@ await notte.Session({ timeoutMinutes: 2 }).use(async (session) => {
577
985
  // Agent usage
578
986
  await notte.Session().use(async (session) => {
579
987
  const agent = notte.Agent({ session, max_steps: 10 });
580
- const response = await agent.run("Find the best italian restaurant in SF");
988
+ const response = await agent.run({ task: "Find the best italian restaurant in SF" });
581
989
  console.log(`Agent completed: ${response.success}, answer: ${response.answer}`);
582
990
  });
583
991
  ```
@@ -615,7 +1023,7 @@ npm test
615
1023
  npm run test -- --coverage
616
1024
 
617
1025
  # Run specific test file
618
- npm run test src/test/client.test.ts
1026
+ npm run test test/client.test.ts
619
1027
  ```
620
1028
 
621
1029
  ### Regenerating from API
@@ -634,15 +1042,15 @@ This will fetch the latest API specification and update the generated client cod
634
1042
  The SDK is built with full TypeScript support:
635
1043
 
636
1044
  ```typescript
637
- import { NotteClient, SessionStatus, AgentResponse } from 'notte-sdk';
1045
+ import { NotteClient, type SessionResponse, type LegacyAgentStatusResponse } from 'notte-sdk';
638
1046
 
639
1047
  const notte = new NotteClient();
640
1048
 
641
1049
  // All types are properly inferred
642
1050
  await notte.Session().use(async (session) => {
643
- const status: SessionStatus = await session.status();
1051
+ const status: SessionResponse = await session.status();
644
1052
  const agent = notte.Agent({ session });
645
- const response: AgentResponse = await agent.run("task");
1053
+ const response: LegacyAgentStatusResponse = await agent.run({ task: "task" });
646
1054
  });
647
1055
  ```
648
1056
 
@@ -650,24 +1058,35 @@ await notte.Session().use(async (session) => {
650
1058
 
651
1059
  ### Types
652
1060
 
653
- The SDK exports all generated types from the OpenAPI specification:
1061
+ The SDK exports all generated types from the OpenAPI specification alongside the
1062
+ hand-written option types:
654
1063
 
655
1064
  ```typescript
656
1065
  import type {
657
1066
  NotteClientConfig,
658
1067
  SessionOptions,
659
- SessionStatus,
1068
+ SessionResponse,
1069
+ SessionListOptions,
660
1070
  AgentConstructor,
661
1071
  AgentRunRequest,
662
1072
  AgentUpdateHandler,
1073
+ AgentListOptions,
663
1074
  VaultConstructor,
664
- CredentialsDict,
665
- CreditCardDict,
1075
+ VaultListOptions,
1076
+ CredentialsDictInput,
666
1077
  Credential,
667
1078
  PersonaConstructor,
668
1079
  MessageReadOptions,
669
1080
  PersonaListOptions,
670
- CreatePhoneNumberOptions
1081
+ FunctionConstructor,
1082
+ FunctionListOptions,
1083
+ RetryOptions,
1084
+ NotteAPIErrorBody,
1085
+ SearchOptions,
1086
+ SearchResultsResponse,
1087
+ AnythingStartResponse,
1088
+ SecretMetadata,
1089
+ UsageResponse,
671
1090
  } from 'notte-sdk';
672
1091
  ```
673
1092
 
@@ -678,6 +1097,6 @@ MIT
678
1097
  ## Support
679
1098
 
680
1099
  For issues and questions:
681
- - GitHub Issues: [Report issues](https://github.com/notte/sdk/issues)
1100
+ - GitHub Issues: [Report issues](https://github.com/nottelabs/notte/issues)
682
1101
  - Documentation: [docs.notte.cc](https://docs.notte.cc)
683
1102
  - Python SDK: [notte-sdk](https://pypi.org/project/notte-sdk/)