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.
- package/package.json +3 -3
- package/src/backend/kilo/factory.ts +39 -119
- package/src/backend/kilo/handler/index.ts +1 -11
- package/src/backend/kilo/handler/message.ts +20 -317
- package/src/backend/kilo/server.ts +55 -272
- package/src/backend/kilo/sessions.ts +5 -83
- package/src/backend/opencode/factory.ts +41 -118
- package/src/backend/opencode/handler/index.ts +1 -11
- package/src/backend/opencode/handler/message.ts +20 -321
- package/src/backend/opencode/server.ts +57 -217
- package/src/backend/opencode/sessions.ts +5 -83
- package/src/backend/remote-server/chat-turn.ts +359 -0
- package/src/backend/remote-server/factory.ts +150 -0
- package/src/backend/remote-server/index.ts +14 -19
- package/src/backend/remote-server/server-bindings.ts +232 -0
- package/src/backend/{kilo/handler → remote-server}/turn.ts +103 -67
- package/src/core/mesh/bridge-links.ts +319 -0
- package/src/core/mesh/common.ts +30 -0
- package/src/core/mesh/device-files.ts +710 -0
- package/src/core/mesh/service.ts +72 -894
- package/src/frontend/discord/handlers/access.ts +7 -56
- package/src/frontend/discord/handlers/state.ts +15 -8
- package/src/frontend/native/server.ts +391 -323
- package/src/frontend/shared/access.ts +152 -0
- package/src/frontend/telegram/handlers/access.ts +8 -25
- package/src/frontend/telegram/handlers/queue.ts +3 -35
- package/src/frontend/telegram/handlers/state.ts +15 -8
- package/src/backend/kilo/events.ts +0 -60
- package/src/backend/kilo/handler/state.ts +0 -9
- package/src/backend/opencode/handler/state.ts +0 -9
- package/src/backend/opencode/handler/turn.ts +0 -314
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Kilo server lifecycle —
|
|
2
|
+
* Kilo server lifecycle — the Kilo profile bound through
|
|
3
|
+
* `backend/remote-server/server-bindings.ts`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
*
|
|
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(
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
*
|
|
304
|
-
* Kilo
|
|
305
|
-
*
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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 —
|
|
3
|
-
*
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
|
32
|
-
runOneShotAgent
|
|
15
|
+
handleMessage,
|
|
16
|
+
runOneShotAgent,
|
|
33
17
|
getOpenCodeSessionSnapshot,
|
|
34
|
-
resolveModel
|
|
35
|
-
getModelInfo
|
|
36
|
-
getSettingsPresentation
|
|
37
|
-
getProviders
|
|
38
|
-
getProviderModels
|
|
39
|
-
formatModelError
|
|
40
|
-
listModels
|
|
41
|
-
refreshPluginMcpServers
|
|
42
|
-
updateSystemPrompt
|
|
43
|
-
warmSession
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
initOpenCodeAgent
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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";
|