@prajwalshete/telegram-plugin 0.0.0-stage → 0.1.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.
Files changed (295) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +109 -2
  4. package/dist/adapters/clock.d.ts +3 -0
  5. package/dist/adapters/clock.js +4 -0
  6. package/dist/adapters/console-log.d.ts +13 -0
  7. package/dist/adapters/console-log.js +41 -0
  8. package/dist/adapters/d1-db.d.ts +5 -0
  9. package/dist/adapters/d1-db.js +37 -0
  10. package/dist/adapters/ids.d.ts +5 -0
  11. package/dist/adapters/ids.js +25 -0
  12. package/dist/adapters/index.d.ts +7 -0
  13. package/dist/adapters/keyring.d.ts +50 -0
  14. package/dist/adapters/keyring.js +174 -0
  15. package/dist/adapters/metrics.d.ts +28 -0
  16. package/dist/adapters/metrics.js +48 -0
  17. package/dist/adapters/r2-files.d.ts +20 -0
  18. package/dist/adapters/r2-files.js +53 -0
  19. package/dist/bin/cli.d.ts +1 -0
  20. package/dist/bin/cli.js +45 -0
  21. package/dist/client/broadcasts.d.ts +35 -0
  22. package/dist/client/commands.d.ts +23 -0
  23. package/dist/client/endpoint.d.ts +34 -0
  24. package/dist/client/events.d.ts +33 -0
  25. package/dist/client/index.d.ts +11 -0
  26. package/dist/client/index.js +3 -0
  27. package/dist/client/schedules.d.ts +42 -0
  28. package/dist/client/schema.d.ts +181 -0
  29. package/dist/client/send.d.ts +15 -0
  30. package/dist/client/templates.d.ts +47 -0
  31. package/dist/contracts/bots.d.ts +129 -0
  32. package/dist/contracts/bots.js +70 -0
  33. package/dist/contracts/broadcasts.d.ts +116 -0
  34. package/dist/contracts/broadcasts.js +63 -0
  35. package/dist/contracts/chats.d.ts +97 -0
  36. package/dist/contracts/chats.js +66 -0
  37. package/dist/contracts/commands.d.ts +52 -0
  38. package/dist/contracts/commands.js +53 -0
  39. package/dist/contracts/common.d.ts +38 -0
  40. package/dist/contracts/common.js +41 -0
  41. package/dist/contracts/deliveries.d.ts +103 -0
  42. package/dist/contracts/deliveries.js +74 -0
  43. package/dist/contracts/destinations.d.ts +24 -0
  44. package/dist/contracts/destinations.js +28 -0
  45. package/dist/contracts/events.d.ts +165 -0
  46. package/dist/contracts/events.js +98 -0
  47. package/dist/contracts/features.d.ts +20 -0
  48. package/dist/contracts/features.js +21 -0
  49. package/dist/contracts/health.d.ts +55 -0
  50. package/dist/contracts/health.js +58 -0
  51. package/dist/contracts/index.d.ts +22 -0
  52. package/dist/contracts/index.js +22 -0
  53. package/dist/contracts/labels.d.ts +38 -0
  54. package/dist/contracts/labels.js +52 -0
  55. package/dist/contracts/links.d.ts +51 -0
  56. package/dist/contracts/links.js +50 -0
  57. package/dist/contracts/meta.d.ts +132 -0
  58. package/dist/contracts/meta.js +85 -0
  59. package/dist/contracts/permissions.d.ts +24 -0
  60. package/dist/contracts/permissions.js +27 -0
  61. package/dist/contracts/problems.d.ts +106 -0
  62. package/dist/contracts/problems.js +101 -0
  63. package/dist/contracts/recipients.d.ts +36 -0
  64. package/dist/contracts/recipients.js +22 -0
  65. package/dist/contracts/schedules.d.ts +276 -0
  66. package/dist/contracts/schedules.js +143 -0
  67. package/dist/contracts/send.d.ts +126 -0
  68. package/dist/contracts/send.js +69 -0
  69. package/dist/contracts/subscriptions.d.ts +38 -0
  70. package/dist/contracts/subscriptions.js +31 -0
  71. package/dist/contracts/templates.d.ts +358 -0
  72. package/dist/contracts/templates.js +174 -0
  73. package/dist/contracts/users.d.ts +15 -0
  74. package/dist/contracts/users.js +19 -0
  75. package/dist/core/guards.d.ts +22 -0
  76. package/dist/core/guards.js +47 -0
  77. package/dist/core/html/escape.d.ts +2 -0
  78. package/dist/core/html/escape.js +36 -0
  79. package/dist/core/html/tokenize.js +65 -0
  80. package/dist/core/index.d.ts +18 -0
  81. package/dist/core/pacing.d.ts +1 -0
  82. package/dist/core/pacing.js +86 -0
  83. package/dist/core/random.d.ts +2 -0
  84. package/dist/core/random.js +38 -0
  85. package/dist/core/recipients.d.ts +1 -0
  86. package/dist/core/recipients.js +86 -0
  87. package/dist/core/schedule/describe.d.ts +3 -0
  88. package/dist/core/schedule/describe.js +74 -0
  89. package/dist/core/schedule/index.d.ts +4 -0
  90. package/dist/core/schedule/policy.d.ts +1 -0
  91. package/dist/core/schedule/policy.js +37 -0
  92. package/dist/core/schedule/rule.d.ts +25 -0
  93. package/dist/core/schedule/rule.js +142 -0
  94. package/dist/core/schedule/tz.d.ts +2 -0
  95. package/dist/core/schedule/tz.js +59 -0
  96. package/dist/core/split/plain.d.ts +2 -0
  97. package/dist/core/split/plain.js +23 -0
  98. package/dist/core/split/split.d.ts +15 -0
  99. package/dist/core/split/split.js +158 -0
  100. package/dist/core/template/ast.d.ts +58 -0
  101. package/dist/core/template/ast.js +5 -0
  102. package/dist/core/template/expr.js +108 -0
  103. package/dist/core/template/filters.d.ts +20 -0
  104. package/dist/core/template/filters.js +127 -0
  105. package/dist/core/template/format.d.ts +17 -0
  106. package/dist/core/template/format.js +88 -0
  107. package/dist/core/template/index.d.ts +20 -0
  108. package/dist/core/template/index.js +29 -0
  109. package/dist/core/template/parse.d.ts +8 -0
  110. package/dist/core/template/parse.js +369 -0
  111. package/dist/core/template/render.d.ts +17 -0
  112. package/dist/core/template/render.js +118 -0
  113. package/dist/core/template/shape.d.ts +28 -0
  114. package/dist/core/template/shape.js +151 -0
  115. package/dist/core/template/tags.js +105 -0
  116. package/dist/core/template/validate.d.ts +10 -0
  117. package/dist/core/template/validate.js +144 -0
  118. package/dist/core/template/variables.d.ts +15 -0
  119. package/dist/core/template/variables.js +41 -0
  120. package/dist/do/index.d.ts +3 -0
  121. package/dist/do/index.js +3 -0
  122. package/dist/do/telegram-bot-do.d.ts +45 -0
  123. package/dist/do/telegram-bot-do.js +219 -0
  124. package/dist/engine/actions/callback.js +256 -0
  125. package/dist/engine/actions/tokens.d.ts +2 -0
  126. package/dist/engine/actions/tokens.js +51 -0
  127. package/dist/engine/bot-api.js +20 -0
  128. package/dist/engine/bot-features.js +39 -0
  129. package/dist/engine/chat-state.js +66 -0
  130. package/dist/engine/content/audience.js +54 -0
  131. package/dist/engine/content/buttons.d.ts +1 -0
  132. package/dist/engine/content/buttons.js +113 -0
  133. package/dist/engine/content/fanout.js +284 -0
  134. package/dist/engine/content/intersect.d.ts +6 -0
  135. package/dist/engine/content/intersect.js +26 -0
  136. package/dist/engine/content/providers.d.ts +6 -0
  137. package/dist/engine/content/providers.js +142 -0
  138. package/dist/engine/content/recipients.js +128 -0
  139. package/dist/engine/content/render.js +138 -0
  140. package/dist/engine/content/reply.js +156 -0
  141. package/dist/engine/content/report-files.js +78 -0
  142. package/dist/engine/content/template-rows.js +74 -0
  143. package/dist/engine/dialogs/dialog.d.ts +2 -0
  144. package/dist/engine/dialogs/dialog.js +375 -0
  145. package/dist/engine/dispatch/builtins.js +198 -0
  146. package/dist/engine/dispatch/commands.js +194 -0
  147. package/dist/engine/dispatch/context.d.ts +2 -0
  148. package/dist/engine/dispatch/context.js +194 -0
  149. package/dist/engine/dispatch/dispatcher.js +66 -0
  150. package/dist/engine/dispatch/handlers.d.ts +1 -0
  151. package/dist/engine/dispatch/handlers.js +103 -0
  152. package/dist/engine/dispatch/lifecycle.js +85 -0
  153. package/dist/engine/dispatch/link-chat.js +31 -0
  154. package/dist/engine/dispatch/linking.js +273 -0
  155. package/dist/engine/errors.d.ts +60 -0
  156. package/dist/engine/errors.js +72 -0
  157. package/dist/engine/events/filters.d.ts +5 -0
  158. package/dist/engine/events/filters.js +82 -0
  159. package/dist/engine/events/kick.js +30 -0
  160. package/dist/engine/events/payload.d.ts +3 -0
  161. package/dist/engine/events/payload.js +16 -0
  162. package/dist/engine/events/tick.js +208 -0
  163. package/dist/engine/i18n.d.ts +12 -0
  164. package/dist/engine/i18n.js +55 -0
  165. package/dist/engine/inbound/files.js +121 -0
  166. package/dist/engine/inbound/handler.js +46 -0
  167. package/dist/engine/jobs.d.ts +32 -0
  168. package/dist/engine/jobs.js +85 -0
  169. package/dist/engine/link-codes.js +46 -0
  170. package/dist/engine/metrics.js +12 -0
  171. package/dist/engine/options.d.ts +369 -0
  172. package/dist/engine/options.js +81 -0
  173. package/dist/engine/outbox/dead-letter.js +78 -0
  174. package/dist/engine/outbox/do-registry.d.ts +10 -0
  175. package/dist/engine/outbox/do-registry.js +10 -0
  176. package/dist/engine/outbox/do-transport.d.ts +33 -0
  177. package/dist/engine/outbox/do-transport.js +73 -0
  178. package/dist/engine/outbox/drain.d.ts +13 -0
  179. package/dist/engine/outbox/drain.js +381 -0
  180. package/dist/engine/outbox/enqueue.d.ts +1 -0
  181. package/dist/engine/outbox/enqueue.js +63 -0
  182. package/dist/engine/outbox/select.js +24 -0
  183. package/dist/engine/outbox/send.js +362 -0
  184. package/dist/engine/outbox/transport.d.ts +35 -0
  185. package/dist/engine/outbox/transport.js +175 -0
  186. package/dist/engine/outbox/wake.js +28 -0
  187. package/dist/engine/registry.d.ts +502 -0
  188. package/dist/engine/registry.js +258 -0
  189. package/dist/engine/rows.d.ts +25 -0
  190. package/dist/engine/rows.js +131 -0
  191. package/dist/engine/runtime.d.ts +95 -0
  192. package/dist/engine/runtime.js +149 -0
  193. package/dist/engine/schedules/fire.js +208 -0
  194. package/dist/engine/schedules/resume.js +75 -0
  195. package/dist/engine/schedules/rows.js +69 -0
  196. package/dist/engine/schedules/tick.js +30 -0
  197. package/dist/engine/schedules/wake.js +50 -0
  198. package/dist/engine/state.d.ts +11 -0
  199. package/dist/engine/state.js +78 -0
  200. package/dist/locales/en.js +339 -0
  201. package/dist/locales/en.json +341 -0
  202. package/dist/locales/mr.js +339 -0
  203. package/dist/locales/mr.json +341 -0
  204. package/dist/ports/db.d.ts +22 -0
  205. package/dist/ports/index.d.ts +4 -0
  206. package/dist/ports/ports.d.ts +257 -0
  207. package/dist/ports/sql.d.ts +39 -0
  208. package/dist/ports/sql.js +76 -0
  209. package/dist/renderers/csv.d.ts +14 -0
  210. package/dist/renderers/csv.js +51 -0
  211. package/dist/renderers/html.d.ts +44 -0
  212. package/dist/renderers/html.js +81 -0
  213. package/dist/renderers/index.d.ts +5 -0
  214. package/dist/renderers/index.js +4 -0
  215. package/dist/renderers/table.d.ts +30 -0
  216. package/dist/renderers/table.js +114 -0
  217. package/dist/routes/admin.js +146 -0
  218. package/dist/routes/app.d.ts +3111 -0
  219. package/dist/routes/app.js +9 -0
  220. package/dist/routes/broadcasts.js +37 -0
  221. package/dist/routes/commands.js +35 -0
  222. package/dist/routes/content.js +12 -0
  223. package/dist/routes/events.js +36 -0
  224. package/dist/routes/http.d.ts +30 -0
  225. package/dist/routes/http.js +223 -0
  226. package/dist/routes/me.js +59 -0
  227. package/dist/routes/schedules.js +62 -0
  228. package/dist/routes/send.js +24 -0
  229. package/dist/routes/templates.js +70 -0
  230. package/dist/routes/webhook.js +147 -0
  231. package/dist/schema/columns.js +307 -0
  232. package/dist/schema/ddl.d.ts +7 -0
  233. package/dist/schema/ddl.js +45 -0
  234. package/dist/schema/index.d.ts +4255 -0
  235. package/dist/schema/index.js +44 -0
  236. package/dist/schema/reference.sql +359 -0
  237. package/dist/schema/tables.d.ts +4243 -0
  238. package/dist/schema/tables.js +327 -0
  239. package/dist/schema/version.d.ts +5 -0
  240. package/dist/schema/version.js +7 -0
  241. package/dist/server/index.d.ts +55 -0
  242. package/dist/server/index.js +46 -0
  243. package/dist/server/plugin.d.ts +81 -0
  244. package/dist/server/plugin.js +247 -0
  245. package/dist/server/version.d.ts +2 -0
  246. package/dist/server/version.js +4 -0
  247. package/dist/services/ai-tool.d.ts +26 -0
  248. package/dist/services/ai-tool.js +148 -0
  249. package/dist/services/bots.js +300 -0
  250. package/dist/services/broadcasts.js +390 -0
  251. package/dist/services/chats.js +83 -0
  252. package/dist/services/cleanup.js +83 -0
  253. package/dist/services/commands.js +140 -0
  254. package/dist/services/deliveries.js +53 -0
  255. package/dist/services/destinations.js +91 -0
  256. package/dist/services/event-rules.js +229 -0
  257. package/dist/services/events.js +20 -0
  258. package/dist/services/idempotency.js +64 -0
  259. package/dist/services/links.js +200 -0
  260. package/dist/services/menu.js +95 -0
  261. package/dist/services/ops.js +90 -0
  262. package/dist/services/rekey.js +63 -0
  263. package/dist/services/reports.js +87 -0
  264. package/dist/services/schedules.js +214 -0
  265. package/dist/services/send.d.ts +14 -0
  266. package/dist/services/send.js +421 -0
  267. package/dist/services/subscriptions.js +28 -0
  268. package/dist/services/templates.js +259 -0
  269. package/dist/telegram/classify.d.ts +30 -0
  270. package/dist/telegram/classify.js +164 -0
  271. package/dist/telegram/client.d.ts +73 -0
  272. package/dist/telegram/client.js +135 -0
  273. package/dist/telegram/errors.d.ts +37 -0
  274. package/dist/telegram/errors.js +62 -0
  275. package/dist/telegram/inbound.d.ts +40 -0
  276. package/dist/telegram/inbound.js +72 -0
  277. package/dist/telegram/index.d.ts +8 -0
  278. package/dist/telegram/multipart.d.ts +5 -0
  279. package/dist/telegram/multipart.js +47 -0
  280. package/dist/telegram/types.d.ts +271 -0
  281. package/dist/telegram/update.d.ts +833 -0
  282. package/dist/telegram/update.js +129 -0
  283. package/dist/testing/fake-html.d.ts +9 -0
  284. package/dist/testing/fake-html.js +83 -0
  285. package/dist/testing/fake-telegram.d.ts +206 -0
  286. package/dist/testing/fake-telegram.js +856 -0
  287. package/dist/testing/index.d.ts +7 -0
  288. package/dist/testing/index.js +7 -0
  289. package/dist/testing/reference-sql.d.ts +4 -0
  290. package/dist/testing/reference-sql.js +8 -0
  291. package/dist/testing/test-plugin.d.ts +126 -0
  292. package/dist/testing/test-plugin.js +182 -0
  293. package/dist/testing/updates.d.ts +100 -0
  294. package/dist/testing/updates.js +212 -0
  295. package/package.json +113 -3
