talon-agent 3.33.0 → 3.33.2

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 (31) hide show
  1. package/package.json +3 -3
  2. package/src/backend/kilo/factory.ts +39 -119
  3. package/src/backend/kilo/handler/index.ts +1 -11
  4. package/src/backend/kilo/handler/message.ts +20 -317
  5. package/src/backend/kilo/server.ts +55 -272
  6. package/src/backend/kilo/sessions.ts +5 -83
  7. package/src/backend/opencode/factory.ts +41 -118
  8. package/src/backend/opencode/handler/index.ts +1 -11
  9. package/src/backend/opencode/handler/message.ts +20 -321
  10. package/src/backend/opencode/server.ts +57 -217
  11. package/src/backend/opencode/sessions.ts +5 -83
  12. package/src/backend/remote-server/chat-turn.ts +359 -0
  13. package/src/backend/remote-server/factory.ts +150 -0
  14. package/src/backend/remote-server/index.ts +14 -19
  15. package/src/backend/remote-server/server-bindings.ts +232 -0
  16. package/src/backend/{kilo/handler → remote-server}/turn.ts +103 -67
  17. package/src/core/mesh/bridge-links.ts +319 -0
  18. package/src/core/mesh/common.ts +30 -0
  19. package/src/core/mesh/device-files.ts +710 -0
  20. package/src/core/mesh/service.ts +72 -894
  21. package/src/frontend/discord/handlers/access.ts +7 -56
  22. package/src/frontend/discord/handlers/state.ts +15 -8
  23. package/src/frontend/native/server.ts +391 -323
  24. package/src/frontend/shared/access.ts +152 -0
  25. package/src/frontend/telegram/handlers/access.ts +8 -25
  26. package/src/frontend/telegram/handlers/queue.ts +3 -35
  27. package/src/frontend/telegram/handlers/state.ts +15 -8
  28. package/src/backend/kilo/events.ts +0 -60
  29. package/src/backend/kilo/handler/state.ts +0 -9
  30. package/src/backend/opencode/handler/state.ts +0 -9
  31. package/src/backend/opencode/handler/turn.ts +0 -314
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Kilo server lifecycle — thin wrapper around `backend/remote-server/`.
2
+ * Kilo server lifecycle — the Kilo profile bound through
3
+ * `backend/remote-server/server-bindings.ts`.
3
4
  *
4
- * The MCP / session / provider plumbing is identical to OpenCode (Kilo
5
- * is a fork of OpenCode that exposes the same HTTP API), so this module
6
- * keeps only the Kilo-specific bits:
7
- *
8
- * - Constants (port 4097, system-prompt suffix wording).
9
- * - SDK-specific client factory (`createKiloClient` + `createKiloServer`).
10
- * - Backend-specific model parser (the `kilo/` prefix hint).
11
- * - Pre-warm hook that triggers immediately at init.
12
- *
13
- * The shared machinery (server spawn, MCP registration, session creation,
14
- * provider resolution) lives in `backend/remote-server/`. All the
15
- * existing exports from this module are preserved for back-compat with
16
- * the handler / tests / heartbeat code that already imports them.
5
+ * Kilo is a fork of OpenCode with the same HTTP API, so the server spawn,
6
+ * MCP registration, session creation, and provider resolution are the
7
+ * shared family implementation. This module holds only what is Kilo's:
8
+ * the SDK constructors, port 4097, the text-or-tools delivery contract,
9
+ * and the `kilo/`-prefix model parser. The exports keep their historical
10
+ * names — the models module, one-shot runner, tests, and
11
+ * `vi.mock("../backend/kilo/server.js")` all address them.
17
12
  */
18
13
 
