@columenlabs/lumen-ai 0.1.88 → 0.1.89
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/dist/assistant/engine.d.ts +6 -0
- package/dist/assistant/engine.js +10 -0
- package/dist/assistant/routes.d.ts +1 -1
- package/dist/assistant/routes.js +4 -2
- package/dist/assistant/state.d.ts +2 -0
- package/dist/assistant/state.js +4 -0
- package/dist/assistant/store.d.ts +16 -2
- package/dist/assistant/store.js +45 -9
- package/package.json +3 -3
|
@@ -160,6 +160,12 @@ export declare class Assistant {
|
|
|
160
160
|
readonly state: State;
|
|
161
161
|
private readonly groups;
|
|
162
162
|
constructor(o: AssistantOptions);
|
|
163
|
+
/**
|
|
164
|
+
* This assistant for one request: its documents are read once each (`Docs.cached`), so a page of
|
|
165
|
+
* Settings or a chat turn costs a handful of store calls, not dozens. `assistantRoutes` makes one
|
|
166
|
+
* per request itself.
|
|
167
|
+
*/
|
|
168
|
+
forRequest(): Assistant;
|
|
163
169
|
private anthropic;
|
|
164
170
|
private openai;
|
|
165
171
|
private clients;
|
package/dist/assistant/engine.js
CHANGED
|
@@ -172,6 +172,16 @@ export class Assistant {
|
|
|
172
172
|
: {}),
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* This assistant for one request: its documents are read once each (`Docs.cached`), so a page of
|
|
177
|
+
* Settings or a chat turn costs a handful of store calls, not dozens. `assistantRoutes` makes one
|
|
178
|
+
* per request itself.
|
|
179
|
+
*/
|
|
180
|
+
forRequest() {
|
|
181
|
+
const view = Object.create(this);
|
|
182
|
+
Object.defineProperty(view, 'state', { value: this.state.withDocs(this.state.docs.cached()) });
|
|
183
|
+
return view;
|
|
184
|
+
}
|
|
175
185
|
anthropic(apiKey) {
|
|
176
186
|
const f = this.o.fetch;
|
|
177
187
|
return new Anthropic({ apiKey, maxRetries: 1, ...(f ? { fetch: f } : {}) });
|
|
@@ -6,5 +6,5 @@ export interface AssistantRoutesOptions {
|
|
|
6
6
|
/** The verified caller, or null (403). The app decides who is an admin. */
|
|
7
7
|
caller: (req: Request) => Awaitable<Caller | null>;
|
|
8
8
|
}
|
|
9
|
-
export declare function assistantRoutes(
|
|
9
|
+
export declare function assistantRoutes(app: Assistant, o: AssistantRoutesOptions): (req: Request) => Promise<Response>;
|
|
10
10
|
export {};
|
package/dist/assistant/routes.js
CHANGED
|
@@ -121,10 +121,12 @@ function settingsChange(a, groups, b) {
|
|
|
121
121
|
};
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
|
-
export function assistantRoutes(
|
|
124
|
+
export function assistantRoutes(app, o) {
|
|
125
125
|
const base = o.base.replace(/\/+$/, '');
|
|
126
|
-
const docs = a.state.docs;
|
|
127
126
|
return async (req) => {
|
|
127
|
+
// One request's view: each stored document is read once however many routes ask for it.
|
|
128
|
+
const a = app.forRequest();
|
|
129
|
+
const docs = a.state.docs;
|
|
128
130
|
const url = new URL(req.url);
|
|
129
131
|
if (!url.pathname.startsWith(base))
|
|
130
132
|
return fail('Not found.', 404);
|
|
@@ -127,6 +127,8 @@ export declare class State {
|
|
|
127
127
|
fetch?: typeof fetch;
|
|
128
128
|
allowance?: AllowanceConfig;
|
|
129
129
|
});
|
|
130
|
+
/** The same state over other documents (a request's `Docs.cached`). */
|
|
131
|
+
withDocs(docs: Docs): State;
|
|
130
132
|
get allowanceConfig(): AllowanceConfig | undefined;
|
|
131
133
|
allowance(id: string): Promise<AllowanceDoc>;
|
|
132
134
|
setAllowance(id: string, change: (d: AllowanceDoc) => AllowanceDoc): Promise<AllowanceDoc | null>;
|
package/dist/assistant/state.js
CHANGED
|
@@ -32,6 +32,10 @@ export class State {
|
|
|
32
32
|
this.docs = docs;
|
|
33
33
|
this.o = o;
|
|
34
34
|
}
|
|
35
|
+
/** The same state over other documents (a request's `Docs.cached`). */
|
|
36
|
+
withDocs(docs) {
|
|
37
|
+
return new State(docs, this.o);
|
|
38
|
+
}
|
|
35
39
|
get allowanceConfig() {
|
|
36
40
|
return this.o.allowance;
|
|
37
41
|
}
|
|
@@ -61,12 +61,26 @@ export declare function sqlAssistantStore(sql: SqlRunner): AssistantStore;
|
|
|
61
61
|
export declare class Docs {
|
|
62
62
|
private readonly store;
|
|
63
63
|
private readonly sealer?;
|
|
64
|
-
|
|
64
|
+
/** Opened values already read in this request (`cached`), by scope and key. */
|
|
65
|
+
private readonly memo?;
|
|
66
|
+
constructor(store: AssistantStore, sealer?: Sealer | undefined,
|
|
67
|
+
/** Opened values already read in this request (`cached`), by scope and key. */
|
|
68
|
+
memo?: Map<string, Promise<string | null>> | undefined);
|
|
69
|
+
/**
|
|
70
|
+
* The same documents with each one read once: one request (a Settings page, a chat turn) reads
|
|
71
|
+
* a person's settings, keys and spend many times, and each read is a store call and an unseal.
|
|
72
|
+
* Writes keep it current; `update` always reads the store, so a change never builds on a value
|
|
73
|
+
* another request has since replaced. Make one per request, never one per app.
|
|
74
|
+
*/
|
|
75
|
+
cached(): Docs;
|
|
65
76
|
private seal;
|
|
66
77
|
private open;
|
|
78
|
+
private id;
|
|
79
|
+
private fresh;
|
|
67
80
|
read<T>(scope: string, key: string): Promise<T | null>;
|
|
68
81
|
write<T>(scope: string, key: string, value: T, meta?: string): Promise<void>;
|
|
69
|
-
/** Read, change and write back; `change` returning
|
|
82
|
+
/** Read (from the store, never the request's copy), change and write back; `change` returning
|
|
83
|
+
* null removes it. */
|
|
70
84
|
update<T>(scope: string, key: string, change: (prev: T | null) => T | null): Promise<T | null>;
|
|
71
85
|
remove(scope: string, key: string): Promise<void>;
|
|
72
86
|
list(scope: string, prefix: string, limit: number): Promise<{
|
package/dist/assistant/store.js
CHANGED
|
@@ -84,30 +84,66 @@ export function sqlAssistantStore(sql) {
|
|
|
84
84
|
export class Docs {
|
|
85
85
|
store;
|
|
86
86
|
sealer;
|
|
87
|
-
|
|
87
|
+
memo;
|
|
88
|
+
constructor(store, sealer,
|
|
89
|
+
/** Opened values already read in this request (`cached`), by scope and key. */
|
|
90
|
+
memo) {
|
|
88
91
|
this.store = store;
|
|
89
92
|
this.sealer = sealer;
|
|
93
|
+
this.memo = memo;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The same documents with each one read once: one request (a Settings page, a chat turn) reads
|
|
97
|
+
* a person's settings, keys and spend many times, and each read is a store call and an unseal.
|
|
98
|
+
* Writes keep it current; `update` always reads the store, so a change never builds on a value
|
|
99
|
+
* another request has since replaced. Make one per request, never one per app.
|
|
100
|
+
*/
|
|
101
|
+
cached() {
|
|
102
|
+
return new Docs(this.store, this.sealer, new Map());
|
|
90
103
|
}
|
|
91
104
|
seal = (s) => (this.sealer ? this.sealer.seal(s) : Promise.resolve(s));
|
|
92
105
|
open = (s) => (this.sealer ? this.sealer.open(s) : Promise.resolve(s));
|
|
93
|
-
|
|
106
|
+
id = (scope, key) => `${scope}\u0000${key}`;
|
|
107
|
+
async fresh(scope, key) {
|
|
94
108
|
const d = await this.store.get(scope, key);
|
|
95
|
-
return d ?
|
|
109
|
+
return d ? this.open(d.value) : null;
|
|
110
|
+
}
|
|
111
|
+
async read(scope, key) {
|
|
112
|
+
let text;
|
|
113
|
+
if (this.memo) {
|
|
114
|
+
const id = this.id(scope, key);
|
|
115
|
+
const hit = this.memo.get(id);
|
|
116
|
+
text = hit ?? this.fresh(scope, key);
|
|
117
|
+
if (!hit) {
|
|
118
|
+
this.memo.set(id, text);
|
|
119
|
+
text.catch(() => this.memo?.delete(id));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else
|
|
123
|
+
text = this.fresh(scope, key);
|
|
124
|
+
const t = await text;
|
|
125
|
+
// Parsed each time, so a caller that changes what it got never changes the next read.
|
|
126
|
+
return t === null ? null : JSON.parse(t);
|
|
96
127
|
}
|
|
97
128
|
async write(scope, key, value, meta) {
|
|
98
|
-
|
|
129
|
+
const text = JSON.stringify(value);
|
|
130
|
+
await this.store.put(scope, key, await this.seal(text), meta === undefined ? null : await this.seal(meta));
|
|
131
|
+
this.memo?.set(this.id(scope, key), Promise.resolve(text));
|
|
99
132
|
}
|
|
100
|
-
/** Read, change and write back; `change` returning
|
|
133
|
+
/** Read (from the store, never the request's copy), change and write back; `change` returning
|
|
134
|
+
* null removes it. */
|
|
101
135
|
async update(scope, key, change) {
|
|
102
|
-
const
|
|
136
|
+
const t = await this.fresh(scope, key);
|
|
137
|
+
const next = change(t === null ? null : JSON.parse(t));
|
|
103
138
|
if (next === null)
|
|
104
|
-
await this.
|
|
139
|
+
await this.remove(scope, key);
|
|
105
140
|
else
|
|
106
141
|
await this.write(scope, key, next);
|
|
107
142
|
return next;
|
|
108
143
|
}
|
|
109
|
-
remove(scope, key) {
|
|
110
|
-
|
|
144
|
+
async remove(scope, key) {
|
|
145
|
+
await this.store.remove(scope, key);
|
|
146
|
+
this.memo?.set(this.id(scope, key), Promise.resolve(null));
|
|
111
147
|
}
|
|
112
148
|
async list(scope, prefix, limit) {
|
|
113
149
|
const rows = await this.store.list(scope, prefix, limit);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.89",
|
|
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.
|
|
45
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
44
|
+
"@columenlabs/lumen-ops": "0.1.89",
|
|
45
|
+
"@columenlabs/lumen-ui-types": "0.1.89",
|
|
46
46
|
"openai": "7.32.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|