@getpaseo/server 0.1.37 → 0.1.38

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 (295) hide show
  1. package/dist/scripts/dev-runner.js +1 -1
  2. package/dist/scripts/dev-runner.js.map +1 -1
  3. package/dist/scripts/{daemon-runner.js → supervisor-entrypoint.js} +7 -9
  4. package/dist/scripts/supervisor-entrypoint.js.map +1 -0
  5. package/dist/scripts/supervisor.js +17 -1
  6. package/dist/scripts/supervisor.js.map +1 -1
  7. package/dist/server/server/bootstrap.d.ts +0 -4
  8. package/dist/server/server/bootstrap.d.ts.map +1 -1
  9. package/dist/server/server/bootstrap.js +8 -22
  10. package/dist/server/server/bootstrap.js.map +1 -1
  11. package/dist/server/server/index.js +0 -4
  12. package/dist/server/server/index.js.map +1 -1
  13. package/dist/server/server/pid-lock.d.ts +7 -2
  14. package/dist/server/server/pid-lock.d.ts.map +1 -1
  15. package/dist/server/server/pid-lock.js +21 -0
  16. package/dist/server/server/pid-lock.js.map +1 -1
  17. package/dist/server/server/session.d.ts.map +1 -1
  18. package/dist/server/server/session.js +2 -2
  19. package/dist/server/server/session.js.map +1 -1
  20. package/dist/src/server/pid-lock.js +21 -0
  21. package/dist/src/server/pid-lock.js.map +1 -1
  22. package/package.json +3 -3
  23. package/dist/scripts/daemon-runner.js.map +0 -1
  24. package/dist/src/server/agent/activity-curator.js +0 -243
  25. package/dist/src/server/agent/activity-curator.js.map +0 -1
  26. package/dist/src/server/agent/agent-manager.js +0 -1855
  27. package/dist/src/server/agent/agent-manager.js.map +0 -1
  28. package/dist/src/server/agent/agent-metadata-generator.js +0 -161
  29. package/dist/src/server/agent/agent-metadata-generator.js.map +0 -1
  30. package/dist/src/server/agent/agent-projections.js +0 -254
  31. package/dist/src/server/agent/agent-projections.js.map +0 -1
  32. package/dist/src/server/agent/agent-response-loop.js +0 -304
  33. package/dist/src/server/agent/agent-response-loop.js.map +0 -1
  34. package/dist/src/server/agent/agent-sdk-types.js +0 -12
  35. package/dist/src/server/agent/agent-sdk-types.js.map +0 -1
  36. package/dist/src/server/agent/agent-storage.js +0 -302
  37. package/dist/src/server/agent/agent-storage.js.map +0 -1
  38. package/dist/src/server/agent/agent-title-limits.js +0 -3
  39. package/dist/src/server/agent/agent-title-limits.js.map +0 -1
  40. package/dist/src/server/agent/audio-utils.js +0 -19
  41. package/dist/src/server/agent/audio-utils.js.map +0 -1
  42. package/dist/src/server/agent/dictation-debug.js +0 -50
  43. package/dist/src/server/agent/dictation-debug.js.map +0 -1
  44. package/dist/src/server/agent/mcp-server.js +0 -754
  45. package/dist/src/server/agent/mcp-server.js.map +0 -1
  46. package/dist/src/server/agent/orchestrator-instructions.js +0 -51
  47. package/dist/src/server/agent/orchestrator-instructions.js.map +0 -1
  48. package/dist/src/server/agent/pcm16-resampler.js +0 -63
  49. package/dist/src/server/agent/pcm16-resampler.js.map +0 -1
  50. package/dist/src/server/agent/provider-launch-config.js +0 -213
  51. package/dist/src/server/agent/provider-launch-config.js.map +0 -1
  52. package/dist/src/server/agent/provider-manifest.js +0 -127
  53. package/dist/src/server/agent/provider-manifest.js.map +0 -1
  54. package/dist/src/server/agent/provider-registry.js +0 -45
  55. package/dist/src/server/agent/provider-registry.js.map +0 -1
  56. package/dist/src/server/agent/providers/claude/partial-json.js +0 -306
  57. package/dist/src/server/agent/providers/claude/partial-json.js.map +0 -1
  58. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js +0 -104
  59. package/dist/src/server/agent/providers/claude/sdk-model-resolver.js.map +0 -1
  60. package/dist/src/server/agent/providers/claude/sidechain-tracker.js +0 -230
  61. package/dist/src/server/agent/providers/claude/sidechain-tracker.js.map +0 -1
  62. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js +0 -267
  63. package/dist/src/server/agent/providers/claude/task-notification-tool-call.js.map +0 -1
  64. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js +0 -121
  65. package/dist/src/server/agent/providers/claude/tool-call-detail-parser.js.map +0 -1
  66. package/dist/src/server/agent/providers/claude/tool-call-mapper.js +0 -252
  67. package/dist/src/server/agent/providers/claude/tool-call-mapper.js.map +0 -1
  68. package/dist/src/server/agent/providers/claude-agent.js +0 -3166
  69. package/dist/src/server/agent/providers/claude-agent.js.map +0 -1
  70. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js +0 -104
  71. package/dist/src/server/agent/providers/codex/tool-call-detail-parser.js.map +0 -1
  72. package/dist/src/server/agent/providers/codex/tool-call-mapper.js +0 -758
  73. package/dist/src/server/agent/providers/codex/tool-call-mapper.js.map +0 -1
  74. package/dist/src/server/agent/providers/codex-app-server-agent.js +0 -2949
  75. package/dist/src/server/agent/providers/codex-app-server-agent.js.map +0 -1
  76. package/dist/src/server/agent/providers/codex-rollout-timeline.js +0 -544
  77. package/dist/src/server/agent/providers/codex-rollout-timeline.js.map +0 -1
  78. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js +0 -39
  79. package/dist/src/server/agent/providers/opencode/tool-call-detail-parser.js.map +0 -1
  80. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js +0 -144
  81. package/dist/src/server/agent/providers/opencode/tool-call-mapper.js.map +0 -1
  82. package/dist/src/server/agent/providers/opencode-agent.js +0 -1193
  83. package/dist/src/server/agent/providers/opencode-agent.js.map +0 -1
  84. package/dist/src/server/agent/providers/tool-call-detail-primitives.js +0 -686
  85. package/dist/src/server/agent/providers/tool-call-detail-primitives.js.map +0 -1
  86. package/dist/src/server/agent/providers/tool-call-mapper-utils.js +0 -115
  87. package/dist/src/server/agent/providers/tool-call-mapper-utils.js.map +0 -1
  88. package/dist/src/server/agent/recordings-debug.js +0 -19
  89. package/dist/src/server/agent/recordings-debug.js.map +0 -1
  90. package/dist/src/server/agent/stt-debug.js +0 -33
  91. package/dist/src/server/agent/stt-debug.js.map +0 -1
  92. package/dist/src/server/agent/stt-manager.js +0 -232
  93. package/dist/src/server/agent/stt-manager.js.map +0 -1
  94. package/dist/src/server/agent/timeline-append.js +0 -27
  95. package/dist/src/server/agent/timeline-append.js.map +0 -1
  96. package/dist/src/server/agent/timeline-projection.js +0 -215
  97. package/dist/src/server/agent/timeline-projection.js.map +0 -1
  98. package/dist/src/server/agent/tool-name-normalization.js +0 -45
  99. package/dist/src/server/agent/tool-name-normalization.js.map +0 -1
  100. package/dist/src/server/agent/tts-debug.js +0 -24
  101. package/dist/src/server/agent/tts-debug.js.map +0 -1
  102. package/dist/src/server/agent/tts-manager.js +0 -374
  103. package/dist/src/server/agent/tts-manager.js.map +0 -1
  104. package/dist/src/server/agent/wait-for-agent-tracker.js +0 -53
  105. package/dist/src/server/agent/wait-for-agent-tracker.js.map +0 -1
  106. package/dist/src/server/agent-attention-policy.js +0 -40
  107. package/dist/src/server/agent-attention-policy.js.map +0 -1
  108. package/dist/src/server/allowed-hosts.js +0 -94
  109. package/dist/src/server/allowed-hosts.js.map +0 -1
  110. package/dist/src/server/bootstrap.js +0 -620
  111. package/dist/src/server/bootstrap.js.map +0 -1
  112. package/dist/src/server/chat/chat-mentions.js +0 -71
  113. package/dist/src/server/chat/chat-mentions.js.map +0 -1
  114. package/dist/src/server/chat/chat-rpc-schemas.js +0 -103
  115. package/dist/src/server/chat/chat-rpc-schemas.js.map +0 -1
  116. package/dist/src/server/chat/chat-service.js +0 -330
  117. package/dist/src/server/chat/chat-service.js.map +0 -1
  118. package/dist/src/server/chat/chat-types.js +0 -22
  119. package/dist/src/server/chat/chat-types.js.map +0 -1
  120. package/dist/src/server/checkout-diff-manager.js +0 -272
  121. package/dist/src/server/checkout-diff-manager.js.map +0 -1
  122. package/dist/src/server/checkout-git-utils.js +0 -37
  123. package/dist/src/server/checkout-git-utils.js.map +0 -1
  124. package/dist/src/server/client-message-id.js +0 -12
  125. package/dist/src/server/client-message-id.js.map +0 -1
  126. package/dist/src/server/config.js +0 -73
  127. package/dist/src/server/config.js.map +0 -1
  128. package/dist/src/server/connection-offer.js +0 -59
  129. package/dist/src/server/connection-offer.js.map +0 -1
  130. package/dist/src/server/daemon-keypair.js +0 -40
  131. package/dist/src/server/daemon-keypair.js.map +0 -1
  132. package/dist/src/server/daemon-version.js +0 -22
  133. package/dist/src/server/daemon-version.js.map +0 -1
  134. package/dist/src/server/dictation/dictation-stream-manager.js +0 -571
  135. package/dist/src/server/dictation/dictation-stream-manager.js.map +0 -1
  136. package/dist/src/server/file-download/token-store.js +0 -40
  137. package/dist/src/server/file-download/token-store.js.map +0 -1
  138. package/dist/src/server/file-explorer/service.js +0 -180
  139. package/dist/src/server/file-explorer/service.js.map +0 -1
  140. package/dist/src/server/json-utils.js +0 -45
  141. package/dist/src/server/json-utils.js.map +0 -1
  142. package/dist/src/server/loop/rpc-schemas.js +0 -159
  143. package/dist/src/server/loop/rpc-schemas.js.map +0 -1
  144. package/dist/src/server/loop-service.js +0 -741
  145. package/dist/src/server/loop-service.js.map +0 -1
  146. package/dist/src/server/messages.js +0 -29
  147. package/dist/src/server/messages.js.map +0 -1
  148. package/dist/src/server/package-version.js +0 -46
  149. package/dist/src/server/package-version.js.map +0 -1
  150. package/dist/src/server/pairing-offer.js +0 -45
  151. package/dist/src/server/pairing-offer.js.map +0 -1
  152. package/dist/src/server/pairing-qr.js +0 -45
  153. package/dist/src/server/pairing-qr.js.map +0 -1
  154. package/dist/src/server/path-utils.js +0 -20
  155. package/dist/src/server/path-utils.js.map +0 -1
  156. package/dist/src/server/persisted-config.js +0 -265
  157. package/dist/src/server/persisted-config.js.map +0 -1
  158. package/dist/src/server/persistence-hooks.js +0 -60
  159. package/dist/src/server/persistence-hooks.js.map +0 -1
  160. package/dist/src/server/push/push-service.js +0 -68
  161. package/dist/src/server/push/push-service.js.map +0 -1
  162. package/dist/src/server/push/token-store.js +0 -70
  163. package/dist/src/server/push/token-store.js.map +0 -1
  164. package/dist/src/server/relay-transport.js +0 -461
  165. package/dist/src/server/relay-transport.js.map +0 -1
  166. package/dist/src/server/schedule/cron.js +0 -103
  167. package/dist/src/server/schedule/cron.js.map +0 -1
  168. package/dist/src/server/schedule/rpc-schemas.js +0 -112
  169. package/dist/src/server/schedule/rpc-schemas.js.map +0 -1
  170. package/dist/src/server/schedule/service.js +0 -397
  171. package/dist/src/server/schedule/service.js.map +0 -1
  172. package/dist/src/server/schedule/store.js +0 -56
  173. package/dist/src/server/schedule/store.js.map +0 -1
  174. package/dist/src/server/schedule/types.js +0 -73
  175. package/dist/src/server/schedule/types.js.map +0 -1
  176. package/dist/src/server/server-id.js +0 -63
  177. package/dist/src/server/server-id.js.map +0 -1
  178. package/dist/src/server/session.js +0 -6381
  179. package/dist/src/server/session.js.map +0 -1
  180. package/dist/src/server/speech/audio.js +0 -101
  181. package/dist/src/server/speech/audio.js.map +0 -1
  182. package/dist/src/server/speech/provider-resolver.js +0 -7
  183. package/dist/src/server/speech/provider-resolver.js.map +0 -1
  184. package/dist/src/server/speech/providers/local/config.js +0 -74
  185. package/dist/src/server/speech/providers/local/config.js.map +0 -1
  186. package/dist/src/server/speech/providers/local/models.js +0 -17
  187. package/dist/src/server/speech/providers/local/models.js.map +0 -1
  188. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js +0 -436
  189. package/dist/src/server/speech/providers/local/pocket/pocket-tts-onnx.js.map +0 -1
  190. package/dist/src/server/speech/providers/local/runtime.js +0 -238
  191. package/dist/src/server/speech/providers/local/runtime.js.map +0 -1
  192. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js +0 -166
  193. package/dist/src/server/speech/providers/local/sherpa/model-catalog.js.map +0 -1
  194. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js +0 -165
  195. package/dist/src/server/speech/providers/local/sherpa/model-downloader.js.map +0 -1
  196. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +0 -73
  197. package/dist/src/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js.map +0 -1
  198. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js +0 -84
  199. package/dist/src/server/speech/providers/local/sherpa/sherpa-online-recognizer.js.map +0 -1
  200. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js +0 -11
  201. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-loader.js.map +0 -1
  202. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +0 -102
  203. package/dist/src/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js.map +0 -1
  204. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +0 -135
  205. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js.map +0 -1
  206. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +0 -130
  207. package/dist/src/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js.map +0 -1
  208. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js +0 -110
  209. package/dist/src/server/speech/providers/local/sherpa/sherpa-realtime-session.js.map +0 -1
  210. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js +0 -138
  211. package/dist/src/server/speech/providers/local/sherpa/sherpa-stt.js.map +0 -1
  212. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js +0 -98
  213. package/dist/src/server/speech/providers/local/sherpa/sherpa-tts.js.map +0 -1
  214. package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js +0 -23
  215. package/dist/src/server/speech/providers/local/sherpa/silero-vad-provider.js.map +0 -1
  216. package/dist/src/server/speech/providers/local/sherpa/silero-vad-session.js +0 -107
  217. package/dist/src/server/speech/providers/local/sherpa/silero-vad-session.js.map +0 -1
  218. package/dist/src/server/speech/providers/openai/config.js +0 -80
  219. package/dist/src/server/speech/providers/openai/config.js.map +0 -1
  220. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js +0 -168
  221. package/dist/src/server/speech/providers/openai/realtime-transcription-session.js.map +0 -1
  222. package/dist/src/server/speech/providers/openai/runtime.js +0 -112
  223. package/dist/src/server/speech/providers/openai/runtime.js.map +0 -1
  224. package/dist/src/server/speech/providers/openai/stt.js +0 -206
  225. package/dist/src/server/speech/providers/openai/stt.js.map +0 -1
  226. package/dist/src/server/speech/providers/openai/tts.js +0 -46
  227. package/dist/src/server/speech/providers/openai/tts.js.map +0 -1
  228. package/dist/src/server/speech/speech-config-resolver.js +0 -102
  229. package/dist/src/server/speech/speech-config-resolver.js.map +0 -1
  230. package/dist/src/server/speech/speech-provider.js +0 -2
  231. package/dist/src/server/speech/speech-provider.js.map +0 -1
  232. package/dist/src/server/speech/speech-runtime.js +0 -530
  233. package/dist/src/server/speech/speech-runtime.js.map +0 -1
  234. package/dist/src/server/speech/speech-types.js +0 -8
  235. package/dist/src/server/speech/speech-types.js.map +0 -1
  236. package/dist/src/server/speech/turn-detection-provider.js +0 -2
  237. package/dist/src/server/speech/turn-detection-provider.js.map +0 -1
  238. package/dist/src/server/utils/diff-highlighter.js +0 -257
  239. package/dist/src/server/utils/diff-highlighter.js.map +0 -1
  240. package/dist/src/server/voice/fixed-duration-pcm-ring-buffer.js +0 -35
  241. package/dist/src/server/voice/fixed-duration-pcm-ring-buffer.js.map +0 -1
  242. package/dist/src/server/voice/voice-turn-controller.js +0 -159
  243. package/dist/src/server/voice/voice-turn-controller.js.map +0 -1
  244. package/dist/src/server/voice-config.js +0 -51
  245. package/dist/src/server/voice-config.js.map +0 -1
  246. package/dist/src/server/voice-mcp-bridge-command.js +0 -31
  247. package/dist/src/server/voice-mcp-bridge-command.js.map +0 -1
  248. package/dist/src/server/voice-mcp-bridge.js +0 -109
  249. package/dist/src/server/voice-mcp-bridge.js.map +0 -1
  250. package/dist/src/server/voice-permission-policy.js +0 -13
  251. package/dist/src/server/voice-permission-policy.js.map +0 -1
  252. package/dist/src/server/voice-types.js +0 -2
  253. package/dist/src/server/voice-types.js.map +0 -1
  254. package/dist/src/server/websocket-server.js +0 -1048
  255. package/dist/src/server/websocket-server.js.map +0 -1
  256. package/dist/src/server/workspace-registry-bootstrap.js +0 -98
  257. package/dist/src/server/workspace-registry-bootstrap.js.map +0 -1
  258. package/dist/src/server/workspace-registry-model.js +0 -175
  259. package/dist/src/server/workspace-registry-model.js.map +0 -1
  260. package/dist/src/server/workspace-registry.js +0 -151
  261. package/dist/src/server/workspace-registry.js.map +0 -1
  262. package/dist/src/server/worktree-bootstrap.js +0 -508
  263. package/dist/src/server/worktree-bootstrap.js.map +0 -1
  264. package/dist/src/shared/agent-attention-notification.js +0 -130
  265. package/dist/src/shared/agent-attention-notification.js.map +0 -1
  266. package/dist/src/shared/agent-lifecycle.js +0 -8
  267. package/dist/src/shared/agent-lifecycle.js.map +0 -1
  268. package/dist/src/shared/connection-offer.js +0 -17
  269. package/dist/src/shared/connection-offer.js.map +0 -1
  270. package/dist/src/shared/daemon-endpoints.js +0 -122
  271. package/dist/src/shared/daemon-endpoints.js.map +0 -1
  272. package/dist/src/shared/messages.js +0 -2107
  273. package/dist/src/shared/messages.js.map +0 -1
  274. package/dist/src/shared/path-utils.js +0 -16
  275. package/dist/src/shared/path-utils.js.map +0 -1
  276. package/dist/src/shared/terminal-stream-protocol.js +0 -99
  277. package/dist/src/shared/terminal-stream-protocol.js.map +0 -1
  278. package/dist/src/shared/tool-call-display.js +0 -122
  279. package/dist/src/shared/tool-call-display.js.map +0 -1
  280. package/dist/src/terminal/terminal-manager.js +0 -136
  281. package/dist/src/terminal/terminal-manager.js.map +0 -1
  282. package/dist/src/terminal/terminal.js +0 -333
  283. package/dist/src/terminal/terminal.js.map +0 -1
  284. package/dist/src/utils/checkout-git.js +0 -1518
  285. package/dist/src/utils/checkout-git.js.map +0 -1
  286. package/dist/src/utils/directory-suggestions.js +0 -671
  287. package/dist/src/utils/directory-suggestions.js.map +0 -1
  288. package/dist/src/utils/path.js +0 -15
  289. package/dist/src/utils/path.js.map +0 -1
  290. package/dist/src/utils/project-icon.js +0 -389
  291. package/dist/src/utils/project-icon.js.map +0 -1
  292. package/dist/src/utils/worktree-metadata.js +0 -116
  293. package/dist/src/utils/worktree-metadata.js.map +0 -1
  294. package/dist/src/utils/worktree.js +0 -744
  295. package/dist/src/utils/worktree.js.map +0 -1
