@nextclaw/ncp-toolkit 0.5.6 → 0.5.8

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/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpAgentClientEndpoint, NcpAgentConversationHydrationParams, NcpAgentConversationSnapshot, NcpAgentConversationStateManager, NcpAgentRunApi, NcpAgentRunSendOptions, NcpAgentRunStreamOptions, NcpAgentRuntime, NcpAgentServerEndpoint, NcpAgentStreamProvider, NcpEndpointEvent, NcpEndpointManifest, NcpEndpointSubscriber, NcpError, NcpErrorCode, NcpMessage, NcpMessageAbortPayload, NcpMessageSentPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpReasoningStartPayload, NcpRequestEnvelope, NcpRunErrorPayload, NcpRunFinishedPayload, NcpRunMetadataPayload, NcpRunStartedPayload, NcpSessionApi, NcpSessionPatch, NcpSessionSummary, NcpStreamRequestPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpTextStartPayload, NcpToolCallArgsDeltaPayload, NcpToolCallArgsPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpToolCallStartPayload } from "@nextclaw/ncp";
1
+ import { NcpAgentClientEndpoint, NcpAgentConversationHydrationParams, NcpAgentConversationSnapshot, NcpAgentConversationStateManager, NcpAgentRunApi, NcpAgentRunInput, NcpAgentRunSendOptions, NcpAgentRunStreamOptions, NcpAgentRuntime, NcpAgentServerEndpoint, NcpAgentStreamProvider, NcpEndpointEvent, NcpEndpointManifest, NcpEndpointSubscriber, NcpError, NcpErrorCode, NcpMessage, NcpMessageAbortPayload, NcpMessagePart, NcpMessageSentPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpReasoningStartPayload, NcpRequestEnvelope, NcpRunErrorPayload, NcpRunFinishedPayload, NcpRunMetadataPayload, NcpRunStartedPayload, NcpSessionApi, NcpSessionPatch, NcpSessionSummary, NcpStreamRequestPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpTextStartPayload, NcpToolCallArgsDeltaPayload, NcpToolCallArgsPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpToolCallStartPayload, OpenAITool } from "@nextclaw/ncp";
2
2
 
3
3
  //#region src/agent/agent-conversation-state-manager.d.ts
