rird 1.0.200

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 (350) hide show
  1. package/AGENTS.md +27 -0
  2. package/Dockerfile +18 -0
  3. package/README.md +15 -0
  4. package/bin/opencode +336 -0
  5. package/bin/pty-wrapper.js +285 -0
  6. package/bunfig.toml +4 -0
  7. package/facebook_ads_library.png +0 -0
  8. package/nul`nif +0 -0
  9. package/package.json +111 -0
  10. package/parsers-config.ts +239 -0
  11. package/rird-1.0.199.tgz +0 -0
  12. package/script/build-windows.ts +54 -0
  13. package/script/build.ts +167 -0
  14. package/script/postinstall.mjs +544 -0
  15. package/script/publish-registries.ts +187 -0
  16. package/script/publish.ts +72 -0
  17. package/script/schema.ts +47 -0
  18. package/src/acp/README.md +164 -0
  19. package/src/acp/agent.ts +1063 -0
  20. package/src/acp/session.ts +101 -0
  21. package/src/acp/types.ts +22 -0
  22. package/src/agent/agent.ts +367 -0
  23. package/src/agent/generate.txt +75 -0
  24. package/src/agent/prompt/compaction.txt +12 -0
  25. package/src/agent/prompt/explore.txt +18 -0
  26. package/src/agent/prompt/summary.txt +10 -0
  27. package/src/agent/prompt/title.txt +36 -0
  28. package/src/auth/index.ts +70 -0
  29. package/src/bun/index.ts +114 -0
  30. package/src/bus/bus-event.ts +43 -0
  31. package/src/bus/global.ts +10 -0
  32. package/src/bus/index.ts +105 -0
  33. package/src/cli/bootstrap.ts +17 -0
  34. package/src/cli/cmd/acp.ts +88 -0
  35. package/src/cli/cmd/agent.ts +256 -0
  36. package/src/cli/cmd/auth.ts +391 -0
  37. package/src/cli/cmd/cmd.ts +7 -0
  38. package/src/cli/cmd/debug/config.ts +15 -0
  39. package/src/cli/cmd/debug/file.ts +91 -0
  40. package/src/cli/cmd/debug/index.ts +43 -0
  41. package/src/cli/cmd/debug/lsp.ts +48 -0
  42. package/src/cli/cmd/debug/ripgrep.ts +83 -0
  43. package/src/cli/cmd/debug/scrap.ts +15 -0
  44. package/src/cli/cmd/debug/skill.ts +15 -0
  45. package/src/cli/cmd/debug/snapshot.ts +48 -0
  46. package/src/cli/cmd/export.ts +88 -0
  47. package/src/cli/cmd/generate.ts +38 -0
  48. package/src/cli/cmd/github.ts +1400 -0
  49. package/src/cli/cmd/import.ts +98 -0
  50. package/src/cli/cmd/mcp.ts +654 -0
  51. package/src/cli/cmd/models.ts +77 -0
  52. package/src/cli/cmd/pr.ts +112 -0
  53. package/src/cli/cmd/run.ts +368 -0
  54. package/src/cli/cmd/serve.ts +31 -0
  55. package/src/cli/cmd/session.ts +106 -0
  56. package/src/cli/cmd/stats.ts +298 -0
  57. package/src/cli/cmd/tui/app.tsx +696 -0
  58. package/src/cli/cmd/tui/attach.ts +30 -0
  59. package/src/cli/cmd/tui/component/border.tsx +21 -0
  60. package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
  61. package/src/cli/cmd/tui/component/dialog-command.tsx +124 -0
  62. package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
  63. package/src/cli/cmd/tui/component/dialog-model.tsx +245 -0
  64. package/src/cli/cmd/tui/component/dialog-provider.tsx +224 -0
  65. package/src/cli/cmd/tui/component/dialog-session-list.tsx +102 -0
  66. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
  67. package/src/cli/cmd/tui/component/dialog-stash.tsx +86 -0
  68. package/src/cli/cmd/tui/component/dialog-status.tsx +162 -0
  69. package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
  70. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
  71. package/src/cli/cmd/tui/component/did-you-know.tsx +85 -0
  72. package/src/cli/cmd/tui/component/logo.tsx +35 -0
  73. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +574 -0
  74. package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
  75. package/src/cli/cmd/tui/component/prompt/index.tsx +1090 -0
  76. package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
  77. package/src/cli/cmd/tui/component/tips.ts +27 -0
  78. package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
  79. package/src/cli/cmd/tui/context/args.tsx +14 -0
  80. package/src/cli/cmd/tui/context/directory.ts +13 -0
  81. package/src/cli/cmd/tui/context/exit.tsx +23 -0
  82. package/src/cli/cmd/tui/context/helper.tsx +25 -0
  83. package/src/cli/cmd/tui/context/keybind.tsx +101 -0
  84. package/src/cli/cmd/tui/context/kv.tsx +49 -0
  85. package/src/cli/cmd/tui/context/local.tsx +354 -0
  86. package/src/cli/cmd/tui/context/prompt.tsx +18 -0
  87. package/src/cli/cmd/tui/context/route.tsx +46 -0
  88. package/src/cli/cmd/tui/context/sdk.tsx +74 -0
  89. package/src/cli/cmd/tui/context/sync.tsx +372 -0
  90. package/src/cli/cmd/tui/context/theme/aura.json +69 -0
  91. package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
  92. package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
  93. package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
  94. package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
  95. package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
  96. package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
  97. package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
  98. package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
  99. package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
  100. package/src/cli/cmd/tui/context/theme/github.json +233 -0
  101. package/src/cli/cmd/tui/context/theme/gruvbox.json +95 -0
  102. package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
  103. package/src/cli/cmd/tui/context/theme/lucent-orng.json +227 -0
  104. package/src/cli/cmd/tui/context/theme/material.json +235 -0
  105. package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
  106. package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
  107. package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
  108. package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
  109. package/src/cli/cmd/tui/context/theme/nord.json +223 -0
  110. package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
  111. package/src/cli/cmd/tui/context/theme/orng.json +245 -0
  112. package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
  113. package/src/cli/cmd/tui/context/theme/rird.json +245 -0
  114. package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
  115. package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
  116. package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
  117. package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
  118. package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
  119. package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
  120. package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
  121. package/src/cli/cmd/tui/context/theme.tsx +1109 -0
  122. package/src/cli/cmd/tui/event.ts +40 -0
  123. package/src/cli/cmd/tui/routes/home.tsx +138 -0
  124. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
  125. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
  126. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
  127. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
  128. package/src/cli/cmd/tui/routes/session/footer.tsx +88 -0
  129. package/src/cli/cmd/tui/routes/session/header.tsx +125 -0
  130. package/src/cli/cmd/tui/routes/session/index.tsx +1864 -0
  131. package/src/cli/cmd/tui/routes/session/sidebar.tsx +318 -0
  132. package/src/cli/cmd/tui/spawn.ts +60 -0
  133. package/src/cli/cmd/tui/thread.ts +142 -0
  134. package/src/cli/cmd/tui/ui/dialog-alert.tsx +57 -0
  135. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
  136. package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
  137. package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
  138. package/src/cli/cmd/tui/ui/dialog-select.tsx +332 -0
  139. package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
  140. package/src/cli/cmd/tui/ui/spinner.ts +368 -0
  141. package/src/cli/cmd/tui/ui/toast.tsx +100 -0
  142. package/src/cli/cmd/tui/util/clipboard.ts +127 -0
  143. package/src/cli/cmd/tui/util/editor.ts +32 -0
  144. package/src/cli/cmd/tui/util/terminal.ts +114 -0
  145. package/src/cli/cmd/tui/worker.ts +63 -0
  146. package/src/cli/cmd/uninstall.ts +344 -0
  147. package/src/cli/cmd/upgrade.ts +100 -0
  148. package/src/cli/cmd/web.ts +84 -0
  149. package/src/cli/error.ts +56 -0
  150. package/src/cli/ui.ts +84 -0
  151. package/src/cli/upgrade.ts +25 -0
  152. package/src/command/index.ts +80 -0
  153. package/src/command/template/initialize.txt +10 -0
  154. package/src/command/template/review.txt +97 -0
  155. package/src/config/config.ts +995 -0
  156. package/src/config/markdown.ts +41 -0
  157. package/src/env/index.ts +26 -0
  158. package/src/file/ignore.ts +83 -0
  159. package/src/file/index.ts +328 -0
  160. package/src/file/ripgrep.ts +393 -0
  161. package/src/file/time.ts +64 -0
  162. package/src/file/watcher.ts +103 -0
  163. package/src/flag/flag.ts +46 -0
  164. package/src/format/formatter.ts +315 -0
  165. package/src/format/index.ts +137 -0
  166. package/src/global/index.ts +52 -0
  167. package/src/id/id.ts +73 -0
  168. package/src/ide/index.ts +76 -0
  169. package/src/index.ts +240 -0
  170. package/src/installation/index.ts +239 -0
  171. package/src/lsp/client.ts +229 -0
  172. package/src/lsp/index.ts +485 -0
  173. package/src/lsp/language.ts +116 -0
  174. package/src/lsp/server.ts +1895 -0
  175. package/src/mcp/auth.ts +135 -0
  176. package/src/mcp/index.ts +690 -0
  177. package/src/mcp/oauth-callback.ts +200 -0
  178. package/src/mcp/oauth-provider.ts +154 -0
  179. package/src/patch/index.ts +622 -0
  180. package/src/permission/index.ts +199 -0
  181. package/src/plugin/index.ts +91 -0
  182. package/src/project/bootstrap.ts +31 -0
  183. package/src/project/instance.ts +78 -0
  184. package/src/project/project.ts +221 -0
  185. package/src/project/state.ts +65 -0
  186. package/src/project/vcs.ts +76 -0
  187. package/src/provider/auth.ts +143 -0
  188. package/src/provider/models-macro.ts +11 -0
  189. package/src/provider/models.ts +106 -0
  190. package/src/provider/provider.ts +1071 -0
  191. package/src/provider/sdk/openai-compatible/src/README.md +5 -0
  192. package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
  193. package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
  194. package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
  195. package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
  196. package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
  197. package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
  198. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
  199. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1713 -0
  200. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
  201. package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
  202. package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
  203. package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
  204. package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
  205. package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
  206. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
  207. package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
  208. package/src/provider/transform.ts +455 -0
  209. package/src/pty/index.ts +231 -0
  210. package/src/security/guardrails.test.ts +341 -0
  211. package/src/security/guardrails.ts +558 -0
  212. package/src/security/index.ts +19 -0
  213. package/src/server/error.ts +36 -0
  214. package/src/server/project.ts +79 -0
  215. package/src/server/server.ts +2642 -0
  216. package/src/server/tui.ts +71 -0
  217. package/src/session/compaction.ts +223 -0
  218. package/src/session/index.ts +461 -0
  219. package/src/session/llm.ts +201 -0
  220. package/src/session/message-v2.ts +690 -0
  221. package/src/session/message.ts +189 -0
  222. package/src/session/processor.ts +409 -0
  223. package/src/session/prompt/act-switch.txt +5 -0
  224. package/src/session/prompt/anthropic-20250930.txt +166 -0
  225. package/src/session/prompt/anthropic.txt +85 -0
  226. package/src/session/prompt/anthropic_spoof.txt +1 -0
  227. package/src/session/prompt/beast.txt +103 -0
  228. package/src/session/prompt/codex.txt +304 -0
  229. package/src/session/prompt/copilot-gpt-5.txt +138 -0
  230. package/src/session/prompt/gemini.txt +85 -0
  231. package/src/session/prompt/max-steps.txt +16 -0
  232. package/src/session/prompt/plan-reminder-anthropic.txt +35 -0
  233. package/src/session/prompt/plan.txt +24 -0
  234. package/src/session/prompt/polaris.txt +84 -0
  235. package/src/session/prompt/qwen.txt +106 -0
  236. package/src/session/prompt.ts +1509 -0
  237. package/src/session/retry.ts +86 -0
  238. package/src/session/revert.ts +108 -0
  239. package/src/session/sensitive-filter.test.ts +327 -0
  240. package/src/session/sensitive-filter.ts +466 -0
  241. package/src/session/status.ts +76 -0
  242. package/src/session/summary.ts +194 -0
  243. package/src/session/system.ts +120 -0
  244. package/src/session/todo.ts +37 -0
  245. package/src/share/share-next.ts +194 -0
  246. package/src/share/share.ts +87 -0
  247. package/src/shell/shell.ts +67 -0
  248. package/src/skill/index.ts +1 -0
  249. package/src/skill/skill.ts +83 -0
  250. package/src/snapshot/index.ts +197 -0
  251. package/src/storage/storage.ts +226 -0
  252. package/src/tests/agent.test.ts +308 -0
  253. package/src/tests/build-guards.test.ts +267 -0
  254. package/src/tests/config.test.ts +664 -0
  255. package/src/tests/tool-registry.test.ts +589 -0
  256. package/src/tool/bash.ts +317 -0
  257. package/src/tool/bash.txt +158 -0
  258. package/src/tool/batch.ts +175 -0
  259. package/src/tool/batch.txt +24 -0
  260. package/src/tool/codesearch.ts +168 -0
  261. package/src/tool/codesearch.txt +12 -0
  262. package/src/tool/edit.ts +675 -0
  263. package/src/tool/edit.txt +10 -0
  264. package/src/tool/glob.ts +65 -0
  265. package/src/tool/glob.txt +6 -0
  266. package/src/tool/grep.ts +121 -0
  267. package/src/tool/grep.txt +8 -0
  268. package/src/tool/invalid.ts +17 -0
  269. package/src/tool/ls.ts +110 -0
  270. package/src/tool/ls.txt +1 -0
  271. package/src/tool/lsp-diagnostics.ts +26 -0
  272. package/src/tool/lsp-diagnostics.txt +1 -0
  273. package/src/tool/lsp-hover.ts +31 -0
  274. package/src/tool/lsp-hover.txt +1 -0
  275. package/src/tool/lsp.ts +87 -0
  276. package/src/tool/lsp.txt +19 -0
  277. package/src/tool/multiedit.ts +46 -0
  278. package/src/tool/multiedit.txt +41 -0
  279. package/src/tool/patch.ts +233 -0
  280. package/src/tool/patch.txt +1 -0
  281. package/src/tool/read.ts +219 -0
  282. package/src/tool/read.txt +12 -0
  283. package/src/tool/registry.ts +162 -0
  284. package/src/tool/skill.ts +100 -0
  285. package/src/tool/task.ts +136 -0
  286. package/src/tool/task.txt +51 -0
  287. package/src/tool/todo.ts +39 -0
  288. package/src/tool/todoread.txt +14 -0
  289. package/src/tool/todowrite.txt +167 -0
  290. package/src/tool/tool.ts +71 -0
  291. package/src/tool/webfetch.ts +198 -0
  292. package/src/tool/webfetch.txt +13 -0
  293. package/src/tool/websearch.ts +180 -0
  294. package/src/tool/websearch.txt +11 -0
  295. package/src/tool/write.ts +110 -0
  296. package/src/tool/write.txt +8 -0
  297. package/src/util/archive.ts +16 -0
  298. package/src/util/color.ts +19 -0
  299. package/src/util/context.ts +25 -0
  300. package/src/util/defer.ts +12 -0
  301. package/src/util/eventloop.ts +20 -0
  302. package/src/util/filesystem.ts +83 -0
  303. package/src/util/fn.ts +11 -0
  304. package/src/util/iife.ts +3 -0
  305. package/src/util/keybind.ts +102 -0
  306. package/src/util/lazy.ts +11 -0
  307. package/src/util/license.ts +325 -0
  308. package/src/util/locale.ts +81 -0
  309. package/src/util/lock.ts +98 -0
  310. package/src/util/log.ts +180 -0
  311. package/src/util/queue.ts +32 -0
  312. package/src/util/rpc.ts +42 -0
  313. package/src/util/scrap.ts +10 -0
  314. package/src/util/signal.ts +12 -0
  315. package/src/util/timeout.ts +14 -0
  316. package/src/util/token.ts +7 -0
  317. package/src/util/wildcard.ts +54 -0
  318. package/sst-env.d.ts +9 -0
  319. package/test/agent/agent.test.ts +146 -0
  320. package/test/bun.test.ts +53 -0
  321. package/test/cli/github-remote.test.ts +80 -0
  322. package/test/config/agent-color.test.ts +66 -0
  323. package/test/config/config.test.ts +535 -0
  324. package/test/config/markdown.test.ts +89 -0
  325. package/test/file/ignore.test.ts +10 -0
  326. package/test/fixture/fixture.ts +36 -0
  327. package/test/fixture/lsp/fake-lsp-server.js +77 -0
  328. package/test/ide/ide.test.ts +82 -0
  329. package/test/keybind.test.ts +421 -0
  330. package/test/lsp/client.test.ts +95 -0
  331. package/test/mcp/headers.test.ts +153 -0
  332. package/test/patch/patch.test.ts +348 -0
  333. package/test/preload.ts +57 -0
  334. package/test/project/project.test.ts +72 -0
  335. package/test/provider/provider.test.ts +1809 -0
  336. package/test/provider/transform.test.ts +411 -0
  337. package/test/session/retry.test.ts +111 -0
  338. package/test/session/session.test.ts +71 -0
  339. package/test/skill/skill.test.ts +131 -0
  340. package/test/snapshot/snapshot.test.ts +939 -0
  341. package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
  342. package/test/tool/bash.test.ts +434 -0
  343. package/test/tool/grep.test.ts +108 -0
  344. package/test/tool/patch.test.ts +259 -0
  345. package/test/tool/read.test.ts +42 -0
  346. package/test/util/iife.test.ts +36 -0
  347. package/test/util/lazy.test.ts +50 -0
  348. package/test/util/timeout.test.ts +21 -0
  349. package/test/util/wildcard.test.ts +55 -0
  350. package/tsconfig.json +16 -0
@@ -0,0 +1,86 @@
1
+ import type { NamedError } from "@opencode-ai/util/error"
2
+ import { MessageV2 } from "./message-v2"
3
+
4
+ export namespace SessionRetry {
5
+ export const RETRY_INITIAL_DELAY = 2000
6
+ export const RETRY_BACKOFF_FACTOR = 2
7
+ export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
8
+
9
+ export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
10
+ return new Promise((resolve, reject) => {
11
+ const timeout = setTimeout(resolve, ms)
12
+ signal.addEventListener(
13
+ "abort",
14
+ () => {
15
+ clearTimeout(timeout)
16
+ reject(new DOMException("Aborted", "AbortError"))
17
+ },
18
+ { once: true },
19
+ )
20
+ })
21
+ }
22
+
23
+ export function delay(attempt: number, error?: MessageV2.APIError) {
24
+ if (error) {
25
+ const headers = error.data.responseHeaders
26
+ if (headers) {
27
+ const retryAfterMs = headers["retry-after-ms"]
28
+ if (retryAfterMs) {
29
+ const parsedMs = Number.parseFloat(retryAfterMs)
30
+ if (!Number.isNaN(parsedMs)) {
31
+ return parsedMs
32
+ }
33
+ }
34
+
35
+ const retryAfter = headers["retry-after"]
36
+ if (retryAfter) {
37
+ const parsedSeconds = Number.parseFloat(retryAfter)
38
+ if (!Number.isNaN(parsedSeconds)) {
39
+ // convert seconds to milliseconds
40
+ return Math.ceil(parsedSeconds * 1000)
41
+ }
42
+ // Try parsing as HTTP date format
43
+ const parsed = Date.parse(retryAfter) - Date.now()
44
+ if (!Number.isNaN(parsed) && parsed > 0) {
45
+ return Math.ceil(parsed)
46
+ }
47
+ }
48
+
49
+ return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
50
+ }
51
+ }
52
+
53
+ return Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)
54
+ }
55
+
56
+ export function retryable(error: ReturnType<NamedError["toObject"]>) {
57
+ if (MessageV2.APIError.isInstance(error)) {
58
+ if (!error.data.isRetryable) return undefined
59
+ return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
60
+ }
61
+
62
+ if (typeof error.data?.message === "string") {
63
+ try {
64
+ const json = JSON.parse(error.data.message)
65
+ if (json.type === "error" && json.error?.type === "too_many_requests") {
66
+ return "Too Many Requests"
67
+ }
68
+ if (json.code.includes("exhausted") || json.code.includes("unavailable")) {
69
+ return "Provider is overloaded"
70
+ }
71
+ if (json.type === "error" && json.error?.code?.includes("rate_limit")) {
72
+ return "Rate Limited"
73
+ }
74
+ if (
75
+ json.error?.message?.includes("no_kv_space") ||
76
+ (json.type === "error" && json.error?.type === "server_error") ||
77
+ !!json.error
78
+ ) {
79
+ return "Provider Server Error"
80
+ }
81
+ } catch {}
82
+ }
83
+
84
+ return undefined
85
+ }
86
+ }
@@ -0,0 +1,108 @@
1
+ import z from "zod"
2
+ import { Identifier } from "../id/id"
3
+ import { Snapshot } from "../snapshot"
4
+ import { MessageV2 } from "./message-v2"
5
+ import { Session } from "."
6
+ import { Log } from "../util/log"
7
+ import { splitWhen } from "remeda"
8
+ import { Storage } from "../storage/storage"
9
+ import { Bus } from "../bus"
10
+ import { SessionPrompt } from "./prompt"
11
+
12
+ export namespace SessionRevert {
13
+ const log = Log.create({ service: "session.revert" })
14
+
15
+ export const RevertInput = z.object({
16
+ sessionID: Identifier.schema("session"),
17
+ messageID: Identifier.schema("message"),
18
+ partID: Identifier.schema("part").optional(),
19
+ })
20
+ export type RevertInput = z.infer<typeof RevertInput>
21
+
22
+ export async function revert(input: RevertInput) {
23
+ SessionPrompt.assertNotBusy(input.sessionID)
24
+ const all = await Session.messages({ sessionID: input.sessionID })
25
+ let lastUser: MessageV2.User | undefined
26
+ const session = await Session.get(input.sessionID)
27
+
28
+ let revert: Session.Info["revert"]
29
+ const patches: Snapshot.Patch[] = []
30
+ for (const msg of all) {
31
+ if (msg.info.role === "user") lastUser = msg.info
32
+ const remaining = []
33
+ for (const part of msg.parts) {
34
+ if (revert) {
35
+ if (part.type === "patch") {
36
+ patches.push(part)
37
+ }
38
+ continue
39
+ }
40
+
41
+ if (!revert) {
42
+ if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
43
+ // if no useful parts left in message, same as reverting whole message
44
+ const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
45
+ revert = {
46
+ messageID: !partID && lastUser ? lastUser.id : msg.info.id,
47
+ partID,
48
+ }
49
+ }
50
+ remaining.push(part)
51
+ }
52
+ }
53
+ }
54
+
55
+ if (revert) {
56
+ const session = await Session.get(input.sessionID)
57
+ revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track())
58
+ await Snapshot.revert(patches)
59
+ if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot)
60
+ return Session.update(input.sessionID, (draft) => {
61
+ draft.revert = revert
62
+ })
63
+ }
64
+ return session
65
+ }
66
+
67
+ export async function unrevert(input: { sessionID: string }) {
68
+ log.info("unreverting", input)
69
+ SessionPrompt.assertNotBusy(input.sessionID)
70
+ const session = await Session.get(input.sessionID)
71
+ if (!session.revert) return session
72
+ if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
73
+ const next = await Session.update(input.sessionID, (draft) => {
74
+ draft.revert = undefined
75
+ })
76
+ return next
77
+ }
78
+
79
+ export async function cleanup(session: Session.Info) {
80
+ if (!session.revert) return
81
+ const sessionID = session.id
82
+ let msgs = await Session.messages({ sessionID })
83
+ const messageID = session.revert.messageID
84
+ const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
85
+ msgs = preserve
86
+ for (const msg of remove) {
87
+ await Storage.remove(["message", sessionID, msg.info.id])
88
+ await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
89
+ }
90
+ const last = preserve.at(-1)
91
+ if (session.revert.partID && last) {
92
+ const partID = session.revert.partID
93
+ const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
94
+ last.parts = preserveParts
95
+ for (const part of removeParts) {
96
+ await Storage.remove(["part", last.info.id, part.id])
97
+ await Bus.publish(MessageV2.Event.PartRemoved, {
98
+ sessionID: sessionID,
99
+ messageID: last.info.id,
100
+ partID: part.id,
101
+ })
102
+ }
103
+ }
104
+ await Session.update(sessionID, (draft) => {
105
+ draft.revert = undefined
106
+ })
107
+ }
108
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Tests for sensitive data filter
3
+ * Run with: bun test sensitive-filter.test.ts
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach } from "bun:test"
7
+ import { SensitiveFilter } from "./sensitive-filter"
8
+
9
+ describe("SensitiveFilter", () => {
10
+ const testSessionID = "test-session-123"
11
+
12
+ beforeEach(() => {
13
+ // Clear any previous session data
14
+ SensitiveFilter.clearSession(testSessionID)
15
+ })
16
+
17
+ describe("Credit Card Detection", () => {
18
+ it("should detect and redact valid Visa card numbers", () => {
19
+ const text = "My card is 4532015112830366"
20
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
21
+
22
+ expect(result.text).not.toContain("4532015112830366")
23
+ expect(result.text).toContain("[REDACTED-CREDIT_CARD]")
24
+ expect(result.redactions).toHaveLength(1)
25
+ expect(result.redactions[0].type).toBe("CREDIT_CARD")
26
+ })
27
+
28
+ it("should detect card numbers with dashes", () => {
29
+ const text = "Card: 4532-0151-1283-0366"
30
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
31
+
32
+ expect(result.text).not.toContain("4532-0151-1283-0366")
33
+ expect(result.text).toContain("[REDACTED-CREDIT_CARD]")
34
+ })
35
+
36
+ it("should detect card numbers with spaces", () => {
37
+ const text = "Card: 4532 0151 1283 0366"
38
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
39
+
40
+ expect(result.text).not.toContain("4532 0151 1283 0366")
41
+ expect(result.text).toContain("[REDACTED-CREDIT_CARD]")
42
+ })
43
+
44
+ it("should not redact numbers that fail Luhn check", () => {
45
+ const text = "Random number: 1234567890123456"
46
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
47
+
48
+ // This should not be redacted as it fails Luhn
49
+ expect(result.redactions).toHaveLength(0)
50
+ })
51
+
52
+ it("should detect American Express (15 digits)", () => {
53
+ // Valid Amex test number
54
+ const text = "Amex: 378282246310005"
55
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
56
+
57
+ expect(result.text).not.toContain("378282246310005")
58
+ expect(result.text).toContain("[REDACTED-CREDIT_CARD]")
59
+ })
60
+ })
61
+
62
+ describe("SSN Detection", () => {
63
+ it("should detect and redact SSN format", () => {
64
+ const text = "SSN: 123-45-6789"
65
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
66
+
67
+ expect(result.text).not.toContain("123-45-6789")
68
+ expect(result.text).toContain("[REDACTED-SSN]")
69
+ expect(result.redactions).toHaveLength(1)
70
+ expect(result.redactions[0].type).toBe("SSN")
71
+ })
72
+
73
+ it("should handle multiple SSNs", () => {
74
+ const text = "SSN1: 123-45-6789, SSN2: 987-65-4321"
75
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
76
+
77
+ expect(result.text).not.toContain("123-45-6789")
78
+ expect(result.text).not.toContain("987-65-4321")
79
+ expect(result.redactions).toHaveLength(2)
80
+ })
81
+ })
82
+
83
+ describe("Password Detection", () => {
84
+ it("should detect password: value patterns", () => {
85
+ const text = "password: mySecretPass123"
86
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
87
+
88
+ expect(result.text).not.toContain("mySecretPass123")
89
+ expect(result.text).toContain("[REDACTED-PASSWORD]")
90
+ expect(result.text).toContain("password:")
91
+ })
92
+
93
+ it("should detect pwd=value patterns", () => {
94
+ const text = "config: pwd=hunter2"
95
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
96
+
97
+ expect(result.text).not.toContain("hunter2")
98
+ expect(result.text).toContain("[REDACTED-PASSWORD]")
99
+ })
100
+
101
+ it("should detect quoted passwords", () => {
102
+ const text = 'password="super_secret_123"'
103
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
104
+
105
+ expect(result.text).not.toContain("super_secret_123")
106
+ expect(result.text).toContain("[REDACTED-PASSWORD]")
107
+ })
108
+
109
+ it("should detect secret: patterns", () => {
110
+ const text = "secret: my_api_secret_value"
111
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
112
+
113
+ expect(result.text).not.toContain("my_api_secret_value")
114
+ expect(result.redactions).toHaveLength(1)
115
+ expect(result.redactions[0].type).toBe("PASSWORD")
116
+ })
117
+ })
118
+
119
+ describe("API Key Detection", () => {
120
+ it("should detect OpenAI style keys (sk-*)", () => {
121
+ const text = "API key: sk-proj-abc123def456ghi789jkl012mno345pqr678stu"
122
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
123
+
124
+ expect(result.text).not.toContain("sk-proj")
125
+ expect(result.text).toContain("[REDACTED-API_KEY]")
126
+ })
127
+
128
+ it("should detect Slack tokens (xoxb-*)", () => {
129
+ const text = "Slack: xoxb-1234567890-1234567890123-abcdefghijklmnopqrstuvwx"
130
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
131
+
132
+ expect(result.text).not.toContain("xoxb-")
133
+ expect(result.text).toContain("[REDACTED-API_KEY]")
134
+ })
135
+
136
+ it("should detect GitHub tokens (ghp_*)", () => {
137
+ const text = "GitHub PAT: ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
138
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
139
+
140
+ expect(result.text).not.toContain("ghp_")
141
+ expect(result.text).toContain("[REDACTED-API_KEY]")
142
+ })
143
+
144
+ it("should detect AWS access keys (AKIA*)", () => {
145
+ const text = "AWS key: AKIAIOSFODNN7EXAMPLE"
146
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
147
+
148
+ expect(result.text).not.toContain("AKIAIOSFODNN7EXAMPLE")
149
+ expect(result.text).toContain("[REDACTED-API_KEY]")
150
+ })
151
+
152
+ it("should detect api_key= patterns", () => {
153
+ const text = "config: api_key=abcdef1234567890abcdef"
154
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
155
+
156
+ expect(result.text).not.toContain("abcdef1234567890abcdef")
157
+ expect(result.text).toContain("[REDACTED-API_KEY]")
158
+ })
159
+ })
160
+
161
+ describe("Bearer Token Detection", () => {
162
+ it("should detect Bearer tokens", () => {
163
+ const text = "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
164
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
165
+
166
+ expect(result.text).not.toContain("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9")
167
+ expect(result.text).toContain("Bearer [REDACTED-BEARER_TOKEN]")
168
+ })
169
+ })
170
+
171
+ describe("Email Detection (Optional)", () => {
172
+ it("should not redact emails by default", () => {
173
+ const text = "Contact: user@example.com"
174
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
175
+
176
+ expect(result.text).toContain("user@example.com")
177
+ expect(result.redactions).toHaveLength(0)
178
+ })
179
+
180
+ it("should redact emails when enabled", () => {
181
+ const text = "Contact: user@example.com"
182
+ const result = SensitiveFilter.filter(text, {
183
+ sessionID: testSessionID,
184
+ redactEmails: true,
185
+ })
186
+
187
+ expect(result.text).not.toContain("user@example.com")
188
+ expect(result.text).toContain("[REDACTED-EMAIL]")
189
+ })
190
+ })
191
+
192
+ describe("Restore Functionality", () => {
193
+ it("should restore redacted values", () => {
194
+ const originalText = "password: secretValue123"
195
+ const filtered = SensitiveFilter.filter(originalText, { sessionID: testSessionID })
196
+
197
+ expect(filtered.text).not.toContain("secretValue123")
198
+
199
+ const restored = SensitiveFilter.restore(filtered.text, testSessionID)
200
+ expect(restored).toContain("secretValue123")
201
+ })
202
+
203
+ it("should restore multiple redacted values", () => {
204
+ const originalText = "card: 4532015112830366, ssn: 123-45-6789"
205
+ const filtered = SensitiveFilter.filter(originalText, { sessionID: testSessionID })
206
+
207
+ expect(filtered.text).not.toContain("4532015112830366")
208
+ expect(filtered.text).not.toContain("123-45-6789")
209
+
210
+ const restored = SensitiveFilter.restore(filtered.text, testSessionID)
211
+ expect(restored).toContain("4532015112830366")
212
+ expect(restored).toContain("123-45-6789")
213
+ })
214
+ })
215
+
216
+ describe("containsSensitive Check", () => {
217
+ it("should detect sensitive patterns without redacting", () => {
218
+ const text = "My card is 4532015112830366 and SSN is 123-45-6789"
219
+ const check = SensitiveFilter.containsSensitive(text)
220
+
221
+ expect(check.hasSensitive).toBe(true)
222
+ expect(check.types).toContain("CREDIT_CARD")
223
+ expect(check.types).toContain("SSN")
224
+ })
225
+
226
+ it("should return false for clean text", () => {
227
+ const text = "This is just a regular message with no sensitive data"
228
+ const check = SensitiveFilter.containsSensitive(text)
229
+
230
+ expect(check.hasSensitive).toBe(false)
231
+ expect(check.types).toHaveLength(0)
232
+ })
233
+ })
234
+
235
+ describe("Session Management", () => {
236
+ it("should track redactions per session", () => {
237
+ const session1 = "session-1"
238
+ const session2 = "session-2"
239
+
240
+ SensitiveFilter.filter("password: secret1", { sessionID: session1 })
241
+ SensitiveFilter.filter("password: secret2", { sessionID: session2 })
242
+
243
+ const redactions1 = SensitiveFilter.getRedactions(session1)
244
+ const redactions2 = SensitiveFilter.getRedactions(session2)
245
+
246
+ expect(redactions1).toHaveLength(1)
247
+ expect(redactions1[0].original).toBe("secret1")
248
+
249
+ expect(redactions2).toHaveLength(1)
250
+ expect(redactions2[0].original).toBe("secret2")
251
+
252
+ // Clean up
253
+ SensitiveFilter.clearSession(session1)
254
+ SensitiveFilter.clearSession(session2)
255
+ })
256
+
257
+ it("should clear session data", () => {
258
+ SensitiveFilter.filter("password: secret", { sessionID: testSessionID })
259
+
260
+ expect(SensitiveFilter.getRedactions(testSessionID)).toHaveLength(1)
261
+
262
+ SensitiveFilter.clearSession(testSessionID)
263
+
264
+ expect(SensitiveFilter.getRedactions(testSessionID)).toHaveLength(0)
265
+ })
266
+
267
+ it("should use unique placeholders for multiple redactions", () => {
268
+ const text = "password: secret1, pwd: secret2, pass: secret3"
269
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
270
+
271
+ // Should have 3 different placeholder IDs
272
+ const placeholders = result.text.match(/\[REDACTED-PASSWORD[^\]]*\]/g)
273
+ expect(placeholders).not.toBeNull()
274
+ expect(new Set(placeholders).size).toBe(3)
275
+ })
276
+ })
277
+
278
+ describe("Edge Cases", () => {
279
+ it("should not double-redact already redacted values", () => {
280
+ const text = "password: [REDACTED-PASSWORD]"
281
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
282
+
283
+ expect(result.text).toBe("password: [REDACTED-PASSWORD]")
284
+ expect(result.redactions).toHaveLength(0)
285
+ })
286
+
287
+ it("should handle empty strings", () => {
288
+ const result = SensitiveFilter.filter("", { sessionID: testSessionID })
289
+
290
+ expect(result.text).toBe("")
291
+ expect(result.redactions).toHaveLength(0)
292
+ })
293
+
294
+ it("should handle text with no sensitive data", () => {
295
+ const text = "Hello, this is a normal message about coding."
296
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
297
+
298
+ expect(result.text).toBe(text)
299
+ expect(result.redactions).toHaveLength(0)
300
+ })
301
+
302
+ it("should handle mixed sensitive data types", () => {
303
+ const text = `
304
+ Credit Card: 4532015112830366
305
+ SSN: 123-45-6789
306
+ Password: mySecret123
307
+ API Key: sk-proj-abcdefghijklmnopqrstuvwx
308
+ Bearer Token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI
309
+ `
310
+ const result = SensitiveFilter.filter(text, { sessionID: testSessionID })
311
+
312
+ expect(result.text).not.toContain("4532015112830366")
313
+ expect(result.text).not.toContain("123-45-6789")
314
+ expect(result.text).not.toContain("mySecret123")
315
+ expect(result.text).not.toContain("sk-proj")
316
+ expect(result.text).not.toContain("eyJhbGciOiJIUzI1NiIsInR5cCI")
317
+
318
+ // Check all types were detected
319
+ const types = result.redactions.map((r) => r.type)
320
+ expect(types).toContain("CREDIT_CARD")
321
+ expect(types).toContain("SSN")
322
+ expect(types).toContain("PASSWORD")
323
+ expect(types).toContain("API_KEY")
324
+ expect(types).toContain("BEARER_TOKEN")
325
+ })
326
+ })
327
+ })