@brianli/kimaki 0.4.72-brianli.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 (328) hide show
  1. package/bin.js +2 -0
  2. package/dist/ai-tool-to-genai.js +233 -0
  3. package/dist/ai-tool-to-genai.test.js +267 -0
  4. package/dist/ai-tool.js +6 -0
  5. package/dist/bin.js +87 -0
  6. package/dist/bot-token.js +121 -0
  7. package/dist/bot-token.test.js +134 -0
  8. package/dist/channel-management.js +101 -0
  9. package/dist/cli-parsing.test.js +89 -0
  10. package/dist/cli.js +2529 -0
  11. package/dist/commands/abort.js +82 -0
  12. package/dist/commands/action-buttons.js +257 -0
  13. package/dist/commands/add-project.js +114 -0
  14. package/dist/commands/agent.js +291 -0
  15. package/dist/commands/ask-question.js +223 -0
  16. package/dist/commands/compact.js +120 -0
  17. package/dist/commands/context-usage.js +140 -0
  18. package/dist/commands/create-new-project.js +118 -0
  19. package/dist/commands/diff.js +128 -0
  20. package/dist/commands/file-upload.js +275 -0
  21. package/dist/commands/fork.js +217 -0
  22. package/dist/commands/gemini-apikey.js +70 -0
  23. package/dist/commands/login.js +490 -0
  24. package/dist/commands/mention-mode.js +51 -0
  25. package/dist/commands/merge-worktree.js +124 -0
  26. package/dist/commands/model.js +694 -0
  27. package/dist/commands/permissions.js +163 -0
  28. package/dist/commands/queue.js +217 -0
  29. package/dist/commands/remove-project.js +115 -0
  30. package/dist/commands/restart-opencode-server.js +116 -0
  31. package/dist/commands/resume.js +159 -0
  32. package/dist/commands/run-command.js +79 -0
  33. package/dist/commands/session-id.js +78 -0
  34. package/dist/commands/session.js +192 -0
  35. package/dist/commands/share.js +80 -0
  36. package/dist/commands/types.js +2 -0
  37. package/dist/commands/undo-redo.js +159 -0
  38. package/dist/commands/unset-model.js +152 -0
  39. package/dist/commands/upgrade.js +42 -0
  40. package/dist/commands/user-command.js +148 -0
  41. package/dist/commands/verbosity.js +60 -0
  42. package/dist/commands/worktree-settings.js +50 -0
  43. package/dist/commands/worktree.js +299 -0
  44. package/dist/condense-memory.js +33 -0
  45. package/dist/config.js +110 -0
  46. package/dist/database.js +1050 -0
  47. package/dist/db.js +159 -0
  48. package/dist/db.test.js +49 -0
  49. package/dist/discord-api.js +28 -0
  50. package/dist/discord-auth.js +231 -0
  51. package/dist/discord-auth.test.js +80 -0
  52. package/dist/discord-bot.js +997 -0
  53. package/dist/discord-utils.js +560 -0
  54. package/dist/discord-utils.test.js +115 -0
  55. package/dist/errors.js +167 -0
  56. package/dist/escape-backticks.test.js +429 -0
  57. package/dist/format-tables.js +122 -0
  58. package/dist/format-tables.test.js +199 -0
  59. package/dist/forum-sync/config.js +79 -0
  60. package/dist/forum-sync/discord-operations.js +154 -0
  61. package/dist/forum-sync/index.js +5 -0
  62. package/dist/forum-sync/markdown.js +117 -0
  63. package/dist/forum-sync/sync-to-discord.js +417 -0
  64. package/dist/forum-sync/sync-to-files.js +190 -0
  65. package/dist/forum-sync/types.js +53 -0
  66. package/dist/forum-sync/watchers.js +307 -0
  67. package/dist/gateway-consumer.js +232 -0
  68. package/dist/gateway-consumer.test.js +18 -0
  69. package/dist/genai-worker-wrapper.js +111 -0
  70. package/dist/genai-worker.js +311 -0
  71. package/dist/genai.js +232 -0
  72. package/dist/generated/browser.js +17 -0
  73. package/dist/generated/client.js +35 -0
  74. package/dist/generated/commonInputTypes.js +10 -0
  75. package/dist/generated/enums.js +30 -0
  76. package/dist/generated/internal/class.js +41 -0
  77. package/dist/generated/internal/prismaNamespace.js +239 -0
  78. package/dist/generated/internal/prismaNamespaceBrowser.js +209 -0
  79. package/dist/generated/models/bot_api_keys.js +1 -0
  80. package/dist/generated/models/bot_tokens.js +1 -0
  81. package/dist/generated/models/channel_agents.js +1 -0
  82. package/dist/generated/models/channel_directories.js +1 -0
  83. package/dist/generated/models/channel_mention_mode.js +1 -0
  84. package/dist/generated/models/channel_models.js +1 -0
  85. package/dist/generated/models/channel_verbosity.js +1 -0
  86. package/dist/generated/models/channel_worktrees.js +1 -0
  87. package/dist/generated/models/forum_sync_configs.js +1 -0
  88. package/dist/generated/models/global_models.js +1 -0
  89. package/dist/generated/models/ipc_requests.js +1 -0
  90. package/dist/generated/models/part_messages.js +1 -0
  91. package/dist/generated/models/scheduled_tasks.js +1 -0
  92. package/dist/generated/models/session_agents.js +1 -0
  93. package/dist/generated/models/session_models.js +1 -0
  94. package/dist/generated/models/session_start_sources.js +1 -0
  95. package/dist/generated/models/thread_sessions.js +1 -0
  96. package/dist/generated/models/thread_worktrees.js +1 -0
  97. package/dist/generated/models.js +1 -0
  98. package/dist/heap-monitor.js +95 -0
  99. package/dist/hrana-server.js +416 -0
  100. package/dist/hrana-server.test.js +368 -0
  101. package/dist/image-utils.js +112 -0
  102. package/dist/interaction-handler.js +327 -0
  103. package/dist/ipc-polling.js +251 -0
  104. package/dist/kimaki-digital-twin.e2e.test.js +165 -0
  105. package/dist/limit-heading-depth.js +25 -0
  106. package/dist/limit-heading-depth.test.js +105 -0
  107. package/dist/logger.js +160 -0
  108. package/dist/markdown.js +342 -0
  109. package/dist/markdown.test.js +253 -0
  110. package/dist/message-formatting.js +433 -0
  111. package/dist/message-formatting.test.js +73 -0
  112. package/dist/openai-realtime.js +228 -0
  113. package/dist/opencode-plugin-loading.e2e.test.js +91 -0
  114. package/dist/opencode-plugin.js +536 -0
  115. package/dist/opencode-plugin.test.js +98 -0
  116. package/dist/opencode.js +409 -0
  117. package/dist/privacy-sanitizer.js +105 -0
  118. package/dist/runtime-mode.js +51 -0
  119. package/dist/runtime-mode.test.js +115 -0
  120. package/dist/sentry.js +127 -0
  121. package/dist/session-handler/state.js +151 -0
  122. package/dist/session-handler.js +1874 -0
  123. package/dist/session-search.js +100 -0
  124. package/dist/session-search.test.js +40 -0
  125. package/dist/startup-service.js +153 -0
  126. package/dist/system-message.js +499 -0
  127. package/dist/task-runner.js +282 -0
  128. package/dist/task-schedule.js +191 -0
  129. package/dist/task-schedule.test.js +71 -0
  130. package/dist/thinking-utils.js +35 -0
  131. package/dist/thread-message-queue.e2e.test.js +781 -0
  132. package/dist/tools.js +359 -0
  133. package/dist/unnest-code-blocks.js +136 -0
  134. package/dist/unnest-code-blocks.test.js +641 -0
  135. package/dist/upgrade.js +114 -0
  136. package/dist/utils.js +109 -0
  137. package/dist/voice-handler.js +606 -0
  138. package/dist/voice.js +304 -0
  139. package/dist/voice.test.js +187 -0
  140. package/dist/wait-session.js +94 -0
  141. package/dist/worker-types.js +4 -0
  142. package/dist/worktree-utils.js +727 -0
  143. package/dist/xml.js +92 -0
  144. package/dist/xml.test.js +32 -0
  145. package/package.json +82 -0
  146. package/schema.prisma +246 -0
  147. package/skills/batch/SKILL.md +87 -0
  148. package/skills/critique/SKILL.md +129 -0
  149. package/skills/errore/SKILL.md +589 -0
  150. package/skills/goke/.prettierrc +5 -0
  151. package/skills/goke/CHANGELOG.md +40 -0
  152. package/skills/goke/LICENSE +21 -0
  153. package/skills/goke/README.md +666 -0
  154. package/skills/goke/SKILL.md +458 -0
  155. package/skills/goke/package.json +43 -0
  156. package/skills/goke/src/__test__/coerce.test.ts +411 -0
  157. package/skills/goke/src/__test__/index.test.ts +1798 -0
  158. package/skills/goke/src/__test__/types.test-d.ts +111 -0
  159. package/skills/goke/src/coerce.ts +547 -0
  160. package/skills/goke/src/goke.ts +1362 -0
  161. package/skills/goke/src/index.ts +16 -0
  162. package/skills/goke/src/mri.ts +164 -0
  163. package/skills/goke/tsconfig.json +15 -0
  164. package/skills/jitter/EDITOR.md +219 -0
  165. package/skills/jitter/EXPORT-INTERNALS.md +309 -0
  166. package/skills/jitter/SKILL.md +158 -0
  167. package/skills/jitter/jitter-clipboard.json +1042 -0
  168. package/skills/jitter/package.json +14 -0
  169. package/skills/jitter/tsconfig.json +15 -0
  170. package/skills/jitter/utils/actions.ts +212 -0
  171. package/skills/jitter/utils/export.ts +114 -0
  172. package/skills/jitter/utils/index.ts +141 -0
  173. package/skills/jitter/utils/snapshot.ts +154 -0
  174. package/skills/jitter/utils/traverse.ts +246 -0
  175. package/skills/jitter/utils/types.ts +279 -0
  176. package/skills/jitter/utils/wait.ts +133 -0
  177. package/skills/playwriter/SKILL.md +31 -0
  178. package/skills/security-review/SKILL.md +208 -0
  179. package/skills/simplify/SKILL.md +58 -0
  180. package/skills/termcast/SKILL.md +945 -0
  181. package/skills/tuistory/SKILL.md +250 -0
  182. package/skills/zustand-centralized-state/SKILL.md +582 -0
  183. package/src/__snapshots__/compact-session-context-no-system.md +35 -0
  184. package/src/__snapshots__/compact-session-context.md +41 -0
  185. package/src/__snapshots__/first-session-no-info.md +17 -0
  186. package/src/__snapshots__/first-session-with-info.md +23 -0
  187. package/src/__snapshots__/session-1.md +17 -0
  188. package/src/__snapshots__/session-2.md +5871 -0
  189. package/src/__snapshots__/session-3.md +17 -0
  190. package/src/__snapshots__/session-with-tools.md +5871 -0
  191. package/src/ai-tool-to-genai.test.ts +296 -0
  192. package/src/ai-tool-to-genai.ts +282 -0
  193. package/src/ai-tool.ts +39 -0
  194. package/src/bin.ts +108 -0
  195. package/src/bot-token.test.ts +171 -0
  196. package/src/bot-token.ts +159 -0
  197. package/src/channel-management.ts +172 -0
  198. package/src/cli-parsing.test.ts +132 -0
  199. package/src/cli.ts +3605 -0
  200. package/src/commands/abort.ts +112 -0
  201. package/src/commands/action-buttons.ts +376 -0
  202. package/src/commands/add-project.ts +152 -0
  203. package/src/commands/agent.ts +404 -0
  204. package/src/commands/ask-question.ts +330 -0
  205. package/src/commands/compact.ts +157 -0
  206. package/src/commands/context-usage.ts +199 -0
  207. package/src/commands/create-new-project.ts +179 -0
  208. package/src/commands/diff.ts +165 -0
  209. package/src/commands/file-upload.ts +389 -0
  210. package/src/commands/fork.ts +320 -0
  211. package/src/commands/gemini-apikey.ts +104 -0
  212. package/src/commands/login.ts +634 -0
  213. package/src/commands/mention-mode.ts +77 -0
  214. package/src/commands/merge-worktree.ts +177 -0
  215. package/src/commands/model.ts +961 -0
  216. package/src/commands/permissions.ts +261 -0
  217. package/src/commands/queue.ts +296 -0
  218. package/src/commands/remove-project.ts +155 -0
  219. package/src/commands/restart-opencode-server.ts +162 -0
  220. package/src/commands/resume.ts +242 -0
  221. package/src/commands/run-command.ts +123 -0
  222. package/src/commands/session-id.ts +109 -0
  223. package/src/commands/session.ts +250 -0
  224. package/src/commands/share.ts +106 -0
  225. package/src/commands/types.ts +25 -0
  226. package/src/commands/undo-redo.ts +221 -0
  227. package/src/commands/unset-model.ts +189 -0
  228. package/src/commands/upgrade.ts +52 -0
  229. package/src/commands/user-command.ts +193 -0
  230. package/src/commands/verbosity.ts +88 -0
  231. package/src/commands/worktree-settings.ts +79 -0
  232. package/src/commands/worktree.ts +431 -0
  233. package/src/condense-memory.ts +36 -0
  234. package/src/config.ts +148 -0
  235. package/src/database.ts +1530 -0
  236. package/src/db.test.ts +60 -0
  237. package/src/db.ts +190 -0
  238. package/src/discord-api.ts +35 -0
  239. package/src/discord-bot.ts +1316 -0
  240. package/src/discord-utils.test.ts +132 -0
  241. package/src/discord-utils.ts +767 -0
  242. package/src/errors.ts +213 -0
  243. package/src/escape-backticks.test.ts +469 -0
  244. package/src/format-tables.test.ts +223 -0
  245. package/src/format-tables.ts +145 -0
  246. package/src/forum-sync/config.ts +92 -0
  247. package/src/forum-sync/discord-operations.ts +241 -0
  248. package/src/forum-sync/index.ts +9 -0
  249. package/src/forum-sync/markdown.ts +176 -0
  250. package/src/forum-sync/sync-to-discord.ts +595 -0
  251. package/src/forum-sync/sync-to-files.ts +294 -0
  252. package/src/forum-sync/types.ts +175 -0
  253. package/src/forum-sync/watchers.ts +454 -0
  254. package/src/genai-worker-wrapper.ts +164 -0
  255. package/src/genai-worker.ts +386 -0
  256. package/src/genai.ts +321 -0
  257. package/src/generated/browser.ts +109 -0
  258. package/src/generated/client.ts +131 -0
  259. package/src/generated/commonInputTypes.ts +512 -0
  260. package/src/generated/enums.ts +46 -0
  261. package/src/generated/internal/class.ts +362 -0
  262. package/src/generated/internal/prismaNamespace.ts +2251 -0
  263. package/src/generated/internal/prismaNamespaceBrowser.ts +308 -0
  264. package/src/generated/models/bot_api_keys.ts +1288 -0
  265. package/src/generated/models/bot_tokens.ts +1577 -0
  266. package/src/generated/models/channel_agents.ts +1256 -0
  267. package/src/generated/models/channel_directories.ts +2104 -0
  268. package/src/generated/models/channel_mention_mode.ts +1300 -0
  269. package/src/generated/models/channel_models.ts +1288 -0
  270. package/src/generated/models/channel_verbosity.ts +1224 -0
  271. package/src/generated/models/channel_worktrees.ts +1308 -0
  272. package/src/generated/models/forum_sync_configs.ts +1452 -0
  273. package/src/generated/models/global_models.ts +1288 -0
  274. package/src/generated/models/ipc_requests.ts +1485 -0
  275. package/src/generated/models/part_messages.ts +1302 -0
  276. package/src/generated/models/scheduled_tasks.ts +2320 -0
  277. package/src/generated/models/session_agents.ts +1086 -0
  278. package/src/generated/models/session_models.ts +1114 -0
  279. package/src/generated/models/session_start_sources.ts +1408 -0
  280. package/src/generated/models/thread_sessions.ts +1599 -0
  281. package/src/generated/models/thread_worktrees.ts +1352 -0
  282. package/src/generated/models.ts +29 -0
  283. package/src/heap-monitor.ts +121 -0
  284. package/src/hrana-server.test.ts +428 -0
  285. package/src/hrana-server.ts +547 -0
  286. package/src/image-utils.ts +149 -0
  287. package/src/interaction-handler.ts +461 -0
  288. package/src/ipc-polling.ts +325 -0
  289. package/src/kimaki-digital-twin.e2e.test.ts +201 -0
  290. package/src/limit-heading-depth.test.ts +116 -0
  291. package/src/limit-heading-depth.ts +26 -0
  292. package/src/logger.ts +203 -0
  293. package/src/markdown.test.ts +360 -0
  294. package/src/markdown.ts +410 -0
  295. package/src/message-formatting.test.ts +81 -0
  296. package/src/message-formatting.ts +549 -0
  297. package/src/openai-realtime.ts +362 -0
  298. package/src/opencode-plugin-loading.e2e.test.ts +112 -0
  299. package/src/opencode-plugin.test.ts +108 -0
  300. package/src/opencode-plugin.ts +652 -0
  301. package/src/opencode.ts +554 -0
  302. package/src/privacy-sanitizer.ts +142 -0
  303. package/src/schema.sql +158 -0
  304. package/src/sentry.ts +137 -0
  305. package/src/session-handler/state.ts +232 -0
  306. package/src/session-handler.ts +2668 -0
  307. package/src/session-search.test.ts +50 -0
  308. package/src/session-search.ts +148 -0
  309. package/src/startup-service.ts +200 -0
  310. package/src/system-message.ts +568 -0
  311. package/src/task-runner.ts +425 -0
  312. package/src/task-schedule.test.ts +84 -0
  313. package/src/task-schedule.ts +287 -0
  314. package/src/thinking-utils.ts +61 -0
  315. package/src/thread-message-queue.e2e.test.ts +997 -0
  316. package/src/tools.ts +432 -0
  317. package/src/unnest-code-blocks.test.ts +679 -0
  318. package/src/unnest-code-blocks.ts +168 -0
  319. package/src/upgrade.ts +127 -0
  320. package/src/utils.ts +145 -0
  321. package/src/voice-handler.ts +852 -0
  322. package/src/voice.test.ts +219 -0
  323. package/src/voice.ts +444 -0
  324. package/src/wait-session.ts +147 -0
  325. package/src/worker-types.ts +64 -0
  326. package/src/worktree-utils.ts +988 -0
  327. package/src/xml.test.ts +38 -0
  328. package/src/xml.ts +121 -0
