@columenlabs/lumen-ai 0.1.93 → 0.1.94

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.
@@ -25,6 +25,13 @@ export interface AssistantStore {
25
25
  /** Creates the table. Safe to call often. */
26
26
  init(): Promise<void>;
27
27
  get(scope: string, key: string): Promise<StoredDoc | null>;
28
+ /** Several documents in one call, in the order asked (null where missing). Optional: a store
29
+ * across a network hop (a Durable Object stub) gives it so a request's reads in one tick are one
30
+ * call; `Docs.cached` uses it when present and `get` otherwise. */
31
+ getMany?(refs: {
32
+ scope: string;
33
+ key: string;
34
+ }[]): Promise<(StoredDoc | null)[]>;
28
35
  put(scope: string, key: string, value: string, meta?: string | null): Promise<void>;
29
36
  remove(scope: string, key: string): Promise<void>;
30
37
  /** Keys starting with `prefix`, newest first, without their values. */
@@ -77,6 +84,10 @@ export declare class Docs {
77
84
  private open;
78
85
  private id;
79
86
  private fresh;
87
+ /** Reads asked in the same tick, waiting to go as one `getMany` call. */
88
+ private batch;
89
+ /** One stored doc, joined with the other reads of this tick when the store can batch them. */
90
+ private batched;
80
91
  read<T>(scope: string, key: string): Promise<T | null>;
81
92
  write<T>(scope: string, key: string, value: T, meta?: string): Promise<void>;
82
93
  /** Read (from the store, never the request's copy), change and write back; `change` returning
@@ -55,6 +55,24 @@ export function sqlAssistantStore(sql) {
55
55
  }
56
56
  : null;
57
57
  },
58
+ async getMany(refs) {
59
+ await init();
60
+ if (!refs.length)
61
+ return [];
62
+ const rows = await sql.all(`SELECT scope, key, meta, value, at FROM ${TABLE} WHERE ${refs.map(() => '(scope = ? AND key = ?)').join(' OR ')}`, ...refs.flatMap((r) => [r.scope, r.key]));
63
+ const by = new Map(rows.map((r) => [`${r.scope}\u0000${r.key}`, r]));
64
+ return refs.map(({ scope, key }) => {
65
+ const r = by.get(`${scope}\u0000${key}`);
66
+ return r
67
+ ? {
68
+ key: String(r.key),
69
+ meta: r.meta == null ? null : String(r.meta),
70
+ value: String(r.value),
71
+ at: Number(r.at),
72
+ }
73
+ : null;
74
+ });
75
+ },
58
76
  async put(scope, key, value, meta = null) {
59
77
  await init();
60
78
  await sql.run(`INSERT INTO ${TABLE} (scope, key, meta, value, at) VALUES (?, ?, ?, ?, ?) ON CONFLICT (scope, key) DO UPDATE SET meta = excluded.meta, value = excluded.value, at = excluded.at`, scope, key, meta, value, Date.now());
@@ -108,12 +126,33 @@ export class Docs {
108
126
  const d = await this.store.get(scope, key);
109
127
  return d ? this.open(d.value) : null;
110
128
  }
129
+ /** Reads asked in the same tick, waiting to go as one `getMany` call. */
130
+ batch = null;
131
+ /** One stored doc, joined with the other reads of this tick when the store can batch them. */
132
+ batched(scope, key) {
133
+ const many = this.store.getMany?.bind(this.store);
134
+ if (!many)
135
+ return this.fresh(scope, key);
136
+ const doc = new Promise((resolve, reject) => {
137
+ if (!this.batch) {
138
+ const queue = [];
139
+ this.batch = queue;
140
+ queueMicrotask(() => {
141
+ this.batch = null;
142
+ const all = Promise.resolve().then(() => many(queue.map(({ scope, key }) => ({ scope, key }))));
143
+ queue.forEach((q, i) => q.done(all.then((docs) => docs[i] ?? null)));
144
+ });
145
+ }
146
+ this.batch.push({ scope, key, done: (d) => d.then(resolve, reject) });
147
+ });
148
+ return doc.then((d) => (d ? this.open(d.value) : null));
149
+ }
111
150
  async read(scope, key) {
112
151
  let text;
113
152
  if (this.memo) {
114
153
  const id = this.id(scope, key);
115
154
  const hit = this.memo.get(id);
116
- text = hit ?? this.fresh(scope, key);
155
+ text = hit ?? this.batched(scope, key);
117
156
  if (!hit) {
118
157
  this.memo.set(id, text);
119
158
  text.catch(() => this.memo?.delete(id));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.93",
3
+ "version": "0.1.94",
4
4
  "description": "Lumen's assistant and agent parts: a chat assistant's server (Claude tiers, tools over MCP, the web, hand off, spend caps), serving an MCP server from a Worker, read-only tool sets and metered tool calls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "0.133.0",
44
- "@columenlabs/lumen-ops": "0.1.93",
45
- "@columenlabs/lumen-ui-types": "0.1.93",
44
+ "@columenlabs/lumen-ops": "0.1.94",
45
+ "@columenlabs/lumen-ui-types": "0.1.94",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },