TypeScript SDK

TypeScript - 全部方法,三大类。

写入、读取、删除。以下每个示例都于 2026-08-01 针对线上 API 实际运行;响应为原样展示。

npm install wontopos
import { Client } from "wontopos";

const mem = new Client({ apiKey: "wos-live-..." });

选择模型

API 密钥决定用哪份记忆(您的账户);模型决定用哪个引擎来读取。所有模型共享同一份记忆,因此可以用一个模型存储、用另一个召回。在构造函数中设置默认值;用 withModel() 覆盖单个调用。

const mem = new Client({ apiKey: "wos-live-...", model: "tablet-1" });  // default
mem.recall("...", "alice");                          // tablet-1
mem.withModel("scroll-1").recall("...", "alice");  // or pick a model per call

listModels

模型目录 - 可传给 model 的 id 以及各自是否已上线。memory: "shared" 的模型读取同一存储;"isolated" 则各自独立。无需 API 密钥。

await mem.listModels();
真实响应
[{"id": "tablet-1", "name": "Tablet 1", "available": true, "memory": "shared"},
 {"id": "tablet-2", "name": "Tablet 2", "available": true, "memory": "shared"},
 {"id": "scroll-1", "name": "Scroll 1", "available": true, "memory": "shared"},
 {"id": "scroll-1.2", "name": "Scroll 1.2", "available": true, "memory": "shared"}]

ping

用一行确认连接以及 API 密钥是否有效。

await mem.ping();   // true, or throws AuthenticationError / PaymentRequiredError

上面的目录始终反映当前可用的模型 - 传入其他任何 id 都会得到明确的错误。新模型发布后会自动出现在其中。

写入

add

存储一条记忆。写入时不调用 LLM,您只需支付写入费用。

