@columenlabs/lumen-ai 0.1.246 → 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 +5 -1
- 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 +60 -12
- 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 +25 -12
- package/dist/assistant/state.d.ts +20 -1
- package/dist/assistant/state.js +58 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
- **Providers.** A tier runs any model in `models` (Claude's and OpenAI's by default, `CLAUDE_MODELS`, `OPENAI_MODELS`): Claude through Anthropic's Messages API, OpenAI through the Responses API, with the same tools, drafts, Stop and caps. xAI's Grok models (`XAI_MODELS`) are opt in: an app that lists them gets an xAI row in Model providers (an API key, `xai-…`); they run on the same Responses client at `XAI_BASE_URL` (`https://api.x.ai/v1`). xAI has no public sign-in for other apps, so Grok takes a key only. A hand-off may cross providers. The advisor and prompt caching marks are Claude's only.
|
|
109
|
-
- **Keys.** Each provider has its own keys (`?provider=anthropic|openai|xai` on the key routes). An admin pastes the shared key everyone uses (`PUT /key`); it is tested with one free call before it is kept, then sealed, and no route returns it. A person may add their own (`PUT /my-key`), with one monthly cap on their own keys (`PUT /my-key/cap`).
|
|
109
|
+
- **Keys.** Each provider has its own keys (`?provider=anthropic|openai|xai` on the key routes). An admin pastes the shared key everyone uses (`PUT /key`); it is tested with one free call before it is kept, then sealed, and no route returns it. A person may add their own (`PUT /my-key`), with one monthly cap on their own keys (`PUT /my-key/cap`). The cap counts what their own API keys spent this month on providers they can still pay with; a plan sign-in (ChatGPT) costs them nothing per call, so its calls count $0 (each own spend row records its `way`). Status gives that count as `spend.own_used`.
|
|
110
110
|
- **ChatGPT sign-in.** In place of their own OpenAI key a person may sign in with their ChatGPT plan (`POST /my-signin`, then `POST /my-signin/finish {url}`): the sign-in opens in a new tab and ends on `http://127.0.0.1:1455/auth/callback`, a page that does not load, whose address the person pastes back. The tokens are sealed as their own key and refreshed before they run out; answers on the plan count tokens and no dollars.
|
|
111
111
|
- **Who pays.** The person's pick (the shared keys by default) while it has a key for the tier's provider and money left this month under its cap (an admin sets the shared keys' cap per person, `PUT /app`), else the other; with neither the panel says why.
|
|
112
112
|
- **Tools.** Each group is Off, Read, Read + draft or Read + write per person. A tool is a read when its MCP `readOnlyHint` is set. On Read + draft a write only drafts a card, and runs as the person when they press Save, after access is checked again. A group with `readOnly: true` (read tools only) is offered Off or Read alone. Read + write is offered only for a group with `writes: true`: its writes run at once, and with `undo` set each one's line in the chat has Undo, which calls `undo(caller, { tool, args, result })`; the model is told what was undone. Cards drafted one after another show as one stack with Save all. Use `mcpServerTools` (an SDK `McpServer`), `mcpFetchTools` (a server served as a fetch handler, any SDK), `remoteMcpTools` or `plainTools`.
|
|
@@ -253,6 +253,10 @@ 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
|
+
|
|
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).
|
|
259
|
+
|
|
256
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.
|
|
257
261
|
|
|
258
262
|
**Steer and queue.** While an answer runs, the panel's box stays open: Enter steers the message into the answer (`steer(message)` on the agent; the app routes `POST /chats/:id/steer` `{ message }` to it and answers `{ steered }`), Alt+Enter queues it for after. A steer joins at the answer's next step, after the tool it is on, with `STEER_NOTE` before it for the model; the panel shows it there marked Steered (event `steered`), and `view()` keeps it in place (`SteerNote`, under the message the answer is to). A steer the answer ends before reading comes back as `unsteered` and goes first in the queue. `plan.steer: false` turns it off for a model; an app without the route (404) shows Queue only. Queued messages each go as their own message when the answer before them ends; each has Steer now, Edit and Remove.
|
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) => {
|
|
@@ -108,7 +131,21 @@ export function chunkEvents(o = {}) {
|
|
|
108
131
|
}
|
|
109
132
|
if (name === 'escalate' || name === 'screen' || o.kindOf?.(name) === 'draft')
|
|
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
|
|
135
|
+
// changes is being changed now, before the model has finished writing the call.
|
|
136
|
+
const kind = o.kindOf?.(name);
|
|
137
|
+
const write = kind === 'write';
|
|
138
|
+
return [
|
|
139
|
+
{
|
|
140
|
+
event: 'step_start',
|
|
141
|
+
data: {
|
|
142
|
+
id,
|
|
143
|
+
tool: name,
|
|
144
|
+
title: shownTitle(name, names, kind),
|
|
145
|
+
...(write ? { write } : {}),
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
112
149
|
}
|
|
113
150
|
case 'tool-input-available': {
|
|
114
151
|
const c = calls.get(id) ?? { name: String(chunk.toolName ?? ''), t0: Date.now() };
|
|
@@ -126,7 +163,18 @@ export function chunkEvents(o = {}) {
|
|
|
126
163
|
advising = false;
|
|
127
164
|
return [{ event: 'advice_done', data: error ? { error } : {} }];
|
|
128
165
|
}
|
|
129
|
-
|
|
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);
|
|
130
178
|
}
|
|
131
179
|
case 'error':
|
|
132
180
|
return [
|
|
@@ -144,7 +192,7 @@ export function chunkEvents(o = {}) {
|
|
|
144
192
|
}
|
|
145
193
|
};
|
|
146
194
|
}
|
|
147
|
-
export function itemsOf(messages, cards, screens = new Map(), steers = {}) {
|
|
195
|
+
export function itemsOf(messages, cards, screens = new Map(), steers = {}, names = {}) {
|
|
148
196
|
const byId = new Map(cards.map((c) => [c.id, c]));
|
|
149
197
|
const items = [];
|
|
150
198
|
let asked;
|
|
@@ -215,7 +263,7 @@ export function itemsOf(messages, cards, screens = new Map(), steers = {}) {
|
|
|
215
263
|
: null;
|
|
216
264
|
if (!ended)
|
|
217
265
|
continue;
|
|
218
|
-
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)) {
|
|
219
267
|
if (e.event === 'handoff')
|
|
220
268
|
items.push({ ...e.data, items: [] });
|
|
221
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
|
@@ -15,7 +15,7 @@ import { aiModel, turnSettings } from '../agent/models.js';
|
|
|
15
15
|
import { bearings } from './bearings.js';
|
|
16
16
|
import { CHATGPT_REDIRECT, SigninError, finishChatgpt, startChatgpt, testChatgpt, } from './chatgpt.js';
|
|
17
17
|
import { ALL_MODELS, DEFAULT_OUT, Meter, PROVIDERS, XAI_BASE_URL, effortParams, } from './models.js';
|
|
18
|
-
import { DEFAULT_CAP, State, keyDocName, keyState, planDocName, waitText, } from './state.js';
|
|
18
|
+
import { DEFAULT_CAP, State, keyDocName, keyState, ownKeyUsd, planDocName, waitText, withProvider, } from './state.js';
|
|
19
19
|
import { Docs } from './store.js';
|
|
20
20
|
import { toolName, withDeadline } from './tools.js';
|
|
21
21
|
import { scrapeqlTools, webFetchTools } from './web.js';
|
|
@@ -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),
|
|
@@ -388,7 +390,7 @@ export class Assistant {
|
|
|
388
390
|
const onShared = sharedLabel ? end.parts.filter((x) => x.label === sharedLabel) : [];
|
|
389
391
|
const onPayer = onShared.length ? end.parts.filter((x) => !onShared.includes(x)) : end.parts;
|
|
390
392
|
await this.state
|
|
391
|
-
.addSpend(caller.id, paying.payer, onPayer, { task: 'chat', ms: end.ms }, free)
|
|
393
|
+
.addSpend(caller.id, paying.payer, onPayer, { task: 'chat', ms: end.ms }, free, paying.plans)
|
|
392
394
|
.catch(() => undefined);
|
|
393
395
|
if (onShared.length)
|
|
394
396
|
await this.state
|
|
@@ -400,7 +402,7 @@ export class Assistant {
|
|
|
400
402
|
id: `chat:${input.chat}`,
|
|
401
403
|
kind: 'chat',
|
|
402
404
|
title: message.split('\n')[0] || 'Chat',
|
|
403
|
-
own_usd: paying
|
|
405
|
+
own_usd: ownKeyUsd(paying, onPayer),
|
|
404
406
|
}, end.parts)
|
|
405
407
|
.catch(() => undefined);
|
|
406
408
|
const total = end.parts.reduce((a, m) => ({
|
|
@@ -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
|
},
|
|
@@ -551,7 +565,7 @@ export class Assistant {
|
|
|
551
565
|
// The binding's last test has no token doc to sit in; it is read beside the rest.
|
|
552
566
|
const linkTest = this.o.web?.scrapeql?.fetch ? this.state.scrapeqlLink() : null;
|
|
553
567
|
const ownWays = await this.ownWaysOf(caller);
|
|
554
|
-
const [p, providers, scrapeql, spend, appCap, ownCap, { paying, wait }] = await Promise.all([
|
|
568
|
+
const [p, providers, scrapeql, spend, appCap, ownCap, { paying, wait }, ownUsed] = await Promise.all([
|
|
555
569
|
this.state.person(caller.id),
|
|
556
570
|
Promise.all(this.providers.map(async (id) => {
|
|
557
571
|
const [doc, key, own, plan, use] = await Promise.all([
|
|
@@ -584,6 +598,7 @@ export class Assistant {
|
|
|
584
598
|
this.state.appCap(),
|
|
585
599
|
this.state.ownCap(caller.id),
|
|
586
600
|
this.rulesFor(caller, main.id).then(({ rules }) => this.state.paying(caller.id, mainModel.provider, rules)),
|
|
601
|
+
this.state.ownUsed(caller.id, ownWays),
|
|
587
602
|
]);
|
|
588
603
|
const link = await linkTest;
|
|
589
604
|
const tiers = await Promise.all(this.tiers.map(async (t) => {
|
|
@@ -656,7 +671,8 @@ export class Assistant {
|
|
|
656
671
|
usd: spend.rows.reduce((a, r) => a + r.usd, 0),
|
|
657
672
|
cap: paying?.cap ?? null,
|
|
658
673
|
payer: paying?.payer ?? null,
|
|
659
|
-
rows: spend.rows,
|
|
674
|
+
rows: withProvider(spend.rows),
|
|
675
|
+
own_used: ownUsed,
|
|
660
676
|
},
|
|
661
677
|
...(this.o.allowance ? { allowance: await this.allowanceState(caller) } : {}),
|
|
662
678
|
};
|
|
@@ -1044,7 +1060,7 @@ export class Assistant {
|
|
|
1044
1060
|
}
|
|
1045
1061
|
finally {
|
|
1046
1062
|
const part = s.meter.part();
|
|
1047
|
-
await this.state.addSpend(caller.id, paying.payer, [part], { task: o.tag ?? o.task, ms: Date.now() - began }, free);
|
|
1063
|
+
await this.state.addSpend(caller.id, paying.payer, [part], { task: o.tag ?? o.task, ms: Date.now() - began }, free, paying.plans);
|
|
1048
1064
|
const task = this.tasks.find((t) => t.id === o.task);
|
|
1049
1065
|
await this.state
|
|
1050
1066
|
.addUsage(caller.id, {
|
|
@@ -1052,7 +1068,7 @@ export class Assistant {
|
|
|
1052
1068
|
kind: 'task',
|
|
1053
1069
|
task: o.task,
|
|
1054
1070
|
title: o.run?.title ?? task?.label ?? o.task,
|
|
1055
|
-
own_usd: paying
|
|
1071
|
+
own_usd: ownKeyUsd(paying, [part]),
|
|
1056
1072
|
}, [part])
|
|
1057
1073
|
.catch(() => undefined);
|
|
1058
1074
|
}
|
|
@@ -1132,10 +1148,7 @@ export class Assistant {
|
|
|
1132
1148
|
usd,
|
|
1133
1149
|
model: m.id,
|
|
1134
1150
|
};
|
|
1135
|
-
await this.state.addSpend(caller.id, paying.payer, [part], {
|
|
1136
|
-
task: o.task,
|
|
1137
|
-
ms: Date.now() - began,
|
|
1138
|
-
});
|
|
1151
|
+
await this.state.addSpend(caller.id, paying.payer, [part], { task: o.task, ms: Date.now() - began }, false, paying.plans);
|
|
1139
1152
|
const task = this.tasks.find((t) => t.id === o.task);
|
|
1140
1153
|
await this.state
|
|
1141
1154
|
.addUsage(caller.id, {
|
|
@@ -1143,7 +1156,7 @@ export class Assistant {
|
|
|
1143
1156
|
kind: 'task',
|
|
1144
1157
|
task: o.task,
|
|
1145
1158
|
title: o.run?.title ?? task?.label ?? o.task,
|
|
1146
|
-
own_usd: paying
|
|
1159
|
+
own_usd: ownKeyUsd(paying, [part]),
|
|
1147
1160
|
}, [part])
|
|
1148
1161
|
.catch(() => undefined);
|
|
1149
1162
|
}
|
|
@@ -99,7 +99,24 @@ export interface RequestsDoc {
|
|
|
99
99
|
export type SpendRow = UsagePart & {
|
|
100
100
|
payer: AssistantPayer;
|
|
101
101
|
free?: true;
|
|
102
|
+
/** Of an own row: paid by the person's API key or their plan sign-in (a plan costs them $0). */
|
|
103
|
+
way?: AssistantOwnWay;
|
|
102
104
|
};
|
|
105
|
+
/** The provider a part ran on: by its model id, else by the model's name at the end of its label. */
|
|
106
|
+
export declare function providerOfPart(p: UsagePart): AssistantProvider | undefined;
|
|
107
|
+
/** Spend rows with the provider each ran on, so a page can tell a removed key's spend apart. */
|
|
108
|
+
export declare function withProvider<R extends UsagePart>(rows: R[]): R[];
|
|
109
|
+
/** USD of `parts` the person's own API keys paid: a provider signed in with a plan costs $0. */
|
|
110
|
+
export declare function ownKeyUsd(paying: {
|
|
111
|
+
payer: AssistantPayer;
|
|
112
|
+
plans: Paying['plans'];
|
|
113
|
+
}, parts: UsagePart[]): number;
|
|
114
|
+
/**
|
|
115
|
+
* What counts toward the person's own cap: own rows paid by an API key (a plan sign-in costs
|
|
116
|
+
* nothing per call), on a provider they can still pay with. A row from before ways were recorded
|
|
117
|
+
* counts unless its provider has no own way left (an old key since removed).
|
|
118
|
+
*/
|
|
119
|
+
export declare function ownCapUsed(rows: SpendRow[], usable: Partial<Record<AssistantProvider, unknown>>): number;
|
|
103
120
|
/** The ways of their own a person may pay with, per provider; a provider left out has none. */
|
|
104
121
|
export type OwnWays = Partial<Record<AssistantProvider, AssistantOwnWay[]>>;
|
|
105
122
|
/** How `paying` may choose: the app's limits on a person's own keys, and one payer only. */
|
|
@@ -255,7 +272,7 @@ export declare class State {
|
|
|
255
272
|
addSpend(id: string, payer: AssistantPayer, parts: UsagePart[], at?: {
|
|
256
273
|
task: string;
|
|
257
274
|
ms?: number;
|
|
258
|
-
}, free?: boolean): Promise<void>;
|
|
275
|
+
}, free?: boolean, plans?: Partial<Record<AssistantProvider, boolean>>): Promise<void>;
|
|
259
276
|
/**
|
|
260
277
|
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
261
278
|
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only;
|
|
@@ -275,6 +292,8 @@ export declare class State {
|
|
|
275
292
|
keys: Partial<Record<"anthropic" | "openai" | "xai", string>>;
|
|
276
293
|
plans: Partial<Record<"anthropic" | "openai" | "xai", boolean>>;
|
|
277
294
|
}>;
|
|
295
|
+
/** USD of this month's own spend that counts toward the person's own cap (`ownCapUsed`). */
|
|
296
|
+
ownUsed(id: string, ways?: OwnWays): Promise<number>;
|
|
278
297
|
/** Who pays the person's next answer on `provider`'s model, or why nobody can. */
|
|
279
298
|
paying(id: string, provider: AssistantProvider, rules?: PayingRules): Promise<{
|
|
280
299
|
paying: Paying | null;
|
package/dist/assistant/state.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { refreshChatgpt, staleChatgpt } from './chatgpt.js';
|
|
2
|
-
import { PROVIDERS, keyHint } from './models.js';
|
|
2
|
+
import { ALL_MODELS, PROVIDERS, XAI_MODELS, keyHint } from './models.js';
|
|
3
3
|
import { APP_SCOPE } from './store.js';
|
|
4
4
|
/** Where a provider's key is kept: `key` for Anthropic, `key:<provider>` for the rest. */
|
|
5
5
|
export const keyDocName = (provider) => provider === 'anthropic' ? 'key' : `key:${provider}`;
|
|
@@ -7,6 +7,45 @@ export const keyDocName = (provider) => provider === 'anthropic' ? 'key' : `key:
|
|
|
7
7
|
export const planDocName = (provider) => `plan:${provider}`;
|
|
8
8
|
/** Requests kept for approvers: the newest this many. */
|
|
9
9
|
const REQUESTS_KEPT = 300;
|
|
10
|
+
const KNOWN_MODELS = [...ALL_MODELS, ...XAI_MODELS];
|
|
11
|
+
/** The provider a part ran on: by its model id, else by the model's name at the end of its label. */
|
|
12
|
+
export function providerOfPart(p) {
|
|
13
|
+
const m = KNOWN_MODELS.find((x) => x.id === p.model) ??
|
|
14
|
+
KNOWN_MODELS.find((x) => p.label === x.label || p.label.endsWith(` · ${x.label}`));
|
|
15
|
+
return m?.provider;
|
|
16
|
+
}
|
|
17
|
+
/** Spend rows with the provider each ran on, so a page can tell a removed key's spend apart. */
|
|
18
|
+
export function withProvider(rows) {
|
|
19
|
+
return rows.map((r) => {
|
|
20
|
+
const provider = providerOfPart(r);
|
|
21
|
+
return provider ? { ...r, provider } : r;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** USD of `parts` the person's own API keys paid: a provider signed in with a plan costs $0. */
|
|
25
|
+
export function ownKeyUsd(paying, parts) {
|
|
26
|
+
if (paying.payer !== 'own')
|
|
27
|
+
return 0;
|
|
28
|
+
return parts
|
|
29
|
+
.filter((p) => {
|
|
30
|
+
const prov = providerOfPart(p);
|
|
31
|
+
return !(prov && paying.plans[prov]);
|
|
32
|
+
})
|
|
33
|
+
.reduce((a, p) => a + p.usd, 0);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* What counts toward the person's own cap: own rows paid by an API key (a plan sign-in costs
|
|
37
|
+
* nothing per call), on a provider they can still pay with. A row from before ways were recorded
|
|
38
|
+
* counts unless its provider has no own way left (an old key since removed).
|
|
39
|
+
*/
|
|
40
|
+
export function ownCapUsed(rows, usable) {
|
|
41
|
+
return rows
|
|
42
|
+
.filter((r) => r.payer === 'own' && !r.free && r.way !== 'plan')
|
|
43
|
+
.filter((r) => {
|
|
44
|
+
const prov = providerOfPart(r);
|
|
45
|
+
return !prov || !!usable[prov];
|
|
46
|
+
})
|
|
47
|
+
.reduce((a, r) => a + r.usd, 0);
|
|
48
|
+
}
|
|
10
49
|
/** Chats and runs kept in one month's usage history: the newest this many. */
|
|
11
50
|
export const USAGE_KEPT = 300;
|
|
12
51
|
/** Months of usage history offered (`usage:YYYY-MM` documents are listed this far back). */
|
|
@@ -241,9 +280,16 @@ export class State {
|
|
|
241
280
|
* Adds an answer's use to this month's spend, by label and payer. `at` names the task the answer
|
|
242
281
|
* was for and how long it took, so the month also adds up per task and model (`tasks`).
|
|
243
282
|
*/
|
|
244
|
-
async addSpend(id, payer, parts, at, free = false) {
|
|
283
|
+
async addSpend(id, payer, parts, at, free = false, plans) {
|
|
245
284
|
if (!parts.some((p) => p.calls))
|
|
246
285
|
return;
|
|
286
|
+
// Which own way paid each part: a plan sign-in or a key, by the part's provider.
|
|
287
|
+
const wayOf = (p) => {
|
|
288
|
+
if (payer !== 'own' || !plans)
|
|
289
|
+
return undefined;
|
|
290
|
+
const prov = providerOfPart(p);
|
|
291
|
+
return prov && plans[prov] ? 'plan' : 'key';
|
|
292
|
+
};
|
|
247
293
|
await this.docs.update(id, `spend:${this.month()}`, (d) => {
|
|
248
294
|
const rows = [...(d?.rows ?? [])];
|
|
249
295
|
const tasks = [...(d?.tasks ?? [])];
|
|
@@ -277,7 +323,8 @@ export class State {
|
|
|
277
323
|
for (const p of parts) {
|
|
278
324
|
if (!p.calls)
|
|
279
325
|
continue;
|
|
280
|
-
const
|
|
326
|
+
const way = wayOf(p);
|
|
327
|
+
const i = rows.findIndex((r) => r.label === p.label && r.payer === payer && !!r.free === free && r.way === way);
|
|
281
328
|
const prev = i >= 0 ? rows[i] : undefined;
|
|
282
329
|
const row = prev
|
|
283
330
|
? {
|
|
@@ -288,7 +335,7 @@ export class State {
|
|
|
288
335
|
out: prev.out + p.out,
|
|
289
336
|
usd: prev.usd + p.usd,
|
|
290
337
|
}
|
|
291
|
-
: { ...p, payer, ...(free ? { free: true } : {}) };
|
|
338
|
+
: { ...p, payer, ...(free ? { free: true } : {}), ...(way ? { way } : {}) };
|
|
292
339
|
if (i >= 0)
|
|
293
340
|
rows[i] = row;
|
|
294
341
|
else
|
|
@@ -348,7 +395,7 @@ export class State {
|
|
|
348
395
|
month,
|
|
349
396
|
months,
|
|
350
397
|
usd: spend.rows.reduce((a, r) => a + r.usd, 0),
|
|
351
|
-
rows: spend.rows,
|
|
398
|
+
rows: withProvider(spend.rows),
|
|
352
399
|
entries: doc?.entries ?? [],
|
|
353
400
|
};
|
|
354
401
|
}
|
|
@@ -369,6 +416,11 @@ export class State {
|
|
|
369
416
|
});
|
|
370
417
|
return { keys, plans };
|
|
371
418
|
}
|
|
419
|
+
/** USD of this month's own spend that counts toward the person's own cap (`ownCapUsed`). */
|
|
420
|
+
async ownUsed(id, ways) {
|
|
421
|
+
const [{ keys }, spend] = await Promise.all([this.keysOf(id, 'own', ways), this.spend(id)]);
|
|
422
|
+
return ownCapUsed(spend.rows, keys);
|
|
423
|
+
}
|
|
372
424
|
/** Who pays the person's next answer on `provider`'s model, or why nobody can. */
|
|
373
425
|
async paying(id, provider, rules = {}) {
|
|
374
426
|
const [person, appKeys, ownKeys, spend, ownCap] = await Promise.all([
|
|
@@ -399,7 +451,7 @@ export class State {
|
|
|
399
451
|
apiKey: own,
|
|
400
452
|
...ownKeys,
|
|
401
453
|
cap: ownCap,
|
|
402
|
-
left: ownCap -
|
|
454
|
+
left: ownCap - ownCapUsed(spend.rows, ownKeys.keys),
|
|
403
455
|
}
|
|
404
456
|
: null,
|
|
405
457
|
};
|
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",
|