@sheepbun/yips 0.1.1

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 (99) hide show
  1. package/dist/agent/commands/command-catalog.js +243 -0
  2. package/dist/agent/commands/commands.js +418 -0
  3. package/dist/agent/conductor.js +118 -0
  4. package/dist/agent/context/code-context.js +68 -0
  5. package/dist/agent/context/memory-store.js +159 -0
  6. package/dist/agent/context/session-store.js +211 -0
  7. package/dist/agent/protocol/tool-protocol.js +160 -0
  8. package/dist/agent/skills/skills.js +327 -0
  9. package/dist/agent/tools/tool-executor.js +415 -0
  10. package/dist/agent/tools/tool-safety.js +52 -0
  11. package/dist/app/index.js +35 -0
  12. package/dist/app/repl.js +105 -0
  13. package/dist/app/update-check.js +132 -0
  14. package/dist/app/version.js +51 -0
  15. package/dist/code-context.js +68 -0
  16. package/dist/colors.js +204 -0
  17. package/dist/command-catalog.js +242 -0
  18. package/dist/commands.js +350 -0
  19. package/dist/conductor.js +94 -0
  20. package/dist/config/config.js +335 -0
  21. package/dist/config/hooks.js +187 -0
  22. package/dist/config.js +335 -0
  23. package/dist/downloader-state.js +302 -0
  24. package/dist/downloader-ui.js +289 -0
  25. package/dist/gateway/adapters/discord.js +108 -0
  26. package/dist/gateway/adapters/formatting.js +96 -0
  27. package/dist/gateway/adapters/telegram.js +106 -0
  28. package/dist/gateway/adapters/types.js +2 -0
  29. package/dist/gateway/adapters/whatsapp.js +124 -0
  30. package/dist/gateway/auth-policy.js +66 -0
  31. package/dist/gateway/core.js +87 -0
  32. package/dist/gateway/headless-conductor.js +328 -0
  33. package/dist/gateway/message-router.js +23 -0
  34. package/dist/gateway/rate-limiter.js +48 -0
  35. package/dist/gateway/runtime/backend-policy.js +18 -0
  36. package/dist/gateway/runtime/discord-bot.js +104 -0
  37. package/dist/gateway/runtime/discord-main.js +69 -0
  38. package/dist/gateway/session-manager.js +77 -0
  39. package/dist/gateway/types.js +2 -0
  40. package/dist/hardware.js +92 -0
  41. package/dist/hooks.js +187 -0
  42. package/dist/index.js +34 -0
  43. package/dist/input-engine.js +250 -0
  44. package/dist/llama-client.js +227 -0
  45. package/dist/llama-server.js +620 -0
  46. package/dist/llm/llama-client.js +227 -0
  47. package/dist/llm/llama-server.js +620 -0
  48. package/dist/llm/token-counter.js +47 -0
  49. package/dist/memory-store.js +159 -0
  50. package/dist/messages.js +59 -0
  51. package/dist/model-downloader.js +382 -0
  52. package/dist/model-manager-state.js +118 -0
  53. package/dist/model-manager-ui.js +194 -0
  54. package/dist/model-manager.js +190 -0
  55. package/dist/models/hardware.js +92 -0
  56. package/dist/models/model-downloader.js +382 -0
  57. package/dist/models/model-manager.js +190 -0
  58. package/dist/prompt-box.js +78 -0
  59. package/dist/prompt-composer.js +498 -0
  60. package/dist/repl.js +105 -0
  61. package/dist/session-store.js +211 -0
  62. package/dist/spinner.js +76 -0
  63. package/dist/title-box.js +388 -0
  64. package/dist/token-counter.js +47 -0
  65. package/dist/tool-executor.js +415 -0
  66. package/dist/tool-protocol.js +121 -0
  67. package/dist/tool-safety.js +52 -0
  68. package/dist/tui/app.js +2553 -0
  69. package/dist/tui/startup.js +56 -0
  70. package/dist/tui-input-routing.js +53 -0
  71. package/dist/tui.js +51 -0
  72. package/dist/types/app-types.js +2 -0
  73. package/dist/types.js +2 -0
  74. package/dist/ui/colors.js +204 -0
  75. package/dist/ui/downloader/downloader-state.js +302 -0
  76. package/dist/ui/downloader/downloader-ui.js +289 -0
  77. package/dist/ui/input/input-engine.js +250 -0
  78. package/dist/ui/input/tui-input-routing.js +53 -0
  79. package/dist/ui/input/vt-session.js +168 -0
  80. package/dist/ui/messages.js +59 -0
  81. package/dist/ui/model-manager/model-manager-state.js +118 -0
  82. package/dist/ui/model-manager/model-manager-ui.js +194 -0
  83. package/dist/ui/prompt/prompt-box.js +78 -0
  84. package/dist/ui/prompt/prompt-composer.js +498 -0
  85. package/dist/ui/spinner.js +76 -0
  86. package/dist/ui/title-box.js +388 -0
  87. package/dist/ui/tui/app.js +6 -0
  88. package/dist/ui/tui/autocomplete.js +85 -0
  89. package/dist/ui/tui/constants.js +18 -0
  90. package/dist/ui/tui/history.js +29 -0
  91. package/dist/ui/tui/layout.js +341 -0
  92. package/dist/ui/tui/runtime-core.js +2584 -0
  93. package/dist/ui/tui/runtime-utils.js +53 -0
  94. package/dist/ui/tui/start-tui.js +54 -0
  95. package/dist/ui/tui/startup.js +56 -0
  96. package/dist/version.js +51 -0
  97. package/dist/vt-session.js +168 -0
  98. package/install.sh +457 -0
  99. package/package.json +128 -0
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderDownloaderLines = renderDownloaderLines;
4
+ const colors_1 = require("./colors");
5
+ const TAB_INACTIVE = { r: 0x88, g: 0x88, b: 0x88 };
6
+ const BLACK = { r: 0x00, g: 0x00, b: 0x00 };
7
+ const FOCUS_ACCENT_BG = { r: 0xff, g: 0xcc, b: 0xff };
8
+ const DOWNLOADER_MODEL_BODY_ROWS = 10;
9
+ const DOWNLOADER_FILE_BODY_ROWS = 10;
10
+ const ANSI_BOLD_ON = "\u001b[1m";
11
+ const ANSI_BOLD_OFF = "\u001b[22m";
12
+ function charLength(text) {
13
+ return Array.from((0, colors_1.stripAnsi)(text)).length;
14
+ }
15
+ function fitRight(text, width) {
16
+ const chars = Array.from(text);
17
+ if (chars.length <= width) {
18
+ return text;
19
+ }
20
+ return chars.slice(chars.length - width).join("");
21
+ }
22
+ function fitLeft(text, width) {
23
+ const chars = Array.from(text);
24
+ if (chars.length <= width) {
25
+ return text;
26
+ }
27
+ if (width <= 3) {
28
+ return chars.slice(0, width).join("");
29
+ }
30
+ return `${chars.slice(0, width - 3).join("")}...`;
31
+ }
32
+ function formatCount(value) {
33
+ if (value >= 1_000_000) {
34
+ return `${(value / 1_000_000).toFixed(1)}M`;
35
+ }
36
+ if (value >= 1_000) {
37
+ return `${(value / 1_000).toFixed(1)}k`;
38
+ }
39
+ return String(value);
40
+ }
41
+ function formatSize(bytes) {
42
+ if (bytes === null || bytes <= 0) {
43
+ return "?";
44
+ }
45
+ const gib = bytes / (1024 * 1024 * 1024);
46
+ return `${gib.toFixed(1)}G`;
47
+ }
48
+ function tabText(tab, activeTab) {
49
+ const label = ` ${tab} `;
50
+ if (tab === activeTab) {
51
+ return `${ANSI_BOLD_ON}${(0, colors_1.horizontalGradientBackground)(label, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, BLACK)}${ANSI_BOLD_OFF}`;
52
+ }
53
+ return `${ANSI_BOLD_ON}${(0, colors_1.colorText)(label, TAB_INACTIVE)}${ANSI_BOLD_OFF}`;
54
+ }
55
+ function makeBorderTop(width) {
56
+ if (width <= 1)
57
+ return "╭";
58
+ const prefix = "╭─── ";
59
+ const titleBrand = "Yips";
60
+ const titleDetail = " Model Downloader";
61
+ const titleTail = " ";
62
+ const prefixLen = charLength(prefix);
63
+ const titleBrandLen = charLength(titleBrand);
64
+ const titleDetailLen = charLength(titleDetail);
65
+ const titleTailLen = charLength(titleTail);
66
+ const plainTitleLen = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
67
+ const fill = "─".repeat(Math.max(0, width - plainTitleLen - 1));
68
+ const fillOffset = prefixLen + titleBrandLen + titleDetailLen + titleTailLen;
69
+ const cornerOffset = width - 1;
70
+ return `${(0, colors_1.horizontalGradientAtOffset)(prefix, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, 0, width)}${ANSI_BOLD_ON}${(0, colors_1.horizontalGradient)(titleBrand, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)(titleDetail, colors_1.GRADIENT_BLUE)}${ANSI_BOLD_OFF}${(0, colors_1.horizontalGradientAtOffset)(titleTail, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, prefixLen + titleBrandLen + titleDetailLen, width)}${(0, colors_1.horizontalGradientAtOffset)(fill, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, fillOffset, width)}${(0, colors_1.horizontalGradientAtOffset)("╮", colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, cornerOffset, width)}`;
71
+ }
72
+ function makeBorderBottom(width) {
73
+ if (width <= 1)
74
+ return "╰";
75
+ const mid = "─".repeat(Math.max(0, width - 2));
76
+ return `${(0, colors_1.colorText)("╰", colors_1.GRADIENT_PINK)}${(0, colors_1.horizontalGradient)(mid, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW)}${(0, colors_1.colorText)("╯", colors_1.GRADIENT_YELLOW)}`;
77
+ }
78
+ function lineWithBorders(inner, innerWidth) {
79
+ let fitted = inner;
80
+ if (charLength(fitted) > innerWidth) {
81
+ fitted = fitLeft((0, colors_1.stripAnsi)(inner), innerWidth);
82
+ }
83
+ const padding = " ".repeat(Math.max(0, innerWidth - charLength(fitted)));
84
+ return `${(0, colors_1.colorText)("│", colors_1.GRADIENT_PINK)}${fitted}${padding}${(0, colors_1.colorText)("│", colors_1.GRADIENT_YELLOW)}`;
85
+ }
86
+ function composeSplitFooter(left, right, innerWidth) {
87
+ const leftLen = charLength(left);
88
+ const rightLen = charLength(right);
89
+ if (leftLen + rightLen + 1 > innerWidth) {
90
+ return `${left} ${fitRight(right, Math.max(1, innerWidth - leftLen - 1))}`;
91
+ }
92
+ const gap = " ".repeat(Math.max(1, innerWidth - leftLen - rightLen));
93
+ return `${left}${gap}${right}`;
94
+ }
95
+ function highlightedRow(row) {
96
+ const chars = Array.from(row);
97
+ if (chars.length === 0)
98
+ return row;
99
+ const first = (0, colors_1.bgColorText)(chars[0] ?? "", FOCUS_ACCENT_BG, BLACK);
100
+ const rest = chars.slice(1).join("");
101
+ if (rest.length === 0) {
102
+ return first;
103
+ }
104
+ return `${first}${(0, colors_1.horizontalGradientBackground)(rest, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW, BLACK)}`;
105
+ }
106
+ function buildModelRows(state, contentWidth, rowCount) {
107
+ const rows = [];
108
+ if (state.models.length === 0) {
109
+ rows.push((0, colors_1.colorText)("No compatible models found for current RAM+VRAM.", colors_1.WARNING_YELLOW));
110
+ return rows;
111
+ }
112
+ const dlWidth = 9;
113
+ const likesWidth = 6;
114
+ const sizeWidth = 5;
115
+ const dateWidth = 10;
116
+ const staticWidth = 2 + 3 + dlWidth + 3 + likesWidth + 3 + sizeWidth + 3 + dateWidth;
117
+ const modelWidth = Math.max(8, contentWidth - staticWidth);
118
+ const header = ` ${"Model".padEnd(modelWidth, " ")} | ${"Downloads".padStart(dlWidth, " ")} | ${"Likes".padStart(likesWidth, " ")} | ${"Size".padStart(sizeWidth, " ")} | ${"Updated".padStart(dateWidth, " ")}`;
119
+ rows.push((0, colors_1.horizontalGradient)(header, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW));
120
+ const visibleRowCount = Math.max(0, rowCount - 1);
121
+ const start = state.modelScrollOffset;
122
+ const end = Math.min(state.models.length, start + visibleRowCount);
123
+ for (let index = start; index < end; index++) {
124
+ const model = state.models[index];
125
+ if (!model)
126
+ continue;
127
+ const prefix = index === state.selectedModelIndex ? ">" : " ";
128
+ const modelCell = fitLeft(model.id, modelWidth).padEnd(modelWidth, " ");
129
+ const downloadsCell = `${formatCount(model.downloads)}↓`.padStart(dlWidth, " ");
130
+ const likesCell = `${formatCount(model.likes)}♥`.padStart(likesWidth, " ");
131
+ const sizeCell = formatSize(model.sizeBytes).padStart(sizeWidth, " ");
132
+ const dateCell = model.lastModified.slice(0, 10).padStart(dateWidth, " ");
133
+ const row = `${prefix} ${modelCell} | ${downloadsCell} | ${likesCell} | ${sizeCell} | ${dateCell}`;
134
+ rows.push(index === state.selectedModelIndex ? highlightedRow(row) : row);
135
+ }
136
+ return rows;
137
+ }
138
+ function toFileFitLabel(state, file) {
139
+ if (!file.canRun) {
140
+ return file.reason;
141
+ }
142
+ if (file.sizeBytes === null || file.sizeBytes <= 0) {
143
+ return file.reason;
144
+ }
145
+ if (!Number.isFinite(state.vramGb) || state.vramGb <= 0) {
146
+ return file.reason;
147
+ }
148
+ const vramBytes = state.vramGb * 1024 ** 3;
149
+ return file.sizeBytes <= vramBytes ? "Fits on GPU" : "Fits on GPU+CPU";
150
+ }
151
+ function buildFileRows(state, contentWidth, rowCount) {
152
+ const rows = [];
153
+ if (state.files.length === 0) {
154
+ rows.push((0, colors_1.colorText)("No GGUF files found for this model.", colors_1.WARNING_YELLOW));
155
+ return rows;
156
+ }
157
+ const quantWidth = 21;
158
+ const sizeWidth = 5;
159
+ const fitWidth = 16;
160
+ const staticWidth = 2 + 3 + quantWidth + 3 + sizeWidth + 3 + fitWidth;
161
+ const fileWidth = Math.max(8, contentWidth - staticWidth);
162
+ const header = ` ${"File".padEnd(fileWidth, " ")} | ${"Quant".padEnd(quantWidth, " ")} | ${"Size".padStart(sizeWidth, " ")} | ${"Fit".padEnd(fitWidth, " ")}`;
163
+ rows.push((0, colors_1.horizontalGradient)(header, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW));
164
+ const visibleRowCount = Math.max(0, rowCount - 1);
165
+ const start = state.fileScrollOffset;
166
+ const end = Math.min(state.files.length, start + visibleRowCount);
167
+ for (let index = start; index < end; index++) {
168
+ const file = state.files[index];
169
+ if (!file)
170
+ continue;
171
+ const prefix = index === state.selectedFileIndex ? ">" : " ";
172
+ const basename = file.path.includes("/")
173
+ ? (file.path.split("/").pop() ?? file.path)
174
+ : file.path;
175
+ const fileCell = fitLeft(basename, fileWidth).padEnd(fileWidth, " ");
176
+ const quantCell = fitLeft(file.quant, quantWidth).padEnd(quantWidth, " ");
177
+ const sizeCell = formatSize(file.sizeBytes).padStart(sizeWidth, " ");
178
+ const fitCell = fitLeft(toFileFitLabel(state, file), fitWidth).padEnd(fitWidth, " ");
179
+ const row = `${prefix} ${fileCell} | ${quantCell} | ${sizeCell} | ${fitCell}`;
180
+ if (index === state.selectedFileIndex) {
181
+ rows.push(highlightedRow(row));
182
+ continue;
183
+ }
184
+ rows.push((0, colors_1.colorText)(row, file.canRun ? colors_1.SUCCESS_GREEN : colors_1.ERROR_RED));
185
+ }
186
+ return rows;
187
+ }
188
+ function fillRows(rows, rowCount) {
189
+ if (rows.length >= rowCount) {
190
+ return rows.slice(0, rowCount);
191
+ }
192
+ return [...rows, ...new Array(rowCount - rows.length).fill("")];
193
+ }
194
+ function buildProgressBar(innerWidth, bytesDownloaded, totalBytes) {
195
+ if (totalBytes === null || totalBytes <= 0) {
196
+ return "Progress: downloading...";
197
+ }
198
+ const suffix = ` ${((bytesDownloaded / totalBytes) * 100).toFixed(1)}%`;
199
+ const barWidth = Math.max(1, innerWidth - suffix.length - 2);
200
+ const progress = Math.max(0, Math.min(1, bytesDownloaded / totalBytes));
201
+ const filled = Math.round(barWidth * progress);
202
+ const empty = Math.max(0, barWidth - filled);
203
+ return `[${"█".repeat(filled)}${"░".repeat(empty)}]${suffix}`;
204
+ }
205
+ function buildDownloadingRows(state, innerWidth) {
206
+ if (!state.download) {
207
+ return [`Loading: ${state.loadingMessage}`];
208
+ }
209
+ const filename = state.download.filename.includes("/")
210
+ ? (state.download.filename.split("/").pop() ?? state.download.filename)
211
+ : state.download.filename;
212
+ const rows = [];
213
+ rows.push((0, colors_1.colorText)(`Downloading ${fitLeft(filename, Math.max(1, innerWidth - 12))}`, colors_1.GRADIENT_BLUE));
214
+ rows.push((0, colors_1.colorText)(buildProgressBar(innerWidth, state.download.bytesDownloaded, state.download.totalBytes), colors_1.SUCCESS_GREEN));
215
+ return rows;
216
+ }
217
+ function buildCancelConfirmRows(innerWidth) {
218
+ const message = fitLeft("Cancel download and delete partial file?", Math.max(1, innerWidth));
219
+ return [(0, colors_1.colorText)(message, colors_1.WARNING_YELLOW), ""];
220
+ }
221
+ function renderDownloaderLines(options) {
222
+ const width = Math.max(20, options.width);
223
+ const state = options.state;
224
+ const innerWidth = Math.max(1, width - 2);
225
+ const rows = [];
226
+ rows.push(makeBorderTop(width));
227
+ if (state.phase !== "downloading") {
228
+ const tabs = [
229
+ tabText("Most Downloaded", state.tab),
230
+ tabText("Top Rated", state.tab),
231
+ tabText("Newest", state.tab)
232
+ ].join(" ");
233
+ const memText = `RAM: ${state.ramGb.toFixed(1)}GB | VRAM: ${state.vramGb.toFixed(1)}GB | Disk: ${state.diskFreeGb.toFixed(1)}GB`;
234
+ const memWidth = Math.min(charLength(memText), Math.max(24, innerWidth - 10));
235
+ const tabsWidth = Math.max(1, innerWidth - memWidth - 1);
236
+ let tabsCell = tabs;
237
+ if (charLength(tabsCell) > tabsWidth) {
238
+ tabsCell = fitLeft(" Most Downloaded Top Rated Newest ", tabsWidth);
239
+ }
240
+ const tabsPadding = " ".repeat(Math.max(0, tabsWidth - charLength(tabsCell)));
241
+ rows.push(lineWithBorders(`${tabsCell}${tabsPadding} ${fitRight(memText, memWidth)}`, innerWidth));
242
+ }
243
+ let bodyRows = [];
244
+ if (state.view === "models") {
245
+ if (state.phase === "loading-models") {
246
+ bodyRows = fillRows([`Loading: ${state.loadingMessage}`], DOWNLOADER_MODEL_BODY_ROWS);
247
+ }
248
+ else if (state.phase === "error" && state.errorMessage.length > 0) {
249
+ bodyRows = fillRows([`Error: ${state.errorMessage}`], DOWNLOADER_MODEL_BODY_ROWS);
250
+ }
251
+ else {
252
+ bodyRows = fillRows(buildModelRows(state, innerWidth, DOWNLOADER_MODEL_BODY_ROWS), DOWNLOADER_MODEL_BODY_ROWS);
253
+ }
254
+ }
255
+ else if (state.phase === "downloading") {
256
+ bodyRows = state.cancelConfirmOpen
257
+ ? buildCancelConfirmRows(innerWidth)
258
+ : buildDownloadingRows(state, innerWidth);
259
+ }
260
+ else if (state.phase === "loading-files") {
261
+ bodyRows = fillRows([`Loading: ${state.loadingMessage}`], DOWNLOADER_FILE_BODY_ROWS);
262
+ }
263
+ else if (state.phase === "error" && state.errorMessage.length > 0) {
264
+ bodyRows = fillRows([`Error: ${state.errorMessage}`], DOWNLOADER_FILE_BODY_ROWS);
265
+ }
266
+ else {
267
+ const fileRows = [
268
+ (0, colors_1.colorText)(`Files for ${fitLeft(state.selectedRepoId, Math.max(1, innerWidth - 10))}`, colors_1.GRADIENT_BLUE),
269
+ ...buildFileRows(state, innerWidth, DOWNLOADER_FILE_BODY_ROWS - 1)
270
+ ];
271
+ bodyRows = fillRows(fileRows, DOWNLOADER_FILE_BODY_ROWS);
272
+ }
273
+ rows.push(...bodyRows.map((row) => lineWithBorders(row, innerWidth)));
274
+ if (state.phase === "downloading") {
275
+ const status = state.download?.statusText ?? "Downloading...";
276
+ const left = state.cancelConfirmOpen ? "[Enter] Yes [Esc] No" : "[Esc] Cancel";
277
+ const statusWidth = Math.max(1, innerWidth - Array.from(left).length - 1);
278
+ const footer = composeSplitFooter(left, fitRight(status, statusWidth), innerWidth);
279
+ rows.push(lineWithBorders((0, colors_1.horizontalGradient)(footer, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW), innerWidth));
280
+ }
281
+ else {
282
+ const footer = state.view === "models"
283
+ ? "[Enter] Select [↑/↓] Move [←/→] Sort [Esc] Close"
284
+ : "[Enter] Download [↑/↓] Move [Esc] Back";
285
+ rows.push(lineWithBorders((0, colors_1.horizontalGradient)(footer, colors_1.GRADIENT_PINK, colors_1.GRADIENT_YELLOW), innerWidth));
286
+ }
287
+ rows.push(makeBorderBottom(width));
288
+ return rows;
289
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiscordAdapter = void 0;
4
+ exports.chunkDiscordMessage = chunkDiscordMessage;
5
+ const formatting_1 = require("#gateway/adapters/formatting");
6
+ const DEFAULT_API_BASE_URL = "https://discord.com/api/v10";
7
+ const DEFAULT_MAX_MESSAGE_LENGTH = 2000;
8
+ function isObject(value) {
9
+ return typeof value === "object" && value !== null;
10
+ }
11
+ function toDate(value) {
12
+ if (value === undefined) {
13
+ return undefined;
14
+ }
15
+ if (value instanceof Date) {
16
+ return Number.isNaN(value.valueOf()) ? undefined : value;
17
+ }
18
+ if (typeof value === "number") {
19
+ if (!Number.isFinite(value)) {
20
+ return undefined;
21
+ }
22
+ return new Date(value);
23
+ }
24
+ const parsed = new Date(value);
25
+ return Number.isNaN(parsed.valueOf()) ? undefined : parsed;
26
+ }
27
+ function toDiscordMessageLike(payload) {
28
+ if (!isObject(payload)) {
29
+ return null;
30
+ }
31
+ return payload;
32
+ }
33
+ function chunkDiscordMessage(text, maxLength = DEFAULT_MAX_MESSAGE_LENGTH) {
34
+ const normalizedText = (0, formatting_1.normalizeOutboundText)(text);
35
+ return (0, formatting_1.chunkOutboundText)(normalizedText, maxLength);
36
+ }
37
+ function toInboundMessage(payload) {
38
+ const message = toDiscordMessageLike(payload);
39
+ if (!message) {
40
+ return null;
41
+ }
42
+ const senderId = message.author?.id;
43
+ const username = message.author?.username;
44
+ const text = message.content;
45
+ const channelId = message.channelId ?? message.channel?.id;
46
+ if (typeof senderId !== "string" ||
47
+ typeof text !== "string" ||
48
+ typeof channelId !== "string" ||
49
+ senderId.trim().length === 0 ||
50
+ text.trim().length === 0 ||
51
+ channelId.trim().length === 0) {
52
+ return null;
53
+ }
54
+ if (message.author?.bot || message.author?.system || message.webhookId) {
55
+ return null;
56
+ }
57
+ return {
58
+ platform: "discord",
59
+ senderId,
60
+ channelId,
61
+ text,
62
+ messageId: message.id,
63
+ timestamp: toDate(message.createdTimestamp ?? message.createdAt),
64
+ metadata: {
65
+ guildId: message.guildId ?? undefined,
66
+ authorUsername: username,
67
+ isDm: !message.guildId
68
+ }
69
+ };
70
+ }
71
+ class DiscordAdapter {
72
+ platform = "discord";
73
+ botToken;
74
+ apiBaseUrl;
75
+ maxMessageLength;
76
+ constructor(options) {
77
+ this.botToken = options.botToken.trim();
78
+ this.apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_API_BASE_URL).trim().replace(/\/+$/, "");
79
+ this.maxMessageLength = Math.max(1, Math.trunc(options.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH));
80
+ }
81
+ parseInbound(payload) {
82
+ const message = toInboundMessage(payload);
83
+ return message ? [message] : [];
84
+ }
85
+ formatOutbound(context, response) {
86
+ const channelId = context.session.channelId.trim() || context.message.channelId?.trim();
87
+ if (!channelId) {
88
+ return null;
89
+ }
90
+ const chunks = chunkDiscordMessage(response.text, this.maxMessageLength);
91
+ if (chunks.length === 0) {
92
+ return null;
93
+ }
94
+ const requests = chunks.map((chunk) => ({
95
+ method: "POST",
96
+ endpoint: `${this.apiBaseUrl}/channels/${channelId}/messages`,
97
+ headers: {
98
+ authorization: `Bot ${this.botToken}`,
99
+ "content-type": "application/json"
100
+ },
101
+ body: {
102
+ content: chunk
103
+ }
104
+ }));
105
+ return requests.length === 1 ? (requests[0] ?? null) : requests;
106
+ }
107
+ }
108
+ exports.DiscordAdapter = DiscordAdapter;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripCommonMarkdown = stripCommonMarkdown;
4
+ exports.sanitizeMentions = sanitizeMentions;
5
+ exports.normalizeOutboundText = normalizeOutboundText;
6
+ exports.chunkOutboundText = chunkOutboundText;
7
+ const MENTION_GUARD = "\u200B";
8
+ function splitByLimit(value, maxLength) {
9
+ if (value.length <= maxLength) {
10
+ return [value];
11
+ }
12
+ const chunks = [];
13
+ let current = value;
14
+ while (current.length > maxLength) {
15
+ chunks.push(current.slice(0, maxLength));
16
+ current = current.slice(maxLength);
17
+ }
18
+ if (current.length > 0) {
19
+ chunks.push(current);
20
+ }
21
+ return chunks;
22
+ }
23
+ function stripCommonMarkdown(input) {
24
+ let output = input;
25
+ output = output.replace(/```[^\n]*\n?/gu, "");
26
+ output = output.replace(/```/gu, "");
27
+ output = output.replace(/`([^`]+)`/gu, "$1");
28
+ output = output.replace(/`/gu, "");
29
+ const pairedPatterns = [
30
+ [/\*\*([^*\n][^*]*?)\*\*/gu, "$1"],
31
+ [/__([^_\n][^_]*?)__/gu, "$1"],
32
+ [/~~([^~\n][^~]*?)~~/gu, "$1"],
33
+ [/\*([^*\n]+)\*/gu, "$1"],
34
+ [/_([^_\n]+)_/gu, "$1"]
35
+ ];
36
+ for (const [pattern, replacement] of pairedPatterns) {
37
+ let previous = "";
38
+ while (previous !== output) {
39
+ previous = output;
40
+ output = output.replace(pattern, replacement);
41
+ }
42
+ }
43
+ return output;
44
+ }
45
+ function sanitizeMentions(input) {
46
+ let output = input;
47
+ output = output.replace(/@(everyone|here)\b/giu, (_match, group) => `@${MENTION_GUARD}${group}`);
48
+ output = output.replace(/<@([!&]?\d+)>/gu, `<@${MENTION_GUARD}$1>`);
49
+ output = output.replace(/(^|[\s([{'"`])@([A-Za-z0-9_.-]{1,64})/gu, (_match, prefix, mention) => `${prefix}@${MENTION_GUARD}${mention}`);
50
+ return output;
51
+ }
52
+ function normalizeOutboundText(input) {
53
+ let output = input.replace(/\r\n?/gu, "\n");
54
+ output = output.trim();
55
+ output = output.replace(/\n{3,}/gu, "\n\n");
56
+ output = stripCommonMarkdown(output);
57
+ output = sanitizeMentions(output);
58
+ output = output.replace(/\n{3,}/gu, "\n\n");
59
+ return output.trim();
60
+ }
61
+ function chunkOutboundText(input, maxLength) {
62
+ const normalizedMaxLength = Math.max(1, Math.trunc(maxLength));
63
+ const text = input.trim();
64
+ if (text.length === 0) {
65
+ return [];
66
+ }
67
+ const chunks = [];
68
+ let start = 0;
69
+ while (start < text.length) {
70
+ if (text.length - start <= normalizedMaxLength) {
71
+ const lastChunk = text.slice(start).trim();
72
+ if (lastChunk.length > 0) {
73
+ chunks.push(lastChunk);
74
+ }
75
+ break;
76
+ }
77
+ const window = text.slice(start, start + normalizedMaxLength + 1);
78
+ const breakOnNewline = window.lastIndexOf("\n");
79
+ const breakOnSpace = window.lastIndexOf(" ");
80
+ const breakOffset = Math.max(breakOnNewline, breakOnSpace);
81
+ let end = breakOffset > 0 ? start + breakOffset : start + normalizedMaxLength;
82
+ let chunk = text.slice(start, end).trim();
83
+ if (chunk.length === 0) {
84
+ end = start + normalizedMaxLength;
85
+ chunk = text.slice(start, end).trim();
86
+ }
87
+ if (chunk.length > 0) {
88
+ chunks.push(...splitByLimit(chunk, normalizedMaxLength));
89
+ }
90
+ start = end;
91
+ while (start < text.length && /\s/u.test(text[start] ?? "")) {
92
+ start += 1;
93
+ }
94
+ }
95
+ return chunks.filter((chunk) => chunk.length > 0);
96
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TelegramAdapter = void 0;
4
+ const formatting_1 = require("#gateway/adapters/formatting");
5
+ const DEFAULT_API_BASE_URL = "https://api.telegram.org";
6
+ const DEFAULT_MAX_MESSAGE_LENGTH = 4000;
7
+ function isObject(value) {
8
+ return typeof value === "object" && value !== null;
9
+ }
10
+ function asTelegramUpdate(value) {
11
+ if (!isObject(value)) {
12
+ return null;
13
+ }
14
+ return value;
15
+ }
16
+ function toInboundMessage(update) {
17
+ const message = update.message;
18
+ if (!message) {
19
+ return null;
20
+ }
21
+ const text = message?.text;
22
+ const fromId = message?.from?.id;
23
+ const chatId = message?.chat?.id;
24
+ if (typeof text !== "string" || typeof fromId !== "number" || typeof chatId !== "number") {
25
+ return null;
26
+ }
27
+ return {
28
+ platform: "telegram",
29
+ senderId: fromId.toString(),
30
+ channelId: chatId.toString(),
31
+ text,
32
+ messageId: message.message_id?.toString(),
33
+ timestamp: typeof message.date === "number" ? new Date(message.date * 1000) : undefined,
34
+ metadata: {
35
+ updateId: update.update_id,
36
+ chatType: message.chat?.type,
37
+ username: message.from?.username
38
+ }
39
+ };
40
+ }
41
+ function parseUpdates(payload) {
42
+ if (!isObject(payload)) {
43
+ return [];
44
+ }
45
+ const envelope = payload;
46
+ if (Array.isArray(envelope.result)) {
47
+ return envelope.result.map((item) => asTelegramUpdate(item)).filter((item) => item !== null);
48
+ }
49
+ const single = asTelegramUpdate(payload);
50
+ return single ? [single] : [];
51
+ }
52
+ function parseReplyMessageId(messageId) {
53
+ if (!messageId) {
54
+ return undefined;
55
+ }
56
+ const parsed = Number.parseInt(messageId, 10);
57
+ if (!Number.isFinite(parsed)) {
58
+ return undefined;
59
+ }
60
+ return parsed;
61
+ }
62
+ class TelegramAdapter {
63
+ platform = "telegram";
64
+ botToken;
65
+ apiBaseUrl;
66
+ maxMessageLength;
67
+ constructor(options) {
68
+ this.botToken = options.botToken.trim();
69
+ this.apiBaseUrl = (options.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
70
+ this.maxMessageLength = Math.max(1, Math.trunc(options.maxMessageLength ?? DEFAULT_MAX_MESSAGE_LENGTH));
71
+ }
72
+ parseInbound(payload) {
73
+ const updates = parseUpdates(payload);
74
+ return updates
75
+ .map((update) => toInboundMessage(update))
76
+ .filter((message) => message !== null);
77
+ }
78
+ formatOutbound(context, response) {
79
+ const normalizedText = (0, formatting_1.normalizeOutboundText)(response.text);
80
+ const chunks = (0, formatting_1.chunkOutboundText)(normalizedText, this.maxMessageLength);
81
+ if (chunks.length === 0) {
82
+ return null;
83
+ }
84
+ const chatId = context.session.channelId.trim() || context.message.channelId?.trim() || context.message.senderId;
85
+ const replyToMessageId = parseReplyMessageId(context.message.messageId);
86
+ const requests = chunks.map((chunk, index) => {
87
+ const body = {
88
+ chat_id: chatId,
89
+ text: chunk
90
+ };
91
+ if (index === 0 && replyToMessageId !== undefined) {
92
+ body.reply_to_message_id = replyToMessageId;
93
+ }
94
+ return {
95
+ method: "POST",
96
+ endpoint: `${this.apiBaseUrl}/bot${this.botToken}/sendMessage`,
97
+ headers: {
98
+ "content-type": "application/json"
99
+ },
100
+ body
101
+ };
102
+ });
103
+ return requests.length === 1 ? (requests[0] ?? null) : requests;
104
+ }
105
+ }
106
+ exports.TelegramAdapter = TelegramAdapter;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });