talon-agent 3.32.1 → 3.33.1

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 (138) hide show
  1. package/package.json +1 -1
  2. package/src/backend/claude-sdk/constants.ts +1 -6
  3. package/src/backend/claude-sdk/index.ts +2 -11
  4. package/src/backend/claude-sdk/models/parsing.ts +2 -2
  5. package/src/backend/claude-sdk/plan-usage.ts +0 -5
  6. package/src/backend/claude-sdk/stream.ts +1 -1
  7. package/src/backend/codex/auth.ts +2 -2
  8. package/src/backend/codex/constants.ts +0 -21
  9. package/src/backend/codex/handler/index.ts +0 -1
  10. package/src/backend/codex/mcp-config.ts +2 -2
  11. package/src/backend/codex/plan-usage.ts +0 -5
  12. package/src/backend/codex/state.ts +1 -1
  13. package/src/backend/codex/token-usage.ts +1 -1
  14. package/src/backend/kilo/factory.ts +39 -119
  15. package/src/backend/kilo/handler/index.ts +1 -11
  16. package/src/backend/kilo/handler/message.ts +20 -317
  17. package/src/backend/kilo/index.ts +0 -6
  18. package/src/backend/kilo/server.ts +55 -292
  19. package/src/backend/kilo/sessions.ts +5 -86
  20. package/src/backend/openai-agents/handler/index.ts +0 -1
  21. package/src/backend/openai-agents/handler/state.ts +0 -5
  22. package/src/backend/openai-agents/init.ts +1 -18
  23. package/src/backend/opencode/factory.ts +41 -118
  24. package/src/backend/opencode/handler/index.ts +1 -11
  25. package/src/backend/opencode/handler/message.ts +20 -321
  26. package/src/backend/opencode/index.ts +0 -4
  27. package/src/backend/opencode/server.ts +56 -225
  28. package/src/backend/opencode/sessions.ts +5 -86
  29. package/src/backend/remote-server/chat-turn.ts +359 -0
  30. package/src/backend/remote-server/client.ts +1 -1
  31. package/src/backend/remote-server/events.ts +1 -1
  32. package/src/backend/remote-server/factory.ts +150 -0
  33. package/src/backend/remote-server/index.ts +15 -43
  34. package/src/backend/remote-server/mcp.ts +1 -4
  35. package/src/backend/remote-server/model-catalog/catalog.ts +1 -1
  36. package/src/backend/remote-server/model-catalog/index.ts +1 -15
  37. package/src/backend/remote-server/model-catalog/provider.ts +0 -1
  38. package/src/backend/remote-server/server-bindings.ts +232 -0
  39. package/src/backend/remote-server/sse-stream.ts +1 -3
  40. package/src/backend/remote-server/turn-timeout.ts +1 -1
  41. package/src/backend/{kilo/handler → remote-server}/turn.ts +103 -67
  42. package/src/backend/shared/index.ts +7 -43
  43. package/src/cli/logs.ts +1 -1
  44. package/src/cli/status.ts +1 -1
  45. package/src/core/agent-runtime/backend-registry.ts +1 -1
  46. package/src/core/agent-runtime/capabilities.ts +1 -1
  47. package/src/core/agent-runtime/events.ts +1 -1
  48. package/src/core/background/heartbeat/agent.ts +1 -1
  49. package/src/core/background/heartbeat/index.ts +1 -2
  50. package/src/core/background/isolated-agent.ts +1 -1
  51. package/src/core/background/triggers/exit.ts +1 -1
  52. package/src/core/background/triggers/index.ts +1 -1
  53. package/src/core/bus/events.ts +2 -2
  54. package/src/core/doctor.ts +1 -1
  55. package/src/core/engine/backend-controller/index.ts +1 -3
  56. package/src/core/engine/backend-controller/rebind.ts +2 -2
  57. package/src/core/engine/dispatcher.ts +1 -15
  58. package/src/core/engine/gateway-actions/index.ts +0 -3
  59. package/src/core/engine/gateway-actions/types.ts +1 -1
  60. package/src/core/frontend-runtime/capabilities.ts +0 -2
  61. package/src/core/frontend-runtime/index.ts +1 -6
  62. package/src/core/frontend-runtime/routing.ts +0 -1
  63. package/src/core/mcp-hub/children.ts +0 -5
  64. package/src/core/mesh/bridge-links.ts +319 -0
  65. package/src/core/mesh/common.ts +30 -0
  66. package/src/core/mesh/device-files.ts +710 -0
  67. package/src/core/mesh/index.ts +1 -18
  68. package/src/core/mesh/service.ts +72 -894
  69. package/src/core/models/active-model.ts +0 -19
  70. package/src/core/plugin/index.ts +0 -1
  71. package/src/core/plugin/registry.ts +1 -1
  72. package/src/core/prompt/templates.ts +0 -5
  73. package/src/core/scripting/lua-runner.ts +1 -1
  74. package/src/core/scripts/runner.ts +1 -1
  75. package/src/core/soul/critic.ts +1 -1
  76. package/src/core/soul/embedder.ts +2 -2
  77. package/src/core/soul/governance.ts +1 -1
  78. package/src/core/soul/hdc.ts +1 -1
  79. package/src/core/soul/reflex.ts +2 -2
  80. package/src/core/soul/retrieve.ts +1 -1
  81. package/src/core/soul/signals.ts +4 -4
  82. package/src/core/soul/types.ts +1 -1
  83. package/src/core/tasks/index.ts +0 -4
  84. package/src/core/tasks/types.ts +1 -1
  85. package/src/core/tools/index.ts +1 -6
  86. package/src/core/tools/mcp-env.ts +3 -33
  87. package/src/core/types.ts +1 -1
  88. package/src/core/update/self-update.ts +1 -1
  89. package/src/core/vfs/fusefs.ts +1 -1
  90. package/src/core/vfs/types.ts +1 -1
  91. package/src/core/weaver/index.ts +1 -1
  92. package/src/core/weaver/typing-loop.ts +1 -1
  93. package/src/frontend/discord/actions/types.ts +1 -1
  94. package/src/frontend/discord/commands/definitions.ts +1 -1
  95. package/src/frontend/discord/handlers/access.ts +7 -61
  96. package/src/frontend/discord/handlers/index.ts +2 -15
  97. package/src/frontend/discord/handlers/registry.ts +0 -6
  98. package/src/frontend/discord/handlers/state.ts +15 -8
  99. package/src/frontend/native/protocol.ts +1 -4
  100. package/src/frontend/native/server.ts +392 -324
  101. package/src/frontend/shared/access.ts +152 -0
  102. package/src/frontend/shared/status-context.ts +1 -1
  103. package/src/frontend/telegram/actions/types.ts +1 -1
  104. package/src/frontend/telegram/commands/index.ts +1 -1
  105. package/src/frontend/telegram/commands/state.ts +1 -1
  106. package/src/frontend/telegram/handlers/access.ts +8 -25
  107. package/src/frontend/telegram/handlers/index.ts +0 -1
  108. package/src/frontend/telegram/handlers/queue.ts +3 -35
  109. package/src/frontend/telegram/handlers/state.ts +15 -8
  110. package/src/frontend/telegram/helpers/menu.ts +1 -1
  111. package/src/frontend/terminal/commands.ts +1 -4
  112. package/src/frontend/terminal/renderer.ts +1 -1
  113. package/src/frontend/whatsapp/actions/chat-info.ts +0 -3
  114. package/src/frontend/whatsapp/actions/messaging.ts +1 -7
  115. package/src/frontend/whatsapp/actions/shared.ts +1 -1
  116. package/src/frontend/whatsapp/actions/types.ts +1 -1
  117. package/src/frontend/whatsapp/formatting.ts +1 -1
  118. package/src/frontend/whatsapp/media-store.ts +1 -1
  119. package/src/frontend/whatsapp/pins.ts +0 -5
  120. package/src/native/fusefs.ts +3 -3
  121. package/src/native/scheduler-core.ts +1 -1
  122. package/src/native/warden.ts +2 -2
  123. package/src/plugins/mempalace/provision.ts +1 -5
  124. package/src/plugins/playwright/provision.ts +1 -1
  125. package/src/storage/db.ts +1 -1
  126. package/src/storage/goal-store.ts +1 -5
  127. package/src/storage/metrics.ts +0 -5
  128. package/src/storage/repositories/scripts-repo.ts +0 -5
  129. package/src/storage/scheduled-store.ts +1 -5
  130. package/src/storage/script-store.ts +5 -13
  131. package/src/storage/sessions.ts +0 -3
  132. package/src/storage/skill-store.ts +3 -3
  133. package/src/util/exec-output.ts +1 -1
  134. package/src/util/mcp-launcher.ts +3 -0
  135. package/src/backend/kilo/events.ts +0 -60
  136. package/src/backend/kilo/handler/state.ts +0 -9
  137. package/src/backend/opencode/handler/state.ts +0 -9
  138. package/src/backend/opencode/handler/turn.ts +0 -314
