@columenlabs/lumen-ai 0.1.148 → 0.1.150

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 CHANGED
@@ -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<{
@@ -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 = '';
@@ -1711,7 +1711,22 @@ export class Assistant {
1711
1711
  const stream = await client.responses.create({
1712
1712
  model: s.model.id,
1713
1713
  instructions,
1714
- input,
1714
+ // A plan sign-in takes only a list of items, never bare text ("Input must be a list").
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
+ ],
1715
1730
  store: false,
1716
1731
  stream: true,
1717
1732
  ...(plan ? {} : { max_output_tokens: max }),
@@ -1803,14 +1818,31 @@ export class Assistant {
1803
1818
  let text = '';
1804
1819
  try {
1805
1820
  if (s.model.provider === 'openai' && clients.openai) {
1806
- 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);
1807
1822
  }
1808
1823
  else if (clients.anthropic) {
1809
1824
  const msg = await clients.anthropic.messages.create({
1810
1825
  model: s.model.id,
1811
1826
  max_tokens: max,
1812
1827
  system: o.system,
1813
- messages: [{ role: 'user', content: o.prompt }],
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
+ ],
1814
1846
  ...effortParams(s.effort),
1815
1847
  }, o.signal ? { signal: o.signal } : undefined);
1816
1848
  s.meter.add(msg.usage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.148",
3
+ "version": "0.1.150",
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.148",
45
- "@columenlabs/lumen-ui-types": "0.1.148",
44
+ "@columenlabs/lumen-ops": "0.1.150",
45
+ "@columenlabs/lumen-ui-types": "0.1.150",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },