@letta-ai/letta-code-sdk 0.0.5 → 0.0.6
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/README.md +21 -246
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +331 -19
- package/dist/index.js.map +8 -6
- package/dist/interactiveToolPolicy.d.ts +4 -0
- package/dist/interactiveToolPolicy.d.ts.map +1 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/tool-helpers.d.ts +47 -0
- package/dist/tool-helpers.d.ts.map +1 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/types.d.ts +69 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -20,6 +20,10 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
20
20
|
// src/transport.ts
|
|
21
21
|
import { spawn } from "node:child_process";
|
|
22
22
|
import { createInterface } from "node:readline";
|
|
23
|
+
function sdkLog(tag, ...args) {
|
|
24
|
+
if (process.env.DEBUG_SDK)
|
|
25
|
+
console.error(`[SDK-Transport] [${tag}]`, ...args);
|
|
26
|
+
}
|
|
23
27
|
|
|
24
28
|
class SubprocessTransport {
|
|
25
29
|
options;
|
|
@@ -29,21 +33,25 @@ class SubprocessTransport {
|
|
|
29
33
|
messageResolvers = [];
|
|
30
34
|
closed = false;
|
|
31
35
|
agentId;
|
|
36
|
+
wireMessageCount = 0;
|
|
37
|
+
lastMessageAt = 0;
|
|
32
38
|
constructor(options = {}) {
|
|
33
39
|
this.options = options;
|
|
34
40
|
}
|
|
35
41
|
async connect() {
|
|
36
42
|
const args = this.buildArgs();
|
|
37
43
|
const cliPath = await this.findCli();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
44
|
+
sdkLog("connect", `CLI: ${cliPath}`);
|
|
45
|
+
sdkLog("connect", `args: ${args.join(" ")}`);
|
|
46
|
+
sdkLog("connect", `cwd: ${this.options.cwd || process.cwd()}`);
|
|
47
|
+
sdkLog("connect", `permissionMode: ${this.options.permissionMode || "default"}`);
|
|
42
48
|
this.process = spawn("node", [cliPath, ...args], {
|
|
43
49
|
cwd: this.options.cwd || process.cwd(),
|
|
44
50
|
stdio: ["pipe", "pipe", "pipe"],
|
|
45
51
|
env: { ...process.env }
|
|
46
52
|
});
|
|
53
|
+
const pid = this.process.pid;
|
|
54
|
+
sdkLog("connect", `CLI process spawned, pid=${pid}`);
|
|
47
55
|
if (!this.process.stdout || !this.process.stdin) {
|
|
48
56
|
throw new Error("Failed to create subprocess pipes");
|
|
49
57
|
}
|
|
@@ -57,7 +65,9 @@ class SubprocessTransport {
|
|
|
57
65
|
try {
|
|
58
66
|
const msg = JSON.parse(line);
|
|
59
67
|
this.handleMessage(msg);
|
|
60
|
-
} catch {
|
|
68
|
+
} catch {
|
|
69
|
+
sdkLog("stdout", `[non-JSON] ${line.slice(0, 500)}`);
|
|
70
|
+
}
|
|
61
71
|
});
|
|
62
72
|
if (this.process.stderr) {
|
|
63
73
|
this.process.stderr.on("data", (data) => {
|
|
@@ -67,11 +77,16 @@ class SubprocessTransport {
|
|
|
67
77
|
}
|
|
68
78
|
});
|
|
69
79
|
}
|
|
70
|
-
this.process.on("close", (code) => {
|
|
71
|
-
this.closed = true;
|
|
80
|
+
this.process.on("close", (code, signal) => {
|
|
72
81
|
if (code !== 0 && code !== null) {
|
|
73
82
|
console.error(`[letta-code-sdk] CLI process exited with code ${code}`);
|
|
74
83
|
}
|
|
84
|
+
sdkLog("close", `CLI process exited: pid=${pid} code=${code} signal=${signal} wireMessages=${this.wireMessageCount} msSinceLastMsg=${this.lastMessageAt ? Date.now() - this.lastMessageAt : 0} pendingResolvers=${this.messageResolvers.length} queueLen=${this.messageQueue.length}`);
|
|
85
|
+
this.closed = true;
|
|
86
|
+
for (const resolve of this.messageResolvers) {
|
|
87
|
+
resolve(null);
|
|
88
|
+
}
|
|
89
|
+
this.messageResolvers = [];
|
|
75
90
|
});
|
|
76
91
|
this.process.on("error", (err) => {
|
|
77
92
|
console.error("[letta-code-sdk] CLI process error:", err);
|
|
@@ -80,8 +95,12 @@ class SubprocessTransport {
|
|
|
80
95
|
}
|
|
81
96
|
async write(data) {
|
|
82
97
|
if (!this.process?.stdin || this.closed) {
|
|
83
|
-
|
|
98
|
+
const err = new Error(`Transport not connected (closed=${this.closed}, pid=${this.process?.pid}, stdin=${!!this.process?.stdin})`);
|
|
99
|
+
sdkLog("write", err.message);
|
|
100
|
+
throw err;
|
|
84
101
|
}
|
|
102
|
+
const payload = data;
|
|
103
|
+
sdkLog("write", `type=${payload.type} subtype=${payload.request?.subtype || payload.response?.subtype || "N/A"}`);
|
|
85
104
|
this.process.stdin.write(JSON.stringify(data) + `
|
|
86
105
|
`);
|
|
87
106
|
}
|
|
@@ -90,8 +109,10 @@ class SubprocessTransport {
|
|
|
90
109
|
return this.messageQueue.shift();
|
|
91
110
|
}
|
|
92
111
|
if (this.closed) {
|
|
112
|
+
sdkLog("read", `returning null (closed), total wireMessages=${this.wireMessageCount}`);
|
|
93
113
|
return null;
|
|
94
114
|
}
|
|
115
|
+
sdkLog("read", `waiting for next message (resolvers=${this.messageResolvers.length + 1}, queue=${this.messageQueue.length})`);
|
|
95
116
|
return new Promise((resolve) => {
|
|
96
117
|
this.messageResolvers.push(resolve);
|
|
97
118
|
});
|
|
@@ -99,12 +120,15 @@ class SubprocessTransport {
|
|
|
99
120
|
async* messages() {
|
|
100
121
|
while (true) {
|
|
101
122
|
const msg = await this.read();
|
|
102
|
-
if (msg === null)
|
|
123
|
+
if (msg === null) {
|
|
124
|
+
sdkLog("messages", `iterator ending (closed=${this.closed}, wireMessages=${this.wireMessageCount})`);
|
|
103
125
|
break;
|
|
126
|
+
}
|
|
104
127
|
yield msg;
|
|
105
128
|
}
|
|
106
129
|
}
|
|
107
130
|
close() {
|
|
131
|
+
sdkLog("close", `explicit close called (wireMessages=${this.wireMessageCount}, pendingResolvers=${this.messageResolvers.length}, pid=${this.process?.pid})`);
|
|
108
132
|
if (this.process) {
|
|
109
133
|
this.process.stdin?.end();
|
|
110
134
|
this.process.kill();
|
|
@@ -120,8 +144,22 @@ class SubprocessTransport {
|
|
|
120
144
|
return this.closed;
|
|
121
145
|
}
|
|
122
146
|
handleMessage(msg) {
|
|
147
|
+
this.wireMessageCount++;
|
|
148
|
+
this.lastMessageAt = Date.now();
|
|
149
|
+
const wirePayload = msg;
|
|
150
|
+
const msgType = wirePayload.message_type || wirePayload.subtype || "";
|
|
151
|
+
sdkLog("wire", `#${this.wireMessageCount} type=${msg.type} ${msgType ? `msg_type=${msgType}` : ""} resolvers=${this.messageResolvers.length} queue=${this.messageQueue.length}`);
|
|
152
|
+
if (msg.type === "result") {
|
|
153
|
+
const result = wirePayload;
|
|
154
|
+
sdkLog("wire", `RESULT: subtype=${result.subtype} stop_reason=${result.stop_reason || "N/A"} duration=${result.duration_ms}ms resultLen=${result.result?.length || 0}`);
|
|
155
|
+
}
|
|
123
156
|
if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
|
|
124
157
|
this.agentId = msg.agent_id;
|
|
158
|
+
sdkLog("wire", `INIT: agent_id=${this.agentId}`);
|
|
159
|
+
}
|
|
160
|
+
if (msg.type === "control_request") {
|
|
161
|
+
const req = wirePayload;
|
|
162
|
+
sdkLog("wire", `CONTROL_REQUEST: id=${req.request_id} subtype=${req.request?.subtype} tool=${req.request?.tool_name || "N/A"}`);
|
|
125
163
|
}
|
|
126
164
|
if (this.messageResolvers.length > 0) {
|
|
127
165
|
const resolve = this.messageResolvers.shift();
|
|
@@ -146,7 +184,7 @@ class SubprocessTransport {
|
|
|
146
184
|
} else if (this.options.defaultConversation) {
|
|
147
185
|
args.push("--default");
|
|
148
186
|
}
|
|
149
|
-
} else if (this.options.
|
|
187
|
+
} else if (this.options.createOnly) {
|
|
150
188
|
args.push("--new-agent");
|
|
151
189
|
} else if (this.options.newConversation) {
|
|
152
190
|
args.push("--new");
|
|
@@ -154,6 +192,9 @@ class SubprocessTransport {
|
|
|
154
192
|
if (this.options.model) {
|
|
155
193
|
args.push("-m", this.options.model);
|
|
156
194
|
}
|
|
195
|
+
if (this.options.embedding) {
|
|
196
|
+
args.push("--embedding", this.options.embedding);
|
|
197
|
+
}
|
|
157
198
|
if (this.options.systemPrompt !== undefined) {
|
|
158
199
|
if (typeof this.options.systemPrompt === "string") {
|
|
159
200
|
const validPresets = [
|
|
@@ -212,12 +253,15 @@ class SubprocessTransport {
|
|
|
212
253
|
}
|
|
213
254
|
if (this.options.permissionMode === "bypassPermissions") {
|
|
214
255
|
args.push("--yolo");
|
|
215
|
-
} else if (this.options.permissionMode
|
|
216
|
-
args.push("--
|
|
256
|
+
} else if (this.options.permissionMode && this.options.permissionMode !== "default") {
|
|
257
|
+
args.push("--permission-mode", this.options.permissionMode);
|
|
217
258
|
}
|
|
218
259
|
if (this.options.allowedTools) {
|
|
219
260
|
args.push("--allowedTools", this.options.allowedTools.join(","));
|
|
220
261
|
}
|
|
262
|
+
if (this.options.disallowedTools) {
|
|
263
|
+
args.push("--disallowedTools", this.options.disallowedTools.join(","));
|
|
264
|
+
}
|
|
221
265
|
return args;
|
|
222
266
|
}
|
|
223
267
|
async findCli() {
|
|
@@ -251,7 +295,27 @@ class SubprocessTransport {
|
|
|
251
295
|
}
|
|
252
296
|
}
|
|
253
297
|
|
|
298
|
+
// src/interactiveToolPolicy.ts
|
|
299
|
+
var INTERACTIVE_APPROVAL_TOOLS = new Set([
|
|
300
|
+
"AskUserQuestion",
|
|
301
|
+
"EnterPlanMode",
|
|
302
|
+
"ExitPlanMode"
|
|
303
|
+
]);
|
|
304
|
+
var RUNTIME_USER_INPUT_TOOLS = new Set(["AskUserQuestion", "ExitPlanMode"]);
|
|
305
|
+
var HEADLESS_AUTO_ALLOW_TOOLS = new Set(["EnterPlanMode"]);
|
|
306
|
+
function requiresRuntimeUserInput(toolName) {
|
|
307
|
+
return RUNTIME_USER_INPUT_TOOLS.has(toolName);
|
|
308
|
+
}
|
|
309
|
+
function isHeadlessAutoAllowTool(toolName) {
|
|
310
|
+
return HEADLESS_AUTO_ALLOW_TOOLS.has(toolName);
|
|
311
|
+
}
|
|
312
|
+
|
|
254
313
|
// src/session.ts
|
|
314
|
+
function sessionLog(tag, ...args) {
|
|
315
|
+
if (process.env.DEBUG_SDK)
|
|
316
|
+
console.error(`[SDK-Session] [${tag}]`, ...args);
|
|
317
|
+
}
|
|
318
|
+
|
|
255
319
|
class Session {
|
|
256
320
|
options;
|
|
257
321
|
transport;
|
|
@@ -259,77 +323,216 @@ class Session {
|
|
|
259
323
|
_sessionId = null;
|
|
260
324
|
_conversationId = null;
|
|
261
325
|
initialized = false;
|
|
326
|
+
externalTools = new Map;
|
|
262
327
|
constructor(options = {}) {
|
|
263
328
|
this.options = options;
|
|
264
329
|
this.transport = new SubprocessTransport(options);
|
|
330
|
+
if (options.tools) {
|
|
331
|
+
for (const tool of options.tools) {
|
|
332
|
+
this.externalTools.set(tool.name, tool);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
265
335
|
}
|
|
266
336
|
async initialize() {
|
|
267
337
|
if (this.initialized) {
|
|
268
338
|
throw new Error("Session already initialized");
|
|
269
339
|
}
|
|
340
|
+
sessionLog("init", "connecting transport...");
|
|
270
341
|
await this.transport.connect();
|
|
342
|
+
sessionLog("init", "transport connected, sending initialize request");
|
|
271
343
|
await this.transport.write({
|
|
272
344
|
type: "control_request",
|
|
273
345
|
request_id: "init_1",
|
|
274
346
|
request: { subtype: "initialize" }
|
|
275
347
|
});
|
|
348
|
+
sessionLog("init", "waiting for init message from CLI...");
|
|
276
349
|
for await (const msg of this.transport.messages()) {
|
|
350
|
+
sessionLog("init", `received wire message: type=${msg.type}`);
|
|
277
351
|
if (msg.type === "system" && "subtype" in msg && msg.subtype === "init") {
|
|
278
352
|
const initMsg = msg;
|
|
279
353
|
this._agentId = initMsg.agent_id;
|
|
280
354
|
this._sessionId = initMsg.session_id;
|
|
281
355
|
this._conversationId = initMsg.conversation_id;
|
|
282
356
|
this.initialized = true;
|
|
357
|
+
if (this.externalTools.size > 0) {
|
|
358
|
+
await this.registerExternalTools();
|
|
359
|
+
}
|
|
360
|
+
const allTools = [
|
|
361
|
+
...initMsg.tools,
|
|
362
|
+
...Array.from(this.externalTools.keys())
|
|
363
|
+
];
|
|
364
|
+
sessionLog("init", `initialized: agent=${initMsg.agent_id} conversation=${initMsg.conversation_id} model=${initMsg.model} tools=${allTools.length} (${this.externalTools.size} external)`);
|
|
283
365
|
return {
|
|
284
366
|
type: "init",
|
|
285
367
|
agentId: initMsg.agent_id,
|
|
286
368
|
sessionId: initMsg.session_id,
|
|
287
369
|
conversationId: initMsg.conversation_id,
|
|
288
370
|
model: initMsg.model,
|
|
289
|
-
tools:
|
|
371
|
+
tools: allTools
|
|
290
372
|
};
|
|
291
373
|
}
|
|
292
374
|
}
|
|
375
|
+
sessionLog("init", "ERROR: transport closed before init message received");
|
|
293
376
|
throw new Error("Failed to initialize session - no init message received");
|
|
294
377
|
}
|
|
295
378
|
async send(message) {
|
|
296
379
|
if (!this.initialized) {
|
|
380
|
+
sessionLog("send", "auto-initializing (not yet initialized)");
|
|
297
381
|
await this.initialize();
|
|
298
382
|
}
|
|
383
|
+
const preview = typeof message === "string" ? message.slice(0, 100) : Array.isArray(message) ? `[multimodal: ${message.length} parts]` : String(message).slice(0, 100);
|
|
384
|
+
sessionLog("send", `sending message: ${preview}${typeof message === "string" && message.length > 100 ? "..." : ""}`);
|
|
299
385
|
await this.transport.write({
|
|
300
386
|
type: "user",
|
|
301
387
|
message: { role: "user", content: message }
|
|
302
388
|
});
|
|
389
|
+
sessionLog("send", "message written to transport");
|
|
303
390
|
}
|
|
304
391
|
async* stream() {
|
|
392
|
+
const streamStart = Date.now();
|
|
393
|
+
let yieldCount = 0;
|
|
394
|
+
let dropCount = 0;
|
|
395
|
+
let gotResult = false;
|
|
396
|
+
sessionLog("stream", `starting stream (agent=${this._agentId}, conversation=${this._conversationId})`);
|
|
305
397
|
for await (const wireMsg of this.transport.messages()) {
|
|
306
398
|
if (wireMsg.type === "control_request") {
|
|
307
399
|
const controlReq = wireMsg;
|
|
308
|
-
|
|
400
|
+
const subtype = controlReq.request.subtype;
|
|
401
|
+
sessionLog("stream", `control_request: subtype=${subtype} tool=${controlReq.request.tool_name || "N/A"}`);
|
|
402
|
+
if (subtype === "can_use_tool") {
|
|
309
403
|
await this.handleCanUseTool(controlReq.request_id, controlReq.request);
|
|
310
404
|
continue;
|
|
311
405
|
}
|
|
406
|
+
if (subtype === "execute_external_tool") {
|
|
407
|
+
const rawReq = controlReq.request;
|
|
408
|
+
await this.handleExecuteExternalTool(controlReq.request_id, {
|
|
409
|
+
subtype: "execute_external_tool",
|
|
410
|
+
tool_call_id: rawReq.tool_call_id,
|
|
411
|
+
tool_name: rawReq.tool_name,
|
|
412
|
+
input: rawReq.input
|
|
413
|
+
});
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
312
416
|
}
|
|
313
417
|
const sdkMsg = this.transformMessage(wireMsg);
|
|
314
418
|
if (sdkMsg) {
|
|
419
|
+
yieldCount++;
|
|
420
|
+
sessionLog("stream", `yield #${yieldCount}: type=${sdkMsg.type}${sdkMsg.type === "result" ? ` success=${sdkMsg.success} error=${sdkMsg.error || "none"}` : ""}`);
|
|
315
421
|
yield sdkMsg;
|
|
316
422
|
if (sdkMsg.type === "result") {
|
|
423
|
+
gotResult = true;
|
|
317
424
|
break;
|
|
318
425
|
}
|
|
426
|
+
} else {
|
|
427
|
+
dropCount++;
|
|
428
|
+
const wireMsgAny = wireMsg;
|
|
429
|
+
sessionLog("stream", `DROPPED wire message #${dropCount}: type=${wireMsg.type} message_type=${wireMsgAny.message_type || "N/A"} subtype=${wireMsgAny.subtype || "N/A"}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const elapsed = Date.now() - streamStart;
|
|
433
|
+
sessionLog("stream", `stream ended: duration=${elapsed}ms yielded=${yieldCount} dropped=${dropCount} gotResult=${gotResult}`);
|
|
434
|
+
if (!gotResult) {
|
|
435
|
+
sessionLog("stream", `WARNING: stream ended WITHOUT a result message -- transport may have closed unexpectedly`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async registerExternalTools() {
|
|
439
|
+
const toolDefs = Array.from(this.externalTools.values()).map((tool) => ({
|
|
440
|
+
name: tool.name,
|
|
441
|
+
label: tool.label,
|
|
442
|
+
description: tool.description,
|
|
443
|
+
parameters: this.schemaToJsonSchema(tool.parameters)
|
|
444
|
+
}));
|
|
445
|
+
sessionLog("registerTools", `registering ${toolDefs.length} external tools: ${toolDefs.map((t) => t.name).join(", ")}`);
|
|
446
|
+
await this.transport.write({
|
|
447
|
+
type: "control_request",
|
|
448
|
+
request_id: `register_tools_${Date.now()}`,
|
|
449
|
+
request: {
|
|
450
|
+
subtype: "register_external_tools",
|
|
451
|
+
tools: toolDefs
|
|
319
452
|
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
schemaToJsonSchema(schema) {
|
|
456
|
+
if (schema && typeof schema === "object") {
|
|
457
|
+
const s = schema;
|
|
458
|
+
return {
|
|
459
|
+
type: s.type,
|
|
460
|
+
properties: s.properties,
|
|
461
|
+
required: s.required,
|
|
462
|
+
additionalProperties: s.additionalProperties,
|
|
463
|
+
description: s.description
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
return { type: "object" };
|
|
467
|
+
}
|
|
468
|
+
async handleExecuteExternalTool(requestId, req) {
|
|
469
|
+
const tool = this.externalTools.get(req.tool_name);
|
|
470
|
+
if (!tool) {
|
|
471
|
+
sessionLog("executeExternalTool", `ERROR: unknown tool ${req.tool_name}`);
|
|
472
|
+
await this.transport.write({
|
|
473
|
+
type: "control_response",
|
|
474
|
+
response: {
|
|
475
|
+
subtype: "external_tool_result",
|
|
476
|
+
request_id: requestId,
|
|
477
|
+
tool_call_id: req.tool_call_id,
|
|
478
|
+
content: [{ type: "text", text: `Unknown external tool: ${req.tool_name}` }],
|
|
479
|
+
is_error: true
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
sessionLog("executeExternalTool", `executing ${req.tool_name} (call_id=${req.tool_call_id})`);
|
|
486
|
+
const result = await tool.execute(req.tool_call_id, req.input);
|
|
487
|
+
await this.transport.write({
|
|
488
|
+
type: "control_response",
|
|
489
|
+
response: {
|
|
490
|
+
subtype: "external_tool_result",
|
|
491
|
+
request_id: requestId,
|
|
492
|
+
tool_call_id: req.tool_call_id,
|
|
493
|
+
content: result.content,
|
|
494
|
+
is_error: false
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
sessionLog("executeExternalTool", `${req.tool_name} completed successfully`);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
500
|
+
sessionLog("executeExternalTool", `${req.tool_name} failed: ${errorMessage}`);
|
|
501
|
+
await this.transport.write({
|
|
502
|
+
type: "control_response",
|
|
503
|
+
response: {
|
|
504
|
+
subtype: "external_tool_result",
|
|
505
|
+
request_id: requestId,
|
|
506
|
+
tool_call_id: req.tool_call_id,
|
|
507
|
+
content: [{ type: "text", text: `Tool execution error: ${errorMessage}` }],
|
|
508
|
+
is_error: true
|
|
509
|
+
}
|
|
510
|
+
});
|
|
320
511
|
}
|
|
321
512
|
}
|
|
322
513
|
async handleCanUseTool(requestId, req) {
|
|
323
514
|
let response;
|
|
324
|
-
|
|
515
|
+
const toolName = req.tool_name;
|
|
516
|
+
const hasCallback = typeof this.options.canUseTool === "function";
|
|
517
|
+
const toolNeedsRuntimeUserInput = requiresRuntimeUserInput(toolName);
|
|
518
|
+
const autoAllowWithoutCallback = isHeadlessAutoAllowTool(toolName);
|
|
519
|
+
sessionLog("canUseTool", `tool=${toolName} mode=${this.options.permissionMode || "default"} requestId=${requestId}`);
|
|
520
|
+
if (toolNeedsRuntimeUserInput && !hasCallback) {
|
|
521
|
+
response = {
|
|
522
|
+
behavior: "deny",
|
|
523
|
+
message: "No canUseTool callback registered",
|
|
524
|
+
interrupt: false
|
|
525
|
+
};
|
|
526
|
+
} else if (this.options.permissionMode === "bypassPermissions" && !toolNeedsRuntimeUserInput) {
|
|
527
|
+
sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (bypassPermissions)`);
|
|
325
528
|
response = {
|
|
326
529
|
behavior: "allow",
|
|
327
530
|
updatedInput: null,
|
|
328
531
|
updatedPermissions: []
|
|
329
532
|
};
|
|
330
|
-
} else if (
|
|
533
|
+
} else if (hasCallback) {
|
|
331
534
|
try {
|
|
332
|
-
const result = await this.options.canUseTool(
|
|
535
|
+
const result = await this.options.canUseTool(toolName, req.input);
|
|
333
536
|
if (result.behavior === "allow") {
|
|
334
537
|
response = {
|
|
335
538
|
behavior: "allow",
|
|
@@ -350,6 +553,13 @@ class Session {
|
|
|
350
553
|
interrupt: false
|
|
351
554
|
};
|
|
352
555
|
}
|
|
556
|
+
} else if (autoAllowWithoutCallback) {
|
|
557
|
+
sessionLog("canUseTool", `AUTO-ALLOW ${toolName} (default behavior)`);
|
|
558
|
+
response = {
|
|
559
|
+
behavior: "allow",
|
|
560
|
+
updatedInput: null,
|
|
561
|
+
updatedPermissions: []
|
|
562
|
+
};
|
|
353
563
|
} else {
|
|
354
564
|
response = {
|
|
355
565
|
behavior: "deny",
|
|
@@ -357,6 +567,8 @@ class Session {
|
|
|
357
567
|
interrupt: false
|
|
358
568
|
};
|
|
359
569
|
}
|
|
570
|
+
const responseBehavior = "behavior" in response ? response.behavior : "unknown";
|
|
571
|
+
sessionLog("canUseTool", `responding: requestId=${requestId} behavior=${responseBehavior}`);
|
|
360
572
|
await this.transport.write({
|
|
361
573
|
type: "control_response",
|
|
362
574
|
response: {
|
|
@@ -365,8 +577,10 @@ class Session {
|
|
|
365
577
|
response
|
|
366
578
|
}
|
|
367
579
|
});
|
|
580
|
+
sessionLog("canUseTool", `response sent for ${toolName}`);
|
|
368
581
|
}
|
|
369
582
|
async abort() {
|
|
583
|
+
sessionLog("abort", `aborting session (agent=${this._agentId})`);
|
|
370
584
|
await this.transport.write({
|
|
371
585
|
type: "control_request",
|
|
372
586
|
request_id: `interrupt-${Date.now()}`,
|
|
@@ -374,6 +588,7 @@ class Session {
|
|
|
374
588
|
});
|
|
375
589
|
}
|
|
376
590
|
close() {
|
|
591
|
+
sessionLog("close", `closing session (agent=${this._agentId}, conversation=${this._conversationId})`);
|
|
377
592
|
this.transport.close();
|
|
378
593
|
}
|
|
379
594
|
get agentId() {
|
|
@@ -459,6 +674,7 @@ class Session {
|
|
|
459
674
|
success: msg.subtype === "success",
|
|
460
675
|
result: msg.result,
|
|
461
676
|
error: msg.subtype !== "success" ? msg.subtype : undefined,
|
|
677
|
+
stopReason: msg.stop_reason,
|
|
462
678
|
durationMs: msg.duration_ms,
|
|
463
679
|
totalCostUsd: msg.total_cost_usd,
|
|
464
680
|
conversationId: msg.conversation_id
|
|
@@ -524,6 +740,97 @@ function validateCreateAgentOptions(options) {
|
|
|
524
740
|
}
|
|
525
741
|
}
|
|
526
742
|
}
|
|
743
|
+
// src/tool-helpers.ts
|
|
744
|
+
function jsonResult(payload) {
|
|
745
|
+
return {
|
|
746
|
+
content: [
|
|
747
|
+
{
|
|
748
|
+
type: "text",
|
|
749
|
+
text: JSON.stringify(payload, null, 2)
|
|
750
|
+
}
|
|
751
|
+
],
|
|
752
|
+
details: payload
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function readStringParam(params, key, options = {}) {
|
|
756
|
+
const { required = false, trim = true, label = key, allowEmpty = false } = options;
|
|
757
|
+
const raw = params[key];
|
|
758
|
+
if (typeof raw !== "string") {
|
|
759
|
+
if (required)
|
|
760
|
+
throw new Error(`${label} required`);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
const value = trim ? raw.trim() : raw;
|
|
764
|
+
if (!value && !allowEmpty) {
|
|
765
|
+
if (required)
|
|
766
|
+
throw new Error(`${label} required`);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
return value;
|
|
770
|
+
}
|
|
771
|
+
function readNumberParam(params, key, options = {}) {
|
|
772
|
+
const { required = false, label = key, integer = false } = options;
|
|
773
|
+
const raw = params[key];
|
|
774
|
+
let value;
|
|
775
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
776
|
+
value = raw;
|
|
777
|
+
} else if (typeof raw === "string") {
|
|
778
|
+
const trimmed = raw.trim();
|
|
779
|
+
if (trimmed) {
|
|
780
|
+
const parsed = Number.parseFloat(trimmed);
|
|
781
|
+
if (Number.isFinite(parsed))
|
|
782
|
+
value = parsed;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (value === undefined) {
|
|
786
|
+
if (required)
|
|
787
|
+
throw new Error(`${label} required`);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
return integer ? Math.trunc(value) : value;
|
|
791
|
+
}
|
|
792
|
+
function readBooleanParam(params, key, options = {}) {
|
|
793
|
+
const { required = false, label = key } = options;
|
|
794
|
+
const raw = params[key];
|
|
795
|
+
if (typeof raw === "boolean") {
|
|
796
|
+
return raw;
|
|
797
|
+
}
|
|
798
|
+
if (typeof raw === "string") {
|
|
799
|
+
const lower = raw.toLowerCase().trim();
|
|
800
|
+
if (lower === "true" || lower === "1" || lower === "yes")
|
|
801
|
+
return true;
|
|
802
|
+
if (lower === "false" || lower === "0" || lower === "no")
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
if (required)
|
|
806
|
+
throw new Error(`${label} required`);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
function readStringArrayParam(params, key, options = {}) {
|
|
810
|
+
const { required = false, label = key } = options;
|
|
811
|
+
const raw = params[key];
|
|
812
|
+
if (Array.isArray(raw)) {
|
|
813
|
+
const values = raw.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
814
|
+
if (values.length === 0) {
|
|
815
|
+
if (required)
|
|
816
|
+
throw new Error(`${label} required`);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
return values;
|
|
820
|
+
}
|
|
821
|
+
if (typeof raw === "string") {
|
|
822
|
+
const value = raw.trim();
|
|
823
|
+
if (!value) {
|
|
824
|
+
if (required)
|
|
825
|
+
throw new Error(`${label} required`);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
return [value];
|
|
829
|
+
}
|
|
830
|
+
if (required)
|
|
831
|
+
throw new Error(`${label} required`);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
527
834
|
|
|
528
835
|
// src/index.ts
|
|
529
836
|
import { readFileSync } from "node:fs";
|
|
@@ -551,7 +858,7 @@ function resumeSession(id, options = {}) {
|
|
|
551
858
|
}
|
|
552
859
|
}
|
|
553
860
|
async function prompt(message, agentId) {
|
|
554
|
-
const session = agentId ? createSession(agentId) :
|
|
861
|
+
const session = agentId ? createSession(agentId) : createSession();
|
|
555
862
|
try {
|
|
556
863
|
await session.send(message);
|
|
557
864
|
let result = null;
|
|
@@ -610,7 +917,12 @@ async function imageFromURL(url) {
|
|
|
610
917
|
}
|
|
611
918
|
export {
|
|
612
919
|
resumeSession,
|
|
920
|
+
readStringParam,
|
|
921
|
+
readStringArrayParam,
|
|
922
|
+
readNumberParam,
|
|
923
|
+
readBooleanParam,
|
|
613
924
|
prompt,
|
|
925
|
+
jsonResult,
|
|
614
926
|
imageFromURL,
|
|
615
927
|
imageFromFile,
|
|
616
928
|
imageFromBase64,
|
|
@@ -619,4 +931,4 @@ export {
|
|
|
619
931
|
Session
|
|
620
932
|
};
|
|
621
933
|
|
|
622
|
-
//# debugId=
|
|
934
|
+
//# debugId=63415FA0837719DA64756E2164756E21
|