@@ -1,130 +1,53 @@
1
1
  /**
2
2
  * OpenCode backend factory — wires OpenCode into the registry.
3
3
  *
4
- * Mirrors the Kilo factory in structure. OpenCode and Kilo share the
5
- * same protocol shape (Kilo is a fork) so the adapter logic is nearly
6
- * identical — only the SDK import and a few constant names differ.
7
- *
8
- * Returns a composed `Backend` with capability slots for chat,
9
- * background, models, and usage.
4
+ * Side-effect import (`import "./factory.js"` from `bootstrap.ts`) calls
5
+ * `registerBackend(...)` at module load, making OpenCode available under
6
+ * `config.backend === "opencode"`. The capability wiring is the shared
7
+ * remote-server composition; only the bound functions are OpenCode's.
10
8
  */
11
9
 
12
10
  import { registerBackend } from "../../core/agent-runtime/backend-registry.js";
13
- import type { BackendFactory } from "../../core/agent-runtime/backend-registry.js";
14
- import { log } from "../../util/log.js";
15
- import { handlerToEvents } from "../shared/handler-to-events.js";
16
- import { interruptChatTurn } from "../shared/turn-interrupt.js";
17
- import {
18
- composeBackend,
19
- type ChatBackend,
20
- type BackgroundRunner,
21
- type ModelCatalog,
22
- type SessionBackend,
23
- type SystemControl,
24
- type ToolRuntime,
25
- type UsageTelemetry,
26
- } from "../../core/agent-runtime/capabilities.js";
27
-
11
+ import { createRemoteBackendFactory } from "../remote-server/factory.js";
28
12
  import {
29
13
  initOpenCodeAgent,
30
14
  stopOpenCodeServer,
31
- handleMessage as ocHandleMessage,
32
- runOneShotAgent as ocRunOneShotAgent,
15
+ handleMessage,
16
+ runOneShotAgent,
33
17
  getOpenCodeSessionSnapshot,
34
- resolveModel as ocResolveModel,
35
- getModelInfo as ocGetModelInfo,
36
- getSettingsPresentation as ocGetSettingsPresentation,
37
- getProviders as ocGetProviders,
38
- getProviderModels as ocGetProviderModels,
39
- formatModelError as ocFormatModelError,
40
- listModels as ocListModels,
41
- refreshPluginMcpServers as ocRefreshPluginMcpServers,
42
- updateSystemPrompt as ocUpdateSystemPrompt,
43
- warmSession as ocWarmSession,
18
+ resolveModel,
19
+ getModelInfo,
20
+ getSettingsPresentation,
21
+ getProviders,
22
+ getProviderModels,
23
+ formatModelError,
24
+ listModels,
25
+ refreshPluginMcpServers,
26
+ updateSystemPrompt,
27
+ warmSession,
44
28
  } from "./index.js";
