korely-memory 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.
package/dist/index.js ADDED
@@ -0,0 +1,354 @@
1
+ // src/errors.ts
2
+ var KorelyError = class extends Error {
3
+ constructor(message = "", opts = {}) {
4
+ super(message || opts.code || "Korely error");
5
+ this.name = "KorelyError";
6
+ this.status = opts.status;
7
+ this.code = opts.code;
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ };
11
+ var AuthenticationError = class _AuthenticationError extends KorelyError {
12
+ constructor(message = "", opts = {}) {
13
+ super(message, opts);
14
+ this.name = "AuthenticationError";
15
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
16
+ }
17
+ };
18
+ var NamespaceForbiddenError = class _NamespaceForbiddenError extends KorelyError {
19
+ constructor(message = "", opts = {}) {
20
+ super(message, opts);
21
+ this.name = "NamespaceForbiddenError";
22
+ Object.setPrototypeOf(this, _NamespaceForbiddenError.prototype);
23
+ }
24
+ };
25
+ var NotFoundError = class _NotFoundError extends KorelyError {
26
+ constructor(message = "", opts = {}) {
27
+ super(message, opts);
28
+ this.name = "NotFoundError";
29
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
30
+ }
31
+ };
32
+ var StaleWriteError = class _StaleWriteError extends KorelyError {
33
+ constructor(message = "", opts = {}) {
34
+ super(message, opts);
35
+ this.name = "StaleWriteError";
36
+ Object.setPrototypeOf(this, _StaleWriteError.prototype);
37
+ }
38
+ };
39
+ var QuotaExceededError = class _QuotaExceededError extends KorelyError {
40
+ constructor(message = "", opts = {}) {
41
+ super(message, opts);
42
+ this.name = "QuotaExceededError";
43
+ this.retryAfter = opts.retryAfter;
44
+ Object.setPrototypeOf(this, _QuotaExceededError.prototype);
45
+ }
46
+ };
47
+ var APIError = class _APIError extends KorelyError {
48
+ constructor(message = "", opts = {}) {
49
+ super(message, opts);
50
+ this.name = "APIError";
51
+ Object.setPrototypeOf(this, _APIError.prototype);
52
+ }
53
+ };
54
+
55
+ // src/client.ts
56
+ var VERSION = "0.1.0";
57
+ var REGIONS = { eu: "https://api.korely.ai" };
58
+ function coerceContent(content) {
59
+ if (typeof content === "string") return content;
60
+ if (Array.isArray(content)) {
61
+ const parts = [];
62
+ for (const m of content) {
63
+ if (m && typeof m === "object") {
64
+ const role = String(m.role ?? "").trim();
65
+ const raw = m.content;
66
+ const body = raw == null ? "" : String(raw).trim();
67
+ if (role && body) parts.push(`${role}: ${body}`);
68
+ else if (body) parts.push(body);
69
+ } else {
70
+ const s = String(m).trim();
71
+ if (s) parts.push(s);
72
+ }
73
+ }
74
+ return parts.join("\n");
75
+ }
76
+ return String(content);
77
+ }
78
+ var Korely = class {
79
+ constructor(opts = {}) {
80
+ const envKey = typeof process !== "undefined" ? process.env?.KORELY_API_KEY : void 0;
81
+ const key = opts.apiKey ?? envKey;
82
+ if (!key) {
83
+ throw new KorelyError(
84
+ "No API key. Pass { apiKey: 'kor_live_...' } or set KORELY_API_KEY."
85
+ );
86
+ }
87
+ this.apiKey = key;
88
+ this.baseUrl = (opts.baseUrl ?? REGIONS[opts.region ?? "eu"] ?? REGIONS.eu).replace(/\/+$/, "");
89
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
90
+ const f = opts.fetch ?? globalThis.fetch;
91
+ if (!f) {
92
+ throw new KorelyError(
93
+ "No fetch available. On Node < 18 pass a fetch implementation via { fetch }."
94
+ );
95
+ }
96
+ this.fetchImpl = f;
97
+ }
98
+ // ── transport ─────────────────────────────────────────────────────────────
99
+ async request(method, path, opts = {}) {
100
+ let url = this.baseUrl + path;
101
+ if (opts.params) {
102
+ const qs = new URLSearchParams();
103
+ for (const [k, v] of Object.entries(opts.params)) {
104
+ if (v !== void 0 && v !== null) qs.append(k, String(v));
105
+ }
106
+ const s = qs.toString();
107
+ if (s) url += "?" + s;
108
+ }
109
+ const headers = {
110
+ Authorization: `Bearer ${this.apiKey}`,
111
+ Accept: "application/json",
112
+ "X-Korely-Client": `korely-js/${VERSION}`
113
+ };
114
+ let body;
115
+ if (opts.body !== void 0) {
116
+ body = JSON.stringify(opts.body);
117
+ headers["Content-Type"] = "application/json";
118
+ }
119
+ const controller = new AbortController();
120
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
121
+ let resp;
122
+ try {
123
+ resp = await this.fetchImpl(url, {
124
+ method,
125
+ headers,
126
+ body,
127
+ signal: controller.signal
128
+ });
129
+ } catch (e) {
130
+ throw new KorelyError(`Connection error: ${e?.message ?? String(e)}`);
131
+ } finally {
132
+ clearTimeout(timer);
133
+ }
134
+ const text = await resp.text();
135
+ let parsed = {};
136
+ if (text) {
137
+ try {
138
+ parsed = JSON.parse(text);
139
+ } catch {
140
+ parsed = { message: text };
141
+ }
142
+ }
143
+ if (!resp.ok) {
144
+ this.raise(resp.status, parsed, resp.headers.get("retry-after"));
145
+ }
146
+ return parsed && typeof parsed === "object" ? parsed : {};
147
+ }
148
+ raise(status, body, retryAfter) {
149
+ const code = body?.code;
150
+ const msg = body?.message || code || `HTTP ${status}`;
151
+ if (status === 401) throw new AuthenticationError(msg, { status, code });
152
+ if (status === 403) throw new NamespaceForbiddenError(msg, { status, code });
153
+ if (status === 404) throw new NotFoundError(msg, { status, code });
154
+ if (status === 409) throw new StaleWriteError(msg, { status, code });
155
+ if (status === 429) {
156
+ const raw = retryAfter ?? body?.retry_after ?? body?._retry_after;
157
+ let ra;
158
+ if (raw != null) {
159
+ const n = parseInt(String(raw), 10);
160
+ ra = Number.isNaN(n) ? void 0 : n;
161
+ }
162
+ throw new QuotaExceededError(msg, { status, code, retryAfter: ra });
163
+ }
164
+ throw new APIError(msg, { status, code });
165
+ }
166
+ // ── memories ────────────────────────────────────────────────────────────
167
+ /**
168
+ * POST /v1/memories — store a memory; resolves to it with extracted facts.
169
+ * `content` is a string, or a list of chat messages (role/content), joined
170
+ * into one block before sending.
171
+ */
172
+ async add(content, opts = {}) {
173
+ const text = coerceContent(content);
174
+ if (!text.trim()) {
175
+ throw new KorelyError(
176
+ "content is empty \u2014 pass a non-blank string or messages with content."
177
+ );
178
+ }
179
+ return this.request("POST", "/v1/memories", {
180
+ body: {
181
+ content: text,
182
+ agent_id: opts.agent_id,
183
+ user_id: opts.user_id,
184
+ run_id: opts.run_id,
185
+ metadata: opts.metadata
186
+ }
187
+ });
188
+ }
189
+ /** POST /v1/memories/search — hybrid retrieval, ranked by score. */
190
+ async search(query, opts = {}) {
191
+ const body = await this.request("POST", "/v1/memories/search", {
192
+ body: {
193
+ query,
194
+ user_id: opts.user_id,
195
+ agent_id: opts.agent_id,
196
+ limit: opts.limit ?? 10
197
+ }
198
+ });
199
+ return body.results ?? [];
200
+ }
201
+ /** GET /v1/memories — list a scope, newest first. */
202
+ async getAll(opts = {}) {
203
+ return this.request("GET", "/v1/memories", {
204
+ params: {
205
+ user_id: opts.user_id,
206
+ agent_id: opts.agent_id,
207
+ limit: opts.limit ?? 50,
208
+ offset: opts.offset ?? 0
209
+ }
210
+ });
211
+ }
212
+ /** GET /v1/memories/:id — full content, metadata, extracted facts. */
213
+ async get(memoryId) {
214
+ return this.request("GET", `/v1/memories/${memoryId}`);
215
+ }
216
+ /**
217
+ * PATCH /v1/memories/:id — re-runs extraction. Pass `expected_updated_at`
218
+ * for optimistic concurrency (throws StaleWriteError instead of clobbering).
219
+ */
220
+ async update(memoryId, opts) {
221
+ return this.request("PATCH", `/v1/memories/${memoryId}`, {
222
+ body: {
223
+ content: opts.content,
224
+ expected_updated_at: opts.expected_updated_at
225
+ }
226
+ });
227
+ }
228
+ /** DELETE /v1/memories/:id — forget one memory (audited invalidation). */
229
+ async delete(memoryId) {
230
+ return this.request("DELETE", `/v1/memories/${memoryId}`);
231
+ }
232
+ /**
233
+ * DELETE /v1/users/:user_id/memories — forget every memory + fact for one
234
+ * end user in a single call.
235
+ */
236
+ async deleteAll(opts) {
237
+ return this.request(
238
+ "DELETE",
239
+ `/v1/users/${opts.user_id}/memories`
240
+ );
241
+ }
242
+ /**
243
+ * GET /v1/memories/:id/history — the lifecycle timeline of a memory:
244
+ * created / updated / deleted, plus every typed fact it produced.
245
+ */
246
+ async history(memoryId) {
247
+ return this.request("GET", `/v1/memories/${memoryId}/history`);
248
+ }
249
+ /**
250
+ * GET /v1/users — the end users you've stored data for (distinct user_id
251
+ * namespaces), each with active memory + fact counts and last-active time.
252
+ */
253
+ async users(opts = {}) {
254
+ return this.request("GET", "/v1/users", {
255
+ params: {
256
+ agent_id: opts.agent_id,
257
+ limit: opts.limit ?? 50,
258
+ offset: opts.offset ?? 0
259
+ }
260
+ });
261
+ }
262
+ // ── facts ─────────────────────────────────────────────────────────────────
263
+ /**
264
+ * GET /v1/facts — typed (subject, predicate, object) triples with bi-temporal
265
+ * validity. Pass `as_of` (ISO date) for a point-in-time query.
266
+ */
267
+ async getFacts(opts = {}) {
268
+ const params = {
269
+ subject: opts.subject,
270
+ entity: opts.entity,
271
+ predicate: opts.predicate,
272
+ predicate_family: opts.predicate_family,
273
+ as_of: opts.as_of,
274
+ user_id: opts.user_id,
275
+ agent_id: opts.agent_id,
276
+ limit: opts.limit ?? 50,
277
+ offset: opts.offset ?? 0
278
+ };
279
+ if (opts.include_invalidated) params.include_invalidated = "true";
280
+ const body = await this.request("GET", "/v1/facts", { params });
281
+ return body.facts ?? [];
282
+ }
283
+ /**
284
+ * POST /v1/facts — write a typed (subject, predicate, object) triple directly,
285
+ * skipping extraction. The server runs the contradiction check; the fact is
286
+ * bi-temporal (pass `valid_from` for a historical fact). Resolves to the
287
+ * written Fact, with `invalidated` listing any fact ids it superseded.
288
+ */
289
+ async addFactTriple(subject, predicate, object, opts = {}) {
290
+ return this.request("POST", "/v1/facts", {
291
+ body: {
292
+ subject,
293
+ predicate,
294
+ object,
295
+ user_id: opts.user_id,
296
+ agent_id: opts.agent_id,
297
+ run_id: opts.run_id,
298
+ subject_type: opts.subject_type ?? "unknown",
299
+ object_is_literal: opts.object_is_literal ?? false,
300
+ confidence: opts.confidence ?? 0.9,
301
+ valid_from: opts.valid_from
302
+ }
303
+ });
304
+ }
305
+ /**
306
+ * GET /v1/profile — the assembled profile of one end user: the active typed
307
+ * facts known about them, the end user's own facts first, grouped by family.
308
+ * Pass `as_of` (ISO date) for the point-in-time profile.
309
+ */
310
+ async getProfile(opts) {
311
+ return this.request("GET", "/v1/profile", {
312
+ params: {
313
+ user_id: opts.user_id,
314
+ agent_id: opts.agent_id,
315
+ as_of: opts.as_of
316
+ }
317
+ });
318
+ }
319
+ // ── context ─────────────────────────────────────────────────────────────
320
+ /**
321
+ * GET /v1/context — one call that assembles a prompt-ready context block
322
+ * (profile + relevant facts + memories) within a token budget.
323
+ */
324
+ async getContext(opts) {
325
+ return this.request("GET", "/v1/context", {
326
+ params: {
327
+ query: opts.query,
328
+ user_id: opts.user_id,
329
+ agent_id: opts.agent_id,
330
+ token_budget: opts.token_budget ?? 800
331
+ }
332
+ });
333
+ }
334
+ // ── batch ─────────────────────────────────────────────────────────────────
335
+ /** POST /v1/batch — bulk import (up to 500 memory objects), async. */
336
+ async batch(memories) {
337
+ return this.request("POST", "/v1/batch", { body: { memories } });
338
+ }
339
+ /** GET /v1/batch/:id — poll an import job. */
340
+ async batchStatus(jobId) {
341
+ return this.request("GET", `/v1/batch/${jobId}`);
342
+ }
343
+ };
344
+ export {
345
+ APIError,
346
+ AuthenticationError,
347
+ Korely,
348
+ KorelyError,
349
+ NamespaceForbiddenError,
350
+ NotFoundError,
351
+ QuotaExceededError,
352
+ StaleWriteError,
353
+ VERSION
354
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "korely-memory",
3
+ "version": "0.1.0",
4
+ "description": "JavaScript / TypeScript SDK for Korely Agents — memory for AI agents with bi-temporal typed facts.",
5
+ "keywords": [
6
+ "ai",
7
+ "agents",
8
+ "memory",
9
+ "llm",
10
+ "rag",
11
+ "knowledge-graph",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://korely.ai/agents",
15
+ "license": "MIT",
16
+ "author": "Korely <hello@korely.ai>",
17
+ "type": "module",
18
+ "main": "./dist/index.cjs",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "sideEffects": false,
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup",
39
+ "test": "node --test",
40
+ "prepublishOnly": "npm run build"
41
+ },
42
+ "dependencies": {},
43
+ "devDependencies": {
44
+ "@types/node": "^22.9.0",
45
+ "tsup": "^8.3.5",
46
+ "typescript": "^5.6.3"
47
+ }
48
+ }