cerebras-cli 1.0.1 → 1.0.4

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 (314) hide show
  1. package/README.md +3 -5
  2. package/cerebras-cli-1.0.0.tgz +0 -0
  3. package/package.json +7 -88
  4. package/AGENTS.md +0 -27
  5. package/Dockerfile +0 -10
  6. package/bunfig.toml +0 -4
  7. package/parsers-config.ts +0 -239
  8. package/script/build.ts +0 -151
  9. package/script/postinstall.mjs +0 -122
  10. package/script/publish.ts +0 -256
  11. package/script/schema.ts +0 -47
  12. package/src/acp/README.md +0 -164
  13. package/src/acp/agent.ts +0 -812
  14. package/src/acp/session.ts +0 -70
  15. package/src/acp/types.ts +0 -22
  16. package/src/agent/agent.ts +0 -310
  17. package/src/agent/generate.txt +0 -75
  18. package/src/auth/index.ts +0 -70
  19. package/src/bun/index.ts +0 -152
  20. package/src/bus/global.ts +0 -10
  21. package/src/bus/index.ts +0 -142
  22. package/src/cli/bootstrap.ts +0 -17
  23. package/src/cli/cmd/acp.ts +0 -88
  24. package/src/cli/cmd/agent.ts +0 -165
  25. package/src/cli/cmd/auth.ts +0 -369
  26. package/src/cli/cmd/cmd.ts +0 -7
  27. package/src/cli/cmd/debug/config.ts +0 -15
  28. package/src/cli/cmd/debug/file.ts +0 -91
  29. package/src/cli/cmd/debug/index.ts +0 -41
  30. package/src/cli/cmd/debug/lsp.ts +0 -47
  31. package/src/cli/cmd/debug/ripgrep.ts +0 -83
  32. package/src/cli/cmd/debug/scrap.ts +0 -15
  33. package/src/cli/cmd/debug/snapshot.ts +0 -48
  34. package/src/cli/cmd/export.ts +0 -88
  35. package/src/cli/cmd/generate.ts +0 -38
  36. package/src/cli/cmd/github.ts +0 -1200
  37. package/src/cli/cmd/import.ts +0 -98
  38. package/src/cli/cmd/mcp.ts +0 -400
  39. package/src/cli/cmd/models.ts +0 -77
  40. package/src/cli/cmd/pr.ts +0 -112
  41. package/src/cli/cmd/run.ts +0 -342
  42. package/src/cli/cmd/serve.ts +0 -31
  43. package/src/cli/cmd/session.ts +0 -106
  44. package/src/cli/cmd/stats.ts +0 -298
  45. package/src/cli/cmd/tui/app.tsx +0 -732
  46. package/src/cli/cmd/tui/attach.ts +0 -25
  47. package/src/cli/cmd/tui/component/border.tsx +0 -21
  48. package/src/cli/cmd/tui/component/dialog-agent.tsx +0 -31
  49. package/src/cli/cmd/tui/component/dialog-command.tsx +0 -124
  50. package/src/cli/cmd/tui/component/dialog-feedback.tsx +0 -160
  51. package/src/cli/cmd/tui/component/dialog-mcp.tsx +0 -86
  52. package/src/cli/cmd/tui/component/dialog-model.tsx +0 -223
  53. package/src/cli/cmd/tui/component/dialog-notification.tsx +0 -78
  54. package/src/cli/cmd/tui/component/dialog-provider.tsx +0 -222
  55. package/src/cli/cmd/tui/component/dialog-session-list.tsx +0 -97
  56. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +0 -31
  57. package/src/cli/cmd/tui/component/dialog-status.tsx +0 -114
  58. package/src/cli/cmd/tui/component/dialog-tag.tsx +0 -44
  59. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +0 -50
  60. package/src/cli/cmd/tui/component/logo.tsx +0 -37
  61. package/src/cli/cmd/tui/component/notification-banner.tsx +0 -58
  62. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +0 -530
  63. package/src/cli/cmd/tui/component/prompt/history.tsx +0 -107
  64. package/src/cli/cmd/tui/component/prompt/index.tsx +0 -931
  65. package/src/cli/cmd/tui/context/args.tsx +0 -14
  66. package/src/cli/cmd/tui/context/directory.ts +0 -12
  67. package/src/cli/cmd/tui/context/exit.tsx +0 -23
  68. package/src/cli/cmd/tui/context/helper.tsx +0 -25
  69. package/src/cli/cmd/tui/context/keybind.tsx +0 -111
  70. package/src/cli/cmd/tui/context/kv.tsx +0 -49
  71. package/src/cli/cmd/tui/context/local.tsx +0 -339
  72. package/src/cli/cmd/tui/context/prompt.tsx +0 -18
  73. package/src/cli/cmd/tui/context/route.tsx +0 -45
  74. package/src/cli/cmd/tui/context/sdk.tsx +0 -75
  75. package/src/cli/cmd/tui/context/sync.tsx +0 -374
  76. package/src/cli/cmd/tui/context/theme/aura.json +0 -69
  77. package/src/cli/cmd/tui/context/theme/ayu.json +0 -80
  78. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +0 -233
  79. package/src/cli/cmd/tui/context/theme/catppuccin.json +0 -112
  80. package/src/cli/cmd/tui/context/theme/cobalt2.json +0 -228
  81. package/src/cli/cmd/tui/context/theme/dracula.json +0 -219
  82. package/src/cli/cmd/tui/context/theme/everforest.json +0 -241
  83. package/src/cli/cmd/tui/context/theme/flexoki.json +0 -237
  84. package/src/cli/cmd/tui/context/theme/github.json +0 -233
  85. package/src/cli/cmd/tui/context/theme/gruvbox.json +0 -95
  86. package/src/cli/cmd/tui/context/theme/kanagawa.json +0 -77
  87. package/src/cli/cmd/tui/context/theme/material.json +0 -235
  88. package/src/cli/cmd/tui/context/theme/matrix.json +0 -77
  89. package/src/cli/cmd/tui/context/theme/mercury.json +0 -252
  90. package/src/cli/cmd/tui/context/theme/monokai.json +0 -221
  91. package/src/cli/cmd/tui/context/theme/nightowl.json +0 -221
  92. package/src/cli/cmd/tui/context/theme/nord.json +0 -223
  93. package/src/cli/cmd/tui/context/theme/one-dark.json +0 -84
  94. package/src/cli/cmd/tui/context/theme/orng.json +0 -245
  95. package/src/cli/cmd/tui/context/theme/palenight.json +0 -222
  96. package/src/cli/cmd/tui/context/theme/rosepine.json +0 -234
  97. package/src/cli/cmd/tui/context/theme/solarized.json +0 -223
  98. package/src/cli/cmd/tui/context/theme/synthwave84.json +0 -226
  99. package/src/cli/cmd/tui/context/theme/tokyonight.json +0 -243
  100. package/src/cli/cmd/tui/context/theme/vercel.json +0 -245
  101. package/src/cli/cmd/tui/context/theme/vesper.json +0 -218
  102. package/src/cli/cmd/tui/context/theme/zenburn.json +0 -223
  103. package/src/cli/cmd/tui/context/theme.tsx +0 -1077
  104. package/src/cli/cmd/tui/event.ts +0 -39
  105. package/src/cli/cmd/tui/routes/home.tsx +0 -104
  106. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +0 -93
  107. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +0 -37
  108. package/src/cli/cmd/tui/routes/session/footer.tsx +0 -76
  109. package/src/cli/cmd/tui/routes/session/header.tsx +0 -183
  110. package/src/cli/cmd/tui/routes/session/index.tsx +0 -1703
  111. package/src/cli/cmd/tui/routes/session/sidebar.tsx +0 -586
  112. package/src/cli/cmd/tui/spawn.ts +0 -60
  113. package/src/cli/cmd/tui/thread.ts +0 -120
  114. package/src/cli/cmd/tui/ui/dialog-alert.tsx +0 -55
  115. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +0 -81
  116. package/src/cli/cmd/tui/ui/dialog-help.tsx +0 -36
  117. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +0 -75
  118. package/src/cli/cmd/tui/ui/dialog-select.tsx +0 -317
  119. package/src/cli/cmd/tui/ui/dialog.tsx +0 -170
  120. package/src/cli/cmd/tui/ui/spinner.ts +0 -368
  121. package/src/cli/cmd/tui/ui/toast.tsx +0 -100
  122. package/src/cli/cmd/tui/util/clipboard.ts +0 -127
  123. package/src/cli/cmd/tui/util/editor.ts +0 -32
  124. package/src/cli/cmd/tui/util/terminal.ts +0 -114
  125. package/src/cli/cmd/tui/worker.ts +0 -63
  126. package/src/cli/cmd/uninstall.ts +0 -344
  127. package/src/cli/cmd/upgrade.ts +0 -67
  128. package/src/cli/cmd/web.ts +0 -84
  129. package/src/cli/error.ts +0 -55
  130. package/src/cli/ui.ts +0 -84
  131. package/src/cli/upgrade.ts +0 -25
  132. package/src/command/index.ts +0 -79
  133. package/src/command/template/initialize.txt +0 -10
  134. package/src/command/template/review.txt +0 -73
  135. package/src/config/config.ts +0 -886
  136. package/src/config/markdown.ts +0 -41
  137. package/src/env/index.ts +0 -26
  138. package/src/file/fzf.ts +0 -124
  139. package/src/file/ignore.ts +0 -83
  140. package/src/file/index.ts +0 -326
  141. package/src/file/ripgrep.ts +0 -391
  142. package/src/file/time.ts +0 -38
  143. package/src/file/watcher.ts +0 -89
  144. package/src/flag/flag.ts +0 -28
  145. package/src/format/formatter.ts +0 -277
  146. package/src/format/index.ts +0 -137
  147. package/src/global/index.ts +0 -52
  148. package/src/id/id.ts +0 -73
  149. package/src/ide/index.ts +0 -75
  150. package/src/index.ts +0 -158
  151. package/src/installation/index.ts +0 -194
  152. package/src/lsp/client.ts +0 -215
  153. package/src/lsp/index.ts +0 -370
  154. package/src/lsp/language.ts +0 -111
  155. package/src/lsp/server.ts +0 -1327
  156. package/src/mcp/auth.ts +0 -82
  157. package/src/mcp/index.ts +0 -576
  158. package/src/mcp/oauth-callback.ts +0 -203
  159. package/src/mcp/oauth-provider.ts +0 -132
  160. package/src/notification/index.ts +0 -101
  161. package/src/patch/index.ts +0 -622
  162. package/src/permission/index.ts +0 -198
  163. package/src/plugin/index.ts +0 -95
  164. package/src/project/bootstrap.ts +0 -31
  165. package/src/project/instance.ts +0 -68
  166. package/src/project/project.ts +0 -133
  167. package/src/project/state.ts +0 -65
  168. package/src/project/vcs.ts +0 -77
  169. package/src/provider/auth.ts +0 -143
  170. package/src/provider/models-macro.ts +0 -11
  171. package/src/provider/models.ts +0 -93
  172. package/src/provider/provider.ts +0 -996
  173. package/src/provider/sdk/openai-compatible/src/README.md +0 -5
  174. package/src/provider/sdk/openai-compatible/src/index.ts +0 -2
  175. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +0 -100
  176. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +0 -303
  177. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +0 -27
  178. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +0 -18
  179. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +0 -22
  180. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +0 -207
  181. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +0 -1713
  182. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +0 -177
  183. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +0 -1
  184. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +0 -88
  185. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +0 -128
  186. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +0 -115
  187. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +0 -65
  188. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +0 -104
  189. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +0 -103
  190. package/src/provider/transform.ts +0 -406
  191. package/src/pty/index.ts +0 -226
  192. package/src/ratelimit/index.ts +0 -185
  193. package/src/server/error.ts +0 -36
  194. package/src/server/project.ts +0 -50
  195. package/src/server/server.ts +0 -2463
  196. package/src/server/tui.ts +0 -71
  197. package/src/session/compaction.ts +0 -257
  198. package/src/session/index.ts +0 -470
  199. package/src/session/message-v2.ts +0 -641
  200. package/src/session/message.ts +0 -189
  201. package/src/session/processor.ts +0 -443
  202. package/src/session/prompt/anthropic-20250930.txt +0 -166
  203. package/src/session/prompt/anthropic.txt +0 -105
  204. package/src/session/prompt/anthropic_spoof.txt +0 -1
  205. package/src/session/prompt/beast.txt +0 -147
  206. package/src/session/prompt/build-switch.txt +0 -5
  207. package/src/session/prompt/codex.txt +0 -318
  208. package/src/session/prompt/compaction.txt +0 -12
  209. package/src/session/prompt/copilot-gpt-5.txt +0 -143
  210. package/src/session/prompt/gemini.txt +0 -155
  211. package/src/session/prompt/max-steps.txt +0 -16
  212. package/src/session/prompt/plan-reminder-anthropic.txt +0 -67
  213. package/src/session/prompt/plan.txt +0 -26
  214. package/src/session/prompt/polaris.txt +0 -107
  215. package/src/session/prompt/qwen.txt +0 -109
  216. package/src/session/prompt/summarize.txt +0 -4
  217. package/src/session/prompt/title.txt +0 -36
  218. package/src/session/prompt.ts +0 -1541
  219. package/src/session/retry.ts +0 -82
  220. package/src/session/revert.ts +0 -108
  221. package/src/session/status.ts +0 -75
  222. package/src/session/summary.ts +0 -203
  223. package/src/session/system.ts +0 -148
  224. package/src/session/todo.ts +0 -36
  225. package/src/share/share-next.ts +0 -195
  226. package/src/share/share.ts +0 -87
  227. package/src/snapshot/index.ts +0 -197
  228. package/src/storage/storage.ts +0 -226
  229. package/src/telemetry/index.ts +0 -232
  230. package/src/tool/bash.ts +0 -365
  231. package/src/tool/bash.txt +0 -128
  232. package/src/tool/batch.ts +0 -173
  233. package/src/tool/batch.txt +0 -28
  234. package/src/tool/codesearch.ts +0 -138
  235. package/src/tool/codesearch.txt +0 -12
  236. package/src/tool/edit.ts +0 -674
  237. package/src/tool/edit.txt +0 -10
  238. package/src/tool/glob.ts +0 -65
  239. package/src/tool/glob.txt +0 -6
  240. package/src/tool/grep.ts +0 -120
  241. package/src/tool/grep.txt +0 -8
  242. package/src/tool/invalid.ts +0 -17
  243. package/src/tool/ls.ts +0 -110
  244. package/src/tool/ls.txt +0 -1
  245. package/src/tool/lsp-diagnostics.ts +0 -26
  246. package/src/tool/lsp-diagnostics.txt +0 -1
  247. package/src/tool/lsp-hover.ts +0 -31
  248. package/src/tool/lsp-hover.txt +0 -1
  249. package/src/tool/multiedit.ts +0 -46
  250. package/src/tool/multiedit.txt +0 -41
  251. package/src/tool/patch.ts +0 -233
  252. package/src/tool/patch.txt +0 -1
  253. package/src/tool/read.ts +0 -217
  254. package/src/tool/read.txt +0 -12
  255. package/src/tool/registry.ts +0 -148
  256. package/src/tool/task.ts +0 -135
  257. package/src/tool/task.txt +0 -60
  258. package/src/tool/todo.ts +0 -39
  259. package/src/tool/todoread.txt +0 -14
  260. package/src/tool/todowrite.txt +0 -167
  261. package/src/tool/tool.ts +0 -66
  262. package/src/tool/webfetch.ts +0 -187
  263. package/src/tool/webfetch.txt +0 -14
  264. package/src/tool/websearch.ts +0 -150
  265. package/src/tool/websearch.txt +0 -11
  266. package/src/tool/write.ts +0 -99
  267. package/src/tool/write.txt +0 -8
  268. package/src/types/shims.d.ts +0 -3
  269. package/src/util/color.ts +0 -19
  270. package/src/util/context.ts +0 -25
  271. package/src/util/defer.ts +0 -12
  272. package/src/util/eventloop.ts +0 -20
  273. package/src/util/filesystem.ts +0 -69
  274. package/src/util/fn.ts +0 -11
  275. package/src/util/iife.ts +0 -3
  276. package/src/util/keybind.ts +0 -79
  277. package/src/util/lazy.ts +0 -11
  278. package/src/util/locale.ts +0 -81
  279. package/src/util/lock.ts +0 -98
  280. package/src/util/log.ts +0 -177
  281. package/src/util/queue.ts +0 -32
  282. package/src/util/rpc.ts +0 -42
  283. package/src/util/scrap.ts +0 -10
  284. package/src/util/signal.ts +0 -12
  285. package/src/util/timeout.ts +0 -14
  286. package/src/util/token.ts +0 -7
  287. package/src/util/wildcard.ts +0 -54
  288. package/sst-env.d.ts +0 -9
  289. package/test/bun.test.ts +0 -53
  290. package/test/config/agent-color.test.ts +0 -66
  291. package/test/config/config.test.ts +0 -503
  292. package/test/config/markdown.test.ts +0 -89
  293. package/test/file/ignore.test.ts +0 -10
  294. package/test/fixture/fixture.ts +0 -28
  295. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  296. package/test/ide/ide.test.ts +0 -82
  297. package/test/keybind.test.ts +0 -317
  298. package/test/lsp/client.test.ts +0 -95
  299. package/test/patch/patch.test.ts +0 -348
  300. package/test/preload.ts +0 -38
  301. package/test/project/project.test.ts +0 -42
  302. package/test/provider/provider.test.ts +0 -1809
  303. package/test/provider/transform.test.ts +0 -305
  304. package/test/session/retry.test.ts +0 -61
  305. package/test/session/session.test.ts +0 -71
  306. package/test/snapshot/snapshot.test.ts +0 -939
  307. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  308. package/test/tool/bash.test.ts +0 -55
  309. package/test/tool/patch.test.ts +0 -259
  310. package/test/util/iife.test.ts +0 -36
  311. package/test/util/lazy.test.ts +0 -50
  312. package/test/util/timeout.test.ts +0 -21
  313. package/test/util/wildcard.test.ts +0 -55
  314. package/tsconfig.json +0 -17