19
14
  import {
@@ -21,243 +16,14 @@ import {
21
16
  createKiloServer,
22
17
  type KiloClient,
23
18
  } from "@kilocode/sdk/v2";
24
- import type { TalonConfig } from "../../util/config.js";
25
- import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
26
- import { buildDeliveryContract } from "../shared/delivery-contract.js";
27
- import {
28
- guessProviderID,
29
- getBucketPriority,
30
- } from "../remote-server/model-catalog/index.js";
31
19
  import {
32
- createRemoteServerState,
33
- ensureRemoteServer as ensureRemoteServerShared,
34
- stopRemoteServer,
35
- ensureChatMcpServer as ensureChatMcpServerShared,
36
- ensurePluginMcpServers as ensurePluginMcpServersShared,
37
- buildToolOverrides as buildToolOverridesShared,
38
- disconnectChatMcpServer as disconnectChatMcpServerShared,
39
- refreshPluginMcpServers as refreshPluginMcpServersShared,
40
- ensureRemoteSession,
41
- warmRemoteSession,
42
- resolveProviderID as resolveProviderIDShared,
43
- getRegisteredMcpServerNames as getRegisteredMcpServerNamesShared,
44
- errMsg as sharedErrMsg,
45
- type RemoteServerState,
46
- } from "../remote-server/index.js";
47
-
48
- // ── Constants ───────────────────────────────────────────────────────────────
49
-
50
- /** Loopback hostname Talon binds the Kilo server on — never exposed externally. */
51
- const KILO_HOSTNAME = "127.0.0.1";
52
- /** Default TCP port for the local Kilo server. Overridable via `KILO_PORT`
53
- * env var so integration tests can spawn an isolated Kilo server alongside
54
- * a running production Talon (which holds the default 4097). */
55
- const KILO_PORT = Number(process.env.KILO_PORT ?? 4097);
56
- /** Convenience URL composed from KILO_HOSTNAME + KILO_PORT. */
57
- export const KILO_BASE_URL = `http://${KILO_HOSTNAME}:${KILO_PORT}`;
58
- /**
59
- * System-prompt suffix appended to the user-configured system prompt.
60
- *
61
- * Kilo delivery model: the model's reply reaches the user via either
62
- *
63
- * 1. A `type: "text"` part — what most Kilo-routed models emit by
64
- * default (DeepSeek, GLM, openrouter routes). Talon walks the
65
- * `parts` list at end of turn and ships text-part content via
66
- * `onTextBlock`. Reasoning parts are dropped as scratchpad.
67
- *
68
- * 2. A delivery tool — `end_turn` / `send` / `react`. The tool itself
69
- * bridges to Telegram, so it's the right path when you need
70
- * reply-to targeting, buttons, photos, polls, etc.
71
- *
72
- * Both routes work; pick whichever fits the message. The shared
73
- * text-or-tools contract (prompts/system/contract-text-or-tools.md)
74
- * documents the choice — the tool descriptions carry the detail.
75
- * Telegram-shaped tool names: this constant is static and the prior
76
- * hand-written text hardcoded the same names.
77
- */
78
- export function kiloSystemPromptSuffix(frontend: string): string {
79
- return `\n\n${buildDeliveryContract("text-or-tools", frontend)}\n`;
80
- }
81
-
82
- /** Telegram-shaped default, kept for one-shot (cross-surface) paths. */
83
- export const KILO_SYSTEM_PROMPT_SUFFIX = kiloSystemPromptSuffix("telegram");
84
-
85
- // ── State ───────────────────────────────────────────────────────────────────
86
-
87
- const state: RemoteServerState<KiloClient> =
88
- createRemoteServerState<KiloClient>({
89
- label: "Kilo",
90
- hostname: KILO_HOSTNAME,
91
- port: KILO_PORT,
92
- });
93
-
94
- function createStrictKiloClient(baseUrl: string): KiloClient {
95
- return createKiloClient({
96
- baseUrl,
97
- throwOnError: true,
98
- });
99
- }
100
-
101
- // ── Init / teardown ─────────────────────────────────────────────────────────
102
-
103
- /**
104
- * Initialise the Kilo backend.
105
- *
106
- * Stores the config + gateway-port resolver + frontend label needed by
107
- * downstream helpers. Does NOT spawn the Kilo server — that happens
108
- * lazily on the first `ensureServer()` call (so empty / heartbeat-only
109
- * Talon installs don't pay the startup cost until they need it).
110
- */
111
- export function initKiloAgent(
112
- cfg: TalonConfig,
113
- getGatewayPort?: () => number,
114
- frontend?: FrontendName,
115
- ): void {
116
- state.config = cfg;
117
- if (getGatewayPort) state.gatewayPortFn = getGatewayPort;
118
- if (frontend) state.frontendName = frontend;
119
- }
120
-
121
- /**
122
- * Callbacks run when the server stops — cache invalidation lives with the
123
- * caches. The models module registers its clear here at load time, so the
124
- * server never has to import it (which would be a cycle).
125
- */
126
- const stopHooks = new Set<() => void>();
127
-
128
- /** Register a callback to run whenever the Kilo server is stopped. */
129
- export function onServerStop(hook: () => void): void {
130
- stopHooks.add(hook);
131
- }
132
-
133
- /**
134
- * Stop the local Kilo server (if we own it) and clear caches.
135
- *
136
- * Idempotent: safe to call multiple times. If we reused a pre-existing
137
- * server, this leaves it running — we don't own it.
138
- */
139
- export function stopKiloServer(): void {
140
- stopRemoteServer(state, () => {
141
- for (const hook of stopHooks) hook();
142
- });
143
- }
144
-
145
- // ── Server lifecycle ────────────────────────────────────────────────────────
146
-
147
- /**
148
- * Lazily start (or reuse) the local Kilo server and return a strict client.
149
- *
150
- * Delegates to the shared `ensureRemoteServer` helper which handles the
151
- * spawn-race, the reuse-existing-server probe (`/global/health`), and
152
- * the wrap-after-bind fallback.
153
- */
154
- export function ensureServer(): Promise<KiloClient> {
155
- return ensureRemoteServerShared({
156
- state,
157
- createClient: createStrictKiloClient,
158
- createServer: ({ hostname, port, timeout }) =>
159
- createKiloServer({ hostname, port, timeout }),
160
- });
161
- }
162
-
163
- // ── MCP server registration ─────────────────────────────────────────────────
164
-
165
- /**
166
- * Ensure the per-chat Talon MCP server is registered with Kilo. See
167
- * `backend/remote-server/mcp.ts` for the full rationale + visibility-model
168
- * explanation; this wrapper just passes the Kilo state through.
169
- */
170
- export function ensureChatMcpServer(
171
- oc: KiloClient,
172
- chatId: string,
173
- ): Promise<string> {
174
- return ensureChatMcpServerShared(oc, state, chatId);
175
- }
176
-
177
- /** Register all plugin-provided MCP servers with Kilo. */
178
- export function ensurePluginMcpServers(
179
- oc: KiloClient,
180
- chatId: string,
181
- ): Promise<string[]> {
182
- return ensurePluginMcpServersShared(oc, state, chatId);
183
- }
20
+ bindRemoteServer,
21
+ type RemoteModelSelection,
22
+ } from "../remote-server/server-bindings.js";
184
23
 
