@persistmemory/sdk 0.1.2 → 0.3.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 +3 -0
- package/dist/http.d.ts +20 -0
- package/dist/index.cjs +253 -10
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +253 -10
- package/dist/index.js.map +3 -3
- package/dist/resources/google.d.ts +84 -0
- package/dist/resources/spaces.d.ts +73 -6
- package/dist/types.d.ts +131 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
}
|
|
@@ -670,17 +702,22 @@ var Spaces = class {
|
|
|
670
702
|
/**
|
|
671
703
|
* The memories filed in a Space.
|
|
672
704
|
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
705
|
+
* The cursor is PASSED. This fetch used to ignore the paginator's cursor
|
|
706
|
+
* on the stale belief that the endpoint had none — the server has minted
|
|
707
|
+
* `pagination.nextCursor` since it started paging, and its own comment
|
|
708
|
+
* says "both SDKs iterate by reading pagination". Ignoring it meant every
|
|
709
|
+
* page request was identical: the loop guard saw a non-advancing fetch and
|
|
710
|
+
* stopped silently, so `all()` returned the first page twice and dropped
|
|
711
|
+
* everything after it — duplicated AND truncated data, with no error.
|
|
678
712
|
*/
|
|
679
713
|
memories(id, params = {}, options) {
|
|
680
714
|
return new Paginated(
|
|
681
|
-
() => this.#http.get(
|
|
715
|
+
(cursor) => this.#http.get(
|
|
682
716
|
`/api/v1/spaces/${encodeURIComponent(id)}/memories`,
|
|
683
|
-
{
|
|
717
|
+
{
|
|
718
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
719
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
720
|
+
},
|
|
684
721
|
options
|
|
685
722
|
)
|
|
686
723
|
);
|
|
@@ -706,6 +743,94 @@ var Spaces = class {
|
|
|
706
743
|
options
|
|
707
744
|
);
|
|
708
745
|
}
|
|
746
|
+
/* ----------------------- who else can see it ----------------------- */
|
|
747
|
+
/**
|
|
748
|
+
* Who can see this Space, including invitations nobody has accepted.
|
|
749
|
+
*
|
|
750
|
+
* A DIFFERENT EDGE from `memories()` next door, and the difference is worth
|
|
751
|
+
* holding on to: that one maps a MEMORY to a Space, this one maps a PERSON
|
|
752
|
+
* to a Space. The server keeps them in two tables with two names for exactly
|
|
753
|
+
* that reason.
|
|
754
|
+
*
|
|
755
|
+
* Read `acceptedAt` before you render a row. An invitation grants nothing
|
|
756
|
+
* until it is accepted, so a list that draws invited and accepted people the
|
|
757
|
+
* same way tells its user somebody is reading their memories when nobody is.
|
|
758
|
+
*
|
|
759
|
+
* Paginated like every other list here. A Space has a handful of
|
|
760
|
+
* collaborators rather than thousands, so this will usually be one page -
|
|
761
|
+
* which costs a caller nothing and means the shape does not change if a
|
|
762
|
+
* Space ever has an organisation on it.
|
|
763
|
+
*/
|
|
764
|
+
collaborators(id, params = {}, options) {
|
|
765
|
+
return new Paginated(
|
|
766
|
+
(cursor) => this.#http.get(
|
|
767
|
+
`/api/v1/sharing/spaces/${encodeURIComponent(id)}/collaborators`,
|
|
768
|
+
{
|
|
769
|
+
...params.limit !== void 0 ? { limit: params.limit } : {},
|
|
770
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
771
|
+
},
|
|
772
|
+
options
|
|
773
|
+
)
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Offers somebody sight of a Space. Answers with the invitation.
|
|
778
|
+
*
|
|
779
|
+
* AN OFFER, NOT A GRANT, and the returned `acceptedAt` will be absent to
|
|
780
|
+
* prove it. The recipient has to accept before they can see anything, which
|
|
781
|
+
* is the property that keeps "nothing enters your memory without you" true
|
|
782
|
+
* even when somebody else starts the sharing. Do not tell your user their
|
|
783
|
+
* Space "has been shared" on the strength of a 2xx here.
|
|
784
|
+
*
|
|
785
|
+
* WHAT THEY GET IS THE WHOLE SPACE: every memory already filed in it and
|
|
786
|
+
* every memory that lands in it afterwards. There is no narrower grant, and
|
|
787
|
+
* `role` does not make one - it decides what they may do BESIDES read.
|
|
788
|
+
*
|
|
789
|
+
* Worth an idempotency key when a person is behind it. A double-clicked
|
|
790
|
+
* "share" is two invitations to the same address, and the second one is a
|
|
791
|
+
* second email arriving at somebody who has already been asked.
|
|
792
|
+
*/
|
|
793
|
+
async share(id, params, options) {
|
|
794
|
+
return this.#http.post(
|
|
795
|
+
`/api/v1/sharing/spaces/${encodeURIComponent(id)}/collaborators`,
|
|
796
|
+
params,
|
|
797
|
+
options
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Ends somebody's access, or withdraws an invitation they never accepted.
|
|
802
|
+
*
|
|
803
|
+
* Nothing was ever copied into their account - a collaborator SEES the
|
|
804
|
+
* owner's memories rather than holding a duplicate - so this is one write
|
|
805
|
+
* and not a cascade, and there is no orphaned copy left behind.
|
|
806
|
+
*
|
|
807
|
+
* A body on a DELETE, matching `removeMemories` above. The alternative is an
|
|
808
|
+
* address in a path segment, where every `.`, `+` and `@` is a chance for a
|
|
809
|
+
* proxy or a router to normalise somebody else's email into the one that
|
|
810
|
+
* gets revoked.
|
|
811
|
+
*/
|
|
812
|
+
async unshare(id, email, options) {
|
|
813
|
+
return this.#http.delete(
|
|
814
|
+
`/api/v1/sharing/spaces/${encodeURIComponent(id)}/collaborators`,
|
|
815
|
+
{ email },
|
|
816
|
+
options
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Changes what an existing collaborator may do. Never invites anybody.
|
|
821
|
+
*
|
|
822
|
+
* The quiet one. Moving somebody from `viewer` to `owner` sends no
|
|
823
|
+
* invitation and needs no acceptance, and afterwards they can share the
|
|
824
|
+
* Space onward and revoke the person who promoted them. Show your user what
|
|
825
|
+
* `owner` means before you send this, not after.
|
|
826
|
+
*/
|
|
827
|
+
async setRole(id, params, options) {
|
|
828
|
+
return this.#http.patch(
|
|
829
|
+
`/api/v1/sharing/spaces/${encodeURIComponent(id)}/collaborators`,
|
|
830
|
+
params,
|
|
831
|
+
options
|
|
832
|
+
);
|
|
833
|
+
}
|
|
709
834
|
};
|
|
710
835
|
|
|
711
836
|
// src/resources/ingestion.ts
|
|
@@ -950,6 +1075,121 @@ var Conversations = class {
|
|
|
950
1075
|
}
|
|
951
1076
|
};
|
|
952
1077
|
|
|
1078
|
+
// src/resources/google.ts
|
|
1079
|
+
var Google = class {
|
|
1080
|
+
#http;
|
|
1081
|
+
constructor(http) {
|
|
1082
|
+
this.#http = http;
|
|
1083
|
+
}
|
|
1084
|
+
/** Files by name, newest first. Omit the query for recently changed ones. */
|
|
1085
|
+
async searchDrive(params = {}, options) {
|
|
1086
|
+
return this.#http.get(
|
|
1087
|
+
"/api/v1/google/drive/files",
|
|
1088
|
+
{
|
|
1089
|
+
...params.query !== void 0 ? { query: params.query } : {},
|
|
1090
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
1091
|
+
},
|
|
1092
|
+
options
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
async getDriveFile(fileId, options) {
|
|
1096
|
+
return this.#http.get(
|
|
1097
|
+
`/api/v1/google/drive/files/${encodeURIComponent(fileId)}`,
|
|
1098
|
+
void 0,
|
|
1099
|
+
options
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* The bytes of a Drive file.
|
|
1104
|
+
*
|
|
1105
|
+
* A Google Doc, Sheet or Slide holds no bytes of its own and is exported on
|
|
1106
|
+
* the way - a document as PDF, a spreadsheet as CSV - so `filename` comes
|
|
1107
|
+
* back describing what it BECAME. Writing it under the id instead produces a
|
|
1108
|
+
* file nothing will open.
|
|
1109
|
+
*/
|
|
1110
|
+
async downloadDriveFile(fileId, options) {
|
|
1111
|
+
return this.#http.getBytes(
|
|
1112
|
+
`/api/v1/google/drive/files/${encodeURIComponent(fileId)}/content`,
|
|
1113
|
+
void 0,
|
|
1114
|
+
options
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Writes a file into the user's Drive.
|
|
1119
|
+
*
|
|
1120
|
+
* Needs one of the Drive write permissions on their connection. A read-only
|
|
1121
|
+
* grant is refused by Google, and the error names the missing permission
|
|
1122
|
+
* rather than reporting a failed upload - one is fixed with a checkbox and
|
|
1123
|
+
* the other sends somebody looking for a bug.
|
|
1124
|
+
*/
|
|
1125
|
+
async saveToDrive(params, options) {
|
|
1126
|
+
return this.#http.postBytes(
|
|
1127
|
+
"/api/v1/google/drive/files",
|
|
1128
|
+
params.bytes,
|
|
1129
|
+
params.contentType ?? "application/octet-stream",
|
|
1130
|
+
{
|
|
1131
|
+
name: params.name,
|
|
1132
|
+
...params.folderId !== void 0 ? { folderId: params.folderId } : {}
|
|
1133
|
+
},
|
|
1134
|
+
options
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Recent messages - senders, subjects and a one-line preview, never bodies.
|
|
1139
|
+
*
|
|
1140
|
+
* `query` is Gmail's own syntax passed through as written: `from:priya`,
|
|
1141
|
+
* `has:attachment`, `newer_than:7d`. It selects within the connected mailbox
|
|
1142
|
+
* and cannot reach another one.
|
|
1143
|
+
*/
|
|
1144
|
+
async searchMail(params = {}, options) {
|
|
1145
|
+
return this.#http.get(
|
|
1146
|
+
"/api/v1/google/mail",
|
|
1147
|
+
{
|
|
1148
|
+
...params.query !== void 0 ? { query: params.query } : {},
|
|
1149
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
1150
|
+
},
|
|
1151
|
+
options
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
/** One message, with its body and the names of what is attached. */
|
|
1155
|
+
async readMail(messageId, options) {
|
|
1156
|
+
return this.#http.get(
|
|
1157
|
+
`/api/v1/google/mail/${encodeURIComponent(messageId)}`,
|
|
1158
|
+
void 0,
|
|
1159
|
+
options
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* The bytes of one attachment.
|
|
1164
|
+
*
|
|
1165
|
+
* Separate from `readMail` so listing a mailbox never drags attachments
|
|
1166
|
+
* across the network: a message with a 40 MB deck should not cost 40 MB to
|
|
1167
|
+
* summarise.
|
|
1168
|
+
*/
|
|
1169
|
+
async downloadAttachment(messageId, attachmentId, options) {
|
|
1170
|
+
return this.#http.getBytes(
|
|
1171
|
+
`/api/v1/google/mail/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
1172
|
+
void 0,
|
|
1173
|
+
options
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
/** Sends as the connected account. Needs the send permission. */
|
|
1177
|
+
async sendMail(params, options) {
|
|
1178
|
+
return this.#http.post("/api/v1/google/mail/send", params, options);
|
|
1179
|
+
}
|
|
1180
|
+
/** People in the user's contacts. Omit the query to list them. */
|
|
1181
|
+
async contacts(params = {}, options) {
|
|
1182
|
+
return this.#http.get(
|
|
1183
|
+
"/api/v1/google/contacts",
|
|
1184
|
+
{
|
|
1185
|
+
...params.query !== void 0 ? { query: params.query } : {},
|
|
1186
|
+
...params.limit !== void 0 ? { limit: params.limit } : {}
|
|
1187
|
+
},
|
|
1188
|
+
options
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
|
|
953
1193
|
// src/resources/integrations.ts
|
|
954
1194
|
var Integrations = class {
|
|
955
1195
|
#http;
|
|
@@ -1087,6 +1327,8 @@ var PersistMemory = class {
|
|
|
1087
1327
|
conflicts;
|
|
1088
1328
|
conversations;
|
|
1089
1329
|
integrations;
|
|
1330
|
+
/** Drive, mail and contacts on the user's connected Google account. */
|
|
1331
|
+
google;
|
|
1090
1332
|
health;
|
|
1091
1333
|
agent;
|
|
1092
1334
|
#http;
|
|
@@ -1103,6 +1345,7 @@ var PersistMemory = class {
|
|
|
1103
1345
|
this.conflicts = new Conflicts(this.#http);
|
|
1104
1346
|
this.conversations = new Conversations(this.#http);
|
|
1105
1347
|
this.integrations = new Integrations(this.#http);
|
|
1348
|
+
this.google = new Google(this.#http);
|
|
1106
1349
|
this.health = new Health(this.#http);
|
|
1107
1350
|
this.agent = new Agent(this.#http);
|
|
1108
1351
|
}
|