@seawork/server 1.0.4 → 1.0.10-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/server/client/daemon-client.d.ts +32 -1
  2. package/dist/server/client/daemon-client.d.ts.map +1 -1
  3. package/dist/server/client/daemon-client.js +69 -0
  4. package/dist/server/client/daemon-client.js.map +1 -1
  5. package/dist/server/server/agent/agent-management-mcp.d.ts.map +1 -1
  6. package/dist/server/server/agent/agent-management-mcp.js +6 -5
  7. package/dist/server/server/agent/agent-management-mcp.js.map +1 -1
  8. package/dist/server/server/agent/agent-response-loop.d.ts.map +1 -1
  9. package/dist/server/server/agent/agent-response-loop.js +0 -1
  10. package/dist/server/server/agent/agent-response-loop.js.map +1 -1
  11. package/dist/server/server/agent/agent-storage.d.ts +68 -68
  12. package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
  13. package/dist/server/server/agent/mcp-server.js +6 -5
  14. package/dist/server/server/agent/mcp-server.js.map +1 -1
  15. package/dist/server/server/agent/mcp-shared.d.ts +30 -30
  16. package/dist/server/server/agent/provider-manifest.d.ts +2 -1
  17. package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
  18. package/dist/server/server/agent/provider-manifest.js +8 -69
  19. package/dist/server/server/agent/provider-manifest.js.map +1 -1
  20. package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
  21. package/dist/server/server/agent/provider-registry.js +2 -10
  22. package/dist/server/server/agent/provider-registry.js.map +1 -1
  23. package/dist/server/server/agent/providers/claude/claude-models.d.ts +1 -1
  24. package/dist/server/server/agent/providers/claude/claude-models.d.ts.map +1 -1
  25. package/dist/server/server/agent/providers/claude/claude-models.js +4 -39
  26. package/dist/server/server/agent/providers/claude/claude-models.js.map +1 -1
  27. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
  28. package/dist/server/server/agent/providers/claude-agent.d.ts +2 -1
  29. package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
  30. package/dist/server/server/agent/providers/claude-agent.js +175 -46
  31. package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
  32. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
  33. package/dist/server/server/agent/providers/codex-app-server-agent.js +128 -72
  34. package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
  35. package/dist/server/server/agent/providers/codex-rollout-timeline.d.ts.map +1 -1
  36. package/dist/server/server/agent/providers/codex-rollout-timeline.js +76 -0
  37. package/dist/server/server/agent/providers/codex-rollout-timeline.js.map +1 -1
  38. package/dist/server/server/agent/providers/seawork-models.d.ts +4 -0
  39. package/dist/server/server/agent/providers/seawork-models.d.ts.map +1 -0
  40. package/dist/server/server/agent/providers/seawork-models.js +128 -0
  41. package/dist/server/server/agent/providers/seawork-models.js.map +1 -0
  42. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +16 -16
  43. package/dist/server/server/bootstrap.d.ts.map +1 -1
  44. package/dist/server/server/bootstrap.js +35 -1
  45. package/dist/server/server/bootstrap.js.map +1 -1
  46. package/dist/server/server/bug-report-handler.d.ts +9 -0
  47. package/dist/server/server/bug-report-handler.d.ts.map +1 -0
  48. package/dist/server/server/bug-report-handler.js +111 -0
  49. package/dist/server/server/bug-report-handler.js.map +1 -0
  50. package/dist/server/server/bug-report-redact.d.ts +12 -0
  51. package/dist/server/server/bug-report-redact.d.ts.map +1 -0
  52. package/dist/server/server/bug-report-redact.js +63 -0
  53. package/dist/server/server/bug-report-redact.js.map +1 -0
  54. package/dist/server/server/chat/chat-rpc-schemas.d.ts +26 -26
  55. package/dist/server/server/chat/chat-types.d.ts +2 -2
  56. package/dist/server/server/config.d.ts.map +1 -1
  57. package/dist/server/server/config.js +6 -1
  58. package/dist/server/server/config.js.map +1 -1
  59. package/dist/server/server/exports.d.ts +4 -2
  60. package/dist/server/server/exports.d.ts.map +1 -1
  61. package/dist/server/server/exports.js +3 -0
  62. package/dist/server/server/exports.js.map +1 -1
  63. package/dist/server/server/index.js +54 -0
  64. package/dist/server/server/index.js.map +1 -1
  65. package/dist/server/server/loop/rpc-schemas.d.ts +775 -775
  66. package/dist/server/server/loop-service.d.ts +108 -108
  67. package/dist/server/server/persisted-config.d.ts +121 -121
  68. package/dist/server/server/pid-lock.d.ts.map +1 -1
  69. package/dist/server/server/pid-lock.js +35 -2
  70. package/dist/server/server/pid-lock.js.map +1 -1
  71. package/dist/server/server/sac/auth.d.ts +41 -0
  72. package/dist/server/server/sac/auth.d.ts.map +1 -0
  73. package/dist/server/server/sac/auth.js +103 -0
  74. package/dist/server/server/sac/auth.js.map +1 -0
  75. package/dist/server/server/sac/client.d.ts +15 -0
  76. package/dist/server/server/sac/client.d.ts.map +1 -0
  77. package/dist/server/server/sac/client.js +70 -0
  78. package/dist/server/server/sac/client.js.map +1 -0
  79. package/dist/server/server/sac/download.d.ts +10 -0
  80. package/dist/server/server/sac/download.d.ts.map +1 -0
  81. package/dist/server/server/sac/download.js +67 -0
  82. package/dist/server/server/sac/download.js.map +1 -0
  83. package/dist/server/server/sac/endpoints.d.ts +13 -0
  84. package/dist/server/server/sac/endpoints.d.ts.map +1 -0
  85. package/dist/server/server/sac/endpoints.js +16 -0
  86. package/dist/server/server/sac/endpoints.js.map +1 -0
  87. package/dist/server/server/sac/errors.d.ts +40 -0
  88. package/dist/server/server/sac/errors.d.ts.map +1 -0
  89. package/dist/server/server/sac/errors.js +47 -0
  90. package/dist/server/server/sac/errors.js.map +1 -0
  91. package/dist/server/server/sac/generate.d.ts +43 -0
  92. package/dist/server/server/sac/generate.d.ts.map +1 -0
  93. package/dist/server/server/sac/generate.js +206 -0
  94. package/dist/server/server/sac/generate.js.map +1 -0
  95. package/dist/server/server/sac/index.d.ts +22 -0
  96. package/dist/server/server/sac/index.d.ts.map +1 -0
  97. package/dist/server/server/sac/index.js +14 -0
  98. package/dist/server/server/sac/index.js.map +1 -0
  99. package/dist/server/server/sac/output.d.ts +22 -0
  100. package/dist/server/server/sac/output.d.ts.map +1 -0
  101. package/dist/server/server/sac/output.js +142 -0
  102. package/dist/server/server/sac/output.js.map +1 -0
  103. package/dist/server/server/sac/poll.d.ts +12 -0
  104. package/dist/server/server/sac/poll.d.ts.map +1 -0
  105. package/dist/server/server/sac/poll.js +67 -0
  106. package/dist/server/server/sac/poll.js.map +1 -0
  107. package/dist/server/server/sac/providers/alibaba.d.ts +2 -0
  108. package/dist/server/server/sac/providers/alibaba.d.ts.map +1 -0
  109. package/dist/server/server/sac/providers/alibaba.js +506 -0
  110. package/dist/server/server/sac/providers/alibaba.js.map +1 -0
  111. package/dist/server/server/sac/providers/audio.d.ts +2 -0
  112. package/dist/server/server/sac/providers/audio.d.ts.map +1 -0
  113. package/dist/server/server/sac/providers/audio.js +57 -0
  114. package/dist/server/server/sac/providers/audio.js.map +1 -0
  115. package/dist/server/server/sac/providers/index.d.ts +19 -0
  116. package/dist/server/server/sac/providers/index.d.ts.map +1 -0
  117. package/dist/server/server/sac/providers/index.js +32 -0
  118. package/dist/server/server/sac/providers/index.js.map +1 -0
  119. package/dist/server/server/sac/providers/kling.d.ts +2 -0
  120. package/dist/server/server/sac/providers/kling.d.ts.map +1 -0
  121. package/dist/server/server/sac/providers/kling.js +589 -0
  122. package/dist/server/server/sac/providers/kling.js.map +1 -0
  123. package/dist/server/server/sac/providers/nano.d.ts +2 -0
  124. package/dist/server/server/sac/providers/nano.d.ts.map +1 -0
  125. package/dist/server/server/sac/providers/nano.js +37 -0
  126. package/dist/server/server/sac/providers/nano.js.map +1 -0
  127. package/dist/server/server/sac/providers/pixverse.d.ts +2 -0
  128. package/dist/server/server/sac/providers/pixverse.d.ts.map +1 -0
  129. package/dist/server/server/sac/providers/pixverse.js +1017 -0
  130. package/dist/server/server/sac/providers/pixverse.js.map +1 -0
  131. package/dist/server/server/sac/providers/registry.d.ts +34 -0
  132. package/dist/server/server/sac/providers/registry.d.ts.map +1 -0
  133. package/dist/server/server/sac/providers/registry.js +57 -0
  134. package/dist/server/server/sac/providers/registry.js.map +1 -0
  135. package/dist/server/server/sac/providers/seaart.d.ts +4 -0
  136. package/dist/server/server/sac/providers/seaart.d.ts.map +1 -0
  137. package/dist/server/server/sac/providers/seaart.js +57 -0
  138. package/dist/server/server/sac/providers/seaart.js.map +1 -0
  139. package/dist/server/server/sac/providers/store.d.ts +12 -0
  140. package/dist/server/server/sac/providers/store.d.ts.map +1 -0
  141. package/dist/server/server/sac/providers/store.js +7 -0
  142. package/dist/server/server/sac/providers/store.js.map +1 -0
  143. package/dist/server/server/sac/providers/tencent-3d.d.ts +2 -0
  144. package/dist/server/server/sac/providers/tencent-3d.d.ts.map +1 -0
  145. package/dist/server/server/sac/providers/tencent-3d.js +210 -0
  146. package/dist/server/server/sac/providers/tencent-3d.js.map +1 -0
  147. package/dist/server/server/sac/providers/tencent-image.d.ts +2 -0
  148. package/dist/server/server/sac/providers/tencent-image.d.ts.map +1 -0
  149. package/dist/server/server/sac/providers/tencent-image.js +90 -0
  150. package/dist/server/server/sac/providers/tencent-image.js.map +1 -0
  151. package/dist/server/server/sac/providers/tencent-video.d.ts +2 -0
  152. package/dist/server/server/sac/providers/tencent-video.d.ts.map +1 -0
  153. package/dist/server/server/sac/providers/tencent-video.js +103 -0
  154. package/dist/server/server/sac/providers/tencent-video.js.map +1 -0
  155. package/dist/server/server/sac/providers/tripo3d.d.ts +2 -0
  156. package/dist/server/server/sac/providers/tripo3d.d.ts.map +1 -0
  157. package/dist/server/server/sac/providers/tripo3d.js +302 -0
  158. package/dist/server/server/sac/providers/tripo3d.js.map +1 -0
  159. package/dist/server/server/sac/providers/vidu.d.ts +2 -0
  160. package/dist/server/server/sac/providers/vidu.d.ts.map +1 -0
  161. package/dist/server/server/sac/providers/vidu.js +965 -0
  162. package/dist/server/server/sac/providers/vidu.js.map +1 -0
  163. package/dist/server/server/sac/providers/volces-3d.d.ts +2 -0
  164. package/dist/server/server/sac/providers/volces-3d.d.ts.map +1 -0
  165. package/dist/server/server/sac/providers/volces-3d.js +77 -0
  166. package/dist/server/server/sac/providers/volces-3d.js.map +1 -0
  167. package/dist/server/server/sac/providers/volces-video.d.ts +2 -0
  168. package/dist/server/server/sac/providers/volces-video.d.ts.map +1 -0
  169. package/dist/server/server/sac/providers/volces-video.js +392 -0
  170. package/dist/server/server/sac/providers/volces-video.js.map +1 -0
  171. package/dist/server/server/sac/providers/volces.d.ts +2 -0
  172. package/dist/server/server/sac/providers/volces.d.ts.map +1 -0
  173. package/dist/server/server/sac/providers/volces.js +301 -0
  174. package/dist/server/server/sac/providers/volces.js.map +1 -0
  175. package/dist/server/server/sac/types.d.ts +59 -0
  176. package/dist/server/server/sac/types.d.ts.map +1 -0
  177. package/dist/server/server/sac/types.js +5 -0
  178. package/dist/server/server/sac/types.js.map +1 -0
  179. package/dist/server/server/schedule/rpc-schemas.d.ts +493 -493
  180. package/dist/server/server/schedule/types.d.ts +140 -140
  181. package/dist/server/server/session.d.ts +9 -2
  182. package/dist/server/server/session.d.ts.map +1 -1
  183. package/dist/server/server/session.js +137 -19
  184. package/dist/server/server/session.js.map +1 -1
  185. package/dist/server/server/speech/speech-types.d.ts +2 -2
  186. package/dist/server/server/workspace-registry.d.ts +4 -4
  187. package/dist/server/shared/messages.d.ts +17455 -15917
  188. package/dist/server/shared/messages.d.ts.map +1 -1
  189. package/dist/server/shared/messages.js +83 -0
  190. package/dist/server/shared/messages.js.map +1 -1
  191. package/dist/server/utils/spawn.d.ts.map +1 -1
  192. package/dist/server/utils/spawn.js +8 -3
  193. package/dist/server/utils/spawn.js.map +1 -1
  194. package/dist/server/utils/worktree-metadata.d.ts +4 -4
  195. package/dist/src/server/pid-lock.js +35 -2
  196. package/dist/src/server/pid-lock.js.map +1 -1
  197. package/package.json +14 -9
  198. package/dist/server/server/agent/providers/acp-agent.d.ts +0 -202
  199. package/dist/server/server/agent/providers/acp-agent.d.ts.map +0 -1
  200. package/dist/server/server/agent/providers/acp-agent.js +0 -1650
  201. package/dist/server/server/agent/providers/acp-agent.js.map +0 -1
  202. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +0 -16
  203. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +0 -1
  204. package/dist/server/server/agent/providers/copilot-acp-agent.js +0 -95
  205. package/dist/server/server/agent/providers/copilot-acp-agent.js.map +0 -1
  206. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +0 -3
  207. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts.map +0 -1
  208. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +0 -39
  209. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js.map +0 -1
  210. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +0 -13
  211. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts.map +0 -1
  212. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +0 -144
  213. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js.map +0 -1
  214. package/dist/server/server/agent/providers/opencode-agent.d.ts +0 -121
  215. package/dist/server/server/agent/providers/opencode-agent.d.ts.map +0 -1
  216. package/dist/server/server/agent/providers/opencode-agent.js +0 -1649
  217. package/dist/server/server/agent/providers/opencode-agent.js.map +0 -1
  218. package/dist/server/server/agent/providers/pi-acp-agent.d.ts +0 -28
  219. package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +0 -1
  220. package/dist/server/server/agent/providers/pi-acp-agent.js +0 -302
  221. package/dist/server/server/agent/providers/pi-acp-agent.js.map +0 -1
