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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Korely
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # korely-memory
2
+
3
+ The JavaScript / TypeScript SDK for [Korely Agents](https://korely.ai/agents) —
4
+ memory for AI agents, with a typed bi-temporal knowledge graph behind every
5
+ write. A thin, **zero-dependency** client over the Korely REST API (uses the
6
+ native `fetch`). Same package name as the Python twin on
7
+ [PyPI](https://pypi.org/project/korely-memory/).
8
+
9
+ ```bash
10
+ npm install korely-memory
11
+ ```
12
+
13
+ ## Quickstart
14
+
15
+ ```ts
16
+ import { Korely } from "korely-memory";
17
+
18
+ const korely = new Korely({ apiKey: "kor_live_..." }); // or set KORELY_API_KEY
19
+
20
+ // Remember something your agent learned about an end user
21
+ await korely.add("Maria prefers email over Slack, and her renewal is in October.", {
22
+ user_id: "customer-4812",
23
+ });
24
+
25
+ // Later — even in a new session — pull a prompt-ready block back
26
+ const ctx = await korely.getContext({
27
+ query: "how does Maria like to be contacted?",
28
+ user_id: "customer-4812",
29
+ });
30
+ console.log(ctx.context); // drop straight into your system prompt
31
+ ```
32
+
33
+ That's the whole loop: `add` to remember, `getContext` (or `search`) to recall.
34
+ Behind `add`, Korely extracts typed, bi-temporal facts and builds a graph — you
35
+ just hand it text.
36
+
37
+ ## Methods
38
+
39
+ Every method maps to one REST endpoint.
40
+
41
+ | Method | Endpoint | |
42
+ |---|---|---|
43
+ | `add(content, opts?)` | `POST /v1/memories` | Write. `content` is a string or a list of chat messages. |
44
+ | `search(query, opts?)` | `POST /v1/memories/search` | Hybrid search over memories. |
45
+ | `getAll(opts?)` | `GET /v1/memories` | List a scope, newest first. |
46
+ | `get(id)` | `GET /v1/memories/:id` | One memory, with its facts. |
47
+ | `update(id, { content })` | `PATCH /v1/memories/:id` | Re-runs extraction. |
48
+ | `delete(id)` | `DELETE /v1/memories/:id` | Forget one (audited). |
49
+ | `deleteAll({ user_id })` | `DELETE /v1/users/:user_id/memories` | Forget everything for a user (GDPR). |
50
+ | `history(id)` | `GET /v1/memories/:id/history` | A memory's timeline + the facts it produced. |
51
+ | `users(opts?)` | `GET /v1/users` | Your end users, with counts. |
52
+ | `getFacts(opts?)` | `GET /v1/facts` | Typed facts; `as_of` for point-in-time. |
53
+ | `addFactTriple(s, p, o, opts?)` | `POST /v1/facts` | Write a fact directly (bi-temporal). |
54
+ | `getProfile({ user_id, as_of? })` | `GET /v1/profile` | The assembled profile of one end user. |
55
+ | `getContext({ query, user_id? })` | `GET /v1/context` | One call → a prompt-ready context block. |
56
+ | `batch(memories)` | `POST /v1/batch` | Bulk import, for migrations. |
57
+ | `batchStatus(jobId)` | `GET /v1/batch/:id` | Poll an import job. |
58
+
59
+ ## Bi-temporal facts (the moat)
60
+
61
+ Every write extracts typed `(subject, predicate, object)` facts with validity in
62
+ time. Ask what was true on a past date:
63
+
64
+ ```ts
65
+ // What did we know about this user on March 1st?
66
+ const past = await korely.getProfile({ user_id: "customer-4812", as_of: "2026-03-01" });
67
+
68
+ // Write a fact directly, dated in the past
69
+ await korely.addFactTriple("Marco", "works_at", "Acme GmbH", {
70
+ user_id: "customer-4812",
71
+ valid_from: "2026-06-01",
72
+ });
73
+ ```
74
+
75
+ ## Errors
76
+
77
+ Every error subclasses `KorelyError`, so one `catch` covers them all:
78
+
79
+ ```ts
80
+ import { Korely, QuotaExceededError, AuthenticationError } from "korely-memory";
81
+
82
+ try {
83
+ await korely.add("...", { user_id: "u" });
84
+ } catch (e) {
85
+ if (e instanceof QuotaExceededError) {
86
+ console.log(`Rate limited — retry after ${e.retryAfter}s`);
87
+ } else if (e instanceof AuthenticationError) {
88
+ console.log("Bad or missing API key");
89
+ } else {
90
+ throw e;
91
+ }
92
+ }
93
+ ```
94
+
95
+ `AuthenticationError` (401) · `NamespaceForbiddenError` (403) · `NotFoundError`
96
+ (404) · `StaleWriteError` (409) · `QuotaExceededError` (429, carries
97
+ `retryAfter`) · `APIError` (everything else).
98
+
99
+ ## Configuration
100
+
101
+ ```ts
102
+ new Korely({
103
+ apiKey: "kor_live_...", // or the KORELY_API_KEY env var
104
+ region: "eu", // EU only — data stored and processed in the EU
105
+ timeoutMs: 30000,
106
+ });
107
+ ```
108
+
109
+ Requires Node 18+ (native `fetch`), or any runtime with a global `fetch`. On
110
+ older runtimes, pass one via `{ fetch }`.
111
+
112
+ ## Docs
113
+
114
+ - [SDK reference](https://korely.ai/agents/docs/surfaces/sdk)
115
+ - [REST API reference](https://korely.ai/agents/docs/api-reference)
116
+
117
+ MIT © Korely
package/dist/index.cjs ADDED
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ APIError: () => APIError,
24
+ AuthenticationError: () => AuthenticationError,
25
+ Korely: () => Korely,
26
+ KorelyError: () => KorelyError,
27
+ NamespaceForbiddenError: () => NamespaceForbiddenError,
28
+ NotFoundError: () => NotFoundError,
29
+ QuotaExceededError: () => QuotaExceededError,
30
+ StaleWriteError: () => StaleWriteError,
31
+ VERSION: () => VERSION
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/errors.ts
36
+ var KorelyError = class extends Error {
37
+ constructor(message = "", opts = {}) {
38
+ super(message || opts.code || "Korely error");
39
+ this.name = "KorelyError";
40
+ this.status = opts.status;
41
+ this.code = opts.code;
42
+ Object.setPrototypeOf(this, new.target.prototype);
43
+ }
44
+ };
45
+ var AuthenticationError = class _AuthenticationError extends KorelyError {
46
+ constructor(message = "", opts = {}) {
47
+ super(message, opts);
48
+ this.name = "AuthenticationError";
49
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
50
+ }
51
+ };
52
+ var NamespaceForbiddenError = class _NamespaceForbiddenError extends KorelyError {
53
+ constructor(message = "", opts = {}) {
54
+ super(message, opts);
55
+ this.name = "NamespaceForbiddenError";
56
+ Object.setPrototypeOf(this, _NamespaceForbiddenError.prototype);
57
+ }
58
+ };
59
+ var NotFoundError = class _NotFoundError extends KorelyError {
60
+ constructor(message = "", opts = {}) {
61
+ super(message, opts);
62
+ this.name = "NotFoundError";
63
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
64
+ }
65
+ };
66
+ var StaleWriteError = class _StaleWriteError extends KorelyError {
67
+ constructor(message = "", opts = {}) {
68
+ super(message, opts);
69
+ this.name = "StaleWriteError";
70
+ Object.setPrototypeOf(this, _StaleWriteError.prototype);
71
+ }
72
+ };
73
+ var QuotaExceededError = class _QuotaExceededError extends KorelyError {
74
+ constructor(message = "", opts = {}) {
75
+ super(message, opts);
76
+ this.name = "QuotaExceededError";
77
+ this.retryAfter = opts.retryAfter;
78
+ Object.setPrototypeOf(this, _QuotaExceededError.prototype);
79
+ }
80
+ };
81
+ var APIError = class _APIError extends KorelyError {
82
+ constructor(message = "", opts = {}) {
83
+ super(message, opts);
84
+ this.name = "APIError";
85
+ Object.setPrototypeOf(this, _APIError.prototype);
86
+ }
87
+ };
88
+
89
+ // src/client.ts
90
+ var VERSION = "0.1.0";
91
+ var REGIONS = { eu: "https://api.korely.ai" };
92
+ function coerceContent(content) {
93
+ if (typeof content === "string") return content;
94
+ if (Array.isArray(content)) {
95
+ const parts = [];
96
+ for (const m of content) {
97
+ if (m && typeof m === "object") {
98
+ const role = String(m.role ?? "").trim();
99
+ const raw = m.content;
100
+ const body = raw == null ? "" : String(raw).trim();
101
+ if (role && body) parts.push(`${role}: ${body}`);
102
+ else if (body) parts.push(body);
103
+ } else {
104
+ const s = String(m).trim();
105
+ if (s) parts.push(s);
106
+ }
107
+ }
108
+ return parts.join("\n");
109
+ }
110
+ return String(content);
111
+ }
112
+ var Korely = class {
113
+ constructor(opts = {}) {
114
+ const envKey = typeof process !== "undefined" ? process.env?.KORELY_API_KEY : void 0;
115
+ const key = opts.apiKey ?? envKey;
116
+ if (!key) {
117
+ throw new KorelyError(
118
+ "No API key. Pass { apiKey: 'kor_live_...' } or set KORELY_API_KEY."
119
+ );
120
+ }
121
+ this.apiKey = key;
122
+ this.baseUrl = (opts.baseUrl ?? REGIONS[opts.region ?? "eu"] ?? REGIONS.eu).replace(/\/+$/, "");
123
+ this.timeoutMs = opts.timeoutMs ?? 3e4;
124
+ const f = opts.fetch ?? globalThis.fetch;
125
+ if (!f) {
126
+ throw new KorelyError(
127
+ "No fetch available. On Node < 18 pass a fetch implementation via { fetch }."
128
+ );
129
+ }
130
+ this.fetchImpl = f;
131
+ }
132
+ // ── transport ─────────────────────────────────────────────────────────────
133
+ async request(method, path, opts = {}) {
134
+ let url = this.baseUrl + path;
135
+ if (opts.params) {
136
+ const qs = new URLSearchParams();
137
+ for (const [k, v] of Object.entries(opts.params)) {
138
+ if (v !== void 0 && v !== null) qs.append(k, String(v));
139
+ }
140
+ const s = qs.toString();
141
+ if (s) url += "?" + s;
142
+ }
143
+ const headers = {
144
+ Authorization: `Bearer ${this.apiKey}`,
145
+ Accept: "application/json",
146
+ "X-Korely-Client": `korely-js/${VERSION}`
147
+ };
148
+ let body;
149
+ if (opts.body !== void 0) {
150
+ body = JSON.stringify(opts.body);
151
+ headers["Content-Type"] = "application/json";
152
+ }
153
+ const controller = new AbortController();
154
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
155
+ let resp;
156
+ try {
157
+ resp = await this.fetchImpl(url, {
158
+ method,
159
+ headers,
160
+ body,
161
+ signal: controller.signal
162
+ });
163
+ } catch (e) {
164
+ throw new KorelyError(`Connection error: ${e?.message ?? String(e)}`);
165
+ } finally {
166
+ clearTimeout(timer);
167
+ }
168
+ const text = await resp.text();
169
+ let parsed = {};
170
+ if (text) {
171
+ try {
172
+ parsed = JSON.parse(text);
173
+ } catch {
174
+ parsed = { message: text };
175
+ }
176
+ }
177
+ if (!resp.ok) {
178
+ this.raise(resp.status, parsed, resp.headers.get("retry-after"));
179
+ }
180
+ return parsed && typeof parsed === "object" ? parsed : {};
181
+ }
182
+ raise(status, body, retryAfter) {
183
+ const code = body?.code;
184
+ const msg = body?.message || code || `HTTP ${status}`;
185
+ if (status === 401) throw new AuthenticationError(msg, { status, code });
186
+ if (status === 403) throw new NamespaceForbiddenError(msg, { status, code });
187
+ if (status === 404) throw new NotFoundError(msg, { status, code });
188
+ if (status === 409) throw new StaleWriteError(msg, { status, code });
189
+ if (status === 429) {
190
+ const raw = retryAfter ?? body?.retry_after ?? body?._retry_after;
191
+ let ra;
192
+ if (raw != null) {
193
+ const n = parseInt(String(raw), 10);
194
+ ra = Number.isNaN(n) ? void 0 : n;
195
+ }
196
+ throw new QuotaExceededError(msg, { status, code, retryAfter: ra });
197
+ }
198
+ throw new APIError(msg, { status, code });
199
+ }
200
+ // ── memories ────────────────────────────────────────────────────────────
201
+ /**
202
+ * POST /v1/memories — store a memory; resolves to it with extracted facts.
203
+ * `content` is a string, or a list of chat messages (role/content), joined
204
+ * into one block before sending.
205
+ */
206
+ async add(content, opts = {}) {
207
+ const text = coerceContent(content);
208
+ if (!text.trim()) {
209
+ throw new KorelyError(
210
+ "content is empty \u2014 pass a non-blank string or messages with content."
211
+ );
212
+ }
213
+ return this.request("POST", "/v1/memories", {
214
+ body: {
215
+ content: text,
216
+ agent_id: opts.agent_id,
217
+ user_id: opts.user_id,
218
+ run_id: opts.run_id,
219
+ metadata: opts.metadata
220
+ }
221
+ });
222
+ }
223
+ /** POST /v1/memories/search — hybrid retrieval, ranked by score. */
224
+ async search(query, opts = {}) {
225
+ const body = await this.request("POST", "/v1/memories/search", {
226
+ body: {
227
+ query,
228
+ user_id: opts.user_id,
229
+ agent_id: opts.agent_id,
230
+ limit: opts.limit ?? 10
231
+ }
232
+ });
233
+ return body.results ?? [];
234
+ }
235
+ /** GET /v1/memories — list a scope, newest first. */
236
+ async getAll(opts = {}) {
237
+ return this.request("GET", "/v1/memories", {
238
+ params: {
239
+ user_id: opts.user_id,
240
+ agent_id: opts.agent_id,
241
+ limit: opts.limit ?? 50,
242
+ offset: opts.offset ?? 0
243
+ }
244
+ });
245
+ }
246
+ /** GET /v1/memories/:id — full content, metadata, extracted facts. */
247
+ async get(memoryId) {
248
+ return this.request("GET", `/v1/memories/${memoryId}`);
249
+ }
250
+ /**
251
+ * PATCH /v1/memories/:id — re-runs extraction. Pass `expected_updated_at`
252
+ * for optimistic concurrency (throws StaleWriteError instead of clobbering).
253
+ */
254
+ async update(memoryId, opts) {
255
+ return this.request("PATCH", `/v1/memories/${memoryId}`, {
256
+ body: {
257
+ content: opts.content,
258
+ expected_updated_at: opts.expected_updated_at
259
+ }
260
+ });
261
+ }
262
+ /** DELETE /v1/memories/:id — forget one memory (audited invalidation). */
263
+ async delete(memoryId) {
264
+ return this.request("DELETE", `/v1/memories/${memoryId}`);
265
+ }
266
+ /**
267
+ * DELETE /v1/users/:user_id/memories — forget every memory + fact for one
268
+ * end user in a single call.
269
+ */
270
+ async deleteAll(opts) {
271
+ return this.request(
272
+ "DELETE",
273
+ `/v1/users/${opts.user_id}/memories`
274
+ );
275
+ }
276
+ /**
277
+ * GET /v1/memories/:id/history — the lifecycle timeline of a memory:
278
+ * created / updated / deleted, plus every typed fact it produced.
279
+ */
280
+ async history(memoryId) {
281
+ return this.request("GET", `/v1/memories/${memoryId}/history`);
282
+ }
283
+ /**
284
+ * GET /v1/users — the end users you've stored data for (distinct user_id
285
+ * namespaces), each with active memory + fact counts and last-active time.
286
+ */
287
+ async users(opts = {}) {
288
+ return this.request("GET", "/v1/users", {
289
+ params: {
290
+ agent_id: opts.agent_id,
291
+ limit: opts.limit ?? 50,
292
+ offset: opts.offset ?? 0
293
+ }
294
+ });
295
+ }
296
+ // ── facts ─────────────────────────────────────────────────────────────────
297
+ /**
298
+ * GET /v1/facts — typed (subject, predicate, object) triples with bi-temporal
299
+ * validity. Pass `as_of` (ISO date) for a point-in-time query.
300
+ */
301
+ async getFacts(opts = {}) {
302
+ const params = {
303
+ subject: opts.subject,
304
+ entity: opts.entity,
305
+ predicate: opts.predicate,
306
+ predicate_family: opts.predicate_family,
307
+ as_of: opts.as_of,
308
+ user_id: opts.user_id,
309
+ agent_id: opts.agent_id,
310
+ limit: opts.limit ?? 50,
311
+ offset: opts.offset ?? 0
312
+ };
313
+ if (opts.include_invalidated) params.include_invalidated = "true";
314
+ const body = await this.request("GET", "/v1/facts", { params });
315
+ return body.facts ?? [];
316
+ }
317
+ /**
318
+ * POST /v1/facts — write a typed (subject, predicate, object) triple directly,
319
+ * skipping extraction. The server runs the contradiction check; the fact is
320
+ * bi-temporal (pass `valid_from` for a historical fact). Resolves to the
321
+ * written Fact, with `invalidated` listing any fact ids it superseded.
322
+ */
323
+ async addFactTriple(subject, predicate, object, opts = {}) {
324
+ return this.request("POST", "/v1/facts", {
325
+ body: {
326
+ subject,
327
+ predicate,
328
+ object,
329
+ user_id: opts.user_id,
330
+ agent_id: opts.agent_id,
331
+ run_id: opts.run_id,
332
+ subject_type: opts.subject_type ?? "unknown",
333
+ object_is_literal: opts.object_is_literal ?? false,
334
+ confidence: opts.confidence ?? 0.9,
335
+ valid_from: opts.valid_from
336
+ }
337
+ });
338
+ }
339
+ /**
340
+ * GET /v1/profile — the assembled profile of one end user: the active typed
341
+ * facts known about them, the end user's own facts first, grouped by family.
342
+ * Pass `as_of` (ISO date) for the point-in-time profile.
343
+ */
344
+ async getProfile(opts) {
345
+ return this.request("GET", "/v1/profile", {
346
+ params: {
347
+ user_id: opts.user_id,
348
+ agent_id: opts.agent_id,
349
+ as_of: opts.as_of
350
+ }
351
+ });
352
+ }
353
+ // ── context ─────────────────────────────────────────────────────────────
354
+ /**
355
+ * GET /v1/context — one call that assembles a prompt-ready context block
356
+ * (profile + relevant facts + memories) within a token budget.
357
+ */
358
+ async getContext(opts) {
359
+ return this.request("GET", "/v1/context", {
360
+ params: {
361
+ query: opts.query,
362
+ user_id: opts.user_id,
363
+ agent_id: opts.agent_id,
364
+ token_budget: opts.token_budget ?? 800
365
+ }
366
+ });
367
+ }
368
+ // ── batch ─────────────────────────────────────────────────────────────────
369
+ /** POST /v1/batch — bulk import (up to 500 memory objects), async. */
370
+ async batch(memories) {
371
+ return this.request("POST", "/v1/batch", { body: { memories } });
372
+ }
373
+ /** GET /v1/batch/:id — poll an import job. */
374
+ async batchStatus(jobId) {
375
+ return this.request("GET", `/v1/batch/${jobId}`);
376
+ }
377
+ };
378
+ // Annotate the CommonJS export names for ESM import in node:
379
+ 0 && (module.exports = {
380
+ APIError,
381
+ AuthenticationError,
382
+ Korely,
383
+ KorelyError,
384
+ NamespaceForbiddenError,
385
+ NotFoundError,
386
+ QuotaExceededError,
387
+ StaleWriteError,
388
+ VERSION
389
+ });