@trylucy/agent-memory 0.0.0-stage → 0.1.2
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/LICENSE +202 -0
- package/README.md +205 -2
- package/contracts/agent-manifest.json +27 -0
- package/contracts/agent-memory-api.contract.json +199 -0
- package/contracts/hashes.json +23 -0
- package/package.json +84 -4
- package/schemas/canonical-recall/response.schema.json +56 -0
- package/schemas/v1/common.schema.json +190 -0
- package/schemas/v1/contract-document.schema.json +277 -0
- package/schemas/v1/create-proposal-request.schema.json +42 -0
- package/schemas/v1/error.schema.json +90 -0
- package/schemas/v1/memory-version-state.schema.json +39 -0
- package/schemas/v1/memory.schema.json +47 -0
- package/schemas/v1/proposal-status.schema.json +277 -0
- package/schemas/v1/public-surface.schema.json +26 -0
- package/schemas/v1/recall-request.schema.json +45 -0
- package/schemas/v1/recall-response.schema.json +60 -0
- package/schemas/v1/receipt.schema.json +67 -0
- package/schemas/v1/replace-proposal-request.schema.json +46 -0
- package/schemas/v1/replace-transition.schema.json +19 -0
- package/schemas/v1/scope.schema.json +10 -0
- package/scripts/lib/am01.mjs +159 -0
- package/src/client/canonical-validation.mjs +37 -0
- package/src/client/client.mjs +151 -0
- package/src/client/errors.mjs +101 -0
- package/src/client/http-transport.d.ts +12 -0
- package/src/client/http-transport.mjs +212 -0
- package/src/client/index.d.ts +243 -0
- package/src/client/index.mjs +38 -0
- package/src/client/serialization.mjs +53 -0
- package/src/client/validation.mjs +174 -0
- package/src/clients/claude-desktop.json +13 -0
- package/src/clients/claude.json +13 -0
- package/src/clients/codex.toml +8 -0
- package/src/clients/cursor.json +12 -0
- package/src/clients/generic.mcp.json +12 -0
- package/src/clients/openclaw.json +14 -0
- package/src/clients/vscode.json +13 -0
- package/src/integrations/hermes-advisory-lock.mjs +140 -0
- package/src/integrations/hermes-artifact.mjs +551 -0
- package/src/integrations/hermes-filesystem.mjs +112 -0
- package/src/integrations/hermes.d.ts +106 -0
- package/src/integrations/hermes.mjs +425 -0
- package/src/mcp/bin.mjs +4 -0
- package/src/mcp/config.mjs +60 -0
- package/src/mcp/index.d.ts +27 -0
- package/src/mcp/lifecycle.mjs +50 -0
- package/src/mcp/schemas.mjs +98 -0
- package/src/mcp/server.mjs +163 -0
- package/src/mcp/stdio.d.ts +28 -0
- package/src/mcp/stdio.mjs +59 -0
- package/src/onboarding/usage.mjs +44 -0
- package/src/projection/memory-md.mjs +319 -0
- package/src/provider/index.d.ts +9 -0
- package/src/provider/index.mjs +1 -0
- package/src/provider/memory-first.mjs +95 -0
- package/src/routing/destination.mjs +42 -0
- package/src/safety/secrets.mjs +31 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
2
|
+
import { MalformedResponseError, ValidationFailureError } from './errors.mjs';
|
|
3
|
+
|
|
4
|
+
const PREFIX = '/api/lucy/agent-memory/v1';
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
const MAX_RESPONSE_BYTES = 65_536;
|
|
7
|
+
const MAX_RETRY_AFTER_MS = 60_000;
|
|
8
|
+
|
|
9
|
+
function isLoopbackHostname(hostname) {
|
|
10
|
+
if (hostname === 'localhost') return true;
|
|
11
|
+
const literal = hostname.startsWith('[') && hostname.endsWith(']')
|
|
12
|
+
? hostname.slice(1, -1)
|
|
13
|
+
: hostname;
|
|
14
|
+
if (isIP(literal) === 4) return literal.split('.')[0] === '127';
|
|
15
|
+
return isIP(literal) === 6 && literal === '::1';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeBaseUrl(baseUrl, allowInsecureLoopback) {
|
|
19
|
+
if (typeof baseUrl !== 'string' || baseUrl.length === 0 || /[\r\n\0]/.test(baseUrl)) {
|
|
20
|
+
throw new ValidationFailureError('baseUrl must be a non-empty http(s) URL.');
|
|
21
|
+
}
|
|
22
|
+
let url;
|
|
23
|
+
try { url = new URL(baseUrl); } catch { throw new ValidationFailureError('baseUrl must be a valid URL.'); }
|
|
24
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
25
|
+
throw new ValidationFailureError('baseUrl must use http or https.');
|
|
26
|
+
}
|
|
27
|
+
if (url.protocol === 'http:' && !(allowInsecureLoopback && isLoopbackHostname(url.hostname))) {
|
|
28
|
+
throw new ValidationFailureError('baseUrl must use https; cleartext HTTP requires explicit loopback development opt-in.');
|
|
29
|
+
}
|
|
30
|
+
return url.origin + url.pathname.replace(/\/+$/, '');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseRetryAfterMs(value) {
|
|
34
|
+
if (value == null || value === '') return undefined;
|
|
35
|
+
const seconds = Number(value);
|
|
36
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.min(MAX_RETRY_AFTER_MS, Math.trunc(seconds * 1000));
|
|
37
|
+
const date = Date.parse(value);
|
|
38
|
+
if (Number.isFinite(date)) return Math.min(MAX_RETRY_AFTER_MS, Math.max(0, date - Date.now()));
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function abortReason(signal) {
|
|
43
|
+
if (signal?.reason !== undefined) return signal.reason;
|
|
44
|
+
return new DOMException('The operation was aborted.', 'AbortError');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function throwIfAborted(signal) {
|
|
48
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function defaultSleep(ms, signal) {
|
|
52
|
+
throwIfAborted(signal);
|
|
53
|
+
await new Promise((resolve, reject) => {
|
|
54
|
+
const finish = () => {
|
|
55
|
+
signal?.removeEventListener('abort', onAbort);
|
|
56
|
+
resolve();
|
|
57
|
+
};
|
|
58
|
+
const timer = setTimeout(finish, ms);
|
|
59
|
+
const onAbort = () => {
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
signal?.removeEventListener('abort', onAbort);
|
|
62
|
+
reject(abortReason(signal));
|
|
63
|
+
};
|
|
64
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function waitForRetry(ms, signal, sleep) {
|
|
69
|
+
throwIfAborted(signal);
|
|
70
|
+
let onAbort;
|
|
71
|
+
const aborted = new Promise((_, reject) => {
|
|
72
|
+
onAbort = () => reject(abortReason(signal));
|
|
73
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
74
|
+
});
|
|
75
|
+
try {
|
|
76
|
+
await Promise.race([Promise.resolve().then(() => sleep(ms, signal)), aborted]);
|
|
77
|
+
} finally {
|
|
78
|
+
signal?.removeEventListener('abort', onAbort);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function readResponseBody(response, signal) {
|
|
83
|
+
const reader = response?.body?.getReader?.();
|
|
84
|
+
if (!reader) {
|
|
85
|
+
throw new MalformedResponseError('Lucy response does not expose a bounded streaming body.', { status: response?.status });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const chunks = [];
|
|
89
|
+
let byteLength = 0;
|
|
90
|
+
try {
|
|
91
|
+
while (true) {
|
|
92
|
+
throwIfAborted(signal);
|
|
93
|
+
const { done, value } = await reader.read();
|
|
94
|
+
if (done) break;
|
|
95
|
+
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
96
|
+
byteLength += chunk.byteLength;
|
|
97
|
+
if (byteLength > MAX_RESPONSE_BYTES) {
|
|
98
|
+
try { await reader.cancel(); } catch { /* retain the bounded-response error */ }
|
|
99
|
+
throw new MalformedResponseError(`Lucy response exceeds ${MAX_RESPONSE_BYTES} bytes.`, { status: response.status });
|
|
100
|
+
}
|
|
101
|
+
chunks.push(chunk);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
reader.releaseLock?.();
|
|
105
|
+
}
|
|
106
|
+
return { rawText: Buffer.concat(chunks, byteLength).toString('utf8'), byteLength };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function routeFor(operation, payload) {
|
|
110
|
+
if (operation === 'recall') {
|
|
111
|
+
return { method: 'POST', path: `${PREFIX}/recall`, body: payload };
|
|
112
|
+
}
|
|
113
|
+
if (operation === 'canonical-recall') {
|
|
114
|
+
return { method: 'POST', path: `${PREFIX}/recall`, body: payload, projection: 'canonical-v1' };
|
|
115
|
+
}
|
|
116
|
+
if (operation === 'propose') {
|
|
117
|
+
return { method: 'POST', path: `${PREFIX}/proposals`, body: payload };
|
|
118
|
+
}
|
|
119
|
+
if (operation === 'proposal-status') {
|
|
120
|
+
let parsed;
|
|
121
|
+
try { parsed = JSON.parse(payload); } catch { parsed = {}; }
|
|
122
|
+
const id = typeof parsed.proposal_id === 'string' ? parsed.proposal_id : '';
|
|
123
|
+
return { method: 'GET', path: `${PREFIX}/proposals/${encodeURIComponent(id)}`, body: null };
|
|
124
|
+
}
|
|
125
|
+
throw new ValidationFailureError(`Unsupported Agent Memory operation: ${operation}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function idempotencyKey(payload) {
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(payload);
|
|
131
|
+
return typeof parsed.idempotency_key === 'string' ? parsed.idempotency_key : null;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function requestId(payload) {
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(payload);
|
|
140
|
+
return typeof parsed.request_id === 'string' ? parsed.request_id : null;
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function shouldRetry(operation, status, attempt, maxRetries) {
|
|
147
|
+
if (operation !== 'propose') return false;
|
|
148
|
+
return attempt < maxRetries && (status === 429 || status === 503 || status === 502 || status === 504);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function createLucyHttpTransport({
|
|
152
|
+
baseUrl,
|
|
153
|
+
allowInsecureLoopback = false,
|
|
154
|
+
fetchImpl = globalThis.fetch,
|
|
155
|
+
maxRetries = DEFAULT_MAX_RETRIES,
|
|
156
|
+
sleep = defaultSleep,
|
|
157
|
+
} = {}) {
|
|
158
|
+
if (typeof allowInsecureLoopback !== 'boolean') throw new ValidationFailureError('allowInsecureLoopback must be a boolean.');
|
|
159
|
+
const origin = normalizeBaseUrl(baseUrl, allowInsecureLoopback);
|
|
160
|
+
if (typeof fetchImpl !== 'function') throw new ValidationFailureError('fetchImpl must be a function.');
|
|
161
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 0 || maxRetries > 5) {
|
|
162
|
+
throw new ValidationFailureError('maxRetries must be an integer from 0 through 5.');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return async function lucyHttpTransport(request) {
|
|
166
|
+
const { operation, payload, accessToken, signal } = request;
|
|
167
|
+
const route = routeFor(operation, payload);
|
|
168
|
+
const headers = {
|
|
169
|
+
accept: 'application/json',
|
|
170
|
+
authorization: `Bearer ${accessToken}`,
|
|
171
|
+
};
|
|
172
|
+
if (route.body != null) headers['content-type'] = 'application/json';
|
|
173
|
+
if (route.projection) headers['x-lucy-memory-projection'] = route.projection;
|
|
174
|
+
const key = route.method === 'POST' && operation === 'propose' ? idempotencyKey(payload) : null;
|
|
175
|
+
if (key) headers['idempotency-key'] = key;
|
|
176
|
+
const rid = requestId(payload);
|
|
177
|
+
if (rid) headers['x-request-id'] = rid;
|
|
178
|
+
|
|
179
|
+
let attempt = 0;
|
|
180
|
+
let last;
|
|
181
|
+
while (true) {
|
|
182
|
+
throwIfAborted(signal);
|
|
183
|
+
const response = await fetchImpl(origin + route.path, {
|
|
184
|
+
method: route.method,
|
|
185
|
+
headers,
|
|
186
|
+
body: route.body,
|
|
187
|
+
signal,
|
|
188
|
+
redirect: 'error',
|
|
189
|
+
});
|
|
190
|
+
const { rawText, byteLength } = await readResponseBody(response, signal);
|
|
191
|
+
let body = null;
|
|
192
|
+
if (rawText.length > 0) {
|
|
193
|
+
try { body = JSON.parse(rawText); } catch { body = rawText; }
|
|
194
|
+
}
|
|
195
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get('retry-after'));
|
|
196
|
+
const memoryOperationId = response.headers.get('x-lucy-memory-operation-id');
|
|
197
|
+
last = {
|
|
198
|
+
status: response.status,
|
|
199
|
+
body,
|
|
200
|
+
byteLength,
|
|
201
|
+
...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
|
|
202
|
+
...(typeof memoryOperationId === 'string' && memoryOperationId.length > 0
|
|
203
|
+
? { memoryOperationId }
|
|
204
|
+
: {}),
|
|
205
|
+
};
|
|
206
|
+
if (!shouldRetry(operation, response.status, attempt, maxRetries)) return last;
|
|
207
|
+
attempt += 1;
|
|
208
|
+
const wait = retryAfterMs ?? 0;
|
|
209
|
+
if (wait > 0) await waitForRetry(wait, signal, sleep);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
export type MemoryKind = 'fact' | 'preference' | 'decision' | 'rule';
|
|
2
|
+
export type MemoryLifecycle = 'active' | 'superseded' | 'revoked' | 'forgotten';
|
|
3
|
+
export type ProposalLifecycle = 'pending' | 'approved' | 'rejected' | 'expired' | 'superseded';
|
|
4
|
+
export type PublicPermission = 'memory:read' | 'memory:propose';
|
|
5
|
+
|
|
6
|
+
export type UserScope = Readonly<{ kind: 'user' }>;
|
|
7
|
+
export type WorkspaceScope = Readonly<{ kind: 'workspace' }>;
|
|
8
|
+
export type ProjectScope = Readonly<{ kind: 'project'; id: `project_${string}` }>;
|
|
9
|
+
export type MemoryScope = UserScope | WorkspaceScope | ProjectScope;
|
|
10
|
+
|
|
11
|
+
export interface ReceiptRelation {
|
|
12
|
+
type: 'supersedes';
|
|
13
|
+
memory_id: `mem_${string}`;
|
|
14
|
+
memory_version: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface MemoryReceipt {
|
|
18
|
+
schema_version: '1';
|
|
19
|
+
id: `rcpt_${string}`;
|
|
20
|
+
memory_id: `mem_${string}`;
|
|
21
|
+
memory_version: number;
|
|
22
|
+
key: string;
|
|
23
|
+
kind: MemoryKind;
|
|
24
|
+
scope: MemoryScope;
|
|
25
|
+
lifecycle: MemoryLifecycle;
|
|
26
|
+
authority: { type: 'human_approved' };
|
|
27
|
+
authoritative_at: string;
|
|
28
|
+
issued_at: string;
|
|
29
|
+
provenance: { type: 'governed_record'; ref: string };
|
|
30
|
+
relations: ReceiptRelation[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ActiveMemory {
|
|
34
|
+
schema_version: '1';
|
|
35
|
+
id: `mem_${string}`;
|
|
36
|
+
version: number;
|
|
37
|
+
key: string;
|
|
38
|
+
kind: MemoryKind;
|
|
39
|
+
scope: MemoryScope;
|
|
40
|
+
content: string;
|
|
41
|
+
status: 'active';
|
|
42
|
+
receipt: MemoryReceipt;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RecallRequest {
|
|
46
|
+
query: string;
|
|
47
|
+
scope: MemoryScope;
|
|
48
|
+
keys?: string[];
|
|
49
|
+
kinds?: MemoryKind[];
|
|
50
|
+
limit?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RecallResponse {
|
|
54
|
+
schema_version: '1';
|
|
55
|
+
request_id: `req_${string}`;
|
|
56
|
+
as_of: string;
|
|
57
|
+
consistency: 'canonical_active';
|
|
58
|
+
scope: MemoryScope;
|
|
59
|
+
results: ActiveMemory[];
|
|
60
|
+
notices: string[];
|
|
61
|
+
/** Present when Lucy returns x-lucy-memory-operation-id; attached after AM-01 validation. */
|
|
62
|
+
operation_id?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type CanonicalApprovalStatus = 'user_approved' | 'approved' | 'replayed' | 'controller_approved';
|
|
66
|
+
export interface CanonicalProvenance {
|
|
67
|
+
type: 'lucy_canonical';
|
|
68
|
+
record_id: string;
|
|
69
|
+
proposal_id: string | null;
|
|
70
|
+
canonical_payload_hash: string | null;
|
|
71
|
+
written_at: string;
|
|
72
|
+
}
|
|
73
|
+
export interface CanonicalRecallResult {
|
|
74
|
+
record_id: string;
|
|
75
|
+
key: string | null;
|
|
76
|
+
kind: string;
|
|
77
|
+
scope: MemoryScope;
|
|
78
|
+
content: string;
|
|
79
|
+
status: 'active';
|
|
80
|
+
persisted: true;
|
|
81
|
+
/** Native approval state; controller_approved does not claim a human receipt. */
|
|
82
|
+
approval_status: CanonicalApprovalStatus;
|
|
83
|
+
version: number | null;
|
|
84
|
+
provenance: CanonicalProvenance;
|
|
85
|
+
}
|
|
86
|
+
export interface CanonicalRecallResponse {
|
|
87
|
+
schema_version: 'canonical-recall-1';
|
|
88
|
+
request_id: `req_${string}`;
|
|
89
|
+
as_of: string;
|
|
90
|
+
consistency: 'canonical_active';
|
|
91
|
+
scope: MemoryScope;
|
|
92
|
+
results: CanonicalRecallResult[];
|
|
93
|
+
notices: [];
|
|
94
|
+
operation_id?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CreateProposalRequest {
|
|
98
|
+
operation: 'create';
|
|
99
|
+
key: string;
|
|
100
|
+
kind: MemoryKind;
|
|
101
|
+
scope: MemoryScope;
|
|
102
|
+
content: string;
|
|
103
|
+
justification: string;
|
|
104
|
+
idempotency_key: string;
|
|
105
|
+
source_ref?: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ReplaceProposalRequest {
|
|
109
|
+
operation: 'replace';
|
|
110
|
+
memory_id: `mem_${string}`;
|
|
111
|
+
expected_version: number;
|
|
112
|
+
key: string;
|
|
113
|
+
scope: MemoryScope;
|
|
114
|
+
content: string;
|
|
115
|
+
justification: string;
|
|
116
|
+
idempotency_key: string;
|
|
117
|
+
source_ref?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type ProposalRequest = CreateProposalRequest | ReplaceProposalRequest;
|
|
121
|
+
|
|
122
|
+
export interface ProposalResult {
|
|
123
|
+
memory_id: `mem_${string}`;
|
|
124
|
+
memory_version: number;
|
|
125
|
+
receipt_id: `rcpt_${string}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ProposalStatus {
|
|
129
|
+
schema_version: '1';
|
|
130
|
+
id: `proposal_${string}`;
|
|
131
|
+
proposal_version: 1;
|
|
132
|
+
operation: 'create' | 'replace';
|
|
133
|
+
status: ProposalLifecycle;
|
|
134
|
+
requested: {
|
|
135
|
+
memory_id?: `mem_${string}`;
|
|
136
|
+
expected_version?: number;
|
|
137
|
+
key: string;
|
|
138
|
+
kind: MemoryKind;
|
|
139
|
+
scope: MemoryScope;
|
|
140
|
+
};
|
|
141
|
+
relations: [];
|
|
142
|
+
created_at: string;
|
|
143
|
+
expires_at: string;
|
|
144
|
+
decided_at: string | null;
|
|
145
|
+
result: ProposalResult | null;
|
|
146
|
+
/** Present when Lucy returns x-lucy-memory-operation-id; attached after AM-01 validation. */
|
|
147
|
+
operation_id?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ProposalStatusRequest {
|
|
151
|
+
proposal_id: `proposal_${string}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface TransportRequest {
|
|
155
|
+
operation: 'recall' | 'canonical-recall' | 'propose' | 'proposal-status';
|
|
156
|
+
payload: string;
|
|
157
|
+
accessToken: string;
|
|
158
|
+
signal: AbortSignal;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface TransportResponse {
|
|
162
|
+
status: number;
|
|
163
|
+
body: unknown;
|
|
164
|
+
byteLength: number;
|
|
165
|
+
retryAfterMs?: number;
|
|
166
|
+
/** Echo of Lucy response header x-lucy-memory-operation-id when present. */
|
|
167
|
+
memoryOperationId?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type AgentMemoryTransport = (request: Readonly<TransportRequest>) => Promise<TransportResponse>;
|
|
171
|
+
|
|
172
|
+
export interface AgentMemoryClientOptions {
|
|
173
|
+
transport: AgentMemoryTransport;
|
|
174
|
+
token: string;
|
|
175
|
+
permissions: PublicPermission[];
|
|
176
|
+
timeoutMs?: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export type RecallRequestJson = string;
|
|
180
|
+
export type ProposalRequestJson = string;
|
|
181
|
+
export type ProposalStatusRequestJson = string;
|
|
182
|
+
|
|
183
|
+
export { createLucyHttpTransport, type LucyHttpTransportOptions } from './http-transport.js';
|
|
184
|
+
|
|
185
|
+
export class AgentMemoryClient {
|
|
186
|
+
constructor(options: AgentMemoryClientOptions);
|
|
187
|
+
recall(requestJson: RecallRequestJson): Promise<RecallResponse>;
|
|
188
|
+
recallCanonical(requestJson: RecallRequestJson): Promise<CanonicalRecallResponse>;
|
|
189
|
+
propose(requestJson: ProposalRequestJson): Promise<ProposalStatus>;
|
|
190
|
+
proposalStatus(requestJson: ProposalStatusRequestJson): Promise<ProposalStatus>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export interface AgentMemoryErrorOptions {
|
|
194
|
+
code?: string;
|
|
195
|
+
status?: number | null;
|
|
196
|
+
retryable?: boolean;
|
|
197
|
+
details?: unknown;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export class AgentMemoryError extends Error {
|
|
201
|
+
readonly code: string;
|
|
202
|
+
readonly status: number | null;
|
|
203
|
+
readonly retryable: boolean;
|
|
204
|
+
readonly details: unknown;
|
|
205
|
+
constructor(message: string, options?: AgentMemoryErrorOptions);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export class AuthenticationFailureError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
209
|
+
export class AuthorizationFailureError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
210
|
+
export class InvalidScopeError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
211
|
+
export class InvalidKeyError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
212
|
+
export class InvalidKindError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
213
|
+
export class ValidationFailureError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
214
|
+
export class ProposalNotFoundError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
215
|
+
export class MemoryNotFoundError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
216
|
+
export class VersionConflictError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
217
|
+
export class LifecycleConflictError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
218
|
+
export class UpstreamUnavailableError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
219
|
+
export class TimeoutError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
220
|
+
export class MalformedResponseError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
221
|
+
export class InternalLucyError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
222
|
+
export class InsufficientCreditsError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
223
|
+
export class BillingUnavailableError extends AgentMemoryError { constructor(message?: string, options?: AgentMemoryErrorOptions); }
|
|
224
|
+
export class RateLimitError extends AgentMemoryError {
|
|
225
|
+
readonly retryAfterMs: number | null;
|
|
226
|
+
constructor(message?: string, options?: AgentMemoryErrorOptions & { retryAfterMs?: number | null });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export const MEMORY_KINDS: readonly MemoryKind[];
|
|
230
|
+
export const MEMORY_LIFECYCLES: readonly MemoryLifecycle[];
|
|
231
|
+
export const PROPOSAL_LIFECYCLES: readonly ProposalLifecycle[];
|
|
232
|
+
export const SCOPE_KINDS: readonly MemoryScope['kind'][];
|
|
233
|
+
|
|
234
|
+
export function stableSerialize(value: unknown): string;
|
|
235
|
+
export function validateScope(scope: unknown): MemoryScope;
|
|
236
|
+
export function validateKey(key: unknown): string;
|
|
237
|
+
export function validateKind(kind: unknown): MemoryKind;
|
|
238
|
+
export function validateRecallRequest(request: unknown): RecallRequest;
|
|
239
|
+
export function validateProposalRequest(request: unknown): ProposalRequest;
|
|
240
|
+
export function validateProposalStatusRequest(request: unknown): ProposalStatusRequest;
|
|
241
|
+
export function validateRecallResponse(value: unknown, request?: RecallRequest): RecallResponse;
|
|
242
|
+
export function validateCanonicalRecallResponse(value: unknown, request?: RecallRequest): CanonicalRecallResponse;
|
|
243
|
+
export function validateProposalResponse(value: unknown, request?: ProposalRequest | ProposalStatusRequest): ProposalStatus;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { AgentMemoryClient } from './client.mjs';
|
|
2
|
+
export { createLucyHttpTransport } from './http-transport.mjs';
|
|
3
|
+
export {
|
|
4
|
+
AgentMemoryError,
|
|
5
|
+
AuthenticationFailureError,
|
|
6
|
+
AuthorizationFailureError,
|
|
7
|
+
InternalLucyError,
|
|
8
|
+
InvalidKeyError,
|
|
9
|
+
InvalidKindError,
|
|
10
|
+
InvalidScopeError,
|
|
11
|
+
LifecycleConflictError,
|
|
12
|
+
MalformedResponseError,
|
|
13
|
+
MemoryNotFoundError,
|
|
14
|
+
ProposalNotFoundError,
|
|
15
|
+
RateLimitError,
|
|
16
|
+
TimeoutError,
|
|
17
|
+
UpstreamUnavailableError,
|
|
18
|
+
ValidationFailureError,
|
|
19
|
+
VersionConflictError,
|
|
20
|
+
InsufficientCreditsError,
|
|
21
|
+
BillingUnavailableError,
|
|
22
|
+
} from './errors.mjs';
|
|
23
|
+
export { stableSerialize } from './serialization.mjs';
|
|
24
|
+
export {
|
|
25
|
+
MEMORY_KINDS,
|
|
26
|
+
MEMORY_LIFECYCLES,
|
|
27
|
+
PROPOSAL_LIFECYCLES,
|
|
28
|
+
SCOPE_KINDS,
|
|
29
|
+
validateKey,
|
|
30
|
+
validateKind,
|
|
31
|
+
validateProposalRequest,
|
|
32
|
+
validateProposalResponse,
|
|
33
|
+
validateProposalStatusRequest,
|
|
34
|
+
validateRecallRequest,
|
|
35
|
+
validateRecallResponse,
|
|
36
|
+
validateScope,
|
|
37
|
+
} from './validation.mjs';
|
|
38
|
+
export { validateCanonicalRecallResponse } from './canonical-validation.mjs';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ValidationFailureError } from './errors.mjs';
|
|
2
|
+
|
|
3
|
+
function inertRecord(value) {
|
|
4
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return value;
|
|
5
|
+
const record = Object.create(null);
|
|
6
|
+
for (const key of Object.keys(value)) record[key] = value[key];
|
|
7
|
+
return record;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parseRequestJson(value, label, maxBytes) {
|
|
11
|
+
if (typeof value !== 'string') {
|
|
12
|
+
throw new ValidationFailureError(`${label} must be supplied as primitive JSON text; caller objects are not accepted.`);
|
|
13
|
+
}
|
|
14
|
+
if (Buffer.byteLength(value) > maxBytes) throw new ValidationFailureError(`${label} exceeds ${maxBytes} UTF-8 bytes.`);
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(value, (_key, child) => inertRecord(child));
|
|
17
|
+
} catch {
|
|
18
|
+
throw new ValidationFailureError(`${label} must be valid JSON text.`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function serialize(value, seen, inArray = false) {
|
|
23
|
+
if (value === null) return 'null';
|
|
24
|
+
if (value === undefined) return inArray ? 'null' : undefined;
|
|
25
|
+
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
|
|
26
|
+
if (typeof value === 'number') {
|
|
27
|
+
if (!Number.isFinite(value)) throw new ValidationFailureError('Requests cannot contain non-finite numbers.');
|
|
28
|
+
return JSON.stringify(value);
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== 'object') throw new ValidationFailureError(`Requests cannot contain ${typeof value} values.`);
|
|
31
|
+
if (seen.has(value)) throw new ValidationFailureError('Requests cannot contain circular values.');
|
|
32
|
+
seen.add(value);
|
|
33
|
+
let result;
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
result = `[${value.map((item) => serialize(item, seen, true)).join(',')}]`;
|
|
36
|
+
} else {
|
|
37
|
+
const prototype = Object.getPrototypeOf(value);
|
|
38
|
+
if (prototype !== Object.prototype && prototype !== null) throw new ValidationFailureError('Requests may contain only plain objects and arrays.');
|
|
39
|
+
const entries = Object.keys(value).sort().flatMap((key) => {
|
|
40
|
+
const encoded = serialize(value[key], seen, false);
|
|
41
|
+
return encoded === undefined ? [] : [`${JSON.stringify(key)}:${encoded}`];
|
|
42
|
+
});
|
|
43
|
+
result = `{${entries.join(',')}}`;
|
|
44
|
+
}
|
|
45
|
+
seen.delete(value);
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function stableSerialize(value) {
|
|
50
|
+
const serialized = serialize(value, new Set());
|
|
51
|
+
if (serialized === undefined) throw new ValidationFailureError('The request body cannot be undefined.');
|
|
52
|
+
return serialized;
|
|
53
|
+
}
|