@@ -1,1541 +0,0 @@
1
- import path from "path"
2
- import os from "os"
3
- import fs from "fs/promises"
4
- import z from "zod"
5
- import { Identifier } from "../id/id"
6
- import { MessageV2 } from "./message-v2"
7
- import { Log } from "../util/log"
8
- import { SessionRevert } from "./revert"
9
- import { Session } from "."
10
- import { Agent } from "../agent/agent"
11
- import { Provider } from "../provider/provider"
12
- import {
13
- generateText,
14
- type ModelMessage,
15
- type Tool as AITool,
16
- tool,
17
- wrapLanguageModel,
18
- stepCountIs,
19
- jsonSchema,
20
- } from "ai"
21
- import { SessionCompaction } from "./compaction"
22
- import { Instance } from "../project/instance"
23
- import { Bus } from "../bus"
24
- import { ProviderTransform } from "../provider/transform"
25
- import { SystemPrompt } from "./system"
26
- import { Plugin } from "../plugin"
27
-
28
- import PROMPT_PLAN from "../session/prompt/plan.txt"
29
- import BUILD_SWITCH from "../session/prompt/build-switch.txt"
30
- import MAX_STEPS from "../session/prompt/max-steps.txt"
31
- import { defer } from "../util/defer"
32
- import { mergeDeep, pipe } from "remeda"
33
- import { ToolRegistry } from "../tool/registry"
34
- import { Wildcard } from "../util/wildcard"
35
- import { MCP } from "../mcp"
36
- import { LSP } from "../lsp"
37
- import { ReadTool } from "../tool/read"
38
- import { ListTool } from "../tool/ls"
39
- import { FileTime } from "../file/time"
40
- import { ulid } from "ulid"
41
- import { spawn } from "child_process"
42
- import { Command } from "../command"
43
- import { $, fileURLToPath } from "bun"
44
- import { ConfigMarkdown } from "../config/markdown"
45
- import { SessionSummary } from "./summary"
46
- import { Config } from "../config/config"
47
- import { NamedError } from "@opencode-ai/util/error"
48
- import { fn } from "@/util/fn"
49
- import { SessionProcessor } from "./processor"
50
- import { TaskTool } from "@/tool/task"
51
- import { SessionStatus } from "./status"
52
-
53
- // @ts-ignore
54
- globalThis.AI_SDK_LOG_WARNINGS = false
55
-
56
- export namespace SessionPrompt {
57
- const log = Log.create({ service: "session.prompt" })
58
- export const OUTPUT_TOKEN_MAX = 32_000
59
-
60
- const state = Instance.state(
61
- () => {
62
- const data: Record<
63
- string,
64
- {
65
- abort: AbortController
66
- callbacks: {
67
- resolve(input: MessageV2.WithParts): void
68
- reject(): void
69
- }[]
70
- }
71
- > = {}
72
- return data
73
- },
74
- async (current) => {
75
- for (const item of Object.values(current)) {
76
- item.abort.abort()
77
- }
78
- },
79
- )
80
-
81
- export function assertNotBusy(sessionID: string) {
82
- const match = state()[sessionID]
83
- if (match) throw new Session.BusyError(sessionID)
84
- }
85
-
86
- export const PromptInput = z.object({
87
- sessionID: Identifier.schema("session"),
88
- messageID: Identifier.schema("message").optional(),
89
- model: z
90
- .object({
91
- providerID: z.string(),
92
- modelID: z.string(),
93
- })
94
- .optional(),
95
- agent: z.string().optional(),
96
- noReply: z.boolean().optional(),
97
- system: z.string().optional(),
98
- tools: z.record(z.string(), z.boolean()).optional(),
99
- parts: z.array(
100
- z.discriminatedUnion("type", [
101
- MessageV2.TextPart.omit({
102
- messageID: true,
103
- sessionID: true,
104
- })
105
- .partial({
106
- id: true,
107
- })
108
- .meta({
109
- ref: "TextPartInput",
110
- }),
111
- MessageV2.FilePart.omit({
112
- messageID: true,
113
- sessionID: true,
114
- })
115
- .partial({
116
- id: true,
117
- })
118
- .meta({
119
- ref: "FilePartInput",
120
- }),
121
- MessageV2.AgentPart.omit({
122
- messageID: true,
123
- sessionID: true,
124
- })
125
- .partial({
126
- id: true,
127
- })
128
- .meta({
129
- ref: "AgentPartInput",
130
- }),
131
- MessageV2.SubtaskPart.omit({
132
- messageID: true,
133
- sessionID: true,
134
- })
135
- .partial({
136
- id: true,
137
- })
138
- .meta({
139
- ref: "SubtaskPartInput",
140
- }),
141
- ]),
142
- ),
143
- })
144
- export type PromptInput = z.infer<typeof PromptInput>
145
-
146
- export async function resolvePromptParts(template: string): Promise<PromptInput["parts"]> {
147
- const parts: PromptInput["parts"] = [
148
- {
149
- type: "text",
150
- text: template,
151
- },
152
- ]
153
- const files = ConfigMarkdown.files(template)
154
- const seen = new Set<string>()
155
- await Promise.all(
156
- files.map(async (match) => {
157
- const name = match[1]
158
- if (seen.has(name)) return
159
- seen.add(name)
160
- const filepath = name.startsWith("~/")
161
- ? path.join(os.homedir(), name.slice(2))
162
- : path.resolve(Instance.worktree, name)
163
-
164
- const stats = await fs.stat(filepath).catch(() => undefined)
165
- if (!stats) {
166
- const agent = await Agent.get(name)
167
- if (agent) {
168
- parts.push({
169
- type: "agent",
170
- name: agent.name,
171
- })
172
- }
173
- return
174
- }
175
-
176
- if (stats.isDirectory()) {
177
- parts.push({
178
- type: "file",
179
- url: `file://${filepath}`,
180
- filename: name,
181
- mime: "application/x-directory",
182
- })
183
- return
184
- }
185
-
186
- parts.push({
187
- type: "file",
188
- url: `file://${filepath}`,
189
- filename: name,
190
- mime: "text/plain",
191
- })
192
- }),
193
- )
194
- return parts
195
- }
196
-
197
- export const prompt = fn(PromptInput, async (input) => {
198
- const session = await Session.get(input.sessionID)
199
- await SessionRevert.cleanup(session)
200
-
201
- const message = await createUserMessage(input)
202
- await Session.touch(input.sessionID)
203
-
204
- if (input.noReply === true) {
205
- return message
206
- }
207
-
208
- return loop(input.sessionID)
209
- })
210
-
211
- function start(sessionID: string) {
212
- const s = state()
213
- if (s[sessionID]) return
214
- const controller = new AbortController()
215
- s[sessionID] = {
216
- abort: controller,
217
- callbacks: [],
218
- }
219
- return controller.signal
220
- }
221
-
222
- export function cancel(sessionID: string) {
223
- log.info("cancel", { sessionID })
224
- const s = state()
225
- const match = s[sessionID]
226
- if (!match) return
227
- match.abort.abort()
228
- for (const item of match.callbacks) {
229
- item.reject()
230
- }
231
- delete s[sessionID]
232
- SessionStatus.set(sessionID, { type: "idle" })
233
- return
234
- }
235
-
236
- export const loop = fn(Identifier.schema("session"), async (sessionID) => {
237
- const abort = start(sessionID)
238
- if (!abort) {
239
- return new Promise<MessageV2.WithParts>((resolve, reject) => {
240
- const callbacks = state()[sessionID].callbacks
241
- callbacks.push({ resolve, reject })
242
- })
243
- }
244
-
245
- using _ = defer(() => cancel(sessionID))
246
-
247
- let step = 0
248
- while (true) {
249
- SessionStatus.set(sessionID, { type: "busy" })
250
- log.info("loop", { step, sessionID })
251
- if (abort.aborted) break
252
- let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))
253
-
254
- let lastUser: MessageV2.User | undefined
255
- let lastAssistant: MessageV2.Assistant | undefined
256
- let lastFinished: MessageV2.Assistant | undefined
257
- let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
258
- for (let i = msgs.length - 1; i >= 0; i--) {
259
- const msg = msgs[i]
260
- if (!lastUser && msg.info.role === "user") lastUser = msg.info as MessageV2.User
261
- if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info as MessageV2.Assistant
262
- if (!lastFinished && msg.info.role === "assistant" && msg.info.finish)
263
- lastFinished = msg.info as MessageV2.Assistant
264
- if (lastUser && lastFinished) break
265
- const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
266
- if (task && !lastFinished) {
267
- tasks.push(...task)
268
- }
269
- }
270
-
271
- if (!lastUser) throw new Error("No user message found in stream. This should never happen.")
272
- if (
273
- lastAssistant?.finish &&
274
- !["tool-calls", "unknown"].includes(lastAssistant.finish) &&
275
- lastUser.id < lastAssistant.id
276
- ) {
277
- log.info("exiting loop", { sessionID })
278
- break
279
- }
280
-
281
- step++
282
- if (step === 1)
283
- ensureTitle({
284
- session: await Session.get(sessionID),
285
- modelID: lastUser.model.modelID,
286
- providerID: lastUser.model.providerID,
287
- message: msgs.find((m) => m.info.role === "user")!,
288
- history: msgs,
289
- })
290
-
291
- const model = await Provider.getModel(lastUser.model.providerID, lastUser.model.modelID)
292
- const language = await Provider.getLanguage(model)
293
- const task = tasks.pop()
294
-
295
- // pending subtask
296
- // TODO: centralize "invoke tool" logic
297
- if (task?.type === "subtask") {
298
- const taskTool = await TaskTool.init()
299
- const assistantMessage = (await Session.updateMessage({
300
- id: Identifier.ascending("message"),
301
- role: "assistant",
302
- parentID: lastUser.id,
303
- sessionID,
304
- mode: task.agent,
305
- path: {
306
- cwd: Instance.directory,
307
- root: Instance.worktree,
308
- },
309
- cost: 0,
310
- tokens: {
311
- input: 0,
312
- output: 0,
313
- reasoning: 0,
314
- cache: { read: 0, write: 0 },
315
- },
316
- modelID: model.id,
317
- providerID: model.providerID,
318
- time: {
319
- created: Date.now(),
320
- },
321
- })) as MessageV2.Assistant
322
- let part = (await Session.updatePart({
323
- id: Identifier.ascending("part"),
324
- messageID: assistantMessage.id,
325
- sessionID: assistantMessage.sessionID,
326
- type: "tool",
327
- callID: ulid(),
328
- tool: TaskTool.id,
329
- state: {
330
- status: "running",
331
- input: {
332
- prompt: task.prompt,
333
- description: task.description,
334
- subagent_type: task.agent,
335
- },
336
- time: {
337
- start: Date.now(),
338
- },
339
- },
340
- })) as MessageV2.ToolPart
341
- const result = await taskTool
342
- .execute(
343
- {
344
- prompt: task.prompt,
345
- description: task.description,
346
- subagent_type: task.agent,
347
- },
348
- {
349
- agent: task.agent,
350
- messageID: assistantMessage.id,
351
- sessionID: sessionID,
352
- abort,
353
- async metadata(input) {
354
- await Session.updatePart({
355
- ...part,
356
- type: "tool",
357
- state: {
358
- ...part.state,
359
- ...input,
360
- },
361
- } satisfies MessageV2.ToolPart)
362
- },
363
- },
364
- )
365
- .catch(() => {})
366
- assistantMessage.finish = "tool-calls"
367
- assistantMessage.time.completed = Date.now()
368
- await Session.updateMessage(assistantMessage)
369
- if (result && part.state.status === "running") {
370
- await Session.updatePart({
371
- ...part,
372
- state: {
373
- status: "completed",
374
- input: part.state.input,
375
- title: result.title,
376
- metadata: result.metadata,
377
- output: result.output,
378
- attachments: result.attachments,
379
- time: {
380
- ...part.state.time,
381
- end: Date.now(),
382
- },
383
- },
384
- } satisfies MessageV2.ToolPart)
385
- }
386
- if (!result) {
387
- await Session.updatePart({
388
- ...part,
389
- state: {
390
- status: "error",
391
- error: "Tool execution failed",
392
- time: {
393
- start: part.state.status === "running" ? part.state.time.start : Date.now(),
394
- end: Date.now(),
395
- },
396
- metadata: part.metadata,
397
- input: part.state.input,
398
- },
399
- } satisfies MessageV2.ToolPart)
400
- }
401
- continue
402
- }
403
-
404
- // pending compaction
405
- if (task?.type === "compaction") {
406
- const result = await SessionCompaction.process({
407
- messages: msgs,
408
- parentID: lastUser.id,
409
- abort,
410
- agent: lastUser.agent,
411
- model: {
412
- providerID: model.providerID,
413
- modelID: model.id,
414
- },
415
- sessionID,
416
- auto: task.auto,
417
- })
418
- if (result === "stop") break
419
- continue
420
- }
421
-
422
- // context overflow, needs compaction
423
- if (
424
- lastFinished &&
425
- lastFinished.summary !== true &&
426
- SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model })
427
- ) {
428
- await SessionCompaction.create({
429
- sessionID,
430
- agent: lastUser.agent,
431
- model: lastUser.model,
432
- auto: true,
433
- })
434
- continue
435
- }
436
-
437
- // normal processing
438
- const cfg = await Config.get()
439
- const agent = await Agent.get(lastUser.agent)
440
- const maxSteps = agent.maxSteps ?? Infinity
441
- const isLastStep = step >= maxSteps
442
- msgs = insertReminders({
443
- messages: msgs,
444
- agent,
445
- })
446
- // Count conversation turns (user messages) for telemetry
447
- const conversationTurns = msgs.filter((m) => m.info.role === "user").length
448
- const processor = SessionProcessor.create({
449
- assistantMessage: (await Session.updateMessage({
450
- id: Identifier.ascending("message"),
451
- parentID: lastUser.id,
452
- role: "assistant",
453
- mode: agent.name,
454
- path: {
455
- cwd: Instance.directory,
456
- root: Instance.worktree,
457
- },
458
- cost: 0,
459
- tokens: {
460
- input: 0,
461
- output: 0,
462
- reasoning: 0,
463
- cache: { read: 0, write: 0 },
464
- },
465
- modelID: model.id,
466
- providerID: model.providerID,
467
- time: {
468
- created: Date.now(),
469
- },
470
- sessionID,
471
- })) as MessageV2.Assistant,
472
- sessionID: sessionID,
473
- model,
474
- abort,
475
- conversationTurns,
476
- })
477
- const system = await resolveSystemPrompt({
478
- model,
479
- agent,
480
- system: lastUser.system,
481
- isLastStep,
482
- })
483
- // Get environment separately for cache-optimal placement (after conversation history)
484
- const environment = (await SystemPrompt.environment()).join("\n")
485
- const tools = await resolveTools({
486
- agent,
487
- sessionID,
488
- model,
489
- tools: lastUser.tools,
490
- processor,
491
- })
492
- const provider = await Provider.getProvider(model.providerID)
493
- const params = await Plugin.trigger(
494
- "chat.params",
495
- {
496
- sessionID: sessionID,
497
- agent: lastUser.agent,
498
- model: model,
499
- provider,
500
- message: lastUser,
501
- },
502
- {
503
- temperature: model.capabilities.temperature
504
- ? (agent.temperature ?? ProviderTransform.temperature(model))
505
- : undefined,
506
- topP: agent.topP ?? ProviderTransform.topP(model),
507
- options: pipe(
508
- {},
509
- mergeDeep(ProviderTransform.options(model, sessionID, provider?.options)),
510
- mergeDeep(model.options),
511
- mergeDeep(agent.options),
512
- ),
513
- },
514
- )
515
-
516
- if (step === 1) {
517
- SessionSummary.summarize({
518
- sessionID: sessionID,
519
- messageID: lastUser.id,
520
- })
521
- }
522
-
523
- const result = await processor.process({
524
- onError(error) {
525
- log.error("stream error", {
526
- error,
527
- })
528
- },
529
- async experimental_repairToolCall(input) {
530
- const lower = input.toolCall.toolName.toLowerCase()
531
- if (lower !== input.toolCall.toolName && tools[lower]) {
532
- log.info("repairing tool call", {
533
- tool: input.toolCall.toolName,
534
- repaired: lower,
535
- })
536
- return {
537
- ...input.toolCall,
538
- toolName: lower,
539
- }
540
- }
541
- return {
542
- ...input.toolCall,
543
- input: JSON.stringify({
544
- tool: input.toolCall.toolName,
545
- error: input.error.message,
546
- }),
547
- toolName: "invalid",
548
- }
549
- },
550
- headers: {
551
- ...(model.providerID.startsWith("opencode")
552
- ? {
553
- "x-opencode-project": Instance.project.id,
554
- "x-opencode-session": sessionID,
555
- "x-opencode-request": lastUser.id,
556
- }
557
- : undefined),
558
- ...model.headers,
559
- },
560
- // set to 0, we handle loop
561
- maxRetries: 0,
562
- activeTools: Object.keys(tools).filter((x) => x !== "invalid"),
563
- maxOutputTokens: ProviderTransform.maxOutputTokens(
564
- model.api.npm,
565
- params.options,
566
- model.limit.output,
567
- OUTPUT_TOKEN_MAX,
568
- ),
569
- abortSignal: abort,
570
- providerOptions: ProviderTransform.providerOptions(model.api.npm, model.providerID, params.options),
571
- stopWhen: stepCountIs(1),
572
- temperature: params.temperature,
573
- topP: params.topP,
574
- toolChoice: isLastStep ? "none" : undefined,
575
- messages: (() => {
576
- // Filter messages first
577
- const filteredMsgs = msgs.filter((m) => {
578
- if (m.info.role !== "assistant" || m.info.error === undefined) {
579
- return true
580
- }
581
- if (
582
- MessageV2.AbortedError.isInstance(m.info.error) &&
583
- m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
584
- ) {
585
- return true
586
- }
587
- return false
588
- })
589
-
590
- // Convert to model messages
591
- const modelMessages = MessageV2.toModelMessage(filteredMsgs)
592
-
593
- // Find the last user message index to inject environment before it
594
- let lastUserIdx = modelMessages.length
595
- for (let i = modelMessages.length - 1; i >= 0; i--) {
596
- if (modelMessages[i].role === "user") {
597
- lastUserIdx = i
598
- break
599
- }
600
- }
601
-
602
- // Build final messages: system + history + environment + last user message
603
- const result = [
604
- ...system.map((x) => ({
605
- role: "system" as const,
606
- content: x,
607
- })),
608
- // Messages before the last user message
609
- ...modelMessages.slice(0, lastUserIdx),
610
- // Environment context (injected right before last user message for cache efficiency)
611
- {
612
- role: "user" as const,
613
- content: environment,
614
- },
615
- // Last user message and any messages after it
616
- ...modelMessages.slice(lastUserIdx),
617
- ]
618
-
619
- // Add MAX_STEPS assistant prefill if on last step
620
- if (isLastStep) {
621
- result.push({
622
- role: "assistant" as const,
623
- content: MAX_STEPS,
624
- })
625
- }
626
-
627
- return result
628
- })(),
629
- tools: model.capabilities.toolcall === false ? undefined : tools,
630
- model: wrapLanguageModel({
631
- model: language,
632
- middleware: [
633
- {
634
- async transformParams(args) {
635
- if (args.type === "stream") {
636
- // @ts-expect-error - prompt types are compatible at runtime
637
- args.params.prompt = ProviderTransform.message(args.params.prompt, model)
638
- }
639
- // Transform tool schemas for provider compatibility
640
- if (args.params.tools && Array.isArray(args.params.tools)) {
641
- args.params.tools = args.params.tools.map((tool: any) => {
642
- // Tools at middleware level have inputSchema, not parameters
643
- if (tool.inputSchema && typeof tool.inputSchema === "object") {
644
- // Transform the inputSchema for provider compatibility
645
- return {
646
- ...tool,
647
- inputSchema: ProviderTransform.schema(model, tool.inputSchema),
648
- }
649
- }
650
- // If no inputSchema, return tool unchanged
651
- return tool
652
- })
653
- }
654
- return args.params
655
- },
656
- },
657
- ],
658
- }),
659
- experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
660
- })
661
- if (result === "stop") break
662
- continue
663
- }
664
- SessionCompaction.prune({ sessionID })
665
- for await (const item of MessageV2.stream(sessionID)) {
666
- if (item.info.role === "user") continue
667
- const queued = state()[sessionID]?.callbacks ?? []
668
- for (const q of queued) {
669
- q.resolve(item)
670
- }
671
- return item
672
- }
673
- throw new Error("Impossible")
674
- })
675
-
676
- async function lastModel(sessionID: string) {
677
- for await (const item of MessageV2.stream(sessionID)) {
678
- if (item.info.role === "user" && item.info.model) return item.info.model
679
- }
680
- return Provider.defaultModel()
681
- }
682
-
683
- async function resolveSystemPrompt(input: {
684
- system?: string
685
- agent: Agent.Info
686
- model: Provider.Model
687
- isLastStep?: boolean
688
- }) {
689
- let system = SystemPrompt.header(input.model.providerID)
690
- system.push(
691
- ...(() => {
692
- if (input.system) return [input.system]
693
- if (input.agent.prompt) return [input.agent.prompt]
694
- return SystemPrompt.provider(input.model)
695
- })(),
696
- )
697
- system.push(...(await SystemPrompt.custom()))
698
-
699
- if (input.isLastStep) {
700
- system.push(MAX_STEPS)
701
- }
702
-
703
- // max 2 system prompt messages for caching purposes
704
- const [first, ...rest] = system
705
- system = [first, rest.join("\n")]
706
- return system
707
- }
708
-
709
- async function resolveTools(input: {
710
- agent: Agent.Info
711
- model: Provider.Model
712
- sessionID: string
713
- tools?: Record<string, boolean>
714
- processor: SessionProcessor.Info
715
- }) {
716
- const tools: Record<string, AITool> = {}
717
- const enabledTools = pipe(
718
- input.agent.tools,
719
- mergeDeep(await ToolRegistry.enabled(input.agent)),
720
- mergeDeep(input.tools ?? {}),
721
- )
722
- for (const item of await ToolRegistry.tools(input.model.providerID)) {
723
- if (Wildcard.all(item.id, enabledTools) === false) continue
724
- const schema = ProviderTransform.schema(input.model, z.toJSONSchema(item.parameters))
725
- tools[item.id] = tool({
726
- id: item.id as any,
727
- description: item.description,
728
- inputSchema: jsonSchema(schema as any),
729
- async execute(args, options) {
730
- await Plugin.trigger(
731
- "tool.execute.before",
732
- {
733
- tool: item.id,
734
- sessionID: input.sessionID,
735
- callID: options.toolCallId,
736
- },
737
- {
738
- args,
739
- },
740
- )
741
- const result = await item.execute(args, {
742
- sessionID: input.sessionID,
743
- abort: options.abortSignal!,
744
- messageID: input.processor.message.id,
745
- callID: options.toolCallId,
746
- extra: { model: input.model },
747
- agent: input.agent.name,
748
- metadata: async (val) => {
749
- const match = input.processor.partFromToolCall(options.toolCallId)
750
- if (match && match.state.status === "running") {
751
- await Session.updatePart({
752
- ...match,
753
- state: {
754
- title: val.title,
755
- metadata: val.metadata,
756
- status: "running",
757
- input: args,
758
- time: {
759
- start: Date.now(),
760
- },
761
- },
762
- })
763
- }
764
- },
765
- })
766
- await Plugin.trigger(
767
- "tool.execute.after",
768
- {
769
- tool: item.id,
770
- sessionID: input.sessionID,
771
- callID: options.toolCallId,
772
- },
773
- result,
774
- )
775
- return result
776
- },
777
- toModelOutput(result) {
778
- return {
779
- type: "text",
780
- value: result.output,
781
- }
782
- },
783
- })
784
- }
785
-
786
- for (const [key, item] of Object.entries(await MCP.tools())) {
787
- if (Wildcard.all(key, enabledTools) === false) continue
788
- const execute = item.execute
789
- if (!execute) continue
790
-
791
- // Wrap execute to add plugin hooks and format output
792
- item.execute = async (args, opts) => {
793
- await Plugin.trigger(
794
- "tool.execute.before",
795
- {
796
- tool: key,
797
- sessionID: input.sessionID,
798
- callID: opts.toolCallId,
799
- },
800
- {
801
- args,
802
- },
803
- )
804
- const result = await execute(args, opts)
805
-
806
- await Plugin.trigger(
807
- "tool.execute.after",
808
- {
809
- tool: key,
810
- sessionID: input.sessionID,
811
- callID: opts.toolCallId,
812
- },
813
- result,
814
- )
815
-
816
- const textParts: string[] = []
817
- const attachments: MessageV2.FilePart[] = []
818
-
819
- for (const contentItem of result.content) {
820
- if (contentItem.type === "text") {
821
- textParts.push(contentItem.text)
822
- } else if (contentItem.type === "image") {
823
- attachments.push({
824
- id: Identifier.ascending("part"),
825
- sessionID: input.sessionID,
826
- messageID: input.processor.message.id,
827
- type: "file",
828
- mime: contentItem.mimeType,
829
- url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
830
- })
831
- }
832
- // Add support for other types if needed
833
- }
834
-
835
- return {
836
- title: "",
837
- metadata: result.metadata ?? {},
838
- output: textParts.join("\n\n"),
839
- attachments,
840
- content: result.content, // directly return content to preserve ordering when outputting to model
841
- }
842
- }
843
- item.toModelOutput = (result) => {
844
- return {
845
- type: "text",
846
- value: result.output,
847
- }
848
- }
849
- tools[key] = item
850
- }
851
- return tools
852
- }
853
-
854
- async function createUserMessage(input: PromptInput) {
855
- const agent = await Agent.get(input.agent ?? "build")
856
- const info: MessageV2.Info = {
857
- id: input.messageID ?? Identifier.ascending("message"),
858
- role: "user",
859
- sessionID: input.sessionID,
860
- time: {
861
- created: Date.now(),
862
- },
863
- tools: input.tools,
864
- system: input.system,
865
- agent: agent.name,
866
- model: input.model ?? agent.model ?? (await lastModel(input.sessionID)),
867
- }
868
-
869
- const parts = await Promise.all(
870
- input.parts.map(async (part): Promise<MessageV2.Part[]> => {
871
- if (part.type === "file") {
872
- const url = new URL(part.url)
873
- switch (url.protocol) {
874
- case "data:":
875
- if (part.mime === "text/plain") {
876
- return [
877
- {
878
- id: Identifier.ascending("part"),
879
- messageID: info.id,
880
- sessionID: input.sessionID,
881
- type: "text",
882
- synthetic: true,
883
- text: `Called the Read tool with the following input: ${JSON.stringify({ filePath: part.filename })}`,
884
- },
885
- {
886
- id: Identifier.ascending("part"),
887
- messageID: info.id,
888
- sessionID: input.sessionID,
889
- type: "text",
890
- synthetic: true,
891
- text: Buffer.from(part.url, "base64url").toString(),
892
- },
893
- {
894
- ...part,
895
- id: part.id ?? Identifier.ascending("part"),
896
- messageID: info.id,
897
- sessionID: input.sessionID,
898
- },
899
- ]
900
- }
901
- break
902
- case "file:":
903
- log.info("file", { mime: part.mime })
904
- // have to normalize, symbol search returns absolute paths
905
- // Decode the pathname since URL constructor doesn't automatically decode it
906
- const filepath = fileURLToPath(part.url)
907
- const stat = await Bun.file(filepath).stat()
908
-
909
- if (stat.isDirectory()) {
910
- part.mime = "application/x-directory"
911
- }
912
-
913
- if (part.mime === "text/plain") {
914
- let offset: number | undefined = undefined
915
- let limit: number | undefined = undefined
916
- const range = {
917
- start: url.searchParams.get("start"),
918
- end: url.searchParams.get("end"),
919
- }
920
- if (range.start != null) {
921
- const filePathURI = part.url.split("?")[0]
922
- let start = parseInt(range.start)
923
- let end = range.end ? parseInt(range.end) : undefined
924
- // some LSP servers (eg, gopls) don't give full range in
925
- // workspace/symbol searches, so we'll try to find the
926
- // symbol in the document to get the full range
927
- if (start === end) {
928
- const symbols = await LSP.documentSymbol(filePathURI)
929
- for (const symbol of symbols) {
930
- let range: LSP.Range | undefined
931
- if ("range" in symbol) {
932
- range = symbol.range
933
- } else if ("location" in symbol) {
934
- range = symbol.location.range
935
- }
936
- if (range?.start?.line && range?.start?.line === start) {
937
- start = range.start.line
938
- end = range?.end?.line ?? start
939
- break
940
- }
941
- }
942
- }
943
- offset = Math.max(start - 1, 0)
944
- if (end) {
945
- limit = end - offset
946
- }
947
- }
948
- const args = { filePath: filepath, offset, limit }
949
-
950
- const pieces: MessageV2.Part[] = [
951
- {
952
- id: Identifier.ascending("part"),
953
- messageID: info.id,
954
- sessionID: input.sessionID,
955
- type: "text",
956
- synthetic: true,
957
- text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
958
- },
959
- ]
960
-
961
- await ReadTool.init()
962
- .then(async (t) => {
963
- const model = await Provider.getModel(info.model.providerID, info.model.modelID)
964
- const result = await t.execute(args, {
965
- sessionID: input.sessionID,
966
- abort: new AbortController().signal,
967
- agent: input.agent!,
968
- messageID: info.id,
969
- extra: { bypassCwdCheck: true, model },
970
- metadata: async () => {},
971
- })
972
- pieces.push({
973
- id: Identifier.ascending("part"),
974
- messageID: info.id,
975
- sessionID: input.sessionID,
976
- type: "text",
977
- synthetic: true,
978
- text: result.output,
979
- })
980
- if (result.attachments?.length) {
981
- pieces.push(
982
- ...result.attachments.map((attachment) => ({
983
- ...attachment,
984
- synthetic: true,
985
- filename: attachment.filename ?? part.filename,
986
- messageID: info.id,
987
- sessionID: input.sessionID,
988
- })),
989
- )
990
- } else {
991
- pieces.push({
992
- ...part,
993
- id: part.id ?? Identifier.ascending("part"),
994
- messageID: info.id,
995
- sessionID: input.sessionID,
996
- })
997
- }
998
- })
999
- .catch((error) => {
1000
- log.error("failed to read file", { error })
1001
- const message = error instanceof Error ? error.message : error.toString()
1002
- Bus.publish(Session.Event.Error, {
1003
- sessionID: input.sessionID,
1004
- error: new NamedError.Unknown({
1005
- message,
1006
- }).toObject(),
1007
- })
1008
- pieces.push({
1009
- id: Identifier.ascending("part"),
1010
- messageID: info.id,
1011
- sessionID: input.sessionID,
1012
- type: "text",
1013
- synthetic: true,
1014
- text: `Read tool failed to read ${filepath} with the following error: ${message}`,
1015
- })
1016
- })
1017
-
1018
- return pieces
1019
- }
1020
-
1021
- if (part.mime === "application/x-directory") {
1022
- const args = { path: filepath }
1023
- const result = await ListTool.init().then((t) =>
1024
- t.execute(args, {
1025
- sessionID: input.sessionID,
1026
- abort: new AbortController().signal,
1027
- agent: input.agent!,
1028
- messageID: info.id,
1029
- extra: { bypassCwdCheck: true },
1030
- metadata: async () => {},
1031
- }),
1032
- )
1033
- return [
1034
- {
1035
- id: Identifier.ascending("part"),
1036
- messageID: info.id,
1037
- sessionID: input.sessionID,
1038
- type: "text",
1039
- synthetic: true,
1040
- text: `Called the list tool with the following input: ${JSON.stringify(args)}`,
1041
- },
1042
- {
1043
- id: Identifier.ascending("part"),
1044
- messageID: info.id,
1045
- sessionID: input.sessionID,
1046
- type: "text",
1047
- synthetic: true,
1048
- text: result.output,
1049
- },
1050
- {
1051
- ...part,
1052
- id: part.id ?? Identifier.ascending("part"),
1053
- messageID: info.id,
1054
- sessionID: input.sessionID,
1055
- },
1056
- ]
1057
- }
1058
-
1059
- const file = Bun.file(filepath)
1060
- FileTime.read(input.sessionID, filepath)
1061
- return [
1062
- {
1063
- id: Identifier.ascending("part"),
1064
- messageID: info.id,
1065
- sessionID: input.sessionID,
1066
- type: "text",
1067
- text: `Called the Read tool with the following input: {\"filePath\":\"${filepath}\"}`,
1068
- synthetic: true,
1069
- },
1070
- {
1071
- id: part.id ?? Identifier.ascending("part"),
1072
- messageID: info.id,
1073
- sessionID: input.sessionID,
1074
- type: "file",
1075
- url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"),
1076
- mime: part.mime,
1077
- filename: part.filename!,
1078
- source: part.source,
1079
- },
1080
- ]
1081
- }
1082
- }
1083
-
1084
- if (part.type === "agent") {
1085
- return [
1086
- {
1087
- id: Identifier.ascending("part"),
1088
- ...part,
1089
- messageID: info.id,
1090
- sessionID: input.sessionID,
1091
- },
1092
- {
1093
- id: Identifier.ascending("part"),
1094
- messageID: info.id,
1095
- sessionID: input.sessionID,
1096
- type: "text",
1097
- synthetic: true,
1098
- text:
1099
- "Use the above message and context to generate a prompt and call the task tool with subagent: " +
1100
- part.name,
1101
- },
1102
- ]
1103
- }
1104
-
1105
- return [
1106
- {
1107
- id: Identifier.ascending("part"),
1108
- ...part,
1109
- messageID: info.id,
1110
- sessionID: input.sessionID,
1111
- },
1112
- ]
1113
- }),
1114
- ).then((x) => x.flat())
1115
-
1116
- await Plugin.trigger(
1117
- "chat.message",
1118
- {
1119
- sessionID: input.sessionID,
1120
- agent: input.agent,
1121
- model: input.model,
1122
- messageID: input.messageID,
1123
- },
1124
- {
1125
- message: info,
1126
- parts,
1127
- },
1128
- )
1129
-
1130
- await Session.updateMessage(info)
1131
- for (const part of parts) {
1132
- await Session.updatePart(part)
1133
- }
1134
-
1135
- return {
1136
- info,
1137
- parts,
1138
- }
1139
- }
1140
-
1141
- function insertReminders(input: { messages: MessageV2.WithParts[]; agent: Agent.Info }) {
1142
- const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
1143
- if (!userMessage) return input.messages
1144
- if (input.agent.name === "plan") {
1145
- userMessage.parts.push({
1146
- id: Identifier.ascending("part"),
1147
- messageID: userMessage.info.id,
1148
- sessionID: userMessage.info.sessionID,
1149
- type: "text",
1150
- // TODO (for mr dax): update to use the anthropic full fledged one (see plan-reminder-anthropic.txt)
1151
- text: PROMPT_PLAN,
1152
- synthetic: true,
1153
- })
1154
- }
1155
- const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.mode === "plan")
1156
- if (wasPlan && input.agent.name === "build") {
1157
- userMessage.parts.push({
1158
- id: Identifier.ascending("part"),
1159
- messageID: userMessage.info.id,
1160
- sessionID: userMessage.info.sessionID,
1161
- type: "text",
1162
- text: BUILD_SWITCH,
1163
- synthetic: true,
1164
- })
1165
- }
1166
- return input.messages
1167
- }
1168
-
1169
- export const ShellInput = z.object({
1170
- sessionID: Identifier.schema("session"),
1171
- agent: z.string(),
1172
- model: z
1173
- .object({
1174
- providerID: z.string(),
1175
- modelID: z.string(),
1176
- })
1177
- .optional(),
1178
- command: z.string(),
1179
- })
1180
- export type ShellInput = z.infer<typeof ShellInput>
1181
- export async function shell(input: ShellInput) {
1182
- const session = await Session.get(input.sessionID)
1183
- if (session.revert) {
1184
- SessionRevert.cleanup(session)
1185
- }
1186
- const agent = await Agent.get(input.agent)
1187
- const model = input.model ?? agent.model ?? (await lastModel(input.sessionID))
1188
- const userMsg: MessageV2.User = {
1189
- id: Identifier.ascending("message"),
1190
- sessionID: input.sessionID,
1191
- time: {
1192
- created: Date.now(),
1193
- },
1194
- role: "user",
1195
- agent: input.agent,
1196
- model: {
1197
- providerID: model.providerID,
1198
- modelID: model.modelID,
1199
- },
1200
- }
1201
- await Session.updateMessage(userMsg)
1202
- const userPart: MessageV2.Part = {
1203
- type: "text",
1204
- id: Identifier.ascending("part"),
1205
- messageID: userMsg.id,
1206
- sessionID: input.sessionID,
1207
- text: "The following tool was executed by the user",
1208
- synthetic: true,
1209
- }
1210
- await Session.updatePart(userPart)
1211
-
1212
- const msg: MessageV2.Assistant = {
1213
- id: Identifier.ascending("message"),
1214
- sessionID: input.sessionID,
1215
- parentID: userMsg.id,
1216
- mode: input.agent,
1217
- cost: 0,
1218
- path: {
1219
- cwd: Instance.directory,
1220
- root: Instance.worktree,
1221
- },
1222
- time: {
1223
- created: Date.now(),
1224
- },
1225
- role: "assistant",
1226
- tokens: {
1227
- input: 0,
1228
- output: 0,
1229
- reasoning: 0,
1230
- cache: { read: 0, write: 0 },
1231
- },
1232
- modelID: model.modelID,
1233
- providerID: model.providerID,
1234
- }
1235
- await Session.updateMessage(msg)
1236
- const part: MessageV2.Part = {
1237
- type: "tool",
1238
- id: Identifier.ascending("part"),
1239
- messageID: msg.id,
1240
- sessionID: input.sessionID,
1241
- tool: "bash",
1242
- callID: ulid(),
1243
- state: {
1244
- status: "running",
1245
- time: {
1246
- start: Date.now(),
1247
- },
1248
- input: {
1249
- command: input.command,
1250
- },
1251
- },
1252
- }
1253
- await Session.updatePart(part)
1254
- const shell = process.env["SHELL"] ?? "bash"
1255
- const shellName = path.basename(shell)
1256
-
1257
- const invocations: Record<string, { args: string[] }> = {
1258
- nu: {
1259
- args: ["-c", input.command],
1260
- },
1261
- fish: {
1262
- args: ["-c", input.command],
1263
- },
1264
- zsh: {
1265
- args: [
1266
- "-c",
1267
- "-l",
1268
- `
1269
- [[ -f ~/.zshenv ]] && source ~/.zshenv >/dev/null 2>&1 || true
1270
- [[ -f "\${ZDOTDIR:-$HOME}/.zshrc" ]] && source "\${ZDOTDIR:-$HOME}/.zshrc" >/dev/null 2>&1 || true
1271
- ${input.command}
1272
- `,
1273
- ],
1274
- },
1275
- bash: {
1276
- args: [
1277
- "-c",
1278
- "-l",
1279
- `
1280
- [[ -f ~/.bashrc ]] && source ~/.bashrc >/dev/null 2>&1 || true
1281
- ${input.command}
1282
- `,
1283
- ],
1284
- },
1285
- // Fallback: any shell that doesn't match those above
1286
- "": {
1287
- args: ["-c", "-l", `${input.command}`],
1288
- },
1289
- }
1290
-
1291
- const matchingInvocation = invocations[shellName] ?? invocations[""]
1292
- const args = matchingInvocation?.args
1293
-
1294
- const proc = spawn(shell, args, {
1295
- cwd: Instance.directory,
1296
- detached: true,
1297
- stdio: ["ignore", "pipe", "pipe"],
1298
- env: {
1299
- ...process.env,
1300
- TERM: "dumb",
1301
- },
1302
- })
1303
-
1304
- let output = ""
1305
-
1306
- proc.stdout?.on("data", (chunk) => {
1307
- output += chunk.toString()
1308
- if (part.state.status === "running") {
1309
- part.state.metadata = {
1310
- output: output,
1311
- description: "",
1312
- }
1313
- Session.updatePart(part)
1314
- }
1315
- })
1316
-
1317
- proc.stderr?.on("data", (chunk) => {
1318
- output += chunk.toString()
1319
- if (part.state.status === "running") {
1320
- part.state.metadata = {
1321
- output: output,
1322
- description: "",
1323
- }
1324
- Session.updatePart(part)
1325
- }
1326
- })
1327
-
1328
- await new Promise<void>((resolve) => {
1329
- proc.on("close", () => {
1330
- resolve()
1331
- })
1332
- })
1333
- msg.time.completed = Date.now()
1334
- await Session.updateMessage(msg)
1335
- if (part.state.status === "running") {
1336
- part.state = {
1337
- status: "completed",
1338
- time: {
1339
- ...part.state.time,
1340
- end: Date.now(),
1341
- },
1342
- input: part.state.input,
1343
- title: "",
1344
- metadata: {
1345
- output,
1346
- description: "",
1347
- },
1348
- output,
1349
- }
1350
- await Session.updatePart(part)
1351
- }
1352
- return { info: msg, parts: [part] }
1353
- }
1354
-
1355
- export const CommandInput = z.object({
1356
- messageID: Identifier.schema("message").optional(),
1357
- sessionID: Identifier.schema("session"),
1358
- agent: z.string().optional(),
1359
- model: z.string().optional(),
1360
- arguments: z.string(),
1361
- command: z.string(),
1362
- })
1363
- export type CommandInput = z.infer<typeof CommandInput>
1364
- const bashRegex = /!`([^`]+)`/g
1365
- const argsRegex = /(?:[^\s"']+|"[^"]*"|'[^']*')+/g
1366
- const placeholderRegex = /\$(\d+)/g
1367
- const quoteTrimRegex = /^["']|["']$/g
1368
- /**
1369
- * Regular expression to match @ file references in text
1370
- * Matches @ followed by file paths, excluding commas, periods at end of sentences, and backticks
1371
- * Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references)
1372
- */
1373
-
1374
- export async function command(input: CommandInput) {
1375
- log.info("command", input)
1376
- const command = await Command.get(input.command)
1377
- const agentName = command.agent ?? input.agent ?? "build"
1378
-
1379
- const raw = input.arguments.match(argsRegex) ?? []
1380
- const args = raw.map((arg) => arg.replace(quoteTrimRegex, ""))
1381
-
1382
- const placeholders = command.template.match(placeholderRegex) ?? []
1383
- let last = 0
1384
- for (const item of placeholders) {
1385
- const value = Number(item.slice(1))
1386
- if (value > last) last = value
1387
- }
1388
-
1389
- // Let the final placeholder swallow any extra arguments so prompts read naturally
1390
- const withArgs = command.template.replaceAll(placeholderRegex, (_, index) => {
1391
- const position = Number(index)
1392
- const argIndex = position - 1
1393
- if (argIndex >= args.length) return ""
1394
- if (position === last) return args.slice(argIndex).join(" ")
1395
- return args[argIndex]
1396
- })
1397
- let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
1398
-
1399
- const shell = ConfigMarkdown.shell(template)
1400
- if (shell.length > 0) {
1401
- const results = await Promise.all(
1402
- shell.map(async ([, cmd]) => {
1403
- try {
1404
- return await $`${{ raw: cmd }}`.nothrow().text()
1405
- } catch (error) {
1406
- return `Error executing command: ${error instanceof Error ? error.message : String(error)}`
1407
- }
1408
- }),
1409
- )
1410
- let index = 0
1411
- template = template.replace(bashRegex, () => results[index++])
1412
- }
1413
- template = template.trim()
1414
-
1415
- const model = await (async () => {
1416
- if (command.model) {
1417
- return Provider.parseModel(command.model)
1418
- }
1419
- if (command.agent) {
1420
- const cmdAgent = await Agent.get(command.agent)
1421
- if (cmdAgent.model) {
1422
- return cmdAgent.model
1423
- }
1424
- }
1425
- if (input.model) return Provider.parseModel(input.model)
1426
- return await lastModel(input.sessionID)
1427
- })()
1428
- const agent = await Agent.get(agentName)
1429
-
1430
- const parts =
1431
- (agent.mode === "subagent" && command.subtask !== false) || command.subtask === true
1432
- ? [
1433
- {
1434
- type: "subtask" as const,
1435
- agent: agent.name,
1436
- description: command.description ?? "",
1437
- // TODO: how can we make task tool accept a more complex input?
1438
- prompt: await resolvePromptParts(template).then((x) => x.find((y) => y.type === "text")?.text ?? ""),
1439
- },
1440
- ]
1441
- : await resolvePromptParts(template)
1442
-
1443
- const result = (await prompt({
1444
- sessionID: input.sessionID,
1445
- messageID: input.messageID,
1446
- model,
1447
- agent: agentName,
1448
- parts,
1449
- })) as MessageV2.WithParts
1450
-
1451
- Bus.publish(Command.Event.Executed, {
1452
- name: input.command,
1453
- sessionID: input.sessionID,
1454
- arguments: input.arguments,
1455
- messageID: result.info.id,
1456
- })
1457
-
1458
- return result
1459
- }
1460
-
1461
- async function ensureTitle(input: {
1462
- session: Session.Info
1463
- message: MessageV2.WithParts
1464
- history: MessageV2.WithParts[]
1465
- providerID: string
1466
- modelID: string
1467
- }) {
1468
- if (input.session.parentID) return
1469
- if (!Session.isDefaultTitle(input.session.title)) return
1470
- const isFirst =
1471
- input.history.filter((m) => m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic))
1472
- .length === 1
1473
- if (!isFirst) return
1474
- const cfg = await Config.get()
1475
- const small =
1476
- (await Provider.getSmallModel(input.providerID)) ?? (await Provider.getModel(input.providerID, input.modelID))
1477
- const language = await Provider.getLanguage(small)
1478
- const provider = await Provider.getProvider(small.providerID)
1479
- const options = pipe(
1480
- {},
1481
- mergeDeep(ProviderTransform.options(small, input.session.id, provider?.options)),
1482
- mergeDeep(ProviderTransform.smallOptions(small)),
1483
- mergeDeep(small.options),
1484
- )
1485
- await generateText({
1486
- // use higher # for reasoning models since reasoning tokens eat up a lot of the budget
1487
- maxOutputTokens: small.capabilities.reasoning ? 3000 : 20,
1488
- providerOptions: ProviderTransform.providerOptions(small.api.npm, small.providerID, options),
1489
- messages: [
1490
- ...SystemPrompt.title(small.providerID).map(
1491
- (x): ModelMessage => ({
1492
- role: "system",
1493
- content: x,
1494
- }),
1495
- ),
1496
- {
1497
- role: "user",
1498
- content: "Generate a title for this conversation:\n",
1499
- },
1500
- ...MessageV2.toModelMessage([
1501
- {
1502
- info: {
1503
- id: Identifier.ascending("message"),
1504
- role: "user",
1505
- sessionID: input.session.id,
1506
- time: {
1507
- created: Date.now(),
1508
- },
1509
- agent: input.message.info.role === "user" ? input.message.info.agent : "build",
1510
- model: {
1511
- providerID: input.providerID,
1512
- modelID: input.modelID,
1513
- },
1514
- },
1515
- parts: input.message.parts,
1516
- },
1517
- ]),
1518
- ],
1519
- headers: small.headers,
1520
- model: language,
1521
- experimental_telemetry: { isEnabled: cfg.experimental?.openTelemetry },
1522
- })
1523
- .then((result) => {
1524
- if (result.text)
1525
- return Session.update(input.session.id, (draft) => {
1526
- const cleaned = result.text
1527
- .replace(/<think>[\s\S]*?<\/think>\s*/g, "")
1528
- .split("\n")
1529
- .map((line) => line.trim())
1530
- .find((line) => line.length > 0)
1531
- if (!cleaned) return
1532
-
1533
- const title = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
1534
- draft.title = title
1535
- })
1536
- })
1537
- .catch((error) => {
1538
- log.error("failed to generate title", { error, model: small.id })
1539
- })
1540
- }
1541
- }