@tpsdev-ai/flair-client 0.17.0 → 0.19.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 +41 -5
- package/dist/client.js +84 -19
- package/dist/types.d.ts +23 -2
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -39,20 +39,56 @@ export declare class FlairClient {
|
|
|
39
39
|
declare class MemoryApi {
|
|
40
40
|
private client;
|
|
41
41
|
constructor(client: FlairClient);
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Write a memory. NEVER suppresses the write — the record is always
|
|
44
|
+
* created. `dedup`/`dedupThreshold` are passthrough HINTS forwarded to the
|
|
45
|
+
* server, which runs a conservative (cosine + lexical) near-duplicate check
|
|
46
|
+
* and, when a match is found, attaches a collision signal to the response
|
|
47
|
+
* (`deduplicated`, `matchedId`, `matchConfidence`) instead of dropping the
|
|
48
|
+
* new content. (Historical note: a near-duplicate previously short-circuited
|
|
49
|
+
* this method to return the EXISTING record without writing — that silently
|
|
50
|
+
* dropped distinct-but-similar content, e.g. flair#526. The check now lives
|
|
51
|
+
* server-side in Memory.put()/Memory.post() and never suppresses a write.)
|
|
52
|
+
*/
|
|
43
53
|
write(content: string, opts?: {
|
|
44
54
|
id?: string;
|
|
45
55
|
type?: MemoryType;
|
|
46
56
|
durability?: Durability;
|
|
47
57
|
tags?: string[];
|
|
48
58
|
subject?: string;
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
* Default: false (
|
|
59
|
+
/** Ask the server to run its conservative near-duplicate check for this
|
|
60
|
+
* write and report a collision signal if found. Does NOT suppress the
|
|
61
|
+
* write either way. Default: false (server still applies its own
|
|
62
|
+
* default gate — this only requests the signal be computed/reported;
|
|
63
|
+
* see dedupThreshold to tune it). */
|
|
52
64
|
dedup?: boolean;
|
|
53
|
-
/**
|
|
65
|
+
/** Cosine-similarity threshold hint for the server's dedup gate.
|
|
66
|
+
* Default (server-side): 0.95 */
|
|
54
67
|
dedupThreshold?: number;
|
|
55
68
|
}): Promise<Memory>;
|
|
69
|
+
/**
|
|
70
|
+
* Update an existing memory by id. Dedup-BYPASSED (this IS the intentional
|
|
71
|
+
* overwrite/version path, not an ambiguous new write) — always writes.
|
|
72
|
+
* Auth: the caller must own the memory (enforced server-side by the SAME
|
|
73
|
+
* ownership check Memory.put()/Memory.post() already run — no parallel
|
|
74
|
+
* check here).
|
|
75
|
+
*
|
|
76
|
+
* Default (`preserveHistory` unset/false): same-id overwrite via a
|
|
77
|
+
* full-record PUT. Harper's PUT is FULL RECORD REPLACEMENT, so we read the
|
|
78
|
+
* existing record first and merge the new content on top (never a bare
|
|
79
|
+
* partial). The stale embedding is cleared so the server's existing
|
|
80
|
+
* "generate embedding if missing" step recomputes it for the new content.
|
|
81
|
+
*
|
|
82
|
+
* `opts.preserveHistory: true`: write a NEW id with `supersedes: id`; the
|
|
83
|
+
* server closes the old record's `validTo` afterward, write-new BEFORE
|
|
84
|
+
* close-old (safe failure = two active records, never a lost write). If the
|
|
85
|
+
* old record is owned by a DIFFERENT agent, the server requires a "write"
|
|
86
|
+
* MemoryGrant from that owner — otherwise it denies the request (cross-agent
|
|
87
|
+
* write).
|
|
88
|
+
*/
|
|
89
|
+
update(id: string, content: string, opts?: {
|
|
90
|
+
preserveHistory?: boolean;
|
|
91
|
+
}): Promise<Memory>;
|
|
56
92
|
/** Search memories by meaning. Optionally filter to facts valid at a specific point in time. */
|
|
57
93
|
search(query: string, opts?: {
|
|
58
94
|
limit?: number;
|
package/dist/client.js
CHANGED
|
@@ -106,23 +106,18 @@ class MemoryApi {
|
|
|
106
106
|
constructor(client) {
|
|
107
107
|
this.client = client;
|
|
108
108
|
}
|
|
109
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Write a memory. NEVER suppresses the write — the record is always
|
|
111
|
+
* created. `dedup`/`dedupThreshold` are passthrough HINTS forwarded to the
|
|
112
|
+
* server, which runs a conservative (cosine + lexical) near-duplicate check
|
|
113
|
+
* and, when a match is found, attaches a collision signal to the response
|
|
114
|
+
* (`deduplicated`, `matchedId`, `matchConfidence`) instead of dropping the
|
|
115
|
+
* new content. (Historical note: a near-duplicate previously short-circuited
|
|
116
|
+
* this method to return the EXISTING record without writing — that silently
|
|
117
|
+
* dropped distinct-but-similar content, e.g. flair#526. The check now lives
|
|
118
|
+
* server-side in Memory.put()/Memory.post() and never suppresses a write.)
|
|
119
|
+
*/
|
|
110
120
|
async write(content, opts = {}) {
|
|
111
|
-
// Near-duplicate check — skip for very short content where similarity
|
|
112
|
-
// is unreliable (e.g., "ok", "thanks" would match each other)
|
|
113
|
-
if (opts.dedup && content.length >= 20) {
|
|
114
|
-
const threshold = opts.dedupThreshold ?? 0.95;
|
|
115
|
-
// Use raw scoring to avoid retrieval-boost feedback loop where repeated
|
|
116
|
-
// dedup checks inflate scores above the threshold.
|
|
117
|
-
const existing = await this.search(content, { limit: 1, minScore: threshold, scoring: "raw" });
|
|
118
|
-
if (existing.length > 0) {
|
|
119
|
-
// Return the existing memory instead of creating a duplicate.
|
|
120
|
-
// Flag deduped so callers know this write was suppressed.
|
|
121
|
-
const match = await this.get(existing[0].id);
|
|
122
|
-
if (match)
|
|
123
|
-
return { ...match, deduped: true };
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
121
|
const id = opts.id ?? `${this.client.agentId}-${crypto.randomUUID()}`;
|
|
127
122
|
const record = {
|
|
128
123
|
id,
|
|
@@ -134,9 +129,79 @@ class MemoryApi {
|
|
|
134
129
|
subject: opts.subject,
|
|
135
130
|
createdAt: new Date().toISOString(),
|
|
136
131
|
};
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
132
|
+
// Passthrough hints — the server strips these before persisting; they are
|
|
133
|
+
// never stored on the record itself.
|
|
134
|
+
if (opts.dedup !== undefined)
|
|
135
|
+
record.dedup = opts.dedup;
|
|
136
|
+
if (opts.dedupThreshold !== undefined)
|
|
137
|
+
record.dedupThreshold = opts.dedupThreshold;
|
|
138
|
+
const response = await this.client.request("PUT", `/Memory/${id}`, record);
|
|
139
|
+
// Merge the server response (deduplicated/matchedId/matchConfidence/
|
|
140
|
+
// written, plus any echoed fields) over the locally-constructed record —
|
|
141
|
+
// the write always happens, so `record` always reflects what was sent,
|
|
142
|
+
// and the server's signal fields (if any) always come through.
|
|
143
|
+
return { ...record, ...(response ?? {}) };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Update an existing memory by id. Dedup-BYPASSED (this IS the intentional
|
|
147
|
+
* overwrite/version path, not an ambiguous new write) — always writes.
|
|
148
|
+
* Auth: the caller must own the memory (enforced server-side by the SAME
|
|
149
|
+
* ownership check Memory.put()/Memory.post() already run — no parallel
|
|
150
|
+
* check here).
|
|
151
|
+
*
|
|
152
|
+
* Default (`preserveHistory` unset/false): same-id overwrite via a
|
|
153
|
+
* full-record PUT. Harper's PUT is FULL RECORD REPLACEMENT, so we read the
|
|
154
|
+
* existing record first and merge the new content on top (never a bare
|
|
155
|
+
* partial). The stale embedding is cleared so the server's existing
|
|
156
|
+
* "generate embedding if missing" step recomputes it for the new content.
|
|
157
|
+
*
|
|
158
|
+
* `opts.preserveHistory: true`: write a NEW id with `supersedes: id`; the
|
|
159
|
+
* server closes the old record's `validTo` afterward, write-new BEFORE
|
|
160
|
+
* close-old (safe failure = two active records, never a lost write). If the
|
|
161
|
+
* old record is owned by a DIFFERENT agent, the server requires a "write"
|
|
162
|
+
* MemoryGrant from that owner — otherwise it denies the request (cross-agent
|
|
163
|
+
* write).
|
|
164
|
+
*/
|
|
165
|
+
async update(id, content, opts = {}) {
|
|
166
|
+
const existing = await this.get(id);
|
|
167
|
+
if (!existing) {
|
|
168
|
+
throw new FlairError("PUT", `/Memory/${id}`, 404, `memory ${id} not found`);
|
|
169
|
+
}
|
|
170
|
+
if (opts.preserveHistory) {
|
|
171
|
+
const newId = `${this.client.agentId}-${crypto.randomUUID()}`;
|
|
172
|
+
const record = {
|
|
173
|
+
...existing,
|
|
174
|
+
id: newId,
|
|
175
|
+
content,
|
|
176
|
+
supersedes: id,
|
|
177
|
+
createdAt: new Date().toISOString(),
|
|
178
|
+
};
|
|
179
|
+
delete record.updatedAt;
|
|
180
|
+
delete record.embedding;
|
|
181
|
+
delete record.embeddingModel;
|
|
182
|
+
delete record.validFrom;
|
|
183
|
+
delete record.validTo;
|
|
184
|
+
delete record.archivedAt;
|
|
185
|
+
delete record.deduped;
|
|
186
|
+
// The Memory schema does not expose a working HTTP POST route (see
|
|
187
|
+
// resources/Memory.ts) — Memory.post() is only reachable in-process
|
|
188
|
+
// (resources/mcp-tools.ts). So the supersede-link write goes through
|
|
189
|
+
// the same PUT-with-explicit-(new)-id path as a normal create; the
|
|
190
|
+
// server's Memory.put() validates/authorizes `supersedes`, writes this
|
|
191
|
+
// new record, then closes the old one transactionally (write-new
|
|
192
|
+
// BEFORE close-old — see resources/Memory.ts's closeSupersededIfNeeded).
|
|
193
|
+
// `supersedes` being set also makes the server bypass the dedup gate
|
|
194
|
+
// for this write (it's an intentional version link, not an ambiguous
|
|
195
|
+
// new write).
|
|
196
|
+
const response = await this.client.request("PUT", `/Memory/${newId}`, record);
|
|
197
|
+
return { ...record, ...(response ?? {}) };
|
|
198
|
+
}
|
|
199
|
+
const merged = { ...existing, content, updatedAt: new Date().toISOString() };
|
|
200
|
+
delete merged.embedding;
|
|
201
|
+
delete merged.embeddingModel;
|
|
202
|
+
delete merged.deduped;
|
|
203
|
+
const response = await this.client.request("PUT", `/Memory/${id}`, merged);
|
|
204
|
+
return { ...merged, id, ...(response ?? {}) };
|
|
140
205
|
}
|
|
141
206
|
/** Search memories by meaning. Optionally filter to facts valid at a specific point in time. */
|
|
142
207
|
async search(query, opts = {}) {
|
package/dist/types.d.ts
CHANGED
|
@@ -15,8 +15,29 @@ export interface Memory {
|
|
|
15
15
|
subject?: string;
|
|
16
16
|
createdAt: string;
|
|
17
17
|
updatedAt?: string;
|
|
18
|
-
/**
|
|
19
|
-
*
|
|
18
|
+
/** Always true after a successful write()/update() — the server never
|
|
19
|
+
* suppresses a write, so this is never false/absent on a real response. */
|
|
20
|
+
written?: boolean;
|
|
21
|
+
/** True when the server's conservative dedup gate found a near-duplicate.
|
|
22
|
+
* The new content was ALWAYS written regardless — this is a signal, not a
|
|
23
|
+
* suppression flag. See `matchedId` / `matchConfidence`. */
|
|
24
|
+
deduplicated?: boolean;
|
|
25
|
+
/** The id of the existing memory the server's dedup gate matched against,
|
|
26
|
+
* when `deduplicated` is true. */
|
|
27
|
+
matchedId?: string;
|
|
28
|
+
/** Confidence pair for the `matchedId` collision: raw cosine similarity and
|
|
29
|
+
* Jaccard token-overlap against the new content, both in [0, 1]. */
|
|
30
|
+
matchConfidence?: {
|
|
31
|
+
cosine: number;
|
|
32
|
+
lexical: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Historical field from the pre-fix client-side dedup gate,
|
|
36
|
+
* which suppressed the write and returned the EXISTING record instead
|
|
37
|
+
* (silently dropping distinct-but-similar content — flair#526). The gate is
|
|
38
|
+
* now server-side and NEVER suppresses a write; use `deduplicated` instead.
|
|
39
|
+
* No longer set by write()/update().
|
|
40
|
+
*/
|
|
20
41
|
deduped?: boolean;
|
|
21
42
|
}
|
|
22
43
|
/** A soul entry (persistent personality/values). */
|
package/package.json
CHANGED