@columenlabs/lumen-ai 0.1.149 → 0.1.151
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/README.md +2 -2
- package/dist/assistant/engine.d.ts +7 -0
- package/dist/assistant/engine.js +35 -4
- package/dist/assistant/store.d.ts +6 -1
- package/dist/assistant/store.js +5 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ const assistant = createAssistant({
|
|
|
81
81
|
{ id: 'notes', label: 'Notes', about: 'Read and edit notes', tools: (caller) => mcpServerTools(() => notesServer(caller.id)) },
|
|
82
82
|
],
|
|
83
83
|
web: { fetch: { blockHosts: ['app.example.com'] }, scrapeql: { url: 'https://scrapeql.example.com/mcp' } },
|
|
84
|
-
store: sqlAssistantStore(doSql(ctx.storage.sql)), // or d1Sql(env.DB)
|
|
84
|
+
store: sqlAssistantStore(doSql(ctx.storage.sql)), // or d1Sql(env.DB); { table } names another table
|
|
85
85
|
sealer, // the app's own sealing: keys and chats are stored sealed
|
|
86
86
|
// A provider's key when none is set in Settings.
|
|
87
87
|
appKey: async (provider) => (provider === 'openai' ? env.OPENAI_API_KEY : env.ANTHROPIC_API_KEY),
|
|
@@ -102,7 +102,7 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
|
|
|
102
102
|
- **Web.** `fetch` is a plain GET from the Worker (10 s, 2 MB, never the app's own hosts or private addresses). ScrapeQL's own tools join over its MCP API once an admin pastes a Cloudflare Access service token (`PUT /web/scrapeql`). A Worker on ScrapeQL's own Cloudflare account passes its service binding instead, `scrapeql: { url: 'https://scrapeql/mcp', fetch: env.SCRAPEQL.fetch.bind(env.SCRAPEQL) }`: no token is stored, the tools are always on and Settings shows the link with a Test.
|
|
103
103
|
- **Hand off and advice.** The `escalate` tool hands one task to a higher tier (the person sets next, any or only when asked); Anthropic's advisor tool lets a higher tier send a plan, at most two an answer.
|
|
104
104
|
- **Chats** stream as server-sent events (`POST /chat`); closing the request stops the answer and keeps what was said. Past 150k tokens, or on `/compact`, the history sent to the model becomes a summary. Run `assistant.purge()` once a day to drop chats 30 days after their last message.
|
|
105
|
-
- **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools, billed to their payer and counted in their spend and allowance like a chat turn. It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later.
|
|
105
|
+
- **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, images?, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools (`images`, base64 with their media type, are read with the prompt: a scanned page, a screenshot), billed to their payer and counted in their spend and allowance like a chat turn. It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later.
|
|
106
106
|
|
|
107
107
|
### Commands, `@` and `#`
|
|
108
108
|
|
|
@@ -17,6 +17,11 @@ export interface AssistantCommandDef {
|
|
|
17
17
|
prompt: string | ((args: string, caller: Caller) => string | Promise<string>);
|
|
18
18
|
}
|
|
19
19
|
/** Who is asking: their id (the store's scope for their things) and whether they are an admin. */
|
|
20
|
+
/** A photo `complete` reads with its prompt: base64 bytes and their media type (`image/jpeg`). */
|
|
21
|
+
export interface CompleteImage {
|
|
22
|
+
mediaType: string;
|
|
23
|
+
data: string;
|
|
24
|
+
}
|
|
20
25
|
export interface Caller {
|
|
21
26
|
id: string;
|
|
22
27
|
admin: boolean;
|
|
@@ -257,6 +262,8 @@ export declare class Assistant {
|
|
|
257
262
|
task: string;
|
|
258
263
|
system: string;
|
|
259
264
|
prompt: string;
|
|
265
|
+
/** Photos read with the prompt (a scanned page, a screenshot). */
|
|
266
|
+
images?: CompleteImage[];
|
|
260
267
|
maxTokens?: number;
|
|
261
268
|
signal?: AbortSignal;
|
|
262
269
|
}): Promise<{
|
package/dist/assistant/engine.js
CHANGED
|
@@ -1702,7 +1702,7 @@ export class Assistant {
|
|
|
1702
1702
|
* events like the chat loop, because a ChatGPT plan sign-in answers only streamed calls, takes no
|
|
1703
1703
|
* output cap and may send its words only as deltas.
|
|
1704
1704
|
*/
|
|
1705
|
-
async openaiOnce(clients, s, instructions, input, max, signal) {
|
|
1705
|
+
async openaiOnce(clients, s, instructions, input, max, signal, images) {
|
|
1706
1706
|
const client = clients.openai;
|
|
1707
1707
|
const plan = !!clients.openaiPlan;
|
|
1708
1708
|
let text = '';
|
|
@@ -1712,7 +1712,21 @@ export class Assistant {
|
|
|
1712
1712
|
model: s.model.id,
|
|
1713
1713
|
instructions,
|
|
1714
1714
|
// A plan sign-in takes only a list of items, never bare text ("Input must be a list").
|
|
1715
|
-
input: [
|
|
1715
|
+
input: [
|
|
1716
|
+
{
|
|
1717
|
+
role: 'user',
|
|
1718
|
+
content: images?.length
|
|
1719
|
+
? [
|
|
1720
|
+
{ type: 'input_text', text: input },
|
|
1721
|
+
...images.map((i) => ({
|
|
1722
|
+
type: 'input_image',
|
|
1723
|
+
image_url: `data:${i.mediaType};base64,${i.data}`,
|
|
1724
|
+
detail: 'auto',
|
|
1725
|
+
})),
|
|
1726
|
+
]
|
|
1727
|
+
: input,
|
|
1728
|
+
},
|
|
1729
|
+
],
|
|
1716
1730
|
store: false,
|
|
1717
1731
|
stream: true,
|
|
1718
1732
|
...(plan ? {} : { max_output_tokens: max }),
|
|
@@ -1804,14 +1818,31 @@ export class Assistant {
|
|
|
1804
1818
|
let text = '';
|
|
1805
1819
|
try {
|
|
1806
1820
|
if (s.model.provider === 'openai' && clients.openai) {
|
|
1807
|
-
text = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal);
|
|
1821
|
+
text = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal, o.images);
|
|
1808
1822
|
}
|
|
1809
1823
|
else if (clients.anthropic) {
|
|
1810
1824
|
const msg = await clients.anthropic.messages.create({
|
|
1811
1825
|
model: s.model.id,
|
|
1812
1826
|
max_tokens: max,
|
|
1813
1827
|
system: o.system,
|
|
1814
|
-
messages: [
|
|
1828
|
+
messages: [
|
|
1829
|
+
{
|
|
1830
|
+
role: 'user',
|
|
1831
|
+
content: o.images?.length
|
|
1832
|
+
? [
|
|
1833
|
+
...o.images.map((i) => ({
|
|
1834
|
+
type: 'image',
|
|
1835
|
+
source: {
|
|
1836
|
+
type: 'base64',
|
|
1837
|
+
media_type: i.mediaType,
|
|
1838
|
+
data: i.data,
|
|
1839
|
+
},
|
|
1840
|
+
})),
|
|
1841
|
+
{ type: 'text', text: o.prompt },
|
|
1842
|
+
]
|
|
1843
|
+
: o.prompt,
|
|
1844
|
+
},
|
|
1845
|
+
],
|
|
1815
1846
|
...effortParams(s.effort),
|
|
1816
1847
|
}, o.signal ? { signal: o.signal } : undefined);
|
|
1817
1848
|
s.meter.add(msg.usage);
|
|
@@ -63,7 +63,12 @@ export declare function d1Sql(db: {
|
|
|
63
63
|
};
|
|
64
64
|
};
|
|
65
65
|
}): SqlRunner;
|
|
66
|
-
export
|
|
66
|
+
export interface SqlAssistantStoreOptions {
|
|
67
|
+
/** The table to keep documents in (default `assistant_docs`): a store that must prefix its tables,
|
|
68
|
+
* such as a module of a person's store (`sage_docs`), names its own. Letters, digits and `_`. */
|
|
69
|
+
table?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function sqlAssistantStore(sql: SqlRunner, opts?: SqlAssistantStoreOptions): AssistantStore;
|
|
67
72
|
/** JSON documents over a store, sealed when the app gives a sealer. */
|
|
68
73
|
export declare class Docs {
|
|
69
74
|
private readonly store;
|
package/dist/assistant/store.js
CHANGED
|
@@ -34,8 +34,11 @@ export function d1Sql(db) {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
/** The table name, so an app's own tables never clash with it. */
|
|
37
|
-
const
|
|
38
|
-
export function sqlAssistantStore(sql) {
|
|
37
|
+
const DEFAULT_TABLE = 'assistant_docs';
|
|
38
|
+
export function sqlAssistantStore(sql, opts = {}) {
|
|
39
|
+
const TABLE = opts.table ?? DEFAULT_TABLE;
|
|
40
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(TABLE))
|
|
41
|
+
throw new Error(`Not a table name: ${TABLE}`);
|
|
39
42
|
let ready = null;
|
|
40
43
|
const init = () => {
|
|
41
44
|
ready ??= sql.run(`CREATE TABLE IF NOT EXISTS ${TABLE} (scope TEXT NOT NULL, key TEXT NOT NULL, meta TEXT, value TEXT NOT NULL, at INTEGER NOT NULL, PRIMARY KEY (scope, key))`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.151",
|
|
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.151",
|
|
45
|
+
"@columenlabs/lumen-ui-types": "0.1.151",
|
|
46
46
|
"openai": "7.32.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|