kimaki 0.4.10 → 0.4.13
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/dist/discordBot.js +204 -106
- package/dist/escape-backticks.test.js +286 -1
- package/dist/tools.js +3 -2
- package/package.json +1 -1
- package/src/discordBot.ts +249 -130
- package/src/escape-backticks.test.ts +302 -1
- package/src/tools.ts +6 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { test, expect } from 'vitest'
|
|
2
2
|
import { Lexer } from 'marked'
|
|
3
|
-
import { escapeBackticksInCodeBlocks } from './discordBot.js'
|
|
3
|
+
import { escapeBackticksInCodeBlocks, splitMarkdownForDiscord } from './discordBot.js'
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -144,3 +144,304 @@ const x = \\\`hello\\\`
|
|
|
144
144
|
"
|
|
145
145
|
`)
|
|
146
146
|
})
|
|
147
|
+
|
|
148
|
+
test('splitMarkdownForDiscord returns single chunk for short content', () => {
|
|
149
|
+
const result = splitMarkdownForDiscord({
|
|
150
|
+
content: 'Hello world',
|
|
151
|
+
maxLength: 100,
|
|
152
|
+
})
|
|
153
|
+
expect(result).toMatchInlineSnapshot(`
|
|
154
|
+
[
|
|
155
|
+
"Hello world",
|
|
156
|
+
]
|
|
157
|
+
`)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test('splitMarkdownForDiscord splits at line boundaries', () => {
|
|
161
|
+
const result = splitMarkdownForDiscord({
|
|
162
|
+
content: 'Line 1\nLine 2\nLine 3\nLine 4',
|
|
163
|
+
maxLength: 15,
|
|
164
|
+
})
|
|
165
|
+
expect(result).toMatchInlineSnapshot(`
|
|
166
|
+
[
|
|
167
|
+
"Line 1
|
|
168
|
+
Line 2
|
|
169
|
+
",
|
|
170
|
+
"Line 3
|
|
171
|
+
Line 4",
|
|
172
|
+
]
|
|
173
|
+
`)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('splitMarkdownForDiscord preserves code blocks when not split', () => {
|
|
177
|
+
const result = splitMarkdownForDiscord({
|
|
178
|
+
content: '```js\nconst x = 1\n```',
|
|
179
|
+
maxLength: 100,
|
|
180
|
+
})
|
|
181
|
+
expect(result).toMatchInlineSnapshot(`
|
|
182
|
+
[
|
|
183
|
+
"\`\`\`js
|
|
184
|
+
const x = 1
|
|
185
|
+
\`\`\`",
|
|
186
|
+
]
|
|
187
|
+
`)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('splitMarkdownForDiscord adds closing and opening fences when splitting code block', () => {
|
|
191
|
+
const result = splitMarkdownForDiscord({
|
|
192
|
+
content: '```js\nline1\nline2\nline3\nline4\n```',
|
|
193
|
+
maxLength: 20,
|
|
194
|
+
})
|
|
195
|
+
expect(result).toMatchInlineSnapshot(`
|
|
196
|
+
[
|
|
197
|
+
"\`\`\`js
|
|
198
|
+
line1
|
|
199
|
+
line2
|
|
200
|
+
\`\`\`
|
|
201
|
+
",
|
|
202
|
+
"\`\`\`js
|
|
203
|
+
line3
|
|
204
|
+
line4
|
|
205
|
+
\`\`\`
|
|
206
|
+
",
|
|
207
|
+
]
|
|
208
|
+
`)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('splitMarkdownForDiscord handles code block with language', () => {
|
|
212
|
+
const result = splitMarkdownForDiscord({
|
|
213
|
+
content: '```typescript\nconst a = 1\nconst b = 2\n```',
|
|
214
|
+
maxLength: 30,
|
|
215
|
+
})
|
|
216
|
+
expect(result).toMatchInlineSnapshot(`
|
|
217
|
+
[
|
|
218
|
+
"\`\`\`typescript
|
|
219
|
+
const a = 1
|
|
220
|
+
\`\`\`
|
|
221
|
+
",
|
|
222
|
+
"\`\`\`typescript
|
|
223
|
+
const b = 2
|
|
224
|
+
\`\`\`
|
|
225
|
+
",
|
|
226
|
+
]
|
|
227
|
+
`)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test('splitMarkdownForDiscord handles mixed content with code blocks', () => {
|
|
231
|
+
const result = splitMarkdownForDiscord({
|
|
232
|
+
content: 'Text before\n```js\ncode\n```\nText after',
|
|
233
|
+
maxLength: 25,
|
|
234
|
+
})
|
|
235
|
+
expect(result).toMatchInlineSnapshot(`
|
|
236
|
+
[
|
|
237
|
+
"Text before
|
|
238
|
+
\`\`\`js
|
|
239
|
+
code
|
|
240
|
+
\`\`\`
|
|
241
|
+
",
|
|
242
|
+
"Text after",
|
|
243
|
+
]
|
|
244
|
+
`)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test('splitMarkdownForDiscord handles code block without language', () => {
|
|
248
|
+
const result = splitMarkdownForDiscord({
|
|
249
|
+
content: '```\nline1\nline2\n```',
|
|
250
|
+
maxLength: 12,
|
|
251
|
+
})
|
|
252
|
+
expect(result).toMatchInlineSnapshot(`
|
|
253
|
+
[
|
|
254
|
+
"\`\`\`
|
|
255
|
+
line1
|
|
256
|
+
\`\`\`
|
|
257
|
+
",
|
|
258
|
+
"\`\`\`
|
|
259
|
+
line2
|
|
260
|
+
\`\`\`
|
|
261
|
+
",
|
|
262
|
+
]
|
|
263
|
+
`)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('splitMarkdownForDiscord handles multiple consecutive code blocks', () => {
|
|
267
|
+
const result = splitMarkdownForDiscord({
|
|
268
|
+
content: '```js\nfoo\n```\n```py\nbar\n```',
|
|
269
|
+
maxLength: 20,
|
|
270
|
+
})
|
|
271
|
+
expect(result).toMatchInlineSnapshot(`
|
|
272
|
+
[
|
|
273
|
+
"\`\`\`js
|
|
274
|
+
foo
|
|
275
|
+
\`\`\`
|
|
276
|
+
\`\`\`py
|
|
277
|
+
\`\`\`
|
|
278
|
+
",
|
|
279
|
+
"\`\`\`py
|
|
280
|
+
bar
|
|
281
|
+
\`\`\`
|
|
282
|
+
",
|
|
283
|
+
]
|
|
284
|
+
`)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
test('splitMarkdownForDiscord handles empty code block', () => {
|
|
288
|
+
const result = splitMarkdownForDiscord({
|
|
289
|
+
content: 'before\n```\n```\nafter',
|
|
290
|
+
maxLength: 50,
|
|
291
|
+
})
|
|
292
|
+
expect(result).toMatchInlineSnapshot(`
|
|
293
|
+
[
|
|
294
|
+
"before
|
|
295
|
+
\`\`\`
|
|
296
|
+
\`\`\`
|
|
297
|
+
after",
|
|
298
|
+
]
|
|
299
|
+
`)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test('splitMarkdownForDiscord handles content exactly at maxLength', () => {
|
|
303
|
+
const result = splitMarkdownForDiscord({
|
|
304
|
+
content: '12345678901234567890',
|
|
305
|
+
maxLength: 20,
|
|
306
|
+
})
|
|
307
|
+
expect(result).toMatchInlineSnapshot(`
|
|
308
|
+
[
|
|
309
|
+
"12345678901234567890",
|
|
310
|
+
]
|
|
311
|
+
`)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('splitMarkdownForDiscord handles code block only', () => {
|
|
315
|
+
const result = splitMarkdownForDiscord({
|
|
316
|
+
content: '```ts\nconst x = 1\n```',
|
|
317
|
+
maxLength: 15,
|
|
318
|
+
})
|
|
319
|
+
expect(result).toMatchInlineSnapshot(`
|
|
320
|
+
[
|
|
321
|
+
"\`\`\`ts
|
|
322
|
+
\`\`\`
|
|
323
|
+
",
|
|
324
|
+
"\`\`\`ts
|
|
325
|
+
const x = 1
|
|
326
|
+
\`\`\`
|
|
327
|
+
",
|
|
328
|
+
]
|
|
329
|
+
`)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
test('splitMarkdownForDiscord handles code block at start with text after', () => {
|
|
333
|
+
const result = splitMarkdownForDiscord({
|
|
334
|
+
content: '```js\ncode\n```\nSome text after',
|
|
335
|
+
maxLength: 20,
|
|
336
|
+
})
|
|
337
|
+
expect(result).toMatchInlineSnapshot(`
|
|
338
|
+
[
|
|
339
|
+
"\`\`\`js
|
|
340
|
+
code
|
|
341
|
+
\`\`\`
|
|
342
|
+
",
|
|
343
|
+
"Some text after",
|
|
344
|
+
]
|
|
345
|
+
`)
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('splitMarkdownForDiscord handles text before code block at end', () => {
|
|
349
|
+
const result = splitMarkdownForDiscord({
|
|
350
|
+
content: 'Some text before\n```js\ncode\n```',
|
|
351
|
+
maxLength: 25,
|
|
352
|
+
})
|
|
353
|
+
expect(result).toMatchInlineSnapshot(`
|
|
354
|
+
[
|
|
355
|
+
"Some text before
|
|
356
|
+
\`\`\`js
|
|
357
|
+
\`\`\`
|
|
358
|
+
",
|
|
359
|
+
"\`\`\`js
|
|
360
|
+
code
|
|
361
|
+
\`\`\`
|
|
362
|
+
",
|
|
363
|
+
]
|
|
364
|
+
`)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('splitMarkdownForDiscord handles very long line inside code block', () => {
|
|
368
|
+
const result = splitMarkdownForDiscord({
|
|
369
|
+
content: '```js\nshort\nveryverylonglinethatexceedsmaxlength\nshort\n```',
|
|
370
|
+
maxLength: 25,
|
|
371
|
+
})
|
|
372
|
+
expect(result).toMatchInlineSnapshot(`
|
|
373
|
+
[
|
|
374
|
+
"\`\`\`js
|
|
375
|
+
short
|
|
376
|
+
\`\`\`
|
|
377
|
+
",
|
|
378
|
+
"\`\`\`js
|
|
379
|
+
veryverylonglinethatexceedsmaxlength
|
|
380
|
+
\`\`\`
|
|
381
|
+
",
|
|
382
|
+
"\`\`\`js
|
|
383
|
+
short
|
|
384
|
+
\`\`\`
|
|
385
|
+
",
|
|
386
|
+
]
|
|
387
|
+
`)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test('splitMarkdownForDiscord handles realistic long markdown with code block', () => {
|
|
391
|
+
const content = `Here is some explanation text before the code.
|
|
392
|
+
|
|
393
|
+
\`\`\`typescript
|
|
394
|
+
export function calculateTotal(items: Item[]): number {
|
|
395
|
+
let total = 0
|
|
396
|
+
for (const item of items) {
|
|
397
|
+
total += item.price * item.quantity
|
|
398
|
+
}
|
|
399
|
+
return total
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function formatCurrency(amount: number): string {
|
|
403
|
+
return new Intl.NumberFormat('en-US', {
|
|
404
|
+
style: 'currency',
|
|
405
|
+
currency: 'USD',
|
|
406
|
+
}).format(amount)
|
|
407
|
+
}
|
|
408
|
+
\`\`\`
|
|
409
|
+
|
|
410
|
+
And here is some text after the code block.`
|
|
411
|
+
|
|
412
|
+
const result = splitMarkdownForDiscord({
|
|
413
|
+
content,
|
|
414
|
+
maxLength: 200,
|
|
415
|
+
})
|
|
416
|
+
expect(result).toMatchInlineSnapshot(`
|
|
417
|
+
[
|
|
418
|
+
"Here is some explanation text before the code.
|
|
419
|
+
|
|
420
|
+
\`\`\`typescript
|
|
421
|
+
export function calculateTotal(items: Item[]): number {
|
|
422
|
+
let total = 0
|
|
423
|
+
for (const item of items) {
|
|
424
|
+
\`\`\`
|
|
425
|
+
",
|
|
426
|
+
"\`\`\`typescript
|
|
427
|
+
total += item.price * item.quantity
|
|
428
|
+
}
|
|
429
|
+
return total
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function formatCurrency(amount: number): string {
|
|
433
|
+
return new Intl.NumberFormat('en-US', {
|
|
434
|
+
style: 'currency',
|
|
435
|
+
\`\`\`
|
|
436
|
+
",
|
|
437
|
+
"\`\`\`typescript
|
|
438
|
+
currency: 'USD',
|
|
439
|
+
}).format(amount)
|
|
440
|
+
}
|
|
441
|
+
\`\`\`
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
And here is some text after the code block.",
|
|
445
|
+
]
|
|
446
|
+
`)
|
|
447
|
+
})
|
package/src/tools.ts
CHANGED
|
@@ -15,7 +15,10 @@ import { formatDistanceToNow } from 'date-fns'
|
|
|
15
15
|
|
|
16
16
|
import { ShareMarkdown } from './markdown.js'
|
|
17
17
|
import pc from 'picocolors'
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
initializeOpencodeForDirectory,
|
|
20
|
+
OPENCODE_SYSTEM_MESSAGE,
|
|
21
|
+
} from './discordBot.js'
|
|
19
22
|
|
|
20
23
|
export async function getTools({
|
|
21
24
|
onMessageCompleted,
|
|
@@ -72,10 +75,10 @@ export async function getTools({
|
|
|
72
75
|
getClient()
|
|
73
76
|
.session.prompt({
|
|
74
77
|
path: { id: sessionId },
|
|
75
|
-
|
|
76
78
|
body: {
|
|
77
79
|
parts: [{ type: 'text', text: message }],
|
|
78
80
|
model: sessionModel,
|
|
81
|
+
system: OPENCODE_SYSTEM_MESSAGE,
|
|
79
82
|
},
|
|
80
83
|
})
|
|
81
84
|
.then(async (response) => {
|
|
@@ -149,7 +152,7 @@ export async function getTools({
|
|
|
149
152
|
path: { id: session.data.id },
|
|
150
153
|
body: {
|
|
151
154
|
parts: [{ type: 'text', text: message }],
|
|
152
|
-
|
|
155
|
+
system: OPENCODE_SYSTEM_MESSAGE,
|
|
153
156
|
},
|
|
154
157
|
})
|
|
155
158
|
.then(async (response) => {
|