4
4
  declare class DefaultNcpAgentConversationStateManager implements NcpAgentConversationStateManager {
@@ -78,6 +78,7 @@ type RuntimeFactoryParams = {
78
78
  sessionMetadata: Record<string, unknown>;
79
79
  setSessionMetadata: (nextMetadata: Record<string, unknown>) => void;
80
80
  resolveAssetContentPath?: (assetUri: string) => string | null;
81
+ resolveTools?: (input: NcpAgentRunInput) => ReadonlyArray<OpenAITool> | undefined;
81
82
  };
82
83
  type CreateRuntimeFn = (params: RuntimeFactoryParams) => NcpAgentRuntime;
83
84
  type AgentSessionRecord = {
@@ -186,4 +187,30 @@ declare class NcpErrorException extends Error {
186
187
  constructor(code: NcpErrorCode, message: string, details?: Record<string, unknown>);
187
188
  }
188
189
  //#endregion
189
- export { AgentRunExecutor, type AgentSessionRecord, type AgentSessionStore, type CreateRuntimeFn, DefaultNcpAgentBackend, type DefaultNcpAgentBackendConfig, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, type RuntimeFactoryParams, createAgentClientFromServer };
190
+ //#region src/chat/chat.types.d.ts
191
+ type NcpEventStream = AsyncIterable<NcpEndpointEvent>;
192
+ type ChatTarget = {
193
+ conversationId: string;
194
+ accountId?: string;
195
+ resolveAssetContentPath?: (assetUri: string) => string | null;
196
+ metadata?: Record<string, unknown>;
197
+ };
198
+ type NcpReplyInput = {
199
+ target: ChatTarget;
200
+ eventStream: NcpEventStream;
201
+ };
202
+ interface Chat {
203
+ startTyping(target: ChatTarget): Promise<void>;
204
+ sendPart(target: ChatTarget, part: NcpMessagePart): Promise<void>;
205
+ sendError(target: ChatTarget, message: string): Promise<void>;
206
+ stopTyping(target: ChatTarget): Promise<void>;
207
+ }
208
+ //#endregion
209
+ //#region src/chat/ncp-reply-consumer.d.ts
210
+ declare class NcpReplyConsumer {
211
+ private readonly chat;
212
+ constructor(chat: Chat);
213
+ consume: (input: NcpReplyInput) => Promise<void>;
214
+ }
215
+ //#endregion
216
+ export { AgentRunExecutor, type AgentSessionRecord, type AgentSessionStore, Chat, ChatTarget, type CreateRuntimeFn, DefaultNcpAgentBackend, type DefaultNcpAgentBackendConfig, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, NcpEventStream, NcpReplyConsumer, NcpReplyInput, type RuntimeFactoryParams, createAgentClientFromServer };
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ function normalizeConversationMessage(message) {
12
12
  }
13
13
  //#endregion
14
14
  //#region src/agent/agent-conversation-state-manager.utils.ts
15
+ const ABORTED_TOOL_CALL_SENTINEL = "__nextclaw_aborted_tool_call__";
15
16
  function buildRuntimeError(payload) {
16
17
  const message = payload.error?.trim();
17
18
  return {
@@ -68,6 +69,20 @@ function upsertToolInvocationPart(parts, toolPart) {
68
69
  nextParts.push(toolPart);
69
70
  return nextParts;
70
71
  }
72
+ function cancelInFlightToolInvocations(parts) {
73
+ const toolCallIds = [];
74
+ return {
75
+ parts: parts.map((part) => {
76
+ if (part.type !== "tool-invocation" || !part.toolCallId || part.state === "result" || part.state === "cancelled") return part;
77
+ toolCallIds.push(part.toolCallId);
78
+ return {
79
+ ...part,
80
+ state: "cancelled"
81
+ };
82
+ }),
83
+ toolCallIds
84
+ };
85
+ }
71
86
  //#endregion
72
87
  //#region src/agent/agent-conversation-state-manager.ts
73
88
  const DEFAULT_ASSISTANT_ROLE = "assistant";
@@ -205,13 +220,16 @@ var DefaultNcpAgentConversationStateManager = class {
205
220
  this.setError(null);
206
221
  if (this.streamingMessage && (!targetMessageId || this.streamingMessage.id === targetMessageId)) {
207
222
  const streamingMessageId = this.streamingMessage.id;
223
+ const { parts: nextParts, toolCallIds } = cancelInFlightToolInvocations(this.streamingMessage.parts);
208
224
  this.upsertMessage({
209
225
  ...this.streamingMessage,
210
- status: "final"
226
+ status: "final",
227
+ parts: nextParts
211
228
  });
212
229
  this.replaceStreamingMessage(null);
213
230
  if (targetMessageId) clearToolCallTrackingByMessageId(this.toolCallMessageIdByCallId, this.toolCallArgsRawByCallId, targetMessageId);
214
231
  else clearToolCallTrackingByMessageId(this.toolCallMessageIdByCallId, this.toolCallArgsRawByCallId, streamingMessageId);
232
+ toolCallIds.forEach((toolCallId) => this.toolCallArgsRawByCallId.set(toolCallId, ABORTED_TOOL_CALL_SENTINEL));
215
233
  }
216
234
  };
217
235
  handleMessageTextStart = (payload) => {
@@ -271,6 +289,7 @@ var DefaultNcpAgentConversationStateManager = class {
271
289
  if (this.streamingMessage?.id !== payload.messageId) return;
272
290
  };
273
291
  handleMessageToolCallStart = (payload) => {
292
+ if (this.toolCallArgsRawByCallId.get(payload.toolCallId) === "__nextclaw_aborted_tool_call__") return;
274
293
  const targetMessage = this.resolveToolCallTargetMessage(payload.sessionId, payload.toolCallId, payload.messageId);
275
294
  this.toolCallArgsRawByCallId.set(payload.toolCallId, "");
276
295
  const nextParts = upsertToolInvocationPart(targetMessage.parts, {
@@ -288,15 +307,18 @@ var DefaultNcpAgentConversationStateManager = class {
288
307
  this.setError(null);
289
308
  };
290
309
  handleMessageToolCallArgs = (payload) => {
310
+ if (this.toolCallArgsRawByCallId.get(payload.toolCallId) === "__nextclaw_aborted_tool_call__") return;
291
311
  this.toolCallArgsRawByCallId.set(payload.toolCallId, payload.args);
292
312
  this.applyToolCallArgs(payload.sessionId, payload.toolCallId, payload.args);
293
313
  };
294
314
  handleMessageToolCallArgsDelta = (payload) => {
315
+ if (this.toolCallArgsRawByCallId.get(payload.toolCallId) === "__nextclaw_aborted_tool_call__") return;
295
316
  const nextArgs = `${this.toolCallArgsRawByCallId.get(payload.toolCallId) ?? ""}${payload.delta}`;
296
317
  this.toolCallArgsRawByCallId.set(payload.toolCallId, nextArgs);
297
318
  this.applyToolCallArgs(payload.sessionId, payload.toolCallId, nextArgs, payload.messageId);
298
319
  };
299
320
  handleMessageToolCallEnd = (payload) => {
321
+ if (this.toolCallArgsRawByCallId.get(payload.toolCallId) === "__nextclaw_aborted_tool_call__") return;
300
322
  const targetMessage = this.resolveToolCallTargetMessage(payload.sessionId, payload.toolCallId);
301
323
  const args = this.toolCallArgsRawByCallId.get(payload.toolCallId) ?? "";
302
324
  const nextParts = upsertToolInvocationPart(targetMessage.parts, {
@@ -313,6 +335,7 @@ var DefaultNcpAgentConversationStateManager = class {
313
335
  });
314
336
  };
315
337
  handleMessageToolCallResult = (payload) => {
338
+ if (this.toolCallArgsRawByCallId.get(payload.toolCallId) === "__nextclaw_aborted_tool_call__") return;
316
339
  if (!this.updateMessageContainingToolCall(payload.toolCallId, (targetMessage, existingPart) => {
317
340
  const mergedPart = {
318
341
  type: "tool-invocation",
@@ -972,7 +995,7 @@ function toLiveSessionSummary(session) {
972
995
  function now() {
973
996
  return (/* @__PURE__ */ new Date()).toISOString();
974
997
  }
975
- function readOptionalString(value) {
998
+ function readOptionalString$1(value) {
976
999
  if (typeof value !== "string") return null;
977
1000
  const trimmed = value.trim();
978
1001
  return trimmed.length > 0 ? trimmed : null;
@@ -986,14 +1009,14 @@ function resolveAutoSessionLabelFromMessages(messages) {
986
1009
  for (const message of messages) {
987
1010
  if (message.role !== "user") continue;
988
1011
  for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
989
- const text = readOptionalString(part.text);
1012
+ const text = readOptionalString$1(part.text);
990
1013
  if (text) return truncateLabel(text);
991
1014
  }
992
1015
  }
993
1016
  return null;
994
1017
  }
995
1018
  function withAutoSessionLabel(params) {
996
- if (readOptionalString(params.metadata.label)) return params.metadata;
1019
+ if (readOptionalString$1(params.metadata.label)) return params.metadata;
997
1020
  const nextLabel = resolveAutoSessionLabelFromMessages(params.messages);
998
1021
  if (!nextLabel) return params.metadata;
999
1022
  return {
@@ -1334,4 +1357,240 @@ var InMemoryAgentSessionStore = class {
1334
1357
  };
1335
1358
  };
1336
1359
  //#endregion
1337
- export { AgentRunExecutor, DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, NcpErrorException, createAgentClientFromServer };
1360
+ //#region src/chat/ncp-reply-consumer.ts
1361
+ function isTextLikePart(part) {
1362
+ return part.type === "text" || part.type === "rich-text";
1363
+ }
1364
+ function normalizeMessageParts(message) {
1365
+ return (message.role === "assistant" ? sanitizeAssistantReplyTags(message) : message).parts;
1366
+ }
1367
+ function cloneTextLikePartTail(part, skipChars) {
1368
+ const nextText = part.text.slice(skipChars);
1369
+ if (!nextText) return null;
1370
+ if (part.type === "text") return {
1371
+ type: "text",
1372
+ text: nextText
1373
+ };
1374
+ return {
1375
+ ...part,
1376
+ text: nextText
1377
+ };
1378
+ }
1379
+ function resolveFallbackUnsentParts(fallbackText, sentText) {
1380
+ if (!fallbackText || fallbackText === sentText) return [];
1381
+ const nextText = sentText && fallbackText.startsWith(sentText) ? fallbackText.slice(sentText.length) : fallbackText;
1382
+ return nextText ? [{
1383
+ type: "text",
1384
+ text: nextText
1385
+ }] : [];
1386
+ }
1387
+ function canReuseCompletedTextPrefix(normalizedParts, sentText) {
1388
+ const finalText = normalizedParts.filter(isTextLikePart).map((part) => part.text).join("");
1389
+ return !(sentText && finalText && !finalText.startsWith(sentText));
1390
+ }
1391
+ function collectUnsentPartsFromNormalizedParts(normalizedParts, sentText) {
1392
+ let remainingSentChars = sentText.length;
1393
+ const unsentParts = [];
1394
+ for (const part of normalizedParts) {
1395
+ if (!isTextLikePart(part)) {
1396
+ unsentParts.push(part);
1397
+ continue;
1398
+ }
1399
+ if (remainingSentChars >= part.text.length) {
1400
+ remainingSentChars -= part.text.length;
1401
+ continue;
1402
+ }
1403
+ if (remainingSentChars > 0) {
1404
+ const tail = cloneTextLikePartTail(part, remainingSentChars);
1405
+ remainingSentChars = 0;
1406
+ if (tail) unsentParts.push(tail);
1407
+ continue;
1408
+ }
1409
+ unsentParts.push(part);
1410
+ }
1411
+ return unsentParts;
1412
+ }
1413
+ function resolveUnsentMessageParts(message, sentText, fallbackText) {
1414
+ const normalizedParts = normalizeMessageParts(message);
1415
+ if (normalizedParts.length === 0) return resolveFallbackUnsentParts(fallbackText, sentText);
1416
+ if (!canReuseCompletedTextPrefix(normalizedParts, sentText)) return normalizedParts;
1417
+ return collectUnsentPartsFromNormalizedParts(normalizedParts, sentText);
1418
+ }
1419
+ function isRecord(value) {
1420
+ return typeof value === "object" && value !== null;
1421
+ }
1422
+ function readOptionalString(value) {
1423
+ if (typeof value !== "string") return;
1424
+ return value.trim() || void 0;
1425
+ }
1426
+ function readOptionalNumber(value) {
1427
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1428
+ }
1429
+ function projectAssetPutFiles(result) {
1430
+ if (!isRecord(result)) return [];
1431
+ return (isRecord(result.asset) ? [result.asset] : Array.isArray(result.assets) ? result.assets.filter(isRecord) : []).flatMap((asset) => {
1432
+ const assetUri = readOptionalString(asset.uri);
1433
+ const url = readOptionalString(asset.url);
1434
+ if (!assetUri && !url) return [];
1435
+ return [{
1436
+ type: "file",
1437
+ ...readOptionalString(asset.name) ? { name: readOptionalString(asset.name) } : {},
1438
+ ...readOptionalString(asset.mimeType) ? { mimeType: readOptionalString(asset.mimeType) } : {},
1439
+ ...assetUri ? { assetUri } : {},
1440
+ ...url ? { url } : {},
1441
+ ...readOptionalNumber(asset.sizeBytes) != null ? { sizeBytes: readOptionalNumber(asset.sizeBytes) } : {}
1442
+ }];
1443
+ });
1444
+ }
1445
+ function projectToolResultParts(params) {
1446
+ if (params.toolName !== "asset_put" || !params.toolCallId || params.projectedToolCallIds.has(params.toolCallId)) return [];
1447
+ const projectedFiles = projectAssetPutFiles(params.content);
1448
+ if (projectedFiles.length > 0) params.projectedToolCallIds.add(params.toolCallId);
1449
+ return projectedFiles;
1450
+ }
1451
+ function projectChannelOutputParts(parts, projectedToolCallIds) {
1452
+ return parts.flatMap((part) => {
1453
+ if (part.type === "tool-invocation" && part.toolName === "asset_put" && part.result) {
1454
+ if (part.toolCallId && projectedToolCallIds.has(part.toolCallId)) return [];
1455
+ const projectedFiles = projectAssetPutFiles(part.result);
1456
+ if (projectedFiles.length > 0) {
1457
+ if (part.toolCallId) projectedToolCallIds.add(part.toolCallId);
1458
+ return projectedFiles;
1459
+ }
1460
+ }
1461
+ return [part];
1462
+ });
1463
+ }
1464
+ var NcpReplySession = class {
1465
+ activeText = "";
1466
+ fullText = "";
1467
+ sentText = "";
1468
+ typingStarted = false;
1469
+ closed = false;
1470
+ toolNameByCallId = /* @__PURE__ */ new Map();
1471
+ projectedToolCallIds = /* @__PURE__ */ new Set();
1472
+ constructor(chat, target) {
1473
+ this.chat = chat;
1474
+ this.target = target;
1475
+ }
1476
+ consume = async (eventStream) => {
1477
+ try {
1478
+ for await (const event of eventStream) {
1479
+ if (this.closed) break;
1480
+ await this.applyEvent(event);
1481
+ }
1482
+ } finally {
1483
+ await this.stopTyping();
1484
+ }
1485
+ };
1486
+ applyEvent = async (event) => {
1487
+ switch (event.type) {
1488
+ case NcpEventType.MessageTextStart:
1489
+ await this.handleTextStart();
1490
+ return;
1491
+ case NcpEventType.MessageTextDelta:
1492
+ await this.handleTextDelta(event.payload.delta);
1493
+ return;
1494
+ case NcpEventType.MessageTextEnd:
1495
+ await this.handleTextEnd();
1496
+ return;
1497
+ case NcpEventType.MessageToolCallStart:
1498
+ await this.handleToolCallStart(event.payload.toolCallId, event.payload.toolName);
1499
+ return;
1500
+ case NcpEventType.MessageReasoningStart:
1501
+ await this.flushTextPart();
1502
+ return;
1503
+ case NcpEventType.MessageToolCallResult:
1504
+ await this.handleToolCallResult(event.payload.toolCallId, event.payload.content);
1505
+ return;
1506
+ case NcpEventType.MessageCompleted:
1507
+ await this.handleCompleted(event.payload.message);
1508
+ return;
1509
+ case NcpEventType.MessageFailed:
1510
+ await this.handleReplyFailure(event.payload.error.message.trim());
1511
+ return;
1512
+ case NcpEventType.RunError:
1513
+ await this.handleReplyFailure(String(event.payload.error ?? "NCP run failed.").trim());
1514
+ return;
1515
+ default: return;
1516
+ }
1517
+ };
1518
+ handleTextStart = async () => {
1519
+ await this.ensureTypingStarted();
1520
+ if (this.activeText) await this.flushTextPart();
1521
+ this.activeText = "";
1522
+ };
1523
+ handleTextDelta = async (delta) => {
1524
+ await this.ensureTypingStarted();
1525
+ this.activeText += delta;
1526
+ this.fullText += delta;
1527
+ };
1528
+ handleTextEnd = async () => this.flushTextPart();
1529
+ handleToolCallStart = async (toolCallId, toolName) => {
1530
+ this.toolNameByCallId.set(toolCallId, toolName);
1531
+ await this.ensureTypingStarted();
1532
+ await this.flushTextPart();
1533
+ };
1534
+ handleToolCallResult = async (toolCallId, content) => {
1535
+ await this.ensureTypingStarted();
1536
+ const projectedParts = projectToolResultParts({
1537
+ toolCallId,
1538
+ toolName: this.toolNameByCallId.get(toolCallId),
1539
+ content,
1540
+ projectedToolCallIds: this.projectedToolCallIds
1541
+ });
1542
+ if (projectedParts.length === 0) return;
1543
+ await this.flushTextPart();
1544
+ for (const part of projectedParts) await this.chat.sendPart(this.target, part);
1545
+ };
1546
+ handleCompleted = async (message) => {
1547
+ await this.flushTextPart();
1548
+ const unsentParts = projectChannelOutputParts(resolveUnsentMessageParts(message, this.sentText, this.fullText), this.projectedToolCallIds);
1549
+ for (const part of unsentParts) {
1550
+ await this.ensureTypingStarted();
1551
+ await this.chat.sendPart(this.target, part);
1552
+ if (isTextLikePart(part)) this.sentText += part.text;
1553
+ }
1554
+ await this.stopTyping();
1555
+ };
1556
+ handleReplyFailure = async (message) => {
1557
+ if (message) {
1558
+ await this.ensureTypingStarted();
1559
+ await this.chat.sendError(this.target, message);
1560
+ }
1561
+ await this.stopTyping();
1562
+ };
1563
+ flushTextPart = async () => {
1564
+ if (!this.activeText.trim()) {
1565
+ this.activeText = "";
1566
+ return;
1567
+ }
1568
+ await this.ensureTypingStarted();
1569
+ await this.chat.sendPart(this.target, {
1570
+ type: "text",
1571
+ text: this.activeText
1572
+ });
1573
+ this.sentText += this.activeText;
1574
+ this.activeText = "";
1575
+ };
1576
+ ensureTypingStarted = async () => {
1577
+ if (this.typingStarted) return;
1578
+ await this.chat.startTyping(this.target);
1579
+ this.typingStarted = true;
1580
+ };
1581
+ stopTyping = async () => {
1582
+ if (this.closed || !this.typingStarted) return;
1583
+ await this.chat.stopTyping(this.target);
1584
+ this.closed = true;
1585
+ };
1586
+ };
1587
+ var NcpReplyConsumer = class {
1588
+ constructor(chat) {
1589
+ this.chat = chat;
1590
+ }
1591
+ consume = async (input) => {
1592
+ await new NcpReplySession(this.chat, input.target).consume(input.eventStream);
1593
+ };
1594
+ };
1595
+ //#endregion
1596
+ export { AgentRunExecutor, DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, NcpErrorException, NcpReplyConsumer, createAgentClientFromServer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",
@@ -15,14 +15,14 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.5.1"
18
+ "@nextclaw/ncp": "0.5.3"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",
22
22
  "prettier": "^3.3.3",
23
23
  "typescript": "^5.6.3",
24
24
  "vitest": "^4.1.2",
25
- "@nextclaw/ncp-agent-runtime": "0.3.11"
25
+ "@nextclaw/ncp-agent-runtime": "0.3.13"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",