@persistmemory/sdk 0.1.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.
@@ -0,0 +1,55 @@
1
+ import type { HttpClient, RequestOptions } from "../http.js";
2
+ import { Paginated } from "../pagination.js";
3
+ import type { CreateSpaceParams, ListSpacesParams, Memory, Space, UpdateSpaceParams } from "../types.js";
4
+ /**
5
+ * Spaces: a boundary around a set of memories.
6
+ *
7
+ * They exist because "everything I know" is the wrong scope for most
8
+ * questions. Work and personal contexts hold contradictory truths - two
9
+ * different "my manager" - and answering from both at once is wrong in a way
10
+ * that is hard to notice.
11
+ *
12
+ * Membership is a LINK, not ownership. `removeMemories` takes a memory out of
13
+ * a Space; it does not delete it, and the same memory can belong to several.
14
+ */
15
+ export declare class Spaces {
16
+ #private;
17
+ constructor(http: HttpClient);
18
+ list(params?: ListSpacesParams, options?: RequestOptions): Paginated<Space>;
19
+ get(id: string, options?: RequestOptions): Promise<Space>;
20
+ /**
21
+ * Creates a Space. Answers 201.
22
+ *
23
+ * Worth an idempotency key when a person is behind it: a double-clicked
24
+ * "create Space" button makes two Spaces called Work, and nothing later can
25
+ * tell which of them memories should have gone into.
26
+ */
27
+ create(params: CreateSpaceParams, options?: RequestOptions): Promise<Space>;
28
+ /** Renaming, retention, and archiving - `archived` is a field, not a verb. */
29
+ update(id: string, params: UpdateSpaceParams, options?: RequestOptions): Promise<Space>;
30
+ /**
31
+ * The memories filed in a Space.
32
+ *
33
+ * This endpoint answers `{ data, pagination: { limit } }` with no cursor: it
34
+ * returns the first `limit` members and stops. Wrapped in a `Paginated`
35
+ * anyway so it reads like every other list, and it simply yields one page -
36
+ * a caller who needs more should filter `memories.list` by `spaceIds`, which
37
+ * is the endpoint that actually pages.
38
+ */
39
+ memories(id: string, params?: {
40
+ readonly limit?: number;
41
+ }, options?: RequestOptions): Paginated<Memory>;
42
+ addMemories(id: string, memoryIds: readonly string[], options?: RequestOptions): Promise<{
43
+ added: number;
44
+ }>;
45
+ /**
46
+ * Removes memberships. The memories themselves are untouched.
47
+ *
48
+ * A body on a DELETE, which is unusual and is what the API takes: the
49
+ * alternative is five hundred ids in a query string, and every proxy in
50
+ * between has its own limit on how long a URL may be.
51
+ */
52
+ removeMemories(id: string, memoryIds: readonly string[], options?: RequestOptions): Promise<{
53
+ removed: number;
54
+ }>;
55
+ }
@@ -0,0 +1,500 @@
1
+ /**
2
+ * The wire types, transcribed from the Zod schemas the API validates against.
3
+ *
4
+ * Transcribed rather than generated, because generation from the OpenAPI
5
+ * document would give this package a build step and a code generator to keep
6
+ * working, and the surface is small enough to read. The contract they mirror
7
+ * is `apps/api/src/schemas/*.ts` - those objects both parse requests AND
8
+ * validate responses on the way out, so a field that is not there cannot be
9
+ * returned.
10
+ *
11
+ * Every optional field below is optional in the schema. That matters more than
12
+ * it looks: `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are on,
13
+ * so a caller reading `memory.supersedes` is forced to handle its absence, and
14
+ * an absent field is the normal case rather than a bug.
15
+ *
16
+ * Timestamps are ISO 8601 strings with an offset, never Date. JSON has no date
17
+ * type, and every client that has guessed at a format has eventually guessed
18
+ * wrong.
19
+ */
20
+ export type MemoryType = "fact" | "preference" | "decision" | "task" | "commitment" | "event" | "relationship" | "goal" | "plan" | "project" | "instruction" | "constraint" | "observation" | "experience" | "knowledge" | "change" | "status" | "question" | "assumption";
21
+ export type MemoryState = "candidate" | "active" | "confirmed" | "uncertain" | "superseded" | "expired" | "archived" | "deleted";
22
+ export type SpaceKind = "user" | "system" | "temporary" | "project" | "organizational";
23
+ export type ConflictType = "value" | "temporal" | "identity" | "relationship" | "state" | "source";
24
+ export type ResolutionStrategy = "user-confirmation" | "explicit-correction" | "temporal-validity" | "higher-confidence" | "corroboration" | "newer-source" | "unresolved";
25
+ export type JobStatus = "queued" | "running" | "completed" | "failed" | "dead";
26
+ export type DocumentStatus = "pending" | "processing" | "processed" | "empty" | "failed";
27
+ export type IntegrationStatus = "connected" | "reauth_required" | "syncing" | "error" | "disabled";
28
+ export type MessageRole = "user" | "assistant" | "system" | "tool";
29
+ /** Which memories an operation may see. */
30
+ export type Scope = "universal" | "space" | "combined";
31
+ /**
32
+ * The envelope every list endpoint returns.
33
+ *
34
+ * `nextCursor` absent is the ONLY signal to stop. An empty `data` array is not
35
+ * the same thing - a page can come back empty because everything in it was
36
+ * filtered after the fetch while more pages remain, and a client that stops on
37
+ * an empty page silently truncates the user's results.
38
+ */
39
+ export interface Page<T> {
40
+ readonly data: readonly T[];
41
+ readonly pagination: {
42
+ readonly nextCursor?: string;
43
+ readonly limit: number;
44
+ };
45
+ }
46
+ export interface EntityRef {
47
+ readonly id?: string;
48
+ readonly name: string;
49
+ readonly type: string;
50
+ /** How sure we are this mention IS that entity, not merely a name match. */
51
+ readonly confidence?: number;
52
+ }
53
+ export interface EvidenceRef {
54
+ readonly id: string;
55
+ readonly sourceId?: string;
56
+ readonly documentId?: string;
57
+ /** The passage the claim rests on, so a user can check it. */
58
+ readonly excerpt: string;
59
+ readonly observedAt?: string;
60
+ }
61
+ export interface SourceRef {
62
+ readonly id: string;
63
+ readonly provider: string;
64
+ readonly type: string;
65
+ readonly externalId?: string;
66
+ }
67
+ export interface Temporal {
68
+ readonly validFrom?: string;
69
+ readonly validUntil?: string;
70
+ /** True while the claim is believed to hold. */
71
+ readonly current: boolean;
72
+ }
73
+ export interface Memory {
74
+ readonly id: string;
75
+ readonly type: MemoryType;
76
+ readonly state: MemoryState;
77
+ readonly title: string;
78
+ readonly content: string;
79
+ readonly value?: Readonly<Record<string, unknown>>;
80
+ /** 0 to 1, like every score in this API. */
81
+ readonly confidence: number;
82
+ readonly importance: number;
83
+ readonly entities: readonly EntityRef[];
84
+ readonly sources: readonly SourceRef[];
85
+ readonly evidence: readonly EvidenceRef[];
86
+ readonly temporal: Temporal;
87
+ readonly spaceIds?: readonly string[];
88
+ readonly version: number;
89
+ readonly supersedes?: string;
90
+ readonly supersededBy?: string;
91
+ readonly createdAt: string;
92
+ readonly updatedAt: string;
93
+ readonly lastConfirmedAt?: string;
94
+ }
95
+ export interface ListMemoriesParams {
96
+ readonly limit?: number;
97
+ readonly cursor?: string;
98
+ readonly type?: MemoryType | readonly MemoryType[];
99
+ readonly state?: MemoryState | readonly MemoryState[];
100
+ readonly scope?: Scope;
101
+ readonly spaceIds?: readonly string[];
102
+ readonly createdAfter?: string;
103
+ readonly createdBefore?: string;
104
+ readonly minConfidence?: number;
105
+ /** Off by default: a list of memories almost always means "what is true now". */
106
+ readonly includeHistorical?: boolean;
107
+ }
108
+ /**
109
+ * What `POST /remember` takes.
110
+ *
111
+ * `text`, not `content`. The route parses its own body and this is the name it
112
+ * uses - the schema file next door describes a richer capture shape that this
113
+ * endpoint does not implement, and sending `content` gets a 400 that names
114
+ * `text` as required.
115
+ */
116
+ export interface RememberParams {
117
+ readonly text: string;
118
+ readonly title?: string;
119
+ readonly spaceIds?: readonly string[];
120
+ }
121
+ /**
122
+ * The answer, which is NOT a memory.
123
+ *
124
+ * `remember` hands material to the ingestion pipeline and returns 202.
125
+ * Extraction, entity resolution, deduplication and conflict detection all run
126
+ * afterwards, and may produce one memory, several, or none. Poll `jobId`
127
+ * through `client.jobs.get` to find out which.
128
+ */
129
+ export interface RememberResult {
130
+ readonly status: string;
131
+ readonly jobId: string;
132
+ readonly note: string;
133
+ }
134
+ export interface SearchParams {
135
+ readonly query: string;
136
+ /** Capped at 50 by the server. Every result costs a rerank. */
137
+ readonly limit?: number;
138
+ readonly scope?: Scope;
139
+ readonly spaceIds?: readonly string[];
140
+ readonly types?: MemoryType | readonly MemoryType[];
141
+ readonly minScore?: number;
142
+ /** As of a moment: what was believed true then, not what is true now. */
143
+ readonly asOf?: string;
144
+ readonly includeHistorical?: boolean;
145
+ readonly includeEvidence?: boolean;
146
+ readonly explain?: boolean;
147
+ }
148
+ export interface SearchExplanation {
149
+ /** Which retrieval paths found it: `vector`, `structured`, `graph`. */
150
+ readonly paths: readonly string[];
151
+ readonly vectorScore?: number;
152
+ readonly structuredScore?: number;
153
+ readonly rerankScore?: number;
154
+ readonly adjustments?: readonly {
155
+ readonly reason: string;
156
+ readonly delta: number;
157
+ }[];
158
+ }
159
+ export interface SearchResult {
160
+ readonly memory: Memory;
161
+ readonly score: number;
162
+ readonly explanation?: SearchExplanation;
163
+ }
164
+ export interface SearchResponse {
165
+ readonly results: readonly SearchResult[];
166
+ /**
167
+ * What the planner actually did.
168
+ *
169
+ * Worth reading. Search DEGRADES rather than fails: with embeddings down it
170
+ * falls back to deterministic retrieval and still answers. A client that
171
+ * ignores `degraded` reports "search is broken" when it is merely narrower,
172
+ * and - worse - never reports it when it silently is.
173
+ */
174
+ readonly diagnostics: {
175
+ readonly steps: readonly string[];
176
+ readonly degraded: boolean;
177
+ readonly unavailable?: readonly string[];
178
+ /** A sentence to show the user when a capability is deliberately stopped. */
179
+ readonly notice?: string;
180
+ readonly tookMs: number;
181
+ };
182
+ /** Echoed, so an empty result can be told from a mis-sent query. */
183
+ readonly query: string;
184
+ }
185
+ export interface ContextParams {
186
+ readonly query: string;
187
+ /**
188
+ * The budget, in TOKENS, not rows.
189
+ *
190
+ * The real constraint on the caller's side: ten long memories overflow a
191
+ * window that fifty short ones fit inside.
192
+ */
193
+ readonly tokenBudget?: number;
194
+ readonly scope?: Scope;
195
+ readonly spaceIds?: readonly string[];
196
+ /** Ids the caller already has in the prompt, so they are not spent twice. */
197
+ readonly exclude?: readonly string[];
198
+ readonly asOf?: string;
199
+ }
200
+ export interface ContextResponse {
201
+ /** Ready to paste into a prompt. */
202
+ readonly context: string;
203
+ readonly memories: readonly {
204
+ readonly id: string;
205
+ readonly title: string;
206
+ readonly score: number;
207
+ }[];
208
+ readonly usedTokens: number;
209
+ /** True when something relevant was left out for budget. */
210
+ readonly truncated: boolean;
211
+ }
212
+ export interface Space {
213
+ readonly id: string;
214
+ readonly name: string;
215
+ readonly kind: SpaceKind;
216
+ readonly description?: string;
217
+ readonly parentId?: string;
218
+ readonly audience?: string;
219
+ readonly retentionDays?: number;
220
+ readonly memoryCount?: number;
221
+ readonly createdAt: string;
222
+ /** Set when archived. An archived Space is hidden, not deleted. */
223
+ readonly archivedAt?: string;
224
+ }
225
+ export interface CreateSpaceParams {
226
+ readonly name: string;
227
+ readonly description?: string;
228
+ readonly kind?: SpaceKind;
229
+ readonly parentId?: string;
230
+ readonly retentionDays?: number;
231
+ }
232
+ export interface UpdateSpaceParams {
233
+ readonly name?: string;
234
+ readonly description?: string;
235
+ readonly retentionDays?: number;
236
+ /** Archiving and restoring, as a field rather than a verb endpoint. */
237
+ readonly archived?: boolean;
238
+ }
239
+ export interface ListSpacesParams {
240
+ readonly limit?: number;
241
+ readonly cursor?: string;
242
+ readonly includeArchived?: boolean;
243
+ }
244
+ export interface Source {
245
+ readonly id: string;
246
+ readonly provider: string;
247
+ readonly type: string;
248
+ readonly externalId?: string;
249
+ readonly label?: string;
250
+ readonly documentCount?: number;
251
+ readonly lastIngestedAt?: string;
252
+ readonly createdAt: string;
253
+ }
254
+ export interface ListSourcesParams {
255
+ readonly limit?: number;
256
+ readonly cursor?: string;
257
+ readonly provider?: string;
258
+ }
259
+ export interface Document {
260
+ readonly id: string;
261
+ readonly sourceId: string;
262
+ readonly contentType: string;
263
+ /** Which normaliser handled it - `text`, `pdf`, `email`, `transcript`. */
264
+ readonly plugin: string;
265
+ readonly status: DocumentStatus;
266
+ readonly title?: string;
267
+ readonly bytes?: number;
268
+ readonly memoryCount?: number;
269
+ /** Present only when `failed`, and sanitised - never a provider stack. */
270
+ readonly error?: string;
271
+ readonly createdAt: string;
272
+ readonly processedAt?: string;
273
+ }
274
+ export interface ListDocumentsParams {
275
+ readonly limit?: number;
276
+ readonly cursor?: string;
277
+ readonly sourceId?: string;
278
+ readonly status?: DocumentStatus | readonly DocumentStatus[];
279
+ }
280
+ export interface Job {
281
+ readonly id: string;
282
+ readonly type: string;
283
+ readonly status: JobStatus;
284
+ readonly subjectId?: string;
285
+ readonly attempts: number;
286
+ /** Sanitised. A provider's raw message can carry user content. */
287
+ readonly error?: string;
288
+ /** 0 to 1 when the job can report it. Absent is normal, not a bug. */
289
+ readonly progress?: number;
290
+ readonly queuedAt: string;
291
+ readonly startedAt?: string;
292
+ readonly finishedAt?: string;
293
+ }
294
+ export interface ListJobsParams {
295
+ readonly limit?: number;
296
+ readonly cursor?: string;
297
+ readonly status?: JobStatus | readonly JobStatus[];
298
+ readonly type?: string;
299
+ }
300
+ export interface Entity {
301
+ readonly id: string;
302
+ readonly name: string;
303
+ readonly type: string;
304
+ /** Other names the same thing goes by. */
305
+ readonly aliases: readonly string[];
306
+ readonly memoryCount?: number;
307
+ readonly firstSeenAt?: string;
308
+ readonly lastSeenAt?: string;
309
+ readonly createdAt: string;
310
+ }
311
+ export interface ListEntitiesParams {
312
+ readonly limit?: number;
313
+ readonly cursor?: string;
314
+ readonly type?: string;
315
+ /** Prefix match on name or alias, for a picker. */
316
+ readonly q?: string;
317
+ }
318
+ export interface EntityMention {
319
+ readonly memoryId: string;
320
+ readonly role?: string;
321
+ readonly confidence?: number;
322
+ }
323
+ export interface EntityMemoriesParams {
324
+ readonly limit?: number;
325
+ readonly cursor?: string;
326
+ /** Subject, mention, participant. Two different questions. */
327
+ readonly role?: string;
328
+ readonly minConfidence?: number;
329
+ }
330
+ export interface Relationship {
331
+ readonly id: string;
332
+ /** `works_at`, `uses`, `depends_on`, `other`. */
333
+ readonly predicate: string;
334
+ readonly label?: string;
335
+ readonly subject: {
336
+ readonly id?: string;
337
+ readonly name: string;
338
+ readonly type: string;
339
+ };
340
+ readonly object: {
341
+ readonly id?: string;
342
+ readonly name: string;
343
+ readonly type: string;
344
+ };
345
+ readonly confidence: number;
346
+ readonly observedAt: string;
347
+ }
348
+ export interface GraphNode {
349
+ readonly id: string;
350
+ readonly name: string;
351
+ readonly kind: string;
352
+ /** Hops from the starting entity, so a client can lay the graph out. */
353
+ readonly distance: number;
354
+ }
355
+ /**
356
+ * Walking the graph.
357
+ *
358
+ * `from` is an ENTITY id. Edges run between entities, not between memories,
359
+ * and the walk reports the memories the edges were extracted from.
360
+ */
361
+ export interface TraverseParams {
362
+ readonly from: string;
363
+ /** 1 to 4. Depth explodes exponentially, which is why it is bounded. */
364
+ readonly depth?: number;
365
+ readonly maxNodes?: number;
366
+ readonly memoryLimit?: number;
367
+ }
368
+ export interface GraphResponse {
369
+ readonly nodes: readonly GraphNode[];
370
+ readonly edges: readonly Relationship[];
371
+ readonly memoryIds: readonly string[];
372
+ /** True when a bound stopped the walk. Silence would read as "this is all". */
373
+ readonly truncated: boolean;
374
+ }
375
+ export interface Conflict {
376
+ readonly id: string;
377
+ readonly type: ConflictType;
378
+ readonly description?: string;
379
+ readonly confidence: number;
380
+ /** Both sides in full: a person cannot choose between two ids. */
381
+ readonly a: Memory;
382
+ readonly b: Memory;
383
+ readonly detectedAt: string;
384
+ readonly resolution?: {
385
+ readonly strategy: ResolutionStrategy;
386
+ readonly winnerId?: string;
387
+ readonly reason: string;
388
+ readonly resolvedAt: string;
389
+ };
390
+ }
391
+ export interface ListConflictsParams {
392
+ readonly limit?: number;
393
+ readonly cursor?: string;
394
+ /** Off by default: the list exists to be worked through. */
395
+ readonly includeResolved?: boolean;
396
+ readonly type?: ConflictType | readonly ConflictType[];
397
+ }
398
+ /**
399
+ * Settling a conflict.
400
+ *
401
+ * `keep` names the winner and SUPERSEDES the other; it does not delete it. The
402
+ * losing claim was true once, and a system that erases it cannot answer "what
403
+ * did I think last month".
404
+ */
405
+ export type ResolveConflictParams = {
406
+ readonly action: "keep";
407
+ readonly keepId: string;
408
+ readonly reason?: string;
409
+ } | {
410
+ readonly action: "dismiss";
411
+ readonly reason?: string;
412
+ };
413
+ export interface Message {
414
+ readonly id: string;
415
+ readonly role: MessageRole;
416
+ readonly content: string;
417
+ /** Memories surfaced into this turn, for citation. */
418
+ readonly memoryIds?: readonly string[];
419
+ readonly createdAt: string;
420
+ }
421
+ export interface Conversation {
422
+ readonly id: string;
423
+ readonly title?: string;
424
+ /** Which client this belongs to - `web`, `mcp`, an SDK's own label. */
425
+ readonly channel?: string;
426
+ readonly spaceIds?: readonly string[];
427
+ readonly messageCount: number;
428
+ readonly createdAt: string;
429
+ readonly updatedAt: string;
430
+ }
431
+ export interface CreateConversationParams {
432
+ readonly title?: string;
433
+ readonly channel?: string;
434
+ readonly spaceIds?: readonly string[];
435
+ }
436
+ export interface ListConversationsParams {
437
+ readonly limit?: number;
438
+ readonly cursor?: string;
439
+ readonly channel?: string;
440
+ }
441
+ export interface AppendMessagesParams {
442
+ readonly messages: readonly {
443
+ readonly role: MessageRole;
444
+ readonly content: string;
445
+ readonly createdAt?: string;
446
+ }[];
447
+ /** On by default - that is the point of sending them. */
448
+ readonly extract?: boolean;
449
+ }
450
+ export interface AppendMessagesResult {
451
+ readonly messages: readonly Message[];
452
+ /**
453
+ * Whether extraction was actually queued.
454
+ *
455
+ * Reported honestly: with no queue configured the turns are stored and never
456
+ * become memory, and `note` says so. A caller that ignores this believes a
457
+ * memory is on its way that never arrives.
458
+ */
459
+ readonly extracting: boolean;
460
+ readonly note?: string;
461
+ }
462
+ export interface Integration {
463
+ readonly id: string;
464
+ readonly provider: string;
465
+ readonly status: IntegrationStatus;
466
+ /** Which account, so a user with two can tell them apart. Never a token. */
467
+ readonly accountLabel?: string;
468
+ readonly scopes?: readonly string[];
469
+ readonly spaceIds?: readonly string[];
470
+ readonly lastSyncedAt?: string;
471
+ /** Sanitised, and shown to the user - so it reads as an instruction. */
472
+ readonly error?: string;
473
+ readonly createdAt: string;
474
+ }
475
+ export interface ListIntegrationsParams {
476
+ readonly limit?: number;
477
+ readonly cursor?: string;
478
+ readonly provider?: string;
479
+ readonly status?: IntegrationStatus;
480
+ }
481
+ export interface ConnectIntegrationParams {
482
+ readonly provider: string;
483
+ /** Where to land afterwards. Checked against an allow-list server-side. */
484
+ readonly returnTo?: string;
485
+ readonly spaceIds?: readonly string[];
486
+ }
487
+ export interface ConnectIntegrationResult {
488
+ /** Send the user here. This endpoint never takes third-party credentials. */
489
+ readonly authorizeUrl: string;
490
+ readonly state: string;
491
+ readonly expiresAt: string;
492
+ }
493
+ export interface UpdateIntegrationParams {
494
+ readonly enabled?: boolean;
495
+ readonly spaceIds?: readonly string[];
496
+ }
497
+ export interface HealthResponse {
498
+ readonly status: string;
499
+ readonly [key: string]: unknown;
500
+ }
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@persistmemory/sdk",
3
+ "version": "0.1.0",
4
+ "description": "The official TypeScript client for the PersistMemory API",
5
+ "license": "MIT",
6
+ "author": "PersistMemory",
7
+ "homepage": "https://persistmemory.com/docs/sdk",
8
+ "bugs": {
9
+ "email": "hello@persistmemory.com"
10
+ },
11
+ "keywords": [
12
+ "persistmemory",
13
+ "memory",
14
+ "ai",
15
+ "agent",
16
+ "llm",
17
+ "context",
18
+ "embeddings",
19
+ "sdk",
20
+ "client"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "sideEffects": false,
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsx build.ts",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest run",
50
+ "prepack": "tsx build.ts"
51
+ },
52
+ "dependencies": {},
53
+ "devDependencies": {
54
+ "esbuild": "^0.25.12",
55
+ "tsx": "^4.19.2",
56
+ "typescript": "^5.7.2",
57
+ "vitest": "^2.1.9"
58
+ }
59
+ }