await mem.add("she prefers tea over coffee", "alice");
await mem.add("I promised the summary by Friday", "alice", { speaker: "me" });  // its own words - no registration needed
真实响应
{"id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "stored (1 chunks)"}

addTurn

把一轮对话(用户 + 助手)同时写入短期与长期记忆。

await mem.addTurn("hi", "hello!", "alice");
真实响应
{"status": "ok"}

speaker

每条记忆都可以记录说话者。先注册一个人,之后把名字作为 speaker 传入;"me"(助手自己的话)无需注册。搜索也接受 speaker,可以只取某个人说过的话。

await mem.addSpeaker("Bob", "alice");  // once per person; "me" needs no registration
await mem.add("I promised to send the report on Friday", "alice", { speaker: "me" });
await mem.add("Bob said the deadline moved to Tuesday", "alice", { speaker: "Bob" });
await mem.search("what did Bob say about deadlines?", "alice", 10, { speaker: "Bob" });
说话者和存储库一样是显式的。先注册,再以其名字保存。拼写错误绝不会悄悄变成一个新人。每个存储库起步可注册 50 人(会逐步提高),"me" 永远无需注册也不计数。

addBulk

一次性回填一大段文本。在服务端切分并建立索引 - 适合导入既有历史。

await mem.addBulk("Alice moved to Brooklyn in March. She works at a design studio downtown.", "alice");
真实响应
{"elapsed_secs": 0.154154944, "status": "ok", "stored": 1, "total_chunks": 1}

update

某个事实变了。旧记忆被标记为已取代(保留供追溯);新记忆在召回中取而代之。

await mem.update("576700aa-...", "she switched to coffee this year", "alice");
真实响应
{"new_memory_id": "07e94433-b7cc-4e49-8d8f-f37fc1a392b7",
 "old_memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "superseded"}

读取

search

语义搜索,最相关的排在最前。无论用哪种语言提问,都能找到用任何语言写下的记忆。 SDK 直接返回 memories 数组;下方展示的是原始 HTTP 响应体。部分模型会返回不止一组结果,SDK 会将它们合并返回,因此数组可能多于 max_results。请以实际收到的数组、而非请求的数量来规划提示词长度。

const r = await mem.search("what does she drink?", "alice", 1);
真实响应(HTTP 响应体)
[{
   "id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624",
   "content": "she prefers tea over coffee",
   "category": "general",
   "time_bucket": "2026-06",
   "importance": 0.3,
   "similarity": 0.6316057443618774,
   "is_superseded": false,
   "superseded_by": null,
   "created_at": "2026-07-10T04:20:39.688276876Z"
 }]
字段含义
similarity这条记忆与您的查询有多接近 (0–1)。
is_superseded若该事实已被 update() 取代则为 true。
search_ms服务端检索耗时。

recall

一次往返返回您的 LLM 所需的一切 - 结果可直接粘贴进提示词:无论已存储多少内容,上下文大小固定且有界。

const ctx = await mem.recall("what does she drink?", "alice");
真实响应(结构 - 列表已截短)
{"short_term":  {"count": 2, "turns": [{"role": "user", "content": "hi", ...}]},
 "long_term":   {"count": 4, "memories": [{"content": "she prefers tea over coffee",
                                           "similarity": 0.63, ...}]},
 "context":     {"count": 4, "around_top_memory": [
                  "[match] she prefers tea over coffee",
                  "[after] Alice moved to Brooklyn in March. ..."]},
 "instruction": "Use short_term for recent context, long_term for relevant
                 past memories, context for surrounding conversation of the
                 most relevant memory."}

history

最近的对话轮次(短期记忆),最早的在前。

const turns = await mem.history("alice");
真实响应(HTTP 响应体)
{"count": 2, "turns": [
   {"role": "user",      "content": "hi",     "timestamp": "2026-07-10T04:20:40.989011337Z"},
   {"role": "assistant", "content": "hello!", "timestamp": "2026-07-10T04:20:40.989013416Z"}
 ], "user_id": "alice"}

stats

单个用户的记忆条数统计。

await mem.stats("alice");
真实响应
{"short_term_turns": 2, "total_memories": 4, "user_id": "alice"}

get

按 id 获取单条记忆 - 即 add 或 list_memories 返回的 id。只返回存储的原文和元数据。其他存储空间的 id,或已删除/失效的记忆,返回 404。

const m = await mem.get("alice", "576700aa-...");
response
{"id": "576700aa-...", "content": "she prefers tea over coffee",
 "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}

listMemories

列出存储中的记忆——只返回你保存的原文与元数据。按游标翻页:把返回的 next_cursor 传回以获取下一页。

const page = await mem.listMemories("alice", { limit: 100 });
response
{"count": 2, "next_cursor": null, "memories": [
   {"id": "576700aa-...", "content": "she prefers tea over coffee",
    "category": "general", "created_at": "2026-07-10T04:20:39Z", "event_date": null,
 "is_superseded": false, "superseded_by": null}
 ]}

iterMemories · exportMemories

无需管理游标即可遍历全部记忆,或一次性取回整个存储。

for await (const m of mem.iterMemories("alice")) console.log(m.id, m.content);
const everything = await mem.exportMemories("alice");

删除

delete

按 id 删除单条记忆。

await mem.delete("alice", "576700aa-...");
真实响应
{"memory_id": "576700aa-f0e0-4c26-99a0-10e2d5b0d624", "status": "deleted"}

deleteAll

抹除单个用户的全部数据 - 一次调用,符合 GDPR。

await mem.deleteAll("alice");
真实响应
{"memories_deleted": 4, "status": "deleted", "user_id": "alice"}

错误与可靠性

每个失败都是带类型的错误——可按具体情况(限流、认证、付费)分别捕获,或用基类 WosError 统一捕获。

import { NotFoundError, PaymentRequiredError } from "wontopos";
try {
  await mem.add("...", "alice");
} catch (e) {
  if (e instanceof NotFoundError) await mem.createStore("alice");
  else if (e instanceof PaymentRequiredError) topUp();   // out of credit
  else throw e;
}

rateLimit

在任意调用后读取剩余额度,在触达上限前主动放慢。

await mem.search("...", "alice");
const rl = mem.rateLimit;   // { limit: 150, remaining: 3, reset: ... }

searchSelf

在自我记忆模型(Scroll 1.2+)上一次调用返回两条通道:别人说的话与智能体自己说的话分开返回,读取方不会弄混说话人。

const { memories, self_memories } = await mem.searchSelf("what did I promise?", "alice");
// memories = what others said · self_memories = the agent's OWN words

listEngrams

向服务询问当前模型可运行的 engram 与投递形式,而不是硬编码名称 —— 一旦有新 engram 上线,硬编码的代码就再也看不到它。

const { engrams, forms } = await mem.listEngrams();  // ask, never hard-code

filters

把搜索收窄到存储的一部分。在排序之前应用,因此得到的是过滤范围内最相关的结果 - 而不是对 top-N 再做过滤。

await mem.search("what did we decide", "alice", 10, {
  filters: { categories: ["work"], event_from: "2026-01-01" },  // when it HAPPENED
});
键:categories · event_from / event_to(内容发生的时间 - metadata.event_date)· time_from / time_to(写入的时间)· min_importance。未列出的键会被丢弃而不是报错,所以拼错会悄悄扩大搜索范围。

idempotencyKey

让同一次写入可以安全重复。当重试来自你这边时使用 - 中断后重跑的任务、会重投的队列。

await mem.add("she prefers tea", "alice", {}, { idempotencyKey: `import:${row.id}` });
密钥要从被存储的对象派生(import:row-42),不要用常量:两次不同的写入复用同一密钥会重放第一次的响应,第二次会被悄悄丢弃。格式:1-128 个 [A-Za-z0-9._:-] 字符。

withTimeout / withRetries / withDeadline

不改动已经建好的客户端,只调整单个调用点:大批量回填用超时更长的克隆,自己写重试循环时用关闭重试的克隆。

timeout 限定的是单次尝试,因此会重试的调用可能比它活得更久 — 默认情况下一次调用可以占用连接 30 秒、退避、再试、再试。deadline 限定的是整次调用:每次尝试都被压缩到剩余的时间内,退避也绝不会睡过预算。当调用方有真实上限时设置它 — 比如只有五秒的请求处理器。

await mem.withTimeout(120_000).addBulk(bigBlob, "alice");  // this slow call only
await mem.withRetries(0).add("...", "alice");            // you retry, not the SDK
await mem.withDeadline(5_000).recall("...", "alice");          // 5s for the whole call
await mem.withSignal(ctrl.signal).recall("...", "alice");      // caller can cancel