@@ -0,0 +1,247 @@
1
+ import { FEATURE_KEYS } from "../contracts/features.js";
2
+ import { BUILTIN_COMMANDS } from "../contracts/commands.js";
3
+ import { systemClock } from "../adapters/clock.js";
4
+ import { uuidv7Ids } from "../adapters/ids.js";
5
+ import { BUILTIN_FILTERS } from "../core/template/filters.js";
6
+ import { TelegramConfigError, toPluginError } from "../engine/errors.js";
7
+ import { ensureState } from "../engine/state.js";
8
+ import { createRegistry, jsonSchemaOf } from "../engine/registry.js";
9
+ import { BUILTIN_MENU } from "../engine/dispatch/builtins.js";
10
+ import { enqueueIntent, previewIntent } from "../services/send.js";
11
+ import { resolveAiOptions, resolveFeatures, resolveInboundOptions } from "../engine/options.js";
12
+ import { registerTelegramPlugin } from "../engine/outbox/do-registry.js";
13
+ import { metricCount, metricTiming } from "../engine/metrics.js";
14
+ import { handleQueueBatch } from "../engine/outbox/transport.js";
15
+ import { kickEvents } from "../engine/events/kick.js";
16
+ import { createTranslator } from "../engine/i18n.js";
17
+ import { DEFAULT_CRONS, runCleanup, runRekey, runTick } from "../engine/jobs.js";
18
+ import { selectTransport } from "../engine/outbox/select.js";
19
+ import { createRuntime, defaultLimits, defaultRetention } from "../engine/runtime.js";
20
+ import { buildRoutes } from "../routes/app.js";
21
+ import { aiToolSpec } from "../services/ai-tool.js";
22
+ import { emitEvent } from "../services/events.js";
23
+ import { PLUGIN_VERSION } from "./version.js";
24
+ // src/server/plugin.ts
25
+ function validateOptions(o) {
26
+ if (typeof o.env !== "function") throw new TelegramConfigError("options.env must be a function (env) => ({ db, keyring, … })");
27
+ if (!o.ports?.actor || typeof o.ports.actor.fromRequest !== "function" || typeof o.ports.actor.forUser !== "function") throw new TelegramConfigError("ports.actor { fromRequest, forUser, forAudience, describe } is required");
28
+ if (typeof o.ports.authorize !== "function") throw new TelegramConfigError("ports.authorize is required");
29
+ const prefix = o.permissions?.prefix;
30
+ if (prefix !== void 0 && !/^[a-z][a-zA-Z0-9_.-]{0,31}$/.test(prefix)) throw new TelegramConfigError("permissions.prefix must match [a-z][a-zA-Z0-9_.-]{0,31}");
31
+ if (typeof o.transport === "string" && ![
32
+ "auto",
33
+ "cron",
34
+ "queue",
35
+ "do"
36
+ ].includes(o.transport)) throw new TelegramConfigError("transport must be \"auto\", \"cron\", \"queue\", \"do\" or a factory");
37
+ for (const [k, v] of Object.entries(o.limits ?? {})) {
38
+ if (k === "plan") {
39
+ if (v !== "free" && v !== "paid") throw new TelegramConfigError("limits.plan must be \"free\" or \"paid\"");
40
+ continue;
41
+ }
42
+ if (typeof v !== "number" || !Number.isFinite(v) || v <= 0) throw new TelegramConfigError(`limits.${k} must be a positive number`);
43
+ }
44
+ if (o.tenant !== void 0 && (typeof o.tenant !== "string" || o.tenant.length > 64)) throw new TelegramConfigError("tenant must be a string (≤ 64 chars)");
45
+ const log = o.ports.log;
46
+ if (typeof log === "string" && ![
47
+ "debug",
48
+ "info",
49
+ "warn",
50
+ "error",
51
+ "silent"
52
+ ].includes(log)) throw new TelegramConfigError("ports.log must be a LogPort or \"debug\" | \"info\" | \"warn\" | \"error\" | \"silent\"");
53
+ for (const [name, f] of Object.entries(o.filters ?? {})) {
54
+ if (!/^[a-z][a-zA-Z0-9_]{0,31}$/.test(name)) throw new TelegramConfigError(`filters.${name}: name must match [a-z][a-zA-Z0-9_]{0,31}`);
55
+ if (Object.hasOwn(BUILTIN_FILTERS, name)) throw new TelegramConfigError(`filters.${name}: shadows a built-in filter`);
56
+ if (typeof f !== "function" && (typeof f !== "object" || f === null || typeof f.apply !== "function")) throw new TelegramConfigError(`filters.${name}: must be a function or defineFilter({ apply })`);
57
+ }
58
+ }
59
+ function filterMeta(name, f, builtin) {
60
+ const def = typeof f === "function" ? { apply: f } : f;
61
+ return {
62
+ name,
63
+ builtin,
64
+ minArgs: def.minArgs ?? 0,
65
+ maxArgs: def.maxArgs ?? null
66
+ };
67
+ }
68
+ /** Runs a programmatic API call so that only `TelegramPluginError`s leave it (others become `internal_error`). */
69
+ async function guarded(op, make, run) {
70
+ let rt = null;
71
+ try {
72
+ rt = make();
73
+ return await run(rt);
74
+ } catch (e) {
75
+ const err = toPluginError(e);
76
+ if (err.code === "internal_error") rt?.log.error("tg.api_error", {
77
+ op,
78
+ error: e instanceof Error ? e.name : "unknown"
79
+ });
80
+ throw err;
81
+ }
82
+ }
83
+ /**
84
+ * Builds the plugin: validates `options` once (throws `TelegramConfigError`), indexes the host definitions and
85
+ * registers the instance for TelegramBotDO under its tenant. No I/O happens until a route, job or method runs.
86
+ */
87
+ function createTelegramPlugin(options) {
88
+ validateOptions(options);
89
+ const features = resolveFeatures(options.features);
90
+ const registry = createRegistry(options);
91
+ const shared = {
92
+ options,
93
+ features,
94
+ limits: defaultLimits(options.limits),
95
+ retention: defaultRetention(options.retention),
96
+ registry,
97
+ ai: resolveAiOptions(options.ai),
98
+ inbound: resolveInboundOptions(options.inbound),
99
+ keyrings: /* @__PURE__ */ new Map(),
100
+ filters: Object.freeze({ ...options.filters }),
101
+ makeTransport: (rt, mapped) => selectTransport(rt, mapped, options.transport)
102
+ };
103
+ const runtime = (env, exec) => createRuntime(shared, env, exec);
104
+ registerTelegramPlugin({ runtime: (env, exec) => runtime(env, exec) }, options.tenant ?? "");
105
+ const i18n = createTranslator(options.ports.i18n ?? {
106
+ locales: ["en", "mr"],
107
+ defaultLocale: "en"
108
+ });
109
+ const settings = options.ports.settings ?? { timezone: "UTC" };
110
+ const localized = (key) => Object.fromEntries(i18n.locales.map((l) => [l, i18n.plain(l, key)]));
111
+ const metaValue = () => {
112
+ const builtinScope = (c) => BUILTIN_MENU.group.includes(c) ? [
113
+ "private",
114
+ "group",
115
+ "supergroup"
116
+ ] : ["private"];
117
+ return {
118
+ pluginVersion: PLUGIN_VERSION,
119
+ schemaVersion: 2,
120
+ features: Object.fromEntries(FEATURE_KEYS.map((k) => [k, { enabled: features[k] }])),
121
+ locales: [...i18n.locales],
122
+ defaultLocale: i18n.defaultLocale,
123
+ timezone: settings.timezone,
124
+ providers: [...registry.providers.values()].map((p) => ({
125
+ key: p.key,
126
+ label: p.label,
127
+ permission: p.permission,
128
+ params: jsonSchemaOf(p.params),
129
+ variables: jsonSchemaOf(p.variables),
130
+ hasSample: typeof p.sample === "function"
131
+ })),
132
+ commands: [...BUILTIN_COMMANDS.map((c) => ({
133
+ command: c,
134
+ description: localized(`bot.cmd.${c}`),
135
+ builtin: true,
136
+ permission: null,
137
+ scope: builtinScope(c)
138
+ })), ...features.commands ? registry.commandList.map((c) => ({
139
+ command: c.command,
140
+ description: c.description,
141
+ builtin: false,
142
+ permission: c.permission,
143
+ scope: [...c.scope]
144
+ })) : []],
145
+ events: features.events ? [...registry.events.values()].map((e) => ({
146
+ key: e.key,
147
+ label: e.label,
148
+ payload: jsonSchemaOf(e.payload),
149
+ filterable: [...e.filterable]
150
+ })) : [],
151
+ actions: features.actions ? [...registry.actions.values()].map((a) => ({
152
+ key: a.key,
153
+ permission: a.permission,
154
+ confirm: a.confirm !== null
155
+ })) : [],
156
+ dialogs: features.dialogs ? [...registry.dialogs.values()].map((d) => ({
157
+ key: d.key,
158
+ permission: d.permission,
159
+ steps: d.steps.map((s) => s.id)
160
+ })) : [],
161
+ audiences: [...registry.audiences.values()].map((a) => ({
162
+ key: a.key,
163
+ label: a.label
164
+ })),
165
+ fileSources: [...registry.fileSources.keys()],
166
+ renderers: features.reports ? features.reportRenderers.length > 0 ? [...features.reportRenderers] : [.../* @__PURE__ */ new Set(["csv", ...(options.ports.renderers ?? []).map((r) => r.format)])] : [],
167
+ filters: [...Object.entries(BUILTIN_FILTERS).map(([n, f]) => filterMeta(n, f, true)), ...Object.entries(shared.filters).map(([n, f]) => filterMeta(n, f, false))],
168
+ userSearch: typeof options.ports.users?.search === "function",
169
+ permissions: []
170
+ };
171
+ };
172
+ const metaCache = metaValue();
173
+ const meta = () => structuredClone(metaCache);
174
+ const deps = {
175
+ runtime: (env, exec) => runtime(env, exec),
176
+ features,
177
+ errors: options.ports.errors,
178
+ schemaOk: /* @__PURE__ */ new WeakSet(),
179
+ schemaKey: (env) => {
180
+ const db = options.env(env).db;
181
+ return typeof db === "object" && db !== null ? db : void 0;
182
+ }
183
+ };
184
+ const routes = buildRoutes(deps, meta);
185
+ const crons = {
186
+ ...DEFAULT_CRONS,
187
+ ...options.crons
188
+ };
189
+ const job = (name, cron, description, run) => ({
190
+ name,
191
+ cron,
192
+ description,
193
+ run: async (ctx) => {
194
+ const rt = runtime(ctx.env, ctx.waitUntil ? { waitUntil: ctx.waitUntil } : void 0);
195
+ const started = rt.clock.now();
196
+ const r = await run(rt);
197
+ metricCount(rt, "tg.job", 1, {
198
+ job: name,
199
+ outcome: r.status
200
+ });
201
+ metricTiming(rt, "tg.job_ms", rt.clock.now() - started, { job: name });
202
+ return r;
203
+ }
204
+ });
205
+ return {
206
+ routes,
207
+ jobs: Object.freeze([
208
+ job("telegram:tick", crons.tick, "Telegram: sweep stuck updates, fire due work, drain the outbox", runTick),
209
+ job("telegram:cleanup", crons.cleanup, "Telegram: retention, expired codes, webhook health, menus", runCleanup),
210
+ job("telegram:rekey", crons.rekey, "Telegram: re-encrypt bot secrets on old key ids", runRekey)
211
+ ]),
212
+ async queue(batch, env, ctx) {
213
+ return handleQueueBatch(runtime(env, ctx), batch, { deadLetterQueue: options.deadLetterQueue });
214
+ },
215
+ emit(event, payload, opts) {
216
+ try {
217
+ return emitEvent({
218
+ tenant: options.tenant ?? "",
219
+ enabled: features.events,
220
+ events: registry.events,
221
+ ids: options.ports.ids ?? uuidv7Ids(options.ports.clock),
222
+ now: () => (options.ports.clock ?? systemClock).now()
223
+ }, event, payload, opts);
224
+ } catch (e) {
225
+ throw toPluginError(e);
226
+ }
227
+ },
228
+ preview(env, intent, actor, opts) {
229
+ return guarded("preview", () => runtime(env), (rt) => previewIntent(rt, actor, intent, opts));
230
+ },
231
+ enqueue(env, intent, actor, opts) {
232
+ return guarded("enqueue", () => runtime(env), (rt) => enqueueIntent(rt, actor, intent, opts));
233
+ },
234
+ aiToolSpec(env, actor) {
235
+ return guarded("aiToolSpec", () => runtime(env), (rt) => aiToolSpec(rt, actor));
236
+ },
237
+ async kick(env, opts) {
238
+ await guarded("kick", () => runtime(env, opts?.ctx), (rt) => Promise.all([rt.transport.kick(opts?.botIds), kickEvents(rt)]));
239
+ },
240
+ meta,
241
+ async ensureState(env) {
242
+ return ensureState(runtime(env));
243
+ },
244
+ runtime
245
+ };
246
+ }
247
+ export { createTelegramPlugin };
@@ -0,0 +1,2 @@
1
+ /** Kept equal to package.json `version` (a test checks it). Reported by `GET /admin/meta`. */
2
+ export declare const PLUGIN_VERSION = "0.1.0";
@@ -0,0 +1,4 @@
1
+ // src/server/version.ts
2
+ /** Kept equal to package.json `version` (a test checks it). Reported by `GET /admin/meta`. */
3
+ const PLUGIN_VERSION = "0.1.0";
4
+ export { PLUGIN_VERSION };
@@ -0,0 +1,26 @@
1
+ /** `what` kinds of a `SendIntent` the AI tool offers. */
2
+ export type AiSendKind = "text" | "record" | "template" | "report" | "file";
3
+ /** One `ai_allowed` destination offered to the AI. */
4
+ export interface AiToolDestination {
5
+ /** Destination slug used as `to: { destination }`. */
6
+ slug: string;
7
+ /** Display name. */
8
+ name: string;
9
+ /** Chat kind (`private`, `group`, `supergroup`, `channel`). */
10
+ kind: string;
11
+ }
12
+ /** A generic `send_to_telegram` tool definition for the host's LLM, scoped to one actor. */
13
+ export interface AiToolSpec {
14
+ /** Tool name. */
15
+ name: "send_to_telegram";
16
+ /** Tool description for the model. */
17
+ description: string;
18
+ /** JSON Schema (draft 2020-12) of the tool input; it is a `SendIntent`. */
19
+ input_schema: Record<string, unknown>;
20
+ /** `ai_allowed` destinations this actor may use (empty without `telegram.send`). */
21
+ destinations: AiToolDestination[];
22
+ /** True when the actor has a linked private chat (`to: { me: true }` works). */
23
+ me: boolean;
24
+ /** Kinds available with the current features and registrations (`text` always). */
25
+ kinds: AiSendKind[];
26
+ }
@@ -0,0 +1,148 @@
1
+ import { sql } from "../ports/sql.js";
2
+ import { featureDisabled } from "../engine/errors.js";
3
+ import { FILES_PORT_SOURCE, recordProviderKey } from "./send.js";
4
+ // src/services/ai-tool.ts
5
+ /**
6
+ * The AI tool spec for `actor` (read-only: D1 reads, no Telegram calls). Throws `feature_disabled` unless
7
+ * `features.ai` with `sendIntent` is on. Destinations need `telegram.send`.
8
+ */
9
+ async function aiToolSpec(rt, actor) {
10
+ if (!rt.features.ai || !rt.features.aiSendIntent) throw featureDisabled("ai");
11
+ const allowed = await rt.can(actor, "telegram.send");
12
+ const rows = allowed ? await rt.db.all(sql`SELECT d.*, c.kind AS chat_kind FROM tg_destination d JOIN tg_chat c ON c.id = d.chat_ref
13
+ WHERE d.tenant = ${rt.tenant} AND d.deleted_at IS NULL AND d.ai_allowed = 1 AND c.status = 'active'
14
+ ORDER BY d.name LIMIT 100`) : [];
15
+ const userId = rt.describe(actor).userId;
16
+ const mine = allowed && userId ? await rt.db.first(sql`SELECT id FROM tg_chat WHERE tenant = ${rt.tenant} AND user_id = ${userId} AND kind = 'private'
17
+ AND status = 'active' LIMIT 1`) : null;
18
+ const destinations = rows.map((r) => ({
19
+ slug: r.slug,
20
+ name: r.name,
21
+ kind: r.chat_kind
22
+ }));
23
+ const recordTypes = [...rt.registry.providers.keys()].filter((k) => k.startsWith(recordProviderKey(""))).map((k) => k.slice(recordProviderKey("").length));
24
+ const kinds = ["text"];
25
+ if (recordTypes.length > 0) kinds.push("record");
26
+ if (rt.features.templates) kinds.push("template");
27
+ if (rt.features.templates && rt.features.reports) kinds.push("report");
28
+ if (rt.registry.fileSources.size > 0 || rt.files) kinds.push("file");
29
+ const to = [];
30
+ if (destinations.length > 0) to.push({
31
+ type: "object",
32
+ properties: { destination: {
33
+ type: "string",
34
+ enum: destinations.map((d) => d.slug)
35
+ } },
36
+ required: ["destination"],
37
+ additionalProperties: false
38
+ });
39
+ if (mine) to.push({
40
+ type: "object",
41
+ properties: { me: { const: true } },
42
+ required: ["me"],
43
+ additionalProperties: false
44
+ });
45
+ const what = [{
46
+ type: "object",
47
+ description: "A short plain-text message you write. No links outside the app, no prices unless allowed.",
48
+ properties: { text: {
49
+ type: "string",
50
+ minLength: 1,
51
+ maxLength: rt.ai.maxTextLength
52
+ } },
53
+ required: ["text"],
54
+ additionalProperties: false
55
+ }];
56
+ if (kinds.includes("record")) what.push({
57
+ type: "object",
58
+ description: "An app record (for example an order), shown with the fields the recipients may see.",
59
+ properties: { record: {
60
+ type: "object",
61
+ properties: {
62
+ type: {
63
+ type: "string",
64
+ enum: recordTypes
65
+ },
66
+ key: {
67
+ type: "string",
68
+ minLength: 1,
69
+ maxLength: 128
70
+ },
71
+ fields: {
72
+ type: "array",
73
+ items: { type: "string" },
74
+ maxItems: 50
75
+ }
76
+ },
77
+ required: ["type", "key"],
78
+ additionalProperties: false
79
+ } },
80
+ required: ["record"],
81
+ additionalProperties: false
82
+ });
83
+ if (kinds.includes("template")) what.push({
84
+ type: "object",
85
+ description: "A saved message template by key, with optional params.",
86
+ properties: {
87
+ template: { type: "string" },
88
+ params: { type: "object" }
89
+ },
90
+ required: ["template"],
91
+ additionalProperties: false
92
+ });
93
+ if (kinds.includes("report")) what.push({
94
+ type: "object",
95
+ description: "A report file made from a report template.",
96
+ properties: {
97
+ report: { type: "string" },
98
+ params: { type: "object" },
99
+ format: {
100
+ type: "string",
101
+ enum: ["pdf", "csv"]
102
+ }
103
+ },
104
+ required: ["report", "format"],
105
+ additionalProperties: false
106
+ });
107
+ if (kinds.includes("file")) what.push({
108
+ type: "object",
109
+ description: "An existing app file.",
110
+ properties: { file: {
111
+ type: "object",
112
+ properties: {
113
+ source: {
114
+ type: "string",
115
+ enum: [...rt.registry.fileSources.keys(), ...rt.files ? [FILES_PORT_SOURCE] : []]
116
+ },
117
+ ref: { type: "string" }
118
+ },
119
+ required: ["source", "ref"],
120
+ additionalProperties: false
121
+ } },
122
+ required: ["file"],
123
+ additionalProperties: false
124
+ });
125
+ return {
126
+ name: "send_to_telegram",
127
+ description: "Propose sending something to a Telegram chat. Nothing is sent until the user confirms the preview. " + (to.length === 0 ? "There are no Telegram destinations available to you right now." : `Destinations: ${[...destinations.map((d) => `${d.slug} (${d.name})`), ...mine ? ["me (your own chat)"] : []].join(", ")}.`),
128
+ input_schema: {
129
+ $schema: "https://json-schema.org/draft/2020-12/schema",
130
+ type: "object",
131
+ properties: {
132
+ to: to.length === 1 ? to[0] : { oneOf: to },
133
+ what: { oneOf: what },
134
+ note: {
135
+ type: "string",
136
+ maxLength: 500,
137
+ description: "Optional short note sent with a record or file."
138
+ }
139
+ },
140
+ required: ["to", "what"],
141
+ additionalProperties: false
142
+ },
143
+ destinations,
144
+ me: mine !== null,
145
+ kinds
146
+ };
147
+ }
148
+ export { aiToolSpec };