notte-sdk 0.0.19 → 0.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,7 +6,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  });
7
7
 
8
8
  // src/version.ts
9
- var SDK_VERSION = true ? "0.0.19" : "dev";
9
+ var SDK_VERSION = true ? "0.0.21" : "dev";
10
10
  var SDK_PACKAGE_NAME = true ? "notte-sdk" : "notte-sdk";
11
11
 
12
12
  // src/version-check.ts
@@ -1043,6 +1043,11 @@ var listAgents = (options) => (options?.client ?? client).get({
1043
1043
  url: "/agents",
1044
1044
  ...options
1045
1045
  });
1046
+ var functionRuntimeHealth = (options) => (options?.client ?? client).get({
1047
+ security: [{ scheme: "bearer", type: "http" }],
1048
+ url: "/functions/health",
1049
+ ...options
1050
+ });
1046
1051
  var listFunctions = (options) => (options?.client ?? client).get({
1047
1052
  security: [{ scheme: "bearer", type: "http" }],
1048
1053
  url: "/functions",
@@ -1068,6 +1073,15 @@ var functionDownloadUrl = (options) => (options.client ?? client).get({
1068
1073
  url: "/functions/{function_id}",
1069
1074
  ...options
1070
1075
  });
1076
+ var functionMetadataUpdate = (options) => (options.client ?? client).patch({
1077
+ security: [{ scheme: "bearer", type: "http" }],
1078
+ url: "/functions/{function_id}",
1079
+ ...options,
1080
+ headers: {
1081
+ "Content-Type": "application/json",
1082
+ ...options.headers
1083
+ }
1084
+ });
1071
1085
  var functionUpdate = (options) => (options.client ?? client).post({
1072
1086
  ...formDataBodySerializer,
1073
1087
  security: [{ scheme: "bearer", type: "http" }],
@@ -1083,6 +1097,15 @@ var functionFork = (options) => (options.client ?? client).post({
1083
1097
  url: "/functions/{function_id}/fork",
1084
1098
  ...options
1085
1099
  });
1100
+ var functionRollback = (options) => (options.client ?? client).post({
1101
+ security: [{ scheme: "bearer", type: "http" }],
1102
+ url: "/functions/{function_id}/rollback",
1103
+ ...options,
1104
+ headers: {
1105
+ "Content-Type": "application/json",
1106
+ ...options.headers
1107
+ }
1108
+ });
1086
1109
  var functionScheduleDelete = (options) => (options.client ?? client).delete({
1087
1110
  security: [{ scheme: "bearer", type: "http" }],
1088
1111
  url: "/functions/{function_id}/schedule",
@@ -1333,6 +1356,42 @@ var mailboxUpdate = (options) => (options.client ?? client).patch({
1333
1356
  ...options.headers
1334
1357
  }
1335
1358
  });
1359
+ var managedAuthConnectLinkCreate = (options) => (options.client ?? client).post({
1360
+ security: [{ scheme: "bearer", type: "http" }],
1361
+ url: "/managed-auth/connect-links",
1362
+ ...options,
1363
+ headers: {
1364
+ "Content-Type": "application/json",
1365
+ ...options.headers
1366
+ }
1367
+ });
1368
+ var connectLinkContext = (options) => (options.client ?? client).post({ url: "/managed-auth/connect-links/context", ...options });
1369
+ var connectLinkMailboxes = (options) => (options.client ?? client).post({ url: "/managed-auth/connect-links/mailboxes", ...options });
1370
+ var connectLinkMailboxConnect = (options) => (options.client ?? client).post({
1371
+ url: "/managed-auth/connect-links/mailboxes/connect",
1372
+ ...options,
1373
+ headers: {
1374
+ "Content-Type": "application/json",
1375
+ ...options.headers
1376
+ }
1377
+ });
1378
+ var connectLinkMailboxSync = (options) => (options.client ?? client).post({
1379
+ url: "/managed-auth/connect-links/mailboxes/sync",
1380
+ ...options,
1381
+ headers: {
1382
+ "Content-Type": "application/json",
1383
+ ...options.headers
1384
+ }
1385
+ });
1386
+ var connectLinkSubmit = (options) => (options.client ?? client).post({
1387
+ url: "/managed-auth/connect-links/submit",
1388
+ ...options,
1389
+ headers: {
1390
+ "Content-Type": "application/json",
1391
+ ...options.headers
1392
+ }
1393
+ });
1394
+ var connectLinkStatus = (options) => (options.client ?? client).get({ url: "/managed-auth/connect-links/status", ...options });
1336
1395
  var improvePrompt = (options) => (options.client ?? client).post({
1337
1396
  url: "/prompts/improve",
1338
1397
  ...options,
@@ -1546,14 +1605,24 @@ var Session = class {
1546
1605
  this.isActive = false;
1547
1606
  this.response = null;
1548
1607
  this.client = client2;
1549
- const { open_viewer = false, ...sessionOptions } = options;
1608
+ const { open_viewer = false, headless, ...sessionOptions } = options;
1550
1609
  this.options = sessionOptions;
1551
1610
  this.openViewer = open_viewer;
1611
+ this.legacyHeadless = headless;
1552
1612
  }
1553
1613
  /**
1554
1614
  * Start the session - creates a new session on the server
1555
1615
  */
1556
1616
  async start() {
1617
+ if (this.legacyHeadless === false) {
1618
+ throw new Error(
1619
+ "Remote `headless: false` is no longer supported. `headless: true` sessions still include session replays and access to the live viewer."
1620
+ );
1621
+ }
1622
+ if (this.legacyHeadless === true) {
1623
+ console.warn("`headless: true` is deprecated and ignored; remote sessions always use the supported browser launch mode.");
1624
+ this.legacyHeadless = void 0;
1625
+ }
1557
1626
  if (this.isActive) {
1558
1627
  throw new Error("Session is already active");
1559
1628
  }
@@ -1581,7 +1650,7 @@ var Session = class {
1581
1650
  /**
1582
1651
  * Stop the session - terminates the session on the server
1583
1652
  */
1584
- async stop() {
1653
+ async stop(closeReason = "manual") {
1585
1654
  if (!this.isActive || !this.sessionId) {
1586
1655
  return;
1587
1656
  }
@@ -1589,6 +1658,9 @@ var Session = class {
1589
1658
  client: this.client.getClient(),
1590
1659
  path: {
1591
1660
  session_id: this.sessionId
1661
+ },
1662
+ query: {
1663
+ close_reason: closeReason
1592
1664
  }
1593
1665
  });
1594
1666
  if (response?.error) {
@@ -1807,10 +1879,21 @@ var Session = class {
1807
1879
  */
1808
1880
  async use(callback) {
1809
1881
  await this.start();
1882
+ let callbackFailed = false;
1810
1883
  try {
1811
1884
  return await callback(this);
1885
+ } catch (error) {
1886
+ callbackFailed = true;
1887
+ throw error;
1812
1888
  } finally {
1813
- await this.stop();
1889
+ try {
1890
+ await this.stop(callbackFailed ? "error" : "manual");
1891
+ } catch (stopError) {
1892
+ if (!callbackFailed) {
1893
+ throw stopError;
1894
+ }
1895
+ console.error("[Session] Failed to stop session after callback error:", stopError);
1896
+ }
1814
1897
  }
1815
1898
  }
1816
1899
  /**
@@ -2742,6 +2825,59 @@ var NottePersona = class {
2742
2825
  };
2743
2826
 
2744
2827
  // src/functions.ts
2828
+ import { writeFile } from "fs/promises";
2829
+
2830
+ // src/encryption.ts
2831
+ import { createDecipheriv, createHmac, pbkdf2Sync, timingSafeEqual } from "crypto";
2832
+ var SALT_LENGTH = 16;
2833
+ var PBKDF2_ITERATIONS = 1e5;
2834
+ var PBKDF2_KEY_LENGTH = 32;
2835
+ var FERNET_VERSION = 128;
2836
+ var FERNET_IV_LENGTH = 16;
2837
+ var FERNET_HMAC_LENGTH = 32;
2838
+ var FERNET_HEADER_LENGTH = 1 + 8 + FERNET_IV_LENGTH;
2839
+ var Encryption = class {
2840
+ constructor(rootKey) {
2841
+ if (!rootKey) {
2842
+ throw new Error("Root key cannot be empty");
2843
+ }
2844
+ this.rootKey = rootKey;
2845
+ }
2846
+ decrypt(token) {
2847
+ const combined = Buffer.from(token, "base64url");
2848
+ if (combined.length <= SALT_LENGTH) {
2849
+ throw new Error("Invalid encrypted token: too short to contain a salt");
2850
+ }
2851
+ const salt = combined.subarray(0, SALT_LENGTH);
2852
+ const fernetToken = combined.subarray(SALT_LENGTH).toString("utf8");
2853
+ const key = pbkdf2Sync(this.rootKey, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LENGTH, "sha256");
2854
+ return decryptFernet(fernetToken, key);
2855
+ }
2856
+ };
2857
+ function decryptFernet(token, key) {
2858
+ const signingKey = key.subarray(0, 16);
2859
+ const encryptionKey = key.subarray(16);
2860
+ const data = Buffer.from(token, "base64url");
2861
+ if (data.length < FERNET_HEADER_LENGTH + FERNET_HMAC_LENGTH) {
2862
+ throw new Error("Invalid Fernet token: too short");
2863
+ }
2864
+ if (data[0] !== FERNET_VERSION) {
2865
+ throw new Error(`Invalid Fernet token: unsupported version 0x${data[0].toString(16)}`);
2866
+ }
2867
+ const signed = data.subarray(0, data.length - FERNET_HMAC_LENGTH);
2868
+ const signature = data.subarray(data.length - FERNET_HMAC_LENGTH);
2869
+ const expected = createHmac("sha256", signingKey).update(signed).digest();
2870
+ if (!timingSafeEqual(signature, expected)) {
2871
+ throw new Error("Invalid Fernet token: signature verification failed");
2872
+ }
2873
+ const iv = signed.subarray(9, FERNET_HEADER_LENGTH);
2874
+ const ciphertext = signed.subarray(FERNET_HEADER_LENGTH);
2875
+ const decipher = createDecipheriv("aes-128-cbc", encryptionKey, iv);
2876
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
2877
+ }
2878
+
2879
+ // src/functions.ts
2880
+ var DOWNLOAD_TIMEOUT_MS = 3e4;
2745
2881
  var NotteFunction = class {
2746
2882
  constructor(client2, options) {
2747
2883
  this.client = client2;
@@ -2771,6 +2907,48 @@ var NotteFunction = class {
2771
2907
  throw new Error(`Failed to get function run metadata: ${error instanceof Error ? error.message : String(error)}`);
2772
2908
  }
2773
2909
  }
2910
+ /**
2911
+ * List the runs of this function.
2912
+ *
2913
+ * `only_active` is sent as `false` unless the caller says otherwise, and that
2914
+ * is deliberate rather than redundant. `GET /functions/{function_id}/runs`
2915
+ * now defaults it to `false` server-side as well, but it used to default to
2916
+ * `true`: the request model was a bare subclass of the session one, where
2917
+ * "only active" reads sensibly as "list my running sessions" and, for "list
2918
+ * this function's runs", was a trap. Hitting the endpoint with no query
2919
+ * parameters returned only the runs executing right now - an empty list for
2920
+ * any function that had finished. That cost us a bug in the anything-api
2921
+ * console, where a function with 15 recorded runs displayed "Nothing has run
2922
+ * this endpoint yet".
2923
+ *
2924
+ * Sending it explicitly pins this method to the documented behaviour against
2925
+ * whichever API version it is talking to, including deployments that predate
2926
+ * the server-side change. Callers who want only in-flight runs pass
2927
+ * `only_active: true`.
2928
+ */
2929
+ async runs(options = {}) {
2930
+ try {
2931
+ const response = await listFunctionRunsByFunctionId({
2932
+ client: this.client.getClient(),
2933
+ path: {
2934
+ function_id: this.functionId
2935
+ },
2936
+ query: {
2937
+ ...options,
2938
+ only_active: options.only_active ?? false
2939
+ }
2940
+ });
2941
+ if (response?.error) {
2942
+ throw new Error(`Failed to list function runs: ${formatError(response.error)}`);
2943
+ }
2944
+ return response.data;
2945
+ } catch (error) {
2946
+ if (error instanceof Error && error.message.startsWith("Failed to list function runs:")) {
2947
+ throw error;
2948
+ }
2949
+ throw new Error(`Failed to list function runs: ${error instanceof Error ? error.message : String(error)}`);
2950
+ }
2951
+ }
2774
2952
  /**
2775
2953
  * Start a function run
2776
2954
  */
@@ -2807,6 +2985,72 @@ var NotteFunction = class {
2807
2985
  throw new Error(`Failed to run function: ${error instanceof Error ? error.message : String(error)}`);
2808
2986
  }
2809
2987
  }
2988
+ /**
2989
+ * Get the download URL of the function code, decrypting it when the API
2990
+ * returns an encrypted one (Notte managed functions).
2991
+ */
2992
+ async getUrl(options = {}) {
2993
+ let response;
2994
+ try {
2995
+ response = await functionDownloadUrl({
2996
+ client: this.client.getClient(),
2997
+ path: {
2998
+ function_id: this.functionId
2999
+ },
3000
+ query: options.version ? { version: options.version } : {}
3001
+ });
3002
+ } catch (error) {
3003
+ throw new Error(`Failed to get function download url: ${error instanceof Error ? error.message : String(error)}`);
3004
+ }
3005
+ if (response?.error) {
3006
+ throw new Error(`Failed to get function download url: ${formatError(response.error)}`);
3007
+ }
3008
+ const url = response.data.url;
3009
+ if (isHttpUrl(url)) {
3010
+ return url;
3011
+ }
3012
+ const decryptionKey = options.decryption_key ?? this.decryptionKey;
3013
+ if (!decryptionKey) {
3014
+ throw new Error(
3015
+ "Decryption key is required to decrypt the function download url. Set it when creating the function, i.e. client.NotteFunction({ function_id: '<your-function-id>', decryption_key: '<your-key>' })."
3016
+ );
3017
+ }
3018
+ let decrypted;
3019
+ try {
3020
+ decrypted = new Encryption(decryptionKey).decrypt(url);
3021
+ } catch (error) {
3022
+ throw new Error(`Failed to decrypt the function download url: ${error instanceof Error ? error.message : String(error)}. Contact support@notte.cc if you need help.`);
3023
+ }
3024
+ if (!isHttpUrl(decrypted)) {
3025
+ throw new Error("Failed to decrypt the function download url: the decrypted value is not a URL. Contact support@notte.cc if you need help.");
3026
+ }
3027
+ return decrypted;
3028
+ }
3029
+ /**
3030
+ * Download the function code as a python file. Returns the code, and writes
3031
+ * it to `path` when one is provided.
3032
+ */
3033
+ async download(options = {}) {
3034
+ const { path } = options;
3035
+ if (path !== void 0 && !path.endsWith(".py")) {
3036
+ throw new Error(`Code file path must end with .py, got '${path}'`);
3037
+ }
3038
+ const url = await this.getUrl(options);
3039
+ let response;
3040
+ try {
3041
+ response = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
3042
+ } catch (error) {
3043
+ throw new Error(`Failed to download function code: ${error instanceof Error ? error.message : String(error)}`);
3044
+ }
3045
+ if (!response.ok) {
3046
+ throw new Error(`Failed to download function code: ${response.status} ${response.statusText}`);
3047
+ }
3048
+ const code = await response.text();
3049
+ if (path !== void 0) {
3050
+ await writeFile(path, code, "utf8");
3051
+ }
3052
+ return code;
3053
+ }
2810
3054
  /**
2811
3055
  * Get the function ID
2812
3056
  */
@@ -2814,6 +3058,9 @@ var NotteFunction = class {
2814
3058
  return this.functionId;
2815
3059
  }
2816
3060
  };
3061
+ function isHttpUrl(value) {
3062
+ return value.startsWith("https://") || value.startsWith("http://");
3063
+ }
2817
3064
 
2818
3065
  // src/client.ts
2819
3066
  import { z as z3 } from "zod";
@@ -2986,6 +3233,7 @@ var createClient2 = (config) => {
2986
3233
  };
2987
3234
  export {
2988
3235
  Agent,
3236
+ Encryption,
2989
3237
  NotteClient,
2990
3238
  NotteFunction,
2991
3239
  NottePersona,
@@ -2997,6 +3245,12 @@ export {
2997
3245
  agentStop,
2998
3246
  anythingStart,
2999
3247
  client,
3248
+ connectLinkContext,
3249
+ connectLinkMailboxConnect,
3250
+ connectLinkMailboxSync,
3251
+ connectLinkMailboxes,
3252
+ connectLinkStatus,
3253
+ connectLinkSubmit,
3000
3254
  createClient2 as createClient,
3001
3255
  NotteClient as default,
3002
3256
  deleteSecret,
@@ -3010,10 +3264,13 @@ export {
3010
3264
  functionDelete,
3011
3265
  functionDownloadUrl,
3012
3266
  functionFork,
3267
+ functionMetadataUpdate,
3268
+ functionRollback,
3013
3269
  functionRunGetMetadata,
3014
3270
  functionRunStart,
3015
3271
  functionRunStop,
3016
3272
  functionRunUpdateMetadata,
3273
+ functionRuntimeHealth,
3017
3274
  functionScheduleDelete,
3018
3275
  functionScheduleSet,
3019
3276
  functionUpdate,
@@ -3037,6 +3294,7 @@ export {
3037
3294
  mailboxList,
3038
3295
  mailboxSync,
3039
3296
  mailboxUpdate,
3297
+ managedAuthConnectLinkCreate,
3040
3298
  nudgePrompt,
3041
3299
  pageExecute,
3042
3300
  pageObserve,