@columenlabs/lumen-ai 0.1.208 → 0.1.209

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 CHANGED
@@ -103,7 +103,7 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
103
103
  - **Hand off and advice.** The `escalate` tool hands one task to a higher tier (the person sets next, any or only when asked); Anthropic's advisor tool lets a higher tier send a plan, at most two an answer.
104
104
  - **Chats** run on the agent (`./agent`, `LumenAgent`, below): the app routes `POST /chat` and each chat's reads, cards and Undo to its own agent, and `assistantRoutes` answers `POST /chat` with 410 `moved`. Chats saved by the old turn loop stay readable (`GET /chats`, `GET|DELETE /chats/:id`) until `assistant.purge()` drops them 30 days after their last message.
105
105
  - **Screen moves**: while the person's Settings ▸ Screen is Move (the default), the panel sends its page map (`AssistantMessage.screen`: routes, nav pages, Settings sections, cards on screen, rows to open) and `LumenAgent` gives the model a `screen` tool (`./agent` `screen.ts`). Each call's moves (go, settings, chart, open, point) are checked against the map, kept in the chat and streamed as a `screen` event; the panel runs them through `AssistantScreenProvider` (mounted by `LumenShell`) with an Undo chip, and their outcomes go back with the next message (`outcomes`). Nothing is saved by a move.
106
- - **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, images?, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools (`images`, base64 with their media type, are read with the prompt: a scanned page, a screenshot), billed to their payer and counted in their spend and allowance like a chat turn. It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later.
106
+ - **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, images?, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools (`images`, base64 with their media type, are read with the prompt: a scanned page, a screenshot), billed to their payer and counted in their spend and allowance like a chat turn. `maxTokens` (default 1,000) goes up to `COMPLETE_MAX_TOKENS` (32,000, thinking included); the answer is streamed, so a long one keeps its connection, and `cut: true` says it stopped at the cap before it ended (the app can ask again, shorter, or continue). It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later.
107
107
 
108
108
  ### Commands, `@` and `#`
109
109
 
@@ -56,6 +56,8 @@ export interface AllowanceNotice {
56
56
  request: AllowanceRequest;
57
57
  unit: AllowanceUnit;
58
58
  }
59
+ /** The largest answer `complete` asks for, tokens (thinking included). Streamed, so minutes are fine. */
60
+ export declare const COMPLETE_MAX_TOKENS = 32000;
59
61
  /** A change a write tool made at once, as `undo` gets it. */
60
62
  export interface AssistantWrite {
61
63
  /** The tool's own name (not the `group__tool` the model calls). */
@@ -292,12 +294,14 @@ export declare class Assistant {
292
294
  prompt: string;
293
295
  /** Photos read with the prompt (a scanned page, a screenshot). */
294
296
  images?: CompleteImage[];
297
+ /** The answer's token cap, thinking included (default 1,000, at most COMPLETE_MAX_TOKENS). */
295
298
  maxTokens?: number;
296
299
  signal?: AbortSignal;
297
300
  }): Promise<{
298
301
  text: string;
299
302
  usd: number;
300
303
  model: string;
304
+ cut: boolean;
301
305
  }>;
302
306
  /** Starts a person's ChatGPT plan sign-in: the address to open in a new tab. */
303
307
  startSignin(caller: Caller, provider: AssistantProvider): Promise<{
@@ -23,6 +23,8 @@ const DOLLARS = { one: 'dollar', many: 'dollars', usd: 1 };
23
23
  const round2 = (n) => Math.round(n * 100) / 100;
24
24
  /** A request may ask for at most this many times the starter or monthly cap. */
25
25
  const REQUEST_MAX_TIMES = 100;
26
+ /** The largest answer `complete` asks for, tokens (thinking included). Streamed, so minutes are fine. */
27
+ export const COMPLETE_MAX_TOKENS = 32_000;
26
28
  /** A turn's failure the person can act on. */
27
29
  export class AssistantError extends Error {
28
30
  code;
@@ -887,7 +889,10 @@ export class Assistant {
887
889
  cache_read_input_tokens: read,
888
890
  }, plan);
889
891
  }
890
- return text || done?.output_text || '';
892
+ return {
893
+ text: text || done?.output_text || '',
894
+ cut: done?.status === 'incomplete' && done.incomplete_details?.reason === 'max_output_tokens',
895
+ };
891
896
  }
892
897
  // ---------------------------------------------------------------- one-shot work
893
898
  /**
@@ -904,15 +909,20 @@ export class Assistant {
904
909
  throw new AssistantError(waitText(code, s.model.provider, !!this.o.allowance), code, 402);
905
910
  }
906
911
  const clients = this.clients(paying);
907
- const max = Math.min(o.maxTokens ?? 1000, 8000);
912
+ const max = Math.min(o.maxTokens ?? 1000, COMPLETE_MAX_TOKENS);
908
913
  let text = '';
914
+ // The answer stopped at `max` before it ended (its thinking counts toward the cap too).
915
+ let cut = false;
909
916
  try {
910
917
  if ((s.model.provider === 'openai' && clients.openai) ||
911
918
  (s.model.provider === 'xai' && clients.xai)) {
912
- text = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal, o.images);
919
+ ({ text, cut } = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal, o.images));
913
920
  }
914
921
  else if (clients.anthropic) {
915
- const msg = await clients.anthropic.messages.create({
922
+ // Streamed and read whole: a long answer (minutes at a high cap) keeps its connection alive,
923
+ // and the SDK refuses a long answer as one unstreamed request.
924
+ const msg = await clients.anthropic.messages
925
+ .stream({
916
926
  model: s.model.id,
917
927
  max_tokens: max,
918
928
  system: o.system,
@@ -935,8 +945,10 @@ export class Assistant {
935
945
  },
936
946
  ],
937
947
  ...effortParams(s.effort),
938
- }, o.signal ? { signal: o.signal } : undefined);
948
+ }, o.signal ? { signal: o.signal } : undefined)
949
+ .finalMessage();
939
950
  s.meter.add(msg.usage);
951
+ cut = msg.stop_reason === 'max_tokens';
940
952
  text = msg.content
941
953
  .filter((b) => b.type === 'text')
942
954
  .map((b) => b.text)
@@ -953,7 +965,7 @@ export class Assistant {
953
965
  finally {
954
966
  await this.state.addSpend(caller.id, paying.payer, [s.meter.part()]);
955
967
  }
956
- return { text, usd: s.meter.usd, model: s.model.id };
968
+ return { text, usd: s.meter.usd, model: s.model.id, cut };
957
969
  }
958
970
  // ---------------------------------------------------------------- keys
959
971
  /** Starts a person's ChatGPT plan sign-in: the address to open in a new tab. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.208",
3
+ "version": "0.1.209",
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.208",
56
- "@columenlabs/lumen-ui-types": "0.1.208",
55
+ "@columenlabs/lumen-ops": "0.1.209",
56
+ "@columenlabs/lumen-ui-types": "0.1.209",
57
57
  "agents": "0.32.0",
58
58
  "ai": "7.0.162",
59
59
  "just-bash": "3.6.0",