@columenlabs/lumen-ai 0.1.210 → 0.1.211
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 -0
- package/dist/agent/agent.d.ts +12 -1
- package/dist/agent/agent.js +25 -2
- package/dist/agent/stream.js +4 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -237,6 +237,8 @@ Drafts and writes are cards in the chat's own storage (`lumen_cards` in the agen
|
|
|
237
237
|
|
|
238
238
|
**Lazy tools.** Past `LAZY_AT` (30) tools, a turn sends the model its core tools and `tool_search`, with one catalog line per group in the instructions. By default the core tools are the apps' finds, the hand off and the advisor. `plan.lazy` sets `{ at, core }`. A search matches the words in a tool's name and description and turns on up to 8 tools from the next step on.
|
|
239
239
|
|
|
240
|
+
**Images.** `AskInput.images` (base64 PNG, JPEG, WebP or GIF; at most `ASSISTANT_IMAGES_MAX` (4) and `ASSISTANT_IMAGE_CHARS` together) go into the user message as file parts (`imageParts`), so the model sees them and the chat shows them again. `LumenAssistant images` lets the person paste (Cmd+V) or drop images on the box; the composer scales each to 1568 px on its long edge. An app's `POST /chat` passes `images` on in its `AskInput`.
|
|
241
|
+
|
|
240
242
|
**The panel.** `LumenAssistant` reads a Think chat with no change. `ask(input, sink)` runs one answer and sends the panel's own events (`AssistantEvent`: the chat, the text, each tool line, cards, hand-off sections, advice, the use) to `sink`. The app's `POST /chat` returns `askResponse((sink) => chat.ask(input, sink), { signal, stop: () => chat.stop() })`, the same server-sent events `assistantRoutes` sends. `view()` gives a stored chat as the panel's items, and `usage()` gives the chat's use so far. A card's id is its tool call's, so the panel's Save, Dismiss and Undo go to `saveCard`, `dismissCard` and `undoCard`. The app keeps the list of chats, and each chat is its own agent, such as a facet of the person's store. `chunkEvents` and `itemsOf` are the mapping, for an app that streams some other way.
|
|
241
243
|
|
|
242
244
|
```ts
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* An `Assistant` gives both: `assistant.agentTurn(caller, input)` returns a plan and its settle.
|
|
13
13
|
*/
|
|
14
14
|
import { type ChatResponseResult, type PrepareStepContext, type Session, type StepConfig, type StepContext, Think, type ThinkModel, type TurnConfig, type TurnContext } from '@cloudflare/think';
|
|
15
|
-
import type
|
|
15
|
+
import { type AssistantChat, type AssistantEffort, type AssistantEvent, type AssistantImage, type AssistantRefNote, type AssistantScreenMap, type AssistantScreenOutcome, type ChatUsage, type UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
16
16
|
import { type ModelMessage, type ToolSet } from 'ai';
|
|
17
17
|
import type { ModelDef } from '../assistant/models.js';
|
|
18
18
|
import { type AgentConnector, type ConnectorAt } from './connectors.js';
|
|
@@ -116,6 +116,8 @@ export declare function withLead(messages: ModelMessage[], lead: string | undefi
|
|
|
116
116
|
/** One message the person sent, with the page, refs and tier the composer added. */
|
|
117
117
|
export interface AskInput {
|
|
118
118
|
message: string;
|
|
119
|
+
/** Images to look at with the message; kept to the allowed types, count and size. */
|
|
120
|
+
images?: AssistantImage[];
|
|
119
121
|
context?: {
|
|
120
122
|
label: string;
|
|
121
123
|
note?: unknown;
|
|
@@ -128,6 +130,15 @@ export interface AskInput {
|
|
|
128
130
|
/** How earlier screen calls ended in the browser. */
|
|
129
131
|
outcomes?: AssistantScreenOutcome[];
|
|
130
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* The images a message may carry, as file parts: allowed types only, at most `ASSISTANT_IMAGES_MAX`,
|
|
135
|
+
* and no more than `ASSISTANT_IMAGE_CHARS` of base64 together; the rest are left out.
|
|
136
|
+
*/
|
|
137
|
+
export declare function imageParts(images: unknown): {
|
|
138
|
+
type: 'file';
|
|
139
|
+
mediaType: string;
|
|
140
|
+
url: string;
|
|
141
|
+
}[];
|
|
131
142
|
/** Where an answer's events go: the app's route, over RPC. */
|
|
132
143
|
export interface AskSink {
|
|
133
144
|
event(e: AssistantEvent): void | Promise<void>;
|
package/dist/agent/agent.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
15
15
|
import { Think, } from '@cloudflare/think';
|
|
16
|
+
import { ASSISTANT_IMAGES_MAX, ASSISTANT_IMAGE_CHARS, ASSISTANT_IMAGE_TYPES, } from '@columenlabs/lumen-ui-types/assistant';
|
|
16
17
|
import { createCompactFunction } from 'agents/sessions';
|
|
17
18
|
import { generateText } from 'ai';
|
|
18
19
|
import { CONNECTOR_GROUP, checkConnectorUrl, connectorGroup, connectorId, connectorSource, } from './connectors.js';
|
|
@@ -66,6 +67,26 @@ export function withLead(messages, lead) {
|
|
|
66
67
|
: [{ type: 'text', text: lead }, ...m.content];
|
|
67
68
|
return [...messages.slice(0, i), { ...m, content }, ...messages.slice(i + 1)];
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The images a message may carry, as file parts: allowed types only, at most `ASSISTANT_IMAGES_MAX`,
|
|
72
|
+
* and no more than `ASSISTANT_IMAGE_CHARS` of base64 together; the rest are left out.
|
|
73
|
+
*/
|
|
74
|
+
export function imageParts(images) {
|
|
75
|
+
if (!Array.isArray(images))
|
|
76
|
+
return [];
|
|
77
|
+
const out = [];
|
|
78
|
+
let chars = 0;
|
|
79
|
+
for (const i of images.slice(0, ASSISTANT_IMAGES_MAX)) {
|
|
80
|
+
const type = ASSISTANT_IMAGE_TYPES.find((t) => t === i?.media_type);
|
|
81
|
+
if (!type || typeof i?.data !== 'string' || !/^[A-Za-z0-9+/]+=*$/.test(i.data))
|
|
82
|
+
continue;
|
|
83
|
+
if (chars + i.data.length > ASSISTANT_IMAGE_CHARS)
|
|
84
|
+
break;
|
|
85
|
+
chars += i.data.length;
|
|
86
|
+
out.push({ type: 'file', mediaType: type, url: `data:${type};base64,${i.data}` });
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
69
90
|
/** Usage parts added up: same label, one part. */
|
|
70
91
|
export function chatUsage(parts, context) {
|
|
71
92
|
const by = new Map();
|
|
@@ -174,7 +195,9 @@ export class LumenAgent extends Think {
|
|
|
174
195
|
kindOf: (n) => this.#turn?.tools.info.find((i) => i.name === n)?.kind,
|
|
175
196
|
});
|
|
176
197
|
const map = screenMapOf(input.screen);
|
|
177
|
-
|
|
198
|
+
// The images go in the message's parts; the turn's plan reads the rest.
|
|
199
|
+
const { images: _images, ...asked } = input;
|
|
200
|
+
this.#input = { ...asked, ...(map ? { screen: map } : {}) };
|
|
178
201
|
if (!map)
|
|
179
202
|
delete this.#input.screen;
|
|
180
203
|
let failed = false;
|
|
@@ -182,7 +205,7 @@ export class LumenAgent extends Think {
|
|
|
182
205
|
await this.chat({
|
|
183
206
|
id: `u_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`,
|
|
184
207
|
role: 'user',
|
|
185
|
-
parts: [{ type: 'text', text: input.message }],
|
|
208
|
+
parts: [{ type: 'text', text: input.message }, ...imageParts(input.images)],
|
|
186
209
|
metadata: meta,
|
|
187
210
|
}, {
|
|
188
211
|
onStart: () => undefined,
|
package/dist/agent/stream.js
CHANGED
|
@@ -138,10 +138,14 @@ export function itemsOf(messages, cards, screens = new Map()) {
|
|
|
138
138
|
.map((p) => String(p.text ?? ''))
|
|
139
139
|
.join('');
|
|
140
140
|
const meta = (obj(m.metadata) ?? {});
|
|
141
|
+
const images = m.parts
|
|
142
|
+
.filter((p) => p.type === 'file' && typeof p.url === 'string' && p.url.startsWith('data:image/'))
|
|
143
|
+
.map((p) => p.url);
|
|
141
144
|
items.push({
|
|
142
145
|
kind: 'user',
|
|
143
146
|
text,
|
|
144
147
|
at: meta.at ?? 0,
|
|
148
|
+
...(images.length ? { images } : {}),
|
|
145
149
|
...(meta.context?.label ? { context: meta.context.label } : {}),
|
|
146
150
|
...(meta.refs?.length ? { refs: meta.refs } : {}),
|
|
147
151
|
...(meta.tier ? { tier: meta.tier } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.211",
|
|
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",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@ai-sdk/openai": "4.0.108",
|
|
53
53
|
"@anthropic-ai/sdk": "0.133.0",
|
|
54
54
|
"@cloudflare/think": "0.23.0",
|
|
55
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
56
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
55
|
+
"@columenlabs/lumen-ops": "0.1.211",
|
|
56
|
+
"@columenlabs/lumen-ui-types": "0.1.211",
|
|
57
57
|
"agents": "0.32.0",
|
|
58
58
|
"ai": "7.0.162",
|
|
59
59
|
"just-bash": "3.6.0",
|