@columenlabs/lumen-ai 0.1.178 → 0.1.179
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 +5 -1
- package/dist/agent/agent.d.ts +33 -2
- package/dist/agent/agent.js +154 -4
- package/dist/agent/tools.d.ts +4 -0
- package/dist/agent/tools.js +7 -1
- package/dist/assistant/engine.d.ts +16 -0
- package/dist/assistant/engine.js +34 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -226,7 +226,9 @@ ctx.waitUntil(meaningStep({ def: notesFind, sql: d1Sql(env.DB), embed, index: en
|
|
|
226
226
|
|
|
227
227
|
A ChatGPT plan sign-in is free to the app (`priced` with `free`). Nothing here holds a key or a token: the app passes them per turn.
|
|
228
228
|
|
|
229
|
-
An app with an `Assistant` gets the whole plan from it: `assistant.agentTurn(caller, { message, tier?, context?, refs?, time_zone? })` returns `{ plan, settle }` with the person's tier and model on whoever pays, the tool groups at their access, the kit's rules, the bearings and the page before the message, `escalate` to a higher tier (its own usage part, inside the same cap) and Claude's advisor. `settle` adds the spend to the payer's month and reports the turn through `onStep`. It throws `AssistantError` when nobody can pay.
|
|
229
|
+
An app with an `Assistant` gets the whole plan from it: `assistant.agentTurn(caller, { message, tier?, context?, refs?, time_zone? })` returns `{ plan, settle }` with the person's tier and model on whoever pays, the tool groups at their access, the kit's rules, the bearings and the page before the message, `escalate` to a higher tier (its own usage part, inside the same cap) and Claude's advisor. `settle` adds the spend to the payer's month and reports the turn through `onStep`. It throws `AssistantError` when nobody can pay.
|
|
230
|
+
|
|
231
|
+
Drafts and writes are cards in the chat's own storage (`lumen_cards` in the agent's SQLite). A write the person's access only drafts makes an open card and changes nothing. `saveCard(id)` runs it through the app's `applyDraft`, and `dismissCard(id)` drops it. A write made at once is kept for `undoCard(id)` through the app's `undoWrite`. `cards()` lists the cards. The next answer is told how each card ended and what was undone. With an `Assistant`, `applyDraft` is `assistant.runDraft(caller, card)` and `undoWrite` is `assistant.undoChange(caller, card)`. Think compacts the chat past `COMPACT_AT` (150k tokens) on the last answer's model and key, and that spend is added to the next answer.
|
|
230
232
|
|
|
231
233
|
```ts
|
|
232
234
|
class Chat extends LumenAgent<Env> {
|
|
@@ -237,5 +239,7 @@ class Chat extends LumenAgent<Env> {
|
|
|
237
239
|
return plan;
|
|
238
240
|
}
|
|
239
241
|
settle(end: AgentTurnEnd) { return this.#settle?.(end) ?? Promise.resolve(); }
|
|
242
|
+
protected applyDraft(card: AgentCard) { return assistant.runDraft(caller, card); }
|
|
243
|
+
protected undoWrite(card: AgentCard) { return assistant.undoChange(caller, card); }
|
|
240
244
|
}
|
|
241
245
|
```
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* An `Assistant` gives both: `assistant.agentTurn(caller, input)` returns a plan and its settle.
|
|
13
13
|
*/
|
|
14
|
-
import { type ChatResponseResult, type PrepareStepContext, type StepConfig, type StepContext, Think, type ThinkModel, type TurnConfig, type TurnContext } from '@cloudflare/think';
|
|
14
|
+
import { type ChatResponseResult, type PrepareStepContext, type Session, type StepConfig, type StepContext, Think, type ThinkModel, type TurnConfig, type TurnContext } from '@cloudflare/think';
|
|
15
15
|
import type { AssistantEffort, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
16
|
-
import type
|
|
16
|
+
import { type ModelMessage, type ToolSet } from 'ai';
|
|
17
17
|
import type { ModelDef } from '../assistant/models.js';
|
|
18
18
|
import { type ModelKey } from './models.js';
|
|
19
19
|
import { type AiToolsOptions, type OpenedTools, type SourceAt } from './tools.js';
|
|
@@ -75,6 +75,23 @@ export interface AgentTurnEnd {
|
|
|
75
75
|
capped: boolean;
|
|
76
76
|
}
|
|
77
77
|
export declare 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.";
|
|
78
|
+
/** A card in the chat: a draft the person saves or dismisses, or a write made at once (Undo). */
|
|
79
|
+
export interface AgentCard {
|
|
80
|
+
id: string;
|
|
81
|
+
kind: 'draft' | 'write';
|
|
82
|
+
group: string;
|
|
83
|
+
/** The tool's own name, not the `group__tool` the model calls. */
|
|
84
|
+
tool: string;
|
|
85
|
+
args: unknown;
|
|
86
|
+
result?: unknown;
|
|
87
|
+
status: 'open' | 'saved' | 'dismissed' | 'failed' | 'done' | 'undone';
|
|
88
|
+
error?: string;
|
|
89
|
+
at: number;
|
|
90
|
+
}
|
|
91
|
+
/** Past this many tokens in the chat Think summarizes its older part. */
|
|
92
|
+
export declare const COMPACT_AT = 150000;
|
|
93
|
+
/** What the model is told of cards settled and changes undone since its last answer. */
|
|
94
|
+
export declare function cardNews(cards: AgentCard[]): string;
|
|
78
95
|
/** The messages with `lead` put before the newest message the person wrote. */
|
|
79
96
|
export declare function withLead(messages: ModelMessage[], lead: string | undefined): ModelMessage[];
|
|
80
97
|
export declare abstract class LumenAgent<Env = unknown> extends Think<Env> {
|
|
@@ -83,6 +100,20 @@ export declare abstract class LumenAgent<Env = unknown> extends Think<Env> {
|
|
|
83
100
|
abstract plan(turn: TurnContext): Promise<AgentTurnPlan>;
|
|
84
101
|
/** A turn ended: record its spend and how it went. Errors here are logged, never shown. */
|
|
85
102
|
abstract settle(end: AgentTurnEnd): Promise<void>;
|
|
103
|
+
/** Saves a draft: runs its write as the person. The result is shown and told to the model. */
|
|
104
|
+
protected applyDraft(_card: AgentCard): Promise<unknown>;
|
|
105
|
+
/** Takes back a write made at once (Undo). */
|
|
106
|
+
protected undoWrite(_card: AgentCard): Promise<void>;
|
|
107
|
+
/** The chat's cards, newest last: drafts and writes made at once (at most 200). */
|
|
108
|
+
cards(): AgentCard[];
|
|
109
|
+
/** The person saved a draft: its write runs now. */
|
|
110
|
+
saveCard(id: string): Promise<AgentCard>;
|
|
111
|
+
/** The person dismissed a draft: nothing changes. */
|
|
112
|
+
dismissCard(id: string): AgentCard;
|
|
113
|
+
/** The person undid a write made at once. */
|
|
114
|
+
undoCard(id: string): Promise<AgentCard>;
|
|
115
|
+
/** Think compacts the chat past `COMPACT_AT` tokens, on the last turn's model and key. */
|
|
116
|
+
configureSession(session: Session): Session;
|
|
86
117
|
/** Never read: `beforeTurn` gives every turn its own model. */
|
|
87
118
|
getModel(): ThinkModel;
|
|
88
119
|
beforeTurn(ctx: TurnContext): Promise<TurnConfig>;
|
package/dist/agent/agent.js
CHANGED
|
@@ -13,10 +13,40 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { anthropic } from '@ai-sdk/anthropic';
|
|
15
15
|
import { Think, } from '@cloudflare/think';
|
|
16
|
+
import { createCompactFunction } from 'agents/sessions';
|
|
17
|
+
import { generateText } from 'ai';
|
|
16
18
|
import { aiModel, turnSettings } from './models.js';
|
|
17
|
-
import { openAiTools } from './tools.js';
|
|
19
|
+
import { openAiTools, } from './tools.js';
|
|
18
20
|
import { UsageMeter } from './usage.js';
|
|
19
21
|
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.';
|
|
22
|
+
/** Past this many tokens in the chat Think summarizes its older part. */
|
|
23
|
+
export const COMPACT_AT = 150_000;
|
|
24
|
+
const CARD_NOTE = "A card is on the person's screen; nothing has changed yet. Say what it will do when they save it. The next message tells you how it ended.";
|
|
25
|
+
/** What the model is told of cards settled and changes undone since its last answer. */
|
|
26
|
+
export function cardNews(cards) {
|
|
27
|
+
const undone = cards.filter((c) => c.kind === 'write' && c.status === 'undone');
|
|
28
|
+
const ended = cards.filter((c) => c.kind === 'draft' && c.status !== 'open');
|
|
29
|
+
const line = (c) => `${c.group}/${c.tool} ${JSON.stringify(c.args ?? {}).slice(0, 300)}`;
|
|
30
|
+
const back = undone.length
|
|
31
|
+
? `The person undid these changes you made: ${undone.map(line).join('; ')}. Do not make them again unless asked.\n\n`
|
|
32
|
+
: '';
|
|
33
|
+
if (!ended.length)
|
|
34
|
+
return back;
|
|
35
|
+
return `${back}Cards since your last answer:\n${ended
|
|
36
|
+
.map((c) => `- ${line(c)}: ${c.status}${c.error ? ` (${c.error})` : ''}${c.result !== undefined ? ` → ${JSON.stringify(c.result).slice(0, 300)}` : ''}`)
|
|
37
|
+
.join('\n')}\n\n`;
|
|
38
|
+
}
|
|
39
|
+
const cardOf = (r) => ({
|
|
40
|
+
id: r.id,
|
|
41
|
+
kind: r.kind,
|
|
42
|
+
group: r.grp,
|
|
43
|
+
tool: r.tool,
|
|
44
|
+
args: JSON.parse(r.args),
|
|
45
|
+
...(r.result === null ? {} : { result: JSON.parse(r.result) }),
|
|
46
|
+
status: r.status,
|
|
47
|
+
...(r.error ? { error: r.error } : {}),
|
|
48
|
+
at: r.at,
|
|
49
|
+
});
|
|
20
50
|
/** The messages with `lead` put before the newest message the person wrote. */
|
|
21
51
|
export function withLead(messages, lead) {
|
|
22
52
|
if (!lead)
|
|
@@ -33,7 +63,121 @@ export function withLead(messages, lead) {
|
|
|
33
63
|
return [...messages.slice(0, i), { ...m, content }, ...messages.slice(i + 1)];
|
|
34
64
|
}
|
|
35
65
|
export class LumenAgent extends Think {
|
|
66
|
+
/** Saves a draft: runs its write as the person. The result is shown and told to the model. */
|
|
67
|
+
applyDraft(_card) {
|
|
68
|
+
return Promise.reject(new Error('Saving a card is not set up here.'));
|
|
69
|
+
}
|
|
70
|
+
/** Takes back a write made at once (Undo). */
|
|
71
|
+
undoWrite(_card) {
|
|
72
|
+
return Promise.reject(new Error('This change cannot be undone here.'));
|
|
73
|
+
}
|
|
36
74
|
#turn = null;
|
|
75
|
+
/** The last turn's model and key, for compaction between turns. */
|
|
76
|
+
#last = null;
|
|
77
|
+
/** Usage spent between turns (compaction), settled with the next turn. */
|
|
78
|
+
#owed = [];
|
|
79
|
+
#tabled = false;
|
|
80
|
+
#cards() {
|
|
81
|
+
if (!this.#tabled) {
|
|
82
|
+
this
|
|
83
|
+
.sql `CREATE TABLE IF NOT EXISTS lumen_cards (id TEXT PRIMARY KEY, kind TEXT NOT NULL, grp TEXT NOT NULL, tool TEXT NOT NULL, args TEXT NOT NULL, result TEXT, status TEXT NOT NULL, error TEXT, told INTEGER NOT NULL DEFAULT 0, at INTEGER NOT NULL)`;
|
|
84
|
+
this.#tabled = true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
#addCard(kind, d, result) {
|
|
88
|
+
this.#cards();
|
|
89
|
+
const id = `card_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`;
|
|
90
|
+
this
|
|
91
|
+
.sql `INSERT INTO lumen_cards (id, kind, grp, tool, args, result, status, at) VALUES (${id}, ${kind}, ${d.group}, ${d.tool}, ${JSON.stringify(d.args ?? null)}, ${result === undefined ? null : JSON.stringify(result).slice(0, 20_000)}, ${kind === 'draft' ? 'open' : 'done'}, ${Date.now()})`;
|
|
92
|
+
return id;
|
|
93
|
+
}
|
|
94
|
+
#card(id) {
|
|
95
|
+
this.#cards();
|
|
96
|
+
const [r] = this.sql `SELECT * FROM lumen_cards WHERE id = ${id} LIMIT 1`;
|
|
97
|
+
return r ? cardOf(r) : null;
|
|
98
|
+
}
|
|
99
|
+
#setCard(id, status, o = {}) {
|
|
100
|
+
this
|
|
101
|
+
.sql `UPDATE lumen_cards SET status = ${status}, result = COALESCE(${o.result === undefined ? null : JSON.stringify(o.result).slice(0, 20_000)}, result), error = ${o.error ?? null}, told = 0 WHERE id = ${id}`;
|
|
102
|
+
}
|
|
103
|
+
/** The chat's cards, newest last: drafts and writes made at once (at most 200). */
|
|
104
|
+
cards() {
|
|
105
|
+
this.#cards();
|
|
106
|
+
return this.sql `SELECT * FROM lumen_cards ORDER BY at DESC LIMIT 200`
|
|
107
|
+
.map(cardOf)
|
|
108
|
+
.reverse();
|
|
109
|
+
}
|
|
110
|
+
/** The person saved a draft: its write runs now. */
|
|
111
|
+
async saveCard(id) {
|
|
112
|
+
const c = this.#card(id);
|
|
113
|
+
if (!c || c.kind !== 'draft')
|
|
114
|
+
throw new Error('That card is gone.');
|
|
115
|
+
if (c.status !== 'open')
|
|
116
|
+
throw new Error(`That card is already ${c.status}.`);
|
|
117
|
+
try {
|
|
118
|
+
this.#setCard(id, 'saved', { result: await this.applyDraft(c) });
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
this.#setCard(id, 'failed', { error: String(err?.message ?? err).slice(0, 300) });
|
|
122
|
+
}
|
|
123
|
+
return this.#card(id);
|
|
124
|
+
}
|
|
125
|
+
/** The person dismissed a draft: nothing changes. */
|
|
126
|
+
dismissCard(id) {
|
|
127
|
+
const c = this.#card(id);
|
|
128
|
+
if (!c || c.kind !== 'draft')
|
|
129
|
+
throw new Error('That card is gone.');
|
|
130
|
+
if (c.status !== 'open')
|
|
131
|
+
throw new Error(`That card is already ${c.status}.`);
|
|
132
|
+
this.#setCard(id, 'dismissed');
|
|
133
|
+
return this.#card(id);
|
|
134
|
+
}
|
|
135
|
+
/** The person undid a write made at once. */
|
|
136
|
+
async undoCard(id) {
|
|
137
|
+
const c = this.#card(id);
|
|
138
|
+
if (!c || c.kind !== 'write')
|
|
139
|
+
throw new Error('That change is gone.');
|
|
140
|
+
if (c.status === 'undone')
|
|
141
|
+
throw new Error('That change is already undone.');
|
|
142
|
+
await this.undoWrite(c);
|
|
143
|
+
this.#setCard(id, 'undone');
|
|
144
|
+
return this.#card(id);
|
|
145
|
+
}
|
|
146
|
+
/** Cards settled since the model last heard, as its note; marks them told. */
|
|
147
|
+
#news() {
|
|
148
|
+
this.#cards();
|
|
149
|
+
const rows = this
|
|
150
|
+
.sql `SELECT * FROM lumen_cards WHERE told = 0 AND status IN ('saved', 'dismissed', 'failed', 'undone') ORDER BY at LIMIT 50`;
|
|
151
|
+
if (!rows.length)
|
|
152
|
+
return '';
|
|
153
|
+
for (const r of rows)
|
|
154
|
+
this.sql `UPDATE lumen_cards SET told = 1 WHERE id = ${r.id}`;
|
|
155
|
+
return cardNews(rows.map(cardOf));
|
|
156
|
+
}
|
|
157
|
+
/** Think compacts the chat past `COMPACT_AT` tokens, on the last turn's model and key. */
|
|
158
|
+
configureSession(session) {
|
|
159
|
+
return session
|
|
160
|
+
.onCompaction(createCompactFunction({ summarize: (prompt) => this.#summarize(prompt) }))
|
|
161
|
+
.compactAfter(COMPACT_AT);
|
|
162
|
+
}
|
|
163
|
+
async #summarize(prompt) {
|
|
164
|
+
const last = this.#last;
|
|
165
|
+
if (!last)
|
|
166
|
+
throw new Error('No model to compact with until the next answer.');
|
|
167
|
+
const { plan } = last;
|
|
168
|
+
const meter = new UsageMeter(plan.model, `${plan.label} · compact`, !!plan.key.plan);
|
|
169
|
+
this.#owed.push(meter);
|
|
170
|
+
const r = await generateText({
|
|
171
|
+
model: aiModel(plan.model, plan.key, {
|
|
172
|
+
...(plan.fetch ? { fetch: plan.fetch } : {}),
|
|
173
|
+
name: this.constructor.name,
|
|
174
|
+
}),
|
|
175
|
+
prompt,
|
|
176
|
+
...turnSettings(plan.model, { effort: 'default', out: 4000, plan: !!plan.key.plan }),
|
|
177
|
+
});
|
|
178
|
+
meter.add(r.usage);
|
|
179
|
+
return r.text;
|
|
180
|
+
}
|
|
37
181
|
/** Never read: `beforeTurn` gives every turn its own model. */
|
|
38
182
|
getModel() {
|
|
39
183
|
return '@cf/none';
|
|
@@ -41,10 +185,16 @@ export class LumenAgent extends Think {
|
|
|
41
185
|
async beforeTurn(ctx) {
|
|
42
186
|
await this.#close();
|
|
43
187
|
const plan = await this.plan(ctx);
|
|
44
|
-
|
|
188
|
+
this.#last = { plan };
|
|
189
|
+
const opened = await openAiTools(plan.sources, {
|
|
190
|
+
draft: async (d) => ({ card: this.#addCard('draft', d), note: CARD_NOTE }),
|
|
191
|
+
wrote: async (w) => this.#addCard('write', w, w.result),
|
|
192
|
+
...plan.tools,
|
|
193
|
+
});
|
|
45
194
|
const free = !!plan.key.plan;
|
|
46
195
|
const main = new UsageMeter(plan.model, plan.label, free);
|
|
47
|
-
const meters = [main];
|
|
196
|
+
const meters = [main, ...this.#owed];
|
|
197
|
+
this.#owed = [];
|
|
48
198
|
const advice = plan.advisor
|
|
49
199
|
? new UsageMeter(plan.advisor.model, plan.advisor.label, free)
|
|
50
200
|
: undefined;
|
|
@@ -84,7 +234,7 @@ export class LumenAgent extends Think {
|
|
|
84
234
|
name: this.constructor.name,
|
|
85
235
|
}),
|
|
86
236
|
instructions: system,
|
|
87
|
-
messages: withLead(ctx.messages, plan.lead),
|
|
237
|
+
messages: withLead(ctx.messages, `${this.#news()}${plan.lead ?? ''}`),
|
|
88
238
|
tools: { ...opened.tools, ...more, ...advisor },
|
|
89
239
|
...(plan.first ? { toolChoice: { type: 'tool', toolName: plan.first } } : {}),
|
|
90
240
|
maxSteps: plan.maxSteps ?? 8,
|
package/dist/agent/tools.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface DraftCall {
|
|
|
23
23
|
export interface AiToolsOptions {
|
|
24
24
|
/** A draft's card was made: return what the model is told (its id, "a card is on screen"). */
|
|
25
25
|
draft?: (d: DraftCall) => Promise<unknown>;
|
|
26
|
+
/** A write ran at once: what it did, for Undo. Its answer is added to the model's result. */
|
|
27
|
+
wrote?: (w: DraftCall & {
|
|
28
|
+
result: unknown;
|
|
29
|
+
}) => Promise<unknown>;
|
|
26
30
|
/** Every call's outcome, for the app's meter: shapes only. */
|
|
27
31
|
called?: (c: {
|
|
28
32
|
group: string;
|
package/dist/agent/tools.js
CHANGED
|
@@ -49,7 +49,13 @@ export async function openAiTools(sources, o = {}) {
|
|
|
49
49
|
const out = await open.call(def.name, args);
|
|
50
50
|
o.called?.({ group: s.group, tool: def.name, ok: true, ms: Date.now() - t0 });
|
|
51
51
|
const text = typeof out === 'string' ? out : JSON.stringify(out);
|
|
52
|
-
|
|
52
|
+
const shown = text.length > max ? `${text.slice(0, max)} …(cut)` : out;
|
|
53
|
+
if (kind === 'write' && o.wrote) {
|
|
54
|
+
const change = await o.wrote({ group: s.group, tool: def.name, args, result: out });
|
|
55
|
+
if (change !== undefined)
|
|
56
|
+
return { result: shown, change };
|
|
57
|
+
}
|
|
58
|
+
return shown;
|
|
53
59
|
}
|
|
54
60
|
catch (err) {
|
|
55
61
|
o.called?.({ group: s.group, tool: def.name, ok: false, ms: Date.now() - t0 });
|
|
@@ -227,6 +227,22 @@ export declare class Assistant {
|
|
|
227
227
|
plan: AgentTurnPlan;
|
|
228
228
|
settle: (end: AgentTurnEnd) => Promise<void>;
|
|
229
229
|
}>;
|
|
230
|
+
/**
|
|
231
|
+
* Saves a Think chat's draft card (`LumenAgent.applyDraft`): its group's write runs as the person,
|
|
232
|
+
* while that group is still on Read + draft or Read + write.
|
|
233
|
+
*/
|
|
234
|
+
runDraft(caller: Caller, card: {
|
|
235
|
+
group: string;
|
|
236
|
+
tool: string;
|
|
237
|
+
args: unknown;
|
|
238
|
+
}): Promise<unknown>;
|
|
239
|
+
/** Takes back a Think chat's write made at once (`LumenAgent.undoWrite`) through its group's `undo`. */
|
|
240
|
+
undoChange(caller: Caller, card: {
|
|
241
|
+
group: string;
|
|
242
|
+
tool: string;
|
|
243
|
+
args: unknown;
|
|
244
|
+
result?: unknown;
|
|
245
|
+
}): Promise<void>;
|
|
230
246
|
/** The escalate tool of a Think turn: one task to a higher tier, its memo back. */
|
|
231
247
|
private agentEscalate;
|
|
232
248
|
status(caller: Caller): Promise<AssistantStatus>;
|
package/dist/assistant/engine.js
CHANGED
|
@@ -487,6 +487,40 @@ export class Assistant {
|
|
|
487
487
|
};
|
|
488
488
|
return { plan, settle };
|
|
489
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* Saves a Think chat's draft card (`LumenAgent.applyDraft`): its group's write runs as the person,
|
|
492
|
+
* while that group is still on Read + draft or Read + write.
|
|
493
|
+
*/
|
|
494
|
+
async runDraft(caller, card) {
|
|
495
|
+
const g = this.groupById(card.group);
|
|
496
|
+
const p = await this.state.person(caller.id);
|
|
497
|
+
const access = g ? this.access(p, g) : 'off';
|
|
498
|
+
if (access !== 'draft' && access !== 'write')
|
|
499
|
+
throw new AssistantError('Its tools are no longer on Read + draft or Read + write in Settings ▸ Tools.', 'off', 409);
|
|
500
|
+
const sources = (await this.sourcesOf(caller, p)).filter((x) => x.group === card.group);
|
|
501
|
+
for (const x of sources) {
|
|
502
|
+
const open = await x.source();
|
|
503
|
+
try {
|
|
504
|
+
const def = open.tools.find((d) => d.name === card.tool);
|
|
505
|
+
if (!def)
|
|
506
|
+
continue;
|
|
507
|
+
if (def.read)
|
|
508
|
+
throw new AssistantError('That card is not a change.', 'not_write', 400);
|
|
509
|
+
return await withDeadline(card.tool, 60_000, open.call(card.tool, card.args));
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
await open.close().catch(() => undefined);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
throw new AssistantError('That tool is gone.', 'not_found', 404);
|
|
516
|
+
}
|
|
517
|
+
/** Takes back a Think chat's write made at once (`LumenAgent.undoWrite`) through its group's `undo`. */
|
|
518
|
+
async undoChange(caller, card) {
|
|
519
|
+
const g = this.groupById(card.group);
|
|
520
|
+
if (!g?.undo)
|
|
521
|
+
throw new AssistantError('That change cannot be undone here.', 'no_undo', 400);
|
|
522
|
+
await withDeadline(`Undo ${card.tool}`, 60_000, g.undo(caller, { tool: card.tool, args: card.args, result: card.result }));
|
|
523
|
+
}
|
|
490
524
|
/** The escalate tool of a Think turn: one task to a higher tier, its memo back. */
|
|
491
525
|
agentEscalate(tt, c) {
|
|
492
526
|
const reach = this.above(c.main.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.179",
|
|
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",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"@ai-sdk/openai": "4.0.108",
|
|
49
49
|
"@anthropic-ai/sdk": "0.133.0",
|
|
50
50
|
"@cloudflare/think": "0.23.0",
|
|
51
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
51
|
+
"@columenlabs/lumen-ops": "0.1.179",
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.179",
|
|
53
53
|
"agents": "0.32.0",
|
|
54
54
|
"ai": "7.0.162",
|
|
55
55
|
"just-bash": "3.6.0",
|