@columenlabs/lumen-ai 0.1.253 → 0.1.254
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 +1 -1
- package/dist/agent/agent.d.ts +11 -0
- package/dist/agent/agent.js +33 -0
- package/dist/agent/stream.d.ts +21 -0
- package/dist/agent/stream.js +61 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -254,7 +254,7 @@ Drafts and writes are cards in the chat's own storage (`lumen_cards` in the agen
|
|
|
254
254
|
|
|
255
255
|
**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.
|
|
256
256
|
|
|
257
|
-
**Tool lines.** Each tool call shows as a line with the tool's own `title` (an MCP tool's `title`, a plain tool's `title`), kept in the chat's storage so a chat read back words its lines the same; a tool with no title shows its name. An app that keeps its insides off the screen overrides `plainSteps()` to return `true`: every line reads its tool's title, else a word for what it did (Look something up, Make a change, Draft a change), with no tool id, row count or error text (a failed line says `didn't work`), and a hand-off section reads Take a closer look with no tier or task. The details go to the app's log instead: `stepFailed({ tool, error, ms })` hears each failed call with its whole error (override it to log it with the chat's ids), and `onStep` carries `error` and `caller` on a failed tool or hand-off. `stepTitleOf(name, kind)` gives a line's title for an app's own card answers.
|
|
257
|
+
**Tool lines.** Each tool call shows as a line with the tool's own `title` (an MCP tool's `title`, a plain tool's `title`), kept in the chat's storage so a chat read back words its lines the same; a tool with no title shows its name. An app that keeps its insides off the screen overrides `plainSteps()` to return `true`: every line reads its tool's title, else a word for what it did (Look something up, Make a change, Draft a change), with no tool id, row count or error text (a failed line says `didn't work`), and a hand-off section reads Take a closer look with no tier or task. The details go to the app's log instead: `stepFailed({ tool, error, ms })` hears each failed call with its whole error (override it to log it with the chat's ids), and `onStep` carries `error` and `caller` on a failed tool or hand-off. `stepTitleOf(name, kind)` gives a line's title for an app's own card answers. **Step lines say what a call did** (`stepLine(c)`): override it to word each call as a verb and what it acted on, from the call's own arguments and answer (`c.input`, `c.output`): present tense while it runs (`state: 'running'`, sent again once the arguments are in), past tense with a short result when it answered (`'done'`), and "Couldn't …" when it failed (`'failed'`, which then shows no `didn't work`). Return `undefined` to keep the tool's title. The kit words its own web lookup (Looking up / Looked up the question). A finished line is kept with the chat, so a chat read back reads the same. In a plain chat the tool search that turns more tools on shows no line.
|
|
258
258
|
|
|
259
259
|
**A write in progress.** A tool line starts (`step_start`) as soon as the model starts the call, while it is still writing the arguments. For a Read + write tool it carries `write: true`, and `useAssistantChat` marks that running step `writing` until its result lands or the answer ends, so an app can show what the tool changes as being changed now (a pending edit is not ready to accept while the model rewrites it).
|
|
260
260
|
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ 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';
|
|
19
19
|
import { type ModelKey } from './models.js';
|
|
20
|
+
import { type StepCall } from './stream.js';
|
|
20
21
|
import { type AiToolsOptions, type OpenedTools, type SourceAt } from './tools.js';
|
|
21
22
|
import { UsageMeter } from './usage.js';
|
|
22
23
|
/** What a plan's extra tools get: the open tools, a meter of their own and the cap. */
|
|
@@ -197,6 +198,16 @@ export declare abstract class LumenAgent<Env = unknown> extends Think<Env> {
|
|
|
197
198
|
* (`false`) unless the app overrides it; the app logs the details from `onStep`.
|
|
198
199
|
*/
|
|
199
200
|
protected plainSteps(): boolean;
|
|
201
|
+
/**
|
|
202
|
+
* A tool call's line in the person's words: a verb and what it acted on, from the call's own
|
|
203
|
+
* arguments and answer. `running` is present tense ("Editing Lid"), `done` past tense with a short
|
|
204
|
+
* result when it helps ("Edited Lid: wall 2 → 3 mm"), `failed` says it did not ("Couldn't edit
|
|
205
|
+
* Lid"). Return `undefined` to keep the tool's title. A plain chat's line names no tool id, model,
|
|
206
|
+
* engine or code. The kit words its own web lookup; the app words its tools by overriding this
|
|
207
|
+
* (and may call `super.stepLine(c)` for the kit's). A finished line is kept with the chat, so a
|
|
208
|
+
* chat read back shows the same words.
|
|
209
|
+
*/
|
|
210
|
+
protected stepLine(c: StepCall): string | undefined;
|
|
200
211
|
/**
|
|
201
212
|
* A tool call this answer failed: its name and whole error (a plain line shows neither). Does
|
|
202
213
|
* nothing unless the app overrides it, e.g. to log it with the chat's ids.
|
package/dist/agent/agent.js
CHANGED
|
@@ -201,12 +201,45 @@ export class LumenAgent extends Think {
|
|
|
201
201
|
plainSteps() {
|
|
202
202
|
return false;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* A tool call's line in the person's words: a verb and what it acted on, from the call's own
|
|
206
|
+
* arguments and answer. `running` is present tense ("Editing Lid"), `done` past tense with a short
|
|
207
|
+
* result when it helps ("Edited Lid: wall 2 → 3 mm"), `failed` says it did not ("Couldn't edit
|
|
208
|
+
* Lid"). Return `undefined` to keep the tool's title. A plain chat's line names no tool id, model,
|
|
209
|
+
* engine or code. The kit words its own web lookup; the app words its tools by overriding this
|
|
210
|
+
* (and may call `super.stepLine(c)` for the kit's). A finished line is kept with the chat, so a
|
|
211
|
+
* chat read back shows the same words.
|
|
212
|
+
*/
|
|
213
|
+
stepLine(c) {
|
|
214
|
+
if (c.tool !== 'web__lookup')
|
|
215
|
+
return undefined;
|
|
216
|
+
const q = c.input?.question;
|
|
217
|
+
const question = typeof q === 'string' ? q.trim() : '';
|
|
218
|
+
if (!question)
|
|
219
|
+
return undefined;
|
|
220
|
+
return c.state === 'running'
|
|
221
|
+
? `Looking up ${question}`
|
|
222
|
+
: c.state === 'failed'
|
|
223
|
+
? `Couldn't look up ${question}`
|
|
224
|
+
: `Looked up ${question}`;
|
|
225
|
+
}
|
|
204
226
|
/** How this chat words its tool lines: plain or not, with the titles of every tool it offered. */
|
|
205
227
|
#names() {
|
|
206
228
|
return {
|
|
207
229
|
plain: this.plainSteps(),
|
|
208
230
|
titleOf: (n) => this.#turn?.tools.info.find((i) => i.name === n)?.title ??
|
|
209
231
|
this.#get('titles')?.[n],
|
|
232
|
+
lineOf: (c) => {
|
|
233
|
+
if (c.state === 'running')
|
|
234
|
+
return this.stepLine(c);
|
|
235
|
+
const kept = this.#get(`line:${c.id}`);
|
|
236
|
+
if (kept)
|
|
237
|
+
return kept;
|
|
238
|
+
const line = this.stepLine(c);
|
|
239
|
+
if (line)
|
|
240
|
+
this.#put(`line:${c.id}`, line.slice(0, 200));
|
|
241
|
+
return line;
|
|
242
|
+
},
|
|
210
243
|
};
|
|
211
244
|
}
|
|
212
245
|
/**
|
package/dist/agent/stream.d.ts
CHANGED
|
@@ -17,7 +17,28 @@ export interface StepNames {
|
|
|
17
17
|
plain?: boolean;
|
|
18
18
|
/** The tool's own title, when it has one. */
|
|
19
19
|
titleOf?: (name: string) => string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* A line that says what this call did and to what (`LumenAgent.stepLine`): present tense while it
|
|
22
|
+
* runs, past tense with a short result when it answered, "Couldn't …" when it failed. Left out
|
|
23
|
+
* (or `undefined`), the line reads the tool's title.
|
|
24
|
+
*/
|
|
25
|
+
lineOf?: (c: StepCall) => string | undefined;
|
|
20
26
|
}
|
|
27
|
+
/** One tool call as its line is worded: its name, arguments, answer and where it stands. */
|
|
28
|
+
export interface StepCall {
|
|
29
|
+
id: string;
|
|
30
|
+
/** The tool's name as the model called it (`group__tool`). */
|
|
31
|
+
tool: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
/** Its answer, once it answered. */
|
|
34
|
+
output?: unknown;
|
|
35
|
+
state: 'running' | 'done' | 'failed';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a call shows no line at all: in a plain chat, the tool search that turns more tools on is
|
|
39
|
+
* the assistant finding its own tools, not something it did for the person.
|
|
40
|
+
*/
|
|
41
|
+
export declare function hiddenStep(name: string, names?: StepNames): boolean;
|
|
21
42
|
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
22
43
|
export declare const PLAIN_FAILED = "didn't work";
|
|
23
44
|
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
package/dist/agent/stream.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { rowCount } from '../assistant/tools.js';
|
|
2
2
|
import { webStep } from '../assistant/web.js';
|
|
3
|
+
import { TOOL_SEARCH } from './search.js';
|
|
3
4
|
/** A tool's name as a line's title: `notes__find_notes` → `find notes`. */
|
|
4
5
|
export function stepTitle(name) {
|
|
5
6
|
const own = name.includes('__') ? name.slice(name.indexOf('__') + 2) : name;
|
|
6
7
|
return own.replace(/_/g, ' ');
|
|
7
8
|
}
|
|
9
|
+
/** A line's text kept to one short line. */
|
|
10
|
+
const oneLine = (t) => {
|
|
11
|
+
const s = (t ?? '').replace(/\s+/g, ' ').trim();
|
|
12
|
+
return s ? s.slice(0, 120) : undefined;
|
|
13
|
+
};
|
|
14
|
+
/** The line the app gave for this call, when it gave one; never throws. */
|
|
15
|
+
function lineFor(names, c) {
|
|
16
|
+
try {
|
|
17
|
+
return oneLine(names.lineOf?.(c));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether a call shows no line at all: in a plain chat, the tool search that turns more tools on is
|
|
25
|
+
* the assistant finding its own tools, not something it did for the person.
|
|
26
|
+
*/
|
|
27
|
+
export function hiddenStep(name, names = {}) {
|
|
28
|
+
return !!names.plain && name === TOOL_SEARCH;
|
|
29
|
+
}
|
|
8
30
|
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
9
31
|
export const PLAIN_FAILED = "didn't work";
|
|
10
32
|
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
|
@@ -88,15 +110,27 @@ function settledCall(id, c, out, ms, cards, screens = new Map(), names = {}) {
|
|
|
88
110
|
const image = stepImage(o?.image);
|
|
89
111
|
const web = webStep(c.name, c.input, out.output, !!error || obj(out.output)?.ok === false);
|
|
90
112
|
const wrote = card?.kind === 'write' || (o && typeof o.change === 'string');
|
|
113
|
+
// The app's own line says what the call did; a failed one already says it did not ("Couldn't …").
|
|
114
|
+
const line = lineFor(names, {
|
|
115
|
+
id,
|
|
116
|
+
tool: c.name,
|
|
117
|
+
input: c.input,
|
|
118
|
+
...(error
|
|
119
|
+
? {}
|
|
120
|
+
: { output: o && 'result' in o && o.change !== undefined ? o.result : out.output }),
|
|
121
|
+
state: error ? 'failed' : 'done',
|
|
122
|
+
});
|
|
91
123
|
const step = {
|
|
92
124
|
kind: 'step',
|
|
93
125
|
id,
|
|
94
126
|
tool: c.name,
|
|
95
|
-
title: shownTitle(c.name, names, wrote ? 'write' : 'read'),
|
|
127
|
+
title: line ?? shownTitle(c.name, names, wrote ? 'write' : 'read'),
|
|
96
128
|
ok: !error,
|
|
97
129
|
ms,
|
|
98
130
|
...(rows !== undefined && !error && !names.plain ? { rows } : {}),
|
|
99
|
-
...(error
|
|
131
|
+
...(error && !(line && names.plain)
|
|
132
|
+
? { error: names.plain ? PLAIN_FAILED : error.slice(0, 300) }
|
|
133
|
+
: {}),
|
|
100
134
|
...(wrote
|
|
101
135
|
? { write: { undo: true, status: card?.status === 'undone' ? 'undone' : 'done' } }
|
|
102
136
|
: {}),
|
|
@@ -129,7 +163,10 @@ export function chunkEvents(o = {}) {
|
|
|
129
163
|
advising = true;
|
|
130
164
|
return [{ event: 'advice', data: { tier: 'advisor', label: o.advisor ?? 'Advice' } }];
|
|
131
165
|
}
|
|
132
|
-
if (name === 'escalate' ||
|
|
166
|
+
if (name === 'escalate' ||
|
|
167
|
+
name === 'screen' ||
|
|
168
|
+
o.kindOf?.(name) === 'draft' ||
|
|
169
|
+
hiddenStep(name, names))
|
|
133
170
|
return [];
|
|
134
171
|
// A tool that makes a change at once says so as it starts, so an app can show that what it
|
|
135
172
|
// changes is being changed now, before the model has finished writing the call.
|
|
@@ -150,7 +187,23 @@ export function chunkEvents(o = {}) {
|
|
|
150
187
|
case 'tool-input-available': {
|
|
151
188
|
const c = calls.get(id) ?? { name: String(chunk.toolName ?? ''), t0: Date.now() };
|
|
152
189
|
calls.set(id, { ...c, input: chunk.input });
|
|
153
|
-
|
|
190
|
+
// Its arguments are in: the running line can say what it is doing and to what.
|
|
191
|
+
if (c.name === 'advisor' ||
|
|
192
|
+
c.name === 'escalate' ||
|
|
193
|
+
c.name === 'screen' ||
|
|
194
|
+
o.kindOf?.(c.name) === 'draft' ||
|
|
195
|
+
hiddenStep(c.name, names))
|
|
196
|
+
return [];
|
|
197
|
+
const line = lineFor(names, { id, tool: c.name, input: chunk.input, state: 'running' });
|
|
198
|
+
if (!line)
|
|
199
|
+
return [];
|
|
200
|
+
const write = o.kindOf?.(c.name) === 'write';
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
event: 'step_start',
|
|
204
|
+
data: { id, tool: c.name, title: line, ...(write ? { write } : {}) },
|
|
205
|
+
},
|
|
206
|
+
];
|
|
154
207
|
}
|
|
155
208
|
case 'tool-output-available':
|
|
156
209
|
case 'tool-output-error': {
|
|
@@ -164,6 +217,8 @@ export function chunkEvents(o = {}) {
|
|
|
164
217
|
return [{ event: 'advice_done', data: error ? { error } : {} }];
|
|
165
218
|
}
|
|
166
219
|
const ms = Date.now() - c.t0;
|
|
220
|
+
if (hiddenStep(c.name, names))
|
|
221
|
+
return [];
|
|
167
222
|
const out = obj(chunk.output);
|
|
168
223
|
const said = error ?? (out && typeof out.error === 'string' ? out.error : undefined);
|
|
169
224
|
if (said !== undefined) {
|
|
@@ -256,6 +311,8 @@ export function itemsOf(messages, cards, screens = new Map(), steers = {}, names
|
|
|
256
311
|
items.push({ kind: 'advice', tier: 'advisor', label: 'Advice' });
|
|
257
312
|
continue;
|
|
258
313
|
}
|
|
314
|
+
if (hiddenStep(tool, names))
|
|
315
|
+
continue;
|
|
259
316
|
const ended = p.state === 'output-available'
|
|
260
317
|
? { output: p.output }
|
|
261
318
|
: p.state === 'output-error'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.254",
|
|
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",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"@ai-sdk/openai": "4.0.108",
|
|
57
57
|
"@anthropic-ai/sdk": "0.133.0",
|
|
58
58
|
"@cloudflare/think": "0.23.0",
|
|
59
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
60
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
59
|
+
"@columenlabs/lumen-ops": "0.1.254",
|
|
60
|
+
"@columenlabs/lumen-ui-types": "0.1.254",
|
|
61
61
|
"agents": "0.32.0",
|
|
62
62
|
"ai": "7.0.162",
|
|
63
63
|
"just-bash": "3.6.0",
|