@arkimedehq/recordare-client 0.2.0 → 0.4.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 +33 -7
- package/README_it.md +36 -9
- package/dist/client.d.ts +94 -4
- package/dist/client.js +116 -5
- package/dist/contract.d.ts +185 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/links.d.ts +10 -0
- package/dist/links.js +16 -0
- package/dist/mcp.js +1 -1
- package/dist/people.d.ts +5 -3
- package/dist/people.js +6 -2
- package/dist/tools.d.ts +4 -4
- package/dist/tools.js +4 -4
- package/package.json +1 -1
- package/src/client.ts +142 -8
- package/src/contract.ts +128 -0
- package/src/index.ts +1 -0
- package/src/links.ts +20 -0
- package/src/mcp.ts +1 -1
- package/src/people.ts +11 -5
- package/src/tools.ts +4 -4
package/README.md
CHANGED
|
@@ -19,9 +19,9 @@ import { PersonDirectory, RecordareClient, afterFailure } from '@arkimedehq/reco
|
|
|
19
19
|
const rc = new RecordareClient({ baseUrl: 'http://recordare:8080', apiKey: process.env.RECORDARE_API_KEY!,
|
|
20
20
|
headers: () => traceHeaders() }); // e.g. OpenTelemetry propagation.inject
|
|
21
21
|
|
|
22
|
-
// Which memory the user's account opens, its mode (personal | entity) and Atlas address; the name
|
|
22
|
+
// Which memory the user's account opens, its mode (personal | entity) and Atlas address; the name and gender follow the platform's profile. `enabled` is the
|
|
23
23
|
// platform's own memory switch: while false, Recordare is not contacted (no consent flag in Recordare, D50).
|
|
24
|
-
const people = new PersonDirectory(rc, { user: async (id) => ({ enabled: true, name: 'Andrea' }) });
|
|
24
|
+
const people = new PersonDirectory(rc, { user: async (id) => ({ enabled: true, name: 'Andrea', gender: 'masculine' }) });
|
|
25
25
|
const person = await people.refresh('user-42'); // { memoryId, mode, atlasUrl }; peek() reads the cache
|
|
26
26
|
|
|
27
27
|
// Ingest (split into requests of 500, idempotent on each externalId): → { conversationId, accepted, duplicates, conflicts }.
|
|
@@ -32,8 +32,14 @@ await rc.ingest('user-42', { conversation: { externalId: 'chat-1' }, messages: [
|
|
|
32
32
|
await rc.ingest('user-42', { conversation: { externalId: 'doc-1', source: 'document' }, messages: [
|
|
33
33
|
{ externalId: 'd1', role: 'user', own: true, content: 'La caldaia va revisionata ogni due anni.', sentAt: new Date().toISOString() },
|
|
34
34
|
] });
|
|
35
|
+
// A room transcript (continuous listening): Recordare fans it out to the memories of the people present.
|
|
36
|
+
await rc.ingestRoom({ conversation: { externalId: 'kitchen-1' }, recipients: ['user-42', 'user-7'],
|
|
37
|
+
speakers: [{ ref: 'a', user: 'user-42' }, { ref: 'b', user: 'user-7' }, { ref: 'c', displayName: 'Leo', identity: { channel: 'voice', externalId: 'leo' } }],
|
|
38
|
+
messages: [{ externalId: 'k1', speakerRef: 'c', content: 'Mi hanno preso in squadra!', sentAt: new Date().toISOString() }] });
|
|
35
39
|
// Mode (only while the memory is empty, else MemoryNotEmptyError) and the first person's gender, from the profile.
|
|
36
40
|
await rc.updateMe('user-42', { gender: 'feminine' });
|
|
41
|
+
// The account is deleted: erase the whole memory (a key with the erase scope; the memory id confirms which).
|
|
42
|
+
await rc.eraseMemory('user-42', (await rc.me('user-42')).memoryId);
|
|
37
43
|
|
|
38
44
|
// Recall: one MCP session per user AND conversation (evidence of writes, the current turn), both bound in code.
|
|
39
45
|
const tools = await rc.mcp.listTools('user-42', 'chat-1'); // build the agent's tools from these schemas
|
|
@@ -68,7 +74,28 @@ await rc.learnSource('user-42', { externalId: 'boiler-manual', title: 'Manuale d
|
|
|
68
74
|
|
|
69
75
|
## The diary
|
|
70
76
|
`episodes`, `episode`, `digests`, `facts`, `notes`, `plans` read what Recordare remembers for the person's own view in
|
|
71
|
-
the platform's UI; `correctEpisode`, `forgetEpisode`, `pinNote`, `delete`, `decide` are the person's edits (API.md §4).
|
|
77
|
+
the platform's UI; `correctEpisode`, `forgetEpisode`, `forgetPeriod`, `pinNote`, `delete`, `decide` are the person's edits (API.md §4).
|
|
78
|
+
`fact`, `note` give one item with its evidence; `addEpisode`, `addNote` are written by the person (stated, theirs);
|
|
79
|
+
`correctFact`, `correctNote` keep the wrong version as history. `noteChanges` is the change feed for a platform keeping a
|
|
80
|
+
copy of the notes (pass `next` back as `since`). `settings`, `usage` read the memory's settings and the LLM calls spent on
|
|
81
|
+
it; `updateSettings` and the export (`startExport` → `exportJob` → `exportArchive`) need the memory's personal token
|
|
82
|
+
(scopes `memory_settings`, `export`; pass `user` empty).
|
|
83
|
+
|
|
84
|
+
## Linking the holder's channel identity (D51)
|
|
85
|
+
In a personal memory a channel sender speaks as "I" only once the holder links it: `linkCode(user)` → `{code,
|
|
86
|
+
expiresAt}` (`RCD-XXXX-XXXX`, ten minutes, one use) — the holder sends the code alone to the agent from that channel, in
|
|
87
|
+
a direct chat (set `conversation.direct` on the ingest when you know it; `false` = a group, where a code claims nothing);
|
|
88
|
+
then `holderIdentities(user)` → `{linked, pending}`, and `confirmHolderClaim(user, id)` / `rejectHolderClaim(user, id)`
|
|
89
|
+
within seven days; `unlinkHolderIdentity(user, id)` undoes a link. Recordare never stores the code's message, but your
|
|
90
|
+
model sees it: when `isLinkCode(text)` (the service's own `LINK_CODE` pattern), send the turn at once and give the model
|
|
91
|
+
`LINK_CODE_INSTRUCTION` instead of a memory context.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const { code } = await rc.linkCode('user-42'); // show it to the holder
|
|
95
|
+
if (isLinkCode(text)) systemExtra = LINK_CODE_INSTRUCTION; // the model says the code was received, without repeating it
|
|
96
|
+
const { pending } = await rc.holderIdentities('user-42');
|
|
97
|
+
await rc.confirmHolderClaim('user-42', pending[0].id);
|
|
98
|
+
```
|
|
72
99
|
|
|
73
100
|
## What stays with the host
|
|
74
101
|
How it stores its outbox (its own database, its own transaction) and how it maps its chats to the ingest contract.
|
|
@@ -79,10 +106,9 @@ Everything else is here, the same for every client.
|
|
|
79
106
|
deletions propagate, the name, mode and gender follow the platform, own content is marked, a source is learned in
|
|
80
107
|
parts, listed and forgotten, recall over MCP carries the user and the conversation. Type checks there fail the build when this contract drifts from the service's schemas.
|
|
81
108
|
|
|
82
|
-
##
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
edits them; change the library here and sync again. Switching to the npm package only changes the imports.
|
|
109
|
+
## CommonJS hosts
|
|
110
|
+
The package is ESM only. A CommonJS host (Arkimede's NestJS backend) loads it with `require`, which Node supports for
|
|
111
|
+
ES modules from 20.19 / 22.12; with TypeScript, `"module": "nodenext"` (5.8 or later) type-checks that import.
|
|
86
112
|
|
|
87
113
|
## Development
|
|
88
114
|
`npm ci`, `npm run typecheck`, `npm test`, `npm run build` (Node ≥ 20).
|
package/README_it.md
CHANGED
|
@@ -19,9 +19,9 @@ import { PersonDirectory, RecordareClient, afterFailure } from '@arkimedehq/reco
|
|
|
19
19
|
const rc = new RecordareClient({ baseUrl: 'http://recordare:8080', apiKey: process.env.RECORDARE_API_KEY!,
|
|
20
20
|
headers: () => traceHeaders() }); // es. propagation.inject di OpenTelemetry
|
|
21
21
|
|
|
22
|
-
// Quale memoria apre l'account dell'utente, il suo modo (personal | entity) e l'indirizzo di Atlas; il nome
|
|
22
|
+
// Quale memoria apre l'account dell'utente, il suo modo (personal | entity) e l'indirizzo di Atlas; il nome e il genere seguono il profilo sulla piattaforma. `enabled` è
|
|
23
23
|
// l'interruttore della memoria della piattaforma: finché è false Recordare non viene contattato (niente consenso, D50).
|
|
24
|
-
const people = new PersonDirectory(rc, { user: async (id) => ({ enabled: true, name: 'Andrea' }) });
|
|
24
|
+
const people = new PersonDirectory(rc, { user: async (id) => ({ enabled: true, name: 'Andrea', gender: 'masculine' }) });
|
|
25
25
|
const person = await people.refresh('user-42'); // { memoryId, mode, atlasUrl }; peek() legge la cache
|
|
26
26
|
|
|
27
27
|
// Ingest (diviso in richieste da 500, idempotente su ogni externalId): → { conversationId, accepted, duplicates, conflicts }.
|
|
@@ -32,8 +32,14 @@ await rc.ingest('user-42', { conversation: { externalId: 'chat-1' }, messages: [
|
|
|
32
32
|
await rc.ingest('user-42', { conversation: { externalId: 'doc-1', source: 'document' }, messages: [
|
|
33
33
|
{ externalId: 'd1', role: 'user', own: true, content: 'La caldaia va revisionata ogni due anni.', sentAt: new Date().toISOString() },
|
|
34
34
|
] });
|
|
35
|
+
// Una trascrizione di stanza (ascolto continuo): Recordare la distribuisce alle memorie delle persone presenti.
|
|
36
|
+
await rc.ingestRoom({ conversation: { externalId: 'kitchen-1' }, recipients: ['user-42', 'user-7'],
|
|
37
|
+
speakers: [{ ref: 'a', user: 'user-42' }, { ref: 'b', user: 'user-7' }, { ref: 'c', displayName: 'Leo', identity: { channel: 'voice', externalId: 'leo' } }],
|
|
38
|
+
messages: [{ externalId: 'k1', speakerRef: 'c', content: 'Mi hanno preso in squadra!', sentAt: new Date().toISOString() }] });
|
|
35
39
|
// Modo (solo finché la memoria è vuota, altrimenti MemoryNotEmptyError) e genere della prima persona, dal profilo.
|
|
36
40
|
await rc.updateMe('user-42', { gender: 'feminine' });
|
|
41
|
+
// L'account viene eliminato: si cancella l'intera memoria (una chiave con lo scope erase; l'id della memoria conferma quale).
|
|
42
|
+
await rc.eraseMemory('user-42', (await rc.me('user-42')).memoryId);
|
|
37
43
|
|
|
38
44
|
// Richiamo: una sessione MCP per utente E conversazione (prove delle scritture, il turno corrente), entrambi fissati nel codice.
|
|
39
45
|
const tools = await rc.mcp.listTools('user-42', 'chat-1'); // gli strumenti dell'agente nascono da questi schemi
|
|
@@ -69,8 +75,30 @@ await rc.learnSource('user-42', { externalId: 'boiler-manual', title: 'Manuale d
|
|
|
69
75
|
|
|
70
76
|
## Il diario
|
|
71
77
|
`episodes`, `episode`, `digests`, `facts`, `notes`, `plans` leggono ciò che Recordare ricorda, per la vista della persona
|
|
72
|
-
nell'interfaccia della piattaforma; `correctEpisode`, `forgetEpisode`, `pinNote`, `delete`, `decide` sono le modifiche
|
|
73
|
-
della persona (API.md §4).
|
|
78
|
+
nell'interfaccia della piattaforma; `correctEpisode`, `forgetEpisode`, `forgetPeriod`, `pinNote`, `delete`, `decide` sono le modifiche
|
|
79
|
+
della persona (API.md §4). `fact`, `note` danno un elemento con le sue prove; `addEpisode`, `addNote` sono scritti dalla
|
|
80
|
+
persona (dichiarati, suoi); `correctFact`, `correctNote` tengono la versione sbagliata come cronologia. `noteChanges` è il
|
|
81
|
+
feed delle modifiche per una piattaforma che tiene una copia delle note (si ripassa `next` come `since`). `settings`,
|
|
82
|
+
`usage` leggono le impostazioni della memoria e le chiamate LLM spese per essa; `updateSettings` e l'export
|
|
83
|
+
(`startExport` → `exportJob` → `exportArchive`) richiedono il token personale della memoria (scope `memory_settings`,
|
|
84
|
+
`export`; `user` vuoto).
|
|
85
|
+
|
|
86
|
+
## Collegare l'identità di canale del titolare (D51)
|
|
87
|
+
In una memoria personale un mittente di un canale parla come "io" solo dopo che il titolare l'ha collegato:
|
|
88
|
+
`linkCode(user)` → `{code, expiresAt}` (`RCD-XXXX-XXXX`, dieci minuti, un solo uso) — il titolare invia il codice da
|
|
89
|
+
solo all'agente da quel canale, in una chat diretta (impostare `conversation.direct` nell'ingest quando lo si sa;
|
|
90
|
+
`false` = un gruppo, dove un codice non chiede nulla); poi `holderIdentities(user)` → `{linked, pending}`, e
|
|
91
|
+
`confirmHolderClaim(user, id)` / `rejectHolderClaim(user, id)` entro sette giorni; `unlinkHolderIdentity(user, id)`
|
|
92
|
+
annulla un collegamento. Recordare non salva mai il messaggio del codice, ma il modello lo vede: quando `isLinkCode(text)`
|
|
93
|
+
(lo stesso pattern `LINK_CODE` del servizio), inviare subito il turno e dare al modello `LINK_CODE_INSTRUCTION` al posto
|
|
94
|
+
del contesto di memoria.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const { code } = await rc.linkCode('user-42'); // da mostrare al titolare
|
|
98
|
+
if (isLinkCode(text)) systemExtra = LINK_CODE_INSTRUCTION; // il modello dice che il codice è arrivato, senza ripeterlo
|
|
99
|
+
const { pending } = await rc.holderIdentities('user-42');
|
|
100
|
+
await rc.confirmHolderClaim('user-42', pending[0].id);
|
|
101
|
+
```
|
|
74
102
|
|
|
75
103
|
## Cosa resta all'host
|
|
76
104
|
Come conserva la sua outbox (il suo database, la sua transazione) e come trasforma le sue chat nel contratto di
|
|
@@ -82,11 +110,10 @@ volta sola, le cancellazioni si propagano, nome, modo e genere seguono la piatta
|
|
|
82
110
|
fonte viene imparata a parti, elencata e dimenticata, il richiamo via MCP porta l'utente e la conversazione. I controlli di tipo lì fanno fallire la build se questo contratto si
|
|
83
111
|
allontana dagli schemi del servizio.
|
|
84
112
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
gli import.
|
|
113
|
+
## Host CommonJS
|
|
114
|
+
Il pacchetto è solo ESM. Un host CommonJS (il backend NestJS di Arkimede) lo carica con `require`, che Node supporta per
|
|
115
|
+
i moduli ES dalla 20.19 / 22.12; con TypeScript, `"module": "nodenext"` (5.8 o successivo) controlla i tipi di
|
|
116
|
+
quell'import.
|
|
90
117
|
|
|
91
118
|
## Sviluppo
|
|
92
119
|
`npm ci`, `npm run typecheck`, `npm test`, `npm run build` (Node ≥ 20).
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Digest, type Episode, type EpisodeDetail, type EpisodeQuery, type Fact, type IngestRequest, type IngestResult, type LearnSource, type LearnSourceResult, type Me, type MeSettings, type MemoryContext, type Note, type PlanStatus, type Precision, type Source } from './contract.js';
|
|
1
|
+
import { type Cards, type ContextOptions, type Digest, type Episode, type Erased, type EpisodeDetail, type EpisodeQuery, type ExportJob, type Fact, type FactDetail, type HolderIdentities, type IngestRequest, type IngestResult, type RoomRequest, type RoomResult, type LearnSource, type LinkCode, type LearnSourceResult, type Me, type MemorySettings, type MeSettings, type MemoryContext, type Note, type NoteChange, type NoteDetail, type PlanStatus, type Precision, type Source, type Usage } from './contract.js';
|
|
2
2
|
import { type ClientOptions, Http } from './http.js';
|
|
3
3
|
import { RecordareMcp, type McpOptions } from './mcp.js';
|
|
4
4
|
/**
|
|
@@ -16,22 +16,42 @@ export declare class RecordareClient {
|
|
|
16
16
|
me(user: string): Promise<Me>;
|
|
17
17
|
/** The memory's settings from the platform: the name (keep it in sync on every rename), the mode and the gender. */
|
|
18
18
|
updateMe(user: string, settings: MeSettings): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Erases the whole memory behind `user`, at once and for good (needs the `erase` scope): everything it holds goes.
|
|
21
|
+
* `memoryId` (from `me`) confirms which memory; a user without a memory answers 404, none is created.
|
|
22
|
+
*/
|
|
23
|
+
eraseMemory(user: string, memoryId: string): Promise<Erased>;
|
|
24
|
+
/** A one-time code for the holder to send from a channel (D51; personal memories only, else 409). */
|
|
25
|
+
linkCode(user: string): Promise<LinkCode>;
|
|
26
|
+
/** The identities that speak as the memory's "I" and the claims waiting for confirmation (D51). */
|
|
27
|
+
holderIdentities(user: string): Promise<HolderIdentities>;
|
|
28
|
+
/** Confirms a claim: that channel identity is the holder from now on. */
|
|
29
|
+
confirmHolderClaim(user: string, id: string): Promise<void>;
|
|
30
|
+
/** Rejects a claim (not the holder). */
|
|
31
|
+
rejectHolderClaim(user: string, id: string): Promise<void>;
|
|
32
|
+
/** Unlinks one of the holder's identities. */
|
|
33
|
+
unlinkHolderIdentity(user: string, id: string): Promise<void>;
|
|
19
34
|
/**
|
|
20
35
|
* Sends messages of one conversation, split into requests of at most MAX_MESSAGES_PER_REQUEST. Idempotent: Recordare
|
|
21
36
|
* deduplicates on each message's externalId. Recordare always stores (D50): the on/off switch is the platform's.
|
|
22
37
|
*/
|
|
23
38
|
ingest(user: string, req: IngestRequest): Promise<IngestResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Sends a room transcript to the memories of the people present (continuous listening), split into requests of at
|
|
41
|
+
* most MAX_MESSAGES_PER_REQUEST; needs a client key (not a personal token). Idempotent like `ingest`.
|
|
42
|
+
*/
|
|
43
|
+
ingestRoom(req: RoomRequest): Promise<RoomResult>;
|
|
24
44
|
/**
|
|
25
45
|
* The memories relevant to `query` (the message about to be answered) as a fenced block to append to the prompt — or
|
|
26
46
|
* null. Append it at the end of the system prompt (it changes every turn: keep the stable part first for prompt
|
|
27
|
-
* caching); never store it as a chat message.
|
|
47
|
+
* caching); never store it as a chat message. `card`: open it with the self card (who I am), off by default.
|
|
28
48
|
*/
|
|
29
|
-
context(user: string, conversation: string, query: string): Promise<MemoryContext>;
|
|
49
|
+
context(user: string, conversation: string, query: string, opts?: ContextOptions): Promise<MemoryContext>;
|
|
30
50
|
/**
|
|
31
51
|
* Stores the turn and returns the memory context for it in one round trip (needs the `ingest` and `read` scopes):
|
|
32
52
|
* the context is computed for the ingested conversation, from `query` or else the turn's last user message.
|
|
33
53
|
*/
|
|
34
|
-
contextWithTurn(user: string, turn: IngestRequest, query?: string): Promise<MemoryContext>;
|
|
54
|
+
contextWithTurn(user: string, turn: IngestRequest, query?: string, opts?: ContextOptions): Promise<MemoryContext>;
|
|
35
55
|
/** The conversation ended (session closed, /new): extraction runs now. A conversation Recordare never had is done. */
|
|
36
56
|
endConversation(user: string, conversation: string): Promise<void>;
|
|
37
57
|
/** An edited message: Recordare re-extracts what depended on it. */
|
|
@@ -63,6 +83,20 @@ export declare class RecordareClient {
|
|
|
63
83
|
}): Promise<string>;
|
|
64
84
|
/** The person forgets an episode: it is removed and never recreated. */
|
|
65
85
|
forgetEpisode(user: string, id: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* The person forgets a period (days or months, inclusive): its episodes and raw messages (`keepRaw`: kept but hidden
|
|
88
|
+
* and never extracted again), with what only they supported. Returns how much went.
|
|
89
|
+
*/
|
|
90
|
+
forgetPeriod(user: string, period: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
keepRaw?: boolean;
|
|
94
|
+
}): Promise<{
|
|
95
|
+
episodes: number;
|
|
96
|
+
messages: number;
|
|
97
|
+
facts: number;
|
|
98
|
+
notes: number;
|
|
99
|
+
}>;
|
|
66
100
|
digests(user: string, query?: {
|
|
67
101
|
level?: 'day' | 'month';
|
|
68
102
|
from?: string;
|
|
@@ -73,6 +107,8 @@ export declare class RecordareClient {
|
|
|
73
107
|
asOf?: string;
|
|
74
108
|
includePending?: boolean;
|
|
75
109
|
}): Promise<Fact[]>;
|
|
110
|
+
/** Who I am and who my contacts are: current facts and notes of the self and of each contact (no LLM). */
|
|
111
|
+
cards(user: string): Promise<Cards>;
|
|
76
112
|
notes(user: string, query?: {
|
|
77
113
|
category?: Note['category'];
|
|
78
114
|
pinned?: boolean;
|
|
@@ -87,6 +123,60 @@ export declare class RecordareClient {
|
|
|
87
123
|
delete(user: string, what: 'notes' | 'facts', id: string): Promise<void>;
|
|
88
124
|
/** A pending (inferred) note or fact: confirmed it becomes the person's, rejected it is removed. */
|
|
89
125
|
decide(user: string, what: 'notes' | 'facts', id: string, decision: 'confirm' | 'reject'): Promise<void>;
|
|
126
|
+
/** The person writes an episode in the platform's UI (stated, theirs). Returns its id. */
|
|
127
|
+
addEpisode(user: string, episode: {
|
|
128
|
+
content: string;
|
|
129
|
+
kind?: 'event' | 'plan';
|
|
130
|
+
occurredAt?: string;
|
|
131
|
+
occurredUntil?: string;
|
|
132
|
+
datePrecision?: Exclude<Precision, 'unknown' | 'minute'>;
|
|
133
|
+
place?: string;
|
|
134
|
+
people?: string[];
|
|
135
|
+
}): Promise<string>;
|
|
136
|
+
/** The person writes a note in the platform's UI (stated, theirs). Returns its id. */
|
|
137
|
+
addNote(user: string, note: {
|
|
138
|
+
content: string;
|
|
139
|
+
category?: Note['category'];
|
|
140
|
+
}): Promise<string>;
|
|
141
|
+
fact(user: string, id: string): Promise<FactDetail>;
|
|
142
|
+
note(user: string, id: string): Promise<NoteDetail>;
|
|
143
|
+
/** The person corrects a current fact (`value` null: not known any more); the wrong one stays in its history. */
|
|
144
|
+
correctFact(user: string, id: string, fix: {
|
|
145
|
+
value: string | null;
|
|
146
|
+
validFrom?: string;
|
|
147
|
+
datePrecision?: Exclude<Precision, 'unknown' | 'minute'>;
|
|
148
|
+
}): Promise<string>;
|
|
149
|
+
/** The person corrects a current note; the wrong one stays as `corrected`. */
|
|
150
|
+
correctNote(user: string, id: string, fix: {
|
|
151
|
+
content: string;
|
|
152
|
+
category?: Note['category'];
|
|
153
|
+
}): Promise<string>;
|
|
154
|
+
/** The notes change feed, for a platform keeping a copy: pass `next` back as `since`; `more` when the page was full. */
|
|
155
|
+
noteChanges(user: string, query?: {
|
|
156
|
+
since?: number;
|
|
157
|
+
limit?: number;
|
|
158
|
+
}): Promise<{
|
|
159
|
+
items: NoteChange[];
|
|
160
|
+
next: number;
|
|
161
|
+
more: boolean;
|
|
162
|
+
}>;
|
|
163
|
+
settings(user: string): Promise<MemorySettings>;
|
|
164
|
+
/** Needs `memory_settings`: the memory's personal token (never a client key); pass `user` empty with it. */
|
|
165
|
+
updateSettings(user: string, patch: {
|
|
166
|
+
locale?: string;
|
|
167
|
+
timezone?: string;
|
|
168
|
+
qualityProfile?: MemorySettings['qualityProfile'];
|
|
169
|
+
}): Promise<MemorySettings>;
|
|
170
|
+
/** LLM calls and tokens spent on the memory between two local days (default: the last 30). */
|
|
171
|
+
usage(user: string, query?: {
|
|
172
|
+
from?: string;
|
|
173
|
+
to?: string;
|
|
174
|
+
}): Promise<Usage>;
|
|
175
|
+
/** Starts a full export (needs `export`: the memory's personal token); poll `exportJob`, then `exportArchive`. */
|
|
176
|
+
startExport(user: string): Promise<ExportJob>;
|
|
177
|
+
exportJob(user: string, id: string): Promise<ExportJob>;
|
|
178
|
+
/** The finished export: one JSON archive (`format: "recordare-export/1"`). */
|
|
179
|
+
exportArchive(user: string, id: string): Promise<Record<string, unknown>>;
|
|
90
180
|
/** Closes the MCP sessions (on shutdown). */
|
|
91
181
|
close(): Promise<void>;
|
|
92
182
|
private gone;
|
package/dist/client.js
CHANGED
|
@@ -41,6 +41,33 @@ export class RecordareClient {
|
|
|
41
41
|
throw err;
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Erases the whole memory behind `user`, at once and for good (needs the `erase` scope): everything it holds goes.
|
|
46
|
+
* `memoryId` (from `me`) confirms which memory; a user without a memory answers 404, none is created.
|
|
47
|
+
*/
|
|
48
|
+
eraseMemory(user, memoryId) {
|
|
49
|
+
return this.http.request('DELETE', `api/v1/me${qs({ confirm: memoryId })}`, { user });
|
|
50
|
+
}
|
|
51
|
+
/** A one-time code for the holder to send from a channel (D51; personal memories only, else 409). */
|
|
52
|
+
linkCode(user) {
|
|
53
|
+
return this.http.request('POST', 'api/v1/me/link-codes', { user });
|
|
54
|
+
}
|
|
55
|
+
/** The identities that speak as the memory's "I" and the claims waiting for confirmation (D51). */
|
|
56
|
+
holderIdentities(user) {
|
|
57
|
+
return this.http.request('GET', 'api/v1/me/identities', { user });
|
|
58
|
+
}
|
|
59
|
+
/** Confirms a claim: that channel identity is the holder from now on. */
|
|
60
|
+
async confirmHolderClaim(user, id) {
|
|
61
|
+
await this.http.request('POST', `api/v1/me/identities/pending/${encodeURIComponent(id)}/confirm`, { user });
|
|
62
|
+
}
|
|
63
|
+
/** Rejects a claim (not the holder). */
|
|
64
|
+
async rejectHolderClaim(user, id) {
|
|
65
|
+
await this.http.request('DELETE', `api/v1/me/identities/pending/${encodeURIComponent(id)}`, { user });
|
|
66
|
+
}
|
|
67
|
+
/** Unlinks one of the holder's identities. */
|
|
68
|
+
async unlinkHolderIdentity(user, id) {
|
|
69
|
+
await this.http.request('DELETE', `api/v1/me/identities/${encodeURIComponent(id)}`, { user });
|
|
70
|
+
}
|
|
44
71
|
/**
|
|
45
72
|
* Sends messages of one conversation, split into requests of at most MAX_MESSAGES_PER_REQUEST. Idempotent: Recordare
|
|
46
73
|
* deduplicates on each message's externalId. Recordare always stores (D50): the on/off switch is the platform's.
|
|
@@ -65,20 +92,45 @@ export class RecordareClient {
|
|
|
65
92
|
}
|
|
66
93
|
return total;
|
|
67
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Sends a room transcript to the memories of the people present (continuous listening), split into requests of at
|
|
97
|
+
* most MAX_MESSAGES_PER_REQUEST; needs a client key (not a personal token). Idempotent like `ingest`.
|
|
98
|
+
*/
|
|
99
|
+
async ingestRoom(req) {
|
|
100
|
+
const total = new Map();
|
|
101
|
+
for (let i = 0; i < req.messages.length; i += MAX_MESSAGES_PER_REQUEST) {
|
|
102
|
+
const last = i + MAX_MESSAGES_PER_REQUEST >= req.messages.length;
|
|
103
|
+
const part = await this.http.request('POST', 'api/v1/ingest/room', {
|
|
104
|
+
body: { ...req, messages: req.messages.slice(i, i + MAX_MESSAGES_PER_REQUEST), hints: req.hints && last ? req.hints : undefined },
|
|
105
|
+
});
|
|
106
|
+
for (const r of part.recipients) {
|
|
107
|
+
const t = total.get(r.user);
|
|
108
|
+
if (!t)
|
|
109
|
+
total.set(r.user, { ...r, conflicts: [...r.conflicts] });
|
|
110
|
+
else {
|
|
111
|
+
t.conversationId = r.conversationId ?? t.conversationId;
|
|
112
|
+
t.accepted += r.accepted;
|
|
113
|
+
t.duplicates += r.duplicates;
|
|
114
|
+
t.conflicts.push(...r.conflicts);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { recipients: [...total.values()] };
|
|
119
|
+
}
|
|
68
120
|
/**
|
|
69
121
|
* The memories relevant to `query` (the message about to be answered) as a fenced block to append to the prompt — or
|
|
70
122
|
* null. Append it at the end of the system prompt (it changes every turn: keep the stable part first for prompt
|
|
71
|
-
* caching); never store it as a chat message.
|
|
123
|
+
* caching); never store it as a chat message. `card`: open it with the self card (who I am), off by default.
|
|
72
124
|
*/
|
|
73
|
-
context(user, conversation, query) {
|
|
74
|
-
return this.http.request('POST', 'api/v1/context', { user, body: { query }, headers: { [CONVERSATION_HEADER]: conversation } });
|
|
125
|
+
context(user, conversation, query, opts = {}) {
|
|
126
|
+
return this.http.request('POST', 'api/v1/context', { user, body: { query, ...opts }, headers: { [CONVERSATION_HEADER]: conversation } });
|
|
75
127
|
}
|
|
76
128
|
/**
|
|
77
129
|
* Stores the turn and returns the memory context for it in one round trip (needs the `ingest` and `read` scopes):
|
|
78
130
|
* the context is computed for the ingested conversation, from `query` or else the turn's last user message.
|
|
79
131
|
*/
|
|
80
|
-
contextWithTurn(user, turn, query) {
|
|
81
|
-
return this.http.request('POST', 'api/v1/context', { user, body: { ingest: turn, ...(query ? { query } : {}) } });
|
|
132
|
+
contextWithTurn(user, turn, query, opts = {}) {
|
|
133
|
+
return this.http.request('POST', 'api/v1/context', { user, body: { ingest: turn, ...(query ? { query } : {}), ...opts } });
|
|
82
134
|
}
|
|
83
135
|
/** The conversation ended (session closed, /new): extraction runs now. A conversation Recordare never had is done. */
|
|
84
136
|
async endConversation(user, conversation) {
|
|
@@ -135,12 +187,23 @@ export class RecordareClient {
|
|
|
135
187
|
async forgetEpisode(user, id) {
|
|
136
188
|
await this.http.request('DELETE', `api/v1/episodes/${enc(id)}`, { user });
|
|
137
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* The person forgets a period (days or months, inclusive): its episodes and raw messages (`keepRaw`: kept but hidden
|
|
192
|
+
* and never extracted again), with what only they supported. Returns how much went.
|
|
193
|
+
*/
|
|
194
|
+
forgetPeriod(user, period) {
|
|
195
|
+
return this.http.request('POST', 'api/v1/forget', { user, body: period });
|
|
196
|
+
}
|
|
138
197
|
digests(user, query = {}) {
|
|
139
198
|
return this.http.request('GET', `api/v1/digests${qs(query)}`, { user });
|
|
140
199
|
}
|
|
141
200
|
facts(user, query = {}) {
|
|
142
201
|
return this.http.request('GET', `api/v1/facts${qs(query)}`, { user });
|
|
143
202
|
}
|
|
203
|
+
/** Who I am and who my contacts are: current facts and notes of the self and of each contact (no LLM). */
|
|
204
|
+
cards(user) {
|
|
205
|
+
return this.http.request('GET', 'api/v1/cards', { user });
|
|
206
|
+
}
|
|
144
207
|
notes(user, query = {}) {
|
|
145
208
|
return this.http.request('GET', `api/v1/notes${qs(query)}`, { user });
|
|
146
209
|
}
|
|
@@ -159,6 +222,54 @@ export class RecordareClient {
|
|
|
159
222
|
async decide(user, what, id, decision) {
|
|
160
223
|
await this.http.request('POST', `api/v1/${what}/${enc(id)}/${decision}`, { user });
|
|
161
224
|
}
|
|
225
|
+
/** The person writes an episode in the platform's UI (stated, theirs). Returns its id. */
|
|
226
|
+
async addEpisode(user, episode) {
|
|
227
|
+
return (await this.http.request('POST', 'api/v1/episodes', { user, body: episode })).id;
|
|
228
|
+
}
|
|
229
|
+
/** The person writes a note in the platform's UI (stated, theirs). Returns its id. */
|
|
230
|
+
async addNote(user, note) {
|
|
231
|
+
return (await this.http.request('POST', 'api/v1/notes', { user, body: note })).id;
|
|
232
|
+
}
|
|
233
|
+
fact(user, id) {
|
|
234
|
+
return this.http.request('GET', `api/v1/facts/${enc(id)}`, { user });
|
|
235
|
+
}
|
|
236
|
+
note(user, id) {
|
|
237
|
+
return this.http.request('GET', `api/v1/notes/${enc(id)}`, { user });
|
|
238
|
+
}
|
|
239
|
+
/** The person corrects a current fact (`value` null: not known any more); the wrong one stays in its history. */
|
|
240
|
+
async correctFact(user, id, fix) {
|
|
241
|
+
return (await this.http.request('POST', `api/v1/facts/${enc(id)}/corrections`, { user, body: fix })).id;
|
|
242
|
+
}
|
|
243
|
+
/** The person corrects a current note; the wrong one stays as `corrected`. */
|
|
244
|
+
async correctNote(user, id, fix) {
|
|
245
|
+
return (await this.http.request('POST', `api/v1/notes/${enc(id)}/corrections`, { user, body: fix })).id;
|
|
246
|
+
}
|
|
247
|
+
/** The notes change feed, for a platform keeping a copy: pass `next` back as `since`; `more` when the page was full. */
|
|
248
|
+
noteChanges(user, query = {}) {
|
|
249
|
+
return this.http.request('GET', `api/v1/notes/changes${qs(query)}`, { user });
|
|
250
|
+
}
|
|
251
|
+
settings(user) {
|
|
252
|
+
return this.http.request('GET', 'api/v1/settings', { user });
|
|
253
|
+
}
|
|
254
|
+
/** Needs `memory_settings`: the memory's personal token (never a client key); pass `user` empty with it. */
|
|
255
|
+
updateSettings(user, patch) {
|
|
256
|
+
return this.http.request('PATCH', 'api/v1/settings', { user, body: patch });
|
|
257
|
+
}
|
|
258
|
+
/** LLM calls and tokens spent on the memory between two local days (default: the last 30). */
|
|
259
|
+
usage(user, query = {}) {
|
|
260
|
+
return this.http.request('GET', `api/v1/usage${qs(query)}`, { user });
|
|
261
|
+
}
|
|
262
|
+
/** Starts a full export (needs `export`: the memory's personal token); poll `exportJob`, then `exportArchive`. */
|
|
263
|
+
startExport(user) {
|
|
264
|
+
return this.http.request('POST', 'api/v1/exports', { user });
|
|
265
|
+
}
|
|
266
|
+
exportJob(user, id) {
|
|
267
|
+
return this.http.request('GET', `api/v1/exports/${enc(id)}`, { user });
|
|
268
|
+
}
|
|
269
|
+
/** The finished export: one JSON archive (`format: "recordare-export/1"`). */
|
|
270
|
+
exportArchive(user, id) {
|
|
271
|
+
return this.http.request('GET', `api/v1/exports/${enc(id)}/archive`, { user });
|
|
272
|
+
}
|
|
162
273
|
/** Closes the MCP sessions (on shutdown). */
|
|
163
274
|
close() {
|
|
164
275
|
return this.mcp.close();
|