@columenlabs/lumen-ai 0.1.247 → 0.1.248
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 +17 -0
- package/dist/agent/agent.js +42 -2
- package/dist/agent/stream.d.ts +25 -1
- package/dist/agent/stream.js +53 -13
- package/dist/agent/tools.d.ts +3 -0
- package/dist/agent/tools.js +10 -3
- package/dist/assistant/engine.d.ts +2 -0
- package/dist/assistant/engine.js +14 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -253,6 +253,8 @@ Drafts and writes are cards in the chat's own storage (`lumen_cards` in the agen
|
|
|
253
253
|
|
|
254
254
|
**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.
|
|
255
255
|
|
|
256
|
+
**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
|
+
|
|
256
258
|
**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).
|
|
257
259
|
|
|
258
260
|
**Steps.** One answer makes at most `plan.maxSteps` model calls (`MAX_STEPS`, 8). The last one runs with no tools and `LAST_STEP_NOTE`, so an answer always ends in words: what it found, and what is left when the task did not fit.
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -191,6 +191,23 @@ export declare abstract class LumenAgent<Env = unknown> extends Think<Env> {
|
|
|
191
191
|
* `stay`. Off (`false`) unless the app overrides it; a throw counts as `false`.
|
|
192
192
|
*/
|
|
193
193
|
protected offerFresh(_input: AskInput): Promise<boolean>;
|
|
194
|
+
/**
|
|
195
|
+
* Whether the chat's tool lines keep inner names off the screen: each reads its tool's title
|
|
196
|
+
* (or a word for what it did), with no tool id, tier, model, row count or error text. Off
|
|
197
|
+
* (`false`) unless the app overrides it; the app logs the details from `onStep`.
|
|
198
|
+
*/
|
|
199
|
+
protected plainSteps(): boolean;
|
|
200
|
+
/**
|
|
201
|
+
* A tool call this answer failed: its name and whole error (a plain line shows neither). Does
|
|
202
|
+
* nothing unless the app overrides it, e.g. to log it with the chat's ids.
|
|
203
|
+
*/
|
|
204
|
+
protected stepFailed(_f: {
|
|
205
|
+
tool: string;
|
|
206
|
+
error: string;
|
|
207
|
+
ms: number;
|
|
208
|
+
}): void;
|
|
209
|
+
/** A tool line's title as this chat words it (`plainSteps`), for an app's own card answers. */
|
|
210
|
+
protected stepTitleOf(name: string, kind?: 'read' | 'draft' | 'write'): string;
|
|
194
211
|
/** The chat's running use: every answer's parts added up, and the last context size. */
|
|
195
212
|
usage(): ChatUsage;
|
|
196
213
|
/** The chat as the panel reads it: title, last message, items and use. */
|
package/dist/agent/agent.js
CHANGED
|
@@ -20,7 +20,7 @@ import { CONNECTOR_GROUP, checkConnectorUrl, connectorGroup, connectorId, connec
|
|
|
20
20
|
import { aiModel, cachedTurn, planFallback, turnSettings } from './models.js';
|
|
21
21
|
import { screenMapOf, screenNews, screenOutcomesOf, screenRules, screenTool, } from './screen.js';
|
|
22
22
|
import { LAZY_AT, TOOL_SEARCH, lazyTools, usedTools } from './search.js';
|
|
23
|
-
import { chunkEvents, itemsOf } from './stream.js';
|
|
23
|
+
import { chunkEvents, itemsOf, shownTitle, } from './stream.js';
|
|
24
24
|
import { openAiTools, } from './tools.js';
|
|
25
25
|
import { UsageMeter } from './usage.js';
|
|
26
26
|
export const CAP_NOTE = 'The spend cap for this month is reached: answer now from what you have, in a sentence or two. You can call no more tools.';
|
|
@@ -193,6 +193,43 @@ export class LumenAgent extends Think {
|
|
|
193
193
|
async offerFresh(_input) {
|
|
194
194
|
return false;
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Whether the chat's tool lines keep inner names off the screen: each reads its tool's title
|
|
198
|
+
* (or a word for what it did), with no tool id, tier, model, row count or error text. Off
|
|
199
|
+
* (`false`) unless the app overrides it; the app logs the details from `onStep`.
|
|
200
|
+
*/
|
|
201
|
+
plainSteps() {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
/** How this chat words its tool lines: plain or not, with the titles of every tool it offered. */
|
|
205
|
+
#names() {
|
|
206
|
+
return {
|
|
207
|
+
plain: this.plainSteps(),
|
|
208
|
+
titleOf: (n) => this.#turn?.tools.info.find((i) => i.name === n)?.title ??
|
|
209
|
+
this.#get('titles')?.[n],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* A tool call this answer failed: its name and whole error (a plain line shows neither). Does
|
|
214
|
+
* nothing unless the app overrides it, e.g. to log it with the chat's ids.
|
|
215
|
+
*/
|
|
216
|
+
stepFailed(_f) { }
|
|
217
|
+
/** A tool line's title as this chat words it (`plainSteps`), for an app's own card answers. */
|
|
218
|
+
stepTitleOf(name, kind) {
|
|
219
|
+
return shownTitle(name, this.#names(), kind);
|
|
220
|
+
}
|
|
221
|
+
/** Keeps the titles of a turn's tools, so a chat read back words its lines the same. */
|
|
222
|
+
#keepTitles(info) {
|
|
223
|
+
const titles = this.#get('titles') ?? {};
|
|
224
|
+
let changed = false;
|
|
225
|
+
for (const i of info)
|
|
226
|
+
if (i.title && titles[i.name] !== i.title) {
|
|
227
|
+
titles[i.name] = i.title.slice(0, 80);
|
|
228
|
+
changed = true;
|
|
229
|
+
}
|
|
230
|
+
if (changed)
|
|
231
|
+
this.#put('titles', titles);
|
|
232
|
+
}
|
|
196
233
|
/** The chat's running use: every answer's parts added up, and the last context size. */
|
|
197
234
|
usage() {
|
|
198
235
|
return this.#get('usage') ?? chatUsage([]);
|
|
@@ -203,7 +240,7 @@ export class LumenAgent extends Think {
|
|
|
203
240
|
id,
|
|
204
241
|
title: this.#get('title') ?? '',
|
|
205
242
|
at: this.#get('at') ?? 0,
|
|
206
|
-
items: itemsOf(await this.getMessages(), this.cards(), this.#screens(), this.#get('steers') ?? {}),
|
|
243
|
+
items: itemsOf(await this.getMessages(), this.cards(), this.#screens(), this.#get('steers') ?? {}, this.#names()),
|
|
207
244
|
usage: this.usage(),
|
|
208
245
|
};
|
|
209
246
|
}
|
|
@@ -258,6 +295,8 @@ export class LumenAgent extends Think {
|
|
|
258
295
|
cards: () => new Map(this.cards().map((c) => [c.id, c])),
|
|
259
296
|
screens: () => this.#screens(),
|
|
260
297
|
kindOf: (n) => this.#turn?.tools.info.find((i) => i.name === n)?.kind,
|
|
298
|
+
names: this.#names(),
|
|
299
|
+
failed: (f) => this.stepFailed(f),
|
|
261
300
|
});
|
|
262
301
|
const map = screenMapOf(input.screen);
|
|
263
302
|
const userId = `u_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`;
|
|
@@ -575,6 +614,7 @@ export class LumenAgent extends Think {
|
|
|
575
614
|
if (advice)
|
|
576
615
|
meters.push(advice);
|
|
577
616
|
let system = typeof plan.system === 'string' ? plan.system : plan.system(opened);
|
|
617
|
+
this.#keepTitles(opened.info);
|
|
578
618
|
this.#turn = {
|
|
579
619
|
plan,
|
|
580
620
|
system,
|
package/dist/agent/stream.d.ts
CHANGED
|
@@ -8,6 +8,22 @@ import type { AgentCard } from './agent.js';
|
|
|
8
8
|
import type { ScreenCall } from './screen.js';
|
|
9
9
|
/** A tool's name as a line's title: `notes__find_notes` → `find notes`. */
|
|
10
10
|
export declare function stepTitle(name: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* How a chat's tool lines are worded. `plain` keeps every inner name off the screen: no tool ids,
|
|
13
|
+
* no tier or model, no row counts and no error text (the app logs those; see `StepRecord.error`).
|
|
14
|
+
* A line reads the tool's own `title` (`titleOf`), else a word for what it did.
|
|
15
|
+
*/
|
|
16
|
+
export interface StepNames {
|
|
17
|
+
plain?: boolean;
|
|
18
|
+
/** The tool's own title, when it has one. */
|
|
19
|
+
titleOf?: (name: string) => string | undefined;
|
|
20
|
+
}
|
|
21
|
+
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
22
|
+
export declare const PLAIN_FAILED = "didn't work";
|
|
23
|
+
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
|
24
|
+
export declare const PLAIN_HANDOFF = "Take a closer look";
|
|
25
|
+
/** The line's title: the tool's own title, else its name (or, plain, a word for what it did). */
|
|
26
|
+
export declare function shownTitle(name: string, names?: StepNames, kind?: 'read' | 'draft' | 'write'): string;
|
|
11
27
|
/**
|
|
12
28
|
* Turns UI message chunks into the panel's events, one chunk at a time. `cards` says how a draft
|
|
13
29
|
* or write ended (a card's id is its tool call's). `advisor` names the advice part.
|
|
@@ -19,6 +35,14 @@ export declare function chunkEvents(o?: {
|
|
|
19
35
|
screens?: () => Map<string, ScreenCall>;
|
|
20
36
|
/** A tool's kind this turn: a draft shows as its card only, with no line before it. */
|
|
21
37
|
kindOf?: (name: string) => 'read' | 'draft' | 'write' | undefined;
|
|
38
|
+
/** How the lines are worded. */
|
|
39
|
+
names?: StepNames;
|
|
40
|
+
/** A tool call failed: its name and whole error, before a plain line hides them (for logs). */
|
|
41
|
+
failed?: (f: {
|
|
42
|
+
tool: string;
|
|
43
|
+
error: string;
|
|
44
|
+
ms: number;
|
|
45
|
+
}) => void;
|
|
22
46
|
}): (chunk: {
|
|
23
47
|
type: string;
|
|
24
48
|
[k: string]: unknown;
|
|
@@ -51,5 +75,5 @@ export interface SteerNote {
|
|
|
51
75
|
text: string;
|
|
52
76
|
at: number;
|
|
53
77
|
}
|
|
54
|
-
export declare function itemsOf(messages: StoredMessage[], cards: AgentCard[], screens?: Map<string, ScreenCall>, steers?: Record<string, SteerNote[]
|
|
78
|
+
export declare function itemsOf(messages: StoredMessage[], cards: AgentCard[], screens?: Map<string, ScreenCall>, steers?: Record<string, SteerNote[]>, names?: StepNames): ChatItem[];
|
|
55
79
|
export {};
|
package/dist/agent/stream.js
CHANGED
|
@@ -5,6 +5,25 @@ export function stepTitle(name) {
|
|
|
5
5
|
const own = name.includes('__') ? name.slice(name.indexOf('__') + 2) : name;
|
|
6
6
|
return own.replace(/_/g, ' ');
|
|
7
7
|
}
|
|
8
|
+
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
9
|
+
export const PLAIN_FAILED = "didn't work";
|
|
10
|
+
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
|
11
|
+
export const PLAIN_HANDOFF = 'Take a closer look';
|
|
12
|
+
/** The line's title: the tool's own title, else its name (or, plain, a word for what it did). */
|
|
13
|
+
export function shownTitle(name, names = {}, kind) {
|
|
14
|
+
const own = names.titleOf?.(name);
|
|
15
|
+
if (own)
|
|
16
|
+
return own;
|
|
17
|
+
if (!names.plain)
|
|
18
|
+
return stepTitle(name);
|
|
19
|
+
if (name === 'escalate')
|
|
20
|
+
return PLAIN_HANDOFF;
|
|
21
|
+
return kind === 'write'
|
|
22
|
+
? 'Make a change'
|
|
23
|
+
: kind === 'draft'
|
|
24
|
+
? 'Draft a change'
|
|
25
|
+
: 'Look something up';
|
|
26
|
+
}
|
|
8
27
|
const obj = (v) => v && typeof v === 'object' && !Array.isArray(v) ? v : null;
|
|
9
28
|
/** A tool's `image` output as the panel's card: a same-site path or an https address only. */
|
|
10
29
|
function stepImage(v) {
|
|
@@ -19,7 +38,7 @@ function stepImage(v) {
|
|
|
19
38
|
};
|
|
20
39
|
}
|
|
21
40
|
/** What one finished tool call shows as: a line, a card, a hand-off section or nothing. */
|
|
22
|
-
function settledCall(id, c, out, ms, cards, screens = new Map()) {
|
|
41
|
+
function settledCall(id, c, out, ms, cards, screens = new Map(), names = {}) {
|
|
23
42
|
const o = obj(out.output);
|
|
24
43
|
if (c.name === 'screen') {
|
|
25
44
|
// A call the page map refused goes back to the model only; the person sees the one that ran.
|
|
@@ -43,7 +62,7 @@ function settledCall(id, c, out, ms, cards, screens = new Map()) {
|
|
|
43
62
|
kind: 'draft',
|
|
44
63
|
id,
|
|
45
64
|
tool: c.name,
|
|
46
|
-
title:
|
|
65
|
+
title: shownTitle(c.name, names, 'draft'),
|
|
47
66
|
args: card?.args ?? c.input ?? null,
|
|
48
67
|
status: card && card.status !== 'done' && card.status !== 'undone' ? card.status : 'open',
|
|
49
68
|
...(card?.result !== undefined ? { result: card.result } : {}),
|
|
@@ -53,11 +72,13 @@ function settledCall(id, c, out, ms, cards, screens = new Map()) {
|
|
|
53
72
|
}
|
|
54
73
|
const h = obj(o?.handoff);
|
|
55
74
|
if (h && typeof h.tier === 'string' && typeof o?.memo === 'string') {
|
|
56
|
-
|
|
75
|
+
// Plain: the tier and the task the model wrote for it stay off the screen.
|
|
76
|
+
const task = names.plain ? '' : String(obj(c.input)?.task ?? '');
|
|
77
|
+
const label = names.plain ? PLAIN_HANDOFF : h.label;
|
|
57
78
|
return [
|
|
58
79
|
{
|
|
59
80
|
event: 'handoff',
|
|
60
|
-
data: { kind: 'handoff', tier: h.tier, label
|
|
81
|
+
data: { kind: 'handoff', tier: h.tier, label, task, why: h.why },
|
|
61
82
|
},
|
|
62
83
|
{ event: 'handoff_done', data: { usage: h.usage } },
|
|
63
84
|
];
|
|
@@ -66,16 +87,17 @@ function settledCall(id, c, out, ms, cards, screens = new Map()) {
|
|
|
66
87
|
const rows = rowCount(o && 'result' in o ? o.result : out.output);
|
|
67
88
|
const image = stepImage(o?.image);
|
|
68
89
|
const web = webStep(c.name, c.input, out.output, !!error || obj(out.output)?.ok === false);
|
|
90
|
+
const wrote = card?.kind === 'write' || (o && typeof o.change === 'string');
|
|
69
91
|
const step = {
|
|
70
92
|
kind: 'step',
|
|
71
93
|
id,
|
|
72
94
|
tool: c.name,
|
|
73
|
-
title:
|
|
95
|
+
title: shownTitle(c.name, names, wrote ? 'write' : 'read'),
|
|
74
96
|
ok: !error,
|
|
75
97
|
ms,
|
|
76
|
-
...(rows !== undefined && !error ? { rows } : {}),
|
|
77
|
-
...(error ? { error: error.slice(0, 300) } : {}),
|
|
78
|
-
...(
|
|
98
|
+
...(rows !== undefined && !error && !names.plain ? { rows } : {}),
|
|
99
|
+
...(error ? { error: names.plain ? PLAIN_FAILED : error.slice(0, 300) } : {}),
|
|
100
|
+
...(wrote
|
|
79
101
|
? { write: { undo: true, status: card?.status === 'undone' ? 'undone' : 'done' } }
|
|
80
102
|
: {}),
|
|
81
103
|
...(web ? { web } : {}),
|
|
@@ -88,6 +110,7 @@ function settledCall(id, c, out, ms, cards, screens = new Map()) {
|
|
|
88
110
|
* or write ended (a card's id is its tool call's). `advisor` names the advice part.
|
|
89
111
|
*/
|
|
90
112
|
export function chunkEvents(o = {}) {
|
|
113
|
+
const names = o.names ?? {};
|
|
91
114
|
const calls = new Map();
|
|
92
115
|
let advising = false;
|
|
93
116
|
return (chunk) => {
|
|
@@ -110,11 +133,17 @@ export function chunkEvents(o = {}) {
|
|
|
110
133
|
return [];
|
|
111
134
|
// A tool that makes a change at once says so as it starts, so an app can show that what it
|
|
112
135
|
// changes is being changed now, before the model has finished writing the call.
|
|
113
|
-
const
|
|
136
|
+
const kind = o.kindOf?.(name);
|
|
137
|
+
const write = kind === 'write';
|
|
114
138
|
return [
|
|
115
139
|
{
|
|
116
140
|
event: 'step_start',
|
|
117
|
-
data: {
|
|
141
|
+
data: {
|
|
142
|
+
id,
|
|
143
|
+
tool: name,
|
|
144
|
+
title: shownTitle(name, names, kind),
|
|
145
|
+
...(write ? { write } : {}),
|
|
146
|
+
},
|
|
118
147
|
},
|
|
119
148
|
];
|
|
120
149
|
}
|
|
@@ -134,7 +163,18 @@ export function chunkEvents(o = {}) {
|
|
|
134
163
|
advising = false;
|
|
135
164
|
return [{ event: 'advice_done', data: error ? { error } : {} }];
|
|
136
165
|
}
|
|
137
|
-
|
|
166
|
+
const ms = Date.now() - c.t0;
|
|
167
|
+
const out = obj(chunk.output);
|
|
168
|
+
const said = error ?? (out && typeof out.error === 'string' ? out.error : undefined);
|
|
169
|
+
if (said !== undefined) {
|
|
170
|
+
try {
|
|
171
|
+
o.failed?.({ tool: c.name, error: said.slice(0, 2000), ms });
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// a log hook never breaks the answer
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return settledCall(id, c, error !== undefined ? { error } : { output: chunk.output }, ms, o.cards?.() ?? new Map(), c.name === 'screen' ? (o.screens?.() ?? new Map()) : undefined, names);
|
|
138
178
|
}
|
|
139
179
|
case 'error':
|
|
140
180
|
return [
|
|
@@ -152,7 +192,7 @@ export function chunkEvents(o = {}) {
|
|
|
152
192
|
}
|
|
153
193
|
};
|
|
154
194
|
}
|
|
155
|
-
export function itemsOf(messages, cards, screens = new Map(), steers = {}) {
|
|
195
|
+
export function itemsOf(messages, cards, screens = new Map(), steers = {}, names = {}) {
|
|
156
196
|
const byId = new Map(cards.map((c) => [c.id, c]));
|
|
157
197
|
const items = [];
|
|
158
198
|
let asked;
|
|
@@ -223,7 +263,7 @@ export function itemsOf(messages, cards, screens = new Map(), steers = {}) {
|
|
|
223
263
|
: null;
|
|
224
264
|
if (!ended)
|
|
225
265
|
continue;
|
|
226
|
-
for (const e of settledCall(p.toolCallId, { name: tool, input: p.input, t0: 0 }, ended, 0, byId, screens)) {
|
|
266
|
+
for (const e of settledCall(p.toolCallId, { name: tool, input: p.input, t0: 0 }, ended, 0, byId, screens, names)) {
|
|
227
267
|
if (e.event === 'handoff')
|
|
228
268
|
items.push({ ...e.data, items: [] });
|
|
229
269
|
else if (e.event === 'handoff_done') {
|
package/dist/agent/tools.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface AiToolsOptions {
|
|
|
35
35
|
tool: string;
|
|
36
36
|
ok: boolean;
|
|
37
37
|
ms: number;
|
|
38
|
+
error?: string;
|
|
38
39
|
}) => void;
|
|
39
40
|
/** A result handed to the model is cut past this many characters. */
|
|
40
41
|
maxChars?: number;
|
|
@@ -45,6 +46,8 @@ export interface ToolInfo {
|
|
|
45
46
|
group: string;
|
|
46
47
|
kind: 'read' | 'draft' | 'write';
|
|
47
48
|
find: boolean;
|
|
49
|
+
/** The tool's own title, in words a person reads ("Read the design guide"). */
|
|
50
|
+
title?: string;
|
|
48
51
|
}
|
|
49
52
|
/** Tools open for one answer, as the AI SDK's set, and how to let them go. */
|
|
50
53
|
export interface OpenedTools {
|
package/dist/agent/tools.js
CHANGED
|
@@ -37,7 +37,13 @@ export async function openAiTools(sources, o = {}) {
|
|
|
37
37
|
const name = toolName(s.group, def.name);
|
|
38
38
|
if (kind === 'read')
|
|
39
39
|
reads.push(name);
|
|
40
|
-
info.push({
|
|
40
|
+
info.push({
|
|
41
|
+
name,
|
|
42
|
+
group: s.group,
|
|
43
|
+
kind,
|
|
44
|
+
find: !!def.find,
|
|
45
|
+
...(def.title ? { title: def.title } : {}),
|
|
46
|
+
});
|
|
41
47
|
tools[name] = tool({
|
|
42
48
|
description: describe(def, kind),
|
|
43
49
|
inputSchema: jsonSchema(withoutMeta(def.inputSchema)),
|
|
@@ -65,8 +71,9 @@ export async function openAiTools(sources, o = {}) {
|
|
|
65
71
|
return shown;
|
|
66
72
|
}
|
|
67
73
|
catch (err) {
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
const error = String(err?.message ?? err).slice(0, 500);
|
|
75
|
+
o.called?.({ group: s.group, tool: def.name, ok: false, ms: Date.now() - t0, error });
|
|
76
|
+
return { error };
|
|
70
77
|
}
|
|
71
78
|
},
|
|
72
79
|
});
|
package/dist/assistant/engine.js
CHANGED
|
@@ -363,6 +363,8 @@ export class Assistant {
|
|
|
363
363
|
tool: toolName(c.group, c.tool),
|
|
364
364
|
ok: c.ok,
|
|
365
365
|
ms: c.ms,
|
|
366
|
+
...(c.error ? { error: c.error } : {}),
|
|
367
|
+
caller: caller.id,
|
|
366
368
|
tier: main.tier.id,
|
|
367
369
|
handoff: false,
|
|
368
370
|
})).catch(() => undefined),
|
|
@@ -509,6 +511,7 @@ export class Assistant {
|
|
|
509
511
|
if (this.tiers.indexOf(tier) > this.tiers.indexOf(c.counts.end))
|
|
510
512
|
c.counts.end = tier;
|
|
511
513
|
const shown = asked.has(tier.id) ? 'asked' : why;
|
|
514
|
+
const t0 = Date.now();
|
|
512
515
|
try {
|
|
513
516
|
const r = await generateText({
|
|
514
517
|
model: aiModel(sub.model, key, {
|
|
@@ -539,6 +542,17 @@ export class Assistant {
|
|
|
539
542
|
catch (err) {
|
|
540
543
|
if (abortSignal?.aborted)
|
|
541
544
|
throw err;
|
|
545
|
+
// The whole error goes to the app's log; the chat line may show none of it (plain steps).
|
|
546
|
+
await Promise.resolve(this.o.onStep?.({
|
|
547
|
+
kind: 'tool',
|
|
548
|
+
tool: 'escalate',
|
|
549
|
+
ok: false,
|
|
550
|
+
ms: Date.now() - t0,
|
|
551
|
+
error: `${sub.meter.label}: ${String(err?.message ?? err).slice(0, 500)}`,
|
|
552
|
+
caller: c.caller.id,
|
|
553
|
+
tier: tier.id,
|
|
554
|
+
handoff: true,
|
|
555
|
+
})).catch(() => undefined);
|
|
542
556
|
return { error: `${sub.meter.label} failed: ${shownError(err).message}` };
|
|
543
557
|
}
|
|
544
558
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.248",
|
|
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.248",
|
|
56
|
+
"@columenlabs/lumen-ui-types": "0.1.248",
|
|
57
57
|
"agents": "0.32.0",
|
|
58
58
|
"ai": "7.0.162",
|
|
59
59
|
"just-bash": "3.6.0",
|