45
29
 
46
- const opencodeFactory: BackendFactory = {
47
- id: "opencode",
48
- label: "OpenCode",
49
-
50
- async init(config, ctx) {
51
- initOpenCodeAgent(config, ctx.getBridgePort, ctx.frontendName);
52
- log("bot", "Backend: OpenCode (@opencode-ai/sdk)");
53
-
54
- const chat: ChatBackend = {
55
- runChatTurn: (params) =>
56
- handlerToEvents((p) => ocHandleMessage(p), params),
57
- interruptChatTurn: (chatId) => interruptChatTurn(chatId),
58
- };
59
-
60
- const background: BackgroundRunner = {
61
- runOneShotAgent: (p) => ocRunOneShotAgent(p),
62
- };
63
-
64
- const models: ModelCatalog = {
65
- resolveModelInfo: (q) => ocResolveModel(q),
66
- // Catalog-driven backend with no canonical default.
67
- getDefaultModelId: () => undefined,
68
- getRawModelInfo: (id) => ocGetModelInfo(id),
69
- getSettingsPresentation: (m, options) =>
70
- ocGetSettingsPresentation(m, options),
71
- getProviders: () => ocGetProviders(),
72
- getProviderModels: (p, pg, ps) => ocGetProviderModels(p, pg, ps),
73
- formatModelError: (q, r) => ocFormatModelError(q, r),
74
- listModels: (f) => ocListModels(f),
75
- };
76
-
77
- const usage: UsageTelemetry = {
78
- getSessionSnapshot: async (sessionId) => {
79
- const snap = await getOpenCodeSessionSnapshot(sessionId);
80
- if (!snap) return undefined;
81
- return {
82
- inputTokens: snap.usage?.totalInputTokens,
83
- outputTokens: snap.usage?.totalOutputTokens,
84
- cacheRead: snap.usage?.totalCacheRead,
85
- cacheWrite: snap.usage?.totalCacheWrite,
86
- contextModelId: snap.assistant?.modelID,
87
- };
88
- },
89
- };
90
-
91
- const tools: ToolRuntime = {
92
- refreshTools: (chatId) => ocRefreshPluginMcpServers(chatId),
93
- };
94
-
95
- // Session state lives on the OpenCode server, and `/reset` already
96
- // clears Talon's stored id centrally (storage/sessions.ts), so the
97
- // next turn creates a fresh one — no `resetChat` needed. `warmSession`
98
- // front-loads that creation plus the plugin-MCP sweep, matching what
99
- // the Claude backend does after a reset.
100
- const sessions: SessionBackend = {
101
- warmSession: (chatId) => ocWarmSession(chatId),
102
- };
103
-
104
- const control: SystemControl = {
105
- updateSystemPrompt: (prompt) => ocUpdateSystemPrompt(prompt),
106
- };
107
-
108
- const backend = composeBackend({
109
- id: "opencode",
110
- label: "OpenCode",
111
- cacheMetrics: "readwrite",
112
- chat,
113
- background,
114
- models,
115
- sessions,
116
- tools,
117
- usage,
118
- control,
119
- });
120
-
121
- return {
122
- backend,
123
- cleanup: () => {
124
- stopOpenCodeServer();
125
- },
126
- };
127
- },
128
- };
129
-
130
- registerBackend(opencodeFactory);
30
+ registerBackend(
31
+ createRemoteBackendFactory({
32
+ id: "opencode",
33
+ label: "OpenCode",
34
+ sdkPackage: "@opencode-ai/sdk",
35
+ init: initOpenCodeAgent,
36
+ stop: stopOpenCodeServer,
37
+ handleMessage,
38
+ runOneShotAgent,
39
+ getSessionSnapshot: getOpenCodeSessionSnapshot,
40
+ models: {
41
+ resolveModel,
42
+ getModelInfo,
43
+ getSettingsPresentation,
44
+ getProviders,
45
+ getProviderModels,
46
+ formatModelError,
47
+ listModels,
48
+ },
49
+ refreshPluginMcpServers,
50
+ warmSession,
51
+ updateSystemPrompt,
52
+ }),
53
+ );
@@ -1,12 +1,2 @@
1
- /**
2
- * OpenCode message handler.
3
- *
4
- * Split by responsibility (parallel to the kilo handler/ layout):
5
- * - `state` — in-flight session registry (activeSessions)
6
- * - `turn` — runOpenCodeTurn + SSE subscription + last-assistant lookup
7
- * - `message` — the handleMessage entry point + post-loop accounting
8
- *
9
- * Re-exports the same public surface the old single-file module exposed.
10
- */
11
-
1
+ /** OpenCode message handler barrel. */
12
2
  export { handleMessage } from "./message.js";