package/src/tools.ts ADDED
@@ -0,0 +1,432 @@
1
+ // Voice assistant tool definitions for the GenAI worker.
2
+ // Provides tools for managing OpenCode sessions (create, submit, abort),
3
+ // listing chats, searching files, and reading session messages.
4
+
5
+ import { tool } from './ai-tool.js'
6
+ import { z } from 'zod'
7
+ import { spawn, type ChildProcess } from 'node:child_process'
8
+ import net from 'node:net'
9
+ import {
10
+ type OpencodeClient,
11
+ type AssistantMessage,
12
+ type Provider,
13
+ } from '@opencode-ai/sdk/v2'
14
+ import { createLogger, LogPrefix } from './logger.js'
15
+ import * as errore from 'errore'
16
+
17
+ const toolsLogger = createLogger(LogPrefix.TOOLS)
18
+
19
+ import { ShareMarkdown } from './markdown.js'
20
+ import { formatDistanceToNow } from './utils.js'
21
+ import pc from 'picocolors'
22
+ import {
23
+ initializeOpencodeForDirectory,
24
+ getOpencodeSystemMessage,
25
+ } from './discord-bot.js'
26
+
27
+ export async function getTools({
28
+ onMessageCompleted,
29
+ directory,
30
+ }: {
31
+ directory: string
32
+ onMessageCompleted?: (params: {
33
+ sessionId: string
34
+ messageId: string
35
+ data?: { info: AssistantMessage }
36
+ error?: unknown
37
+ markdown?: string
38
+ }) => void
39
+ }) {
40
+ const getClient = await initializeOpencodeForDirectory(directory)
41
+ if (getClient instanceof Error) {
42
+ throw new Error(getClient.message)
43
+ }
44
+ const client = getClient()
45
+
46
+ const markdownRenderer = new ShareMarkdown(client)
47
+
48
+ const providersResponse = await client.config.providers()
49
+ const providers: Provider[] = providersResponse.data?.providers || []
50
+
51
+ // Helper: get last assistant model for a session (non-summary)
52
+ const getSessionModel = async (
53
+ sessionId: string,
54
+ ): Promise<{ providerID: string; modelID: string } | undefined> => {
55
+ const res = await getClient().session.messages({ sessionID: sessionId })
56
+ const data = res.data
57
+ if (!data || data.length === 0) return undefined
58
+ for (let i = data.length - 1; i >= 0; i--) {
59
+ const info = data?.[i]?.info
60
+ if (info?.role === 'assistant') {
61
+ const ai = info as AssistantMessage
62
+ if (!ai.summary && ai.providerID && ai.modelID) {
63
+ return { providerID: ai.providerID, modelID: ai.modelID }
64
+ }
65
+ }
66
+ }
67
+ return undefined
68
+ }
69
+
70
+ const tools = {
71
+ submitMessage: tool({
72
+ description:
73
+ 'Submit a message to an existing chat session. Does not wait for the message to complete',
74
+ inputSchema: z.object({
75
+ sessionId: z.string().describe('The session ID to send message to'),
76
+ message: z.string().describe('The message text to send'),
77
+ }),
78
+ execute: async ({ sessionId, message }) => {
79
+ const sessionModel = await getSessionModel(sessionId)
80
+
81
+ // do not await
82
+ getClient()
83
+ .session.prompt({
84
+ sessionID: sessionId,
85
+ parts: [{ type: 'text', text: message }],
86
+ model: sessionModel,
87
+ system: getOpencodeSystemMessage({ sessionId }),
88
+ })
89
+ .then(async (response) => {
90
+ const markdownResult = await markdownRenderer.generate({
91
+ sessionID: sessionId,
92
+ lastAssistantOnly: true,
93
+ })
94
+ onMessageCompleted?.({
95
+ sessionId,
96
+ messageId: '',
97
+ data: response.data,
98
+ markdown: errore.unwrapOr(markdownResult, ''),
99
+ })
100
+ })
101
+ .catch((error) => {
102
+ onMessageCompleted?.({
103
+ sessionId,
104
+ messageId: '',
105
+ error,
106
+ })
107
+ })
108
+ return {
109
+ success: true,
110
+ sessionId,
111
+ directive: 'Tell user that message has been sent successfully',
112
+ }
113
+ },
114
+ }),
115
+
116
+ createNewChat: tool({
117
+ description:
118
+ 'Start a new chat session with an initial message. Does not wait for the message to complete',
119
+ inputSchema: z.object({
120
+ message: z
121
+ .string()
122
+ .describe('The initial message to start the chat with'),
123
+ title: z.string().optional().describe('Optional title for the session'),
124
+ model: z
125
+ .object({
126
+ providerId: z
127
+ .string()
128
+ .describe('The provider ID (e.g., "anthropic", "openai")'),
129
+ modelId: z
130
+ .string()
131
+ .describe(
132
+ 'The model ID (e.g., "claude-opus-4-20250514", "gpt-5")',
133
+ ),
134
+ })
135
+ .optional()
136
+ .describe('Optional model to use for this session'),
137
+ }),
138
+ execute: async ({ message, title }) => {
139
+ if (!message.trim()) {
140
+ throw new Error(`message must be a non empty string`)
141
+ }
142
+
143
+ try {
144
+ const session = await getClient().session.create({
145
+ title: title || message.slice(0, 50),
146
+ })
147
+
148
+ if (!session.data) {
149
+ throw new Error('Failed to create session')
150
+ }
151
+
152
+ // do not await
153
+ getClient()
154
+ .session.prompt({
155
+ sessionID: session.data.id,
156
+ parts: [{ type: 'text', text: message }],
157
+ system: getOpencodeSystemMessage({ sessionId: session.data.id }),
158
+ })
159
+ .then(async (response) => {
160
+ const markdownResult = await markdownRenderer.generate({
161
+ sessionID: session.data.id,
162
+ lastAssistantOnly: true,
163
+ })
164
+ onMessageCompleted?.({
165
+ sessionId: session.data.id,
166
+ messageId: '',
167
+ data: response.data,
168
+ markdown: errore.unwrapOr(markdownResult, ''),
169
+ })
170
+ })
171
+ .catch((error) => {
172
+ onMessageCompleted?.({
173
+ sessionId: session.data.id,
174
+ messageId: '',
175
+ error,
176
+ })
177
+ })
178
+
179
+ return {
180
+ success: true,
181
+ sessionId: session.data.id,
182
+ title: session.data.title,
183
+ }
184
+ } catch (error) {
185
+ return {
186
+ success: false,
187
+ error:
188
+ error instanceof Error
189
+ ? error.message
190
+ : 'Failed to create chat session',
191
+ }
192
+ }
193
+ },
194
+ }),
195
+
196
+ listChats: tool({
197
+ description:
198
+ 'Get a list of available chat sessions sorted by most recent',
199
+ inputSchema: z.object({}),
200
+ execute: async () => {
201
+ toolsLogger.log(`Listing opencode sessions`)
202
+ const sessions = await getClient().session.list()
203
+
204
+ if (!sessions.data) {
205
+ return { success: false, error: 'No sessions found' }
206
+ }
207
+
208
+ const sortedSessions = [...sessions.data]
209
+ .sort((a, b) => {
210
+ return b.time.updated - a.time.updated
211
+ })
212
+ .slice(0, 20)
213
+
214
+ const sessionList = sortedSessions.map(async (session) => {
215
+ const finishedAt = session.time.updated
216
+ const status = await (async () => {
217
+ if (session.revert) return 'error'
218
+ const messagesResponse = await getClient().session.messages({
219
+ sessionID: session.id,
220
+ })
221
+ const messages = messagesResponse.data || []
222
+ const lastMessage = messages[messages.length - 1]
223
+ if (
224
+ lastMessage?.info.role === 'assistant' &&
225
+ !lastMessage.info.time.completed
226
+ ) {
227
+ return 'in_progress'
228
+ }
229
+ return 'finished'
230
+ })()
231
+
232
+ return {
233
+ id: session.id,
234
+ folder: session.directory,
235
+ status,
236
+ finishedAt: formatDistanceToNow(new Date(finishedAt)),
237
+ title: session.title,
238
+ prompt: session.title,
239
+ }
240
+ })
241
+
242
+ const resolvedList = await Promise.all(sessionList)
243
+
244
+ return {
245
+ success: true,
246
+ sessions: resolvedList,
247
+ }
248
+ },
249
+ }),
250
+
251
+ searchFiles: tool({
252
+ description: 'Search for files in a folder',
253
+ inputSchema: z.object({
254
+ folder: z
255
+ .string()
256
+ .optional()
257
+ .describe(
258
+ 'The folder path to search in, optional. only use if user specifically asks for it',
259
+ ),
260
+ query: z.string().describe('The search query for files'),
261
+ }),
262
+ execute: async ({ folder, query }) => {
263
+ const results = await getClient().find.files({
264
+ query,
265
+ directory: folder,
266
+ })
267
+
268
+ return {
269
+ success: true,
270
+ files: results.data || [],
271
+ }
272
+ },
273
+ }),
274
+
275
+ readSessionMessages: tool({
276
+ description: 'Read messages from a chat session',
277
+ inputSchema: z.object({
278
+ sessionId: z.string().describe('The session ID to read messages from'),
279
+ lastAssistantOnly: z
280
+ .boolean()
281
+ .optional()
282
+ .describe('Only read the last assistant message'),
283
+ }),
284
+ execute: async ({ sessionId, lastAssistantOnly = false }) => {
285
+ if (lastAssistantOnly) {
286
+ const messages = await getClient().session.messages({
287
+ sessionID: sessionId,
288
+ })
289
+
290
+ if (!messages.data) {
291
+ return { success: false, error: 'No messages found' }
292
+ }
293
+
294
+ const assistantMessages = messages.data.filter(
295
+ (m) => m.info.role === 'assistant',
296
+ )
297
+
298
+ if (assistantMessages.length === 0) {
299
+ return {
300
+ success: false,
301
+ error: 'No assistant messages found',
302
+ }
303
+ }
304
+
305
+ const lastMessage = assistantMessages[assistantMessages.length - 1]
306
+ const status =
307
+ 'completed' in lastMessage!.info.time &&
308
+ lastMessage!.info.time.completed
309
+ ? 'completed'
310
+ : 'in_progress'
311
+
312
+ const markdownResult = await markdownRenderer.generate({
313
+ sessionID: sessionId,
314
+ lastAssistantOnly: true,
315
+ })
316
+ if (markdownResult instanceof Error) {
317
+ throw new Error(markdownResult.message)
318
+ }
319
+
320
+ return {
321
+ success: true,
322
+ markdown: markdownResult,
323
+ status,
324
+ }
325
+ } else {
326
+ const markdownResult = await markdownRenderer.generate({
327
+ sessionID: sessionId,
328
+ })
329
+ if (markdownResult instanceof Error) {
330
+ throw new Error(markdownResult.message)
331
+ }
332
+
333
+ const messages = await getClient().session.messages({
334
+ sessionID: sessionId,
335
+ })
336
+ const lastMessage = messages.data?.[messages.data.length - 1]
337
+ const status =
338
+ lastMessage?.info.role === 'assistant' &&
339
+ lastMessage?.info.time &&
340
+ 'completed' in lastMessage.info.time &&
341
+ !lastMessage.info.time.completed
342
+ ? 'in_progress'
343
+ : 'completed'
344
+
345
+ return {
346
+ success: true,
347
+ markdown: markdownResult,
348
+ status,
349
+ }
350
+ }
351
+ },
352
+ }),
353
+
354
+ abortChat: tool({
355
+ description: 'Abort/stop an in-progress chat session',
356
+ inputSchema: z.object({
357
+ sessionId: z.string().describe('The session ID to abort'),
358
+ }),
359
+ execute: async ({ sessionId }) => {
360
+ try {
361
+ toolsLogger.log(
362
+ `[ABORT] reason=voice-tool sessionId=${sessionId} - user requested abort via voice assistant tool`,
363
+ )
364
+ const result = await getClient().session.abort({
365
+ sessionID: sessionId,
366
+ })
367
+
368
+ if (!result.data) {
369
+ return {
370
+ success: false,
371
+ error: 'Failed to abort session',
372
+ }
373
+ }
374
+
375
+ return {
376
+ success: true,
377
+ sessionId,
378
+ message: 'Session aborted successfully',
379
+ }
380
+ } catch (error) {
381
+ return {
382
+ success: false,
383
+ error:
384
+ error instanceof Error ? error.message : 'Unknown error occurred',
385
+ }
386
+ }
387
+ },
388
+ }),
389
+
390
+ getModels: tool({
391
+ description: 'Get all available AI models from all providers',
392
+ inputSchema: z.object({}),
393
+ execute: async () => {
394
+ try {
395
+ const providersResponse = await getClient().config.providers()
396
+ const providers: Provider[] = providersResponse.data?.providers || []
397
+
398
+ const models: Array<{ providerId: string; modelId: string }> = []
399
+
400
+ providers.forEach((provider) => {
401
+ if (provider.models && typeof provider.models === 'object') {
402
+ Object.entries(provider.models).forEach(([modelId, model]) => {
403
+ models.push({
404
+ providerId: provider.id,
405
+ modelId: modelId,
406
+ })
407
+ })
408
+ }
409
+ })
410
+
411
+ return {
412
+ success: true,
413
+ models,
414
+ totalCount: models.length,
415
+ }
416
+ } catch (error) {
417
+ return {
418
+ success: false,
419
+ error:
420
+ error instanceof Error ? error.message : 'Failed to fetch models',
421
+ models: [],
422
+ }
423
+ }
424
+ },
425
+ }),
426
+ }
427
+
428
+ return {
429
+ tools,
430
+ providers,
431
+ }
432
+ }