@nextclaw/nextclaw-ncp-runtime-stdio-client 0.1.2 → 0.1.3
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.
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/stdio-runtime-error.utils.ts
|
|
2
|
+
function buildSpawnFailureMessage(params) {
|
|
3
|
+
const cwdSuffix = params.cwd ? ` (cwd: ${params.cwd})` : "";
|
|
4
|
+
return `[narp-stdio] failed to start stdio runtime command "${params.command}"${cwdSuffix}: ${params.error.message}`;
|
|
5
|
+
}
|
|
6
|
+
function normalizeRuntimeError(error) {
|
|
7
|
+
if (isNcpError(error)) return error;
|
|
8
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9
|
+
const lowered = message.toLowerCase();
|
|
10
|
+
return {
|
|
11
|
+
code: lowered.includes("abort") || lowered.includes("cancel") ? "abort-error" : "runtime-error",
|
|
12
|
+
message
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function isAbortLikeRuntimeError(error) {
|
|
16
|
+
if (isNcpError(error)) return error.code === "abort-error";
|
|
17
|
+
const lowered = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
18
|
+
return lowered.includes("abort") || lowered.includes("cancel");
|
|
19
|
+
}
|
|
20
|
+
function isNcpError(value) {
|
|
21
|
+
if (!value || typeof value !== "object") return false;
|
|
22
|
+
return typeof value.code === "string" && typeof value.message === "string";
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { buildSpawnFailureMessage, isAbortLikeRuntimeError, normalizeRuntimeError };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readString } from "./stdio-runtime-config.utils.js";
|
|
2
|
+
//#region src/stdio-runtime-tool-name.utils.ts
|
|
3
|
+
function resolveToolNameFromAcpUpdate(update) {
|
|
4
|
+
return resolveToolNameFromRawInput(update.rawInput) ?? resolveHermesAcpToolNameFromTitle(update.title) ?? readString(update.title) ?? "unknown";
|
|
5
|
+
}
|
|
6
|
+
function resolveToolNameFromRawInput(rawInput) {
|
|
7
|
+
if (typeof rawInput !== "object" || !rawInput || Array.isArray(rawInput)) return;
|
|
8
|
+
const input = rawInput;
|
|
9
|
+
return readString(input.toolName) ?? readString(input.tool);
|
|
10
|
+
}
|
|
11
|
+
function resolveHermesAcpToolNameFromTitle(title) {
|
|
12
|
+
const normalizedTitle = readString(title)?.toLowerCase();
|
|
13
|
+
if (!normalizedTitle) return;
|
|
14
|
+
if (normalizedTitle.startsWith("terminal:")) return "terminal";
|
|
15
|
+
if (normalizedTitle.startsWith("read:")) return "read_file";
|
|
16
|
+
if (normalizedTitle.startsWith("write:")) return "write_file";
|
|
17
|
+
if (normalizedTitle.startsWith("patch (") || normalizedTitle.startsWith("patch:")) return "patch";
|
|
18
|
+
if (normalizedTitle.startsWith("search:")) return "search_files";
|
|
19
|
+
if (normalizedTitle.startsWith("web search:")) return "web_search";
|
|
20
|
+
if (normalizedTitle === "web extract" || normalizedTitle.startsWith("extract:")) return "web_extract";
|
|
21
|
+
if (normalizedTitle === "delegate task" || normalizedTitle.startsWith("delegate:")) return "delegate_task";
|
|
22
|
+
if (normalizedTitle === "execute code") return "execute_code";
|
|
23
|
+
if (normalizedTitle.startsWith("analyze image:")) return "vision_analyze";
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { resolveToolNameFromAcpUpdate };
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { NARP_STDIO_PROMPT_META_KEY, buildStdioRuntimeLaunchEnv, readString } from "./stdio-runtime-config.utils.js";
|
|
2
|
+
import { buildSpawnFailureMessage, isAbortLikeRuntimeError, normalizeRuntimeError } from "./stdio-runtime-error.utils.js";
|
|
3
|
+
import { resolveToolNameFromAcpUpdate } from "./stdio-runtime-tool-name.utils.js";
|
|
2
4
|
import { randomUUID } from "node:crypto";
|
|
3
5
|
import { spawn } from "node:child_process";
|
|
4
6
|
import { Readable, Writable } from "node:stream";
|
|
5
7
|
import * as acp from "@agentclientprotocol/sdk";
|
|
6
8
|
import { NcpEventType } from "@nextclaw/ncp";
|
|
7
9
|
//#region src/stdio-runtime.service.ts
|
|
10
|
+
const HERMES_ACP_ROUTE_BRIDGE_ENV = "NEXTCLAW_HERMES_ACP_ROUTE_BRIDGE";
|
|
8
11
|
var UpdateBuffer = class {
|
|
9
12
|
updates = [];
|
|
10
13
|
waiters = /* @__PURE__ */ new Set();
|
|
@@ -205,7 +208,7 @@ var StdioRuntimeSession = class {
|
|
|
205
208
|
});
|
|
206
209
|
const releaseAbort = this.bindAbortSignal(signal);
|
|
207
210
|
try {
|
|
208
|
-
if (modelId) try {
|
|
211
|
+
if (modelId && this.config.env?.[HERMES_ACP_ROUTE_BRIDGE_ENV] !== "1") try {
|
|
209
212
|
await this.connection.unstable_setSessionModel({
|
|
210
213
|
sessionId: this.remoteSessionId,
|
|
211
214
|
modelId
|
|
@@ -247,10 +250,6 @@ var StdioRuntimeSession = class {
|
|
|
247
250
|
};
|
|
248
251
|
readStderr = () => this.stderr;
|
|
249
252
|
};
|
|
250
|
-
function buildSpawnFailureMessage(params) {
|
|
251
|
-
const cwdSuffix = params.cwd ? ` (cwd: ${params.cwd})` : "";
|
|
252
|
-
return `[narp-stdio] failed to start stdio runtime command "${params.command}"${cwdSuffix}: ${params.error.message}`;
|
|
253
|
-
}
|
|
254
253
|
var StdioRuntimeRunController = class {
|
|
255
254
|
buffer = new UpdateBuffer();
|
|
256
255
|
collector = new PromptUpdateCollector();
|
|
@@ -287,16 +286,33 @@ var StdioRuntimeRunController = class {
|
|
|
287
286
|
signal: options?.signal,
|
|
288
287
|
onUpdate: (update) => this.buffer.push(update)
|
|
289
288
|
});
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
const promptState = this.trackPromptState(promptPromise);
|
|
290
|
+
yield* this.emitRunStartedEvents(assistantMessageId);
|
|
291
|
+
try {
|
|
292
|
+
yield* this.drainPromptUpdates(assistantMessageId, promptState);
|
|
293
|
+
if (this.shouldExitForAbort(options, promptState.error)) return;
|
|
294
|
+
yield* this.emitCompletionEvents(assistantMessageId);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
if (this.shouldExitForAbort(options, error)) return;
|
|
297
|
+
yield* this.emitFailureEvents(assistantMessageId, error);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
trackPromptState = (promptPromise) => {
|
|
301
|
+
const promptState = {
|
|
302
|
+
settled: false,
|
|
303
|
+
error: null
|
|
304
|
+
};
|
|
292
305
|
promptPromise.then(() => {
|
|
293
|
-
|
|
306
|
+
promptState.settled = true;
|
|
294
307
|
this.buffer.notify();
|
|
295
308
|
}).catch((error) => {
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
promptState.settled = true;
|
|
310
|
+
promptState.error = error;
|
|
298
311
|
this.buffer.notify();
|
|
299
312
|
});
|
|
313
|
+
return promptState;
|
|
314
|
+
};
|
|
315
|
+
emitRunStartedEvents = async function* (assistantMessageId) {
|
|
300
316
|
yield* this.emitEvent({
|
|
301
317
|
type: NcpEventType.MessageAccepted,
|
|
302
318
|
payload: {
|
|
@@ -312,63 +328,65 @@ var StdioRuntimeRunController = class {
|
|
|
312
328
|
runId: `narp-stdio:${this.input.sessionId}:${Date.now()}`
|
|
313
329
|
}
|
|
314
330
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
for (const event of this.translateUpdate(update, assistantMessageId)) yield* this.emitEvent(event);
|
|
331
|
+
};
|
|
332
|
+
drainPromptUpdates = async function* (assistantMessageId, promptState) {
|
|
333
|
+
while (!promptState.settled || this.buffer.hasItems()) {
|
|
334
|
+
const update = this.buffer.shift();
|
|
335
|
+
if (!update) {
|
|
336
|
+
await this.buffer.waitForChange();
|
|
337
|
+
continue;
|
|
323
338
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
parts: this.collector.buildParts(),
|
|
339
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
yield* this.emitEvent({
|
|
344
|
-
type: NcpEventType.RunFinished,
|
|
345
|
-
payload: {
|
|
346
|
-
sessionId: this.input.sessionId,
|
|
347
|
-
messageId: assistantMessageId,
|
|
348
|
-
runId: `narp-stdio:${this.input.sessionId}`
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
} catch (error) {
|
|
352
|
-
const ncpError = normalizeRuntimeError(error);
|
|
353
|
-
yield* this.emitEvent({
|
|
354
|
-
type: NcpEventType.MessageFailed,
|
|
355
|
-
payload: {
|
|
356
|
-
sessionId: this.input.sessionId,
|
|
357
|
-
messageId: assistantMessageId,
|
|
358
|
-
correlationId: this.input.correlationId,
|
|
359
|
-
error: ncpError
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
yield* this.emitEvent({
|
|
363
|
-
type: NcpEventType.RunError,
|
|
364
|
-
payload: {
|
|
339
|
+
for (const event of this.translateUpdate(update, assistantMessageId)) yield* this.emitEvent(event);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
shouldExitForAbort = (options, error) => options?.signal?.aborted === true || isAbortLikeRuntimeError(error);
|
|
343
|
+
emitCompletionEvents = async function* (assistantMessageId) {
|
|
344
|
+
for (const terminalEvent of this.createTerminalEvents(assistantMessageId)) yield* this.emitEvent(terminalEvent);
|
|
345
|
+
if (!this.collector.hasParts()) throw new Error(`[narp-stdio] ACP prompt completed without any assistant content for session ${this.input.sessionId}. stderr=${this.session.readStderr()}`);
|
|
346
|
+
yield* this.emitEvent({
|
|
347
|
+
type: NcpEventType.MessageCompleted,
|
|
348
|
+
payload: {
|
|
349
|
+
sessionId: this.input.sessionId,
|
|
350
|
+
correlationId: this.input.correlationId,
|
|
351
|
+
message: {
|
|
352
|
+
id: assistantMessageId,
|
|
365
353
|
sessionId: this.input.sessionId,
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
354
|
+
role: "assistant",
|
|
355
|
+
status: "final",
|
|
356
|
+
parts: this.collector.buildParts(),
|
|
357
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
369
358
|
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
yield* this.emitEvent({
|
|
362
|
+
type: NcpEventType.RunFinished,
|
|
363
|
+
payload: {
|
|
364
|
+
sessionId: this.input.sessionId,
|
|
365
|
+
messageId: assistantMessageId,
|
|
366
|
+
runId: `narp-stdio:${this.input.sessionId}`
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
emitFailureEvents = async function* (assistantMessageId, error) {
|
|
371
|
+
const ncpError = normalizeRuntimeError(error);
|
|
372
|
+
yield* this.emitEvent({
|
|
373
|
+
type: NcpEventType.MessageFailed,
|
|
374
|
+
payload: {
|
|
375
|
+
sessionId: this.input.sessionId,
|
|
376
|
+
messageId: assistantMessageId,
|
|
377
|
+
correlationId: this.input.correlationId,
|
|
378
|
+
error: ncpError
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
yield* this.emitEvent({
|
|
382
|
+
type: NcpEventType.RunError,
|
|
383
|
+
payload: {
|
|
384
|
+
sessionId: this.input.sessionId,
|
|
385
|
+
messageId: assistantMessageId,
|
|
386
|
+
runId: `narp-stdio:${this.input.sessionId}`,
|
|
387
|
+
error: ncpError.message
|
|
388
|
+
}
|
|
389
|
+
});
|
|
372
390
|
};
|
|
373
391
|
emitEvent = async function* (event) {
|
|
374
392
|
this.collector.apply(event);
|
|
@@ -553,11 +571,7 @@ function resolveModelId(params) {
|
|
|
553
571
|
return providerRoute?.model ?? readString(metadata?.preferred_model) ?? readString(metadata?.preferredModel) ?? readString(metadata?.model);
|
|
554
572
|
}
|
|
555
573
|
function resolveToolName(update) {
|
|
556
|
-
|
|
557
|
-
const candidate = readString(update.rawInput.toolName) ?? readString(update.rawInput.tool);
|
|
558
|
-
if (candidate) return candidate;
|
|
559
|
-
}
|
|
560
|
-
return readString(update.title) ?? "unknown";
|
|
574
|
+
return resolveToolNameFromAcpUpdate(update);
|
|
561
575
|
}
|
|
562
576
|
function createAssistantMessageId(requestMessageId) {
|
|
563
577
|
return `assistant:${requestMessageId.trim() || "request"}:${randomUUID()}`;
|
|
@@ -567,25 +581,6 @@ function serializeToolArgs(value) {
|
|
|
567
581
|
if (typeof value === "undefined") return "{}";
|
|
568
582
|
return JSON.stringify(value);
|
|
569
583
|
}
|
|
570
|
-
function normalizeRuntimeError(error) {
|
|
571
|
-
if (isNcpError(error)) return error;
|
|
572
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
573
|
-
const lowered = message.toLowerCase();
|
|
574
|
-
return {
|
|
575
|
-
code: lowered.includes("abort") || lowered.includes("cancel") ? "abort-error" : "runtime-error",
|
|
576
|
-
message
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
function createNcpError(code, message) {
|
|
580
|
-
return {
|
|
581
|
-
code,
|
|
582
|
-
message
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
function isNcpError(value) {
|
|
586
|
-
if (!value || typeof value !== "object") return false;
|
|
587
|
-
return typeof value.code === "string" && typeof value.message === "string";
|
|
588
|
-
}
|
|
589
584
|
async function withTimeout(promise, timeoutMs, message) {
|
|
590
585
|
let timeoutHandle = null;
|
|
591
586
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/nextclaw-ncp-runtime-stdio-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Optional NCP runtime adapter backed by a protocol-aware stdio agent command.",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@agentclientprotocol/sdk": "^0.19.0",
|
|
20
|
-
"@nextclaw/ncp": "0.5.
|
|
20
|
+
"@nextclaw/ncp": "0.5.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^20.17.6",
|