185
24
  /**
186
- * Build a `tools` override map that enables ONLY this chat's Talon tools.
187
- *
188
- * Kilo's session-level `permission` rules cover *execution* hiding; this
189
- * `tools` map covers *visibility* — necessary because Kilo exposes every
190
- * registered MCP server's tools to every session by default.
191
- */
192
- export function buildToolOverrides(
193
- oc: KiloClient,
194
- chatServerName: string,
195
- pluginServerNames: readonly string[] = [],
196
- ): Promise<Record<string, boolean> | undefined> {
197
- return buildToolOverridesShared(oc, state, chatServerName, pluginServerNames);
198
- }
199
-
200
- /** Disconnect a per-chat MCP server (explicit teardown for hot-swap paths). */
201
- export function disconnectChatMcpServer(
202
- oc: KiloClient,
203
- serverName: string,
204
- ): Promise<void> {
205
- return disconnectChatMcpServerShared(oc, state, serverName);
206
- }
207
-
208
- export async function refreshPluginMcpServers(chatId: string) {
209
- const oc = await ensureServer();
210
- return refreshPluginMcpServersShared(oc, state, chatId);
211
- }
212
-
213
- export function updateSystemPrompt(prompt: string): void {
214
- if (state.config) state.config.systemPrompt = prompt;
215
- }
216
-
217
- // ── Session management ─────────────────────────────────────────────────────
218
-
219
- /**
220
- * Ensure a Kilo session exists for this chat. Resumes the stored session
221
- * id if `session.get` confirms it's still alive; otherwise resets and
222
- * creates a fresh session with Talon's standard permission ruleset.
223
- */
224
- export function ensureSession(oc: KiloClient, chatId: string): Promise<string> {
225
- return ensureRemoteSession(oc, state, chatId);
226
- }
227
-
228
- /**
229
- * Front-load a chat's cold start after `/reset`. Mirrors the Claude
230
- * backend's `warmSession` so the `sessions` capability slot behaves the
231
- * same across backends. Never throws — see `warmRemoteSession`.
232
- */
233
- export function warmSession(chatId: string): Promise<void> {
234
- return warmRemoteSession(state, chatId, {
235
- ensureServer,
236
- ensureSession,
237
- ensureChatMcpServer,
238
- ensurePluginMcpServers,
239
- });
240
- }
241
-
242
- // ── Provider resolution ────────────────────────────────────────────────────
243
-
244
- /**
245
- * Resolve a model id to its provider id by querying Kilo's provider list.
246
- * The Kilo-specific bucket-priority + name-prefix heuristic comes from
247
- * `./models.ts`.
248
- */
249
- export function resolveProviderID(
250
- oc: KiloClient,
251
- modelID: string,
252
- ): Promise<string> {
253
- return resolveProviderIDShared(oc, state, modelID, {
254
- guessProviderID,
255
- getBucketPriority,
256
- });
257
- }
258
-
259
- /**
260
- * Parse the stored model-selection string into a `{providerID?, modelID}` pair.
25
+ * Parse the stored model-selection string into a `{providerID?, modelID}`
26
+ * pair.
261
27
  *
262
28
  * Kilo model ids frequently contain `/` and `:` inside the model.id itself
263
29
  * (e.g. `inclusionai/ling-2.6-1t:free`, `deepseek/deepseek-v4-flash:free`).
@@ -273,10 +39,9 @@ export function resolveProviderID(
273
39
  * its own provider in front, producing
274
40
  * `Model not found: opencode/kilo/deepseek/deepseek-v4-flash:free`.
275
41
  */
