@persistmemory/sdk 0.1.2 → 0.2.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/dist/client.d.ts CHANGED
@@ -5,6 +5,7 @@ import { Spaces } from "./resources/spaces.js";
5
5
  import { Documents, Jobs, Sources } from "./resources/ingestion.js";
6
6
  import { Conflicts, Entities, Graph } from "./resources/knowledge.js";
7
7
  import { Conversations } from "./resources/conversations.js";
8
+ import { Google } from "./resources/google.js";
8
9
  import { Integrations } from "./resources/integrations.js";
9
10
  import { Health } from "./resources/health.js";
10
11
  import { Agent } from "./resources/agent.js";
@@ -32,6 +33,8 @@ export declare class PersistMemory {
32
33
  readonly conflicts: Conflicts;
33
34
  readonly conversations: Conversations;
34
35
  readonly integrations: Integrations;
36
+ /** Drive, mail and contacts on the user's connected Google account. */
37
+ readonly google: Google;
35
38
  readonly health: Health;
36
39
  readonly agent: Agent;
37
40
  constructor(options: ClientOptions);
package/dist/http.d.ts CHANGED
@@ -64,6 +64,18 @@ interface InternalRequest {
64
64
  readonly path: string;
65
65
  readonly query?: QueryParams;
66
66
  readonly body?: unknown;
67
+ /**
68
+ * Bytes, for the endpoints that take a file.
69
+ *
70
+ * Separate from `body` rather than sniffed out of it: `JSON.stringify` of a
71
+ * `Uint8Array` produces `{"0":137,"1":80,...}`, which is a valid request and
72
+ * a corrupt file - and the failure shows up as "this PDF is not a PDF" a
73
+ * long way from the line that caused it.
74
+ */
75
+ readonly rawBody?: Uint8Array;
76
+ readonly contentType?: string;
77
+ /** Bytes back, for a download. JSON is parsed; a file is not. */
78
+ readonly rawResponse?: boolean;
67
79
  readonly options?: RequestOptions;
68
80
  }
69
81
  export declare class HttpClient {
@@ -80,6 +92,14 @@ export declare class HttpClient {
80
92
  toJSON(): Record<string, unknown>;
81
93
  get<T>(path: string, query?: QueryParams, options?: RequestOptions): Promise<T>;
82
94
  post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
95
+ /** POST with a file as the body. The type describes the bytes, not JSON. */
96
+ postBytes<T>(path: string, bytes: Uint8Array, contentType: string, query?: QueryParams, options?: RequestOptions): Promise<T>;
97
+ /** GET that returns bytes rather than JSON, for downloading a file. */
98
+ getBytes(path: string, query?: QueryParams, options?: RequestOptions): Promise<{
99
+ bytes: Uint8Array;
100
+ contentType: string;
101
+ filename?: string;
102
+ }>;
83
103
  patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
84
104
  delete<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
85
105
  }
package/dist/index.cjs CHANGED
@@ -279,6 +279,27 @@ var HttpClient = class {
279
279
  ...options ? { options } : {}
280
280
  });
281
281
  }
282
+ /** POST with a file as the body. The type describes the bytes, not JSON. */
283
+ async postBytes(path, bytes, contentType, query, options) {
284
+ return this.#request({
285
+ method: "POST",
286
+ path,
287
+ rawBody: bytes,
288
+ contentType,
289
+ ...query ? { query } : {},
290
+ ...options ? { options } : {}
291
+ });
292
+ }
293
+ /** GET that returns bytes rather than JSON, for downloading a file. */
294
+ async getBytes(path, query, options) {
295
+ return this.#request({
296
+ method: "GET",
297
+ path,
298
+ rawResponse: true,
299
+ ...query ? { query } : {},
300
+ ...options ? { options } : {}
301
+ });
302
+ }
282
303
  async patch(path, body, options) {
283
304
  return this.#request({
284
305
  method: "PATCH",
@@ -331,11 +352,20 @@ var HttpClient = class {
331
352
  this.#fetch(url, {
332
353
  method: request.method,
333
354
  headers: this.#headers(request),
334
- ...request.body !== void 0 ? { body: JSON.stringify(request.body) } : {},
355
+ ...request.rawBody !== void 0 ? { body: request.rawBody } : request.body !== void 0 ? { body: JSON.stringify(request.body) } : {},
335
356
  signal: deadline.signal
336
357
  }),
337
358
  deadline.signal
338
359
  );
360
+ if (request.rawResponse && response.ok) {
361
+ const disposition = response.headers.get("content-disposition") ?? "";
362
+ const named = /filename="([^"]+)"/.exec(disposition)?.[1];
363
+ return {
364
+ bytes: new Uint8Array(await response.arrayBuffer()),
365
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
366
+ ...named ? { filename: named } : {}
367
+ };
368
+ }
339
369
  const payload = await readBody(response);
340
370
  if (!response.ok) throw errorFromResponse(response.status, payload, response.headers);
341
371
  return payload;
@@ -356,9 +386,11 @@ var HttpClient = class {
356
386
  return {
357
387
  // The only place the key is ever read.
358
388
  authorization: `Bearer ${this.#apiKey}`,
359
- accept: "application/json",
389
+ // A download route answers with the file's own type, so `*/*` rather
390
+ // than a promise to accept only JSON that the server would have to break.
391
+ accept: request.rawResponse ? "*/*" : "application/json",
360
392
  "user-agent": this.#userAgent,
361
- ...request.body !== void 0 ? { "content-type": "application/json" } : {},
393
+ ...request.rawBody !== void 0 ? { "content-type": request.contentType ?? "application/octet-stream" } : request.body !== void 0 ? { "content-type": "application/json" } : {},
362
394
  ...request.options?.idempotencyKey ? { "idempotency-key": request.options.idempotencyKey } : {}
363
395
  };
364
396
  }
