@columenlabs/lumen-ai 0.1.144 → 0.1.146

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
@@ -102,6 +102,7 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
102
102
  - **Web.** `fetch` is a plain GET from the Worker (10 s, 2 MB, never the app's own hosts or private addresses). ScrapeQL's own tools join over its MCP API once an admin pastes a Cloudflare Access service token (`PUT /web/scrapeql`). A Worker on ScrapeQL's own Cloudflare account passes its service binding instead, `scrapeql: { url: 'https://scrapeql/mcp', fetch: env.SCRAPEQL.fetch.bind(env.SCRAPEQL) }`: no token is stored, the tools are always on and Settings shows the link with a Test.
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** stream as server-sent events (`POST /chat`); closing the request stops the answer and keeps what was said. Past 150k tokens, or on `/compact`, the history sent to the model becomes a summary. Run `assistant.purge()` once a day to drop chats 30 days after their last message.
105
+ - **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools, 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.
105
106
 
106
107
  ### Commands, `@` and `#`
107
108
 
@@ -241,6 +241,23 @@ export declare class Assistant {
241
241
  private loopClaude;
242
242
  /** Swaps the history for a short summary. False with nothing to compact. */
243
243
  private compact;
244
+ /**
245
+ * One prompt answered on a task's tier as the person: no chat, no tools. It is for background
246
+ * work an app runs for someone (a job's fit score, a summary), billed to their payer and capped
247
+ * like a chat turn, so it shows in their spend and allowance. Throws `AssistantError` with code
248
+ * `not_connected`, `app_cap` or `own_cap` when nobody can pay, or `upstream` when the model fails.
249
+ */
250
+ complete(caller: Caller, o: {
251
+ task: string;
252
+ system: string;
253
+ prompt: string;
254
+ maxTokens?: number;
255
+ signal?: AbortSignal;
256
+ }): Promise<{
257
+ text: string;
258
+ usd: number;
259
+ model: string;
260
+ }>;
244
261
  /** How the person's cards ended since the last answer, once each. */
245
262
  private cardNews;
246
263
  /** Starts a person's ChatGPT plan sign-in: the address to open in a new tab. */
@@ -1745,6 +1745,68 @@ export class Assistant {
1745
1745
  emit('compacted', {});
1746
1746
  return true;
1747
1747
  }
1748
+ // ---------------------------------------------------------------- one-shot work
1749
+ /**
1750
+ * One prompt answered on a task's tier as the person: no chat, no tools. It is for background
1751
+ * work an app runs for someone (a job's fit score, a summary), billed to their payer and capped
1752
+ * like a chat turn, so it shows in their spend and allowance. Throws `AssistantError` with code
1753
+ * `not_connected`, `app_cap` or `own_cap` when nobody can pay, or `upstream` when the model fails.
1754
+ */
1755
+ async complete(caller, o) {
1756
+ const s = await this.session(caller, await this.mainTier(caller, o.task));
1757
+ const { paying, wait } = await this.state.paying(caller.id, s.model.provider);
1758
+ if (!paying) {
1759
+ const code = wait ?? 'not_connected';
1760
+ throw new AssistantError(waitText(code, s.model.provider, !!this.o.allowance), code, 402);
1761
+ }
1762
+ const clients = this.clients(paying);
1763
+ const max = Math.min(o.maxTokens ?? 1000, 8000);
1764
+ let text = '';
1765
+ try {
1766
+ if (s.model.provider === 'openai' && clients.openai) {
1767
+ const res = await clients.openai.responses.create({
1768
+ model: s.model.id,
1769
+ instructions: o.system,
1770
+ input: o.prompt,
1771
+ store: false,
1772
+ max_output_tokens: max,
1773
+ }, o.signal ? { signal: o.signal } : undefined);
1774
+ const u = res.usage;
1775
+ if (u)
1776
+ s.meter.add({
1777
+ input_tokens: u.input_tokens - (u.input_tokens_details?.cached_tokens ?? 0),
1778
+ output_tokens: u.output_tokens,
1779
+ cache_read_input_tokens: u.input_tokens_details?.cached_tokens ?? 0,
1780
+ }, !!clients.openaiPlan);
1781
+ text = res.output_text ?? '';
1782
+ }
1783
+ else if (clients.anthropic) {
1784
+ const msg = await clients.anthropic.messages.create({
1785
+ model: s.model.id,
1786
+ max_tokens: max,
1787
+ system: o.system,
1788
+ messages: [{ role: 'user', content: o.prompt }],
1789
+ ...effortParams(s.effort),
1790
+ }, o.signal ? { signal: o.signal } : undefined);
1791
+ s.meter.add(msg.usage);
1792
+ text = msg.content
1793
+ .filter((b) => b.type === 'text')
1794
+ .map((b) => b.text)
1795
+ .join('');
1796
+ }
1797
+ else
1798
+ throw new AssistantError(waitText('not_connected', s.model.provider), 'not_connected', 402);
1799
+ }
1800
+ catch (e) {
1801
+ if (e instanceof AssistantError)
1802
+ throw e;
1803
+ throw new AssistantError(`${s.model.label} did not answer: ${String(e?.message ?? e).slice(0, 200)}`, 'upstream', 502);
1804
+ }
1805
+ finally {
1806
+ await this.state.addSpend(caller.id, paying.payer, [s.meter.part()]);
1807
+ }
1808
+ return { text, usd: s.meter.usd, model: s.model.id };
1809
+ }
1748
1810
  /** How the person's cards ended since the last answer, once each. */
1749
1811
  cardNews(doc) {
1750
1812
  const told = new Set(doc.told ?? []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.144",
3
+ "version": "0.1.146",
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",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "0.133.0",
44
- "@columenlabs/lumen-ops": "0.1.144",
45
- "@columenlabs/lumen-ui-types": "0.1.144",
44
+ "@columenlabs/lumen-ops": "0.1.146",
45
+ "@columenlabs/lumen-ui-types": "0.1.146",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },