@adriangalilea/utils 1.14.1 → 2.0.0

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
@@ -281,7 +281,8 @@ Every `SourcedError` carries `source`, `operation`, `status`, `context`, and the
281
281
  - **XDG**: XDG Base Directory paths — reads env vars set by [xdg-dirs](https://github.com/adriangalilea/xdg-dirs), falls back to spec defaults
282
282
  - **Unseen**: Persistent dedup filter — "what's new since last time?" for cron/monitoring workflows
283
283
  - **Project Discovery**: Find project/monorepo roots, detect JS/TS projects
284
- - **Bot plugins (GramIO)**: `kit` (graceful shutdown + admin context), `access-control` (gate + approve/deny menu, backed by sessions), `llm` (OpenAI-compat SSE parser `streamChat` + Telegram streaming output `ctx.startStream` + per-thread conversation history `ctx.llm`), `payments` (Telegram Stars: VIP tiers + credits + perks + waiver + refund flow, ToS-compliant, Spanish-autónomo-aware), `coalesce`, `language`, `menu`
284
+ - **LLM caller (`llm`)**: multi-provider failover on top of the Vercel AI SDK — priority ordering, per-key circuit breaker (KV-backed), reset events for live previews, usage accounting with actual billed cost (OpenRouter), tool calling. Worker-safe.
285
+ - **Bot plugins (GramIO)**: `kit` (graceful shutdown + admin context), `access-control` (gate + approve/deny menu, backed by sessions), `llm` (draft-streamed replies `streamChatReply` + per-thread conversation history `ctx.llm`), `payments` (Telegram Stars: VIP tiers + credits + perks + waiver + refund flow, ToS-compliant, Spanish-autónomo-aware), `coalesce`, `language`, `menu`
285
286
 
286
287
  ### XDG Base Directories
287
288
 
@@ -447,7 +448,8 @@ Then `pnpm install`. Every `ctx.send` / `ctx.sendDocument` / `ctx.reply` / etc.
447
448
  | `@adriangalilea/utils/bot/language` (picker surface) | The language-picker vocabulary and surfaces, so bots don't fork label lists: `flagFor` / `autonym` / `languageLabel` ("🇪🇸 Español"), `languagePickerItem({ label, codes, isActive, pick })` (the 2-up primary-highlighted MenuItem factory — storage and policy live in your closures), and `addLanguageRows(kb, { codes, pack, active?, activeStyle? })` (the raw-InlineKeyboard twin for onboarding / group-welcome keyboards; `activeStyle: "success"` when primary already marks something else on the keyboard). |
448
449
  | `@adriangalilea/utils/bot/user` | `userLabel(u)` — the conditional "[name] [@username] [id]" line every bot re-rolls for admin DMs and logs: `Ada Lovelace (@ada · 42)` / `Ada (42)` / `@ada (42)` / `id 42`, missing pieces drop instead of padding. Reads both gramio spellings (`firstName` / `first_name`); plain text by design. |
449
450
  | `@adriangalilea/utils/bot/coalesce` | Joins client-split inbound messages back into one. When a user pastes >4096 chars, Telegram clients fragment it into separate `message` updates with no marker. Middleware detects the burst and emits one combined event. |
450
- | `@adriangalilea/utils/bot/llm` | The full LLM-chatbot pipeline in one module. **Input:** `streamChat(response)` parses OpenAI-compatible SSE (OpenAI, vllm, mlx-lm, llama.cpp, Together, Groq, …) into a typed `AsyncGenerator<{type: 'content' \| 'reasoning', text}>`. **Output:** `ctx.startStream()` (low-level: debounced markdown to Telegram, 4000-char split, exposes `wasPartial` after `.end()`). `ctx.startChatStream(response)` (high-level: consumes the stream, renders reasoning as a Telegram `expandable_blockquote` entity + content as streamed markdown — both go through `markdownToFormattable` with graceful degradation — returns `{ content, reasoning }`). **History:** `llmHistory({...})` returns `.plugin` (decorates `ctx.llm` with `.add() / .get() / .clear() / .all() / .clearAll()`, per-(user, thread) OpenAI `ChatMessage` shape, persisted in the shared session record so the menu's 🗑 Forget wipes it automatically) AND `.menuItem` (drop-in "🗑 Delete this thread" for `botMenu` — wipes the LLM history AND calls `deleteForumTopic` so the Telegram thread + all its messages disappear from the chat; falls back to Redis-only clear when no `threadId` is present). |
451
+ | `@adriangalilea/utils/llm` | Multi-provider LLM caller on the Vercel AI SDK — the policy layer the SDK doesn't ship. `createLlm({ providers, health? })` → `.stream(req)` (typed events: `delta` / `reasoning` / `reset` / `tool-call` / `end`) and `.complete(req)`. **Failover:** priority-ordered across OpenAI-compatible, Anthropic-compatible, and OpenRouter endpoints; multiple keys per provider (health tracked per key fingerprint); per-key circuit breaker with exponential cool-down persisted in any KV-shaped `HealthStore`; an attempt that dies after emitting yields `reset` (discard, next candidate regenerates); empty completions count as failures. **Accounting:** tokens summed across billed attempts; `costUsd` is the ACTUAL charge (OpenRouter usage accounting via `providerMetadata`, or a `cost` field on the raw usage frame) — never a price-table estimate. **Knobs:** per-model temperature, maxTokens caps, `disableThinking` mapped to each dialect. **Tools:** AI SDK `tool()` + `toolChoice` pass through (re-exported). Worker-safe; peers: `ai`, `@ai-sdk/openai-compatible`, `@ai-sdk/anthropic`, `@openrouter/ai-sdk-provider`. |
452
+ | `@adriangalilea/utils/bot/llm` | The Telegram side of an LLM chatbot; the model side is `@adriangalilea/utils/llm`. **Output:** `streamChatReply(ctx, events, opts?)` consumes an `AsyncIterable<LlmStreamEvent>` and paints it with Telegram's native message-draft streaming (`sendMessageDraft` full-frame repaints, throttled ~1/s, keepalive under the ~30s draft TTL), then persists the finished markdown via `ctx.send`, entity-split across 4096 by `@gramio/split`. Reasoning models get a thinking phase — streams into the ephemeral draft and evaporates (`reasoning: 'preview'`, default), persists as an expandable blockquote (`'message'`), or never renders (`'hidden'`). Upstream `reset` (provider failover) repaints the draft from scratch. Drafts are private-chat-only (+ BotFather forum-topic mode); elsewhere the preview phase is skipped and only the final send happens. Returns `{ content, reasoning, toolCalls, usage, messages }`. **History:** `llmHistory({...})` returns `.plugin` (decorates `ctx.llm` with `.add() / .get() / .clear() / .all() / .clearAll()`, per-(user, thread) OpenAI `ChatMessage` shape, persisted in the shared session record so the menu's 🗑 Forget wipes it automatically) AND `.menuItem` (drop-in "🗑 Delete this thread" for `botMenu` — wipes the LLM history AND calls `deleteForumTopic` so the Telegram thread + all its messages disappear from the chat; falls back to history-only clear when no `threadId` is present). |
451
453
  | `@adriangalilea/utils/bot/menu` | `botMenu({ command, description, items, privacy?, personalData?, adminContact })` — `/settings` command + InlineKeyboard router. Root view always renders a `🛡️ Privacy & data` submenu button that wraps the privacy policy link plus (if `personalData: { storage }`) 🗑 Forget + 📥 Export buttons. Items take `keepRow` (render on the same row as the next item — e.g. a two-per-row language picker) and `rootExtra` (render at the bottom of the root menu, below Privacy & data). `label` / `header` / `style` / `visible` resolvers may be **async** (read your db at render time — never cache render strings in the session); `parseMode: 'HTML'` renders the header formatted (you own escaping); `personalData.onForget(ctx, userId)` wipes YOUR tables inside the same try as the session delete, so Forget either forgets everything or reports failure. `toggleMenuItem({ id, read, write, label: { off, on }, toast? })` — convenience factory for boolean-toggle items with dynamic label + optional toast, storage-agnostic via `read`/`write` closures. |
452
454
  | `@adriangalilea/utils/bot/payments` | `botPayments({ session, storage, paysupport, paysupportHint?, legal, waiver, vip?, credits?, perks? })` — Telegram Stars monetization in one drop-in plugin. **Three axes, all optional:** `vip` (positional tier ladder — single rung in v1 is just `vip: [{...}]`, ladder is `vip: [{...}, {...}]`; ids are `vip.1`, `vip.2`, …), `credits` (consumable balance + top-up packs `credits.1`, `credits.2`, …), `perks` (orthogonal one-shot unlocks `perks.<key>`). **Surface:** `ctx.payments.atLeast('vip')` / `atLeast('vip.2')` (typed rank check), `ctx.payments.tier()` / `.tier.level()` / `.tier.label()`, `ctx.payments.credits.{balance, consume, tryConsume}` (throws `InsufficientCredits`), `ctx.payments.has(perkId)`, `await ctx.payments.require('vip', { feature? })` (gate that sends a localized upgrade prompt deep-linked to `/settings → 💎 VIP`), `await ctx.payments.invoice(productKey)` (threads Art. 103(m) TRLGDCU consent inline before `sendInvoice`). **Owns:** waiver consent flow (versioned text → forces re-consent on bump, snapshotted on every charge for audit), `/paysupport` slash command (Telegram ToS §6.5; `paysupportHint` overrides the where-to-manage-charges line when your menu isn't `/settings`), idempotent `successful_payment` fulfillment via `pay:idempotency:{chargeId}` sentinel, lazy subscription expiry (no cron needed), tier upgrade auto-cancel of the lower rung's renewal, and admin-DM refund approval (mirror of `accessControl`'s [✅ Aprobar][❌ Denegar] pattern). **Returns:** `{ plugin, menuItem, payouts, onFulfilled }` — `menuItem` is the drop-in `💎 VIP` entry for `botMenu`; `payouts.{record, list, export, exportForUsers}` is the Fragment payout ledger (you receive TON, log the EUR conversion, export time-windowed CSV/JSON for your gestor); `onFulfilled(productKey \| '*', handler)` / `onRefunded(...)` register fire-and-forget hooks (purchase applied / admin-approved refund — a revenue ledger writes on one, reverses on the other). **Stars-only by design** — Telegram ToS §6.2 forbids third-party payment providers for digital goods. Crypto Pay deferred (MiCA risk); Stripe-outside-Telegram is a future v2 channel. Full compliance memo (Spanish-autónomo seller-of-record analysis, Verifactu vs Crea y Crece, MiCA, Art. 103(m) waiver text, GDPR retention) in `src/bot/payments/CLAUDE.md`. |
453
455
  | `@adriangalilea/utils/bot/create` | `createBot<S>({ token?, storage?, initial?, admins?, language?, menu?, access?, payments?, handlers?, worker? })` — the composer (see "One bot file, ideation → production" above). Returns `{ build, session, poll, isMain, fetch }`: `poll()` long-polls, `export default app` is a complete Worker, `app.session(ctx)` / the `handlers` callback's `session(ctx)` is the TYPED accessor for your `S` fields. Owns storage+session wiring; resolves storage per environment (D1 binding → `bot/storage-d1`; `BOT_PERSIST` path → sqlite, `redis://` → redis, lazily-imported optional peers; else announced-ephemeral memory) — or pass `storage: (env) => Storage` when the choice is env-dependent (e.g. a D1 binding not named `DB`). Boot NARRATES the composition (`session: memory …`, `features: language(en,es) · menu(/settings)`) so every implicit decision is visible where you're looking. Runnable demo: `pnpm demo:bot`. |
@@ -463,7 +465,8 @@ import { redisStorage } from '@gramio/storage-redis'
463
465
  import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
464
466
  import { botSession } from '@adriangalilea/utils/bot/session'
465
467
  import { accessControl } from '@adriangalilea/utils/bot/access-control'
466
- import { llmStream, llmHistory } from '@adriangalilea/utils/bot/llm'
468
+ import { createLlm } from '@adriangalilea/utils/llm'
469
+ import { streamChatReply, llmHistory, toModelMessages } from '@adriangalilea/utils/bot/llm'
467
470
 
468
471
  // Raw redis is fine — bot-id namespacing happens inside botSession +
469
472
  // every plugin via ctx.bot.info.id. Multiple bots sharing this Redis
@@ -472,29 +475,26 @@ const storage = redisStorage()
472
475
  const userSession = botSession({ storage, key: 'session', initial: () => ({}) })
473
476
  const chat = llmHistory({ session: userSession, maxTurns: 20, retentionDays: 7 })
474
477
 
478
+ // Any OpenAI-compatible endpoint: vllm-mlx, mlx-lm, llama.cpp, Together, Groq, OpenAI, …
479
+ // More entries = priority failover; a KV-shaped `health` store adds the circuit breaker.
480
+ const llm = createLlm({
481
+ providers: [{ id: 'local', type: 'openai', baseUrl: process.env.LLM_URL!, apiKey: 'none', defaultModel: process.env.LLM_MODEL! }],
482
+ })
483
+
475
484
  const bot = new Bot(process.env.BOT_TOKEN!)
476
485
  .extend(adminContext({ adminId: 190202471 })) // KEV.TELEGRAM_ADMIN_ID overrides
477
486
  .extend(userSession)
478
487
  .extend(accessControl({ session: userSession, storage, defaults: [] }))
479
- .extend(llmStream())
480
488
  .extend(chat.plugin)
481
489
  .on('message', async (ctx) => {
482
490
  if (!ctx.access.allowed) return
483
491
  ctx.llm.add({ role: 'user', content: ctx.text ?? '' })
484
492
 
485
- // Any OpenAI-compatible endpoint: vllm-mlx, mlx-lm, llama.cpp, Together, Groq, OpenAI, …
486
- const response = await fetch(process.env.LLM_URL!, {
487
- method: 'POST',
488
- headers: { 'Content-Type': 'application/json' },
489
- body: JSON.stringify({
490
- model: process.env.LLM_MODEL,
491
- messages: [{ role: 'system', content: 'You are helpful.' }, ...ctx.llm.get()],
492
- stream: true,
493
- }),
494
- })
495
-
496
- // High-level: handles reasoning (collapsed blockquote) + content streaming.
497
- const { content } = await ctx.startChatStream(response)
493
+ // Draft-streamed reply: thinking phase previews + final entity-split send.
494
+ const { content } = await streamChatReply(ctx, llm.stream({
495
+ instructions: 'You are helpful.',
496
+ messages: toModelMessages(ctx.llm.get()),
497
+ }))
498
498
  ctx.llm.add({ role: 'assistant', content })
499
499
  })
500
500
 
@@ -32,6 +32,9 @@ type MaybePromise<T> = T | Promise<T>;
32
32
  * per update. The FIRST id is primary (`ctx.adminId`); `ctx.isAdmin` is true for any id in the set.
33
33
  */
34
34
  export type Admins = number | readonly number[] | (() => MaybePromise<number | readonly number[]>);
35
+ /** Parse a comma/space-separated id list from an env string ("123, 456 789") into clean
36
+ * positive-integer ids — the raw material for an {@link Admins} source. Worker-safe. */
37
+ export declare function parseIdList(raw: string | undefined): number[];
35
38
  export declare const adminContext: (admins: Admins) => Plugin<{}, import("gramio").DeriveDefinitions & {
36
39
  global: {
37
40
  adminId: number;
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/bot/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,MAAM,GACf,MAAM,GACN,SAAS,MAAM,EAAE,GACjB,CAAC,MAAM,YAAY,CAAC,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAQpD,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM;;;;;MAsB1C,CAAC"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/bot/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC;;;GAGG;AACH,MAAM,MAAM,MAAM,GACf,MAAM,GACN,SAAS,MAAM,EAAE,GACjB,CAAC,MAAM,YAAY,CAAC,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAQpD;yFACyF;AACzF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAK7D;AAED,eAAO,MAAM,YAAY,GAAI,QAAQ,MAAM;;;;;MAsB1C,CAAC"}
package/dist/bot/admin.js CHANGED
@@ -28,6 +28,14 @@
28
28
  import { Plugin } from "gramio";
29
29
  /** Coerce any source value to a clean, positive-integer id array (junk writes screamed out). */
30
30
  const toIds = (v) => (typeof v === "number" ? [v] : v).filter((n) => typeof n === "number" && Number.isFinite(n) && n > 0);
31
+ /** Parse a comma/space-separated id list from an env string ("123, 456 789") into clean
32
+ * positive-integer ids — the raw material for an {@link Admins} source. Worker-safe. */
33
+ export function parseIdList(raw) {
34
+ return (raw ?? "")
35
+ .split(/[\s,]+/)
36
+ .map((s) => Number(s.trim()))
37
+ .filter((n) => Number.isFinite(n) && n > 0);
38
+ }
31
39
  export const adminContext = (admins) => {
32
40
  // Normalize every source shape to ONE async resolver. A static source resolves the array once;
33
41
  // a function is called on each update — that liveness is the whole point of the plugin.
@@ -1 +1 @@
1
- {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/bot/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAahC,gGAAgG;AAChG,MAAM,KAAK,GAAG,CAAC,CAA6B,EAAY,EAAE,CACzD,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3D,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAc,EAAE,EAAE;IAC9C,+FAA+F;IAC/F,wFAAwF;IACxF,MAAM,OAAO,GACZ,OAAO,MAAM,KAAK,UAAU;QAC3B,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,MAAM,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC,GAAG,EAAE;YACN,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1B,OAAO,KAAK,IAAI,EAAE,CAAC,GAAG,CAAC;QACxB,CAAC,CAAC,EAAE,CAAC;IAER,OAAO,IAAI,MAAM,CAAC,gCAAgC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACxE,MAAM,GAAG,GAAG,MAAM,OAAO,EAAE,CAAC;QAC5B,4FAA4F;QAC5F,2CAA2C;QAC3C,MAAM,QAAQ,GACb,UAAU,IAAI,GAAG,CAAC,CAAC,CAAE,GAA6B,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,OAAO;YACN,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACpB,OAAO,EAAE,OAAO,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC/D,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../../src/bot/admin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAahC,gGAAgG;AAChG,MAAM,KAAK,GAAG,CAAC,CAA6B,EAAY,EAAE,CACzD,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CACvC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAC3D,CAAC;AAEH;yFACyF;AACzF,MAAM,UAAU,WAAW,CAAC,GAAuB;IAClD,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;SAChB,KAAK,CAAC,QAAQ,CAAC;SACf,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;SAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAc,EAAE,EAAE;IAC9C,+FAA+F;IAC/F,wFAAwF;IACxF,MAAM,OAAO,GACZ,OAAO,MAAM,KAAK,UAAU;QAC3B,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,MAAM,EAAE,CAAC;QACnC,CAAC,CAAC,CAAC,GAAG,EAAE;YACN,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1B,OAAO,KAAK,IAAI,EAAE,CAAC,GAAG,CAAC;QACxB,CAAC,CAAC,EAAE,CAAC;IAER,OAAO,IAAI,MAAM,CAAC,gCAAgC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACxE,MAAM,GAAG,GAAG,MAAM,OAAO,EAAE,CAAC;QAC5B,4FAA4F;QAC5F,2CAA2C;QAC3C,MAAM,QAAQ,GACb,UAAU,IAAI,GAAG,CAAC,CAAC,CAAE,GAA6B,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;QACzE,OAAO;YACN,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;YACpB,OAAO,EAAE,OAAO,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC;SAC/D,CAAC;IACH,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC"}
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
8
8
  * import { accessControl } from '@adriangalilea/utils/bot/access-control'
9
- * import { llmStream } from '@adriangalilea/utils/bot/llm'
9
+ * import { streamChatReply, llmHistory } from '@adriangalilea/utils/bot/llm'
10
10
  *
11
11
  * Or all-in-one (pulls every subpath):
12
12
  * import { ... } from '@adriangalilea/utils/bot'
package/dist/bot/index.js CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * import { adminContext, gracefulStart } from '@adriangalilea/utils/bot/kit'
8
8
  * import { accessControl } from '@adriangalilea/utils/bot/access-control'
9
- * import { llmStream } from '@adriangalilea/utils/bot/llm'
9
+ * import { streamChatReply, llmHistory } from '@adriangalilea/utils/bot/llm'
10
10
  *
11
11
  * Or all-in-one (pulls every subpath):
12
12
  * import { ... } from '@adriangalilea/utils/bot'
package/dist/bot/llm.d.ts CHANGED
@@ -1,248 +1,86 @@
1
1
  /**
2
- * GramIO LLM toolkit — three primitives that together form a complete
3
- * Telegram LLM-chatbot pipeline:
2
+ * GramIO LLM toolkit — the Telegram side of an LLM chatbot. The model side
3
+ * (providers, failover, usage accounting, tools) lives in
4
+ * `@adriangalilea/utils/llm`; this module renders its event stream into a
5
+ * chat and remembers conversations:
4
6
  *
5
- * `streamChat(response)` — INPUT. Parses OpenAI-compatible SSE
6
- * (OpenAI, vllm, mlx-lm, llama.cpp,
7
- * Together, Groq, …) into a typed
8
- * `AsyncGenerator` of `{type, text}` with
9
- * `content` / `reasoning` separation.
10
- *
11
- * `ctx.startStream()` — OUTPUT (low-level). Debounced
12
- * `editMessageText` to Telegram, local
13
- * Markdown parse via `@gramio/format`,
14
- * 4000-char split on paragraph / line /
15
- * word boundary.
16
- *
17
- * `ctx.startChatStream()` — OUTPUT (high-level). Consumes a fetch
18
- * `Response` (or any `AsyncIterable<LLMChunk>`)
19
- * and renders BOTH phases of a thinking
20
- * model: `reasoning` chunks go into one
21
- * `<blockquote expandable>` message;
22
- * `content` chunks go into a streamed
23
- * markdown message below it. Returns
24
- * `{ content, reasoning }` once the
25
- * source generator ends.
7
+ * `streamChatReply(ctx, events)` — OUTPUT. Consumes an
8
+ * `AsyncIterable<LlmStreamEvent>` and paints it with Telegram's native
9
+ * message-draft streaming (`sendMessageDraft`: ephemeral ~30s previews,
10
+ * animated in place per draft_id), then persists the finished text via
11
+ * `ctx.send`, entity-split across the 4096 limit by `@gramio/split`.
12
+ * Reasoning models get a "thinking" phase: reasoning streams into the
13
+ * draft preview and evaporates by default, or persists as an expandable
14
+ * blockquote. A `reset` event (provider failover upstream) repaints the
15
+ * draft from scratch — full-frame previews make that one cheap frame.
16
+ * Drafts are a PRIVATE-chat capability (and the bot needs forum topic
17
+ * mode enabled in BotFather); elsewhere the preview phase is skipped and
18
+ * only the final send happens.
26
19
  *
27
20
  * `ctx.llm.add / .get / …` — HISTORY. Per-(user, thread) conversation
28
- * buffer in OpenAI `ChatMessage` shape.
29
- * Persisted in the shared `@gramio/session`
30
- * record under the `llm` field, so the
31
- * `botMenu` 🗑 Forget button wipes it
32
- * together with everything else (one
33
- * record, one delete, no per-plugin
34
- * registry).
35
- *
36
- * The trio composes:
21
+ * buffer in OpenAI `ChatMessage` shape, persisted in the shared
22
+ * `@gramio/session` record under the `llm` field, so the `botMenu`
23
+ * 🗑 Forget button wipes it together with everything else.
37
24
  *
38
- * fetch(...) ──streamChat──> chunks ──ctx.startStream──> Telegram
39
- * ▲ │
40
- * └──── ctx.llm.get() ◀──── ctx.llm.add(assistant) ◀──┘
25
+ * Peer deps: `gramio`, `@gramio/session`, `@gramio/format`, `@gramio/split`,
26
+ * `marked`.
41
27
  *
42
- * Peer deps: `gramio`, `@gramio/session`, `@gramio/format`, `marked`.
28
+ * @example chatbot turn: history → llm.stream → streamed reply
29
+ * import { createLlm } from '@adriangalilea/utils/llm'
30
+ * import { streamChatReply, llmHistory, toModelMessages } from '@adriangalilea/utils/bot/llm'
43
31
  *
44
- * @example high-level — thinking model in ~10 LOC
45
- * import { Bot } from 'gramio'
46
- * import { session } from '@gramio/session'
47
- * import { llmStream, llmHistory } from '@adriangalilea/utils/bot/llm'
48
- *
49
- * const userSession = session({ storage, key: 'session', initial: () => ({}) })
32
+ * const llm = createLlm({ providers })
50
33
  * const chat = llmHistory({ session: userSession, maxTurns: 20, retentionDays: 7 })
51
34
  *
52
- * const bot = new Bot(process.env.BOT_TOKEN!)
53
- * .extend(userSession)
54
- * .extend(llmStream())
55
- * .extend(chat.plugin)
56
- * .on('message', async (ctx) => {
57
- * ctx.llm.add({ role: 'user', content: ctx.text ?? '' })
58
- * const response = await fetch(process.env.LLM_URL!, {
59
- * method: 'POST',
60
- * headers: { 'Content-Type': 'application/json' },
61
- * body: JSON.stringify({
62
- * model: process.env.LLM_MODEL,
63
- * messages: [{ role: 'system', content: 'You are helpful.' }, ...ctx.llm.get()],
64
- * stream: true,
65
- * }),
66
- * })
67
- * const { content } = await ctx.startChatStream(response)
68
- * ctx.llm.add({ role: 'assistant', content })
69
- * })
70
- *
71
- * bot.start()
72
- *
73
- * @example low-level — manual loop when you need to map/filter chunks
74
- * import { streamChat } from '@adriangalilea/utils/bot/llm'
75
- *
76
- * const stream = ctx.startStream()
77
- * for await (const chunk of streamChat(response)) {
78
- * if (chunk.type === 'content') await stream.append(chunk.text)
79
- * // reasoning chunks dropped here
80
- * }
81
- * await stream.end()
35
+ * bot.extend(userSession).extend(chat.plugin).on('message', async (ctx) => {
36
+ * ctx.llm.add({ role: 'user', content: ctx.text ?? '' })
37
+ * const { content } = await streamChatReply(ctx, llm.stream({
38
+ * instructions: 'You are helpful.',
39
+ * messages: toModelMessages(ctx.llm.get()),
40
+ * }))
41
+ * ctx.llm.add({ role: 'assistant', content })
42
+ * })
82
43
  */
83
44
  import type { session } from "@gramio/session";
84
- import { type DeriveDefinitions, Plugin } from "gramio";
45
+ import { type Bot, type DeriveDefinitions, type MessageContext, Plugin } from "gramio";
46
+ import type { LlmStreamEvent, LlmToolCall, LlmUsage, ModelMessage } from "../llm/index.js";
85
47
  import type { Polyglot } from "../say/index.js";
86
48
  import type { MenuItem } from "./menu.js";
87
- /**
88
- * A single chunk yielded by `streamChat`. Two kinds:
89
- *
90
- * - `content` — the visible reply text the user should see
91
- * - `reasoning` — chain-of-thought / "thinking" text from reasoning
92
- * models. Empty unless the model emits it.
93
- *
94
- * Most callers care about `content` only. Render `reasoning` separately
95
- * (collapsed, italicized) if you want to surface thinking.
96
- */
97
- export type LLMChunk = {
98
- type: "content";
99
- text: string;
100
- } | {
101
- type: "reasoning";
102
- text: string;
103
- };
104
- /**
105
- * Parse an OpenAI-compatible chat-completions SSE response into a
106
- * typed `AsyncGenerator<LLMChunk>`. Reads `response.body` once.
107
- *
108
- * Recognised reasoning aliases (as of 2026): `reasoning_content`
109
- * (vllm, qwen3, DeepSeek-R1, gpt-oss harmony) and `reasoning` (some
110
- * mlx-lm forks, gemma builds). If a model surfaces a new key, add it
111
- * here — single source of truth for the field.
112
- *
113
- * Constrained-SSE assumption: lines are `\n`-delimited and each event
114
- * is `data: <json>` or `data: [DONE]`. This matches every OpenAI-compat
115
- * server in the wild but is NOT the full SSE spec (no comments, no
116
- * multi-line `data:`, no `retry`/`id` fields). Swap to
117
- * `eventsource-parser` if you hit a producer that needs them.
118
- *
119
- * Malformed JSON lines are silently skipped; the generator ends when
120
- * the stream closes.
121
- *
122
- * @param response the `fetch` `Response` from a `stream: true` chat
123
- * completion call. Must not be already consumed.
124
- * @throws if `response.body` is null (non-streaming response).
125
- *
126
- * @example framework-agnostic — parser doesn't know about Telegram
127
- * const res = await fetch(url, { method: 'POST', body })
128
- * for await (const chunk of streamChat(res)) {
129
- * if (chunk.type === 'content') process.stdout.write(chunk.text)
130
- * }
131
- */
132
- export declare function streamChat(response: Response): AsyncGenerator<LLMChunk>;
133
- export type StreamOptions = {
134
- /** Debounce window between edits, in ms. Default 800. */
135
- debounceMs?: number;
136
- /** Initial placeholder shown until the first chunk arrives. Default "…". */
137
- placeholder?: string;
138
- /** Parse buffer as markdown. Default true. Set false for plain text streaming. */
139
- markdown?: boolean;
140
- /** Called on edit/send errors after internal recovery (rate limits, etc.). */
141
- onError?: (err: unknown) => void;
142
- };
143
- export declare class MarkdownStreamer {
144
- private buffer;
145
- private currentMessageId?;
146
- private firstSendPromise?;
147
- private debounceTimer?;
148
- private inFlight;
149
- private dirty;
150
- private ended;
49
+ export interface StreamChatReplyOptions {
151
50
  /**
152
- * `true` after `.end()` returns if the stream finished with un-flushed
153
- * buffer or an outstanding error that the streamer couldn't recover
154
- * from. Useful for partial-response diagnostics in catch blocks:
155
- *
156
- * try { for await (...) await stream.append(...) }
157
- * finally {
158
- * await stream.end()
159
- * if (stream.wasPartial) logger.warn('LLM stream cut off')
160
- * }
51
+ * What happens to a reasoning model's thinking text.
52
+ * `preview` (default) — streams into the ephemeral draft, evaporates when
53
+ * the answer starts. `message` — additionally persists as an expandable
54
+ * blockquote message before the answer. `hidden` — never rendered.
161
55
  */
162
- wasPartial: boolean;
163
- private chatId;
164
- private threadId?;
165
- private bot;
166
- private opts;
167
- constructor(ctx: {
168
- chat: {
169
- id: number;
170
- };
171
- threadId?: number;
172
- bot: MarkdownStreamer["bot"];
173
- }, opts: StreamOptions);
174
- /** Append a chunk. Schedules a debounced edit. */
175
- append(text: string): Promise<void>;
176
- /** Flush any pending edit and close the stream. Idempotent. */
177
- end(): Promise<void>;
178
- private scheduleFlush;
179
- private flushNow;
56
+ reasoning?: "preview" | "message" | "hidden";
57
+ /** Ms between draft repaints. Default 1000. */
58
+ throttleMs?: number;
59
+ /**
60
+ * Extra params for the finalizing `ctx.send` calls (e.g. `reply_markup`).
61
+ * Applied to the LAST part when the reply splits across messages, so a
62
+ * keyboard lands at the end.
63
+ */
64
+ messageParams?: Parameters<MessageContext<Bot>["send"]>[1];
180
65
  }
181
- /**
182
- * GramIO plugin. Adds `ctx.startStream(opts?)` on every message context.
183
- *
184
- * Defaults set here apply to every stream; per-call options in
185
- * `ctx.startStream({...})` override them.
186
- */
187
- export declare const llmStream: (defaults?: StreamOptions) => Plugin<{}, DeriveDefinitions & {
188
- message: {
189
- startStream: (opts?: StreamOptions) => MarkdownStreamer;
190
- /**
191
- * One-call helper for chat completions: consumes a `Response`
192
- * (or any `AsyncIterable<LLMChunk>`), renders reasoning to an
193
- * expandable blockquote message + content to a streamed markdown
194
- * message, returns `{ content, reasoning }` when the stream ends.
195
- */
196
- startChatStream: (source: Response | AsyncIterable<LLMChunk>, opts?: ChatStreamOptions) => Promise<ChatStreamResult>;
197
- };
198
- }, {}>;
199
- /**
200
- * Return shape of `consumeChatStream` / `ctx.startChatStream`. Both
201
- * full transcripts are returned so the caller decides what to persist
202
- * (typically `content` to `ctx.llm`; `reasoning` already lives in the
203
- * Telegram message and is rarely worth storing).
204
- */
205
- export type ChatStreamResult = {
206
- /** Full assistant text concatenated from all `content` chunks. */
66
+ export interface ChatReplyResult {
67
+ /** Full assistant markdown, concatenated from `delta` events. */
207
68
  content: string;
208
- /** Full reasoning text concatenated from all `reasoning` chunks. Empty for non-thinking models. */
69
+ /** Full reasoning text. Empty for non-thinking models. */
209
70
  reasoning: string;
210
- };
211
- export type ChatStreamOptions = StreamOptions;
71
+ /** Tool calls the model made (the caller executes them; nothing is rendered). */
72
+ toolCalls: LlmToolCall[];
73
+ /** End-of-stream usage accounting, when the provider reported it. */
74
+ usage: LlmUsage | null;
75
+ /** The persisted message(s), in order. Empty when the model produced no text (tool-call-only turns). */
76
+ messages: MessageContext<Bot>[];
77
+ }
212
78
  /**
213
- * One-shot helper that consumes an LLM stream and renders BOTH phases
214
- * (reasoning, content) to Telegram with the canonical pattern:
215
- *
216
- * - reasoning chunks → one message containing a single
217
- * `<blockquote expandable>…</blockquote>` (HTML), debounced edits,
218
- * stays expanded after the stream ends so the user can review it
219
- * collapsed/uncollapsed at will
220
- * - content chunks → a fresh `MarkdownStreamer` message below the
221
- * reasoning one (auto-threaded via SendMixin)
222
- *
223
- * Returns when the source generator ends. Equivalent to (and replaces)
224
- * the ~50 LOC of bookkeeping every thinking-model bot would otherwise
225
- * write by hand around `streamChat` + `ctx.startStream`.
226
- *
227
- * Accepts either a raw `fetch` `Response` (wrapped with `streamChat`
228
- * internally) or an `AsyncIterable<LLMChunk>` (when you want to map /
229
- * filter chunks before the stream hits Telegram).
230
- *
231
- * @example via ctx.startChatStream from the llmStream plugin
232
- * const response = await fetch(LLM_URL, { method: 'POST', body })
233
- * const { content } = await ctx.startChatStream(response)
234
- * ctx.llm.add({ role: 'assistant', content })
235
- *
236
- * @example standalone, framework-agnostic
237
- * const { content, reasoning } = await consumeChatStream(ctx, response)
79
+ * Render an LLM event stream into the chat: live draft previews while
80
+ * generating, entity-split persisted message(s) when done. See the module doc
81
+ * for the full contract. Returns the transcript pieces plus the sent messages.
238
82
  */
239
- export declare const consumeChatStream: (ctx: {
240
- chat: {
241
- id: number;
242
- };
243
- threadId?: number;
244
- bot: MarkdownStreamer["bot"];
245
- }, source: Response | AsyncIterable<LLMChunk>, opts?: ChatStreamOptions) => Promise<ChatStreamResult>;
83
+ export declare function streamChatReply(ctx: MessageContext<Bot>, events: AsyncIterable<LlmStreamEvent>, opts?: StreamChatReplyOptions): Promise<ChatReplyResult>;
246
84
  /**
247
85
  * Multimodal content shape from OpenAI's chat-completions spec. Either
248
86
  * a plain string or an ordered array of typed parts. Image URLs cover
@@ -269,6 +107,13 @@ export type ChatMessage = {
269
107
  /** Unix seconds when added — used for retention pruning. */
270
108
  date: number;
271
109
  };
110
+ /**
111
+ * Convert stored history into the AI SDK's `ModelMessage` shape for
112
+ * `llm.stream({ messages })`. Text and image parts map 1:1; `tool` turns are
113
+ * dropped (tool plumbing belongs to the current request, not replayed
114
+ * history); an assistant turn's parts flatten to text.
115
+ */
116
+ export declare function toModelMessages(history: ReadonlyArray<ChatMessage>): ModelMessage[];
272
117
  /** Per-thread shards of `ChatMessage`s, persisted in the session. */
273
118
  type ChatRecord = {
274
119
  shards: {
@@ -325,7 +170,7 @@ export type LLMHistoryFeature = {
325
170
  * thread" button that BOTH wipes `ctx.llm` for the current
326
171
  * (user, thread) shard AND calls `deleteForumTopic` to remove the
327
172
  * Telegram thread (and all its messages) from the chat. Sibling
328
- * threads stay intact. Falls back to Redis-only clear when there is
173
+ * threads stay intact. Falls back to history-only clear when there is
329
174
  * no `threadId` (general/non-threaded chats) or Telegram rejects
330
175
  * the deletion (e.g. forum supergroup without admin rights).
331
176
  */
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/bot/llm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/bot/llm.ts"],"names":[],"mappings":"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"}