@@ -1,1649 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { createOpencodeClient, } from "@opencode-ai/sdk/v2/client";
3
- import net from "node:net";
4
- import { z } from "zod";
5
- import { applyProviderEnv, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
6
- import { findExecutable } from "../../../utils/executable.js";
7
- import { spawnProcess } from "../../../utils/spawn.js";
8
- import { mapOpencodeToolCall } from "./opencode/tool-call-mapper.js";
9
- import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnosticError, resolveBinaryVersion, toDiagnosticErrorMessage, } from "./diagnostic-utils.js";
10
- const OPENCODE_CAPABILITIES = {
11
- supportsStreaming: true,
12
- supportsSessionPersistence: true,
13
- supportsDynamicModes: true,
14
- supportsMcpServers: true,
15
- supportsReasoningStream: true,
16
- supportsToolInvocations: true,
17
- };
18
- const DEFAULT_MODES = [
19
- {
20
- id: "build",
21
- label: "Build",
22
- description: "Allows edits and tool execution for implementation work",
23
- },
24
- {
25
- id: "plan",
26
- label: "Plan",
27
- description: "Read-only planning mode that avoids file edits",
28
- },
29
- ];
30
- const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"];
31
- const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30000;
32
- const OPENCODE_FATAL_RETRY_MESSAGE_TOKENS = [
33
- "insufficient balance",
34
- "no resource package",
35
- "please recharge",
36
- "invalid api key",
37
- "unauthorized",
38
- "authentication",
39
- "model not found",
40
- "unknown model",
41
- "does not exist",
42
- "unsupported model",
43
- ];
44
- const OpencodeToolStateSchema = z
45
- .object({
46
- status: z.string().optional(),
47
- input: z.unknown().optional(),
48
- output: z.unknown().optional(),
49
- error: z.unknown().optional(),
50
- })
51
- .passthrough();
52
- const OpencodeToolPartBaseSchema = z
53
- .object({
54
- tool: z.string().trim().min(1),
55
- state: OpencodeToolStateSchema.optional(),
56
- })
57
- .passthrough();
58
- const OpencodeToolPartWithCallIdSchema = OpencodeToolPartBaseSchema.extend({
59
- callID: z.string().trim().min(1),
60
- id: z.string().optional(),
61
- }).transform((part) => ({
62
- toolName: part.tool,
63
- callId: part.callID,
64
- status: part.state?.status,
65
- input: part.state?.input,
66
- output: part.state?.output,
67
- error: part.state?.error,
68
- }));
69
- const OpencodeToolPartWithIdSchema = OpencodeToolPartBaseSchema.extend({
70
- id: z.string().trim().min(1),
71
- callID: z.string().optional(),
72
- }).transform((part) => ({
73
- toolName: part.tool,
74
- callId: part.id,
75
- status: part.state?.status,
76
- input: part.state?.input,
77
- output: part.state?.output,
78
- error: part.state?.error,
79
- }));
80
- const OpencodeToolPartWithoutIdSchema = OpencodeToolPartBaseSchema.extend({
81
- id: z.string().optional(),
82
- callID: z.string().optional(),
83
- }).transform((part) => ({
84
- toolName: part.tool,
85
- callId: undefined,
86
- status: part.state?.status,
87
- input: part.state?.input,
88
- output: part.state?.output,
89
- error: part.state?.error,
90
- }));
91
- const OpencodeToolPartSchema = z.union([
92
- OpencodeToolPartWithCallIdSchema,
93
- OpencodeToolPartWithIdSchema,
94
- OpencodeToolPartWithoutIdSchema,
95
- ]);
96
- const OpencodeToolPartTimelineEnvelopeSchema = OpencodeToolPartSchema.transform((part) => ({
97
- toolName: part.toolName,
98
- callId: part.callId,
99
- status: part.status,
100
- input: part.input,
101
- output: part.output,
102
- error: part.error,
103
- }));
104
- const OpencodeToolPartToTimelineItemSchema = OpencodeToolPartTimelineEnvelopeSchema.transform((part) => mapOpencodeToolCall({
105
- toolName: part.toolName,
106
- callId: part.callId,
107
- status: part.status,
108
- input: part.input,
109
- output: part.output,
110
- error: part.error,
111
- }));
112
- async function resolveOpenCodeBinary() {
113
- const found = await findExecutable("opencode");
114
- if (found) {
115
- return found;
116
- }
117
- throw new Error("OpenCode binary not found. Install OpenCode (https://github.com/opencode-ai/opencode) and ensure it is available in your shell PATH.");
118
- }
119
- function toOpenCodeMcpConfig(config) {
120
- if (config.type === "stdio") {
121
- return {
122
- type: "local",
123
- command: [config.command, ...(config.args ?? [])],
124
- ...(config.env ? { environment: config.env } : {}),
125
- enabled: true,
126
- };
127
- }
128
- return {
129
- type: "remote",
130
- url: config.url,
131
- ...(config.headers ? { headers: config.headers } : {}),
132
- enabled: true,
133
- };
134
- }
135
- function stringifyUnknownError(error) {
136
- if (typeof error === "string") {
137
- return error;
138
- }
139
- try {
140
- return JSON.stringify(error);
141
- }
142
- catch {
143
- return String(error);
144
- }
145
- }
146
- function normalizeTurnFailureError(error) {
147
- const normalized = stringifyUnknownError(error).trim();
148
- return normalized.length > 0 ? normalized : "Unknown error";
149
- }
150
- function isFatalOpenCodeRetryMessage(message) {
151
- const normalized = typeof message === "string" ? message.trim().toLowerCase() : "";
152
- if (!normalized) {
153
- return false;
154
- }
155
- return OPENCODE_FATAL_RETRY_MESSAGE_TOKENS.some((token) => normalized.includes(token));
156
- }
157
- function isAlreadyPresentMcpError(error) {
158
- const normalized = stringifyUnknownError(error).toLowerCase();
159
- return MCP_ALREADY_PRESENT_ERROR_TOKENS.some((token) => normalized.includes(token));
160
- }
161
- async function findAvailablePort() {
162
- return new Promise((resolve, reject) => {
163
- const server = net.createServer();
164
- server.listen(0, () => {
165
- const address = server.address();
166
- if (address && typeof address === "object") {
167
- const port = address.port;
168
- server.close(() => resolve(port));
169
- }
170
- else {
171
- server.close(() => reject(new Error("Failed to get port")));
172
- }
173
- });
174
- server.on("error", reject);
175
- });
176
- }
177
- function resolvePartDedupeKey(part, partType) {
178
- if (part.id.trim().length > 0) {
179
- return `${partType}:${part.id}`;
180
- }
181
- if (part.messageID.trim().length > 0) {
182
- return `${partType}:message:${part.messageID}`;
183
- }
184
- return null;
185
- }
186
- function normalizeOpenCodeModeId(modeId) {
187
- const trimmed = typeof modeId === "string" ? modeId.trim() : "";
188
- if (!trimmed || trimmed === "default") {
189
- return "build";
190
- }
191
- return trimmed;
192
- }
193
- function sortOpenCodeModes(modes) {
194
- const order = new Map(DEFAULT_MODES.map((mode, index) => [mode.id, index]));
195
- return [...modes].sort((left, right) => {
196
- const leftOrder = order.get(left.id) ?? Number.MAX_SAFE_INTEGER;
197
- const rightOrder = order.get(right.id) ?? Number.MAX_SAFE_INTEGER;
198
- if (leftOrder !== rightOrder) {
199
- return leftOrder - rightOrder;
200
- }
201
- return left.label.localeCompare(right.label);
202
- });
203
- }
204
- function readPositiveFiniteNumber(value) {
205
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
206
- }
207
- function buildOpenCodeModelLookupKey(providerId, modelId) {
208
- return `${providerId}/${modelId}`;
209
- }
210
- function parseOpenCodeModelLookupKey(modelId) {
211
- if (typeof modelId !== "string" || modelId.trim().length === 0) {
212
- return undefined;
213
- }
214
- const slashIndex = modelId.indexOf("/");
215
- if (slashIndex <= 0 || slashIndex === modelId.length - 1) {
216
- return undefined;
217
- }
218
- const providerId = modelId.slice(0, slashIndex).trim();
219
- const providerModelId = modelId.slice(slashIndex + 1).trim();
220
- if (!providerId || !providerModelId) {
221
- return undefined;
222
- }
223
- return buildOpenCodeModelLookupKey(providerId, providerModelId);
224
- }
225
- function extractOpenCodeModelContextWindow(model) {
226
- if (!model || typeof model !== "object") {
227
- return undefined;
228
- }
229
- const limit = model.limit;
230
- return readPositiveFiniteNumber(limit?.context);
231
- }
232
- function buildOpenCodeModelDefinition(provider, modelId, model) {
233
- const rawVariants = model.variants ? Object.keys(model.variants) : [];
234
- const thinkingOptions = rawVariants.map((id, index) => ({
235
- id,
236
- label: id,
237
- isDefault: index === 0,
238
- }));
239
- return {
240
- provider: "opencode",
241
- id: `${provider.id}/${modelId}`,
242
- label: model.name,
243
- description: `${provider.name} - ${model.family ?? ""}`.trim(),
244
- thinkingOptions: thinkingOptions.length > 0 ? thinkingOptions : undefined,
245
- defaultThinkingOptionId: thinkingOptions[0]?.id,
246
- metadata: {
247
- providerId: provider.id,
248
- providerName: provider.name,
249
- modelId,
250
- family: model.family,
251
- releaseDate: model.release_date,
252
- supportsAttachments: model.attachment,
253
- supportsReasoning: model.reasoning,
254
- supportsToolCall: model.tool_call,
255
- cost: model.cost,
256
- contextWindowMaxTokens: extractOpenCodeModelContextWindow(model),
257
- ...(model.limit ? { limit: model.limit } : {}),
258
- },
259
- };
260
- }
261
- function resolveOpenCodeSelectedModelContextWindow(providers, modelId) {
262
- if (!providers) {
263
- return undefined;
264
- }
265
- const modelLookupKey = parseOpenCodeModelLookupKey(modelId);
266
- if (!modelLookupKey) {
267
- return undefined;
268
- }
269
- const lookup = buildOpenCodeModelContextWindowLookup(providers);
270
- return lookup.get(modelLookupKey);
271
- }
272
- function buildOpenCodeModelContextWindowLookup(providers) {
273
- const lookup = new Map();
274
- if (!providers) {
275
- return lookup;
276
- }
277
- const connectedProviderIds = new Set(providers.connected ?? []);
278
- for (const provider of providers.all ?? []) {
279
- if (!connectedProviderIds.has(provider.id)) {
280
- continue;
281
- }
282
- for (const [modelId, modelDefinition] of Object.entries(provider.models ?? {})) {
283
- const contextWindow = extractOpenCodeModelContextWindow(modelDefinition);
284
- if (contextWindow === undefined) {
285
- continue;
286
- }
287
- lookup.set(buildOpenCodeModelLookupKey(provider.id, modelId), contextWindow);
288
- }
289
- }
290
- return lookup;
291
- }
292
- function resolveOpenCodeModelLookupKeyFromAssistantMessage(info) {
293
- const providerId = info.providerID;
294
- const modelId = info.modelID;
295
- if (!providerId || !modelId) {
296
- return undefined;
297
- }
298
- return buildOpenCodeModelLookupKey(providerId, modelId);
299
- }
300
- function mergeOpenCodeStepFinishUsage(usage, part) {
301
- const inputTokens = readPositiveFiniteNumber(part.tokens?.input);
302
- const outputTokens = readPositiveFiniteNumber(part.tokens?.output);
303
- const reasoningTokens = readPositiveFiniteNumber(part.tokens?.reasoning);
304
- const cacheReadTokens = readPositiveFiniteNumber(part.tokens?.cache?.read);
305
- const cacheWriteTokens = readPositiveFiniteNumber(part.tokens?.cache?.write);
306
- const totalTokens = (inputTokens ?? 0) +
307
- (outputTokens ?? 0) +
308
- (reasoningTokens ?? 0) +
309
- (cacheReadTokens ?? 0) +
310
- (cacheWriteTokens ?? 0);
311
- const cost = readPositiveFiniteNumber(part.cost);
312
- if (inputTokens !== undefined) {
313
- usage.inputTokens = inputTokens;
314
- }
315
- if (cacheReadTokens !== undefined) {
316
- usage.cachedInputTokens = cacheReadTokens;
317
- }
318
- if (outputTokens !== undefined) {
319
- usage.outputTokens = outputTokens;
320
- }
321
- if (totalTokens > 0) {
322
- usage.contextWindowUsedTokens = totalTokens;
323
- }
324
- if (cost !== undefined) {
325
- usage.totalCostUsd = (usage.totalCostUsd ?? 0) + cost;
326
- }
327
- }
328
- function hasNormalizedOpenCodeUsage(usage) {
329
- return [
330
- usage.inputTokens,
331
- usage.cachedInputTokens,
332
- usage.outputTokens,
333
- usage.totalCostUsd,
334
- usage.contextWindowMaxTokens,
335
- usage.contextWindowUsedTokens,
336
- ].some((value) => typeof value === "number" && Number.isFinite(value));
337
- }
338
- function getOpenCodeAttachmentExtension(mimeType) {
339
- switch (mimeType) {
340
- case "image/png":
341
- return "png";
342
- case "image/jpeg":
343
- return "jpg";
344
- case "image/webp":
345
- return "webp";
346
- case "image/gif":
347
- return "gif";
348
- case "image/svg+xml":
349
- return "svg";
350
- default:
351
- return "bin";
352
- }
353
- }
354
- function toOpenCodeDataUrl(mimeType, data) {
355
- const match = data.match(/^data:([^;,]+);base64,(.+)$/);
356
- if (match) {
357
- return {
358
- mimeType: match[1] ?? mimeType,
359
- url: data,
360
- };
361
- }
362
- return {
363
- mimeType,
364
- url: `data:${mimeType};base64,${data}`,
365
- };
366
- }
367
- function buildOpenCodePromptParts(prompt) {
368
- if (typeof prompt === "string") {
369
- return [{ type: "text", text: prompt }];
370
- }
371
- let attachmentOrdinal = 0;
372
- const output = [];
373
- for (const part of prompt) {
374
- if (part.type === "text") {
375
- output.push({ type: "text", text: part.text });
376
- continue;
377
- }
378
- attachmentOrdinal += 1;
379
- const normalized = toOpenCodeDataUrl(part.mimeType, part.data);
380
- output.push({
381
- type: "file",
382
- mime: normalized.mimeType,
383
- filename: `attachment-${attachmentOrdinal}.${getOpenCodeAttachmentExtension(normalized.mimeType)}`,
384
- url: normalized.url,
385
- });
386
- }
387
- return output;
388
- }
389
- export const __openCodeInternals = {
390
- buildOpenCodePromptParts,
391
- buildOpenCodeModelContextWindowLookup,
392
- buildOpenCodeModelDefinition,
393
- buildOpenCodeModelLookupKey,
394
- extractOpenCodeModelContextWindow,
395
- hasNormalizedOpenCodeUsage,
396
- mergeOpenCodeStepFinishUsage,
397
- parseOpenCodeModelLookupKey,
398
- resolveOpenCodeModelLookupKeyFromAssistantMessage,
399
- resolveOpenCodeSelectedModelContextWindow,
400
- };
401
- export class OpenCodeServerManager {
402
- constructor(logger, runtimeSettings) {
403
- this.server = null;
404
- this.port = null;
405
- this.startPromise = null;
406
- this.logger = logger;
407
- this.runtimeSettings = runtimeSettings;
408
- this.runtimeSettingsKey = JSON.stringify(runtimeSettings ?? {});
409
- }
410
- static getInstance(logger, runtimeSettings) {
411
- const nextSettingsKey = JSON.stringify(runtimeSettings ?? {});
412
- if (!OpenCodeServerManager.instance) {
413
- OpenCodeServerManager.instance = new OpenCodeServerManager(logger, runtimeSettings);
414
- OpenCodeServerManager.registerExitHandler();
415
- }
416
- else if (OpenCodeServerManager.instance.runtimeSettingsKey !== nextSettingsKey) {
417
- logger.warn({
418
- existingRuntimeSettings: OpenCodeServerManager.instance.runtimeSettingsKey,
419
- requestedRuntimeSettings: nextSettingsKey,
420
- }, "OpenCode server manager already initialized with different runtime settings");
421
- }
422
- return OpenCodeServerManager.instance;
423
- }
424
- static registerExitHandler() {
425
- if (OpenCodeServerManager.exitHandlerRegistered) {
426
- return;
427
- }
428
- OpenCodeServerManager.exitHandlerRegistered = true;
429
- const cleanup = () => {
430
- const instance = OpenCodeServerManager.instance;
431
- if (instance?.server && !instance.server.killed) {
432
- instance.server.kill("SIGTERM");
433
- }
434
- };
435
- process.on("exit", cleanup);
436
- process.on("SIGTERM", cleanup);
437
- process.on("SIGINT", cleanup);
438
- }
439
- async ensureRunning() {
440
- if (this.startPromise) {
441
- return this.startPromise;
442
- }
443
- if (this.server && this.port && !this.server.killed) {
444
- return { port: this.port, url: `http://127.0.0.1:${this.port}` };
445
- }
446
- this.startPromise = this.startServer();
447
- try {
448
- const result = await this.startPromise;
449
- return result;
450
- }
451
- finally {
452
- this.startPromise = null;
453
- }
454
- }
455
- async startServer() {
456
- this.port = await findAvailablePort();
457
- const url = `http://127.0.0.1:${this.port}`;
458
- const launchPrefix = await resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveOpenCodeBinary);
459
- return new Promise((resolve, reject) => {
460
- this.server = spawnProcess(launchPrefix.command, [...launchPrefix.args, "serve", "--port", String(this.port)], {
461
- stdio: ["ignore", "pipe", "pipe"],
462
- env: applyProviderEnv(process.env, this.runtimeSettings),
463
- });
464
- let started = false;
465
- const timeout = setTimeout(() => {
466
- if (!started) {
467
- reject(new Error("OpenCode server startup timeout"));
468
- }
469
- }, 30000);
470
- this.server.stdout?.on("data", (data) => {
471
- const output = data.toString();
472
- if (output.includes("listening on") && !started) {
473
- started = true;
474
- clearTimeout(timeout);
475
- resolve({ port: this.port, url });
476
- }
477
- });
478
- this.server.stderr?.on("data", (data) => {
479
- this.logger.error({ stderr: data.toString().trim() }, "OpenCode server stderr");
480
- });
481
- this.server.on("error", (error) => {
482
- clearTimeout(timeout);
483
- reject(error);
484
- });
485
- this.server.on("exit", (code) => {
486
- if (!started) {
487
- clearTimeout(timeout);
488
- reject(new Error(`OpenCode server exited with code ${code}`));
489
- }
490
- this.server = null;
491
- this.port = null;
492
- });
493
- });
494
- }
495
- async shutdown() {
496
- if (this.server && !this.server.killed) {
497
- this.server.kill("SIGTERM");
498
- await new Promise((resolve) => {
499
- const timeout = setTimeout(() => {
500
- this.server?.kill("SIGKILL");
501
- resolve();
502
- }, 5000);
503
- this.server?.on("exit", () => {
504
- clearTimeout(timeout);
505
- resolve();
506
- });
507
- });
508
- }
509
- this.server = null;
510
- this.port = null;
511
- }
512
- }
513
- OpenCodeServerManager.instance = null;
514
- OpenCodeServerManager.exitHandlerRegistered = false;
515
- export class OpenCodeAgentClient {
516
- constructor(logger, runtimeSettings) {
517
- this.provider = "opencode";
518
- this.capabilities = OPENCODE_CAPABILITIES;
519
- this.modelContextWindows = new Map();
520
- this.logger = logger.child({ module: "agent", provider: "opencode" });
521
- this.runtimeSettings = runtimeSettings;
522
- this.serverManager = OpenCodeServerManager.getInstance(this.logger, runtimeSettings);
523
- }
524
- async createSession(config, _launchContext) {
525
- const openCodeConfig = this.assertConfig(config);
526
- const { url } = await this.serverManager.ensureRunning();
527
- const client = createOpencodeClient({
528
- baseUrl: url,
529
- directory: openCodeConfig.cwd,
530
- });
531
- // Set a timeout for session creation to fail fast
532
- const timeoutPromise = new Promise((_, reject) => {
533
- setTimeout(() => reject(new Error("OpenCode session.create timed out after 10s")), 10000);
534
- });
535
- const response = await Promise.race([
536
- client.session.create({ directory: openCodeConfig.cwd }),
537
- timeoutPromise,
538
- ]);
539
- if (response.error) {
540
- throw new Error(`Failed to create OpenCode session: ${JSON.stringify(response.error)}`);
541
- }
542
- const session = response.data;
543
- if (!session) {
544
- throw new Error("OpenCode session creation returned no data");
545
- }
546
- await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
547
- return new OpenCodeAgentSession(openCodeConfig, client, session.id, this.logger, new Map(this.modelContextWindows));
548
- }
549
- async resumeSession(handle, overrides, _launchContext) {
550
- const cwd = overrides?.cwd ?? handle.metadata?.cwd;
551
- if (!cwd) {
552
- throw new Error("OpenCode resume requires the original working directory");
553
- }
554
- const config = {
555
- provider: "opencode",
556
- cwd,
557
- ...overrides,
558
- };
559
- const openCodeConfig = this.assertConfig(config);
560
- const { url } = await this.serverManager.ensureRunning();
561
- const client = createOpencodeClient({
562
- baseUrl: url,
563
- directory: openCodeConfig.cwd,
564
- });
565
- await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
566
- return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId, this.logger, new Map(this.modelContextWindows));
567
- }
568
- async listModels(options) {
569
- const { url } = await this.serverManager.ensureRunning();
570
- const client = createOpencodeClient({
571
- baseUrl: url,
572
- directory: options?.cwd ?? process.cwd(),
573
- });
574
- // Background model discovery can be legitimately slow while OpenCode refreshes
575
- // provider state, so allow longer than turn execution paths.
576
- const timeoutPromise = new Promise((_, reject) => {
577
- setTimeout(() => reject(new Error(`OpenCode provider.list timed out after ${OPENCODE_PROVIDER_LIST_TIMEOUT_MS / 1000}s - server may not be authenticated or connected to any providers`)), OPENCODE_PROVIDER_LIST_TIMEOUT_MS);
578
- });
579
- const response = await Promise.race([
580
- client.provider.list({ directory: options?.cwd ?? process.cwd() }),
581
- timeoutPromise,
582
- ]);
583
- if (response.error) {
584
- throw new Error(`Failed to fetch OpenCode providers: ${JSON.stringify(response.error)}`);
585
- }
586
- const providers = response.data;
587
- if (!providers) {
588
- return [];
589
- }
590
- // Only include models from connected providers (ones that are actually available)
591
- const connectedProviderIds = new Set(providers.connected);
592
- // Fail fast if no providers are connected
593
- if (connectedProviderIds.size === 0) {
594
- 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).");
595
- }
596
- const models = [];
597
- this.modelContextWindows.clear();
598
- for (const provider of providers.all) {
599
- // Skip providers that aren't connected/configured
600
- if (!connectedProviderIds.has(provider.id)) {
601
- continue;
602
- }
603
- for (const [modelId, model] of Object.entries(provider.models)) {
604
- const definition = buildOpenCodeModelDefinition(provider, modelId, model);
605
- const contextWindowMaxTokens = extractOpenCodeModelContextWindow(model);
606
- if (contextWindowMaxTokens !== undefined) {
607
- this.modelContextWindows.set(buildOpenCodeModelLookupKey(provider.id, modelId), contextWindowMaxTokens);
608
- }
609
- models.push(definition);
610
- }
611
- }
612
- return models;
613
- }
614
- async listModes(options) {
615
- const { url } = await this.serverManager.ensureRunning();
616
- const directory = options?.cwd ?? process.cwd();
617
- const client = createOpencodeClient({ baseUrl: url, directory });
618
- const timeoutPromise = new Promise((_, reject) => {
619
- setTimeout(() => reject(new Error("OpenCode app.agents timed out after 10s")), 10000);
620
- });
621
- const response = await Promise.race([client.app.agents({ directory }), timeoutPromise]);
622
- if (response.error || !response.data) {
623
- return DEFAULT_MODES;
624
- }
625
- const discovered = response.data
626
- .filter((agent) => agent.mode === "primary" && agent.hidden !== true)
627
- .map((agent) => ({
628
- id: agent.name,
629
- label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1),
630
- description: typeof agent.description === "string" && agent.description.trim().length > 0
631
- ? agent.description.trim()
632
- : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description,
633
- }));
634
- return discovered.length > 0 ? sortOpenCodeModes(discovered) : DEFAULT_MODES;
635
- }
636
- async listPersistedAgents(_options) {
637
- // TODO: Implement by listing sessions from OpenCode
638
- return [];
639
- }
640
- async isAvailable() {
641
- const command = this.runtimeSettings?.command;
642
- if (command?.mode === "replace") {
643
- return existsSync(command.argv[0]);
644
- }
645
- return true;
646
- }
647
- async getDiagnostic() {
648
- try {
649
- const available = await this.isAvailable();
650
- const resolvedBinary = await findExecutable("opencode");
651
- let serverStatus = "Not running";
652
- let modelsValue = "Not checked";
653
- let status = formatDiagnosticStatus(available);
654
- try {
655
- const { url } = await this.serverManager.ensureRunning();
656
- serverStatus = `Running (${url})`;
657
- }
658
- catch (error) {
659
- serverStatus = `Unavailable (${normalizeTurnFailureError(error)})`;
660
- }
661
- if (available) {
662
- try {
663
- const models = await this.listModels();
664
- modelsValue = String(models.length);
665
- }
666
- catch (error) {
667
- modelsValue = `Error - ${toDiagnosticErrorMessage(error)}`;
668
- status = formatDiagnosticStatus(available, {
669
- source: "model fetch",
670
- cause: error,
671
- });
672
- }
673
- if (!modelsValue.startsWith("Error -")) {
674
- try {
675
- await this.listModes();
676
- }
677
- catch (error) {
678
- status = formatDiagnosticStatus(available, {
679
- source: "mode fetch",
680
- cause: error,
681
- });
682
- }
683
- }
684
- }
685
- return {
686
- diagnostic: formatProviderDiagnostic("OpenCode", [
687
- {
688
- label: "Binary",
689
- value: resolvedBinary ?? "not found",
690
- },
691
- {
692
- label: "Version",
693
- value: resolvedBinary ? await resolveBinaryVersion(resolvedBinary) : "unknown",
694
- },
695
- { label: "Server", value: serverStatus },
696
- { label: "Models", value: modelsValue },
697
- { label: "Status", value: status },
698
- ]),
699
- };
700
- }
701
- catch (error) {
702
- return {
703
- diagnostic: formatProviderDiagnosticError("OpenCode", error),
704
- };
705
- }
706
- }
707
- assertConfig(config) {
708
- if (config.provider !== "opencode") {
709
- throw new Error(`OpenCodeAgentClient received config for provider '${config.provider}'`);
710
- }
711
- return { ...config, provider: "opencode" };
712
- }
713
- async populateModelContextWindowCache(client, cwd) {
714
- const response = await client.provider.list({ directory: cwd });
715
- if (response.error || !response.data) {
716
- return;
717
- }
718
- const lookup = buildOpenCodeModelContextWindowLookup(response.data);
719
- this.modelContextWindows.clear();
720
- for (const [modelLookupKey, contextWindowMaxTokens] of lookup.entries()) {
721
- this.modelContextWindows.set(modelLookupKey, contextWindowMaxTokens);
722
- }
723
- }
724
- }
725
- function stringifyStructuredAssistantMessage(value) {
726
- if (value === undefined) {
727
- return null;
728
- }
729
- if (typeof value === "string") {
730
- const trimmed = value.trim();
731
- return trimmed.length > 0 ? trimmed : null;
732
- }
733
- try {
734
- return JSON.stringify(value);
735
- }
736
- catch {
737
- return null;
738
- }
739
- }
740
- function readOpenCodeRecord(value) {
741
- return typeof value === "object" && value !== null && !Array.isArray(value)
742
- ? value
743
- : null;
744
- }
745
- function readNonEmptyString(value) {
746
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
747
- }
748
- export function translateOpenCodeEvent(event, state) {
749
- const events = [];
750
- switch (event.type) {
751
- case "session.created":
752
- case "session.updated": {
753
- if (event.properties.info.id === state.sessionId) {
754
- events.push({
755
- type: "thread_started",
756
- sessionId: state.sessionId,
757
- provider: "opencode",
758
- });
759
- }
760
- break;
761
- }
762
- case "message.updated": {
763
- const info = event.properties.info;
764
- if (info.sessionID !== state.sessionId) {
765
- break;
766
- }
767
- state.messageRoles.set(info.id, info.role);
768
- if (info.role === "assistant") {
769
- const modelLookupKey = resolveOpenCodeModelLookupKeyFromAssistantMessage(info);
770
- if (modelLookupKey) {
771
- const contextWindowMaxTokens = state.modelContextWindowsByModelKey?.get(modelLookupKey);
772
- if (contextWindowMaxTokens !== undefined) {
773
- state.onAssistantModelContextWindowResolved?.(contextWindowMaxTokens);
774
- }
775
- }
776
- if (!state.emittedStructuredMessageIds.has(info.id) && info.time?.completed !== undefined) {
777
- const text = stringifyStructuredAssistantMessage(info.structured);
778
- if (text) {
779
- state.emittedStructuredMessageIds.add(info.id);
780
- events.push({
781
- type: "timeline",
782
- provider: "opencode",
783
- item: { type: "assistant_message", text },
784
- });
785
- }
786
- }
787
- }
788
- break;
789
- }
790
- case "message.part.updated": {
791
- const part = event.properties.part;
792
- if (part.sessionID !== state.sessionId) {
793
- break;
794
- }
795
- const messageRole = state.messageRoles.get(part.messageID);
796
- state.partTypes.set(part.id, part.type);
797
- if (part.type === "text") {
798
- const partKey = resolvePartDedupeKey(part, "text");
799
- if (messageRole === "user") {
800
- break;
801
- }
802
- if (part.time?.end) {
803
- if (partKey && state.streamedPartKeys.delete(partKey)) {
804
- break;
805
- }
806
- if (part.text) {
807
- events.push({
808
- type: "timeline",
809
- provider: "opencode",
810
- item: { type: "assistant_message", text: part.text },
811
- });
812
- }
813
- }
814
- }
815
- else if (part.type === "reasoning") {
816
- const partKey = resolvePartDedupeKey(part, "reasoning");
817
- if (part.time.end) {
818
- if (partKey && state.streamedPartKeys.delete(partKey)) {
819
- break;
820
- }
821
- if (part.text) {
822
- events.push({
823
- type: "timeline",
824
- provider: "opencode",
825
- item: { type: "reasoning", text: part.text },
826
- });
827
- }
828
- }
829
- }
830
- else if (part.type === "tool") {
831
- const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
832
- if (parsedToolPart.success && parsedToolPart.data) {
833
- events.push({
834
- type: "timeline",
835
- provider: "opencode",
836
- item: parsedToolPart.data,
837
- });
838
- }
839
- }
840
- else if (part.type === "step-finish") {
841
- mergeOpenCodeStepFinishUsage(state.accumulatedUsage, part);
842
- if (hasNormalizedOpenCodeUsage(state.accumulatedUsage)) {
843
- events.push({
844
- type: "usage_updated",
845
- provider: "opencode",
846
- usage: { ...state.accumulatedUsage },
847
- });
848
- }
849
- }
850
- break;
851
- }
852
- case "message.part.delta": {
853
- const { sessionID, messageID, partID, field, delta } = event.properties;
854
- if (sessionID !== state.sessionId) {
855
- break;
856
- }
857
- if (!delta || !field) {
858
- break;
859
- }
860
- const messageRole = messageID ? state.messageRoles.get(messageID) : undefined;
861
- const knownPartType = partID ? state.partTypes.get(partID) : undefined;
862
- const isReasoning = knownPartType === "reasoning" || field === "reasoning";
863
- if (isReasoning) {
864
- if (partID) {
865
- state.streamedPartKeys.add(`reasoning:${partID}`);
866
- }
867
- events.push({
868
- type: "timeline",
869
- provider: "opencode",
870
- item: { type: "reasoning", text: delta },
871
- });
872
- }
873
- else if (field === "text") {
874
- if (messageRole === "user") {
875
- break;
876
- }
877
- if (partID) {
878
- state.streamedPartKeys.add(`text:${partID}`);
879
- }
880
- events.push({
881
- type: "timeline",
882
- provider: "opencode",
883
- item: { type: "assistant_message", text: delta },
884
- });
885
- }
886
- break;
887
- }
888
- case "permission.asked": {
889
- if (event.properties.sessionID !== state.sessionId) {
890
- break;
891
- }
892
- events.push({
893
- type: "permission_requested",
894
- provider: "opencode",
895
- request: {
896
- id: event.properties.id,
897
- provider: "opencode",
898
- name: event.properties.permission,
899
- kind: "tool",
900
- title: event.properties.permission,
901
- description: event.properties.patterns?.join(", "),
902
- input: event.properties.metadata,
903
- },
904
- });
905
- break;
906
- }
907
- case "question.asked": {
908
- if (event.properties.sessionID !== state.sessionId) {
909
- break;
910
- }
911
- const questions = event.properties.questions.flatMap((q) => {
912
- if (!q.question || !q.header) {
913
- return [];
914
- }
915
- const options = q.options?.map((o) => ({
916
- label: o.label,
917
- ...(o.description ? { description: o.description } : {}),
918
- })) ?? [];
919
- return [
920
- {
921
- question: q.question,
922
- header: q.header,
923
- options,
924
- ...(q.multiple === true ? { multiSelect: true } : {}),
925
- },
926
- ];
927
- });
928
- if (questions.length === 0) {
929
- break;
930
- }
931
- events.push({
932
- type: "permission_requested",
933
- provider: "opencode",
934
- request: {
935
- id: event.properties.id,
936
- provider: "opencode",
937
- name: "question",
938
- kind: "question",
939
- title: "Question",
940
- input: { questions },
941
- metadata: {
942
- source: "opencode_question",
943
- ...(event.properties.tool ?? {}),
944
- },
945
- },
946
- });
947
- break;
948
- }
949
- case "session.idle": {
950
- if (event.properties.sessionID === state.sessionId) {
951
- state.streamedPartKeys.clear();
952
- state.partTypes.clear();
953
- events.push({
954
- type: "turn_completed",
955
- provider: "opencode",
956
- usage: undefined,
957
- });
958
- }
959
- break;
960
- }
961
- case "session.error": {
962
- if (event.properties.sessionID === state.sessionId) {
963
- state.streamedPartKeys.clear();
964
- state.partTypes.clear();
965
- events.push({
966
- type: "turn_failed",
967
- provider: "opencode",
968
- error: normalizeTurnFailureError(event.properties.error),
969
- });
970
- }
971
- break;
972
- }
973
- case "session.status": {
974
- if (event.properties.sessionID !== state.sessionId) {
975
- break;
976
- }
977
- const { status } = event.properties;
978
- if (status.type === "idle") {
979
- state.streamedPartKeys.clear();
980
- state.partTypes.clear();
981
- events.push({
982
- type: "turn_completed",
983
- provider: "opencode",
984
- usage: undefined,
985
- });
986
- }
987
- else if (status.type === "retry" && isFatalOpenCodeRetryMessage(status.message)) {
988
- state.streamedPartKeys.clear();
989
- state.partTypes.clear();
990
- events.push({
991
- type: "turn_failed",
992
- provider: "opencode",
993
- error: normalizeTurnFailureError(status.message),
994
- });
995
- }
996
- // "retry" and "busy" are transient — no terminal event.
997
- break;
998
- }
999
- }
1000
- return events;
1001
- }
1002
- class OpenCodeAgentSession {
1003
- constructor(config, client, sessionId, logger, modelContextWindowsByModelKey = new Map()) {
1004
- this.provider = "opencode";
1005
- this.capabilities = OPENCODE_CAPABILITIES;
1006
- this.currentMode = "default";
1007
- this.pendingPermissions = new Map();
1008
- this.abortController = null;
1009
- this.accumulatedUsage = {};
1010
- this.mcpConfigured = false;
1011
- this.mcpSetupPromise = null;
1012
- /** Tracks the role of each message by ID to distinguish user from assistant messages */
1013
- this.messageRoles = new Map();
1014
- /** Tracks streamed textual part IDs to suppress final full-text echoes from OpenCode. */
1015
- this.streamedPartKeys = new Set();
1016
- /** Tracks assistant messages already emitted from structured payloads. */
1017
- this.emittedStructuredMessageIds = new Set();
1018
- /** Tracks the type of each part by ID, learned from message.part.updated events. */
1019
- this.partTypes = new Map();
1020
- this.availableModesCache = null;
1021
- this.subscribers = new Set();
1022
- this.nextTurnOrdinal = 0;
1023
- this.activeForegroundTurnId = null;
1024
- this.runningToolCalls = new Map();
1025
- this.config = config;
1026
- this.client = client;
1027
- this.sessionId = sessionId;
1028
- this.logger = logger;
1029
- this.modelContextWindowsByModelKey = modelContextWindowsByModelKey;
1030
- this.currentMode = normalizeOpenCodeModeId(config.modeId);
1031
- this.selectedModelContextWindowMaxTokens = this.resolveConfiguredModelContextWindowMaxTokens(config.model);
1032
- }
1033
- get id() {
1034
- return this.sessionId;
1035
- }
1036
- async getRuntimeInfo() {
1037
- return {
1038
- provider: "opencode",
1039
- sessionId: this.sessionId,
1040
- model: this.config.model ?? null,
1041
- modeId: this.currentMode,
1042
- };
1043
- }
1044
- async setModel(modelId) {
1045
- const normalizedModelId = typeof modelId === "string" && modelId.trim().length > 0 ? modelId : null;
1046
- this.config.model = normalizedModelId ?? undefined;
1047
- this.selectedModelContextWindowMaxTokens = this.resolveConfiguredModelContextWindowMaxTokens(this.config.model);
1048
- }
1049
- async setThinkingOption(thinkingOptionId) {
1050
- const normalizedThinkingOptionId = typeof thinkingOptionId === "string" && thinkingOptionId.trim().length > 0
1051
- ? thinkingOptionId
1052
- : null;
1053
- this.config.thinkingOptionId = normalizedThinkingOptionId ?? undefined;
1054
- }
1055
- async run(prompt, options) {
1056
- const timeline = [];
1057
- let finalText = "";
1058
- let usage;
1059
- let turnId = null;
1060
- const bufferedEvents = [];
1061
- let settled = false;
1062
- let resolveCompletion;
1063
- let rejectCompletion;
1064
- const processEvent = (event) => {
1065
- if (settled) {
1066
- return;
1067
- }
1068
- const eventTurnId = event.turnId;
1069
- if (turnId && eventTurnId && eventTurnId !== turnId) {
1070
- return;
1071
- }
1072
- if (event.type === "timeline") {
1073
- timeline.push(event.item);
1074
- if (event.item.type === "assistant_message") {
1075
- finalText = event.item.text;
1076
- }
1077
- return;
1078
- }
1079
- if (event.type === "turn_completed") {
1080
- usage = event.usage;
1081
- settled = true;
1082
- resolveCompletion();
1083
- return;
1084
- }
1085
- if (event.type === "turn_failed") {
1086
- settled = true;
1087
- rejectCompletion(new Error(event.error));
1088
- return;
1089
- }
1090
- if (event.type === "turn_canceled") {
1091
- settled = true;
1092
- resolveCompletion();
1093
- }
1094
- };
1095
- const completion = new Promise((resolve, reject) => {
1096
- resolveCompletion = resolve;
1097
- rejectCompletion = reject;
1098
- });
1099
- const unsubscribe = this.subscribe((event) => {
1100
- if (!turnId) {
1101
- bufferedEvents.push(event);
1102
- return;
1103
- }
1104
- processEvent(event);
1105
- });
1106
- try {
1107
- const result = await this.startTurn(prompt, options);
1108
- turnId = result.turnId;
1109
- for (const event of bufferedEvents) {
1110
- processEvent(event);
1111
- }
1112
- if (!settled) {
1113
- await completion;
1114
- }
1115
- }
1116
- finally {
1117
- unsubscribe();
1118
- }
1119
- return {
1120
- sessionId: this.sessionId,
1121
- finalText,
1122
- usage,
1123
- timeline,
1124
- };
1125
- }
1126
- async interrupt() {
1127
- const turnId = this.activeForegroundTurnId;
1128
- const turnAbortController = this.abortController;
1129
- turnAbortController?.abort();
1130
- await this.client.session.abort({
1131
- sessionID: this.sessionId,
1132
- directory: this.config.cwd,
1133
- });
1134
- if (turnId) {
1135
- this.finishForegroundTurn({ type: "turn_canceled", provider: "opencode", reason: "interrupted" }, turnId);
1136
- }
1137
- }
1138
- async startTurn(prompt, options) {
1139
- if (this.activeForegroundTurnId) {
1140
- throw new Error("A foreground turn is already active");
1141
- }
1142
- this.runningToolCalls.clear();
1143
- const turnAbortController = new AbortController();
1144
- this.abortController = turnAbortController;
1145
- await this.ensureMcpServersConfigured();
1146
- const contextWindowMaxTokens = this.resolveSelectedModelContextWindowMaxTokens();
1147
- this.accumulatedUsage = contextWindowMaxTokens !== undefined ? { contextWindowMaxTokens } : {};
1148
- const parts = buildOpenCodePromptParts(prompt);
1149
- const model = this.parseModel(this.config.model);
1150
- const thinkingOptionId = this.config.thinkingOptionId;
1151
- const effectiveVariant = thinkingOptionId ?? undefined;
1152
- const effectiveMode = normalizeOpenCodeModeId(this.currentMode);
1153
- const turnId = this.createTurnId();
1154
- this.activeForegroundTurnId = turnId;
1155
- void this.consumeEventStream(turnId, turnAbortController);
1156
- const slashCommand = await this.resolveSlashCommandInvocation(prompt);
1157
- if (slashCommand) {
1158
- // command() blocks until the server finishes processing. OpenCode's SSE
1159
- // endpoint does NOT replay past events, so if the command completes before
1160
- // our SSE reader connects, we miss `session.idle` and the turn hangs.
1161
- // Handle both success and error in the response handler as a fallback —
1162
- // finishForegroundTurn's guard prevents duplicate terminal events if the
1163
- // SSE stream already delivered the event.
1164
- void this.client.session
1165
- .command({
1166
- sessionID: this.sessionId,
1167
- directory: this.config.cwd,
1168
- command: slashCommand.commandName,
1169
- arguments: slashCommand.args ?? "",
1170
- ...(this.config.model ? { model: this.config.model } : {}),
1171
- ...(effectiveMode ? { agent: effectiveMode } : {}),
1172
- ...(effectiveVariant ? { variant: effectiveVariant } : {}),
1173
- })
1174
- .then((response) => {
1175
- if (response.error) {
1176
- const errorMsg = normalizeTurnFailureError(response.error);
1177
- this.finishForegroundTurn({ type: "turn_failed", provider: "opencode", error: errorMsg }, turnId);
1178
- }
1179
- else {
1180
- this.finishForegroundTurn({ type: "turn_completed", provider: "opencode", usage: undefined }, turnId);
1181
- }
1182
- })
1183
- .catch((err) => {
1184
- this.finishForegroundTurn({ type: "turn_failed", provider: "opencode", error: normalizeTurnFailureError(err) }, turnId);
1185
- });
1186
- }
1187
- else {
1188
- void this.client.session
1189
- .promptAsync({
1190
- sessionID: this.sessionId,
1191
- directory: this.config.cwd,
1192
- parts,
1193
- ...(options?.outputSchema
1194
- ? {
1195
- format: {
1196
- type: "json_schema",
1197
- schema: options.outputSchema,
1198
- },
1199
- }
1200
- : {}),
1201
- ...(this.config.systemPrompt ? { system: this.config.systemPrompt } : {}),
1202
- ...(model ? { model } : {}),
1203
- ...(effectiveMode ? { agent: effectiveMode } : {}),
1204
- ...(effectiveVariant ? { variant: effectiveVariant } : {}),
1205
- })
1206
- .then((promptResponse) => {
1207
- if (promptResponse.error) {
1208
- this.finishForegroundTurn({
1209
- type: "turn_failed",
1210
- provider: "opencode",
1211
- error: normalizeTurnFailureError(promptResponse.error),
1212
- }, turnId);
1213
- }
1214
- })
1215
- .catch((error) => {
1216
- this.finishForegroundTurn({
1217
- type: "turn_failed",
1218
- provider: "opencode",
1219
- error: normalizeTurnFailureError(error),
1220
- }, turnId);
1221
- });
1222
- }
1223
- return { turnId };
1224
- }
1225
- subscribe(callback) {
1226
- this.subscribers.add(callback);
1227
- return () => {
1228
- this.subscribers.delete(callback);
1229
- };
1230
- }
1231
- async consumeEventStream(turnId, turnAbortController) {
1232
- try {
1233
- const result = await this.client.event.subscribe({ directory: this.config.cwd }, { signal: turnAbortController.signal, sseMaxRetryAttempts: 0 });
1234
- for await (const event of result.stream) {
1235
- if (turnAbortController.signal.aborted || this.activeForegroundTurnId !== turnId) {
1236
- break;
1237
- }
1238
- const translated = this.translateEvent(event);
1239
- for (const e of translated) {
1240
- if (this.activeForegroundTurnId !== turnId) {
1241
- return;
1242
- }
1243
- if (e.type === "timeline" && e.item.type === "tool_call") {
1244
- this.trackToolCall(e.item);
1245
- }
1246
- if (e.type === "turn_completed" ||
1247
- e.type === "turn_failed" ||
1248
- e.type === "turn_canceled") {
1249
- if (e.type === "turn_failed") {
1250
- this.finishForegroundTurn({
1251
- type: "turn_failed",
1252
- provider: "opencode",
1253
- error: normalizeTurnFailureError(e.error),
1254
- }, turnId);
1255
- }
1256
- else {
1257
- this.finishForegroundTurn(e, turnId);
1258
- }
1259
- return;
1260
- }
1261
- this.notifySubscribers(e, turnId);
1262
- }
1263
- }
1264
- if (!turnAbortController.signal.aborted && this.activeForegroundTurnId === turnId) {
1265
- this.finishForegroundTurn({
1266
- type: "turn_failed",
1267
- provider: "opencode",
1268
- error: "OpenCode event stream ended before the turn reached a terminal state",
1269
- }, turnId);
1270
- }
1271
- }
1272
- catch (error) {
1273
- if (!turnAbortController.signal.aborted && this.activeForegroundTurnId === turnId) {
1274
- this.finishForegroundTurn({
1275
- type: "turn_failed",
1276
- provider: "opencode",
1277
- error: normalizeTurnFailureError(error),
1278
- }, turnId);
1279
- }
1280
- }
1281
- finally {
1282
- if (turnAbortController.signal.aborted) {
1283
- this.finishForegroundTurn({
1284
- type: "turn_canceled",
1285
- provider: "opencode",
1286
- reason: "interrupted",
1287
- }, turnId);
1288
- }
1289
- if (this.abortController === turnAbortController && this.activeForegroundTurnId !== turnId) {
1290
- this.abortController = null;
1291
- }
1292
- }
1293
- }
1294
- finishForegroundTurn(event, turnId) {
1295
- if (this.activeForegroundTurnId !== turnId) {
1296
- return;
1297
- }
1298
- if (event.type === "turn_canceled" || event.type === "turn_failed") {
1299
- this.synthesizeInterruptedToolCalls(turnId);
1300
- }
1301
- else {
1302
- this.runningToolCalls.clear();
1303
- }
1304
- this.activeForegroundTurnId = null;
1305
- // Abort the SSE connection so the SDK tears down the underlying fetch.
1306
- this.abortController?.abort();
1307
- this.abortController = null;
1308
- this.notifySubscribers(event, turnId);
1309
- }
1310
- trackToolCall(item) {
1311
- if (item.status === "running") {
1312
- this.runningToolCalls.set(item.callId, item);
1313
- return;
1314
- }
1315
- this.runningToolCalls.delete(item.callId);
1316
- }
1317
- synthesizeInterruptedToolCalls(turnId) {
1318
- for (const item of this.runningToolCalls.values()) {
1319
- this.notifySubscribers({
1320
- type: "timeline",
1321
- provider: "opencode",
1322
- item: {
1323
- ...item,
1324
- status: "failed",
1325
- error: { message: "Tool execution aborted" },
1326
- },
1327
- }, turnId);
1328
- }
1329
- this.runningToolCalls.clear();
1330
- }
1331
- notifySubscribers(event, turnIdOverride) {
1332
- const turnId = turnIdOverride ?? this.activeForegroundTurnId;
1333
- const tagged = turnId ? { ...event, turnId } : event;
1334
- for (const callback of this.subscribers) {
1335
- try {
1336
- callback(tagged);
1337
- }
1338
- catch {
1339
- // Subscriber callback error isolation
1340
- }
1341
- }
1342
- }
1343
- createTurnId() {
1344
- return `opencode-turn-${this.nextTurnOrdinal++}`;
1345
- }
1346
- async *streamHistory() {
1347
- const response = await this.client.session.messages({
1348
- sessionID: this.sessionId,
1349
- directory: this.config.cwd,
1350
- });
1351
- if (response.error || !response.data) {
1352
- return;
1353
- }
1354
- for (const { info, parts } of response.data) {
1355
- if (info.role === "user") {
1356
- const text = parts
1357
- .filter((p) => p.type === "text")
1358
- .map((p) => p.text)
1359
- .join("");
1360
- if (text) {
1361
- yield {
1362
- type: "timeline",
1363
- provider: "opencode",
1364
- item: { type: "user_message", text },
1365
- };
1366
- }
1367
- }
1368
- else {
1369
- let emittedAssistantText = false;
1370
- for (const part of parts) {
1371
- if (part.type === "text") {
1372
- if (part.text) {
1373
- emittedAssistantText = true;
1374
- yield {
1375
- type: "timeline",
1376
- provider: "opencode",
1377
- item: { type: "assistant_message", text: part.text },
1378
- };
1379
- }
1380
- }
1381
- else if (part.type === "reasoning") {
1382
- if (part.text) {
1383
- yield {
1384
- type: "timeline",
1385
- provider: "opencode",
1386
- item: { type: "reasoning", text: part.text },
1387
- };
1388
- }
1389
- }
1390
- else if (part.type === "tool") {
1391
- const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
1392
- if (parsedToolPart.success && parsedToolPart.data) {
1393
- yield {
1394
- type: "timeline",
1395
- provider: "opencode",
1396
- item: parsedToolPart.data,
1397
- };
1398
- }
1399
- }
1400
- }
1401
- if (!emittedAssistantText) {
1402
- const text = stringifyStructuredAssistantMessage(info.structured);
1403
- if (text) {
1404
- yield {
1405
- type: "timeline",
1406
- provider: "opencode",
1407
- item: { type: "assistant_message", text },
1408
- };
1409
- }
1410
- }
1411
- }
1412
- }
1413
- }
1414
- async getAvailableModes() {
1415
- if (this.availableModesCache) {
1416
- return this.availableModesCache;
1417
- }
1418
- const response = await this.client.app.agents({
1419
- directory: this.config.cwd,
1420
- });
1421
- const discoveredModes = response.error || !response.data
1422
- ? []
1423
- : response.data
1424
- .filter((agent) => agent.mode === "primary" && agent.hidden !== true)
1425
- .map((agent) => ({
1426
- id: agent.name,
1427
- label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1),
1428
- description: typeof agent.description === "string" && agent.description.trim().length > 0
1429
- ? agent.description.trim()
1430
- : DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description,
1431
- }));
1432
- this.availableModesCache =
1433
- discoveredModes.length > 0 ? sortOpenCodeModes(discoveredModes) : DEFAULT_MODES;
1434
- return this.availableModesCache;
1435
- }
1436
- async getCurrentMode() {
1437
- return this.currentMode;
1438
- }
1439
- async listCommands() {
1440
- const result = await this.client.command.list({
1441
- directory: this.config.cwd,
1442
- });
1443
- if (result.error || !result.data) {
1444
- return [];
1445
- }
1446
- return result.data.map((cmd) => ({
1447
- name: cmd.name,
1448
- description: cmd.description ?? "",
1449
- argumentHint: cmd.hints?.length ? cmd.hints.join(" ") : "",
1450
- }));
1451
- }
1452
- async setMode(modeId) {
1453
- this.currentMode = normalizeOpenCodeModeId(modeId);
1454
- }
1455
- getPendingPermissions() {
1456
- return Array.from(this.pendingPermissions.values());
1457
- }
1458
- async respondToPermission(requestId, response) {
1459
- const pending = this.pendingPermissions.get(requestId);
1460
- if (!pending) {
1461
- throw new Error(`No pending permission request with id '${requestId}'`);
1462
- }
1463
- if (pending.kind === "question") {
1464
- if (response.behavior === "deny") {
1465
- await this.client.question.reject({
1466
- requestID: requestId,
1467
- directory: this.config.cwd,
1468
- });
1469
- }
1470
- else {
1471
- const answersRecord = readOpenCodeRecord(response.updatedInput?.answers);
1472
- const questions = Array.isArray(pending.input?.questions) ? pending.input.questions : [];
1473
- const answers = questions.map((item) => {
1474
- const header = readNonEmptyString(readOpenCodeRecord(item)?.header);
1475
- const rawAnswer = header ? readNonEmptyString(answersRecord?.[header]) : null;
1476
- if (!rawAnswer) {
1477
- return [];
1478
- }
1479
- return rawAnswer
1480
- .split(",")
1481
- .map((entry) => entry.trim())
1482
- .filter((entry) => entry.length > 0);
1483
- });
1484
- await this.client.question.reply({
1485
- requestID: requestId,
1486
- directory: this.config.cwd,
1487
- answers,
1488
- });
1489
- }
1490
- this.pendingPermissions.delete(requestId);
1491
- return;
1492
- }
1493
- const reply = response.behavior === "allow" ? "once" : "reject";
1494
- await this.client.permission.reply({
1495
- requestID: requestId,
1496
- directory: this.config.cwd,
1497
- reply,
1498
- message: response.behavior === "deny" ? response.message : undefined,
1499
- });
1500
- this.pendingPermissions.delete(requestId);
1501
- }
1502
- describePersistence() {
1503
- return {
1504
- provider: "opencode",
1505
- sessionId: this.sessionId,
1506
- nativeHandle: this.sessionId,
1507
- metadata: {
1508
- cwd: this.config.cwd,
1509
- },
1510
- };
1511
- }
1512
- async close() {
1513
- this.abortController?.abort();
1514
- this.subscribers.clear();
1515
- this.activeForegroundTurnId = null;
1516
- }
1517
- parseSlashCommandInput(text) {
1518
- const trimmed = text.trim();
1519
- if (!trimmed.startsWith("/") || trimmed.length <= 1) {
1520
- return null;
1521
- }
1522
- const withoutPrefix = trimmed.slice(1);
1523
- const firstWhitespaceIdx = withoutPrefix.search(/\s/);
1524
- const commandName = firstWhitespaceIdx === -1 ? withoutPrefix : withoutPrefix.slice(0, firstWhitespaceIdx);
1525
- if (!commandName || commandName.includes("/")) {
1526
- return null;
1527
- }
1528
- const rawArgs = firstWhitespaceIdx === -1 ? "" : withoutPrefix.slice(firstWhitespaceIdx + 1).trim();
1529
- return rawArgs.length > 0 ? { commandName, args: rawArgs } : { commandName };
1530
- }
1531
- async resolveSlashCommandInvocation(prompt) {
1532
- if (typeof prompt !== "string") {
1533
- return null;
1534
- }
1535
- const parsed = this.parseSlashCommandInput(prompt);
1536
- if (!parsed) {
1537
- return null;
1538
- }
1539
- try {
1540
- const commands = await this.listCommands();
1541
- return commands.some((command) => command.name === parsed.commandName) ? parsed : null;
1542
- }
1543
- catch (error) {
1544
- this.logger.warn({ err: error, commandName: parsed.commandName }, "Failed to resolve slash command; falling back to plain prompt input");
1545
- return null;
1546
- }
1547
- }
1548
- parseModel(model) {
1549
- if (!model) {
1550
- return undefined;
1551
- }
1552
- const parts = model.split("/");
1553
- if (parts.length >= 2) {
1554
- return { providerID: parts[0], modelID: parts.slice(1).join("/") };
1555
- }
1556
- return { providerID: "opencode", modelID: model };
1557
- }
1558
- async ensureMcpServersConfigured() {
1559
- if (this.mcpConfigured) {
1560
- return;
1561
- }
1562
- const mcpServers = this.config.mcpServers;
1563
- if (!mcpServers || Object.keys(mcpServers).length === 0) {
1564
- this.mcpConfigured = true;
1565
- return;
1566
- }
1567
- if (!this.mcpSetupPromise) {
1568
- this.mcpSetupPromise = this.configureMcpServers(mcpServers);
1569
- }
1570
- try {
1571
- await this.mcpSetupPromise;
1572
- this.mcpConfigured = true;
1573
- }
1574
- catch (error) {
1575
- this.mcpSetupPromise = null;
1576
- throw error;
1577
- }
1578
- }
1579
- async configureMcpServers(mcpServers) {
1580
- for (const [name, serverConfig] of Object.entries(mcpServers)) {
1581
- const mappedConfig = toOpenCodeMcpConfig(serverConfig);
1582
- await this.registerMcpServer(name, mappedConfig);
1583
- }
1584
- }
1585
- async registerMcpServer(name, config) {
1586
- await this.runMcpOperation("add", name, () => this.client.mcp.add({
1587
- directory: this.config.cwd,
1588
- name,
1589
- config,
1590
- }));
1591
- await this.runMcpOperation("connect", name, () => this.client.mcp.connect({
1592
- directory: this.config.cwd,
1593
- name,
1594
- }));
1595
- }
1596
- async runMcpOperation(operation, name, run) {
1597
- const response = await run();
1598
- const error = response.error;
1599
- if (!error) {
1600
- return;
1601
- }
1602
- if (isAlreadyPresentMcpError(error)) {
1603
- return;
1604
- }
1605
- throw new Error(`Failed to ${operation} OpenCode MCP server '${name}': ${stringifyUnknownError(error)}`);
1606
- }
1607
- translateEvent(event) {
1608
- const translated = translateOpenCodeEvent(event, {
1609
- sessionId: this.sessionId,
1610
- messageRoles: this.messageRoles,
1611
- accumulatedUsage: this.accumulatedUsage,
1612
- streamedPartKeys: this.streamedPartKeys,
1613
- emittedStructuredMessageIds: this.emittedStructuredMessageIds,
1614
- partTypes: this.partTypes,
1615
- modelContextWindowsByModelKey: this.modelContextWindowsByModelKey,
1616
- onAssistantModelContextWindowResolved: (contextWindowMaxTokens) => {
1617
- this.accumulatedUsage.contextWindowMaxTokens = contextWindowMaxTokens;
1618
- if (!this.config.model) {
1619
- this.selectedModelContextWindowMaxTokens = contextWindowMaxTokens;
1620
- }
1621
- },
1622
- });
1623
- for (const translatedEvent of translated) {
1624
- if (translatedEvent.type === "permission_requested") {
1625
- this.pendingPermissions.set(translatedEvent.request.id, translatedEvent.request);
1626
- }
1627
- if (translatedEvent.type === "turn_completed") {
1628
- if (hasNormalizedOpenCodeUsage(this.accumulatedUsage)) {
1629
- translatedEvent.usage = this.accumulatedUsage;
1630
- }
1631
- const contextWindowMaxTokens = this.resolveSelectedModelContextWindowMaxTokens();
1632
- this.accumulatedUsage =
1633
- contextWindowMaxTokens !== undefined ? { contextWindowMaxTokens } : {};
1634
- }
1635
- }
1636
- return translated;
1637
- }
1638
- resolveSelectedModelContextWindowMaxTokens() {
1639
- return this.selectedModelContextWindowMaxTokens;
1640
- }
1641
- resolveConfiguredModelContextWindowMaxTokens(modelId) {
1642
- const modelLookupKey = parseOpenCodeModelLookupKey(modelId);
1643
- if (!modelLookupKey) {
1644
- return undefined;
1645
- }
1646
- return this.modelContextWindowsByModelKey.get(modelLookupKey);
1647
- }
1648
- }
1649
- //# sourceMappingURL=opencode-agent.js.map