@@ -1,1193 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
4
- import net from "node:net";
5
- import { z } from "zod";
6
- import { applyProviderEnv, findExecutable, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
7
- import { mapOpencodeToolCall } from "./opencode/tool-call-mapper.js";
8
- const OPENCODE_CAPABILITIES = {
9
- supportsStreaming: true,
10
- supportsSessionPersistence: true,
11
- supportsDynamicModes: true,
12
- supportsMcpServers: true,
13
- supportsReasoningStream: true,
14
- supportsToolInvocations: true,
15
- };
16
- const DEFAULT_MODES = [
17
- {
18
- id: "build",
19
- label: "Build",
20
- description: "Allows edits and tool execution for implementation work",
21
- },
22
- {
23
- id: "plan",
24
- label: "Plan",
25
- description: "Read-only planning mode that avoids file edits",
26
- },
27
- ];
28
- const OPENCODE_MODE_IDS = new Set(DEFAULT_MODES.map((mode) => mode.id));
29
- const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"];
30
- const OpencodeToolStateSchema = z
31
- .object({
32
- status: z.string().optional(),
33
- input: z.unknown().optional(),
34
- output: z.unknown().optional(),
35
- error: z.unknown().optional(),
36
- })
37
- .passthrough();
38
- const OpencodeToolPartBaseSchema = z
39
- .object({
40
- tool: z.string().trim().min(1),
41
- state: OpencodeToolStateSchema.optional(),
42
- })
43
- .passthrough();
44
- const OpencodeToolPartWithCallIdSchema = OpencodeToolPartBaseSchema.extend({
45
- callID: z.string().trim().min(1),
46
- id: z.string().optional(),
47
- }).transform((part) => ({
48
- toolName: part.tool,
49
- callId: part.callID,
50
- status: part.state?.status,
51
- input: part.state?.input,
52
- output: part.state?.output,
53
- error: part.state?.error,
54
- }));
55
- const OpencodeToolPartWithIdSchema = OpencodeToolPartBaseSchema.extend({
56
- id: z.string().trim().min(1),
57
- callID: z.string().optional(),
58
- }).transform((part) => ({
59
- toolName: part.tool,
60
- callId: part.id,
61
- status: part.state?.status,
62
- input: part.state?.input,
63
- output: part.state?.output,
64
- error: part.state?.error,
65
- }));
66
- const OpencodeToolPartWithoutIdSchema = OpencodeToolPartBaseSchema.extend({
67
- id: z.string().optional(),
68
- callID: z.string().optional(),
69
- }).transform((part) => ({
70
- toolName: part.tool,
71
- callId: undefined,
72
- status: part.state?.status,
73
- input: part.state?.input,
74
- output: part.state?.output,
75
- error: part.state?.error,
76
- }));
77
- const OpencodeToolPartSchema = z.union([
78
- OpencodeToolPartWithCallIdSchema,
79
- OpencodeToolPartWithIdSchema,
80
- OpencodeToolPartWithoutIdSchema,
81
- ]);
82
- const OpencodeToolPartTimelineEnvelopeSchema = OpencodeToolPartSchema.transform((part) => ({
83
- toolName: part.toolName,
84
- callId: part.callId,
85
- status: part.status,
86
- input: part.input,
87
- output: part.output,
88
- error: part.error,
89
- }));
90
- const OpencodeToolPartToTimelineItemSchema = OpencodeToolPartTimelineEnvelopeSchema.transform((part) => mapOpencodeToolCall({
91
- toolName: part.toolName,
92
- callId: part.callId,
93
- status: part.status,
94
- input: part.input,
95
- output: part.output,
96
- error: part.error,
97
- }));
98
- function resolveOpenCodeBinary() {
99
- const found = findExecutable("opencode");
100
- if (found) {
101
- return found;
102
- }
103
- throw new Error("OpenCode binary not found. Install OpenCode (https://github.com/opencode-ai/opencode) and ensure it is available in your shell PATH.");
104
- }
105
- function toOpenCodeMcpConfig(config) {
106
- if (config.type === "stdio") {
107
- return {
108
- type: "local",
109
- command: [config.command, ...(config.args ?? [])],
110
- ...(config.env ? { environment: config.env } : {}),
111
- enabled: true,
112
- };
113
- }
114
- return {
115
- type: "remote",
116
- url: config.url,
117
- ...(config.headers ? { headers: config.headers } : {}),
118
- enabled: true,
119
- };
120
- }
121
- function stringifyUnknownError(error) {
122
- if (typeof error === "string") {
123
- return error;
124
- }
125
- try {
126
- return JSON.stringify(error);
127
- }
128
- catch {
129
- return String(error);
130
- }
131
- }
132
- function isAlreadyPresentMcpError(error) {
133
- const normalized = stringifyUnknownError(error).toLowerCase();
134
- return MCP_ALREADY_PRESENT_ERROR_TOKENS.some((token) => normalized.includes(token));
135
- }
136
- async function findAvailablePort() {
137
- return new Promise((resolve, reject) => {
138
- const server = net.createServer();
139
- server.listen(0, () => {
140
- const address = server.address();
141
- if (address && typeof address === "object") {
142
- const port = address.port;
143
- server.close(() => resolve(port));
144
- }
145
- else {
146
- server.close(() => reject(new Error("Failed to get port")));
147
- }
148
- });
149
- server.on("error", reject);
150
- });
151
- }
152
- function resolvePartDedupeKey(part, partType) {
153
- const partId = part.id;
154
- if (typeof partId === "string" && partId.trim().length > 0) {
155
- return `${partType}:${partId}`;
156
- }
157
- const messageId = part.messageID;
158
- if (typeof messageId === "string" && messageId.trim().length > 0) {
159
- return `${partType}:message:${messageId}`;
160
- }
161
- return null;
162
- }
163
- function normalizeOpenCodeModeId(modeId) {
164
- const trimmed = typeof modeId === "string" ? modeId.trim() : "";
165
- if (!trimmed || trimmed === "default") {
166
- return "build";
167
- }
168
- return trimmed;
169
- }
170
- function sortOpenCodeModes(modes) {
171
- const order = new Map(DEFAULT_MODES.map((mode, index) => [mode.id, index]));
172
- return [...modes].sort((left, right) => {
173
- const leftOrder = order.get(left.id) ?? Number.MAX_SAFE_INTEGER;
174
- const rightOrder = order.get(right.id) ?? Number.MAX_SAFE_INTEGER;
175
- if (leftOrder !== rightOrder) {
176
- return leftOrder - rightOrder;
177
- }
178
- return left.label.localeCompare(right.label);
179
- });
180
- }
181
- export class OpenCodeServerManager {
182
- constructor(logger, runtimeSettings) {
183
- this.server = null;
184
- this.port = null;
185
- this.startPromise = null;
186
- this.logger = logger;
187
- this.runtimeSettings = runtimeSettings;
188
- this.runtimeSettingsKey = JSON.stringify(runtimeSettings ?? {});
189
- }
190
- static getInstance(logger, runtimeSettings) {
191
- const nextSettingsKey = JSON.stringify(runtimeSettings ?? {});
192
- if (!OpenCodeServerManager.instance) {
193
- OpenCodeServerManager.instance = new OpenCodeServerManager(logger, runtimeSettings);
194
- OpenCodeServerManager.registerExitHandler();
195
- }
196
- else if (OpenCodeServerManager.instance.runtimeSettingsKey !== nextSettingsKey) {
197
- logger.warn({
198
- existingRuntimeSettings: OpenCodeServerManager.instance.runtimeSettingsKey,
199
- requestedRuntimeSettings: nextSettingsKey,
200
- }, "OpenCode server manager already initialized with different runtime settings");
201
- }
202
- return OpenCodeServerManager.instance;
203
- }
204
- static registerExitHandler() {
205
- if (OpenCodeServerManager.exitHandlerRegistered) {
206
- return;
207
- }
208
- OpenCodeServerManager.exitHandlerRegistered = true;
209
- const cleanup = () => {
210
- const instance = OpenCodeServerManager.instance;
211
- if (instance?.server && !instance.server.killed) {
212
- instance.server.kill("SIGTERM");
213
- }
214
- };
215
- process.on("exit", cleanup);
216
- process.on("SIGTERM", cleanup);
217
- process.on("SIGINT", cleanup);
218
- }
219
- async ensureRunning() {
220
- if (this.startPromise) {
221
- return this.startPromise;
222
- }
223
- if (this.server && this.port && !this.server.killed) {
224
- return { port: this.port, url: `http://127.0.0.1:${this.port}` };
225
- }
226
- this.startPromise = this.startServer();
227
- try {
228
- const result = await this.startPromise;
229
- return result;
230
- }
231
- finally {
232
- this.startPromise = null;
233
- }
234
- }
235
- async startServer() {
236
- this.port = await findAvailablePort();
237
- const url = `http://127.0.0.1:${this.port}`;
238
- const launchPrefix = resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary);
239
- return new Promise((resolve, reject) => {
240
- this.server = spawn(launchPrefix.command, [...launchPrefix.args, "serve", "--port", String(this.port)], {
241
- stdio: ["ignore", "pipe", "pipe"],
242
- env: applyProviderEnv(process.env, this.runtimeSettings),
243
- });
244
- let started = false;
245
- const timeout = setTimeout(() => {
246
- if (!started) {
247
- reject(new Error("OpenCode server startup timeout"));
248
- }
249
- }, 30000);
250
- this.server.stdout?.on("data", (data) => {
251
- const output = data.toString();
252
- if (output.includes("listening on") && !started) {
253
- started = true;
254
- clearTimeout(timeout);
255
- resolve({ port: this.port, url });
256
- }
257
- });
258
- this.server.stderr?.on("data", (data) => {
259
- this.logger.error({ stderr: data.toString().trim() }, "OpenCode server stderr");
260
- });
261
- this.server.on("error", (error) => {
262
- clearTimeout(timeout);
263
- reject(error);
264
- });
265
- this.server.on("exit", (code) => {
266
- if (!started) {
267
- clearTimeout(timeout);
268
- reject(new Error(`OpenCode server exited with code ${code}`));
269
- }
270
- this.server = null;
271
- this.port = null;
272
- });
273
- });
274
- }
275
- async shutdown() {
276
- if (this.server && !this.server.killed) {
277
- this.server.kill("SIGTERM");
278
- await new Promise((resolve) => {
279
- const timeout = setTimeout(() => {
280
- this.server?.kill("SIGKILL");
281
- resolve();
282
- }, 5000);
283
- this.server?.on("exit", () => {
284
- clearTimeout(timeout);
285
- resolve();
286
- });
287
- });
288
- }
289
- this.server = null;
290
- this.port = null;
291
- }
292
- }
293
- OpenCodeServerManager.instance = null;
294
- OpenCodeServerManager.exitHandlerRegistered = false;
295
- export class OpenCodeAgentClient {
296
- constructor(logger, runtimeSettings) {
297
- this.provider = "opencode";
298
- this.capabilities = OPENCODE_CAPABILITIES;
299
- this.logger = logger.child({ module: "agent", provider: "opencode" });
300
- this.runtimeSettings = runtimeSettings;
301
- this.serverManager = OpenCodeServerManager.getInstance(this.logger, runtimeSettings);
302
- }
303
- async createSession(config, _launchContext) {
304
- const openCodeConfig = this.assertConfig(config);
305
- const { url } = await this.serverManager.ensureRunning();
306
- const client = createOpencodeClient({
307
- baseUrl: url,
308
- directory: openCodeConfig.cwd,
309
- });
310
- // Set a timeout for session creation to fail fast
311
- const timeoutPromise = new Promise((_, reject) => {
312
- setTimeout(() => reject(new Error("OpenCode session.create timed out after 10s")), 10000);
313
- });
314
- const response = await Promise.race([
315
- client.session.create({ directory: openCodeConfig.cwd }),
316
- timeoutPromise,
317
- ]);
318
- if (response.error) {
319
- throw new Error(`Failed to create OpenCode session: ${JSON.stringify(response.error)}`);
320
- }
321
- const session = response.data;
322
- if (!session) {
323
- throw new Error("OpenCode session creation returned no data");
324
- }
325
- return new OpenCodeAgentSession(openCodeConfig, client, session.id);
326
- }
327
- async resumeSession(handle, overrides, _launchContext) {
328
- const cwd = overrides?.cwd ?? handle.metadata?.cwd;
329
- if (!cwd) {
330
- throw new Error("OpenCode resume requires the original working directory");
331
- }
332
- const config = {
333
- provider: "opencode",
334
- cwd,
335
- ...overrides,
336
- };
337
- const openCodeConfig = this.assertConfig(config);
338
- const { url } = await this.serverManager.ensureRunning();
339
- const client = createOpencodeClient({
340
- baseUrl: url,
341
- directory: openCodeConfig.cwd,
342
- });
343
- return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId);
344
- }
345
- async listModels(options) {
346
- const { url } = await this.serverManager.ensureRunning();
347
- const client = createOpencodeClient({
348
- baseUrl: url,
349
- directory: options?.cwd ?? process.cwd(),
350
- });
351
- // Set a timeout for the API call to fail fast if OpenCode isn't responding
352
- const timeoutPromise = new Promise((_, reject) => {
353
- setTimeout(() => reject(new Error("OpenCode provider.list timed out after 10s - server may not be authenticated or connected to any providers")), 10000);
354
- });
355
- const response = await Promise.race([
356
- client.provider.list({ directory: options?.cwd ?? process.cwd() }),
357
- timeoutPromise,
358
- ]);
359
- if (response.error) {
360
- throw new Error(`Failed to fetch OpenCode providers: ${JSON.stringify(response.error)}`);
361
- }
362
- const providers = response.data;
363
- if (!providers) {
364
- return [];
365
- }
366
- // Only include models from connected providers (ones that are actually available)
367
- const connectedProviderIds = new Set(providers.connected);
368
- // Fail fast if no providers are connected
369
- if (connectedProviderIds.size === 0) {
370
- throw new Error("OpenCode has no connected providers. Please authenticate with at least one provider (e.g., openai, anthropic) or set appropriate environment variables (e.g., OPENAI_API_KEY).");
371
- }
372
- const models = [];
373
- for (const provider of providers.all) {
374
- // Skip providers that aren't connected/configured
375
- if (!connectedProviderIds.has(provider.id)) {
376
- continue;
377
- }
378
- for (const [modelId, model] of Object.entries(provider.models)) {
379
- const rawVariants = model.variants ? Object.keys(model.variants) : [];
380
- const thinkingOptions = [
381
- { id: "default", label: "Model default", isDefault: true },
382
- ...rawVariants.map((id) => ({ id, label: id })),
383
- ];
384
- models.push({
385
- provider: "opencode",
386
- id: `${provider.id}/${modelId}`,
387
- label: model.name,
388
- description: `${provider.name} - ${model.family ?? ""}`.trim(),
389
- thinkingOptions: thinkingOptions.length > 1 ? thinkingOptions : undefined,
390
- defaultThinkingOptionId: "default",
391
- metadata: {
392
- providerId: provider.id,
393
- providerName: provider.name,
394
- modelId,
395
- family: model.family,
396
- releaseDate: model.release_date,
397
- supportsAttachments: model.attachment,
398
- supportsReasoning: model.reasoning,
399
- supportsToolCall: model.tool_call,
400
- cost: model.cost,
401
- },
402
- });
403
- }
404
- }
405
- return models;
406
- }
407
- async listPersistedAgents(_options) {
408
- // TODO: Implement by listing sessions from OpenCode
409
- return [];
410
- }
411
- async isAvailable() {
412
- const command = this.runtimeSettings?.command;
413
- if (command?.mode === "replace") {
414
- return existsSync(command.argv[0]);
415
- }
416
- return true;
417
- }
418
- assertConfig(config) {
419
- if (config.provider !== "opencode") {
420
- throw new Error(`OpenCodeAgentClient received config for provider '${config.provider}'`);
421
- }
422
- return { ...config, provider: "opencode" };
423
- }
424
- }
425
- function stringifyStructuredAssistantMessage(value) {
426
- if (value === undefined) {
427
- return null;
428
- }
429
- if (typeof value === "string") {
430
- const trimmed = value.trim();
431
- return trimmed.length > 0 ? trimmed : null;
432
- }
433
- try {
434
- return JSON.stringify(value);
435
- }
436
- catch {
437
- return null;
438
- }
439
- }
440
- function readOpenCodeRecord(value) {
441
- return typeof value === "object" && value !== null && !Array.isArray(value)
442
- ? value
443
- : null;
444
- }
445
- function readNonEmptyString(value) {
446
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
447
- }
448
- function normalizeQuestionOptions(value) {
449
- if (!Array.isArray(value)) {
450
- return null;
451
- }
452
- const options = [];
453
- for (const item of value) {
454
- if (typeof item === "string" && item.trim().length > 0) {
455
- options.push({ label: item.trim() });
456
- continue;
457
- }
458
- const record = readOpenCodeRecord(item);
459
- const label = readNonEmptyString(record?.label);
460
- if (!label) {
461
- continue;
462
- }
463
- const description = readNonEmptyString(record?.description);
464
- options.push(description ? { label, description } : { label });
465
- }
466
- return options;
467
- }
468
- export function translateOpenCodeEvent(event, state) {
469
- const events = [];
470
- if (!event || typeof event !== "object") {
471
- return events;
472
- }
473
- const e = event;
474
- const type = e.type;
475
- const props = e.properties ?? {};
476
- switch (type) {
477
- case "session.created":
478
- case "session.updated": {
479
- const sessionId = props.id;
480
- if (sessionId === state.sessionId) {
481
- events.push({
482
- type: "thread_started",
483
- sessionId: state.sessionId,
484
- provider: "opencode",
485
- });
486
- }
487
- break;
488
- }
489
- case "message.updated": {
490
- const info = props.info;
491
- if (!info) {
492
- break;
493
- }
494
- const messageId = info.id;
495
- const messageSessionId = info.sessionID;
496
- const role = info.role;
497
- if (messageId && messageSessionId === state.sessionId && role) {
498
- state.messageRoles.set(messageId, role);
499
- if (role === "assistant" &&
500
- !state.emittedStructuredMessageIds.has(messageId) &&
501
- typeof info.time === "object" &&
502
- info.time !== null &&
503
- "completed" in info.time) {
504
- const text = stringifyStructuredAssistantMessage(info.structured);
505
- if (text) {
506
- state.emittedStructuredMessageIds.add(messageId);
507
- events.push({
508
- type: "timeline",
509
- provider: "opencode",
510
- item: { type: "assistant_message", text },
511
- });
512
- }
513
- }
514
- }
515
- break;
516
- }
517
- case "message.part.updated": {
518
- const part = props.part;
519
- const delta = props.delta;
520
- if (!part) {
521
- break;
522
- }
523
- const partSessionId = part.sessionID;
524
- if (partSessionId !== state.sessionId) {
525
- break;
526
- }
527
- const messageId = part.messageID;
528
- const messageRole = messageId ? state.messageRoles.get(messageId) : undefined;
529
- const partType = part.type;
530
- const partTime = part.time;
531
- if (partType === "text") {
532
- const partKey = resolvePartDedupeKey(part, "text");
533
- if (messageRole === "user") {
534
- break;
535
- }
536
- if (!messageRole && !delta) {
537
- break;
538
- }
539
- if (delta) {
540
- if (partKey) {
541
- state.streamedPartKeys.add(partKey);
542
- }
543
- events.push({
544
- type: "timeline",
545
- provider: "opencode",
546
- item: { type: "assistant_message", text: delta },
547
- });
548
- }
549
- else if (partTime?.end) {
550
- if (partKey && state.streamedPartKeys.delete(partKey)) {
551
- break;
552
- }
553
- const text = part.text;
554
- if (text) {
555
- events.push({
556
- type: "timeline",
557
- provider: "opencode",
558
- item: { type: "assistant_message", text },
559
- });
560
- }
561
- }
562
- }
563
- else if (partType === "reasoning") {
564
- const partKey = resolvePartDedupeKey(part, "reasoning");
565
- if (delta) {
566
- if (partKey) {
567
- state.streamedPartKeys.add(partKey);
568
- }
569
- events.push({
570
- type: "timeline",
571
- provider: "opencode",
572
- item: { type: "reasoning", text: delta },
573
- });
574
- }
575
- else if (partTime?.end) {
576
- if (partKey && state.streamedPartKeys.delete(partKey)) {
577
- break;
578
- }
579
- const text = part.text;
580
- if (text) {
581
- events.push({
582
- type: "timeline",
583
- provider: "opencode",
584
- item: { type: "reasoning", text },
585
- });
586
- }
587
- }
588
- }
589
- else if (partType === "tool") {
590
- const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
591
- if (parsedToolPart.success && parsedToolPart.data) {
592
- events.push({
593
- type: "timeline",
594
- provider: "opencode",
595
- item: parsedToolPart.data,
596
- });
597
- }
598
- }
599
- else if (partType === "step-finish") {
600
- const tokens = part.tokens;
601
- const cost = part.cost;
602
- if (tokens) {
603
- state.accumulatedUsage.inputTokens =
604
- (state.accumulatedUsage.inputTokens ?? 0) + (tokens.input ?? 0);
605
- state.accumulatedUsage.outputTokens =
606
- (state.accumulatedUsage.outputTokens ?? 0) + (tokens.output ?? 0);
607
- }
608
- if (cost !== undefined) {
609
- state.accumulatedUsage.totalCostUsd = (state.accumulatedUsage.totalCostUsd ?? 0) + cost;
610
- }
611
- }
612
- break;
613
- }
614
- case "permission.asked": {
615
- const sessionId = props.sessionID;
616
- if (sessionId !== state.sessionId) {
617
- break;
618
- }
619
- const requestId = props.id;
620
- const permission = props.permission;
621
- const metadata = props.metadata;
622
- const patterns = props.patterns;
623
- const permRequest = {
624
- id: requestId,
625
- provider: "opencode",
626
- name: permission,
627
- kind: "tool",
628
- title: permission,
629
- description: patterns?.join(", "),
630
- input: metadata,
631
- };
632
- events.push({
633
- type: "permission_requested",
634
- provider: "opencode",
635
- request: permRequest,
636
- });
637
- break;
638
- }
639
- case "question.asked": {
640
- const sessionId = props.sessionID;
641
- if (sessionId !== state.sessionId) {
642
- break;
643
- }
644
- const requestId = props.id;
645
- const rawQuestions = Array.isArray(props.questions) ? props.questions : [];
646
- if (!requestId || rawQuestions.length === 0) {
647
- break;
648
- }
649
- const questions = rawQuestions.flatMap((item) => {
650
- const questionRecord = readOpenCodeRecord(item);
651
- const question = readNonEmptyString(questionRecord?.question);
652
- const header = readNonEmptyString(questionRecord?.header);
653
- if (!question || !header) {
654
- return [];
655
- }
656
- const options = normalizeQuestionOptions(questionRecord?.options) ?? [];
657
- return [
658
- {
659
- question,
660
- header,
661
- options,
662
- ...(questionRecord?.multiple === true ? { multiSelect: true } : {}),
663
- },
664
- ];
665
- });
666
- if (questions.length === 0) {
667
- break;
668
- }
669
- events.push({
670
- type: "permission_requested",
671
- provider: "opencode",
672
- request: {
673
- id: requestId,
674
- provider: "opencode",
675
- name: "question",
676
- kind: "question",
677
- title: "Question",
678
- input: { questions },
679
- metadata: {
680
- source: "opencode_question",
681
- ...(readOpenCodeRecord(props.tool) ?? {}),
682
- },
683
- },
684
- });
685
- break;
686
- }
687
- case "session.idle": {
688
- const sessionId = props.sessionID;
689
- if (sessionId === state.sessionId) {
690
- state.streamedPartKeys.clear();
691
- events.push({
692
- type: "turn_completed",
693
- provider: "opencode",
694
- usage: undefined,
695
- });
696
- }
697
- break;
698
- }
699
- case "session.error": {
700
- const sessionId = props.sessionID;
701
- if (sessionId === state.sessionId) {
702
- state.streamedPartKeys.clear();
703
- const error = props.error;
704
- events.push({
705
- type: "turn_failed",
706
- provider: "opencode",
707
- error: error ?? "Unknown error",
708
- });
709
- }
710
- break;
711
- }
712
- }
713
- return events;
714
- }
715
- class OpenCodeAgentSession {
716
- constructor(config, client, sessionId) {
717
- this.provider = "opencode";
718
- this.capabilities = OPENCODE_CAPABILITIES;
719
- this.currentMode = "default";
720
- this.pendingPermissions = new Map();
721
- this.abortController = null;
722
- this.accumulatedUsage = {};
723
- this.mcpConfigured = false;
724
- this.mcpSetupPromise = null;
725
- /** Tracks the role of each message by ID to distinguish user from assistant messages */
726
- this.messageRoles = new Map();
727
- /** Tracks streamed textual part IDs to suppress final full-text echoes from OpenCode. */
728
- this.streamedPartKeys = new Set();
729
- /** Tracks assistant messages already emitted from structured payloads. */
730
- this.emittedStructuredMessageIds = new Set();
731
- this.availableModesCache = null;
732
- this.subscribers = new Set();
733
- this.nextTurnOrdinal = 0;
734
- this.activeForegroundTurnId = null;
735
- this.config = config;
736
- this.client = client;
737
- this.sessionId = sessionId;
738
- this.currentMode = normalizeOpenCodeModeId(config.modeId);
739
- }
740
- get id() {
741
- return this.sessionId;
742
- }
743
- async getRuntimeInfo() {
744
- return {
745
- provider: "opencode",
746
- sessionId: this.sessionId,
747
- model: this.config.model ?? null,
748
- modeId: this.currentMode,
749
- };
750
- }
751
- async setModel(modelId) {
752
- const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
753
- this.config.model = normalizedModelId ?? undefined;
754
- }
755
- async setThinkingOption(thinkingOptionId) {
756
- const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
757
- ? thinkingOptionId
758
- : null;
759
- this.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
760
- }
761
- async run(prompt, options) {
762
- const timeline = [];
763
- let finalText = "";
764
- let usage;
765
- let turnId = null;
766
- const bufferedEvents = [];
767
- let settled = false;
768
- let resolveCompletion;
769
- let rejectCompletion;
770
- const processEvent = (event) => {
771
- if (settled) {
772
- return;
773
- }
774
- const eventTurnId = event.turnId;
775
- if (turnId && eventTurnId && eventTurnId !== turnId) {
776
- return;
777
- }
778
- if (event.type === "timeline") {
779
- timeline.push(event.item);
780
- if (event.item.type === "assistant_message") {
781
- finalText = event.item.text;
782
- }
783
- return;
784
- }
785
- if (event.type === "turn_completed") {
786
- usage = event.usage;
787
- settled = true;
788
- resolveCompletion();
789
- return;
790
- }
791
- if (event.type === "turn_failed") {
792
- settled = true;
793
- rejectCompletion(new Error(event.error));
794
- return;
795
- }
796
- if (event.type === "turn_canceled") {
797
- settled = true;
798
- resolveCompletion();
799
- }
800
- };
801
- const completion = new Promise((resolve, reject) => {
802
- resolveCompletion = resolve;
803
- rejectCompletion = reject;
804
- });
805
- const unsubscribe = this.subscribe((event) => {
806
- if (!turnId) {
807
- bufferedEvents.push(event);
808
- return;
809
- }
810
- processEvent(event);
811
- });
812
- try {
813
- const result = await this.startTurn(prompt, options);
814
- turnId = result.turnId;
815
- for (const event of bufferedEvents) {
816
- processEvent(event);
817
- }
818
- if (!settled) {
819
- await completion;
820
- }
821
- }
822
- finally {
823
- unsubscribe();
824
- }
825
- return {
826
- sessionId: this.sessionId,
827
- finalText,
828
- usage,
829
- timeline,
830
- };
831
- }
832
- async interrupt() {
833
- this.abortController?.abort();
834
- await this.client.session.abort({
835
- sessionID: this.sessionId,
836
- directory: this.config.cwd,
837
- });
838
- }
839
- async startTurn(prompt, options) {
840
- if (this.activeForegroundTurnId) {
841
- throw new Error("A foreground turn is already active");
842
- }
843
- this.abortController = new AbortController();
844
- await this.ensureMcpServersConfigured();
845
- const parts = this.buildPromptParts(prompt);
846
- const model = this.parseModel(this.config.model);
847
- const thinkingOptionId = this.config.thinkingOptionId;
848
- const effectiveVariant = thinkingOptionId && thinkingOptionId !== "default" ? thinkingOptionId : undefined;
849
- const effectiveMode = normalizeOpenCodeModeId(this.currentMode);
850
- const promptResponse = await this.client.session.promptAsync({
851
- sessionID: this.sessionId,
852
- directory: this.config.cwd,
853
- parts,
854
- ...(options?.outputSchema
855
- ? {
856
- format: {
857
- type: "json_schema",
858
- schema: options.outputSchema,
859
- },
860
- }
861
- : {}),
862
- ...(this.config.systemPrompt ? { system: this.config.systemPrompt } : {}),
863
- ...(model ? { model } : {}),
864
- ...(effectiveMode ? { agent: effectiveMode } : {}),
865
- ...(effectiveVariant ? { variant: effectiveVariant } : {}),
866
- });
867
- if (promptResponse.error) {
868
- const errorMsg = JSON.stringify(promptResponse.error);
869
- this.notifySubscribers({
870
- type: "turn_failed",
871
- provider: "opencode",
872
- error: errorMsg,
873
- });
874
- throw new Error(errorMsg);
875
- }
876
- const turnId = this.createTurnId();
877
- this.activeForegroundTurnId = turnId;
878
- void this.consumeEventStream();
879
- return { turnId };
880
- }
881
- subscribe(callback) {
882
- this.subscribers.add(callback);
883
- return () => {
884
- this.subscribers.delete(callback);
885
- };
886
- }
887
- async consumeEventStream() {
888
- const eventsResult = await this.client.event.subscribe({
889
- directory: this.config.cwd,
890
- });
891
- try {
892
- for await (const event of eventsResult.stream) {
893
- if (this.abortController?.signal.aborted) {
894
- break;
895
- }
896
- const translated = this.translateEvent(event);
897
- for (const e of translated) {
898
- this.notifySubscribers(e);
899
- if (e.type === "turn_completed" || e.type === "turn_failed") {
900
- this.activeForegroundTurnId = null;
901
- return;
902
- }
903
- }
904
- }
905
- }
906
- catch (error) {
907
- if (!this.abortController?.signal.aborted) {
908
- this.notifySubscribers({
909
- type: "turn_failed",
910
- provider: "opencode",
911
- error: error instanceof Error ? error.message : "Stream error",
912
- });
913
- this.activeForegroundTurnId = null;
914
- }
915
- }
916
- }
917
- notifySubscribers(event) {
918
- const turnId = this.activeForegroundTurnId;
919
- const tagged = turnId ? { ...event, turnId } : event;
920
- for (const callback of this.subscribers) {
921
- try {
922
- callback(tagged);
923
- }
924
- catch {
925
- // Subscriber callback error isolation
926
- }
927
- }
928
- }
929
- createTurnId() {
930
- return `opencode-turn-${this.nextTurnOrdinal++}`;
931
- }
932
- async *streamHistory() {
933
- const response = await this.client.session.messages({
934
- sessionID: this.sessionId,
935
- directory: this.config.cwd,
936
- });
937
- if (response.error || !response.data) {
938
- return;
939
- }
940
- const messages = response.data;
941
- for (const message of messages) {
942
- const { info, parts } = message;
943
- const role = info.role;
944
- if (role === "user") {
945
- // Extract user message text from parts
946
- const textParts = parts.filter((p) => p.type === "text");
947
- const text = textParts.map((p) => p.text ?? "").join("");
948
- if (text) {
949
- yield {
950
- type: "timeline",
951
- provider: "opencode",
952
- item: { type: "user_message", text },
953
- };
954
- }
955
- }
956
- else if (role === "assistant") {
957
- let emittedAssistantText = false;
958
- // Process each part
959
- for (const part of parts) {
960
- const partType = part.type;
961
- if (partType === "text") {
962
- const text = part.text;
963
- if (text) {
964
- emittedAssistantText = true;
965
- yield {
966
- type: "timeline",
967
- provider: "opencode",
968
- item: { type: "assistant_message", text },
969
- };
970
- }
971
- }
972
- else if (partType === "reasoning") {
973
- const text = part.text;
974
- if (text) {
975
- yield {
976
- type: "timeline",
977
- provider: "opencode",
978
- item: { type: "reasoning", text },
979
- };
980
- }
981
- }
982
- else if (partType === "tool") {
983
- const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
984
- if (parsedToolPart.success) {
985
- if (parsedToolPart.data) {
986
- yield {
987
- type: "timeline",
988
- provider: "opencode",
989
- item: parsedToolPart.data,
990
- };
991
- }
992
- }
993
- }
994
- }
995
- if (!emittedAssistantText) {
996
- const text = stringifyStructuredAssistantMessage(info.structured);
997
- if (text) {
998
- yield {
999
- type: "timeline",
1000
- provider: "opencode",
1001
- item: { type: "assistant_message", text },
1002
- };
1003
- }
1004
- }
1005
- }
1006
- }
1007
- }
1008
- async getAvailableModes() {
1009
- if (this.availableModesCache) {
1010
- return this.availableModesCache;
1011
- }
1012
- const response = await this.client.app.agents({
1013
- directory: this.config.cwd,
1014
- });
1015
- const discoveredModes = response.error || !response.data
1016
- ? []
1017
- : response.data
1018
- .filter((agent) => agent.mode === "primary" && agent.hidden !== true)
1019
- .filter((agent) => OPENCODE_MODE_IDS.has(agent.name))
1020
- .map((agent) => ({
1021
- id: agent.name,
1022
- label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1),
1023
- description: typeof agent.description === "string" && agent.description.trim().length > 0
1024
- ? agent.description.trim()
1025
- : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description,
1026
- }));
1027
- this.availableModesCache =
1028
- discoveredModes.length > 0 ? sortOpenCodeModes(discoveredModes) : DEFAULT_MODES;
1029
- return this.availableModesCache;
1030
- }
1031
- async getCurrentMode() {
1032
- return this.currentMode;
1033
- }
1034
- async setMode(modeId) {
1035
- this.currentMode = normalizeOpenCodeModeId(modeId);
1036
- }
1037
- getPendingPermissions() {
1038
- return Array.from(this.pendingPermissions.values());
1039
- }
1040
- async respondToPermission(requestId, response) {
1041
- const pending = this.pendingPermissions.get(requestId);
1042
- if (!pending) {
1043
- throw new Error(`No pending permission request with id '${requestId}'`);
1044
- }
1045
- if (pending.kind === "question") {
1046
- if (response.behavior === "deny") {
1047
- await this.client.question.reject({
1048
- requestID: requestId,
1049
- directory: this.config.cwd,
1050
- });
1051
- }
1052
- else {
1053
- const answersRecord = readOpenCodeRecord(response.updatedInput?.answers);
1054
- const questions = Array.isArray(pending.input?.questions) ? pending.input.questions : [];
1055
- const answers = questions.map((item) => {
1056
- const header = readNonEmptyString(readOpenCodeRecord(item)?.header);
1057
- const rawAnswer = header ? readNonEmptyString(answersRecord?.[header]) : null;
1058
- if (!rawAnswer) {
1059
- return [];
1060
- }
1061
- return rawAnswer
1062
- .split(",")
1063
- .map((entry) => entry.trim())
1064
- .filter((entry) => entry.length > 0);
1065
- });
1066
- await this.client.question.reply({
1067
- requestID: requestId,
1068
- directory: this.config.cwd,
1069
- answers,
1070
- });
1071
- }
1072
- this.pendingPermissions.delete(requestId);
1073
- return;
1074
- }
1075
- const reply = response.behavior === "allow" ? "once" : "reject";
1076
- await this.client.permission.reply({
1077
- requestID: requestId,
1078
- directory: this.config.cwd,
1079
- reply,
1080
- message: response.behavior === "deny" ? response.message : undefined,
1081
- });
1082
- this.pendingPermissions.delete(requestId);
1083
- }
1084
- describePersistence() {
1085
- return {
1086
- provider: "opencode",
1087
- sessionId: this.sessionId,
1088
- nativeHandle: this.sessionId,
1089
- metadata: {
1090
- cwd: this.config.cwd,
1091
- },
1092
- };
1093
- }
1094
- async close() {
1095
- this.abortController?.abort();
1096
- this.subscribers.clear();
1097
- this.activeForegroundTurnId = null;
1098
- }
1099
- buildPromptParts(prompt) {
1100
- if (typeof prompt === "string") {
1101
- return [{ type: "text", text: prompt }];
1102
- }
1103
- return prompt
1104
- .filter((p) => p.type === "text")
1105
- .map((p) => ({ type: "text", text: p.text }));
1106
- }
1107
- parseModel(model) {
1108
- if (!model) {
1109
- return undefined;
1110
- }
1111
- const parts = model.split("/");
1112
- if (parts.length >= 2) {
1113
- return { providerID: parts[0], modelID: parts.slice(1).join("/") };
1114
- }
1115
- return { providerID: "opencode", modelID: model };
1116
- }
1117
- async ensureMcpServersConfigured() {
1118
- if (this.mcpConfigured) {
1119
- return;
1120
- }
1121
- const mcpServers = this.config.mcpServers;
1122
- if (!mcpServers || Object.keys(mcpServers).length === 0) {
1123
- this.mcpConfigured = true;
1124
- return;
1125
- }
1126
- if (!this.mcpSetupPromise) {
1127
- this.mcpSetupPromise = this.configureMcpServers(mcpServers);
1128
- }
1129
- try {
1130
- await this.mcpSetupPromise;
1131
- this.mcpConfigured = true;
1132
- }
1133
- catch (error) {
1134
- this.mcpSetupPromise = null;
1135
- throw error;
1136
- }
1137
- }
1138
- async configureMcpServers(mcpServers) {
1139
- for (const [name, serverConfig] of Object.entries(mcpServers)) {
1140
- const mappedConfig = toOpenCodeMcpConfig(serverConfig);
1141
- await this.registerMcpServer(name, mappedConfig);
1142
- }
1143
- }
1144
- async registerMcpServer(name, config) {
1145
- await this.runMcpOperation("add", name, () => this.client.mcp.add({
1146
- directory: this.config.cwd,
1147
- name,
1148
- config,
1149
- }));
1150
- await this.runMcpOperation("connect", name, () => this.client.mcp.connect({
1151
- directory: this.config.cwd,
1152
- name,
1153
- }));
1154
- }
1155
- async runMcpOperation(operation, name, run) {
1156
- const response = await run();
1157
- const error = response.error;
1158
- if (!error) {
1159
- return;
1160
- }
1161
- if (isAlreadyPresentMcpError(error)) {
1162
- return;
1163
- }
1164
- throw new Error(`Failed to ${operation} OpenCode MCP server '${name}': ${stringifyUnknownError(error)}`);
1165
- }
1166
- translateEvent(event) {
1167
- const translated = translateOpenCodeEvent(event, {
1168
- sessionId: this.sessionId,
1169
- messageRoles: this.messageRoles,
1170
- accumulatedUsage: this.accumulatedUsage,
1171
- streamedPartKeys: this.streamedPartKeys,
1172
- emittedStructuredMessageIds: this.emittedStructuredMessageIds,
1173
- });
1174
- for (const translatedEvent of translated) {
1175
- if (translatedEvent.type === "permission_requested") {
1176
- this.pendingPermissions.set(translatedEvent.request.id, translatedEvent.request);
1177
- }
1178
- if (translatedEvent.type === "turn_completed") {
1179
- translatedEvent.usage = this.extractAndResetUsage();
1180
- }
1181
- }
1182
- return translated;
1183
- }
1184
- extractAndResetUsage() {
1185
- const usage = this.accumulatedUsage;
1186
- this.accumulatedUsage = {};
1187
- if (!usage.inputTokens && !usage.outputTokens && !usage.totalCostUsd) {
1188
- return undefined;
1189
- }
1190
- return usage;
1191
- }
1192
- }
1193
- //# sourceMappingURL=opencode-agent.js.map