@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 +35 -0
- package/LICENSE +21 -0
- package/README.md +238 -2
- package/dist/chunk-7KPDU3YV.cjs +920 -0
- package/dist/chunk-OVDZ3D4M.js +101 -0
- package/dist/chunk-TLAHP2PB.cjs +101 -0
- package/dist/chunk-UNVZ22EN.js +920 -0
- package/dist/client-YKZ4fkK-.d.cts +641 -0
- package/dist/client-YKZ4fkK-.d.ts +641 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.cts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +36 -0
- package/dist/langgraph.cjs +185 -0
- package/dist/langgraph.d.cts +93 -0
- package/dist/langgraph.d.ts +93 -0
- package/dist/langgraph.js +185 -0
- package/dist/openai-agents.cjs +234 -0
- package/dist/openai-agents.d.cts +101 -0
- package/dist/openai-agents.d.ts +101 -0
- package/dist/openai-agents.js +234 -0
- package/dist/shared-Btg8LbKC.d.cts +23 -0
- package/dist/shared-Btg8LbKC.d.ts +23 -0
- package/package.json +127 -4
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
|
-
#
|
|
1
|
+
# Alexein for JavaScript and TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
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
|