@@ -950,6 +982,121 @@ var Conversations = class {
950
982
  }
951
983
  };
952
984
 
985
+ // src/resources/google.ts
986
+ var Google = class {
987
+ #http;
988
+ constructor(http) {
989
+ this.#http = http;
990
+ }
991
+ /** Files by name, newest first. Omit the query for recently changed ones. */
992
+ async searchDrive(params = {}, options) {
993
+ return this.#http.get(
994
+ "/api/v1/google/drive/files",
995
+ {
996
+ ...params.query !== void 0 ? { query: params.query } : {},
997
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
998
+ },
999
+ options
1000
+ );
1001
+ }
1002
+ async getDriveFile(fileId, options) {
1003
+ return this.#http.get(
1004
+ `/api/v1/google/drive/files/${encodeURIComponent(fileId)}`,
1005
+ void 0,
1006
+ options
1007
+ );
1008
+ }
1009
+ /**
1010
+ * The bytes of a Drive file.
1011
+ *
1012
+ * A Google Doc, Sheet or Slide holds no bytes of its own and is exported on
1013
+ * the way - a document as PDF, a spreadsheet as CSV - so `filename` comes
1014
+ * back describing what it BECAME. Writing it under the id instead produces a
1015
+ * file nothing will open.
1016
+ */
1017
+ async downloadDriveFile(fileId, options) {
1018
+ return this.#http.getBytes(
1019
+ `/api/v1/google/drive/files/${encodeURIComponent(fileId)}/content`,
1020
+ void 0,
1021
+ options
1022
+ );
1023
+ }
1024
+ /**
1025
+ * Writes a file into the user's Drive.
1026
+ *
1027
+ * Needs one of the Drive write permissions on their connection. A read-only
1028
+ * grant is refused by Google, and the error names the missing permission
1029
+ * rather than reporting a failed upload - one is fixed with a checkbox and
1030
+ * the other sends somebody looking for a bug.
1031
+ */
1032
+ async saveToDrive(params, options) {
1033
+ return this.#http.postBytes(
1034
+ "/api/v1/google/drive/files",
1035
+ params.bytes,
1036
+ params.contentType ?? "application/octet-stream",
1037
+ {
1038
+ name: params.name,
1039
+ ...params.folderId !== void 0 ? { folderId: params.folderId } : {}
1040
+ },
1041
+ options
1042
+ );
1043
+ }
1044
+ /**
1045
+ * Recent messages - senders, subjects and a one-line preview, never bodies.
1046
+ *
1047
+ * `query` is Gmail's own syntax passed through as written: `from:priya`,
1048
+ * `has:attachment`, `newer_than:7d`. It selects within the connected mailbox
1049
+ * and cannot reach another one.
1050
+ */
1051
+ async searchMail(params = {}, options) {
1052
+ return this.#http.get(
1053
+ "/api/v1/google/mail",
1054
+ {
1055
+ ...params.query !== void 0 ? { query: params.query } : {},
1056
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
1057
+ },
1058
+ options
1059
+ );
1060
+ }
1061
+ /** One message, with its body and the names of what is attached. */
1062
+ async readMail(messageId, options) {
1063
+ return this.#http.get(
1064
+ `/api/v1/google/mail/${encodeURIComponent(messageId)}`,
1065
+ void 0,
1066
+ options
1067
+ );
1068
+ }
1069
+ /**
1070
+ * The bytes of one attachment.
1071
+ *
1072
+ * Separate from `readMail` so listing a mailbox never drags attachments
1073
+ * across the network: a message with a 40 MB deck should not cost 40 MB to
1074
+ * summarise.
1075
+ */
1076
+ async downloadAttachment(messageId, attachmentId, options) {
1077
+ return this.#http.getBytes(
1078
+ `/api/v1/google/mail/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`,
1079
+ void 0,
1080
+ options
1081
+ );
1082
+ }
1083
+ /** Sends as the connected account. Needs the send permission. */
1084
+ async sendMail(params, options) {
1085
+ return this.#http.post("/api/v1/google/mail/send", params, options);
1086
+ }
1087
+ /** People in the user's contacts. Omit the query to list them. */
1088
+ async contacts(params = {}, options) {
1089
+ return this.#http.get(
1090
+ "/api/v1/google/contacts",
1091
+ {
1092
+ ...params.query !== void 0 ? { query: params.query } : {},
1093
+ ...params.limit !== void 0 ? { limit: params.limit } : {}
1094
+ },
1095
+ options
1096
+ );
1097
+ }
1098
+ };
1099
+
953
1100
  // src/resources/integrations.ts
954
1101
  var Integrations = class {
955
1102
  #http;
@@ -1087,6 +1234,8 @@ var PersistMemory = class {
1087
1234
  conflicts;
1088
1235
  conversations;
1089
1236
  integrations;
1237
+ /** Drive, mail and contacts on the user's connected Google account. */
1238
+ google;
1090
1239
  health;
1091
1240
  agent;
1092
1241
  #http;
@@ -1103,6 +1252,7 @@ var PersistMemory = class {
1103
1252
  this.conflicts = new Conflicts(this.#http);
1104
1253
  this.conversations = new Conversations(this.#http);
1105
1254
  this.integrations = new Integrations(this.#http);
1255
+ this.google = new Google(this.#http);
1106
1256
  this.health = new Health(this.#http);
1107
1257
  this.agent = new Agent(this.#http);
1108
1258
  }