cerebras-cli 1.0.5 → 1.0.138

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 (322) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +10 -0
  3. package/README.md +5 -3
  4. package/bin/{opencode.cjs → opencode} +4 -4
  5. package/bunfig.toml +4 -0
  6. package/package.json +89 -32
  7. package/parsers-config.ts +239 -0
  8. package/script/build.ts +151 -0
  9. package/script/postinstall.mjs +122 -0
  10. package/script/publish.ts +256 -0
  11. package/script/schema.ts +47 -0
  12. package/snake_game.py +111 -0
  13. package/src/acp/README.md +164 -0
  14. package/src/acp/agent.ts +812 -0
  15. package/src/acp/session.ts +70 -0
  16. package/src/acp/types.ts +22 -0
  17. package/src/agent/agent.ts +310 -0
  18. package/src/agent/generate.txt +75 -0
  19. package/src/auth/index.ts +70 -0
  20. package/src/bun/index.ts +152 -0
  21. package/src/bus/global.ts +10 -0
  22. package/src/bus/index.ts +142 -0
  23. package/src/cli/bootstrap.ts +17 -0
  24. package/src/cli/cmd/acp.ts +88 -0
  25. package/src/cli/cmd/agent.ts +165 -0
  26. package/src/cli/cmd/auth.ts +369 -0
  27. package/src/cli/cmd/cmd.ts +7 -0
  28. package/src/cli/cmd/debug/config.ts +15 -0
  29. package/src/cli/cmd/debug/file.ts +91 -0
  30. package/src/cli/cmd/debug/index.ts +43 -0
  31. package/src/cli/cmd/debug/lsp.ts +47 -0
  32. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  33. package/src/cli/cmd/debug/scrap.ts +15 -0
  34. package/src/cli/cmd/debug/skill.ts +36 -0
  35. package/src/cli/cmd/debug/snapshot.ts +48 -0
  36. package/src/cli/cmd/export.ts +88 -0
  37. package/src/cli/cmd/generate.ts +38 -0
  38. package/src/cli/cmd/github.ts +1200 -0
  39. package/src/cli/cmd/import.ts +98 -0
  40. package/src/cli/cmd/mcp.ts +400 -0
  41. package/src/cli/cmd/models.ts +77 -0
  42. package/src/cli/cmd/pr.ts +112 -0
  43. package/src/cli/cmd/run.ts +342 -0
  44. package/src/cli/cmd/serve.ts +31 -0
  45. package/src/cli/cmd/session.ts +106 -0
  46. package/src/cli/cmd/stats.ts +298 -0
  47. package/src/cli/cmd/tui/app.tsx +833 -0
  48. package/src/cli/cmd/tui/attach.ts +25 -0
  49. package/src/cli/cmd/tui/component/border.tsx +21 -0
  50. package/src/cli/cmd/tui/component/cerebras-onboarding.tsx +225 -0
  51. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  52. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  53. package/src/cli/cmd/tui/component/dialog-feedback.tsx +160 -0
  54. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  55. package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
  56. package/src/cli/cmd/tui/component/dialog-notification.tsx +78 -0
  57. package/src/cli/cmd/tui/component/dialog-provider.tsx +222 -0
  58. package/src/cli/cmd/tui/component/dialog-session-list.tsx +97 -0
  59. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  60. package/src/cli/cmd/tui/component/dialog-status.tsx +114 -0
  61. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  62. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  63. package/src/cli/cmd/tui/component/logo.tsx +43 -0
  64. package/src/cli/cmd/tui/component/notification-banner.tsx +58 -0
  65. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +530 -0
  66. package/src/cli/cmd/tui/component/prompt/history.tsx +107 -0
  67. package/src/cli/cmd/tui/component/prompt/index.tsx +931 -0
  68. package/src/cli/cmd/tui/component/quickstart-onboarding.tsx +116 -0
  69. package/src/cli/cmd/tui/context/args.tsx +14 -0
  70. package/src/cli/cmd/tui/context/directory.ts +12 -0
  71. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  72. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  73. package/src/cli/cmd/tui/context/keybind.tsx +111 -0
  74. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  75. package/src/cli/cmd/tui/context/local.tsx +338 -0
  76. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  77. package/src/cli/cmd/tui/context/route.tsx +45 -0
  78. package/src/cli/cmd/tui/context/sdk.tsx +75 -0
  79. package/src/cli/cmd/tui/context/sync.tsx +374 -0
  80. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  81. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  82. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  83. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  84. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  85. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  86. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  87. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  88. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  89. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  90. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  91. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  92. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  93. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  94. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  95. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  96. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  97. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  98. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  99. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  100. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  101. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  102. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  103. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  104. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  105. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  106. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  107. package/src/cli/cmd/tui/context/theme.tsx +1077 -0
  108. package/src/cli/cmd/tui/event.ts +39 -0
  109. package/src/cli/cmd/tui/routes/home.tsx +150 -0
  110. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +93 -0
  111. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +37 -0
  112. package/src/cli/cmd/tui/routes/session/footer.tsx +76 -0
  113. package/src/cli/cmd/tui/routes/session/header.tsx +181 -0
  114. package/src/cli/cmd/tui/routes/session/index.tsx +1695 -0
  115. package/src/cli/cmd/tui/routes/session/sidebar.tsx +686 -0
  116. package/src/cli/cmd/tui/spawn.ts +60 -0
  117. package/src/cli/cmd/tui/thread.ts +120 -0
  118. package/src/cli/cmd/tui/ui/dialog-alert.tsx +55 -0
  119. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +81 -0
  120. package/src/cli/cmd/tui/ui/dialog-help.tsx +36 -0
  121. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +75 -0
  122. package/src/cli/cmd/tui/ui/dialog-select.tsx +317 -0
  123. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  124. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  125. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  126. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  127. package/src/cli/cmd/tui/util/editor.ts +32 -0
  128. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  129. package/src/cli/cmd/tui/worker.ts +63 -0
  130. package/src/cli/cmd/uninstall.ts +344 -0
  131. package/src/cli/cmd/upgrade.ts +67 -0
  132. package/src/cli/cmd/web.ts +84 -0
  133. package/src/cli/error.ts +55 -0
  134. package/src/cli/ui.ts +84 -0
  135. package/src/cli/upgrade.ts +25 -0
  136. package/src/command/index.ts +79 -0
  137. package/src/command/template/initialize.txt +10 -0
  138. package/src/command/template/review.txt +73 -0
  139. package/src/config/config.ts +886 -0
  140. package/src/config/markdown.ts +41 -0
  141. package/src/env/index.ts +26 -0
  142. package/src/file/fzf.ts +124 -0
  143. package/src/file/ignore.ts +83 -0
  144. package/src/file/index.ts +326 -0
  145. package/src/file/ripgrep.ts +391 -0
  146. package/src/file/time.ts +38 -0
  147. package/src/file/watcher.ts +89 -0
  148. package/src/flag/flag.ts +29 -0
  149. package/src/format/formatter.ts +277 -0
  150. package/src/format/index.ts +137 -0
  151. package/src/global/index.ts +52 -0
  152. package/src/id/id.ts +73 -0
  153. package/src/ide/index.ts +75 -0
  154. package/src/index.ts +158 -0
  155. package/src/installation/index.ts +194 -0
  156. package/src/lsp/client.ts +215 -0
  157. package/src/lsp/index.ts +370 -0
  158. package/src/lsp/language.ts +111 -0
  159. package/src/lsp/server.ts +1327 -0
  160. package/src/mcp/auth.ts +82 -0
  161. package/src/mcp/index.ts +576 -0
  162. package/src/mcp/oauth-callback.ts +203 -0
  163. package/src/mcp/oauth-provider.ts +132 -0
  164. package/src/notification/index.ts +101 -0
  165. package/src/patch/index.ts +622 -0
  166. package/src/permission/index.ts +198 -0
  167. package/src/plugin/index.ts +95 -0
  168. package/src/project/bootstrap.ts +31 -0
  169. package/src/project/instance.ts +68 -0
  170. package/src/project/project.ts +133 -0
  171. package/src/project/state.ts +65 -0
  172. package/src/project/vcs.ts +77 -0
  173. package/src/provider/auth.ts +143 -0
  174. package/src/provider/models-macro.ts +11 -0
  175. package/src/provider/models.ts +93 -0
  176. package/src/provider/provider.ts +1005 -0
  177. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  178. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  179. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  180. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  181. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +27 -0
  182. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  183. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  184. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  185. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  186. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  187. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  188. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  189. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  190. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  191. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  192. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  193. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  194. package/src/provider/transform.ts +406 -0
  195. package/src/pty/index.ts +226 -0
  196. package/src/ratelimit/index.ts +185 -0
  197. package/src/server/error.ts +36 -0
  198. package/src/server/project.ts +50 -0
  199. package/src/server/server.ts +2463 -0
  200. package/src/server/tui.ts +71 -0
  201. package/src/session/compaction.ts +257 -0
  202. package/src/session/index.ts +470 -0
  203. package/src/session/message-v2.ts +641 -0
  204. package/src/session/message.ts +189 -0
  205. package/src/session/processor.ts +448 -0
  206. package/src/session/prompt/anthropic-20250930.txt +166 -0
  207. package/src/session/prompt/anthropic.txt +105 -0
  208. package/src/session/prompt/anthropic_spoof.txt +1 -0
  209. package/src/session/prompt/beast.txt +147 -0
  210. package/src/session/prompt/build-switch.txt +5 -0
  211. package/src/session/prompt/codex.txt +318 -0
  212. package/src/session/prompt/compaction.txt +12 -0
  213. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  214. package/src/session/prompt/gemini.txt +155 -0
  215. package/src/session/prompt/max-steps.txt +16 -0
  216. package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
  217. package/src/session/prompt/plan.txt +26 -0
  218. package/src/session/prompt/polaris.txt +107 -0
  219. package/src/session/prompt/qwen.txt +109 -0
  220. package/src/session/prompt/summarize.txt +4 -0
  221. package/src/session/prompt/title.txt +36 -0
  222. package/src/session/prompt.ts +1541 -0
  223. package/src/session/retry.ts +82 -0
  224. package/src/session/revert.ts +108 -0
  225. package/src/session/status.ts +75 -0
  226. package/src/session/summary.ts +203 -0
  227. package/src/session/system.ts +148 -0
  228. package/src/session/todo.ts +36 -0
  229. package/src/share/share-next.ts +195 -0
  230. package/src/share/share.ts +87 -0
  231. package/src/skill/index.ts +2 -0
  232. package/src/skill/skill.ts +138 -0
  233. package/src/snapshot/index.ts +197 -0
  234. package/src/storage/storage.ts +226 -0
  235. package/src/telemetry/index.ts +247 -0
  236. package/src/tool/bash.ts +365 -0
  237. package/src/tool/bash.txt +128 -0
  238. package/src/tool/batch.ts +173 -0
  239. package/src/tool/batch.txt +28 -0
  240. package/src/tool/codesearch.ts +138 -0
  241. package/src/tool/codesearch.txt +12 -0
  242. package/src/tool/edit.ts +674 -0
  243. package/src/tool/edit.txt +10 -0
  244. package/src/tool/glob.ts +65 -0
  245. package/src/tool/glob.txt +6 -0
  246. package/src/tool/grep.ts +120 -0
  247. package/src/tool/grep.txt +8 -0
  248. package/src/tool/invalid.ts +17 -0
  249. package/src/tool/ls.ts +110 -0
  250. package/src/tool/ls.txt +1 -0
  251. package/src/tool/lsp-diagnostics.ts +26 -0
  252. package/src/tool/lsp-diagnostics.txt +1 -0
  253. package/src/tool/lsp-hover.ts +31 -0
  254. package/src/tool/lsp-hover.txt +1 -0
  255. package/src/tool/multiedit.ts +46 -0
  256. package/src/tool/multiedit.txt +41 -0
  257. package/src/tool/patch.ts +233 -0
  258. package/src/tool/patch.txt +1 -0
  259. package/src/tool/read.ts +217 -0
  260. package/src/tool/read.txt +12 -0
  261. package/src/tool/registry.ts +150 -0
  262. package/src/tool/skill.ts +85 -0
  263. package/src/tool/task.ts +135 -0
  264. package/src/tool/task.txt +60 -0
  265. package/src/tool/todo.ts +39 -0
  266. package/src/tool/todoread.txt +14 -0
  267. package/src/tool/todowrite.txt +167 -0
  268. package/src/tool/tool.ts +66 -0
  269. package/src/tool/webfetch.ts +187 -0
  270. package/src/tool/webfetch.txt +14 -0
  271. package/src/tool/websearch.ts +150 -0
  272. package/src/tool/websearch.txt +11 -0
  273. package/src/tool/write.ts +99 -0
  274. package/src/tool/write.txt +8 -0
  275. package/src/types/shims.d.ts +3 -0
  276. package/src/util/color.ts +19 -0
  277. package/src/util/context.ts +25 -0
  278. package/src/util/defer.ts +12 -0
  279. package/src/util/eventloop.ts +20 -0
  280. package/src/util/filesystem.ts +69 -0
  281. package/src/util/fn.ts +11 -0
  282. package/src/util/iife.ts +3 -0
  283. package/src/util/keybind.ts +79 -0
  284. package/src/util/lazy.ts +11 -0
  285. package/src/util/locale.ts +81 -0
  286. package/src/util/lock.ts +98 -0
  287. package/src/util/log.ts +177 -0
  288. package/src/util/queue.ts +32 -0
  289. package/src/util/rpc.ts +42 -0
  290. package/src/util/scrap.ts +10 -0
  291. package/src/util/signal.ts +12 -0
  292. package/src/util/timeout.ts +14 -0
  293. package/src/util/token.ts +7 -0
  294. package/src/util/wildcard.ts +54 -0
  295. package/sst-env.d.ts +9 -0
  296. package/test/bun.test.ts +53 -0
  297. package/test/config/agent-color.test.ts +66 -0
  298. package/test/config/config.test.ts +503 -0
  299. package/test/config/markdown.test.ts +89 -0
  300. package/test/file/ignore.test.ts +10 -0
  301. package/test/fixture/fixture.ts +28 -0
  302. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  303. package/test/ide/ide.test.ts +82 -0
  304. package/test/keybind.test.ts +317 -0
  305. package/test/lsp/client.test.ts +95 -0
  306. package/test/patch/patch.test.ts +348 -0
  307. package/test/preload.ts +38 -0
  308. package/test/project/project.test.ts +42 -0
  309. package/test/provider/provider.test.ts +1809 -0
  310. package/test/provider/transform.test.ts +305 -0
  311. package/test/session/retry.test.ts +61 -0
  312. package/test/session/session.test.ts +71 -0
  313. package/test/snapshot/snapshot.test.ts +939 -0
  314. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  315. package/test/tool/bash.test.ts +55 -0
  316. package/test/tool/patch.test.ts +259 -0
  317. package/test/util/iife.test.ts +36 -0
  318. package/test/util/lazy.test.ts +50 -0
  319. package/test/util/timeout.test.ts +21 -0
  320. package/test/util/wildcard.test.ts +55 -0
  321. package/tsconfig.json +17 -0
  322. package/cerebras-cli-1.0.0.tgz +0 -0