@@ -1,24 +1,10 @@
1
1
  /**
2
- * OpenCode main message handler.
3
- *
4
- * Orchestrates the full turn lifecycle on top of OpenCode's HTTP API + SSE
5
- * event stream. Behaviour mirrors the Kilo backend and shares its
6
- * non-SDK-specific primitives via `../../shared/` and `../../remote-server/`.
7
- * The OpenCode-specific turn driver + SSE subscription live in `turn.ts`.
2
+ * OpenCode main message handler — the shared remote-server chat turn
3
+ * bound to OpenCode's server bindings.
8
4
  */
9
5
 
10
- import {
11
- getSession,
12
- incrementTurns,
13
- recordUsage,
14
- setSessionId,
15
- setSessionName,
16
- } from "../../../storage/sessions.js";
17
- import { getChatSettings } from "../../../storage/chat-settings.js";
18
- import { log, logError } from "../../../util/log.js";
19
- import { traceMessage } from "../../../util/trace.js";
20
6
  import type { QueryParams, QueryResult } from "../../shared/handler-types.js";
21
-
7
+ import { runRemoteChatTurn } from "../../remote-server/chat-turn.js";
22
8
  import {
23
9
  ensureServer,
24
10
  ensureSession,
@@ -30,311 +16,24 @@ import {
30
16
  getConfig,
31
17
  opencodeSystemPromptSuffix,
32
18
  } from "../server.js";
33
- import {
34
- frontendsForChat,
35
- nonTerminalFrontends,
36
- } from "../../shared/frontends.js";
37
- import { getOpenCodeTurnSummary } from "../sessions.js";
38
- import {
39
- createStreamState,
40
- recordTokens,
41
- finalizeResponseText,
42
- formatUserPrompt,
43
- prepareSystemPrompt,
44
- extractSessionName,
45
- summarizeUsage,
46
- routeDelivery,
47
- applyRetryDecision,
48
- recordTurnMetrics,
49
- recordFailedTurnAccounting,
50
- registerTurnInterrupt,
51
- } from "../../shared/index.js";
52
- import { activeSessions } from "./state.js";
53
- import { runOpenCodeTurn } from "./turn.js";
54
-
55
- export async function handleMessage(
56
- params: QueryParams,
57
- _retried = false,
58
- ): Promise<QueryResult> {
59
- const config = getConfig();
60
- if (!config) throw new Error("OpenCode agent not initialized");
61
-
62
- const {
63
- chatId,
64
- text,
65
- senderName,
66
- senderHandle,
67
- isGroup,
68
- messageId,
69
- onTextBlock,
70
- onToolUse,
71
- } = params;
72
- const t0 = Date.now();
73
- const session = getSession(chatId);
74
- const previousTurns = session.turns;
75
-
76
- // Resolve active model + provider lookup against OpenCode's catalog
77
- const chatSettings = getChatSettings(chatId);
78
- const activeModel = params.model ?? chatSettings.model ?? config.model;
79
- const { providerID: selectedProviderID, modelID } =
80
- parseStoredOpenCodeModelSelection(activeModel);
81
-
82
- const oc = await ensureServer();
83
- const providerID =
84
- selectedProviderID ?? (await resolveProviderID(oc, modelID));
85
- log(
86
- "agent",
87
- `[${chatId}] OpenCode model resolved: provider=${providerID} model=${modelID}` +
88
- (selectedProviderID ? "" : " (provider via catalog lookup)"),
89
- );
90
- const sessionId = await ensureSession(oc, chatId);
91
- const chatMcpServerName = await ensureChatMcpServer(oc, chatId);
92
- const pluginMcpServerNames = await ensurePluginMcpServers(oc, chatId);
93
- const toolOverrides = await buildToolOverrides(
94
- oc,
95
- chatMcpServerName,
96
- pluginMcpServerNames,
97
- );
98
-
99
- // Build the prompt (time tag + sender + msg_id reference)
100
- const prompt = formatUserPrompt({
101
- text,
102
- senderName: senderName ?? "user",
103
- senderHandle,
104
- isGroup,
105
- messageId,
106
- });
107
-
108
- // Per-session frozen prompt + OpenCode-specific delivery suffix
109
- const { text: systemPrompt } = prepareSystemPrompt({
110
- config,
111
- previousTurns,
112
- backendSuffix: opencodeSystemPromptSuffix(
113
- frontendsForChat(chatId, nonTerminalFrontends(config.frontend))[0] ??
114
- "telegram",
115
- ),
116
- chatId,
117
- sessionEpoch: session.createdAt,
118
- });
119
-
120
- log("agent", `[${chatId}] <- (${text.length} chars)`);
121
- traceMessage(chatId, "in", text, { senderName, isGroup });
122
- activeSessions.set(chatId, sessionId);
123
-
124
- // Bind the stream state to the chat so token mutators mirror counts
125
- // into the live-turn overlay — /status updates while the turn runs.
126
- const state = createStreamState(chatId);
127
- state.newSessionId = sessionId;
128
- // A user interrupt is a synthetic turn terminator: marking the flag
129
- // before aborting the session routes the close through the same clean
130
- // path a model-fired end_turn takes (MessageAborted swallowed as the
131
- // expected close, no retry), settling with whatever the turn produced.
132
- const unregisterInterrupt = registerTurnInterrupt(chatId, async () => {
133
- state.turnTerminated = true;
134
- await oc.session.abort({ sessionID: sessionId });
135
- });
136
- const promptStartedAt = Date.now();
137
- const seenQuestionIds = new Set<string>();
138
- const seenPermissionIds = new Set<string>();
139
- const seenToolCallIds = new Set<string>();
140
-
141
- const setupMs = Date.now() - t0;
142
- let promptMs = 0;
143
-
144
- try {
145
- // Drive the OpenCode turn: subscribe to SSE events in parallel with
146
- // promptAsync, surface tool calls + terminator into shared state, and
147
- // exit when the turn closes / goes idle. `onStreamDelta` is
148
- // intentionally NOT forwarded — Telegram's contract is "send the final
149
- // reply once", not live edit_message updates. Final delivery happens
150
- // through `end_turn` / `send`.
151
- const turnStart = Date.now();
152
- await runOpenCodeTurn({
153
- oc,
154
- sessionId,
155
- prompt,
156
- systemPrompt,
157
- providerID,
158
- modelID,
159
- state,
160
- chatId,
161
- seenQuestionIds,
162
- seenPermissionIds,
163
- seenToolCallIds,
164
- toolOverrides,
165
- onStreamDelta: undefined,
166
- onTextBlock,
167
- onToolUse,
168
- });
169
- promptMs = Date.now() - turnStart;
170
- } catch (err) {
171
- const outcome = await applyRetryDecision({
172
- err,
173
- chatId,
174
- activeModel,
175
- retried: _retried,
176
- params,
177
- recurseWithRetried: (p) => handleMessage(p, true),
178
- backendLabel: "OpenCode",
179
- });
180
- if (outcome.retry) return outcome.retry;
181
19
 
182
- // Terminal failure — account for whatever the turn consumed before
183
- // dying and drop the live overlay (the retry path above did its own
184
- // accounting inside the recursive attempt).
185
- recordFailedTurnAccounting({
186
- backend: "opencode",
187
- chatId,
188
- durationMs: Date.now() - t0,
189
- toolCalls: state.toolCalls,
190
- apiCalls: state.numApiCalls,
191
- model: activeModel,
192
- usage: {
193
- inputTokens: state.sdkInputTokens,
194
- outputTokens: state.sdkOutputTokens,
195
- cacheRead: state.sdkCacheRead,
196
- cacheWrite: state.sdkCacheWrite,
197
- },
198
- contextTokens: state.contextTokens,
199
- contextWindow: state.contextWindow,
200
- });
201
-
202
- logError(
203
- "agent",
204
- `[${chatId}] OpenCode error: ${outcome.classified.message}`,
205
- );
206
- throw outcome.classified;
207
- } finally {
208
- unregisterInterrupt();
209
- if (activeSessions.get(chatId) === sessionId) {
210
- activeSessions.delete(chatId);
211
- }
212
- // Note: we deliberately do NOT disconnect the chat MCP server here.
213
- // The server is named per-chat so it's safe to keep across turns;
214
- // re-spawning the subprocess each turn cost ~800ms per message.
215
- // Per-prompt overrides isolate visibility across concurrent chats.
216
- }
217
-
218
- // ── Post-loop accounting ──────────────────────────────────────────────────
219
-
220
- // If the SSE loop missed any usage info, fall back to the session
221
- // summary endpoint (which always reflects the final server state).
222
- if (
223
- state.sdkInputTokens === 0 &&
224
- state.sdkOutputTokens === 0 &&
225
- state.sdkCacheRead === 0
226
- ) {
227
- try {
228
- const summary = await getOpenCodeTurnSummary(
229
- oc,
230
- sessionId,
231
- promptStartedAt,
232
- );
233
- if (summary.usage.assistantMessages > 0) {
234
- recordTokens(state, {
235
- inputTokens: summary.usage.inputTokens,
236
- outputTokens: summary.usage.outputTokens,
237
- cacheRead: summary.usage.cacheRead,
238
- cacheWrite: summary.usage.cacheWrite,
239
- });
240
- }
241
- } catch {
242
- // best-effort — session summaries can race on cancellation
243
- }
244
- }
245
-
246
- const responseText = finalizeResponseText(state);
247
- const durationMs = Date.now() - t0;
248
- recordTurnMetrics({
249
- chatId,
250
- backend: "opencode",
251
- durationMs,
252
- toolCalls: state.toolCalls,
253
- apiCalls: state.numApiCalls,
254
- usage: {
255
- inputTokens: state.sdkInputTokens,
256
- outputTokens: state.sdkOutputTokens,
257
- cacheRead: state.sdkCacheRead,
258
- cacheWrite: state.sdkCacheWrite,
20
+ export function handleMessage(params: QueryParams): Promise<QueryResult> {
21
+ // Assembled per call, not at module load: tests mock `../server.js`
22
+ // with a partial surface and only touch the members they need.
23
+ return runRemoteChatTurn(
24
+ {
25
+ id: "opencode",
26
+ label: "OpenCode",
27
+ getConfig,
28
+ ensureServer,
29
+ parseModelSelection: parseStoredOpenCodeModelSelection,
30
+ resolveProviderID,
31
+ ensureSession,
32
+ ensureChatMcpServer,
33
+ ensurePluginMcpServers,
34
+ buildToolOverrides,
35
+ systemPromptSuffix: opencodeSystemPromptSuffix,
259
36
  },
260
- });
261
-
262
- if (state.newSessionId) {
263
- const stored = getSession(chatId).sessionId;
264
- if (stored !== state.newSessionId) {
265
- setSessionId(chatId, state.newSessionId);
266
- }
267
- }
268
-
269
- incrementTurns(chatId);
270
- recordUsage(chatId, {
271
- inputTokens: state.sdkInputTokens,
272
- outputTokens: state.sdkOutputTokens,
273
- cacheRead: state.sdkCacheRead,
274
- cacheWrite: state.sdkCacheWrite,
275
- durationMs,
276
- model: activeModel,
277
- });
278
-
279
- // Set a descriptive session name from the user's first message
280
- if (previousTurns === 0) {
281
- const name = extractSessionName(text);
282
- if (name) setSessionName(chatId, name);
283
- }
284
-
285
- // ── Delivery — decision tree shared with the Kilo backend ──────────────────
286
- const delivery = await routeDelivery({
287
- backendLabel: "OpenCode",
288
- chatId,
289
- state,
290
- responseText,
291
- onTextBlock,
292
- });
293
-
294
- log(
295
- "agent",
296
- `[${chatId}] delivery: ${delivery.route} (${delivery.chars} chars)`,
297
- );
298
-
299
- log(
300
- "agent",
301
- `[${chatId}] -> (${summarizeUsage(
302
- {
303
- inputTokens: state.sdkInputTokens,
304
- outputTokens: state.sdkOutputTokens,
305
- cacheRead: state.sdkCacheRead,
306
- cacheWrite: state.sdkCacheWrite,
307
- },
308
- { durationMs, toolCalls: state.toolCalls },
309
- )} terminator=${state.turnTerminated ? "yes" : "no"} ` +
310
- `delivered=${state.deliveredTextNorms.length} ` +
311
- `respLen=${responseText.length} ` +
312
- `setup=${setupMs}ms turn=${promptMs}ms ` +
313
- `events=${formatEventCounts(state.eventCounts)})`,
37
+ params,
314
38
  );
315
- traceMessage(chatId, "out", responseText, {
316
- durationMs,
317
- toolCalls: state.toolCalls,
318
- });
319
-
320
- return {
321
- text: responseText,
322
- durationMs,
323
- inputTokens: state.sdkInputTokens,
324
- outputTokens: state.sdkOutputTokens,
325
- cacheRead: state.sdkCacheRead,
326
- cacheWrite: state.sdkCacheWrite,
327
- };
328
- }
329
-
330
- /**
331
- * Compact summary of which SSE event types fired this turn. `none` for a
332
- * silent turn. Otherwise renders as `delta×42,part.updated×1`.
333
- */
334
- function formatEventCounts(counts: Record<string, number>): string {
335
- const entries = Object.entries(counts);
336
- if (entries.length === 0) return "none";
337
- return entries
338
- .map(([type, n]) => `${type.replace(/^(message|session)\./, "")}×${n}`)
339
- .join(",");
340
39
  }
@@ -9,10 +9,6 @@
9
9
  */
10
10
 
11
11
  export {
12
- type OpenCodeModelCatalogEntry,
13
- type OpenCodeModelCatalog,
14
- type OpenCodeModelResolution,
15
- type ModelButton,
16
12
  getOpenCodeModelSelectionValue,
17
13
  resolveOpenCodeModelInput,
18
14
  formatOpenCodeSelectionError,