276
- export function parseStoredKiloModelSelection(value: string): {
277
- providerID?: string;
278
- modelID: string;
279
- } {
42
+ export function parseStoredKiloModelSelection(
43
+ value: string,
44
+ ): RemoteModelSelection {
280
45
  const trimmed = value.trim();
281
46
  if (trimmed.startsWith("kilo/")) {
282
47
  return {
@@ -290,25 +55,43 @@ export function parseStoredKiloModelSelection(value: string): {
290
55
  };
291
56
  }
292
57
 
293
- // ── Internal accessors ─────────────────────────────────────────────────────
294
-
295
- export function getConfig(): TalonConfig {
296
- if (!state.config) {
297
- throw new Error("Kilo agent not initialized — call initKiloAgent first");
298
- }
299
- return state.config;
300
- }
301
-
302
58
  /**
303
- * Snapshot of the locally-cached MCP server registrations. Test-only:
304
- * Kilo's `GET /mcp` returns `{}` regardless of state, so integration
305
- * tests use this to assert concurrent chat registrations are retained.
59
+ * Kilo delivery model: the reply reaches the user either as a `text` part
60
+ * (what most Kilo-routed models emit by default — DeepSeek, GLM,
61
+ * openrouter routes) or through a delivery tool (`end_turn` / `send` /
62
+ * `react`) when reply-to targeting, buttons, photos, or polls are needed.
63
+ * Both routes work; the shared text-or-tools contract documents the
64
+ * choice.
306
65
  */
307
- export function getRegisteredMcpServerNames(): string[] {
308
- return getRegisteredMcpServerNamesShared(state);
309
- }
310
-
311
- /** Common error→message helper, re-exported for legacy importers. */
312
- export const errMsg = sharedErrMsg;
313
-
314
- // Re-export the model-helper imports for kilo-internal consumers
66
+ const kilo = bindRemoteServer<KiloClient>({
67
+ label: "Kilo",
68
+ defaultPort: 4097,
69
+ portEnv: "KILO_PORT",
70
+ deliveryContract: "text-or-tools",
71
+ createClient: (baseUrl) => createKiloClient({ baseUrl, throwOnError: true }),
72
+ createServer: ({ hostname, port, timeout }) =>
73
+ createKiloServer({ hostname, port, timeout }),
74
+ parseModelSelection: parseStoredKiloModelSelection,
75
+ });
76
+
77
+ export const KILO_BASE_URL = kilo.baseUrl;
78
+ export const kiloSystemPromptSuffix = kilo.systemPromptSuffix;
79
+ export const KILO_SYSTEM_PROMPT_SUFFIX = kilo.defaultSystemPromptSuffix;
80
+ export const initKiloAgent = kilo.init;
81
+ export const stopKiloServer = kilo.stop;
82
+ export const {
83
+ onServerStop,
84
+ ensureServer,
85
+ ensureChatMcpServer,
86
+ ensurePluginMcpServers,
87
+ buildToolOverrides,
88
+ disconnectChatMcpServer,
89
+ refreshPluginMcpServers,
90
+ updateSystemPrompt,
91
+ ensureSession,
92
+ warmSession,
93
+ resolveProviderID,
94
+ getConfig,
95
+ getRegisteredMcpServerNames,
96
+ errMsg,
97
+ } = kilo;
@@ -1,14 +1,7 @@
1
1
  /**
2
- * Kilo session helpers — thin wrapper around
3
- * `backend/remote-server/session-helpers.ts`.
4
- *
5
- * Kilo organises a session as an ordered list of `messages`, each with a
6
- * typed parts array (text / tool / reasoning / step-start / step-finish).
7
- * The wire format is identical to OpenCode (Kilo is a fork), so the
8
- * actual parsing, summarisation, snapshot construction, and question-
9
- * rejection logic lives in the shared module. This file binds the Kilo
10
- * backend label and re-exports under Kilo-named symbols for back-compat
11
- * with handler / index imports.
2
+ * Kilo session helpers — the shared `remote-server/session-helpers.ts`
3
+ * bound to the Kilo client, re-exported under Kilo-named symbols for the
4
+ * handler / index / test imports.
12
5
  */
13
6
 
14
7
  import type { KiloClient } from "@kilocode/sdk/v2";
@@ -16,100 +9,29 @@ import {
16
9
  extractPartsSummary as extractPartsSummaryShared,
17
10
  extractAssistantUsage as extractAssistantUsageShared,
18
11
  summarizeAssistantMessages,
19
- getTurnSummary,
20
12
  getSessionSnapshot,
21
- rejectPendingQuestions as rejectPendingQuestionsShared,
22
- approvePendingPermissions as approvePendingPermissionsShared,
23
- type RemoteAssistantInfo,
24
13
  type RemoteSessionSnapshot,
25
14
  type RemoteSessionClient,
26
15
  } from "../remote-server/session-helpers.js";
27
16
  import { ensureServer } from "./server.js";
28
17
 
29
- // ── Constants / type aliases (Kilo-named surface) ──────────────────────────
30
-
31
- /** Subset of Kilo's `Message.info` used by Talon for usage accounting. */
32
- export type KiloAssistantInfo = RemoteAssistantInfo;
33
-
34
18
  /** Snapshot of a Kilo session's lifetime + last-turn assistant info. */
35
19
  export type KiloSessionSnapshot = RemoteSessionSnapshot;
36
20
 
37
- // ── Re-exports of pure helpers ─────────────────────────────────────────────
38
-
39
21
  export const extractPartsSummary = extractPartsSummaryShared;
40
22
  export const extractAssistantUsage = extractAssistantUsageShared;
41
23
 
42
- /**
43
- * Summarise a batch of session messages into per-turn usage totals.
44
- * Re-exported under the Kilo-prefixed name for back-compat with
45
- * existing imports.
46
- */
24
+ /** Summarise a batch of session messages into per-turn usage totals. */
47
25
  export const summarizeKiloAssistantMessages = summarizeAssistantMessages;
48
26
 
49
- // ── Session-messages aggregators ──────────────────────────────────────────
50
-
51
- /**
52
- * Aggregate the most recent turn's assistant messages into a usage
53
- * summary. Delegates to the shared helper with a Kilo-typed client.
54
- */
55
- export function getKiloTurnSummary(
56
- oc: KiloClient,
57
- sessionId: string,
58
- minCreatedAt: number,
59
- ): Promise<ReturnType<typeof summarizeAssistantMessages>> {
60
- return getTurnSummary(
61
- oc as unknown as RemoteSessionClient,
62
- sessionId,
63
- minCreatedAt,
64
- );
65
- }
66
-
67
27
  /**
68
28
  * Build a {@link KiloSessionSnapshot} for the given session id.
69
- *
70
29
  * Returns `undefined` if no session id was provided.
71
30
  */
72
31
  export async function getKiloSessionSnapshot(
73
32
  sessionId: string | undefined,
74
33
  ): Promise<KiloSessionSnapshot | undefined> {
75
34
  if (!sessionId) return undefined;
76
- const oc = await ensureServer();
35
+ const oc: KiloClient = await ensureServer();
77
36
  return getSessionSnapshot(oc as unknown as RemoteSessionClient, sessionId);
78
37
  }
79
-
80
- // ── Pending-question guard ─────────────────────────────────────────────────
81
-
82
- /**
83
- * Auto-respond to pending Kilo questions for this session. Delegates to
84
- * the shared helper with the Kilo backend label.
85
- */
86
- export function rejectPendingQuestions(
87
- oc: KiloClient,
88
- sessionId: string,
89
- chatId: string,
90
- seenQuestionIds: Set<string>,
91
- ): Promise<void> {
92
- return rejectPendingQuestionsShared(
93
- oc as unknown as RemoteSessionClient,
94
- sessionId,
95
- chatId,
96
- seenQuestionIds,
97
- "Kilo",
98
- );
99
- }
100
-
101
- /** Auto-approve pending Kilo permissions for this headless session. */
102
- export function approvePendingPermissions(
103
- oc: KiloClient,
104
- sessionId: string,
105
- chatId: string,
106
- seenPermissionIds: Set<string>,
107
- ): Promise<void> {
108
- return approvePendingPermissionsShared(
109
- oc as unknown as RemoteSessionClient,
110
- sessionId,
111
- chatId,
112
- seenPermissionIds,
113
- "Kilo",
114
- );
115
- }
@@ -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";