@@ -0,0 +1,226 @@
1
+ import { Log } from "../util/log"
2
+ import path from "path"
3
+ import fs from "fs/promises"
4
+ import { Global } from "../global"
5
+ import { lazy } from "../util/lazy"
6
+ import { Lock } from "../util/lock"
7
+ import { $ } from "bun"
8
+ import { NamedError } from "@opencode-ai/util/error"
9
+ import z from "zod"
10
+
11
+ export namespace Storage {
12
+ const log = Log.create({ service: "storage" })
13
+
14
+ type Migration = (dir: string) => Promise<void>
15
+
16
+ export const NotFoundError = NamedError.create(
17
+ "NotFoundError",
18
+ z.object({
19
+ message: z.string(),
20
+ }),
21
+ )
22
+
23
+ const MIGRATIONS: Migration[] = [
24
+ async (dir) => {
25
+ const project = path.resolve(dir, "../project")
26
+ if (!fs.exists(project)) return
27
+ for await (const projectDir of new Bun.Glob("*").scan({
28
+ cwd: project,
29
+ onlyFiles: false,
30
+ })) {
31
+ log.info(`migrating project ${projectDir}`)
32
+ let projectID = projectDir
33
+ const fullProjectDir = path.join(project, projectDir)
34
+ let worktree = "/"
35
+
36
+ if (projectID !== "global") {
37
+ for await (const msgFile of new Bun.Glob("storage/session/message/*/*.json").scan({
38
+ cwd: path.join(project, projectDir),
39
+ absolute: true,
40
+ })) {
41
+ const json = await Bun.file(msgFile).json()
42
+ worktree = json.path?.root
43
+ if (worktree) break
44
+ }
45
+ if (!worktree) continue
46
+ if (!(await fs.exists(worktree))) continue
47
+ const [id] = await $`git rev-list --max-parents=0 --all`
48
+ .quiet()
49
+ .nothrow()
50
+ .cwd(worktree)
51
+ .text()
52
+ .then((x) =>
53
+ x
54
+ .split("\n")
55
+ .filter(Boolean)
56
+ .map((x) => x.trim())
57
+ .toSorted(),
58
+ )
59
+ if (!id) continue
60
+ projectID = id
61
+
62
+ await Bun.write(
63
+ path.join(dir, "project", projectID + ".json"),
64
+ JSON.stringify({
65
+ id,
66
+ vcs: "git",
67
+ worktree,
68
+ time: {
69
+ created: Date.now(),
70
+ initialized: Date.now(),
71
+ },
72
+ }),
73
+ )
74
+
75
+ log.info(`migrating sessions for project ${projectID}`)
76
+ for await (const sessionFile of new Bun.Glob("storage/session/info/*.json").scan({
77
+ cwd: fullProjectDir,
78
+ absolute: true,
79
+ })) {
80
+ const dest = path.join(dir, "session", projectID, path.basename(sessionFile))
81
+ log.info("copying", {
82
+ sessionFile,
83
+ dest,
84
+ })
85
+ const session = await Bun.file(sessionFile).json()
86
+ await Bun.write(dest, JSON.stringify(session))
87
+ log.info(`migrating messages for session ${session.id}`)
88
+ for await (const msgFile of new Bun.Glob(`storage/session/message/${session.id}/*.json`).scan({
89
+ cwd: fullProjectDir,
90
+ absolute: true,
91
+ })) {
92
+ const dest = path.join(dir, "message", session.id, path.basename(msgFile))
93
+ log.info("copying", {
94
+ msgFile,
95
+ dest,
96
+ })
97
+ const message = await Bun.file(msgFile).json()
98
+ await Bun.write(dest, JSON.stringify(message))
99
+
100
+ log.info(`migrating parts for message ${message.id}`)
101
+ for await (const partFile of new Bun.Glob(`storage/session/part/${session.id}/${message.id}/*.json`).scan(
102
+ {
103
+ cwd: fullProjectDir,
104
+ absolute: true,
105
+ },
106
+ )) {
107
+ const dest = path.join(dir, "part", message.id, path.basename(partFile))
108
+ const part = await Bun.file(partFile).json()
109
+ log.info("copying", {
110
+ partFile,
111
+ dest,
112
+ })
113
+ await Bun.write(dest, JSON.stringify(part))
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
119
+ },
120
+ async (dir) => {
121
+ for await (const item of new Bun.Glob("session/*/*.json").scan({
122
+ cwd: dir,
123
+ absolute: true,
124
+ })) {
125
+ const session = await Bun.file(item).json()
126
+ if (!session.projectID) continue
127
+ if (!session.summary?.diffs) continue
128
+ const { diffs } = session.summary
129
+ await Bun.file(path.join(dir, "session_diff", session.id + ".json")).write(JSON.stringify(diffs))
130
+ await Bun.file(path.join(dir, "session", session.projectID, session.id + ".json")).write(
131
+ JSON.stringify({
132
+ ...session,
133
+ summary: {
134
+ additions: diffs.reduce((sum: any, x: any) => sum + x.additions, 0),
135
+ deletions: diffs.reduce((sum: any, x: any) => sum + x.deletions, 0),
136
+ },
137
+ }),
138
+ )
139
+ }
140
+ },
141
+ ]
142
+
143
+ const state = lazy(async () => {
144
+ const dir = path.join(Global.Path.data, "storage")
145
+ const migration = await Bun.file(path.join(dir, "migration"))
146
+ .json()
147
+ .then((x) => parseInt(x))
148
+ .catch(() => 0)
149
+ for (let index = migration; index < MIGRATIONS.length; index++) {
150
+ log.info("running migration", { index })
151
+ const migration = MIGRATIONS[index]
152
+ await migration(dir).catch(() => log.error("failed to run migration", { index }))
153
+ await Bun.write(path.join(dir, "migration"), (index + 1).toString())
154
+ }
155
+ return {
156
+ dir,
157
+ }
158
+ })
159
+
160
+ export async function remove(key: string[]) {
161
+ const dir = await state().then((x) => x.dir)
162
+ const target = path.join(dir, ...key) + ".json"
163
+ return withErrorHandling(async () => {
164
+ await fs.unlink(target).catch(() => {})
165
+ })
166
+ }
167
+
168
+ export async function read<T>(key: string[]) {
169
+ const dir = await state().then((x) => x.dir)
170
+ const target = path.join(dir, ...key) + ".json"
171
+ return withErrorHandling(async () => {
172
+ using _ = await Lock.read(target)
173
+ const result = await Bun.file(target).json()
174
+ return result as T
175
+ })
176
+ }
177
+
178
+ export async function update<T>(key: string[], fn: (draft: T) => void) {
179
+ const dir = await state().then((x) => x.dir)
180
+ const target = path.join(dir, ...key) + ".json"
181
+ return withErrorHandling(async () => {
182
+ using _ = await Lock.write(target)
183
+ const content = await Bun.file(target).json()
184
+ fn(content)
185
+ await Bun.write(target, JSON.stringify(content, null, 2))
186
+ return content as T
187
+ })
188
+ }
189
+
190
+ export async function write<T>(key: string[], content: T) {
191
+ const dir = await state().then((x) => x.dir)
192
+ const target = path.join(dir, ...key) + ".json"
193
+ return withErrorHandling(async () => {
194
+ using _ = await Lock.write(target)
195
+ await Bun.write(target, JSON.stringify(content, null, 2))
196
+ })
197
+ }
198
+
199
+ async function withErrorHandling<T>(body: () => Promise<T>) {
200
+ return body().catch((e) => {
201
+ if (!(e instanceof Error)) throw e
202
+ const errnoException = e as NodeJS.ErrnoException
203
+ if (errnoException.code === "ENOENT") {
204
+ throw new NotFoundError({ message: `Resource not found: ${errnoException.path}` })
205
+ }
206
+ throw e
207
+ })
208
+ }
209
+
210
+ const glob = new Bun.Glob("**/*")
211
+ export async function list(prefix: string[]) {
212
+ const dir = await state().then((x) => x.dir)
213
+ try {
214
+ const result = await Array.fromAsync(
215
+ glob.scan({
216
+ cwd: path.join(dir, ...prefix),
217
+ onlyFiles: true,
218
+ }),
219
+ ).then((results) => results.map((x) => [...prefix, ...x.slice(0, -5).split(path.sep)]))
220
+ result.sort()
221
+ return result
222
+ } catch {
223
+ return []
224
+ }
225
+ }
226
+ }
@@ -0,0 +1,247 @@
1
+ import { Log } from "@/util/log"
2
+ import { Instance } from "@/project/instance"
3
+ import z from "zod"
4
+
5
+ /**
6
+ * Telemetry module for logging API request metrics to Cloudflare worker
7
+ *
8
+ * Tracks cache hit rates, token usage, conversation turns, and other metrics
9
+ * for monitoring and optimization purposes.
10
+ */
11
+ export namespace Telemetry {
12
+ const log = Log.create({ service: "telemetry" })
13
+
14
+ // Telemetry endpoint
15
+ const TELEMETRY_ENDPOINT =
16
+ process.env.OPENCODE_TELEMETRY_URL || "https://opencode-telemetry.kevin-taylor-d8d.workers.dev"
17
+
18
+ // Batch settings
19
+ const BATCH_SIZE = 10
20
+ const BATCH_INTERVAL_MS = 30_000 // 30 seconds
21
+
22
+ // Schema for telemetry entry
23
+ export const Entry = z.object({
24
+ // Identifiers
25
+ sessionID: z.string().optional(),
26
+ providerID: z.string().optional(),
27
+ modelID: z.string().optional(),
28
+ apiKeyHash: z.string().optional(), // Hash of API key for identification
29
+
30
+ // Per-step token metrics
31
+ inputTokens: z.number().default(0),
32
+ outputTokens: z.number().default(0),
33
+ reasoningTokens: z.number().default(0),
34
+ cachedTokens: z.number().default(0),
35
+
36
+ // Per-step computed metrics
37
+ cacheHitRate: z.number().default(0),
38
+
39
+ // Session-level cumulative totals (like sidebar displays)
40
+ sessionTotalCachedTokens: z.number().default(0),
41
+ sessionTotalPromptTokens: z.number().default(0),
42
+ sessionTotalOutputTokens: z.number().default(0),
43
+ sessionOverallHitRate: z.number().default(0),
44
+
45
+ // Session context
46
+ conversationTurns: z.number().default(0),
47
+
48
+ // Metadata
49
+ finishReason: z.string().optional(),
50
+ })
51
+ export type Entry = z.infer<typeof Entry>
52
+
53
+ /**
54
+ * Hash an API key for telemetry (privacy-preserving identifier)
55
+ * Uses last 8 chars + hash to create a unique but non-reversible ID
56
+ */
57
+ export async function hashApiKey(apiKey: string | undefined): Promise<string | undefined> {
58
+ if (!apiKey || apiKey.length < 8) return undefined
59
+ // Take last 4 chars (visible part) + hash of full key
60
+ const suffix = apiKey.slice(-4)
61
+ const hash = Bun.hash(apiKey).toString(16).slice(0, 8)
62
+ return `${hash}_${suffix}`
63
+ }
64
+
65
+ // Internal state for batching
66
+ const state = Instance.state(
67
+ () => {
68
+ const queue: Entry[] = []
69
+ let timer: ReturnType<typeof setTimeout> | undefined
70
+
71
+ return {
72
+ queue,
73
+ timer,
74
+ enabled: true,
75
+ }
76
+ },
77
+ async (entry) => {
78
+ // Flush remaining entries on shutdown
79
+ if (entry.timer) {
80
+ clearTimeout(entry.timer)
81
+ }
82
+ if (entry.queue.length > 0) {
83
+ await flush(entry.queue)
84
+ entry.queue.length = 0
85
+ }
86
+ },
87
+ )
88
+
89
+ /**
90
+ * Check if telemetry is enabled
91
+ */
92
+ export function isEnabled(): boolean {
93
+ return state().enabled
94
+ }
95
+
96
+ /**
97
+ * Enable or disable telemetry
98
+ */
99
+ export function setEnabled(enabled: boolean): void {
100
+ state().enabled = enabled
101
+ if (!enabled) {
102
+ // Clear any pending entries
103
+ state().queue.length = 0
104
+ if (state().timer) {
105
+ clearTimeout(state().timer)
106
+ state().timer = undefined
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Log a telemetry entry
113
+ *
114
+ * Entries are batched and sent periodically to reduce network overhead.
115
+ */
116
+ export function track(entry: Entry): void {
117
+ const s = state()
118
+ if (!s.enabled) return
119
+
120
+ log.info("tracking telemetry", {
121
+ providerID: entry.providerID,
122
+ modelID: entry.modelID,
123
+ cacheHitRate: entry.cacheHitRate,
124
+ cachedTokens: entry.cachedTokens,
125
+ inputTokens: entry.inputTokens,
126
+ })
127
+
128
+ s.queue.push(entry)
129
+
130
+ // Send immediately if batch is full
131
+ if (s.queue.length >= BATCH_SIZE) {
132
+ const entries = s.queue.splice(0, BATCH_SIZE)
133
+ flush(entries).catch((e) => log.error("flush error", { error: e }))
134
+ }
135
+
136
+ // Start batch timer if not already running
137
+ if (!s.timer) {
138
+ s.timer = setTimeout(() => {
139
+ s.timer = undefined
140
+ if (s.queue.length > 0) {
141
+ const entries = s.queue.splice(0)
142
+ flush(entries).catch((e) => log.error("flush error", { error: e }))
143
+ }
144
+ }, BATCH_INTERVAL_MS)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Convenience method to track from session processor data
150
+ */
151
+ export function trackFromUsage(input: {
152
+ sessionID: string
153
+ providerID: string
154
+ modelID: string
155
+ apiKeyHash?: string // Pre-hashed API key identifier
156
+ // Per-step tokens
157
+ tokens: {
158
+ input: number
159
+ output: number
160
+ reasoning: number
161
+ cache: {
162
+ read: number
163
+ }
164
+ }
165
+ conversationTurns: number
166
+ finishReason?: string
167
+ // Session-level cumulative totals
168
+ sessionTotals?: {
169
+ cachedTokens: number
170
+ promptTokens: number
171
+ outputTokens: number
172
+ }
173
+ }): void {
174
+ // Calculate per-step cache hit rate
175
+ const totalPromptTokens = input.tokens.input + input.tokens.cache.read
176
+ const cacheHitRate = totalPromptTokens > 0 ? (input.tokens.cache.read / totalPromptTokens) * 100 : 0
177
+
178
+ // Calculate session-level overall hit rate
179
+ const sessionOverallHitRate =
180
+ input.sessionTotals && input.sessionTotals.promptTokens > 0
181
+ ? (input.sessionTotals.cachedTokens / input.sessionTotals.promptTokens) * 100
182
+ : 0
183
+
184
+ track({
185
+ sessionID: input.sessionID,
186
+ providerID: input.providerID,
187
+ modelID: input.modelID,
188
+ apiKeyHash: input.apiKeyHash,
189
+ // Per-step metrics
190
+ inputTokens: input.tokens.input,
191
+ outputTokens: input.tokens.output,
192
+ reasoningTokens: input.tokens.reasoning,
193
+ cachedTokens: input.tokens.cache.read,
194
+ cacheHitRate,
195
+ // Session-level cumulative totals
196
+ sessionTotalCachedTokens: input.sessionTotals?.cachedTokens ?? 0,
197
+ sessionTotalPromptTokens: input.sessionTotals?.promptTokens ?? 0,
198
+ sessionTotalOutputTokens: input.sessionTotals?.outputTokens ?? 0,
199
+ sessionOverallHitRate,
200
+ // Context
201
+ conversationTurns: input.conversationTurns,
202
+ finishReason: input.finishReason,
203
+ })
204
+ }
205
+
206
+ /**
207
+ * Flush entries to the telemetry endpoint
208
+ */
209
+ async function flush(entries: Entry[]): Promise<void> {
210
+ if (entries.length === 0) return
211
+
212
+ log.info("flushing telemetry", { count: entries.length })
213
+
214
+ try {
215
+ const enrichedEntries = entries
216
+
217
+ const res = await fetch(`${TELEMETRY_ENDPOINT}/batch`, {
218
+ method: "POST",
219
+ headers: { "Content-Type": "application/json" },
220
+ body: JSON.stringify({ entries: enrichedEntries }),
221
+ })
222
+
223
+ if (!res.ok) {
224
+ log.error("telemetry flush failed", { status: res.status })
225
+ }
226
+ } catch (e) {
227
+ // Silently fail - telemetry should not disrupt normal operation
228
+ log.error("telemetry error", { error: e })
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Force flush any pending entries immediately
234
+ */
235
+ export async function forceFlush(): Promise<void> {
236
+ const s = state()
237
+ if (s.timer) {
238
+ clearTimeout(s.timer)
239
+ s.timer = undefined
240
+ }
241
+ if (s.queue.length > 0) {
242
+ const entries = s.queue.splice(0)
243
+ await flush(entries)
244
+ }
245
+ }
246
+ }
247
+