@ilya-lesikov/pi-pi 0.1.0

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 (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,546 @@
1
+ /**
2
+ * OpenCode provider — bridges Plannotator's AI layer with OpenCode's agent server.
3
+ *
4
+ * Uses @opencode-ai/sdk to connect to an existing `opencode serve` first and
5
+ * only spawns a new server when nothing is reachable. One server is shared
6
+ * across all sessions. The user must have the `opencode` CLI installed and
7
+ * authenticated.
8
+ */
9
+
10
+ import type { OpencodeClient } from "@opencode-ai/sdk";
11
+ import { BaseSession } from "../base-session.ts";
12
+ import { buildSystemPrompt } from "../context.ts";
13
+ import type {
14
+ AIMessage,
15
+ AIProvider,
16
+ AIProviderCapabilities,
17
+ AISession,
18
+ CreateSessionOptions,
19
+ OpenCodeConfig,
20
+ } from "../types.ts";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Constants
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const PROVIDER_NAME = "opencode-sdk";
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // SDK import cache — resolve once, reuse across all sessions
30
+ // ---------------------------------------------------------------------------
31
+
32
+ // biome-ignore lint/suspicious/noExplicitAny: SDK types not available at compile time
33
+ let sdk: any = null;
34
+
35
+ async function getSDK() {
36
+ if (!sdk) {
37
+ sdk = await import("@opencode-ai/sdk");
38
+ }
39
+ return sdk;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Provider
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export class OpenCodeProvider implements AIProvider {
47
+ readonly name = PROVIDER_NAME;
48
+ readonly capabilities: AIProviderCapabilities = {
49
+ fork: true,
50
+ resume: true,
51
+ streaming: true,
52
+ tools: true,
53
+ };
54
+ models?: Array<{ id: string; label: string; default?: boolean }>;
55
+
56
+ private config: OpenCodeConfig;
57
+ // biome-ignore lint/suspicious/noExplicitAny: SDK types not available at compile time
58
+ private server: { url: string; close: () => void } | null = null;
59
+ private client: OpencodeClient | null = null;
60
+ private startPromise: Promise<void> | null = null;
61
+ private lastAttachError: string | null = null;
62
+
63
+ constructor(config: OpenCodeConfig) {
64
+ this.config = config;
65
+ }
66
+
67
+ /** Attach to an existing OpenCode server or spawn one if needed. */
68
+ async ensureServer(): Promise<void> {
69
+ if (this.client) return;
70
+ this.startPromise ??= this.doStart().catch((err) => {
71
+ this.startPromise = null;
72
+ throw err;
73
+ });
74
+ return this.startPromise;
75
+ }
76
+
77
+ private async doStart(): Promise<void> {
78
+ this.lastAttachError = null;
79
+ const { createOpencodeServer, createOpencodeClient } = await getSDK();
80
+ const attachedClient = await this.tryAttachExistingServer(createOpencodeClient);
81
+ if (attachedClient) {
82
+ this.client = attachedClient;
83
+ return;
84
+ }
85
+
86
+ try {
87
+ this.server = await createOpencodeServer({
88
+ hostname: this.config.hostname ?? "127.0.0.1",
89
+ ...(this.config.port != null && { port: this.config.port }),
90
+ timeout: 15_000,
91
+ });
92
+ } catch (err) {
93
+ const spawnMessage = err instanceof Error ? err.message : String(err);
94
+ if (this.lastAttachError) {
95
+ throw new Error(`${this.lastAttachError}\nFallback startup also failed: ${spawnMessage}`);
96
+ }
97
+ throw err;
98
+ }
99
+
100
+ this.client = createOpencodeClient({
101
+ baseUrl: this.server!.url,
102
+ directory: this.config.cwd ?? process.cwd(),
103
+ });
104
+ }
105
+
106
+ private async tryAttachExistingServer(
107
+ createOpencodeClient: (config?: { baseUrl?: string; directory?: string }) => OpencodeClient,
108
+ ): Promise<OpencodeClient | null> {
109
+ const cwd = this.config.cwd ?? process.cwd();
110
+ const baseUrl = `http://${this.config.hostname ?? "127.0.0.1"}:${this.config.port ?? 4096}`;
111
+ const client = createOpencodeClient({
112
+ baseUrl,
113
+ directory: cwd,
114
+ });
115
+
116
+ try {
117
+ await client.config.get({
118
+ throwOnError: true,
119
+ signal: AbortSignal.timeout(1_000),
120
+ });
121
+ return client;
122
+ } catch (err) {
123
+ const message = err instanceof Error ? err.message : String(err);
124
+ this.lastAttachError = `Failed to attach to existing OpenCode server at ${baseUrl}: ${message}`;
125
+ return null;
126
+ }
127
+ }
128
+
129
+ private getClient(): OpencodeClient {
130
+ if (!this.client) {
131
+ throw new Error("OpenCode client is not initialized.");
132
+ }
133
+ return this.client;
134
+ }
135
+
136
+ async createSession(options: CreateSessionOptions): Promise<AISession> {
137
+ await this.ensureServer();
138
+ const client = this.getClient();
139
+
140
+ const result = await client.session.create({
141
+ query: { directory: options.cwd ?? this.config.cwd ?? process.cwd() },
142
+ });
143
+ const sessionData = result.data;
144
+ if (!sessionData) {
145
+ throw new Error("OpenCode did not return session data.");
146
+ }
147
+
148
+ const session = new OpenCodeSession({
149
+ sessionId: sessionData.id,
150
+ systemPrompt: buildSystemPrompt(options.context),
151
+ client,
152
+ model: options.model,
153
+ parentSessionId: null,
154
+ });
155
+ return session;
156
+ }
157
+
158
+ async forkSession(options: CreateSessionOptions): Promise<AISession> {
159
+ await this.ensureServer();
160
+ const client = this.getClient();
161
+
162
+ const parentId = options.context.parent?.sessionId;
163
+ if (!parentId) {
164
+ throw new Error("Fork requires a parent session ID.");
165
+ }
166
+
167
+ const result = await client.session.fork({
168
+ path: { id: parentId },
169
+ });
170
+ const sessionData = result.data;
171
+ if (!sessionData) {
172
+ throw new Error("OpenCode did not return forked session data.");
173
+ }
174
+
175
+ return new OpenCodeSession({
176
+ sessionId: sessionData.id,
177
+ systemPrompt: buildSystemPrompt(options.context),
178
+ client,
179
+ model: options.model,
180
+ parentSessionId: parentId,
181
+ });
182
+ }
183
+
184
+ async resumeSession(sessionId: string): Promise<AISession> {
185
+ await this.ensureServer();
186
+ const client = this.getClient();
187
+
188
+ // Verify session exists
189
+ await client.session.get({ path: { id: sessionId } });
190
+
191
+ return new OpenCodeSession({
192
+ sessionId,
193
+ systemPrompt: null,
194
+ client,
195
+ model: undefined,
196
+ parentSessionId: null,
197
+ });
198
+ }
199
+
200
+ dispose(): void {
201
+ if (this.server) {
202
+ this.server.close();
203
+ this.server = null;
204
+ }
205
+ this.client = null;
206
+ this.startPromise = null;
207
+ }
208
+
209
+ /** Fetch available models from OpenCode. Call before registering the provider. */
210
+ async fetchModels(): Promise<void> {
211
+ try {
212
+ await this.ensureServer();
213
+ const client = this.getClient();
214
+
215
+ const result = await client.provider.list({
216
+ query: { directory: this.config.cwd ?? process.cwd() },
217
+ });
218
+ const data = result.data;
219
+ if (!data) {
220
+ return;
221
+ }
222
+ const connected = new Set(data.connected ?? []);
223
+ const allProviders = data.all ?? [];
224
+
225
+ const models: Array<{ id: string; label: string; default?: boolean }> = [];
226
+ for (const provider of allProviders) {
227
+ if (!connected.has(provider.id)) continue;
228
+ for (const model of Object.values(provider.models)) {
229
+ models.push({
230
+ id: `${provider.id}/${model.id}`,
231
+ label: model.name ?? model.id,
232
+ });
233
+ }
234
+ }
235
+
236
+ if (models.length > 0) {
237
+ // Mark first model as default
238
+ models[0].default = true;
239
+ this.models = models;
240
+ }
241
+ } catch {
242
+ // OpenCode not configured or no models available
243
+ }
244
+ }
245
+ }
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Session
249
+ // ---------------------------------------------------------------------------
250
+
251
+ interface SessionConfig {
252
+ sessionId: string;
253
+ systemPrompt: string | null;
254
+ // biome-ignore lint/suspicious/noExplicitAny: SDK types not available at compile time
255
+ client: any;
256
+ /** Model in "providerID/modelID" format. */
257
+ model?: string;
258
+ parentSessionId: string | null;
259
+ }
260
+
261
+ class OpenCodeSession extends BaseSession {
262
+ private config: SessionConfig;
263
+
264
+ constructor(config: SessionConfig) {
265
+ super({
266
+ parentSessionId: config.parentSessionId,
267
+ initialId: config.sessionId,
268
+ });
269
+ this.config = config;
270
+ this._resolvedId = config.sessionId;
271
+ }
272
+
273
+ async *query(prompt: string): AsyncIterable<AIMessage> {
274
+ const started = this.startQuery();
275
+ if (!started) {
276
+ yield BaseSession.BUSY_ERROR;
277
+ return;
278
+ }
279
+ const { gen } = started;
280
+
281
+ try {
282
+ // Build model param if specified
283
+ let modelParam: { providerID: string; modelID: string } | undefined;
284
+ if (this.config.model) {
285
+ const [providerID, ...rest] = this.config.model.split("/");
286
+ const modelID = rest.join("/");
287
+ if (providerID && modelID) {
288
+ modelParam = { providerID, modelID };
289
+ }
290
+ }
291
+
292
+ // Subscribe to SSE events
293
+ const { stream } = await this.config.client.event.subscribe();
294
+
295
+ try {
296
+ // Send prompt asynchronously
297
+ try {
298
+ await this.config.client.session.promptAsync({
299
+ path: { id: this.config.sessionId },
300
+ body: {
301
+ ...(!this._firstQuerySent &&
302
+ this.config.systemPrompt && {
303
+ system: this.config.systemPrompt,
304
+ }),
305
+ ...(modelParam && { model: modelParam }),
306
+ parts: [{ type: "text", text: prompt }],
307
+ },
308
+ });
309
+ } catch (err) {
310
+ yield {
311
+ type: "error",
312
+ error: `OpenCode rejected prompt: ${err instanceof Error ? err.message : String(err)}`,
313
+ code: "opencode_prompt_rejected",
314
+ };
315
+ return;
316
+ }
317
+ this._firstQuerySent = true;
318
+
319
+ // Drain SSE events filtered by session ID
320
+ for await (const event of stream) {
321
+ const eventType = event.type as string;
322
+ const props = event.properties as Record<string, unknown> | undefined;
323
+ if (!props) continue;
324
+
325
+ // Filter: only events for our session
326
+ const eventSessionId =
327
+ (props.sessionID as string) ??
328
+ ((props.info as Record<string, unknown>)?.sessionID as string) ??
329
+ ((props.part as Record<string, unknown>)?.sessionID as string);
330
+ if (eventSessionId && eventSessionId !== this.config.sessionId) continue;
331
+
332
+ const mapped = mapOpenCodeEvent(eventType, props, this.id);
333
+ for (const msg of mapped) {
334
+ yield msg;
335
+ if (msg.type === "result" || (msg.type === "error" && isTerminalEvent(eventType))) {
336
+ return;
337
+ }
338
+ }
339
+ }
340
+ } finally {
341
+ stream.return?.();
342
+ }
343
+ } catch (err) {
344
+ yield {
345
+ type: "error",
346
+ error: err instanceof Error ? err.message : String(err),
347
+ code: "provider_error",
348
+ };
349
+ } finally {
350
+ this.endQuery(gen);
351
+ }
352
+ }
353
+
354
+ abort(): void {
355
+ this.config.client.session
356
+ .abort({ path: { id: this.config.sessionId } })
357
+ .catch(() => {});
358
+ super.abort();
359
+ }
360
+
361
+ respondToPermission(
362
+ requestId: string,
363
+ allow: boolean,
364
+ _message?: string,
365
+ ): void {
366
+ this.config.client
367
+ .postSessionIdPermissionsPermissionId({
368
+ path: { id: this.config.sessionId, permissionID: requestId },
369
+ body: { response: allow ? "once" : "reject" },
370
+ })
371
+ .catch(() => {});
372
+ }
373
+ }
374
+
375
+ // ---------------------------------------------------------------------------
376
+ // Event mapping
377
+ // ---------------------------------------------------------------------------
378
+
379
+ /** Returns true for events that should terminate the query when mapped to an error. */
380
+ function isTerminalEvent(eventType: string): boolean {
381
+ return eventType === "session.error" || eventType === "session.status";
382
+ }
383
+
384
+ /**
385
+ * Map an OpenCode SSE event to AIMessage[].
386
+ *
387
+ * Key events:
388
+ * message.part.delta → text_delta (streaming text)
389
+ * message.part.updated → tool_use / tool_result (tool lifecycle)
390
+ * permission.updated → permission_request
391
+ * session.status → result (when idle)
392
+ * message.updated → error (when message has error)
393
+ */
394
+ export function mapOpenCodeEvent(
395
+ eventType: string,
396
+ props: Record<string, unknown>,
397
+ sessionId: string,
398
+ ): AIMessage[] {
399
+ switch (eventType) {
400
+ case "message.part.delta": {
401
+ const field = props.field as string;
402
+ const delta = props.delta as string;
403
+ if (field === "text" && delta) {
404
+ return [{ type: "text_delta", delta }];
405
+ }
406
+ return [];
407
+ }
408
+
409
+ case "message.part.updated": {
410
+ const part = props.part as Record<string, unknown>;
411
+ if (!part) return [];
412
+
413
+ const partType = part.type as string;
414
+
415
+ if (partType === "tool") {
416
+ const state = part.state as Record<string, unknown>;
417
+ if (!state) return [];
418
+
419
+ const status = state.status as string;
420
+ const callID = (part.callID as string) ?? (part.id as string);
421
+ const toolName = part.tool as string;
422
+
423
+ switch (status) {
424
+ case "running":
425
+ return [
426
+ {
427
+ type: "tool_use",
428
+ toolName: toolName ?? "unknown",
429
+ toolInput: (state.input as Record<string, unknown>) ?? {},
430
+ toolUseId: callID,
431
+ },
432
+ ];
433
+
434
+ case "completed": {
435
+ const output = (state.output as string) ?? "";
436
+ return [
437
+ {
438
+ type: "tool_result",
439
+ toolUseId: callID,
440
+ result: output,
441
+ },
442
+ ];
443
+ }
444
+
445
+ case "error": {
446
+ const error = (state.error as string) ?? "Tool execution failed";
447
+ return [
448
+ {
449
+ type: "tool_result",
450
+ toolUseId: callID,
451
+ result: `[Error] ${error}`,
452
+ },
453
+ ];
454
+ }
455
+
456
+ default:
457
+ return [];
458
+ }
459
+ }
460
+
461
+ return [];
462
+ }
463
+
464
+ case "permission.updated": {
465
+ const id = props.id as string;
466
+ const permType = props.type as string;
467
+ const title = props.title as string;
468
+ const callID = props.callID as string;
469
+ const metadata = (props.metadata as Record<string, unknown>) ?? {};
470
+
471
+ return [
472
+ {
473
+ type: "permission_request",
474
+ requestId: id,
475
+ toolName: permType ?? "unknown",
476
+ toolInput: metadata,
477
+ title: title ?? permType,
478
+ toolUseId: callID ?? id,
479
+ },
480
+ ];
481
+ }
482
+
483
+ case "session.status": {
484
+ const status = props.status as Record<string, unknown>;
485
+ if (status?.type === "idle") {
486
+ return [
487
+ {
488
+ type: "result",
489
+ sessionId,
490
+ success: true,
491
+ },
492
+ ];
493
+ }
494
+ return [];
495
+ }
496
+
497
+ case "session.error": {
498
+ const error = props.error as Record<string, unknown>;
499
+ const message =
500
+ (error?.message as string) ?? (props.message as string) ?? "Session error";
501
+ return [
502
+ {
503
+ type: "error",
504
+ error: message,
505
+ code: "opencode_session_error",
506
+ },
507
+ ];
508
+ }
509
+
510
+ case "message.updated": {
511
+ const info = props.info as Record<string, unknown>;
512
+ if (!info) return [];
513
+
514
+ const msgError = info.error as Record<string, unknown>;
515
+ if (msgError) {
516
+ const errorData = msgError.data as Record<string, unknown>;
517
+ const message =
518
+ (errorData?.message as string) ??
519
+ (msgError.name as string) ??
520
+ "Message error";
521
+ return [
522
+ {
523
+ type: "error",
524
+ error: message,
525
+ code: "opencode_message_error",
526
+ },
527
+ ];
528
+ }
529
+ return [];
530
+ }
531
+
532
+ default:
533
+ return [];
534
+ }
535
+ }
536
+
537
+ // ---------------------------------------------------------------------------
538
+ // Factory registration
539
+ // ---------------------------------------------------------------------------
540
+
541
+ import { registerProviderFactory } from "../provider.ts";
542
+
543
+ registerProviderFactory(
544
+ PROVIDER_NAME,
545
+ async (config) => new OpenCodeProvider(config as OpenCodeConfig),
546
+ );
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Pi event mapping — shared between Bun and Node.js Pi providers.
3
+ *
4
+ * Pure function, no runtime-specific dependencies.
5
+ */
6
+
7
+ import type { AIMessage } from "../types.ts";
8
+
9
+ /**
10
+ * Map a Pi AgentEvent (received as JSONL) to AIMessage[].
11
+ *
12
+ * Pi event hierarchy:
13
+ * agent_start > turn_start > message_start > message_update* > message_end
14
+ * > tool_execution_start > tool_execution_end > turn_end > agent_end
15
+ *
16
+ * We extract:
17
+ * - text_delta from message_update.assistantMessageEvent
18
+ * - tool_use from toolcall_end
19
+ * - tool_result from tool_execution_end
20
+ * - result from agent_end
21
+ */
22
+ export function mapPiEvent(
23
+ event: Record<string, unknown>,
24
+ sessionId: string,
25
+ ): AIMessage[] {
26
+ const eventType = event.type as string;
27
+
28
+ switch (eventType) {
29
+ case "message_update": {
30
+ const ame = event.assistantMessageEvent as
31
+ | Record<string, unknown>
32
+ | undefined;
33
+ if (!ame) return [];
34
+
35
+ const subType = ame.type as string;
36
+
37
+ switch (subType) {
38
+ case "text_delta":
39
+ return [{ type: "text_delta", delta: ame.delta as string }];
40
+
41
+ case "toolcall_end": {
42
+ const tc = ame.toolCall as Record<string, unknown>;
43
+ if (!tc) return [];
44
+ return [
45
+ {
46
+ type: "tool_use",
47
+ toolName: tc.name as string,
48
+ toolInput: (tc.arguments as Record<string, unknown>) ?? {},
49
+ toolUseId: tc.id as string,
50
+ },
51
+ ];
52
+ }
53
+
54
+ case "error": {
55
+ const partial = ame.error as Record<string, unknown> | undefined;
56
+ const errorMessage =
57
+ (partial?.errorMessage as string) ?? "Stream error";
58
+ return [
59
+ { type: "error", error: errorMessage, code: "pi_stream_error" },
60
+ ];
61
+ }
62
+
63
+ default:
64
+ return [];
65
+ }
66
+ }
67
+
68
+ case "tool_execution_end": {
69
+ const result = event.result;
70
+ const isError = event.isError as boolean;
71
+ const resultStr =
72
+ result == null
73
+ ? ""
74
+ : typeof result === "string"
75
+ ? result
76
+ : JSON.stringify(result);
77
+
78
+ return [
79
+ {
80
+ type: "tool_result",
81
+ toolUseId: event.toolCallId as string,
82
+ result: isError
83
+ ? `[Error] ${resultStr || "Tool execution failed"}`
84
+ : resultStr,
85
+ },
86
+ ];
87
+ }
88
+
89
+ case "agent_end":
90
+ return [
91
+ {
92
+ type: "result",
93
+ sessionId,
94
+ success: true,
95
+ },
96
+ ];
97
+
98
+ case "process_exited":
99
+ return [
100
+ {
101
+ type: "error",
102
+ error: "Pi process exited unexpectedly.",
103
+ code: "pi_process_exit",
104
+ },
105
+ ];
106
+
107
+ default:
108
+ return [];
109
+ }
110
+ }