kimaki 0.4.34 → 0.4.36
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/ai-tool-to-genai.js +1 -3
- package/dist/channel-management.js +1 -1
- package/dist/cli.js +142 -39
- package/dist/commands/abort.js +1 -1
- package/dist/commands/add-project.js +1 -1
- package/dist/commands/agent.js +6 -2
- package/dist/commands/ask-question.js +2 -1
- package/dist/commands/fork.js +7 -7
- package/dist/commands/queue.js +2 -2
- package/dist/commands/remove-project.js +109 -0
- package/dist/commands/resume.js +3 -5
- package/dist/commands/session.js +56 -1
- package/dist/commands/share.js +1 -1
- package/dist/commands/undo-redo.js +2 -2
- package/dist/commands/user-command.js +3 -6
- package/dist/config.js +1 -1
- package/dist/discord-bot.js +4 -10
- package/dist/discord-utils.js +33 -9
- package/dist/genai.js +4 -6
- package/dist/interaction-handler.js +8 -1
- package/dist/markdown.js +1 -3
- package/dist/message-formatting.js +7 -3
- package/dist/openai-realtime.js +3 -5
- package/dist/opencode.js +2 -3
- package/dist/session-handler.js +42 -25
- package/dist/system-message.js +5 -3
- package/dist/tools.js +9 -22
- package/dist/unnest-code-blocks.js +4 -2
- package/dist/unnest-code-blocks.test.js +40 -15
- package/dist/voice-handler.js +9 -12
- package/dist/voice.js +5 -3
- package/dist/xml.js +2 -4
- package/package.json +3 -2
- package/src/__snapshots__/compact-session-context-no-system.md +24 -24
- package/src/__snapshots__/compact-session-context.md +31 -31
- package/src/ai-tool-to-genai.ts +3 -11
- package/src/channel-management.ts +14 -25
- package/src/cli.ts +290 -195
- package/src/commands/abort.ts +1 -3
- package/src/commands/add-project.ts +8 -14
- package/src/commands/agent.ts +16 -9
- package/src/commands/ask-question.ts +8 -7
- package/src/commands/create-new-project.ts +8 -14
- package/src/commands/fork.ts +23 -27
- package/src/commands/model.ts +14 -11
- package/src/commands/permissions.ts +1 -1
- package/src/commands/queue.ts +6 -19
- package/src/commands/remove-project.ts +136 -0
- package/src/commands/resume.ts +11 -30
- package/src/commands/session.ts +68 -9
- package/src/commands/share.ts +1 -3
- package/src/commands/types.ts +1 -3
- package/src/commands/undo-redo.ts +6 -18
- package/src/commands/user-command.ts +8 -10
- package/src/config.ts +5 -5
- package/src/database.ts +10 -8
- package/src/discord-bot.ts +22 -46
- package/src/discord-utils.ts +35 -18
- package/src/escape-backticks.test.ts +0 -2
- package/src/format-tables.ts +1 -4
- package/src/genai-worker-wrapper.ts +3 -9
- package/src/genai-worker.ts +4 -19
- package/src/genai.ts +10 -42
- package/src/interaction-handler.ts +133 -121
- package/src/markdown.test.ts +10 -32
- package/src/markdown.ts +6 -14
- package/src/message-formatting.ts +13 -14
- package/src/openai-realtime.ts +25 -47
- package/src/opencode.ts +26 -37
- package/src/session-handler.ts +111 -75
- package/src/system-message.ts +13 -3
- package/src/tools.ts +13 -39
- package/src/unnest-code-blocks.test.ts +42 -15
- package/src/unnest-code-blocks.ts +4 -2
- package/src/utils.ts +1 -4
- package/src/voice-handler.ts +34 -78
- package/src/voice.ts +11 -19
- package/src/xml.test.ts +1 -1
- package/src/xml.ts +3 -12
package/dist/tools.js
CHANGED
|
@@ -11,7 +11,7 @@ const toolsLogger = createLogger('TOOLS');
|
|
|
11
11
|
import { ShareMarkdown } from './markdown.js';
|
|
12
12
|
import { formatDistanceToNow } from './utils.js';
|
|
13
13
|
import pc from 'picocolors';
|
|
14
|
-
import { initializeOpencodeForDirectory, getOpencodeSystemMessage
|
|
14
|
+
import { initializeOpencodeForDirectory, getOpencodeSystemMessage } from './discord-bot.js';
|
|
15
15
|
export async function getTools({ onMessageCompleted, directory, }) {
|
|
16
16
|
const getClient = await initializeOpencodeForDirectory(directory);
|
|
17
17
|
const client = getClient();
|
|
@@ -83,23 +83,17 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
83
83
|
createNewChat: tool({
|
|
84
84
|
description: 'Start a new chat session with an initial message. Does not wait for the message to complete',
|
|
85
85
|
inputSchema: z.object({
|
|
86
|
-
message: z
|
|
87
|
-
.string()
|
|
88
|
-
.describe('The initial message to start the chat with'),
|
|
86
|
+
message: z.string().describe('The initial message to start the chat with'),
|
|
89
87
|
title: z.string().optional().describe('Optional title for the session'),
|
|
90
88
|
model: z
|
|
91
89
|
.object({
|
|
92
|
-
providerId: z
|
|
93
|
-
|
|
94
|
-
.describe('The provider ID (e.g., "anthropic", "openai")'),
|
|
95
|
-
modelId: z
|
|
96
|
-
.string()
|
|
97
|
-
.describe('The model ID (e.g., "claude-opus-4-20250514", "gpt-5")'),
|
|
90
|
+
providerId: z.string().describe('The provider ID (e.g., "anthropic", "openai")'),
|
|
91
|
+
modelId: z.string().describe('The model ID (e.g., "claude-opus-4-20250514", "gpt-5")'),
|
|
98
92
|
})
|
|
99
93
|
.optional()
|
|
100
94
|
.describe('Optional model to use for this session'),
|
|
101
95
|
}),
|
|
102
|
-
execute: async ({ message, title
|
|
96
|
+
execute: async ({ message, title }) => {
|
|
103
97
|
if (!message.trim()) {
|
|
104
98
|
throw new Error(`message must be a non empty string`);
|
|
105
99
|
}
|
|
@@ -149,9 +143,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
149
143
|
catch (error) {
|
|
150
144
|
return {
|
|
151
145
|
success: false,
|
|
152
|
-
error: error instanceof Error
|
|
153
|
-
? error.message
|
|
154
|
-
: 'Failed to create chat session',
|
|
146
|
+
error: error instanceof Error ? error.message : 'Failed to create chat session',
|
|
155
147
|
};
|
|
156
148
|
}
|
|
157
149
|
},
|
|
@@ -180,8 +172,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
180
172
|
});
|
|
181
173
|
const messages = messagesResponse.data || [];
|
|
182
174
|
const lastMessage = messages[messages.length - 1];
|
|
183
|
-
if (lastMessage?.info.role === 'assistant' &&
|
|
184
|
-
!lastMessage.info.time.completed) {
|
|
175
|
+
if (lastMessage?.info.role === 'assistant' && !lastMessage.info.time.completed) {
|
|
185
176
|
return 'in_progress';
|
|
186
177
|
}
|
|
187
178
|
return 'finished';
|
|
@@ -228,10 +219,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
228
219
|
description: 'Read messages from a chat session',
|
|
229
220
|
inputSchema: z.object({
|
|
230
221
|
sessionId: z.string().describe('The session ID to read messages from'),
|
|
231
|
-
lastAssistantOnly: z
|
|
232
|
-
.boolean()
|
|
233
|
-
.optional()
|
|
234
|
-
.describe('Only read the last assistant message'),
|
|
222
|
+
lastAssistantOnly: z.boolean().optional().describe('Only read the last assistant message'),
|
|
235
223
|
}),
|
|
236
224
|
execute: async ({ sessionId, lastAssistantOnly = false }) => {
|
|
237
225
|
if (lastAssistantOnly) {
|
|
@@ -249,8 +237,7 @@ export async function getTools({ onMessageCompleted, directory, }) {
|
|
|
249
237
|
};
|
|
250
238
|
}
|
|
251
239
|
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
252
|
-
const status = 'completed' in lastMessage.info.time &&
|
|
253
|
-
lastMessage.info.time.completed
|
|
240
|
+
const status = 'completed' in lastMessage.info.time && lastMessage.info.time.completed
|
|
254
241
|
? 'completed'
|
|
255
242
|
: 'in_progress';
|
|
256
243
|
const markdown = await markdownRenderer.generate({
|
|
@@ -107,9 +107,11 @@ function renderSegments(segments) {
|
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
109
|
// Raw content (no prefix means it's original raw)
|
|
110
|
-
|
|
110
|
+
// Ensure raw ends with newline for proper separation from next segment
|
|
111
|
+
const raw = segment.content.trimEnd();
|
|
112
|
+
result.push(raw + '\n');
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
|
-
return result.join('');
|
|
116
|
+
return result.join('').trimEnd();
|
|
115
117
|
}
|
|
@@ -11,8 +11,7 @@ test('basic - single item with code block', () => {
|
|
|
11
11
|
|
|
12
12
|
\`\`\`js
|
|
13
13
|
const x = 1
|
|
14
|
-
\`\`\`
|
|
15
|
-
"
|
|
14
|
+
\`\`\`"
|
|
16
15
|
`);
|
|
17
16
|
});
|
|
18
17
|
test('multiple items - code in middle item only', () => {
|
|
@@ -50,8 +49,7 @@ test('multiple code blocks in one item', () => {
|
|
|
50
49
|
\`\`\`
|
|
51
50
|
\`\`\`python
|
|
52
51
|
b = 2
|
|
53
|
-
\`\`\`
|
|
54
|
-
"
|
|
52
|
+
\`\`\`"
|
|
55
53
|
`);
|
|
56
54
|
});
|
|
57
55
|
test('nested list with code', () => {
|
|
@@ -125,8 +123,7 @@ test('mixed - some items have code, some dont', () => {
|
|
|
125
123
|
|
|
126
124
|
\`\`\`python
|
|
127
125
|
y = 2
|
|
128
|
-
\`\`\`
|
|
129
|
-
"
|
|
126
|
+
\`\`\`"
|
|
130
127
|
`);
|
|
131
128
|
});
|
|
132
129
|
test('text before and after code in same item', () => {
|
|
@@ -142,8 +139,7 @@ test('text before and after code in same item', () => {
|
|
|
142
139
|
\`\`\`js
|
|
143
140
|
const x = 1
|
|
144
141
|
\`\`\`
|
|
145
|
-
- End text
|
|
146
|
-
"
|
|
142
|
+
- End text"
|
|
147
143
|
`);
|
|
148
144
|
});
|
|
149
145
|
test('preserves content outside lists', () => {
|
|
@@ -169,7 +165,6 @@ More text after.`;
|
|
|
169
165
|
const x = 1
|
|
170
166
|
\`\`\`
|
|
171
167
|
|
|
172
|
-
|
|
173
168
|
More text after."
|
|
174
169
|
`);
|
|
175
170
|
});
|
|
@@ -195,8 +190,7 @@ test('handles code block without language', () => {
|
|
|
195
190
|
|
|
196
191
|
\`\`\`
|
|
197
192
|
plain code
|
|
198
|
-
\`\`\`
|
|
199
|
-
"
|
|
193
|
+
\`\`\`"
|
|
200
194
|
`);
|
|
201
195
|
});
|
|
202
196
|
test('handles empty list item with code', () => {
|
|
@@ -207,8 +201,7 @@ test('handles empty list item with code', () => {
|
|
|
207
201
|
expect(result).toMatchInlineSnapshot(`
|
|
208
202
|
"\`\`\`js
|
|
209
203
|
const x = 1
|
|
210
|
-
\`\`\`
|
|
211
|
-
"
|
|
204
|
+
\`\`\`"
|
|
212
205
|
`);
|
|
213
206
|
});
|
|
214
207
|
test('numbered list with text after code block', () => {
|
|
@@ -321,7 +314,8 @@ test('deeply nested list with code', () => {
|
|
|
321
314
|
deep code
|
|
322
315
|
\`\`\`
|
|
323
316
|
- Text after deep code
|
|
324
|
-
- Another level 3
|
|
317
|
+
- Another level 3
|
|
318
|
+
- Back to level 2"
|
|
325
319
|
`);
|
|
326
320
|
});
|
|
327
321
|
test('nested numbered list inside unordered with code', () => {
|
|
@@ -342,7 +336,8 @@ test('nested numbered list inside unordered with code', () => {
|
|
|
342
336
|
code
|
|
343
337
|
\`\`\`
|
|
344
338
|
- Text after
|
|
345
|
-
2. Second nested
|
|
339
|
+
2. Second nested
|
|
340
|
+
- Another unordered"
|
|
346
341
|
`);
|
|
347
342
|
});
|
|
348
343
|
test('code block at end of numbered item no text after', () => {
|
|
@@ -405,3 +400,33 @@ test('code block immediately after list marker', () => {
|
|
|
405
400
|
2. Normal item"
|
|
406
401
|
`);
|
|
407
402
|
});
|
|
403
|
+
test('code block with filename metadata', () => {
|
|
404
|
+
const input = `- Item with code
|
|
405
|
+
\`\`\`tsx filename=example.tsx
|
|
406
|
+
const x = 1
|
|
407
|
+
\`\`\``;
|
|
408
|
+
const result = unnestCodeBlocksFromLists(input);
|
|
409
|
+
expect(result).toMatchInlineSnapshot(`
|
|
410
|
+
"- Item with code
|
|
411
|
+
|
|
412
|
+
\`\`\`tsx filename=example.tsx
|
|
413
|
+
const x = 1
|
|
414
|
+
\`\`\`"
|
|
415
|
+
`);
|
|
416
|
+
});
|
|
417
|
+
test('numbered list with filename metadata code block', () => {
|
|
418
|
+
const input = `1. First item
|
|
419
|
+
\`\`\`tsx filename=app.tsx
|
|
420
|
+
export default function App() {}
|
|
421
|
+
\`\`\`
|
|
422
|
+
2. Second item`;
|
|
423
|
+
const result = unnestCodeBlocksFromLists(input);
|
|
424
|
+
expect(result).toMatchInlineSnapshot(`
|
|
425
|
+
"1. First item
|
|
426
|
+
|
|
427
|
+
\`\`\`tsx filename=app.tsx
|
|
428
|
+
export default function App() {}
|
|
429
|
+
\`\`\`
|
|
430
|
+
2. Second item"
|
|
431
|
+
`);
|
|
432
|
+
});
|
package/dist/voice-handler.js
CHANGED
|
@@ -13,7 +13,7 @@ import dedent from 'string-dedent';
|
|
|
13
13
|
import { PermissionsBitField, Events, } from 'discord.js';
|
|
14
14
|
import { createGenAIWorker } from './genai-worker-wrapper.js';
|
|
15
15
|
import { getDatabase } from './database.js';
|
|
16
|
-
import { sendThreadMessage, escapeDiscordFormatting, SILENT_MESSAGE_FLAGS } from './discord-utils.js';
|
|
16
|
+
import { sendThreadMessage, escapeDiscordFormatting, SILENT_MESSAGE_FLAGS, } from './discord-utils.js';
|
|
17
17
|
import { transcribeAudio } from './voice.js';
|
|
18
18
|
import { createLogger } from './logger.js';
|
|
19
19
|
const voiceLogger = createLogger('VOICE');
|
|
@@ -189,7 +189,10 @@ export async function setupVoiceHandling({ connection, guildId, channelId, appId
|
|
|
189
189
|
try {
|
|
190
190
|
const textChannel = await discordClient.channels.fetch(textChannelRow.channel_id);
|
|
191
191
|
if (textChannel?.isTextBased() && 'send' in textChannel) {
|
|
192
|
-
await textChannel.send({
|
|
192
|
+
await textChannel.send({
|
|
193
|
+
content: `⚠️ Voice session error: ${error}`,
|
|
194
|
+
flags: SILENT_MESSAGE_FLAGS,
|
|
195
|
+
});
|
|
193
196
|
}
|
|
194
197
|
}
|
|
195
198
|
catch (e) {
|
|
@@ -235,10 +238,7 @@ export async function setupVoiceHandling({ connection, guildId, channelId, appId
|
|
|
235
238
|
},
|
|
236
239
|
});
|
|
237
240
|
const framer = frameMono16khz();
|
|
238
|
-
const pipeline = audioStream
|
|
239
|
-
.pipe(decoder)
|
|
240
|
-
.pipe(downsampleTransform)
|
|
241
|
-
.pipe(framer);
|
|
241
|
+
const pipeline = audioStream.pipe(decoder).pipe(downsampleTransform).pipe(framer);
|
|
242
242
|
pipeline
|
|
243
243
|
.on('data', (frame) => {
|
|
244
244
|
if (currentSessionCount !== speakingSessionCount) {
|
|
@@ -404,8 +404,7 @@ export function registerVoiceStateHandler({ discordClient, appId, }) {
|
|
|
404
404
|
voiceLogger.log(`Admin user ${member.user.tag} left voice channel: ${oldState.channel?.name}`);
|
|
405
405
|
const guildId = guild.id;
|
|
406
406
|
const voiceData = voiceConnections.get(guildId);
|
|
407
|
-
if (voiceData &&
|
|
408
|
-
voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
407
|
+
if (voiceData && voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
409
408
|
const voiceChannel = oldState.channel;
|
|
410
409
|
if (!voiceChannel)
|
|
411
410
|
return;
|
|
@@ -433,8 +432,7 @@ export function registerVoiceStateHandler({ discordClient, appId, }) {
|
|
|
433
432
|
voiceLogger.log(`Admin user ${member.user.tag} moved from ${oldState.channel?.name} to ${newState.channel?.name}`);
|
|
434
433
|
const guildId = guild.id;
|
|
435
434
|
const voiceData = voiceConnections.get(guildId);
|
|
436
|
-
if (voiceData &&
|
|
437
|
-
voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
435
|
+
if (voiceData && voiceData.connection.joinConfig.channelId === oldState.channelId) {
|
|
438
436
|
const oldVoiceChannel = oldState.channel;
|
|
439
437
|
if (oldVoiceChannel) {
|
|
440
438
|
const hasOtherAdmins = oldVoiceChannel.members.some((m) => {
|
|
@@ -472,8 +470,7 @@ export function registerVoiceStateHandler({ discordClient, appId, }) {
|
|
|
472
470
|
return;
|
|
473
471
|
const existingVoiceData = voiceConnections.get(newState.guild.id);
|
|
474
472
|
if (existingVoiceData &&
|
|
475
|
-
existingVoiceData.connection.state.status !==
|
|
476
|
-
VoiceConnectionStatus.Destroyed) {
|
|
473
|
+
existingVoiceData.connection.state.status !== VoiceConnectionStatus.Destroyed) {
|
|
477
474
|
voiceLogger.log(`Bot already connected to a voice channel in guild ${newState.guild.name}`);
|
|
478
475
|
if (existingVoiceData.connection.joinConfig.channelId !== voiceChannel.id) {
|
|
479
476
|
voiceLogger.log(`Moving bot from channel ${existingVoiceData.connection.joinConfig.channelId} to ${voiceChannel.id}`);
|
package/dist/voice.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Audio transcription service using Google Gemini.
|
|
2
2
|
// Transcribes voice messages with code-aware context, using grep/glob tools
|
|
3
3
|
// to verify technical terms, filenames, and function names in the codebase.
|
|
4
|
-
import { GoogleGenAI, Type
|
|
4
|
+
import { GoogleGenAI, Type } from '@google/genai';
|
|
5
5
|
import { createLogger } from './logger.js';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import { ripGrep } from 'ripgrep-js';
|
|
@@ -87,7 +87,7 @@ const transcriptionResultToolDeclaration = {
|
|
|
87
87
|
required: ['transcription'],
|
|
88
88
|
},
|
|
89
89
|
};
|
|
90
|
-
function createToolRunner({ directory
|
|
90
|
+
function createToolRunner({ directory }) {
|
|
91
91
|
const hasDirectory = directory && directory.trim().length > 0;
|
|
92
92
|
return async ({ name, args }) => {
|
|
93
93
|
if (name === 'transcriptionResult') {
|
|
@@ -200,7 +200,9 @@ export async function runTranscriptionLoop({ genAI, model, initialContents, tool
|
|
|
200
200
|
thinkingConfig: {
|
|
201
201
|
thinkingBudget: 512,
|
|
202
202
|
},
|
|
203
|
-
tools: stepsRemaining <= 0
|
|
203
|
+
tools: stepsRemaining <= 0
|
|
204
|
+
? [{ functionDeclarations: [transcriptionResultToolDeclaration] }]
|
|
205
|
+
: tools,
|
|
204
206
|
},
|
|
205
207
|
});
|
|
206
208
|
}
|
package/dist/xml.js
CHANGED
|
@@ -27,8 +27,7 @@ export function extractTagsArrays({ xml, tags, }) {
|
|
|
27
27
|
// Extract content using original string positions
|
|
28
28
|
const extractContent = () => {
|
|
29
29
|
// Use element's own indices but exclude the tags
|
|
30
|
-
if (element.startIndex !== null &&
|
|
31
|
-
element.endIndex !== null) {
|
|
30
|
+
if (element.startIndex !== null && element.endIndex !== null) {
|
|
32
31
|
// Extract the full element including tags
|
|
33
32
|
const fullElement = xml.substring(element.startIndex, element.endIndex + 1);
|
|
34
33
|
// Find where content starts (after opening tag)
|
|
@@ -57,8 +56,7 @@ export function extractTagsArrays({ xml, tags, }) {
|
|
|
57
56
|
findTags(element.children, currentPath);
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
|
-
else if (node.type === ElementType.Text &&
|
|
61
|
-
node.parent?.type === ElementType.Root) {
|
|
59
|
+
else if (node.type === ElementType.Text && node.parent?.type === ElementType.Root) {
|
|
62
60
|
const textNode = node;
|
|
63
61
|
if (textNode.data.trim()) {
|
|
64
62
|
// console.log('node.parent',node.parent)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "kimaki",
|
|
3
3
|
"module": "index.ts",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.36",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "tsx --env-file .env src/cli.ts",
|
|
8
8
|
"prepublishOnly": "pnpm tsc",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test:events": "tsx test-events.ts",
|
|
12
12
|
"pcm-to-mp3": "bun scripts/pcm-to-mp3",
|
|
13
13
|
"test:send": "tsx send-test-message.ts",
|
|
14
|
-
"register-commands": "tsx scripts/register-commands.ts"
|
|
14
|
+
"register-commands": "tsx scripts/register-commands.ts",
|
|
15
|
+
"format": "oxfmt src"
|
|
15
16
|
},
|
|
16
17
|
"repository": "https://github.com/remorses/kimaki",
|
|
17
18
|
"bin": "bin.js",
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
[Assistant]: Still not showing. The session might not have a `system` field and the first assistant message might not have `synthetic: true`. Let me add debug logging to see what's available:
|
|
2
2
|
|
|
3
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= // Get system prompt if requested
|
|
4
|
+
if (includeSystemPrompt) {
|
|
5
|
+
// try session.system fie, newString= // Get system prompt if requested
|
|
6
|
+
if (includeSystemPrompt) {
|
|
7
|
+
// try session.system fie
|
|
8
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= } else if (messages.length > 0) {
|
|
9
|
+
// fallback: look for synthetic text part in first a, newString= } else if (messages.length > 0) {
|
|
10
|
+
// fallback: look for synthetic text part in first a
|
|
11
11
|
[Tool bash]: command=pnpm vitest run --testNamePattern "getCompactSessionContext generates" 2>&1 | tail -30, description=run test and check output, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord, timeout=120000
|
|
12
12
|
[Tool bash]: command=grep -i "session keys\|text parts" tmp/kimaki.log | tail -10, description=check debug output, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord
|
|
13
13
|
[Assistant]: Session doesn't have a `system` field. Let me fix the debug logging:
|
|
14
14
|
|
|
15
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= const textParts = (firstAssistant.parts || []).filter((p) => p.type === 'text')
|
|
16
|
+
, newString= const textParts = (firstAssistant.parts || []).filter((p) => p.type === 'text')
|
|
17
|
+
|
|
18
18
|
[Tool bash]: command=pnpm vitest run --testNamePattern "getCompactSessionContext generates" 2>&1 && grep -i "text parts" , description=run test and check debug, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord, timeout=120000
|
|
19
19
|
[Assistant]: I see - the first assistant text part has **no `synthetic` field** (it's `undefined`), and it's only 44 chars. The session doesn't expose the system prompt.
|
|
20
20
|
|
|
21
21
|
Let me remove debug logging and fix the test:
|
|
22
22
|
|
|
23
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.test.ts, oldString=
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= // Get system prompt if requested
|
|
24
|
+
if (includeSystemPrompt) {
|
|
25
|
+
// try session.system fie, newString= // Get system prompt if requested
|
|
26
|
+
// Note: OpenCode SDK doesn't expose system prompt directl
|
|
27
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= try {
|
|
28
|
+
// fetch session info to get system prompt
|
|
29
|
+
const sessionResponse = await client.sess, newString= try {
|
|
30
|
+
const messagesResponse = await client.session.messages({
|
|
31
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.test.ts, oldString= expect(context).toBeTruthy()
|
|
32
|
+
expect(context.length).toBeGreaterThan(0)
|
|
33
|
+
// should have tool cal, newString= expect(context).toBeTruthy()
|
|
34
|
+
expect(context.length).toBeGreaterThan(0)
|
|
35
|
+
// should have tool cal
|
|
@@ -2,46 +2,46 @@
|
|
|
2
2
|
[Tool read]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, offset=248, limit=35
|
|
3
3
|
[Assistant]: Let me update the code to also look for system role messages and fetch session info:
|
|
4
4
|
|
|
5
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
, newString=
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= try {
|
|
6
|
+
const messagesResponse = await client.session.messages({
|
|
7
|
+
path: { id: sessionId },
|
|
8
|
+
, newString= try {
|
|
9
|
+
// fetch session info to get system prompt
|
|
10
|
+
const sessionResponse = await client.sess
|
|
11
11
|
[Tool bash]: command=pnpm vitest run -u --testNamePattern "getCompactSessionContext", description=run tests to check system prompt, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord, timeout=120000
|
|
12
|
-
[Tool bash]: command=head -30 discord/src
|
|
12
|
+
[Tool bash]: command=head -30 discord/src/**snapshots**/compact-session-context.md, description=check if system prompt is in snapshot
|
|
13
13
|
[Assistant]: Still not showing. The session might not have a `system` field and the first assistant message might not have `synthetic: true`. Let me add debug logging to see what's available:
|
|
14
14
|
|
|
15
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= // Get system prompt if requested
|
|
16
|
+
if (includeSystemPrompt) {
|
|
17
|
+
// try session.system fie, newString= // Get system prompt if requested
|
|
18
|
+
if (includeSystemPrompt) {
|
|
19
|
+
// try session.system fie
|
|
20
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= } else if (messages.length > 0) {
|
|
21
|
+
// fallback: look for synthetic text part in first a, newString= } else if (messages.length > 0) {
|
|
22
|
+
// fallback: look for synthetic text part in first a
|
|
23
23
|
[Tool bash]: command=pnpm vitest run --testNamePattern "getCompactSessionContext generates" 2>&1 | tail -30, description=run test and check output, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord, timeout=120000
|
|
24
24
|
[Tool bash]: command=grep -i "session keys\|text parts" tmp/kimaki.log | tail -10, description=check debug output, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord
|
|
25
25
|
[Assistant]: Session doesn't have a `system` field. Let me fix the debug logging:
|
|
26
26
|
|
|
27
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= const textParts = (firstAssistant.parts || []).filter((p) => p.type === 'text')
|
|
28
|
+
, newString= const textParts = (firstAssistant.parts || []).filter((p) => p.type === 'text')
|
|
29
|
+
|
|
30
30
|
[Tool bash]: command=pnpm vitest run --testNamePattern "getCompactSessionContext generates" 2>&1 && grep -i "text parts" , description=run test and check debug, workdir=/Users/morse/Documents/GitHub/kimakivoice/discord, timeout=120000
|
|
31
31
|
[Assistant]: I see - the first assistant text part has **no `synthetic` field** (it's `undefined`), and it's only 44 chars. The session doesn't expose the system prompt.
|
|
32
32
|
|
|
33
33
|
Let me remove debug logging and fix the test:
|
|
34
34
|
|
|
35
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString=
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.test.ts, oldString=
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= // Get system prompt if requested
|
|
36
|
+
if (includeSystemPrompt) {
|
|
37
|
+
// try session.system fie, newString= // Get system prompt if requested
|
|
38
|
+
// Note: OpenCode SDK doesn't expose system prompt directl
|
|
39
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.ts, oldString= try {
|
|
40
|
+
// fetch session info to get system prompt
|
|
41
|
+
const sessionResponse = await client.sess, newString= try {
|
|
42
|
+
const messagesResponse = await client.session.messages({
|
|
43
|
+
[Tool edit]: filePath=/Users/morse/Documents/GitHub/kimakivoice/discord/src/markdown.test.ts, oldString= expect(context).toBeTruthy()
|
|
44
|
+
expect(context.length).toBeGreaterThan(0)
|
|
45
|
+
// should have tool cal, newString= expect(context).toBeTruthy()
|
|
46
|
+
expect(context.length).toBeGreaterThan(0)
|
|
47
|
+
// should have tool cal
|
package/src/ai-tool-to-genai.ts
CHANGED
|
@@ -119,9 +119,7 @@ function jsonSchemaToGenAISchema(jsonSchema: any): Schema {
|
|
|
119
119
|
/**
|
|
120
120
|
* Convert AI SDK Tool to GenAI FunctionDeclaration
|
|
121
121
|
*/
|
|
122
|
-
export function aiToolToGenAIFunction(
|
|
123
|
-
tool: Tool<any, any>,
|
|
124
|
-
): FunctionDeclaration {
|
|
122
|
+
export function aiToolToGenAIFunction(tool: Tool<any, any>): FunctionDeclaration {
|
|
125
123
|
// Extract the input schema - assume it's a Zod schema
|
|
126
124
|
const inputSchema = tool.inputSchema as z.ZodType<any>
|
|
127
125
|
|
|
@@ -183,11 +181,7 @@ export function aiToolToCallableTool(
|
|
|
183
181
|
|
|
184
182
|
for (const functionCall of functionCalls) {
|
|
185
183
|
// Check if this function call matches our tool
|
|
186
|
-
if (
|
|
187
|
-
functionCall.name !== toolName &&
|
|
188
|
-
name &&
|
|
189
|
-
functionCall.name !== name
|
|
190
|
-
) {
|
|
184
|
+
if (functionCall.name !== toolName && name && functionCall.name !== name) {
|
|
191
185
|
continue
|
|
192
186
|
}
|
|
193
187
|
|
|
@@ -249,7 +243,5 @@ export function extractSchemaFromTool(tool: Tool<any, any>): any {
|
|
|
249
243
|
export function callableToolsFromObject(
|
|
250
244
|
tools: Record<string, Tool<any, any>>,
|
|
251
245
|
): Array<CallableTool & { name: string }> {
|
|
252
|
-
return Object.entries(tools).map(([name, tool]) =>
|
|
253
|
-
aiToolToCallableTool(tool, name),
|
|
254
|
-
)
|
|
246
|
+
return Object.entries(tools).map(([name, tool]) => aiToolToCallableTool(tool, name))
|
|
255
247
|
}
|
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
// Creates and manages Kimaki project channels (text + voice pairs),
|
|
3
3
|
// extracts channel metadata from topic tags, and ensures category structure.
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
ChannelType,
|
|
7
|
-
type CategoryChannel,
|
|
8
|
-
type Guild,
|
|
9
|
-
type TextChannel,
|
|
10
|
-
} from 'discord.js'
|
|
5
|
+
import { ChannelType, type CategoryChannel, type Guild, type TextChannel } from 'discord.js'
|
|
11
6
|
import path from 'node:path'
|
|
12
7
|
import { getDatabase } from './database.js'
|
|
13
8
|
import { extractTagsArrays } from './xml.js'
|
|
@@ -18,15 +13,13 @@ export async function ensureKimakiCategory(
|
|
|
18
13
|
): Promise<CategoryChannel> {
|
|
19
14
|
const categoryName = botName ? `Kimaki ${botName}` : 'Kimaki'
|
|
20
15
|
|
|
21
|
-
const existingCategory = guild.channels.cache.find(
|
|
22
|
-
(channel
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
16
|
+
const existingCategory = guild.channels.cache.find((channel): channel is CategoryChannel => {
|
|
17
|
+
if (channel.type !== ChannelType.GuildCategory) {
|
|
18
|
+
return false
|
|
19
|
+
}
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
21
|
+
return channel.name.toLowerCase() === categoryName.toLowerCase()
|
|
22
|
+
})
|
|
30
23
|
|
|
31
24
|
if (existingCategory) {
|
|
32
25
|
return existingCategory
|
|
@@ -44,15 +37,13 @@ export async function ensureKimakiAudioCategory(
|
|
|
44
37
|
): Promise<CategoryChannel> {
|
|
45
38
|
const categoryName = botName ? `Kimaki Audio ${botName}` : 'Kimaki Audio'
|
|
46
39
|
|
|
47
|
-
const existingCategory = guild.channels.cache.find(
|
|
48
|
-
(channel
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
40
|
+
const existingCategory = guild.channels.cache.find((channel): channel is CategoryChannel => {
|
|
41
|
+
if (channel.type !== ChannelType.GuildCategory) {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
45
|
+
return channel.name.toLowerCase() === categoryName.toLowerCase()
|
|
46
|
+
})
|
|
56
47
|
|
|
57
48
|
if (existingCategory) {
|
|
58
49
|
return existingCategory
|
|
@@ -124,9 +115,7 @@ export type ChannelWithTags = {
|
|
|
124
115
|
kimakiApp?: string
|
|
125
116
|
}
|
|
126
117
|
|
|
127
|
-
export async function getChannelsWithDescriptions(
|
|
128
|
-
guild: Guild,
|
|
129
|
-
): Promise<ChannelWithTags[]> {
|
|
118
|
+
export async function getChannelsWithDescriptions(guild: Guild): Promise<ChannelWithTags[]> {
|
|
130
119
|
const channels: ChannelWithTags[] = []
|
|
131
120
|
|
|
132
121
|
guild.channels.cache
|