@alexein/sdk 0.0.0-stage → 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 - 2026-11-04
4
+
5
+ Renamed from `twinmemory` to `@alexein/sdk`, rebuilt on the API's `/v2` surface, and numbered as
6
+ the API is (`0.3.0`), as the Python client is.
7
+
8
+ - **Renamed.** The package is `@alexein/sdk`; the base error is `AlexeinError`. The key is read from
9
+ `ALEXEIN_API_KEY` and the host from `ALEXEIN_API_URL`; the User-Agent is `alexein-js/0.3.0`.
10
+ - **`/v2`.** Every request goes to `/v2` and names its app user with `X-Alexein-User`. A base URL
11
+ given with a trailing `/` or `/v2` means the same host.
12
+ - **Refs, not paths.** Memories come back as known lines, facts and excerpts, told apart by
13
+ `type`; facts and excerpts carry a `ref`, which `open`, `around`, `forget` and `correct` take.
14
+ `read`, `ls`, `overview` and `lineage` are gone: `open` and `summary` replace them.
15
+ - **New methods:** `open`, `summary`, `facts`, `openItems`, `sources`, `items`, `item`, `export`,
16
+ `delete`, `memory.users()`, and `iterFacts`, `iterItems`, `iterExport`, `iterUsers` as async
17
+ iterables over every page.
18
+ - **Results** are camelCase and keep the API's JSON as `.raw`. `SearchResult.context()` writes the
19
+ notes, a blank line, then each memory on a line, the same text as the Python client.
20
+ - **Errors:** every 5xx is a `ServiceUnavailableError`; any other 4xx an `InvalidRequestError`;
21
+ each error has `detail` as a string, `errors` (a 422's fields), `requestId` and `outage`. An app
22
+ user id is checked before any request (a `RangeError`).
23
+ - **Integrations**, as their own entry points over optional peer dependencies:
24
+ `@alexein/sdk/langgraph` (LangGraph.js: `AlexeinMemory` with `recall`, `remember`, `prompt` and
25
+ `tools`, `MemoryAnnotation`, `MemoryContext`) and `@alexein/sdk/openai-agents` (the OpenAI Agents
26
+ SDK: `recall` as a `callModelInputFilter`, `remember`, `tools`). The main entry imports no
27
+ framework.
28
+ - Still no runtime dependencies; ESM and CommonJS with types, Node 20 or newer, edge runtimes.
29
+ - Every call sends an `X-Request-Id` of its own (`req_...`), kept across its retries, and every error
30
+ carries it as `requestId` - even a 500 a proxy answered, or no answer at all - so it can be found in
31
+ the console's request logs.
32
+
33
+ ## 0.1.0
34
+
35
+ First release, over the API's earlier routes, under the old package name.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexein AI
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 CHANGED
@@ -1,3 +1,239 @@
1
- # Temporary Holding Version
1
+ # Alexein for JavaScript and TypeScript
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Long-term memory for your app users and agents. Add what they say; search it later in plain
4
+ language.
5
+
6
+ ```bash
7
+ npm install @alexein/sdk
8
+ ```
9
+
10
+ Node 20 or newer, or any runtime with `fetch`: Bun, Deno, edge runtimes. No dependencies. ESM and
11
+ CommonJS, with types. LangGraph.js and the OpenAI Agents SDK are built in, as their own entry
12
+ points.
13
+
14
+ The full guide is at [docs.alexein.ai](https://docs.alexein.ai/sdks/typescript).
15
+
16
+ ## Quickstart
17
+
18
+ ```ts
19
+ import { Memory } from "@alexein/sdk";
20
+
21
+ const memory = new Memory(); // reads ALEXEIN_API_KEY
22
+ const dana = memory.user("dana"); // your own id for one of your app users
23
+
24
+ await dana.add("I moved to Lisbon for the job at Acme.", { role: "user", session: "onboarding" });
25
+ await dana.add(
26
+ { id: "diet", kind: "note", text: "Vegetarian, allergic to peanuts." },
27
+ { wait: true },
28
+ );
29
+
30
+ const result = await dana.search("Where does Dana live?");
31
+ console.log(result.context()); // ready to paste into a prompt
32
+ ```
33
+
34
+ Make a key in the console, under **API keys**. A key belongs to one project; each app user you name
35
+ with `memory.user(...)` has a memory of their own inside it. An app user id is 1 to 35 letters,
36
+ digits, `.`, `_` and `-`, beginning with a letter or digit; `memory.user()` throws a `RangeError`
37
+ for any other, before a request is made.
38
+
39
+ ## Settings
40
+
41
+ | Setting | Option | Environment variable | Default |
42
+ | ----------- | ------------- | -------------------- | ------------------------ |
43
+ | API key | `apiKey` | `ALEXEIN_API_KEY` | required |
44
+ | Base URL | `baseUrl` | `ALEXEIN_API_URL` | `https://api.alexein.ai` |
45
+ | Timeout | `timeout` | | 60000 ms per attempt |
46
+ | Retries | `maxRetries` | | 2 |
47
+ | Fail open | `failOpen` | | `false` |
48
+ | Integration | `integration` | | none |
49
+ | Logger | `logger` | | `console.warn` |
50
+ | HTTP | `fetch` | | the global `fetch` |
51
+
52
+ The base URL is the API's host: `http://localhost:8210`, `http://localhost:8210/` and
53
+ `http://localhost:8210/v2` all mean the same. The environment is read where there is a
54
+ `process.env`; elsewhere, pass the options. `integration` adds one lowercase word to the
55
+ `User-Agent` (`alexein-js/0.3.0 langgraph`), so the console's request logs show where requests came
56
+ from.
57
+
58
+ Run it on your server: a key in a browser bundle can be copied, and it speaks for every user of the
59
+ project.
60
+
61
+ ## Adding
62
+
63
+ `add` takes a string, an item, or an array of either. The options fill in what an item leaves out.
64
+
65
+ ```ts
66
+ await dana.add(
67
+ [
68
+ { text: "Can you book the same hotel as last time?", role: "user" },
69
+ { text: "Booked the Memmo Alfama again for 3-5 May.", role: "assistant" },
70
+ ],
71
+ { session: "trip-may" },
72
+ );
73
+ await dana.add("Moving house on 1 December.", { kind: "note", expires: "2026-12-31" });
74
+ ```
75
+
76
+ The item fields are `text` (required), `kind`, `id`, `session`, `role`, `author`, `title`, `about`,
77
+ `at`, `expires` and `metadata`. The same `id` again is an edit. `wait: true` answers once the items
78
+ are searchable; `redact: true` replaces phone numbers, cards, keys and the like before memory keeps
79
+ them. More than 500 items go as several requests, each with an `Idempotency-Key` its retries send
80
+ again, so a retry never saves an item twice.
81
+
82
+ ## Searching
83
+
84
+ ```ts
85
+ const result = await dana.search("What should I avoid cooking for Dana?");
86
+ result.memories; // in order: known lines, then facts, then excerpts; facts and excerpts with a ref
87
+ result.known; // result.facts, result.excerpts: the memories of one type
88
+ result.answerable; // false when memory does not seem to hold the answer; null when not checked
89
+ result.notes; // guidance for the model reading the answer
90
+ result.context(4000); // notes, then one memory per line, never longer than 4000 characters
91
+ for (const ref of result.refs) await dana.open(ref); // each memory whole
92
+ ```
93
+
94
+ `fast: true` answers sooner, a little less precisely, and is free. `before` and `after` (ISO dates),
95
+ `filters`, and `provider`, `source` and `kind` narrow a search.
96
+
97
+ ## Everything else
98
+
99
+ | Method | Route |
100
+ | -------------------------------------------------------- | --------------------- |
101
+ | `memory.me()` | `GET /v2/me` |
102
+ | `memory.users({ limit, cursor })`, `memory.iterUsers()` | `users` |
103
+ | `user.grep(pattern, { limit, provider, source, kind })` | `grep` |
104
+ | `user.latest({ k, place, after, before, filters, ... })` | `latest` |
105
+ | `user.around(ref, { width })` | `around` |
106
+ | `user.open(ref)` | `open` |
107
+ | `user.history(subject, { attribute })` | `history` |
108
+ | `user.summary()` | `summary` |
109
+ | `user.facts({ limit, cursor })`, `user.iterFacts()` | `facts` |
110
+ | `user.openItems({ kind, provider, place, mine, ... })` | `open_items` |
111
+ | `user.sources()` | `sources` |
112
+ | `user.items({ limit, cursor, filters })`, `iterItems()` | `items` |
113
+ | `user.item(itemId)` | `item` (null if none) |
114
+ | `user.status(ids)` | `status` |
115
+ | `user.forget({ items, sessions, refs })` | `forget` |
116
+ | `user.correct(ref, replacement?)` | `correct` |
117
+ | `user.versions(itemId)` | `versions` |
118
+ | `user.export({ limit, cursor })`, `user.iterExport()` | `export` |
119
+ | `user.delete()` | `users/delete` |
120
+
121
+ Every route but `me` is `POST /v2/memory/<route>`. Results are plain objects with camelCase fields
122
+ (`addedAt`, `followUps`, `lastWrite`); each keeps the JSON the API sent as `.raw`. The `iter*`
123
+ methods are async iterables that follow `next` from page to page:
124
+
125
+ ```ts
126
+ for await (const fact of dana.iterFacts()) console.log(fact.text);
127
+ ```
128
+
129
+ ## Errors
130
+
131
+ Every error is an `AlexeinError` with `status` (null when no answer came), `detail` (the API's
132
+ words), `errors` (on a 422, each field as `{ field, message }`), `requestId` (the response's
133
+ `X-Request-Id`: quote it when you ask for help) and `outage`.
134
+
135
+ | Error | When |
136
+ | ------------------------- | ----------------------------------------------------- |
137
+ | `AuthenticationError` | 401: no key, or one the service did not issue |
138
+ | `PermissionDeniedError` | 403: the key reads, and the call writes |
139
+ | `NotFoundError` | 404: nothing added for this app user, or no such ref |
140
+ | `InvalidRequestError` | 400, 413, 422 and any other 4xx: the request is wrong |
141
+ | `RateLimitError` | 429, after retries |
142
+ | `ServiceUnavailableError` | 5xx, after retries |
143
+ | `APIConnectionError` | the service could not be reached, after retries |
144
+ | `APITimeoutError` | an `APIConnectionError`: no answer in time |
145
+
146
+ 429 and 503, a timeout and a failed connection are tried up to `maxRetries` more times: after the
147
+ `Retry-After` header's seconds when it says 60 or fewer (more, and the error is thrown at once),
148
+ else after 0.5 s, 1 s, ... with jitter. Nothing else is retried. A missing key throws an
149
+ `AlexeinError` from `new Memory()`.
150
+
151
+ ## Failing open
152
+
153
+ Memory being down should cost a reply its memory, not the reply. With
154
+ `new Memory({ failOpen: true })`, an outage - memory unreachable, timing out, or answering 429 or
155
+ 5xx after its retries - does not throw from `search` or `add`:
156
+
157
+ - `search` answers an empty result with `failed: true`;
158
+ - `add` saves nothing and answers `failed: true`;
159
+ - each logs one warning (`console.warn`, or the `logger` you pass).
160
+
161
+ A wrong key or a wrong request (401, 403, 400, 422) still throws, and so does every other method.
162
+ Pair it with a short `timeout` where a reply cannot wait.
163
+
164
+ ## LangGraph.js
165
+
166
+ ```bash
167
+ npm install @alexein/sdk @langchain/langgraph @langchain/core zod
168
+ ```
169
+
170
+ ```ts
171
+ import { AlexeinMemory, MemoryAnnotation, MemoryContext } from "@alexein/sdk/langgraph";
172
+ import { END, START, StateGraph } from "@langchain/langgraph";
173
+
174
+ const memory = new AlexeinMemory(); // or new AlexeinMemory(client, { fast, maxChars, strict })
175
+
176
+ const graph = new StateGraph(MemoryAnnotation, MemoryContext)
177
+ .addNode("recall", memory.recall) // what memory holds for the latest human message
178
+ .addNode("agent", agent) // yours: put memory.prompt(state) in its system prompt
179
+ .addNode("remember", memory.remember) // save the turn
180
+ .addEdge(START, "recall")
181
+ .addEdge("recall", "agent")
182
+ .addEdge("agent", "remember")
183
+ .addEdge("remember", END)
184
+ .compile();
185
+
186
+ await graph.invoke(inputs, { context: { user_id: "dana" }, configurable: { thread_id: "t-1" } });
187
+ ```
188
+
189
+ Whose memory is read is decided by your code, never by a model: the run's `context.user_id`, else
190
+ `configurable.user_id`. `remember` saves the latest human message and the last reply that is not a
191
+ tool call, in the thread's session, each under its LangGraph message id (else a stable hash), so
192
+ running a turn again edits rather than duplicates. Tool calls and tool results are not saved.
193
+
194
+ Tools, for a model that decides when to look: `memory.tools()` is `memory_search` and
195
+ `memory_open`; `memory.tools({ write: true })` adds `memory_add`.
196
+
197
+ ```ts
198
+ import { createAgent } from "langchain";
199
+
200
+ const agent = createAgent({ model, tools: memory.tools(), contextSchema: MemoryContext });
201
+ await agent.invoke(inputs, { context: { user_id: "dana" } });
202
+ ```
203
+
204
+ Without a client, `AlexeinMemory` makes one on first use (so importing needs no key) that fails
205
+ open, with a 10 s timeout. An outage then costs a turn its memory, and a tool answers the model
206
+ "Memory is unavailable right now; carry on without it." With `strict: true`, it throws instead.
207
+ For the five-line case, `recall`, `remember`, `memoryPrompt` and `memoryTools` are exported too,
208
+ over one default `AlexeinMemory`. `MemoryState` is the same state as a `StateSchema`.
209
+
210
+ ## OpenAI Agents SDK
211
+
212
+ ```bash
213
+ npm install @alexein/sdk @openai/agents zod
214
+ ```
215
+
216
+ ```ts
217
+ import { Agent, run } from "@openai/agents";
218
+ import { AlexeinMemory } from "@alexein/sdk/openai-agents";
219
+
220
+ const memory = new AlexeinMemory();
221
+ const agent = new Agent({ name: "Assistant", tools: memory.tools() });
222
+
223
+ const result = await run(agent, message, {
224
+ context: { userId: "dana", sessionId: "conv-7" },
225
+ callModelInputFilter: memory.recall,
226
+ });
227
+ await memory.remember(result);
228
+ ```
229
+
230
+ `recall` searches once a turn with the user's latest message and adds what it finds to the
231
+ instructions of every model call in that turn, so it never lands in your session or history.
232
+ `remember` saves the user's message and the final output, under the agent's name, in the context's
233
+ `sessionId`; saving the same result again edits rather than copies, and a run paused for an
234
+ approval saves nothing. The tools and the fail-open behaviour are as for LangGraph.js; module-level
235
+ `recall`, `remember` and `memoryTools` use one default `AlexeinMemory`.
236
+
237
+ ## License
238
+
239
+ MIT