wispy-cli 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/engine.mjs +3 -0
- package/core/providers.mjs +53 -12
- package/core/tools.mjs +24 -4
- package/lib/wispy-repl.mjs +3 -0
- package/package.json +1 -1
package/core/engine.mjs
CHANGED
|
@@ -1009,6 +1009,9 @@ export class WispyEngine {
|
|
|
1009
1009
|
|
|
1010
1010
|
destroy() {
|
|
1011
1011
|
try { this.mcpManager.disconnectAll(); } catch {}
|
|
1012
|
+
try { this.sessions.destroyAll?.(); } catch {}
|
|
1013
|
+
try { this.subagents.killAll?.(); } catch {}
|
|
1014
|
+
try { this.audit.close?.(); } catch {}
|
|
1012
1015
|
}
|
|
1013
1016
|
}
|
|
1014
1017
|
|
package/core/providers.mjs
CHANGED
|
@@ -345,7 +345,7 @@ export class ProviderRegistry {
|
|
|
345
345
|
if (this._provider === "openrouter") headers["HTTP-Referer"] = "https://wispy.dev";
|
|
346
346
|
|
|
347
347
|
const supportsTools = !["ollama"].includes(this._provider);
|
|
348
|
-
const body = { model, messages: openaiMessages, temperature: 0.7, max_tokens: 4096 };
|
|
348
|
+
const body = { model, messages: openaiMessages, temperature: 0.7, max_tokens: 4096, stream: true };
|
|
349
349
|
if (supportsTools && tools.length > 0) {
|
|
350
350
|
body.tools = tools.map(t => ({
|
|
351
351
|
type: "function",
|
|
@@ -364,24 +364,65 @@ export class ProviderRegistry {
|
|
|
364
364
|
throw new Error(`OpenAI API error ${response.status}: ${err.slice(0, 300)}`);
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
|
|
367
|
+
const reader = response.body.getReader();
|
|
368
|
+
const decoder = new TextDecoder();
|
|
369
|
+
let buffer = "";
|
|
370
|
+
let fullText = "";
|
|
371
|
+
const toolCallsMap = {};
|
|
372
|
+
|
|
373
|
+
while (true) {
|
|
374
|
+
const { done, value } = await reader.read();
|
|
375
|
+
if (done) break;
|
|
376
|
+
|
|
377
|
+
buffer += decoder.decode(value, { stream: true });
|
|
378
|
+
const lines = buffer.split("\n");
|
|
379
|
+
buffer = lines.pop() ?? "";
|
|
380
|
+
|
|
381
|
+
for (const line of lines) {
|
|
382
|
+
if (!line.startsWith("data: ")) continue;
|
|
383
|
+
const data = line.slice(6).trim();
|
|
384
|
+
if (!data || data === "[DONE]") continue;
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const event = JSON.parse(data);
|
|
388
|
+
const delta = event.choices?.[0]?.delta;
|
|
389
|
+
if (!delta) continue;
|
|
390
|
+
|
|
391
|
+
// Text content
|
|
392
|
+
if (delta.content) {
|
|
393
|
+
fullText += delta.content;
|
|
394
|
+
opts.onChunk?.(delta.content);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Tool calls (streamed as incremental chunks)
|
|
398
|
+
if (delta.tool_calls) {
|
|
399
|
+
for (const tc of delta.tool_calls) {
|
|
400
|
+
const idx = tc.index ?? 0;
|
|
401
|
+
if (!toolCallsMap[idx]) {
|
|
402
|
+
toolCallsMap[idx] = { id: tc.id ?? `call_${idx}`, name: "", arguments: "" };
|
|
403
|
+
}
|
|
404
|
+
if (tc.id) toolCallsMap[idx].id = tc.id;
|
|
405
|
+
if (tc.function?.name) toolCallsMap[idx].name += tc.function.name;
|
|
406
|
+
if (tc.function?.arguments) toolCallsMap[idx].arguments += tc.function.arguments;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch { /* skip malformed chunks */ }
|
|
410
|
+
}
|
|
411
|
+
}
|
|
370
412
|
|
|
371
|
-
|
|
372
|
-
|
|
413
|
+
const toolCalls = Object.values(toolCallsMap);
|
|
414
|
+
if (toolCalls.length > 0) {
|
|
415
|
+
const calls = toolCalls.map(tc => ({
|
|
373
416
|
id: tc.id,
|
|
374
|
-
name: tc.
|
|
375
|
-
args: JSON.parse(tc.
|
|
417
|
+
name: tc.name,
|
|
418
|
+
args: (() => { try { return JSON.parse(tc.arguments); } catch { return {}; } })(),
|
|
376
419
|
}));
|
|
377
420
|
this._sessionTokens.output += this._estimateTokens(JSON.stringify(calls));
|
|
378
421
|
return { type: "tool_calls", calls };
|
|
379
422
|
}
|
|
380
423
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
opts.onChunk?.(text);
|
|
384
|
-
return { type: "text", text };
|
|
424
|
+
this._sessionTokens.output += this._estimateTokens(fullText);
|
|
425
|
+
return { type: "text", text: fullText };
|
|
385
426
|
}
|
|
386
427
|
|
|
387
428
|
formatCost() {
|
package/core/tools.mjs
CHANGED
|
@@ -337,10 +337,18 @@ export class ToolRegistry {
|
|
|
337
337
|
try {
|
|
338
338
|
const serverUrl = `http://127.0.0.1:${DEFAULT_SERVER_PORT}`;
|
|
339
339
|
if (name === "read_file") {
|
|
340
|
+
// Resolve path relative to process.cwd() before sending to server
|
|
341
|
+
let resolvedPath = args.path.replace(/^~/, os.homedir());
|
|
342
|
+
if (!path.isAbsolute(resolvedPath)) {
|
|
343
|
+
resolvedPath = path.resolve(process.cwd(), resolvedPath);
|
|
344
|
+
}
|
|
345
|
+
if (process.env.WISPY_DEBUG) {
|
|
346
|
+
process.stderr.write(`[wispy] read_file via server: "${args.path}" → "${resolvedPath}"\n`);
|
|
347
|
+
}
|
|
340
348
|
const resp = await fetch(`${serverUrl}/api/node-filesystem-actions`, {
|
|
341
349
|
method: "POST",
|
|
342
350
|
headers: { "Content-Type": "application/json" },
|
|
343
|
-
body: JSON.stringify({ subAction: "read_file", path:
|
|
351
|
+
body: JSON.stringify({ subAction: "read_file", path: resolvedPath }),
|
|
344
352
|
signal: AbortSignal.timeout(10_000),
|
|
345
353
|
});
|
|
346
354
|
const data = await resp.json();
|
|
@@ -386,7 +394,13 @@ export class ToolRegistry {
|
|
|
386
394
|
try {
|
|
387
395
|
switch (name) {
|
|
388
396
|
case "read_file": {
|
|
389
|
-
|
|
397
|
+
let filePath = args.path.replace(/^~/, os.homedir());
|
|
398
|
+
if (!path.isAbsolute(filePath)) {
|
|
399
|
+
filePath = path.resolve(process.cwd(), filePath);
|
|
400
|
+
}
|
|
401
|
+
if (process.env.WISPY_DEBUG) {
|
|
402
|
+
process.stderr.write(`[wispy] read_file: "${args.path}" → "${filePath}"\n`);
|
|
403
|
+
}
|
|
390
404
|
const content = await readFile(filePath, "utf8");
|
|
391
405
|
const truncated = content.length > 10_000
|
|
392
406
|
? content.slice(0, 10_000) + `\n\n... (truncated, ${content.length} chars total)`
|
|
@@ -395,7 +409,10 @@ export class ToolRegistry {
|
|
|
395
409
|
}
|
|
396
410
|
|
|
397
411
|
case "write_file": {
|
|
398
|
-
|
|
412
|
+
let filePath = args.path.replace(/^~/, os.homedir());
|
|
413
|
+
if (!path.isAbsolute(filePath)) {
|
|
414
|
+
filePath = path.resolve(process.cwd(), filePath);
|
|
415
|
+
}
|
|
399
416
|
const dir = path.dirname(filePath);
|
|
400
417
|
await mkdir(dir, { recursive: true });
|
|
401
418
|
await writeFile(filePath, args.content, "utf8");
|
|
@@ -453,7 +470,10 @@ export class ToolRegistry {
|
|
|
453
470
|
}
|
|
454
471
|
|
|
455
472
|
case "file_edit": {
|
|
456
|
-
|
|
473
|
+
let filePath = args.path.replace(/^~/, os.homedir());
|
|
474
|
+
if (!path.isAbsolute(filePath)) {
|
|
475
|
+
filePath = path.resolve(process.cwd(), filePath);
|
|
476
|
+
}
|
|
457
477
|
const content = await readFile(filePath, "utf8");
|
|
458
478
|
if (!content.includes(args.old_text)) {
|
|
459
479
|
return { success: false, error: `Text not found in ${filePath}` };
|
package/lib/wispy-repl.mjs
CHANGED
|
@@ -865,8 +865,11 @@ async function runOneShot(engine, message) {
|
|
|
865
865
|
} else {
|
|
866
866
|
console.error(red(`\n❌ ${err.message.slice(0, 200)}`));
|
|
867
867
|
}
|
|
868
|
+
engine.destroy();
|
|
868
869
|
process.exit(1);
|
|
869
870
|
}
|
|
871
|
+
engine.destroy();
|
|
872
|
+
process.exit(0);
|
|
870
873
|
}
|
|
871
874
|
|
|
872
875
|
// ---------------------------------------------------------------------------
|