@rkat/sdk 0.3.4 → 0.4.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.
- package/dist/client.d.ts +136 -83
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +660 -311
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +251 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +179 -0
- package/dist/events.js.map +1 -0
- package/dist/generated/types.d.ts +25 -1
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/generated/types.js +2 -2
- package/dist/index.d.ts +27 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -4
- package/dist/index.js.map +1 -1
- package/dist/session.d.ts +96 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +131 -0
- package/dist/session.js.map +1 -0
- package/dist/streaming.d.ts +68 -14
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +165 -60
- package/dist/streaming.js.map +1 -1
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
- package/dist/capabilities.d.ts +0 -24
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/capabilities.js +0 -36
- package/dist/capabilities.js.map +0 -1
- package/dist/skills.d.ts +0 -41
- package/dist/skills.d.ts.map +0 -1
- package/dist/skills.js +0 -57
- package/dist/skills.js.map +0 -1
package/dist/client.js
CHANGED
|
@@ -1,25 +1,640 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Meerkat client — spawns rkat-rpc
|
|
2
|
+
* Meerkat client — spawns rkat-rpc and communicates via JSON-RPC.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { MeerkatClient } from "@rkat/sdk";
|
|
7
|
+
*
|
|
8
|
+
* const client = new MeerkatClient();
|
|
9
|
+
* await client.connect();
|
|
10
|
+
*
|
|
11
|
+
* const session = await client.createSession("Hello!");
|
|
12
|
+
* console.log(session.text);
|
|
13
|
+
*
|
|
14
|
+
* const result = await session.turn("Tell me more");
|
|
15
|
+
* console.log(result.text);
|
|
16
|
+
*
|
|
17
|
+
* for await (const event of session.stream("Explain in detail")) {
|
|
18
|
+
* if (event.type === "text_delta") {
|
|
19
|
+
* process.stdout.write(event.delta);
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* await session.archive();
|
|
24
|
+
* await client.close();
|
|
25
|
+
* ```
|
|
3
26
|
*/
|
|
4
27
|
import { spawn } from "node:child_process";
|
|
5
28
|
import { chmodSync, existsSync, mkdirSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
6
29
|
import os from "node:os";
|
|
7
30
|
import path from "node:path";
|
|
8
31
|
import { createInterface } from "node:readline";
|
|
9
|
-
import { MeerkatError } from "./generated/errors.js";
|
|
10
|
-
import { CONTRACT_VERSION } from "./generated/types.js";
|
|
32
|
+
import { MeerkatError, CapabilityUnavailableError } from "./generated/errors.js";
|
|
33
|
+
import { CONTRACT_VERSION, } from "./generated/types.js";
|
|
34
|
+
import { Session } from "./session.js";
|
|
35
|
+
import { CommsEventStream, EventStream, AsyncQueue } from "./streaming.js";
|
|
11
36
|
const MEERKAT_REPO = "lukacf/meerkat";
|
|
12
37
|
const MEERKAT_RELEASE_BINARY = "rkat-rpc";
|
|
13
38
|
const MEERKAT_BINARY_CACHE_ROOT = path.join(os.homedir(), ".cache", "meerkat", "bin", MEERKAT_RELEASE_BINARY);
|
|
39
|
+
/**
|
|
40
|
+
* Normalize a SkillRef to the wire format { source_uuid, skill_name }.
|
|
41
|
+
*
|
|
42
|
+
* SkillKey objects are converted from camelCase to snake_case.
|
|
43
|
+
* Legacy strings are parsed and emit a console warning.
|
|
44
|
+
*/
|
|
45
|
+
function normalizeSkillRef(ref) {
|
|
46
|
+
if (typeof ref !== "string") {
|
|
47
|
+
return { source_uuid: ref.sourceUuid, skill_name: ref.skillName };
|
|
48
|
+
}
|
|
49
|
+
const value = ref.startsWith("/") ? ref.slice(1) : ref;
|
|
50
|
+
const [sourceUuid, ...rest] = value.split("/");
|
|
51
|
+
if (!sourceUuid || rest.length === 0) {
|
|
52
|
+
throw new Error(`Invalid skill reference '${ref}'. Expected '<source_uuid>/<skill_name>'.`);
|
|
53
|
+
}
|
|
54
|
+
console.warn(`[meerkat-sdk] legacy skill reference '${ref}' is deprecated; pass { sourceUuid, skillName } instead.`);
|
|
55
|
+
return { source_uuid: sourceUuid, skill_name: rest.join("/") };
|
|
56
|
+
}
|
|
57
|
+
function skillRefsToWire(refs) {
|
|
58
|
+
if (!refs)
|
|
59
|
+
return undefined;
|
|
60
|
+
return refs.map(normalizeSkillRef);
|
|
61
|
+
}
|
|
14
62
|
export class MeerkatClient {
|
|
15
63
|
process = null;
|
|
16
64
|
requestId = 0;
|
|
17
|
-
|
|
65
|
+
_capabilities = [];
|
|
18
66
|
rkatPath;
|
|
19
67
|
pendingRequests = new Map();
|
|
68
|
+
eventQueues = new Map();
|
|
69
|
+
commsStreamQueues = new Map();
|
|
70
|
+
pendingStreamQueue = null;
|
|
71
|
+
pendingStreamRequestId = null;
|
|
72
|
+
unmatchedStreamBuffer = new Map();
|
|
73
|
+
rl = null;
|
|
20
74
|
constructor(rkatPath = "rkat-rpc") {
|
|
21
75
|
this.rkatPath = rkatPath;
|
|
22
76
|
}
|
|
77
|
+
// -- Connection ---------------------------------------------------------
|
|
78
|
+
async connect(options) {
|
|
79
|
+
if (options?.realmId && options?.isolated) {
|
|
80
|
+
throw new MeerkatError("INVALID_ARGS", "realmId and isolated cannot both be set");
|
|
81
|
+
}
|
|
82
|
+
const resolved = await MeerkatClient.resolveBinaryPath(this.rkatPath);
|
|
83
|
+
this.rkatPath = resolved.command;
|
|
84
|
+
const args = MeerkatClient.buildArgs(resolved.useLegacySubcommand, options);
|
|
85
|
+
if (resolved.useLegacySubcommand && args.length > 1) {
|
|
86
|
+
throw new MeerkatError("LEGACY_BINARY_UNSUPPORTED", "Realm/context options require the standalone rkat-rpc binary. Install rkat-rpc and retry.");
|
|
87
|
+
}
|
|
88
|
+
this.process = spawn(this.rkatPath, args, {
|
|
89
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
90
|
+
});
|
|
91
|
+
this.rl = createInterface({ input: this.process.stdout });
|
|
92
|
+
this.rl.on("line", (line) => this.handleLine(line));
|
|
93
|
+
// Handshake
|
|
94
|
+
const initResult = await this.request("initialize", {});
|
|
95
|
+
const serverVersion = String(initResult.contract_version ?? "");
|
|
96
|
+
if (!MeerkatClient.checkVersionCompatible(serverVersion, CONTRACT_VERSION)) {
|
|
97
|
+
throw new MeerkatError("VERSION_MISMATCH", `Server version ${serverVersion} incompatible with SDK ${CONTRACT_VERSION}`);
|
|
98
|
+
}
|
|
99
|
+
// Fetch capabilities
|
|
100
|
+
const capsResult = await this.request("capabilities/get", {});
|
|
101
|
+
const rawCaps = capsResult.capabilities ?? [];
|
|
102
|
+
this._capabilities = rawCaps.map((cap) => ({
|
|
103
|
+
id: String(cap.id ?? ""),
|
|
104
|
+
description: String(cap.description ?? ""),
|
|
105
|
+
status: MeerkatClient.normalizeStatus(cap.status),
|
|
106
|
+
}));
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
async close() {
|
|
110
|
+
if (this.rl) {
|
|
111
|
+
this.rl.close();
|
|
112
|
+
this.rl = null;
|
|
113
|
+
}
|
|
114
|
+
if (this.process) {
|
|
115
|
+
this.process.kill();
|
|
116
|
+
this.process = null;
|
|
117
|
+
}
|
|
118
|
+
for (const [, pending] of this.pendingRequests) {
|
|
119
|
+
pending.reject(new MeerkatError("CLIENT_CLOSED", "Client closed"));
|
|
120
|
+
}
|
|
121
|
+
this.pendingRequests.clear();
|
|
122
|
+
for (const [, queue] of this.eventQueues) {
|
|
123
|
+
queue.put(null);
|
|
124
|
+
}
|
|
125
|
+
this.eventQueues.clear();
|
|
126
|
+
for (const [, queue] of this.commsStreamQueues) {
|
|
127
|
+
queue.put(null);
|
|
128
|
+
}
|
|
129
|
+
this.commsStreamQueues.clear();
|
|
130
|
+
if (this.pendingStreamQueue) {
|
|
131
|
+
this.pendingStreamQueue.put(null);
|
|
132
|
+
this.pendingStreamQueue = null;
|
|
133
|
+
this.pendingStreamRequestId = null;
|
|
134
|
+
this.unmatchedStreamBuffer.clear();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// -- Session lifecycle --------------------------------------------------
|
|
138
|
+
/**
|
|
139
|
+
* Create a new session, run the first turn, and return a {@link Session}.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const session = await client.createSession("Summarise this project", {
|
|
144
|
+
* model: "claude-sonnet-4-5",
|
|
145
|
+
* });
|
|
146
|
+
* console.log(session.text);
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
async createSession(prompt, options) {
|
|
150
|
+
const params = MeerkatClient.buildCreateParams(prompt, options);
|
|
151
|
+
const raw = await this.request("session/create", params);
|
|
152
|
+
const result = MeerkatClient.parseRunResult(raw);
|
|
153
|
+
return new Session(this, result);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Create a new session and stream typed events from the first turn.
|
|
157
|
+
*
|
|
158
|
+
* Returns an {@link EventStream} `AsyncIterable<AgentEvent>`.
|
|
159
|
+
*/
|
|
160
|
+
createSessionStreaming(prompt, options) {
|
|
161
|
+
if (!this.process?.stdin) {
|
|
162
|
+
throw new MeerkatError("NOT_CONNECTED", "Client not connected");
|
|
163
|
+
}
|
|
164
|
+
const params = MeerkatClient.buildCreateParams(prompt, options);
|
|
165
|
+
this.requestId++;
|
|
166
|
+
const requestId = this.requestId;
|
|
167
|
+
if (this.pendingStreamQueue) {
|
|
168
|
+
throw new MeerkatError("INVALID_STATE", "Only one createSessionStreaming request can be pending at a time");
|
|
169
|
+
}
|
|
170
|
+
const queue = new AsyncQueue();
|
|
171
|
+
this.pendingStreamQueue = queue;
|
|
172
|
+
this.pendingStreamRequestId = requestId;
|
|
173
|
+
const responsePromise = this.registerRequest(requestId);
|
|
174
|
+
// When response arrives, bind the queue to the session_id
|
|
175
|
+
const wrappedPromise = responsePromise.then((result) => {
|
|
176
|
+
const sid = String(result.session_id ?? "");
|
|
177
|
+
if (sid) {
|
|
178
|
+
const buffered = this.unmatchedStreamBuffer.get(sid) ?? [];
|
|
179
|
+
for (const evt of buffered) {
|
|
180
|
+
queue.put(evt);
|
|
181
|
+
}
|
|
182
|
+
this.unmatchedStreamBuffer.delete(sid);
|
|
183
|
+
this.eventQueues.set(sid, queue);
|
|
184
|
+
}
|
|
185
|
+
this.pendingStreamQueue = null;
|
|
186
|
+
this.pendingStreamRequestId = null;
|
|
187
|
+
this.unmatchedStreamBuffer.clear();
|
|
188
|
+
return result;
|
|
189
|
+
});
|
|
190
|
+
const rpcRequest = { jsonrpc: "2.0", id: requestId, method: "session/create", params };
|
|
191
|
+
this.process.stdin.write(JSON.stringify(rpcRequest) + "\n");
|
|
192
|
+
return new EventStream({
|
|
193
|
+
sessionId: "",
|
|
194
|
+
eventQueue: queue,
|
|
195
|
+
responsePromise: wrappedPromise,
|
|
196
|
+
parseResult: MeerkatClient.parseRunResult,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// -- Session queries ----------------------------------------------------
|
|
200
|
+
async listSessions() {
|
|
201
|
+
const result = await this.request("session/list", {});
|
|
202
|
+
const sessions = result.sessions ?? [];
|
|
203
|
+
return sessions.map((s) => ({
|
|
204
|
+
sessionId: String(s.session_id ?? ""),
|
|
205
|
+
sessionRef: s.session_ref != null ? String(s.session_ref) : undefined,
|
|
206
|
+
createdAt: String(s.created_at ?? ""),
|
|
207
|
+
updatedAt: String(s.updated_at ?? ""),
|
|
208
|
+
messageCount: Number(s.message_count ?? 0),
|
|
209
|
+
totalTokens: Number(s.total_tokens ?? 0),
|
|
210
|
+
isActive: Boolean(s.is_active),
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
async readSession(sessionId) {
|
|
214
|
+
return this.request("session/read", { session_id: sessionId });
|
|
215
|
+
}
|
|
216
|
+
// -- Capabilities -------------------------------------------------------
|
|
217
|
+
get capabilities() {
|
|
218
|
+
return this._capabilities;
|
|
219
|
+
}
|
|
220
|
+
hasCapability(capabilityId) {
|
|
221
|
+
return this._capabilities.some((c) => c.id === capabilityId && c.status === "Available");
|
|
222
|
+
}
|
|
223
|
+
requireCapability(capabilityId) {
|
|
224
|
+
if (!this.hasCapability(capabilityId)) {
|
|
225
|
+
throw new CapabilityUnavailableError("CAPABILITY_UNAVAILABLE", `Capability '${capabilityId}' is not available`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// -- Config -------------------------------------------------------------
|
|
229
|
+
async getConfig() {
|
|
230
|
+
return this.request("config/get", {});
|
|
231
|
+
}
|
|
232
|
+
async setConfig(config, options) {
|
|
233
|
+
const params = { config };
|
|
234
|
+
if (options?.expectedGeneration !== undefined) {
|
|
235
|
+
params.expected_generation = options.expectedGeneration;
|
|
236
|
+
}
|
|
237
|
+
return this.request("config/set", params);
|
|
238
|
+
}
|
|
239
|
+
async patchConfig(patch, options) {
|
|
240
|
+
const params = { patch };
|
|
241
|
+
if (options?.expectedGeneration !== undefined) {
|
|
242
|
+
params.expected_generation = options.expectedGeneration;
|
|
243
|
+
}
|
|
244
|
+
return this.request("config/patch", params);
|
|
245
|
+
}
|
|
246
|
+
async mcpAdd(params) {
|
|
247
|
+
const raw = await this.request("mcp/add", params);
|
|
248
|
+
return MeerkatClient.parseMcpLiveOpResponse(raw);
|
|
249
|
+
}
|
|
250
|
+
async mcpRemove(params) {
|
|
251
|
+
const raw = await this.request("mcp/remove", params);
|
|
252
|
+
return MeerkatClient.parseMcpLiveOpResponse(raw);
|
|
253
|
+
}
|
|
254
|
+
async mcpReload(params) {
|
|
255
|
+
const raw = await this.request("mcp/reload", params);
|
|
256
|
+
return MeerkatClient.parseMcpLiveOpResponse(raw);
|
|
257
|
+
}
|
|
258
|
+
// snake_case aliases for parity with other SDKs/surfaces
|
|
259
|
+
async mcp_add(params) {
|
|
260
|
+
return this.mcpAdd(params);
|
|
261
|
+
}
|
|
262
|
+
async mcp_remove(params) {
|
|
263
|
+
return this.mcpRemove(params);
|
|
264
|
+
}
|
|
265
|
+
async mcp_reload(params) {
|
|
266
|
+
return this.mcpReload(params);
|
|
267
|
+
}
|
|
268
|
+
// -- Skills ---------------------------------------------------------------
|
|
269
|
+
async listSkills() {
|
|
270
|
+
const result = await this.request("skills/list", {});
|
|
271
|
+
return result.skills ?? [];
|
|
272
|
+
}
|
|
273
|
+
async inspectSkill(id, options) {
|
|
274
|
+
const params = { id };
|
|
275
|
+
if (options?.source !== undefined) {
|
|
276
|
+
params.source = options.source;
|
|
277
|
+
}
|
|
278
|
+
return this.request("skills/inspect", params);
|
|
279
|
+
}
|
|
280
|
+
async listMobPrefabs() {
|
|
281
|
+
const result = await this.request("mob/prefabs", {});
|
|
282
|
+
return result.prefabs ?? [];
|
|
283
|
+
}
|
|
284
|
+
async list_mob_prefabs() {
|
|
285
|
+
return this.listMobPrefabs();
|
|
286
|
+
}
|
|
287
|
+
async listMobTools() {
|
|
288
|
+
const result = await this.request("mob/tools", {});
|
|
289
|
+
return result.tools ?? [];
|
|
290
|
+
}
|
|
291
|
+
async callMobTool(name, argumentsPayload) {
|
|
292
|
+
return this.request("mob/call", {
|
|
293
|
+
name,
|
|
294
|
+
arguments: argumentsPayload ?? {},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// -- Internal methods used by Session -----------------------------------
|
|
298
|
+
/** @internal */
|
|
299
|
+
async _startTurn(sessionId, prompt, options) {
|
|
300
|
+
const params = { session_id: sessionId, prompt };
|
|
301
|
+
const wireRefs = skillRefsToWire(options?.skillRefs);
|
|
302
|
+
if (wireRefs) {
|
|
303
|
+
params.skill_refs = wireRefs;
|
|
304
|
+
}
|
|
305
|
+
if (options?.skillReferences) {
|
|
306
|
+
params.skill_references = options.skillReferences;
|
|
307
|
+
}
|
|
308
|
+
if (options?.flowToolOverlay) {
|
|
309
|
+
params.flow_tool_overlay = {
|
|
310
|
+
allowed_tools: options.flowToolOverlay.allowedTools,
|
|
311
|
+
blocked_tools: options.flowToolOverlay.blockedTools,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const raw = await this.request("turn/start", params);
|
|
315
|
+
return MeerkatClient.parseRunResult(raw);
|
|
316
|
+
}
|
|
317
|
+
/** @internal */
|
|
318
|
+
_startTurnStreaming(sessionId, prompt, options, session) {
|
|
319
|
+
if (!this.process?.stdin) {
|
|
320
|
+
throw new MeerkatError("NOT_CONNECTED", "Client not connected");
|
|
321
|
+
}
|
|
322
|
+
this.requestId++;
|
|
323
|
+
const requestId = this.requestId;
|
|
324
|
+
const queue = new AsyncQueue();
|
|
325
|
+
this.eventQueues.set(sessionId, queue);
|
|
326
|
+
const responsePromise = this.registerRequest(requestId);
|
|
327
|
+
const params = { session_id: sessionId, prompt };
|
|
328
|
+
const wireRefs = skillRefsToWire(options?.skillRefs);
|
|
329
|
+
if (wireRefs) {
|
|
330
|
+
params.skill_refs = wireRefs;
|
|
331
|
+
}
|
|
332
|
+
if (options?.skillReferences) {
|
|
333
|
+
params.skill_references = options.skillReferences;
|
|
334
|
+
}
|
|
335
|
+
if (options?.flowToolOverlay) {
|
|
336
|
+
params.flow_tool_overlay = {
|
|
337
|
+
allowed_tools: options.flowToolOverlay.allowedTools,
|
|
338
|
+
blocked_tools: options.flowToolOverlay.blockedTools,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const rpcRequest = { jsonrpc: "2.0", id: requestId, method: "turn/start", params };
|
|
342
|
+
this.process.stdin.write(JSON.stringify(rpcRequest) + "\n");
|
|
343
|
+
return new EventStream({
|
|
344
|
+
sessionId,
|
|
345
|
+
eventQueue: queue,
|
|
346
|
+
responsePromise,
|
|
347
|
+
parseResult: MeerkatClient.parseRunResult,
|
|
348
|
+
session,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/** @internal */
|
|
352
|
+
async _interrupt(sessionId) {
|
|
353
|
+
await this.request("turn/interrupt", { session_id: sessionId });
|
|
354
|
+
}
|
|
355
|
+
/** @internal */
|
|
356
|
+
async _archive(sessionId) {
|
|
357
|
+
await this.request("session/archive", { session_id: sessionId });
|
|
358
|
+
}
|
|
359
|
+
/** @internal */
|
|
360
|
+
async _send(sessionId, command) {
|
|
361
|
+
return this.send(sessionId, command);
|
|
362
|
+
}
|
|
363
|
+
/** @internal */
|
|
364
|
+
async _peers(sessionId) {
|
|
365
|
+
return this.peers(sessionId);
|
|
366
|
+
}
|
|
367
|
+
async send(sessionId, command) {
|
|
368
|
+
return this.request("comms/send", { session_id: sessionId, ...command });
|
|
369
|
+
}
|
|
370
|
+
async peers(sessionId) {
|
|
371
|
+
return this.request("comms/peers", { session_id: sessionId });
|
|
372
|
+
}
|
|
373
|
+
async openCommsStream(sessionId, options) {
|
|
374
|
+
const scope = options?.scope ?? "session";
|
|
375
|
+
const params = { session_id: sessionId, scope };
|
|
376
|
+
if (options?.interactionId) {
|
|
377
|
+
params.interaction_id = options.interactionId;
|
|
378
|
+
}
|
|
379
|
+
const opened = await this.request("comms/stream_open", params);
|
|
380
|
+
const streamId = String(opened.stream_id ?? "");
|
|
381
|
+
if (!streamId) {
|
|
382
|
+
throw new MeerkatError("INVALID_RESPONSE", "Missing stream_id in comms/stream_open response");
|
|
383
|
+
}
|
|
384
|
+
const queue = new AsyncQueue();
|
|
385
|
+
this.commsStreamQueues.set(streamId, queue);
|
|
386
|
+
return new CommsEventStream({
|
|
387
|
+
streamId,
|
|
388
|
+
eventQueue: queue,
|
|
389
|
+
onClose: async (id) => {
|
|
390
|
+
await this.request("comms/stream_close", { stream_id: id });
|
|
391
|
+
const q = this.commsStreamQueues.get(id);
|
|
392
|
+
if (q) {
|
|
393
|
+
q.put(null);
|
|
394
|
+
}
|
|
395
|
+
this.commsStreamQueues.delete(id);
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
async open_comms_stream(sessionId, options) {
|
|
400
|
+
return this.openCommsStream(sessionId, options);
|
|
401
|
+
}
|
|
402
|
+
async sendAndStream(sessionId, command) {
|
|
403
|
+
const receipt = await this.send(sessionId, command);
|
|
404
|
+
const interactionId = String(receipt.interaction_id ?? "");
|
|
405
|
+
const streamReserved = Boolean(receipt.stream_reserved ?? false);
|
|
406
|
+
if (!interactionId) {
|
|
407
|
+
throw new MeerkatError("INVALID_RESPONSE", "comms/send response missing interaction_id for sendAndStream");
|
|
408
|
+
}
|
|
409
|
+
if (!streamReserved) {
|
|
410
|
+
throw new MeerkatError("INVALID_RESPONSE", "comms/send response did not reserve stream for sendAndStream");
|
|
411
|
+
}
|
|
412
|
+
const stream = await this.openCommsStream(sessionId, {
|
|
413
|
+
scope: "interaction",
|
|
414
|
+
interactionId,
|
|
415
|
+
});
|
|
416
|
+
return { receipt, stream };
|
|
417
|
+
}
|
|
418
|
+
async send_and_stream(sessionId, command) {
|
|
419
|
+
return this.sendAndStream(sessionId, command);
|
|
420
|
+
}
|
|
421
|
+
// -- Transport ----------------------------------------------------------
|
|
422
|
+
handleLine(line) {
|
|
423
|
+
let data;
|
|
424
|
+
try {
|
|
425
|
+
data = JSON.parse(line);
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if ("id" in data && typeof data.id === "number") {
|
|
431
|
+
const pending = this.pendingRequests.get(data.id);
|
|
432
|
+
if (pending) {
|
|
433
|
+
this.pendingRequests.delete(data.id);
|
|
434
|
+
const error = data.error;
|
|
435
|
+
if (error) {
|
|
436
|
+
pending.reject(new MeerkatError(String(error.code ?? "UNKNOWN"), String(error.message ?? "Unknown error")));
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
pending.resolve((data.result ?? {}));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else if ("method" in data) {
|
|
444
|
+
if (data.method === "comms/stream_event") {
|
|
445
|
+
const params = (data.params ?? {});
|
|
446
|
+
const streamId = String(params.stream_id ?? "");
|
|
447
|
+
const queue = this.commsStreamQueues.get(streamId);
|
|
448
|
+
if (queue) {
|
|
449
|
+
queue.put(params);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const params = (data.params ?? {});
|
|
454
|
+
const sessionId = String(params.session_id ?? "");
|
|
455
|
+
const event = params.event;
|
|
456
|
+
if (event) {
|
|
457
|
+
const queue = this.eventQueues.get(sessionId);
|
|
458
|
+
if (queue) {
|
|
459
|
+
queue.put(event);
|
|
460
|
+
}
|
|
461
|
+
else if (this.pendingStreamQueue) {
|
|
462
|
+
const buffered = this.unmatchedStreamBuffer.get(sessionId) ?? [];
|
|
463
|
+
buffered.push(event);
|
|
464
|
+
this.unmatchedStreamBuffer.set(sessionId, buffered);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
request(method, params) {
|
|
470
|
+
if (!this.process?.stdin) {
|
|
471
|
+
throw new MeerkatError("NOT_CONNECTED", "Client not connected");
|
|
472
|
+
}
|
|
473
|
+
this.requestId++;
|
|
474
|
+
const id = this.requestId;
|
|
475
|
+
const rpcRequest = { jsonrpc: "2.0", id, method, params };
|
|
476
|
+
const promise = this.registerRequest(id);
|
|
477
|
+
this.process.stdin.write(JSON.stringify(rpcRequest) + "\n");
|
|
478
|
+
return promise;
|
|
479
|
+
}
|
|
480
|
+
registerRequest(id) {
|
|
481
|
+
return new Promise((resolve, reject) => {
|
|
482
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
// -- Static helpers -----------------------------------------------------
|
|
486
|
+
static normalizeStatus(raw) {
|
|
487
|
+
if (typeof raw === "string")
|
|
488
|
+
return raw;
|
|
489
|
+
if (typeof raw === "object" && raw !== null) {
|
|
490
|
+
// Rust can emit externally-tagged enums for status:
|
|
491
|
+
// { DisabledByPolicy: { description: "..." } }
|
|
492
|
+
return Object.keys(raw)[0] ?? "Unknown";
|
|
493
|
+
}
|
|
494
|
+
return String(raw);
|
|
495
|
+
}
|
|
496
|
+
static parseSkillDiagnostics(raw) {
|
|
497
|
+
if (!raw || typeof raw !== "object")
|
|
498
|
+
return undefined;
|
|
499
|
+
const data = raw;
|
|
500
|
+
const sourceHealthRaw = data.source_health;
|
|
501
|
+
const rawQuarantined = Array.isArray(data.quarantined)
|
|
502
|
+
? data.quarantined
|
|
503
|
+
: [];
|
|
504
|
+
const quarantined = rawQuarantined
|
|
505
|
+
.filter((item) => typeof item === "object" && item !== null)
|
|
506
|
+
.map((item) => ({
|
|
507
|
+
sourceUuid: String(item.source_uuid ?? ""),
|
|
508
|
+
skillId: String(item.skill_id ?? ""),
|
|
509
|
+
location: String(item.location ?? ""),
|
|
510
|
+
errorCode: String(item.error_code ?? ""),
|
|
511
|
+
errorClass: String(item.error_class ?? ""),
|
|
512
|
+
message: String(item.message ?? ""),
|
|
513
|
+
firstSeenUnixSecs: Number(item.first_seen_unix_secs ?? 0),
|
|
514
|
+
lastSeenUnixSecs: Number(item.last_seen_unix_secs ?? 0),
|
|
515
|
+
}));
|
|
516
|
+
return {
|
|
517
|
+
sourceHealth: {
|
|
518
|
+
state: String(sourceHealthRaw?.state ?? ""),
|
|
519
|
+
invalidRatio: Number(sourceHealthRaw?.invalid_ratio ?? 0),
|
|
520
|
+
invalidCount: Number(sourceHealthRaw?.invalid_count ?? 0),
|
|
521
|
+
totalCount: Number(sourceHealthRaw?.total_count ?? 0),
|
|
522
|
+
failureStreak: Number(sourceHealthRaw?.failure_streak ?? 0),
|
|
523
|
+
handshakeFailed: Boolean(sourceHealthRaw?.handshake_failed ?? false),
|
|
524
|
+
},
|
|
525
|
+
quarantined,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
static checkVersionCompatible(server, client) {
|
|
529
|
+
try {
|
|
530
|
+
const s = server.split(".").map(Number);
|
|
531
|
+
const c = client.split(".").map(Number);
|
|
532
|
+
if (s[0] === 0 && c[0] === 0)
|
|
533
|
+
return s[1] === c[1];
|
|
534
|
+
return s[0] === c[0];
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
static parseRunResult(data) {
|
|
541
|
+
const usageRaw = data.usage;
|
|
542
|
+
const usage = {
|
|
543
|
+
inputTokens: Number(usageRaw?.input_tokens ?? 0),
|
|
544
|
+
outputTokens: Number(usageRaw?.output_tokens ?? 0),
|
|
545
|
+
cacheCreationTokens: usageRaw?.cache_creation_tokens != null
|
|
546
|
+
? Number(usageRaw.cache_creation_tokens)
|
|
547
|
+
: undefined,
|
|
548
|
+
cacheReadTokens: usageRaw?.cache_read_tokens != null
|
|
549
|
+
? Number(usageRaw.cache_read_tokens)
|
|
550
|
+
: undefined,
|
|
551
|
+
};
|
|
552
|
+
const rawWarnings = data.schema_warnings;
|
|
553
|
+
const schemaWarnings = rawWarnings?.map((w) => ({
|
|
554
|
+
provider: String(w.provider ?? ""),
|
|
555
|
+
path: String(w.path ?? ""),
|
|
556
|
+
message: String(w.message ?? ""),
|
|
557
|
+
}));
|
|
558
|
+
return {
|
|
559
|
+
sessionId: String(data.session_id ?? ""),
|
|
560
|
+
sessionRef: data.session_ref != null ? String(data.session_ref) : undefined,
|
|
561
|
+
text: String(data.text ?? ""),
|
|
562
|
+
turns: Number(data.turns ?? 0),
|
|
563
|
+
toolCalls: Number(data.tool_calls ?? 0),
|
|
564
|
+
usage,
|
|
565
|
+
structuredOutput: data.structured_output,
|
|
566
|
+
schemaWarnings,
|
|
567
|
+
skillDiagnostics: MeerkatClient.parseSkillDiagnostics(data.skill_diagnostics),
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
static parseMcpLiveOpResponse(raw) {
|
|
571
|
+
const sessionId = raw.session_id;
|
|
572
|
+
const operation = raw.operation;
|
|
573
|
+
const status = raw.status;
|
|
574
|
+
const persisted = raw.persisted;
|
|
575
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
576
|
+
throw new MeerkatError("INVALID_RESPONSE", "Invalid mcp response: missing session_id");
|
|
577
|
+
}
|
|
578
|
+
if (operation !== "add" && operation !== "remove" && operation !== "reload") {
|
|
579
|
+
throw new MeerkatError("INVALID_RESPONSE", "Invalid mcp response: invalid operation");
|
|
580
|
+
}
|
|
581
|
+
if (typeof status !== "string" || status.length === 0) {
|
|
582
|
+
throw new MeerkatError("INVALID_RESPONSE", "Invalid mcp response: missing status");
|
|
583
|
+
}
|
|
584
|
+
if (typeof persisted !== "boolean") {
|
|
585
|
+
throw new MeerkatError("INVALID_RESPONSE", "Invalid mcp response: persisted must be boolean");
|
|
586
|
+
}
|
|
587
|
+
return raw;
|
|
588
|
+
}
|
|
589
|
+
static buildCreateParams(prompt, options) {
|
|
590
|
+
const params = { prompt };
|
|
591
|
+
if (!options)
|
|
592
|
+
return params;
|
|
593
|
+
if (options.model)
|
|
594
|
+
params.model = options.model;
|
|
595
|
+
if (options.provider)
|
|
596
|
+
params.provider = options.provider;
|
|
597
|
+
if (options.systemPrompt)
|
|
598
|
+
params.system_prompt = options.systemPrompt;
|
|
599
|
+
if (options.maxTokens)
|
|
600
|
+
params.max_tokens = options.maxTokens;
|
|
601
|
+
if (options.outputSchema)
|
|
602
|
+
params.output_schema = options.outputSchema;
|
|
603
|
+
if (options.structuredOutputRetries != null && options.structuredOutputRetries !== 2) {
|
|
604
|
+
params.structured_output_retries = options.structuredOutputRetries;
|
|
605
|
+
}
|
|
606
|
+
if (options.hooksOverride)
|
|
607
|
+
params.hooks_override = options.hooksOverride;
|
|
608
|
+
if (options.enableBuiltins)
|
|
609
|
+
params.enable_builtins = true;
|
|
610
|
+
if (options.enableShell)
|
|
611
|
+
params.enable_shell = true;
|
|
612
|
+
if (options.enableSubagents)
|
|
613
|
+
params.enable_subagents = true;
|
|
614
|
+
if (options.enableMemory)
|
|
615
|
+
params.enable_memory = true;
|
|
616
|
+
if (options.enableMob)
|
|
617
|
+
params.enable_mob = true;
|
|
618
|
+
if (options.hostMode)
|
|
619
|
+
params.host_mode = true;
|
|
620
|
+
if (options.commsName)
|
|
621
|
+
params.comms_name = options.commsName;
|
|
622
|
+
if (options.peerMeta != null)
|
|
623
|
+
params.peer_meta = options.peerMeta;
|
|
624
|
+
if (options.budgetLimits != null)
|
|
625
|
+
params.budget_limits = options.budgetLimits;
|
|
626
|
+
if (options.providerParams)
|
|
627
|
+
params.provider_params = options.providerParams;
|
|
628
|
+
if (options.preloadSkills != null)
|
|
629
|
+
params.preload_skills = options.preloadSkills;
|
|
630
|
+
const wireRefs = skillRefsToWire(options.skillRefs);
|
|
631
|
+
if (wireRefs)
|
|
632
|
+
params.skill_refs = wireRefs;
|
|
633
|
+
if (options.skillReferences != null)
|
|
634
|
+
params.skill_references = options.skillReferences;
|
|
635
|
+
return params;
|
|
636
|
+
}
|
|
637
|
+
// -- Binary resolution --------------------------------------------------
|
|
23
638
|
static commandPath(command) {
|
|
24
639
|
if (path.isAbsolute(command)) {
|
|
25
640
|
return existsSync(command) ? command : null;
|
|
@@ -59,39 +674,20 @@ export class MeerkatClient {
|
|
|
59
674
|
const architecture = os.arch();
|
|
60
675
|
if (process.platform === "darwin") {
|
|
61
676
|
if (architecture === "arm64") {
|
|
62
|
-
return {
|
|
63
|
-
target: "aarch64-apple-darwin",
|
|
64
|
-
archiveExt: "tar.gz",
|
|
65
|
-
binaryName: "rkat-rpc",
|
|
66
|
-
};
|
|
677
|
+
return { target: "aarch64-apple-darwin", archiveExt: "tar.gz", binaryName: "rkat-rpc" };
|
|
67
678
|
}
|
|
68
|
-
throw new MeerkatError("UNSUPPORTED_PLATFORM", `Unsupported macOS architecture '${architecture}'
|
|
679
|
+
throw new MeerkatError("UNSUPPORTED_PLATFORM", `Unsupported macOS architecture '${architecture}'.`);
|
|
69
680
|
}
|
|
70
681
|
if (process.platform === "linux") {
|
|
71
|
-
if (architecture === "x64")
|
|
72
|
-
return {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
binaryName: "rkat-rpc",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
if (architecture === "arm64") {
|
|
79
|
-
return {
|
|
80
|
-
target: "aarch64-unknown-linux-gnu",
|
|
81
|
-
archiveExt: "tar.gz",
|
|
82
|
-
binaryName: "rkat-rpc",
|
|
83
|
-
};
|
|
84
|
-
}
|
|
682
|
+
if (architecture === "x64")
|
|
683
|
+
return { target: "x86_64-unknown-linux-gnu", archiveExt: "tar.gz", binaryName: "rkat-rpc" };
|
|
684
|
+
if (architecture === "arm64")
|
|
685
|
+
return { target: "aarch64-unknown-linux-gnu", archiveExt: "tar.gz", binaryName: "rkat-rpc" };
|
|
85
686
|
throw new MeerkatError("UNSUPPORTED_PLATFORM", `Unsupported Linux architecture '${architecture}'.`);
|
|
86
687
|
}
|
|
87
688
|
if (process.platform === "win32") {
|
|
88
|
-
if (architecture === "x64")
|
|
89
|
-
return {
|
|
90
|
-
target: "x86_64-pc-windows-msvc",
|
|
91
|
-
archiveExt: "zip",
|
|
92
|
-
binaryName: "rkat-rpc.exe",
|
|
93
|
-
};
|
|
94
|
-
}
|
|
689
|
+
if (architecture === "x64")
|
|
690
|
+
return { target: "x86_64-pc-windows-msvc", archiveExt: "zip", binaryName: "rkat-rpc.exe" };
|
|
95
691
|
throw new MeerkatError("UNSUPPORTED_PLATFORM", `Unsupported Windows architecture '${architecture}'.`);
|
|
96
692
|
}
|
|
97
693
|
throw new MeerkatError("UNSUPPORTED_PLATFORM", `Unsupported platform '${process.platform}'.`);
|
|
@@ -100,12 +696,8 @@ export class MeerkatClient {
|
|
|
100
696
|
return new Promise((resolve, reject) => {
|
|
101
697
|
const proc = spawn(command, args, { stdio: ["ignore", "inherit", "pipe"] });
|
|
102
698
|
let stderr = "";
|
|
103
|
-
proc.stderr?.on("data", (chunk) => {
|
|
104
|
-
|
|
105
|
-
});
|
|
106
|
-
proc.on("error", (error) => {
|
|
107
|
-
reject(error);
|
|
108
|
-
});
|
|
699
|
+
proc.stderr?.on("data", (chunk) => { stderr += String(chunk); });
|
|
700
|
+
proc.on("error", (error) => { reject(error); });
|
|
109
701
|
proc.on("close", (code) => {
|
|
110
702
|
if (code === 0) {
|
|
111
703
|
resolve();
|
|
@@ -118,12 +710,10 @@ export class MeerkatClient {
|
|
|
118
710
|
static async extractZip(archivePath, destinationDir) {
|
|
119
711
|
try {
|
|
120
712
|
await MeerkatClient.runCommand("tar", ["-xf", archivePath, "-C", destinationDir]);
|
|
121
|
-
return;
|
|
122
713
|
}
|
|
123
|
-
catch
|
|
714
|
+
catch {
|
|
124
715
|
await MeerkatClient.runCommand("powershell", [
|
|
125
|
-
"-NoProfile",
|
|
126
|
-
"-Command",
|
|
716
|
+
"-NoProfile", "-Command",
|
|
127
717
|
`Expand-Archive -LiteralPath '${archivePath.replaceAll("'", "''")}' -DestinationPath '${destinationDir.replaceAll("'", "''")}' -Force`,
|
|
128
718
|
]);
|
|
129
719
|
}
|
|
@@ -136,9 +726,8 @@ export class MeerkatClient {
|
|
|
136
726
|
const baseDir = path.join(MEERKAT_BINARY_CACHE_ROOT, `v${version}`, target);
|
|
137
727
|
mkdirSync(baseDir, { recursive: true });
|
|
138
728
|
const cached = path.join(baseDir, binaryName);
|
|
139
|
-
if (existsSync(cached))
|
|
729
|
+
if (existsSync(cached))
|
|
140
730
|
return cached;
|
|
141
|
-
}
|
|
142
731
|
const response = await fetch(url);
|
|
143
732
|
if (!response.ok) {
|
|
144
733
|
throw new MeerkatError("BINARY_DOWNLOAD_FAILED", `Failed to download binary from ${url} (HTTP ${response.status})`);
|
|
@@ -163,9 +752,8 @@ export class MeerkatClient {
|
|
|
163
752
|
}
|
|
164
753
|
catch { /* best-effort cleanup */ }
|
|
165
754
|
}
|
|
166
|
-
if (process.platform !== "win32")
|
|
755
|
+
if (process.platform !== "win32")
|
|
167
756
|
chmodSync(cached, 0o755);
|
|
168
|
-
}
|
|
169
757
|
return cached;
|
|
170
758
|
}
|
|
171
759
|
static async resolveBinaryPath(requestedPath) {
|
|
@@ -173,293 +761,54 @@ export class MeerkatClient {
|
|
|
173
761
|
if (overridden) {
|
|
174
762
|
const candidate = MeerkatClient.resolveCandidatePath(overridden);
|
|
175
763
|
if (!candidate) {
|
|
176
|
-
throw new MeerkatError("BINARY_NOT_FOUND", `Binary not found at MEERKAT_BIN_PATH='${overridden}'
|
|
764
|
+
throw new MeerkatError("BINARY_NOT_FOUND", `Binary not found at MEERKAT_BIN_PATH='${overridden}'.`);
|
|
177
765
|
}
|
|
178
|
-
return {
|
|
179
|
-
command: candidate,
|
|
180
|
-
useLegacySubcommand: path.basename(candidate) === "rkat",
|
|
181
|
-
};
|
|
766
|
+
return { command: candidate, useLegacySubcommand: path.basename(candidate) === "rkat" };
|
|
182
767
|
}
|
|
183
768
|
if (requestedPath !== "rkat-rpc") {
|
|
184
769
|
const candidate = MeerkatClient.resolveCandidatePath(requestedPath);
|
|
185
770
|
if (!candidate) {
|
|
186
|
-
throw new MeerkatError("BINARY_NOT_FOUND", `Binary not found at '${requestedPath}'
|
|
771
|
+
throw new MeerkatError("BINARY_NOT_FOUND", `Binary not found at '${requestedPath}'.`);
|
|
187
772
|
}
|
|
188
|
-
return {
|
|
189
|
-
command: candidate,
|
|
190
|
-
useLegacySubcommand: path.basename(candidate) === "rkat",
|
|
191
|
-
};
|
|
773
|
+
return { command: candidate, useLegacySubcommand: path.basename(candidate) === "rkat" };
|
|
192
774
|
}
|
|
193
775
|
const defaultBinary = MeerkatClient.commandPath("rkat-rpc");
|
|
194
|
-
if (defaultBinary)
|
|
195
|
-
return {
|
|
196
|
-
command: defaultBinary,
|
|
197
|
-
useLegacySubcommand: false,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
776
|
+
if (defaultBinary)
|
|
777
|
+
return { command: defaultBinary, useLegacySubcommand: false };
|
|
200
778
|
try {
|
|
201
779
|
const downloaded = await MeerkatClient.ensureDownloadedBinary();
|
|
202
780
|
return { command: downloaded, useLegacySubcommand: false };
|
|
203
781
|
}
|
|
204
782
|
catch (error) {
|
|
205
783
|
const legacy = MeerkatClient.commandPath("rkat");
|
|
206
|
-
if (legacy)
|
|
784
|
+
if (legacy)
|
|
207
785
|
return { command: legacy, useLegacySubcommand: true };
|
|
208
|
-
}
|
|
209
786
|
if (error instanceof MeerkatError)
|
|
210
787
|
throw error;
|
|
211
788
|
throw new MeerkatError("BINARY_NOT_FOUND", `Could not find '${MEERKAT_RELEASE_BINARY}' and auto-download failed.`);
|
|
212
789
|
}
|
|
213
790
|
}
|
|
214
|
-
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
this.rkatPath = resolved.command;
|
|
791
|
+
static buildArgs(legacy, options) {
|
|
792
|
+
if (legacy)
|
|
793
|
+
return ["rpc"];
|
|
794
|
+
if (!options)
|
|
795
|
+
return [];
|
|
220
796
|
const args = [];
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
options
|
|
225
|
-
|
|
226
|
-
options
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
args.push("
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
args.push("--isolated");
|
|
237
|
-
}
|
|
238
|
-
if (options?.realmId) {
|
|
239
|
-
args.push("--realm", options.realmId);
|
|
240
|
-
}
|
|
241
|
-
if (options?.instanceId) {
|
|
242
|
-
args.push("--instance", options.instanceId);
|
|
243
|
-
}
|
|
244
|
-
if (options?.realmBackend) {
|
|
245
|
-
args.push("--realm-backend", options.realmBackend);
|
|
246
|
-
}
|
|
247
|
-
if (options?.stateRoot) {
|
|
248
|
-
args.push("--state-root", options.stateRoot);
|
|
249
|
-
}
|
|
250
|
-
if (options?.contextRoot) {
|
|
251
|
-
args.push("--context-root", options.contextRoot);
|
|
252
|
-
}
|
|
253
|
-
if (options?.userConfigRoot) {
|
|
254
|
-
args.push("--user-config-root", options.userConfigRoot);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
this.process = spawn(this.rkatPath, args, {
|
|
258
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
259
|
-
});
|
|
260
|
-
const rl = createInterface({ input: this.process.stdout });
|
|
261
|
-
rl.on("line", (line) => {
|
|
262
|
-
try {
|
|
263
|
-
const response = JSON.parse(line);
|
|
264
|
-
if ("id" in response &&
|
|
265
|
-
typeof response.id === "number" &&
|
|
266
|
-
this.pendingRequests.has(response.id)) {
|
|
267
|
-
const pending = this.pendingRequests.get(response.id);
|
|
268
|
-
this.pendingRequests.delete(response.id);
|
|
269
|
-
const error = response.error;
|
|
270
|
-
if (error) {
|
|
271
|
-
pending.reject(new MeerkatError(String(error.code ?? "UNKNOWN"), String(error.message ?? "Unknown error")));
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
pending.resolve(response.result ?? {});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch {
|
|
279
|
-
// Ignore non-JSON lines
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
// Initialize
|
|
283
|
-
const initResult = (await this.request("initialize", {}));
|
|
284
|
-
const serverVersion = String(initResult.contract_version ?? "");
|
|
285
|
-
if (!this.checkVersionCompatible(serverVersion, CONTRACT_VERSION)) {
|
|
286
|
-
throw new MeerkatError("VERSION_MISMATCH", `Server version ${serverVersion} incompatible with SDK ${CONTRACT_VERSION}`);
|
|
287
|
-
}
|
|
288
|
-
// Fetch capabilities
|
|
289
|
-
const capsResult = (await this.request("capabilities/get", {}));
|
|
290
|
-
this.capabilities = {
|
|
291
|
-
contract_version: String(capsResult.contract_version ?? ""),
|
|
292
|
-
capabilities: (capsResult.capabilities ?? []).map((cap) => ({
|
|
293
|
-
id: String(cap.id ?? ""),
|
|
294
|
-
description: String(cap.description ?? ""),
|
|
295
|
-
status: MeerkatClient.normalizeStatus(cap.status),
|
|
296
|
-
})),
|
|
297
|
-
};
|
|
298
|
-
return this;
|
|
299
|
-
}
|
|
300
|
-
async close() {
|
|
301
|
-
if (this.process) {
|
|
302
|
-
this.process.kill();
|
|
303
|
-
this.process = null;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
async createSession(params) {
|
|
307
|
-
const result = (await this.request("session/create", params));
|
|
308
|
-
return this.parseRunResult(result);
|
|
309
|
-
}
|
|
310
|
-
async startTurn(sessionId, prompt, options) {
|
|
311
|
-
const result = (await this.request("turn/start", {
|
|
312
|
-
session_id: sessionId,
|
|
313
|
-
prompt,
|
|
314
|
-
...options,
|
|
315
|
-
}));
|
|
316
|
-
return this.parseRunResult(result);
|
|
317
|
-
}
|
|
318
|
-
async interrupt(sessionId) {
|
|
319
|
-
await this.request("turn/interrupt", { session_id: sessionId });
|
|
320
|
-
}
|
|
321
|
-
async listSessions() {
|
|
322
|
-
const result = (await this.request("session/list", {}));
|
|
323
|
-
return result.sessions ?? [];
|
|
324
|
-
}
|
|
325
|
-
async readSession(sessionId) {
|
|
326
|
-
return (await this.request("session/read", {
|
|
327
|
-
session_id: sessionId,
|
|
328
|
-
}));
|
|
329
|
-
}
|
|
330
|
-
async archiveSession(sessionId) {
|
|
331
|
-
await this.request("session/archive", { session_id: sessionId });
|
|
332
|
-
}
|
|
333
|
-
async getCapabilities() {
|
|
334
|
-
if (this.capabilities)
|
|
335
|
-
return this.capabilities;
|
|
336
|
-
const result = (await this.request("capabilities/get", {}));
|
|
337
|
-
return {
|
|
338
|
-
contract_version: String(result.contract_version ?? ""),
|
|
339
|
-
capabilities: [],
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
hasCapability(capabilityId) {
|
|
343
|
-
if (!this.capabilities)
|
|
344
|
-
return false;
|
|
345
|
-
return this.capabilities.capabilities.some((c) => c.id === capabilityId && c.status === "Available");
|
|
346
|
-
}
|
|
347
|
-
requireCapability(capabilityId) {
|
|
348
|
-
if (!this.hasCapability(capabilityId)) {
|
|
349
|
-
throw new MeerkatError("CAPABILITY_UNAVAILABLE", `Capability '${capabilityId}' is not available`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// --- Config ---
|
|
353
|
-
async getConfig() {
|
|
354
|
-
return (await this.request("config/get", {}));
|
|
355
|
-
}
|
|
356
|
-
async setConfig(config) {
|
|
357
|
-
await this.request("config/set", config);
|
|
358
|
-
}
|
|
359
|
-
async patchConfig(patch) {
|
|
360
|
-
return (await this.request("config/patch", patch));
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Send a canonical comms command to a session.
|
|
364
|
-
*
|
|
365
|
-
* @param sessionId - Target session ID
|
|
366
|
-
* @param command - Command fields (kind, to, body, intent, params, etc.)
|
|
367
|
-
* @returns Receipt info on success
|
|
368
|
-
*/
|
|
369
|
-
async send(sessionId, command) {
|
|
370
|
-
return (await this.request("comms/send", {
|
|
371
|
-
session_id: sessionId,
|
|
372
|
-
...command,
|
|
373
|
-
}));
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Send a command and open a stream in one call.
|
|
377
|
-
*
|
|
378
|
-
* @param sessionId - Target session ID
|
|
379
|
-
* @param command - Command fields (kind, to, body, etc.)
|
|
380
|
-
* @returns Receipt and stream info
|
|
381
|
-
*/
|
|
382
|
-
async sendAndStream(sessionId, command) {
|
|
383
|
-
return (await this.request("comms/send", {
|
|
384
|
-
session_id: sessionId,
|
|
385
|
-
stream: "reserve_interaction",
|
|
386
|
-
...command,
|
|
387
|
-
}));
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* List peers visible to a session's comms runtime.
|
|
391
|
-
*
|
|
392
|
-
* @param sessionId - Target session ID
|
|
393
|
-
* @returns Object with `{ peers: [...] }`
|
|
394
|
-
*/
|
|
395
|
-
async peers(sessionId) {
|
|
396
|
-
return (await this.request("comms/peers", {
|
|
397
|
-
session_id: sessionId,
|
|
398
|
-
}));
|
|
399
|
-
}
|
|
400
|
-
// --- Internal ---
|
|
401
|
-
/**
|
|
402
|
-
* Normalize a CapabilityStatus from the wire.
|
|
403
|
-
* Available is the string "Available", but other variants are
|
|
404
|
-
* externally-tagged objects like {"DisabledByPolicy": {"description": "..."}}.
|
|
405
|
-
* We normalize to the variant name string.
|
|
406
|
-
*/
|
|
407
|
-
static normalizeStatus(raw) {
|
|
408
|
-
if (typeof raw === "string")
|
|
409
|
-
return raw;
|
|
410
|
-
if (typeof raw === "object" && raw !== null) {
|
|
411
|
-
const keys = Object.keys(raw);
|
|
412
|
-
return keys[0] ?? "Unknown";
|
|
413
|
-
}
|
|
414
|
-
return String(raw);
|
|
415
|
-
}
|
|
416
|
-
request(method, params) {
|
|
417
|
-
if (!this.process?.stdin) {
|
|
418
|
-
throw new MeerkatError("NOT_CONNECTED", "Client not connected");
|
|
419
|
-
}
|
|
420
|
-
this.requestId++;
|
|
421
|
-
const id = this.requestId;
|
|
422
|
-
const request = {
|
|
423
|
-
jsonrpc: "2.0",
|
|
424
|
-
id,
|
|
425
|
-
method,
|
|
426
|
-
params,
|
|
427
|
-
};
|
|
428
|
-
return new Promise((resolve, reject) => {
|
|
429
|
-
this.pendingRequests.set(id, { resolve, reject });
|
|
430
|
-
this.process.stdin.write(JSON.stringify(request) + "\n");
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
checkVersionCompatible(server, client) {
|
|
434
|
-
try {
|
|
435
|
-
const s = server.split(".").map(Number);
|
|
436
|
-
const c = client.split(".").map(Number);
|
|
437
|
-
if (s[0] === 0 && c[0] === 0)
|
|
438
|
-
return s[1] === c[1];
|
|
439
|
-
return s[0] === c[0];
|
|
440
|
-
}
|
|
441
|
-
catch {
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
parseRunResult(data) {
|
|
446
|
-
const usage = data.usage;
|
|
447
|
-
return {
|
|
448
|
-
session_id: String(data.session_id ?? ""),
|
|
449
|
-
session_ref: data.session_ref == null ? undefined : String(data.session_ref),
|
|
450
|
-
text: String(data.text ?? ""),
|
|
451
|
-
turns: Number(data.turns ?? 0),
|
|
452
|
-
tool_calls: Number(data.tool_calls ?? 0),
|
|
453
|
-
usage: {
|
|
454
|
-
input_tokens: Number(usage?.input_tokens ?? 0),
|
|
455
|
-
output_tokens: Number(usage?.output_tokens ?? 0),
|
|
456
|
-
total_tokens: Number(usage?.total_tokens ?? 0),
|
|
457
|
-
cache_creation_tokens: usage?.cache_creation_tokens,
|
|
458
|
-
cache_read_tokens: usage?.cache_read_tokens,
|
|
459
|
-
},
|
|
460
|
-
structured_output: data.structured_output,
|
|
461
|
-
schema_warnings: data.schema_warnings,
|
|
462
|
-
};
|
|
797
|
+
if (options.isolated)
|
|
798
|
+
args.push("--isolated");
|
|
799
|
+
if (options.realmId)
|
|
800
|
+
args.push("--realm", options.realmId);
|
|
801
|
+
if (options.instanceId)
|
|
802
|
+
args.push("--instance", options.instanceId);
|
|
803
|
+
if (options.realmBackend)
|
|
804
|
+
args.push("--realm-backend", options.realmBackend);
|
|
805
|
+
if (options.stateRoot)
|
|
806
|
+
args.push("--state-root", options.stateRoot);
|
|
807
|
+
if (options.contextRoot)
|
|
808
|
+
args.push("--context-root", options.contextRoot);
|
|
809
|
+
if (options.userConfigRoot)
|
|
810
|
+
args.push("--user-config-root", options.userConfigRoot);
|
|
811
|
+
return args;
|
|
463
812
|
}
|
|
464
813
|
}
|
|
465
814
|
//# sourceMappingURL=client.js.map
|