kodelet 0.4.25-alpha1 → 0.4.31-alpha
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/agent.d.ts +241 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +956 -0
- package/dist/agent.js.map +1 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +20 -8
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/agent.js
ADDED
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
import { spawn as spawnProcess } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createServer } from "node:net";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { createExtensionHost } from "./api.js";
|
|
9
|
+
import { runWithHostRPCClient } from "./context.js";
|
|
10
|
+
const ACP_PROTOCOL_VERSION = 1;
|
|
11
|
+
export class Profile {
|
|
12
|
+
name;
|
|
13
|
+
config;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
if (typeof config === "string") {
|
|
16
|
+
this.name = config;
|
|
17
|
+
this.config = { name: config };
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const normalized = { ...config };
|
|
21
|
+
if (normalized.provider === undefined && typeof normalized.profiler === "string") {
|
|
22
|
+
normalized.provider = normalized.profiler;
|
|
23
|
+
}
|
|
24
|
+
delete normalized.profiler;
|
|
25
|
+
this.name = typeof normalized.name === "string" ? normalized.name : undefined;
|
|
26
|
+
this.config = normalized;
|
|
27
|
+
}
|
|
28
|
+
static named(name) {
|
|
29
|
+
return new Profile(name);
|
|
30
|
+
}
|
|
31
|
+
isNamedOnly() {
|
|
32
|
+
return Object.keys(this.config).every((key) => key === "name");
|
|
33
|
+
}
|
|
34
|
+
toLaunchConfig() {
|
|
35
|
+
if (this.name && this.isNamedOnly()) {
|
|
36
|
+
return { args: ["--profile", this.name] };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
args: [],
|
|
40
|
+
config: this.config,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class AgentRunError extends Error {
|
|
45
|
+
code;
|
|
46
|
+
signal;
|
|
47
|
+
stderr;
|
|
48
|
+
constructor(message, opts) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = "AgentRunError";
|
|
51
|
+
this.code = opts.code;
|
|
52
|
+
this.signal = opts.signal;
|
|
53
|
+
this.stderr = opts.stderr;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
class RPCError extends Error {
|
|
57
|
+
code;
|
|
58
|
+
data;
|
|
59
|
+
constructor(error) {
|
|
60
|
+
super(error.message);
|
|
61
|
+
this.name = "RPCError";
|
|
62
|
+
this.code = error.code;
|
|
63
|
+
this.data = error.data;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export class Client {
|
|
67
|
+
command;
|
|
68
|
+
cwd;
|
|
69
|
+
env;
|
|
70
|
+
spawn;
|
|
71
|
+
sessions = new Set();
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.command = options.command ?? "kodelet";
|
|
74
|
+
this.cwd = path.resolve(options.cwd ?? process.cwd());
|
|
75
|
+
this.env = options.env ?? {};
|
|
76
|
+
this.spawn = options.spawn ?? ((command, args, spawnOptions) => spawnProcess(command, args, spawnOptions));
|
|
77
|
+
}
|
|
78
|
+
async createSession(options = {}) {
|
|
79
|
+
const bridge = options.extensions?.length
|
|
80
|
+
? await InMemoryExtensionBridge.create(options.extensions, { ui: options.ui })
|
|
81
|
+
: undefined;
|
|
82
|
+
const cwd = path.resolve(options.cwd ?? this.cwd);
|
|
83
|
+
const profile = normalizeProfile(options.profile);
|
|
84
|
+
let launch;
|
|
85
|
+
let rpc;
|
|
86
|
+
try {
|
|
87
|
+
launch = await buildLaunchConfig(profile, bridge);
|
|
88
|
+
const env = cleanEnv({
|
|
89
|
+
...this._baseEnv({ isolateKodeletEnv: launch.configFileMode === "isolated" }),
|
|
90
|
+
...launch.env,
|
|
91
|
+
});
|
|
92
|
+
const args = [...launch.args, "acp", ...acpServerArgs(options)];
|
|
93
|
+
rpc = new ACPRPCClient(this._spawn(args, { cwd, env, stdio: ["pipe", "pipe", "pipe"] }));
|
|
94
|
+
await rpc.initialize();
|
|
95
|
+
const sessionID = options.resume ? await rpc.loadSession(options.resume, cwd) : await rpc.createSession(cwd);
|
|
96
|
+
const session = new Session(this, {
|
|
97
|
+
...options,
|
|
98
|
+
cwd,
|
|
99
|
+
profile,
|
|
100
|
+
sessionID,
|
|
101
|
+
rpc,
|
|
102
|
+
extensionBridge: bridge,
|
|
103
|
+
tempConfig: launch.tempConfig,
|
|
104
|
+
});
|
|
105
|
+
this.sessions.add(session);
|
|
106
|
+
return session;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
await rpc?.close();
|
|
110
|
+
await bridge?.close();
|
|
111
|
+
await launch?.tempConfig?.close();
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async close() {
|
|
116
|
+
await Promise.all([...this.sessions].map((session) => session.close()));
|
|
117
|
+
this.sessions.clear();
|
|
118
|
+
}
|
|
119
|
+
_spawn(args, options) {
|
|
120
|
+
return this.spawn(this.command, args, options);
|
|
121
|
+
}
|
|
122
|
+
_baseEnv(options = {}) {
|
|
123
|
+
const env = { ...process.env };
|
|
124
|
+
if (options.isolateKodeletEnv) {
|
|
125
|
+
for (const key of Object.keys(env)) {
|
|
126
|
+
if (key.startsWith("KODELET_")) {
|
|
127
|
+
delete env[key];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return { ...env, ...this.env };
|
|
132
|
+
}
|
|
133
|
+
_deleteSession(session) {
|
|
134
|
+
this.sessions.delete(session);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export class Session extends EventEmitter {
|
|
138
|
+
cwd;
|
|
139
|
+
client;
|
|
140
|
+
rpc;
|
|
141
|
+
maxTurns;
|
|
142
|
+
extensionBridge;
|
|
143
|
+
tempConfig;
|
|
144
|
+
conversationId;
|
|
145
|
+
closed = false;
|
|
146
|
+
running = false;
|
|
147
|
+
constructor(client, options) {
|
|
148
|
+
super();
|
|
149
|
+
this.client = client;
|
|
150
|
+
this.cwd = options.cwd;
|
|
151
|
+
this.rpc = options.rpc;
|
|
152
|
+
this.maxTurns = options.maxTurns;
|
|
153
|
+
this.conversationId = options.sessionID;
|
|
154
|
+
this.extensionBridge = options.extensionBridge;
|
|
155
|
+
this.tempConfig = options.tempConfig;
|
|
156
|
+
}
|
|
157
|
+
get id() {
|
|
158
|
+
return this.conversationId;
|
|
159
|
+
}
|
|
160
|
+
on(eventName, listener) {
|
|
161
|
+
return super.on(eventName, listener);
|
|
162
|
+
}
|
|
163
|
+
once(eventName, listener) {
|
|
164
|
+
return super.once(eventName, listener);
|
|
165
|
+
}
|
|
166
|
+
off(eventName, listener) {
|
|
167
|
+
return super.off(eventName, listener);
|
|
168
|
+
}
|
|
169
|
+
async runAndWait(options) {
|
|
170
|
+
if (this.closed) {
|
|
171
|
+
throw new Error("Cannot run a closed Kodelet session");
|
|
172
|
+
}
|
|
173
|
+
if (this.running) {
|
|
174
|
+
throw new Error("Cannot run a Kodelet session while another run is in progress");
|
|
175
|
+
}
|
|
176
|
+
if (options.maxTurns !== undefined && options.maxTurns !== this.maxTurns) {
|
|
177
|
+
throw new Error("Per-run maxTurns is not supported by the RPC transport; set maxTurns in createSession instead");
|
|
178
|
+
}
|
|
179
|
+
throwIfAlreadyAborted(options.signal);
|
|
180
|
+
this.running = true;
|
|
181
|
+
const events = [];
|
|
182
|
+
const assistantChunks = [];
|
|
183
|
+
const thinkingActive = { value: false };
|
|
184
|
+
const toolNames = new Map();
|
|
185
|
+
const unsubscribe = this.rpc.onNotification((method, params) => {
|
|
186
|
+
if (method !== "session/update") {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.handleSessionUpdate(params, events, assistantChunks, thinkingActive, toolNames);
|
|
190
|
+
});
|
|
191
|
+
const abort = () => this.rpc.cancelSession(this.conversationId);
|
|
192
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
193
|
+
this.emitSDKEvent("agent.start", { message: options.message }, events);
|
|
194
|
+
this.emitSDKEvent("user.message", { content: options.message }, events);
|
|
195
|
+
try {
|
|
196
|
+
const result = await this.rpc.prompt(this.conversationId, buildPromptBlocks(options));
|
|
197
|
+
if (thinkingActive.value) {
|
|
198
|
+
thinkingActive.value = false;
|
|
199
|
+
this.emitSDKEvent("assistant.thinking_end", {}, events);
|
|
200
|
+
}
|
|
201
|
+
this.emitSDKEvent("assistant.content_end", {}, events);
|
|
202
|
+
const content = assistantChunks.join("");
|
|
203
|
+
if (content !== "") {
|
|
204
|
+
this.emitSDKEvent("assistant.message", { content }, events);
|
|
205
|
+
}
|
|
206
|
+
const response = {
|
|
207
|
+
content,
|
|
208
|
+
conversationId: this.conversationId,
|
|
209
|
+
events,
|
|
210
|
+
exitCode: 0,
|
|
211
|
+
stopReason: result.stopReason,
|
|
212
|
+
};
|
|
213
|
+
this.emitSDKEvent("agent.end", { ...response, events: [...events] }, events);
|
|
214
|
+
return response;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
this.emitSDKEvent("agent.error", { message: errorMessage(error) }, events);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
options.signal?.removeEventListener("abort", abort);
|
|
222
|
+
unsubscribe();
|
|
223
|
+
this.running = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async close() {
|
|
227
|
+
if (this.closed) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
this.closed = true;
|
|
231
|
+
await this.rpc.close();
|
|
232
|
+
await this.extensionBridge?.close();
|
|
233
|
+
await this.tempConfig?.close();
|
|
234
|
+
this.client._deleteSession(this);
|
|
235
|
+
}
|
|
236
|
+
handleSessionUpdate(params, events, assistantChunks, thinkingActive, toolNames) {
|
|
237
|
+
if (!isRecord(params)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const sessionId = stringField(params, "sessionId");
|
|
241
|
+
if (sessionId !== this.conversationId) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const update = params.update;
|
|
245
|
+
if (!isRecord(update)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
switch (stringField(update, "sessionUpdate")) {
|
|
249
|
+
case "agent_message_chunk": {
|
|
250
|
+
const content = textFromACPContent(update.content);
|
|
251
|
+
if (content !== "") {
|
|
252
|
+
assistantChunks.push(content);
|
|
253
|
+
this.emitSDKEvent("assistant.message_delta", { deltaContent: content }, events, update);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case "agent_thought_chunk": {
|
|
258
|
+
if (!thinkingActive.value) {
|
|
259
|
+
thinkingActive.value = true;
|
|
260
|
+
this.emitSDKEvent("assistant.thinking_start", {}, events, update);
|
|
261
|
+
}
|
|
262
|
+
const content = textFromACPContent(update.content);
|
|
263
|
+
if (content !== "") {
|
|
264
|
+
this.emitSDKEvent("assistant.thinking_delta", { deltaContent: content }, events, update);
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case "tool_call": {
|
|
269
|
+
const tool = update;
|
|
270
|
+
const toolCallId = tool.toolCallId;
|
|
271
|
+
const toolName = toolNameFromUpdate(tool);
|
|
272
|
+
if (toolCallId && toolName) {
|
|
273
|
+
toolNames.set(toolCallId, toolName);
|
|
274
|
+
}
|
|
275
|
+
this.emitSDKEvent("tool.call", {
|
|
276
|
+
toolName,
|
|
277
|
+
input: tool.rawInput,
|
|
278
|
+
rawInput: typeof tool.rawInput === "string" ? tool.rawInput : JSON.stringify(tool.rawInput ?? null),
|
|
279
|
+
toolCallId,
|
|
280
|
+
}, events, update);
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case "tool_call_update": {
|
|
284
|
+
const tool = update;
|
|
285
|
+
if (tool.status !== "completed" && tool.status !== "failed") {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const toolCallId = tool.toolCallId;
|
|
289
|
+
this.emitSDKEvent("tool.result", {
|
|
290
|
+
toolName: (toolCallId && toolNames.get(toolCallId)) || toolNameFromUpdate(tool),
|
|
291
|
+
result: toolContentToText(tool.content),
|
|
292
|
+
toolCallId,
|
|
293
|
+
status: tool.status,
|
|
294
|
+
}, events, update);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
default:
|
|
298
|
+
this.emitSDKEvent("event", update, events, update);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
emitSDKEvent(type, data, events, raw) {
|
|
302
|
+
const event = { type, data, conversationId: this.conversationId, raw };
|
|
303
|
+
events.push(event);
|
|
304
|
+
this.emit(type, event);
|
|
305
|
+
if (type !== "event") {
|
|
306
|
+
this.emit("event", event);
|
|
307
|
+
}
|
|
308
|
+
return event;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
class ACPRPCClient {
|
|
312
|
+
child;
|
|
313
|
+
nextId = 0;
|
|
314
|
+
pending = new Map();
|
|
315
|
+
stdoutBuffer = new LineBuffer();
|
|
316
|
+
stderrChunks = [];
|
|
317
|
+
notificationHandlers = new Set();
|
|
318
|
+
closed = false;
|
|
319
|
+
constructor(child) {
|
|
320
|
+
this.child = child;
|
|
321
|
+
if (!child.stdin) {
|
|
322
|
+
throw new Error("kodelet acp process did not expose stdin");
|
|
323
|
+
}
|
|
324
|
+
child.stdout?.on("data", (chunk) => {
|
|
325
|
+
this.stdoutBuffer.push(String(chunk));
|
|
326
|
+
for (const line of this.stdoutBuffer.drainLines()) {
|
|
327
|
+
this.handleLine(line);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
child.stderr?.on("data", (chunk) => {
|
|
331
|
+
this.stderrChunks.push(String(chunk));
|
|
332
|
+
});
|
|
333
|
+
child.once("error", (error) => {
|
|
334
|
+
this.closed = true;
|
|
335
|
+
this.rejectPending(error);
|
|
336
|
+
});
|
|
337
|
+
child.once("close", (code, signal) => {
|
|
338
|
+
this.closed = true;
|
|
339
|
+
const message = this.stderrChunks.join("").trim() || `kodelet acp exited with status ${code ?? "unknown"}${signal ? ` (${signal})` : ""}`;
|
|
340
|
+
this.rejectPending(new AgentRunError(message, { code, signal, stderr: this.stderrChunks.join("") }));
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async initialize() {
|
|
344
|
+
await this.request("initialize", {
|
|
345
|
+
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
346
|
+
clientCapabilities: {
|
|
347
|
+
terminal: true,
|
|
348
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
349
|
+
},
|
|
350
|
+
clientInfo: { name: "kodelet-sdk", title: "Kodelet SDK" },
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async createSession(cwd) {
|
|
354
|
+
const result = await this.request("session/new", { cwd });
|
|
355
|
+
if (!isRecord(result) || typeof result.sessionId !== "string") {
|
|
356
|
+
throw new Error("Invalid session/new response from kodelet acp");
|
|
357
|
+
}
|
|
358
|
+
return result.sessionId;
|
|
359
|
+
}
|
|
360
|
+
async loadSession(sessionId, cwd) {
|
|
361
|
+
await this.request("session/load", { sessionId, cwd });
|
|
362
|
+
return sessionId;
|
|
363
|
+
}
|
|
364
|
+
async prompt(sessionId, prompt) {
|
|
365
|
+
const result = await this.request("session/prompt", { sessionId, prompt });
|
|
366
|
+
if (!isRecord(result)) {
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
return { stopReason: stringField(result, "stopReason") };
|
|
370
|
+
}
|
|
371
|
+
cancelSession(sessionId) {
|
|
372
|
+
this.notify("session/cancel", { sessionId });
|
|
373
|
+
}
|
|
374
|
+
onNotification(handler) {
|
|
375
|
+
this.notificationHandlers.add(handler);
|
|
376
|
+
return () => this.notificationHandlers.delete(handler);
|
|
377
|
+
}
|
|
378
|
+
async close() {
|
|
379
|
+
if (this.closed) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.closed = true;
|
|
383
|
+
this.child.kill("SIGTERM");
|
|
384
|
+
await new Promise((resolve) => {
|
|
385
|
+
this.child.once("close", () => resolve());
|
|
386
|
+
setTimeout(resolve, 1000).unref?.();
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
request(method, params) {
|
|
390
|
+
if (this.closed) {
|
|
391
|
+
throw new Error("kodelet acp process is closed");
|
|
392
|
+
}
|
|
393
|
+
const id = ++this.nextId;
|
|
394
|
+
return new Promise((resolve, reject) => {
|
|
395
|
+
this.pending.set(id, { resolve, reject });
|
|
396
|
+
this.write({ jsonrpc: "2.0", id, method, params });
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
notify(method, params) {
|
|
400
|
+
if (!this.closed) {
|
|
401
|
+
this.write({ jsonrpc: "2.0", method, params });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
write(message) {
|
|
405
|
+
this.child.stdin?.write(`${JSON.stringify(message)}\n`);
|
|
406
|
+
}
|
|
407
|
+
rejectPending(error) {
|
|
408
|
+
for (const pending of this.pending.values()) {
|
|
409
|
+
pending.reject(error);
|
|
410
|
+
}
|
|
411
|
+
this.pending.clear();
|
|
412
|
+
}
|
|
413
|
+
handleLine(line) {
|
|
414
|
+
const trimmed = line.trim();
|
|
415
|
+
if (!trimmed) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
let message;
|
|
419
|
+
try {
|
|
420
|
+
message = JSON.parse(trimmed);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
for (const handler of this.notificationHandlers) {
|
|
424
|
+
handler("$/stdout", { line });
|
|
425
|
+
}
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (message.method && message.id !== undefined && message.id !== null) {
|
|
429
|
+
this.respondToServerRequest(message);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (message.method) {
|
|
433
|
+
for (const handler of this.notificationHandlers) {
|
|
434
|
+
handler(message.method, message.params);
|
|
435
|
+
}
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (typeof message.id !== "number") {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const pending = this.pending.get(message.id);
|
|
442
|
+
if (!pending) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.pending.delete(message.id);
|
|
446
|
+
if (message.error) {
|
|
447
|
+
pending.reject(new RPCError(message.error));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
pending.resolve(message.result);
|
|
451
|
+
}
|
|
452
|
+
respondToServerRequest(message) {
|
|
453
|
+
this.write({
|
|
454
|
+
jsonrpc: "2.0",
|
|
455
|
+
id: message.id,
|
|
456
|
+
error: { code: -32601, message: `Unsupported client RPC method: ${message.method}` },
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
class InMemoryExtensionBridge {
|
|
461
|
+
rootDir;
|
|
462
|
+
servers;
|
|
463
|
+
constructor(rootDir, servers) {
|
|
464
|
+
this.rootDir = rootDir;
|
|
465
|
+
this.servers = servers;
|
|
466
|
+
}
|
|
467
|
+
static async create(entrypoints, options = {}) {
|
|
468
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "kodelet-sdk-extensions-"));
|
|
469
|
+
const bridgeId = randomUUID().replace(/-/g, "").slice(0, 16);
|
|
470
|
+
const servers = [];
|
|
471
|
+
try {
|
|
472
|
+
for (const [index, entrypoint] of entrypoints.entries()) {
|
|
473
|
+
const id = `sdk-${bridgeId}-${index + 1}`;
|
|
474
|
+
const socketPath = extensionSocketPath(rootDir, id);
|
|
475
|
+
const host = await createExtensionHost(entrypoint);
|
|
476
|
+
const server = new ExtensionSocketServer(host, socketPath, options.ui);
|
|
477
|
+
await server.listen();
|
|
478
|
+
servers.push(server);
|
|
479
|
+
const executablePath = path.join(rootDir, `kodelet-extension-${id}`);
|
|
480
|
+
await writeFile(executablePath, extensionBridgeExecutable(socketPath), "utf8");
|
|
481
|
+
await chmod(executablePath, 0o755);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
await Promise.allSettled(servers.map((server) => server.close()));
|
|
486
|
+
await rm(rootDir, { recursive: true, force: true });
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
return new InMemoryExtensionBridge(rootDir, servers);
|
|
490
|
+
}
|
|
491
|
+
config() {
|
|
492
|
+
return {
|
|
493
|
+
enabled: true,
|
|
494
|
+
local_dir: this.rootDir,
|
|
495
|
+
allow: [this.rootDir],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
async close() {
|
|
499
|
+
await Promise.allSettled(this.servers.map((server) => server.close()));
|
|
500
|
+
await rm(this.rootDir, { recursive: true, force: true });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
class TempConfig {
|
|
504
|
+
rootDir;
|
|
505
|
+
path;
|
|
506
|
+
constructor(rootDir, path) {
|
|
507
|
+
this.rootDir = rootDir;
|
|
508
|
+
this.path = path;
|
|
509
|
+
}
|
|
510
|
+
static async create(config) {
|
|
511
|
+
const rootDir = await mkdtemp(path.join(os.tmpdir(), "kodelet-sdk-config-"));
|
|
512
|
+
const configPath = path.join(rootDir, "kodelet-config.json");
|
|
513
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
514
|
+
return new TempConfig(rootDir, configPath);
|
|
515
|
+
}
|
|
516
|
+
async close() {
|
|
517
|
+
await rm(this.rootDir, { recursive: true, force: true });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
class ExtensionSocketServer {
|
|
521
|
+
host;
|
|
522
|
+
socketPath;
|
|
523
|
+
ui;
|
|
524
|
+
server;
|
|
525
|
+
socket;
|
|
526
|
+
nextId = 0;
|
|
527
|
+
pending = new Map();
|
|
528
|
+
constructor(host, socketPath, ui) {
|
|
529
|
+
this.host = host;
|
|
530
|
+
this.socketPath = socketPath;
|
|
531
|
+
this.ui = ui;
|
|
532
|
+
}
|
|
533
|
+
async listen() {
|
|
534
|
+
await rm(this.socketPath, { force: true });
|
|
535
|
+
this.server = createServer((socket) => {
|
|
536
|
+
this.socket = socket;
|
|
537
|
+
const reader = new FrameReader();
|
|
538
|
+
socket.on("data", (chunk) => {
|
|
539
|
+
for (const payload of reader.push(chunk)) {
|
|
540
|
+
void this.handlePayload(payload, socket);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
socket.on("close", () => {
|
|
544
|
+
if (this.socket === socket) {
|
|
545
|
+
this.socket = undefined;
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
await new Promise((resolve, reject) => {
|
|
550
|
+
this.server?.once("error", reject);
|
|
551
|
+
this.server?.listen(this.socketPath, () => {
|
|
552
|
+
this.server?.off("error", reject);
|
|
553
|
+
resolve();
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async close() {
|
|
558
|
+
for (const pending of this.pending.values()) {
|
|
559
|
+
pending.reject(new Error("Extension bridge closed"));
|
|
560
|
+
}
|
|
561
|
+
this.pending.clear();
|
|
562
|
+
this.socket?.destroy();
|
|
563
|
+
await new Promise((resolve) => {
|
|
564
|
+
if (!this.server) {
|
|
565
|
+
resolve();
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
this.server.close(() => resolve());
|
|
569
|
+
});
|
|
570
|
+
await rm(this.socketPath, { force: true });
|
|
571
|
+
}
|
|
572
|
+
async request(method, params) {
|
|
573
|
+
const localUIResponse = await this.tryHandleLocalUIRequest(method, params);
|
|
574
|
+
if (localUIResponse.handled) {
|
|
575
|
+
return localUIResponse.result;
|
|
576
|
+
}
|
|
577
|
+
if (!this.socket) {
|
|
578
|
+
throw new Error("Extension bridge is not connected");
|
|
579
|
+
}
|
|
580
|
+
const id = ++this.nextId;
|
|
581
|
+
writeFrame(this.socket, JSON.stringify({ jsonrpc: "2.0", id, method, params }));
|
|
582
|
+
return await new Promise((resolve, reject) => {
|
|
583
|
+
this.pending.set(id, { resolve, reject });
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
async handlePayload(payload, socket) {
|
|
587
|
+
let message;
|
|
588
|
+
try {
|
|
589
|
+
message = JSON.parse(payload.toString("utf8"));
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
writeFrame(socket, JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: errorMessage(error) } }));
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (!message.method && message.id !== undefined) {
|
|
596
|
+
this.handleResponse(message);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!message.method || message.id === undefined || message.id === null) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const result = await runWithHostRPCClient(this, () => this.dispatch(message));
|
|
604
|
+
writeFrame(socket, JSON.stringify({ jsonrpc: "2.0", id: message.id, result }));
|
|
605
|
+
}
|
|
606
|
+
catch (error) {
|
|
607
|
+
writeFrame(socket, JSON.stringify({ jsonrpc: "2.0", id: message.id, error: { code: -32000, message: errorMessage(error) } }));
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
handleResponse(response) {
|
|
611
|
+
if (typeof response.id !== "number") {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const pending = this.pending.get(response.id);
|
|
615
|
+
if (!pending) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.pending.delete(response.id);
|
|
619
|
+
if (response.error) {
|
|
620
|
+
pending.reject(new Error(response.error.message));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
pending.resolve(response.result);
|
|
624
|
+
}
|
|
625
|
+
async dispatch(message) {
|
|
626
|
+
switch (message.method) {
|
|
627
|
+
case "extension.initialize":
|
|
628
|
+
return this.host.initialize(message.params);
|
|
629
|
+
case "extension.tool.execute":
|
|
630
|
+
return await this.host.executeTool(message.params);
|
|
631
|
+
case "extension.command.execute":
|
|
632
|
+
return await this.host.executeCommand(message.params);
|
|
633
|
+
case "extension.event.handle":
|
|
634
|
+
return await this.host.handleEvent(message.params);
|
|
635
|
+
case "$/cancelRequest":
|
|
636
|
+
return undefined;
|
|
637
|
+
default:
|
|
638
|
+
throw new Error(`Unknown JSON-RPC method: ${message.method}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async tryHandleLocalUIRequest(method, params) {
|
|
642
|
+
switch (method) {
|
|
643
|
+
case "kodelet.ui.input": {
|
|
644
|
+
if (!this.ui?.input) {
|
|
645
|
+
return { handled: true, result: unavailableUI("ui input is not available") };
|
|
646
|
+
}
|
|
647
|
+
const value = await this.ui.input(params);
|
|
648
|
+
return { handled: true, result: value === undefined ? dismissedUI() : { status: "submitted", value } };
|
|
649
|
+
}
|
|
650
|
+
case "kodelet.ui.confirm": {
|
|
651
|
+
if (!this.ui?.confirm) {
|
|
652
|
+
return { handled: true, result: unavailableUI("ui confirm is not available") };
|
|
653
|
+
}
|
|
654
|
+
const confirmed = await this.ui.confirm(params);
|
|
655
|
+
return { handled: true, result: { status: "submitted", confirmed } };
|
|
656
|
+
}
|
|
657
|
+
case "kodelet.ui.select": {
|
|
658
|
+
if (!this.ui?.select) {
|
|
659
|
+
return { handled: true, result: unavailableUI("ui select is not available") };
|
|
660
|
+
}
|
|
661
|
+
const value = await this.ui.select(params);
|
|
662
|
+
return { handled: true, result: value === undefined ? dismissedUI() : { status: "submitted", value } };
|
|
663
|
+
}
|
|
664
|
+
case "kodelet.ui.notify": {
|
|
665
|
+
if (!this.ui?.notify) {
|
|
666
|
+
return { handled: true, result: unavailableUI("ui notify is not available") };
|
|
667
|
+
}
|
|
668
|
+
await this.ui.notify(params);
|
|
669
|
+
return { handled: true, result: { status: "submitted" } };
|
|
670
|
+
}
|
|
671
|
+
default:
|
|
672
|
+
return { handled: false };
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
class FrameReader {
|
|
677
|
+
buffer = Buffer.alloc(0);
|
|
678
|
+
push(chunk) {
|
|
679
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
680
|
+
const frames = [];
|
|
681
|
+
while (true) {
|
|
682
|
+
const frame = tryReadFrame(this.buffer);
|
|
683
|
+
if (!frame) {
|
|
684
|
+
return frames;
|
|
685
|
+
}
|
|
686
|
+
frames.push(frame.payload);
|
|
687
|
+
this.buffer = frame.remaining;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
class LineBuffer {
|
|
692
|
+
buffer = "";
|
|
693
|
+
push(chunk) {
|
|
694
|
+
this.buffer += chunk;
|
|
695
|
+
}
|
|
696
|
+
drainLines() {
|
|
697
|
+
const lines = [];
|
|
698
|
+
while (true) {
|
|
699
|
+
const index = this.buffer.indexOf("\n");
|
|
700
|
+
if (index === -1) {
|
|
701
|
+
return lines;
|
|
702
|
+
}
|
|
703
|
+
lines.push(this.buffer.slice(0, index).replace(/\r$/, ""));
|
|
704
|
+
this.buffer = this.buffer.slice(index + 1);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function normalizeProfile(profile) {
|
|
709
|
+
if (profile === undefined) {
|
|
710
|
+
return undefined;
|
|
711
|
+
}
|
|
712
|
+
return profile instanceof Profile ? profile : new Profile(profile);
|
|
713
|
+
}
|
|
714
|
+
function acpServerArgs(options) {
|
|
715
|
+
const args = [];
|
|
716
|
+
if (options.maxTurns !== undefined && options.maxTurns > 0) {
|
|
717
|
+
args.push("--max-turns", String(options.maxTurns));
|
|
718
|
+
}
|
|
719
|
+
return args;
|
|
720
|
+
}
|
|
721
|
+
async function buildLaunchConfig(profile, bridge) {
|
|
722
|
+
const resolved = profile?.toLaunchConfig();
|
|
723
|
+
const profileConfig = resolved?.config;
|
|
724
|
+
const config = pruneUndefined({
|
|
725
|
+
...(profileConfig ?? {}),
|
|
726
|
+
...(profileConfig && profileConfig.profile === undefined ? { profile: "default" } : {}),
|
|
727
|
+
...(bridge ? { extensions: bridge.config() } : {}),
|
|
728
|
+
});
|
|
729
|
+
if (Object.keys(config).length === 0) {
|
|
730
|
+
return { args: resolved?.args ?? [], env: {} };
|
|
731
|
+
}
|
|
732
|
+
const tempConfig = await TempConfig.create(config);
|
|
733
|
+
const configFileMode = profileConfig ? "isolated" : "merge";
|
|
734
|
+
return {
|
|
735
|
+
args: resolved?.args ?? [],
|
|
736
|
+
env: {
|
|
737
|
+
KODELET_CONFIG_FILE: tempConfig.path,
|
|
738
|
+
KODELET_CONFIG_FILE_MODE: configFileMode,
|
|
739
|
+
},
|
|
740
|
+
tempConfig,
|
|
741
|
+
configFileMode,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function pruneUndefined(value) {
|
|
745
|
+
const result = {};
|
|
746
|
+
for (const [key, item] of Object.entries(value)) {
|
|
747
|
+
if (item === undefined) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (isPlainObject(item)) {
|
|
751
|
+
result[key] = pruneUndefined(item);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
result[key] = item;
|
|
755
|
+
}
|
|
756
|
+
return result;
|
|
757
|
+
}
|
|
758
|
+
function buildPromptBlocks(options) {
|
|
759
|
+
const prompt = [{ type: "text", text: options.message }];
|
|
760
|
+
for (const image of options.images ?? []) {
|
|
761
|
+
prompt.push(imageToContentBlock(image));
|
|
762
|
+
}
|
|
763
|
+
return prompt;
|
|
764
|
+
}
|
|
765
|
+
function throwIfAlreadyAborted(signal) {
|
|
766
|
+
if (!signal?.aborted) {
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
signal.throwIfAborted();
|
|
770
|
+
const error = new Error("The operation was aborted");
|
|
771
|
+
error.name = "AbortError";
|
|
772
|
+
throw error;
|
|
773
|
+
}
|
|
774
|
+
function imageToContentBlock(image) {
|
|
775
|
+
const match = image.match(/^data:([^;,]+);base64,(.*)$/);
|
|
776
|
+
if (match) {
|
|
777
|
+
return { type: "image", mimeType: match[1], data: match[2] };
|
|
778
|
+
}
|
|
779
|
+
return { type: "image", uri: image };
|
|
780
|
+
}
|
|
781
|
+
function textFromACPContent(content) {
|
|
782
|
+
if (!isRecord(content)) {
|
|
783
|
+
return "";
|
|
784
|
+
}
|
|
785
|
+
if (typeof content.text === "string") {
|
|
786
|
+
return content.text;
|
|
787
|
+
}
|
|
788
|
+
if (isRecord(content.resource) && typeof content.resource.text === "string") {
|
|
789
|
+
return content.resource.text;
|
|
790
|
+
}
|
|
791
|
+
return "";
|
|
792
|
+
}
|
|
793
|
+
function toolNameFromUpdate(update) {
|
|
794
|
+
if (typeof update.toolName === "string" && update.toolName.trim() !== "") {
|
|
795
|
+
return update.toolName;
|
|
796
|
+
}
|
|
797
|
+
return "";
|
|
798
|
+
}
|
|
799
|
+
function toolContentToText(content) {
|
|
800
|
+
if (!Array.isArray(content)) {
|
|
801
|
+
return "";
|
|
802
|
+
}
|
|
803
|
+
const parts = [];
|
|
804
|
+
for (const item of content) {
|
|
805
|
+
if (!isRecord(item)) {
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (item.type === "content" && isRecord(item.content)) {
|
|
809
|
+
const text = textFromACPContent(item.content);
|
|
810
|
+
if (text !== "") {
|
|
811
|
+
parts.push(text);
|
|
812
|
+
}
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (typeof item.path === "string") {
|
|
816
|
+
parts.push(item.path);
|
|
817
|
+
}
|
|
818
|
+
if (typeof item.newText === "string") {
|
|
819
|
+
parts.push(item.newText);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return parts.join("\n");
|
|
823
|
+
}
|
|
824
|
+
function cleanEnv(env) {
|
|
825
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
|
|
826
|
+
}
|
|
827
|
+
function stringField(record, key) {
|
|
828
|
+
const value = record[key];
|
|
829
|
+
return typeof value === "string" ? value : undefined;
|
|
830
|
+
}
|
|
831
|
+
function isRecord(value) {
|
|
832
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
833
|
+
}
|
|
834
|
+
function isPlainObject(value) {
|
|
835
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
836
|
+
}
|
|
837
|
+
function tryReadFrame(buffer) {
|
|
838
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
839
|
+
const fallbackHeaderEnd = headerEnd === -1 ? buffer.indexOf("\n\n") : -1;
|
|
840
|
+
const separatorIndex = headerEnd === -1 ? fallbackHeaderEnd : headerEnd;
|
|
841
|
+
if (separatorIndex === -1) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
const separatorLength = headerEnd === -1 ? 2 : 4;
|
|
845
|
+
const header = buffer.subarray(0, separatorIndex).toString("ascii");
|
|
846
|
+
const contentLength = parseContentLength(header);
|
|
847
|
+
const payloadStart = separatorIndex + separatorLength;
|
|
848
|
+
const payloadEnd = payloadStart + contentLength;
|
|
849
|
+
if (buffer.length < payloadEnd) {
|
|
850
|
+
return undefined;
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
payload: buffer.subarray(payloadStart, payloadEnd),
|
|
854
|
+
remaining: buffer.subarray(payloadEnd),
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function parseContentLength(header) {
|
|
858
|
+
for (const line of header.split(/\r?\n/)) {
|
|
859
|
+
const [key, value] = line.split(":", 2);
|
|
860
|
+
if (key?.trim().toLowerCase() === "content-length") {
|
|
861
|
+
const parsed = Number.parseInt(value?.trim() ?? "", 10);
|
|
862
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
863
|
+
return parsed;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
throw new Error("Missing Content-Length header");
|
|
868
|
+
}
|
|
869
|
+
function writeFrame(socket, payload) {
|
|
870
|
+
socket.write(`Content-Length: ${Buffer.byteLength(payload, "utf8")}\r\n\r\n${payload}`);
|
|
871
|
+
}
|
|
872
|
+
function extensionSocketPath(rootDir, id) {
|
|
873
|
+
if (process.platform === "win32") {
|
|
874
|
+
return `\\\\.\\pipe\\kodelet-sdk-${process.pid}-${Date.now()}-${id}`;
|
|
875
|
+
}
|
|
876
|
+
return path.join(rootDir, `${id}.sock`);
|
|
877
|
+
}
|
|
878
|
+
function extensionBridgeExecutable(socketPath) {
|
|
879
|
+
return `#!/usr/bin/env node
|
|
880
|
+
const net = require("node:net");
|
|
881
|
+
const process = require("node:process");
|
|
882
|
+
const SOCKET_PATH = ${JSON.stringify(socketPath)};
|
|
883
|
+
|
|
884
|
+
let stdinBuffer = Buffer.alloc(0);
|
|
885
|
+
let socketBuffer = Buffer.alloc(0);
|
|
886
|
+
const socket = net.createConnection(SOCKET_PATH);
|
|
887
|
+
|
|
888
|
+
socket.on("data", (chunk) => {
|
|
889
|
+
socketBuffer = Buffer.concat([socketBuffer, chunk]);
|
|
890
|
+
while (true) {
|
|
891
|
+
const frame = tryReadFrame(socketBuffer);
|
|
892
|
+
if (!frame) break;
|
|
893
|
+
socketBuffer = frame.remaining;
|
|
894
|
+
writeFrame(process.stdout, frame.payload);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
socket.on("error", (error) => {
|
|
899
|
+
process.stderr.write(JSON.stringify({ level: "error", message: "kodelet SDK extension bridge failed", error: error.message }) + "\\n");
|
|
900
|
+
process.exit(1);
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
socket.on("close", () => process.exit(0));
|
|
904
|
+
|
|
905
|
+
process.stdin.on("data", (chunk) => {
|
|
906
|
+
stdinBuffer = Buffer.concat([stdinBuffer, chunk]);
|
|
907
|
+
while (true) {
|
|
908
|
+
const frame = tryReadFrame(stdinBuffer);
|
|
909
|
+
if (!frame) break;
|
|
910
|
+
stdinBuffer = frame.remaining;
|
|
911
|
+
writeFrame(socket, frame.payload);
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
process.stdin.resume();
|
|
915
|
+
|
|
916
|
+
function tryReadFrame(buffer) {
|
|
917
|
+
const headerEnd = buffer.indexOf("\\r\\n\\r\\n");
|
|
918
|
+
const fallbackHeaderEnd = headerEnd === -1 ? buffer.indexOf("\\n\\n") : -1;
|
|
919
|
+
const separatorIndex = headerEnd === -1 ? fallbackHeaderEnd : headerEnd;
|
|
920
|
+
if (separatorIndex === -1) return undefined;
|
|
921
|
+
const separatorLength = headerEnd === -1 ? 2 : 4;
|
|
922
|
+
const header = buffer.subarray(0, separatorIndex).toString("ascii");
|
|
923
|
+
const contentLength = parseContentLength(header);
|
|
924
|
+
const payloadStart = separatorIndex + separatorLength;
|
|
925
|
+
const payloadEnd = payloadStart + contentLength;
|
|
926
|
+
if (buffer.length < payloadEnd) return undefined;
|
|
927
|
+
return { payload: buffer.subarray(payloadStart, payloadEnd), remaining: buffer.subarray(payloadEnd) };
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function parseContentLength(header) {
|
|
931
|
+
for (const line of header.split(/\\r?\\n/)) {
|
|
932
|
+
const [key, value] = line.split(":", 2);
|
|
933
|
+
if (key && key.trim().toLowerCase() === "content-length") {
|
|
934
|
+
const parsed = Number.parseInt((value || "").trim(), 10);
|
|
935
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
throw new Error("Missing Content-Length header");
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function writeFrame(stream, payload) {
|
|
942
|
+
stream.write("Content-Length: " + Buffer.byteLength(payload) + "\\r\\n\\r\\n");
|
|
943
|
+
stream.write(payload);
|
|
944
|
+
}
|
|
945
|
+
`;
|
|
946
|
+
}
|
|
947
|
+
function unavailableUI(reason) {
|
|
948
|
+
return { status: "unavailable", reason };
|
|
949
|
+
}
|
|
950
|
+
function dismissedUI() {
|
|
951
|
+
return { status: "dismissed" };
|
|
952
|
+
}
|
|
953
|
+
function errorMessage(error) {
|
|
954
|
+
return error instanceof Error ? error.message : String(error);
|
|
955
|
+
}
|
|
956
|
+
//# sourceMappingURL=agent.js.map
|