@nextclaw/ncp-toolkit 0.5.5 → 0.5.7

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
@@ -947,12 +947,14 @@ function toSessionSummary(session, liveSession) {
947
947
  ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
948
948
  messageCount: session.messages.length,
949
949
  updatedAt: session.updatedAt,
950
+ ...session.messages.length > 0 ? { lastMessageAt: session.messages[session.messages.length - 1]?.timestamp ?? session.updatedAt } : {},
950
951
  status: liveSession?.activeExecution ? "running" : "idle",
951
952
  ...Object.keys(metadata).length > 0 ? { metadata } : {}
952
953
  };
953
954
  }
954
955
  function toLiveSessionSummary(session) {
955
956
  const messages = readMessages(session.stateManager.getSnapshot());
957
+ const updatedAt = now();
956
958
  const metadata = withAutoSessionLabel({
957
959
  metadata: Object.keys(session.metadata).length > 0 ? structuredClone(session.metadata) : session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {},
958
960
  messages
@@ -961,7 +963,8 @@ function toLiveSessionSummary(session) {
961
963
  sessionId: session.sessionId,
962
964
  ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
963
965
  messageCount: messages.length,
964
- updatedAt: now(),
966
+ updatedAt,
967
+ ...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
965
968
  status: session.activeExecution ? "running" : "idle",
966
969
  ...Object.keys(metadata).length > 0 ? { metadata } : {}
967
970
  };
@@ -969,7 +972,7 @@ function toLiveSessionSummary(session) {
969
972
  function now() {
970
973
  return (/* @__PURE__ */ new Date()).toISOString();
971
974
  }
972
- function readOptionalString(value) {
975
+ function readOptionalString$1(value) {
973
976
  if (typeof value !== "string") return null;
974
977
  const trimmed = value.trim();
975
978
  return trimmed.length > 0 ? trimmed : null;
@@ -983,14 +986,14 @@ function resolveAutoSessionLabelFromMessages(messages) {
983
986
  for (const message of messages) {
984
987
  if (message.role !== "user") continue;
985
988
  for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
986
- const text = readOptionalString(part.text);
989
+ const text = readOptionalString$1(part.text);
987
990
  if (text) return truncateLabel(text);
988
991
  }
989
992
  }
990
993
  return null;
991
994
  }
992
995
  function withAutoSessionLabel(params) {
993
- if (readOptionalString(params.metadata.label)) return params.metadata;
996
+ if (readOptionalString$1(params.metadata.label)) return params.metadata;
994
997
  const nextLabel = resolveAutoSessionLabelFromMessages(params.messages);
995
998
  if (!nextLabel) return params.metadata;
996
999
  return {
@@ -1331,4 +1334,240 @@ var InMemoryAgentSessionStore = class {
1331
1334
  };
1332
1335
  };
1333
1336
  //#endregion
1334
- export { AgentRunExecutor, DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, NcpErrorException, createAgentClientFromServer };
1337
+ //#region src/chat/ncp-reply-consumer.ts
1338
+ function isTextLikePart(part) {
1339
+ return part.type === "text" || part.type === "rich-text";
1340
+ }
1341
+ function normalizeMessageParts(message) {
1342
+ return (message.role === "assistant" ? sanitizeAssistantReplyTags(message) : message).parts;
1343
+ }
1344
+ function cloneTextLikePartTail(part, skipChars) {
1345
+ const nextText = part.text.slice(skipChars);
1346
+ if (!nextText) return null;
1347
+ if (part.type === "text") return {
1348
+ type: "text",
1349
+ text: nextText
1350
+ };
1351
+ return {
1352
+ ...part,
1353
+ text: nextText
1354
+ };
1355
+ }
1356
+ function resolveFallbackUnsentParts(fallbackText, sentText) {
1357
+ if (!fallbackText || fallbackText === sentText) return [];
1358
+ const nextText = sentText && fallbackText.startsWith(sentText) ? fallbackText.slice(sentText.length) : fallbackText;
1359
+ return nextText ? [{
1360
+ type: "text",
1361
+ text: nextText
1362
+ }] : [];
1363
+ }
1364
+ function canReuseCompletedTextPrefix(normalizedParts, sentText) {
1365
+ const finalText = normalizedParts.filter(isTextLikePart).map((part) => part.text).join("");
1366
+ return !(sentText && finalText && !finalText.startsWith(sentText));
1367
+ }
1368
+ function collectUnsentPartsFromNormalizedParts(normalizedParts, sentText) {
1369
+ let remainingSentChars = sentText.length;
1370
+ const unsentParts = [];
1371
+ for (const part of normalizedParts) {
1372
+ if (!isTextLikePart(part)) {
1373
+ unsentParts.push(part);
1374
+ continue;
1375
+ }
1376
+ if (remainingSentChars >= part.text.length) {
1377
+ remainingSentChars -= part.text.length;
1378
+ continue;
1379
+ }
1380
+ if (remainingSentChars > 0) {
1381
+ const tail = cloneTextLikePartTail(part, remainingSentChars);
1382
+ remainingSentChars = 0;
1383
+ if (tail) unsentParts.push(tail);
1384
+ continue;
1385
+ }
1386
+ unsentParts.push(part);
1387
+ }
1388
+ return unsentParts;
1389
+ }
1390
+ function resolveUnsentMessageParts(message, sentText, fallbackText) {
1391
+ const normalizedParts = normalizeMessageParts(message);
1392
+ if (normalizedParts.length === 0) return resolveFallbackUnsentParts(fallbackText, sentText);
1393
+ if (!canReuseCompletedTextPrefix(normalizedParts, sentText)) return normalizedParts;
1394
+ return collectUnsentPartsFromNormalizedParts(normalizedParts, sentText);
1395
+ }
1396
+ function isRecord(value) {
1397
+ return typeof value === "object" && value !== null;
1398
+ }
1399
+ function readOptionalString(value) {
1400
+ if (typeof value !== "string") return;
1401
+ return value.trim() || void 0;
1402
+ }
1403
+ function readOptionalNumber(value) {
1404
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1405
+ }
1406
+ function projectAssetPutFiles(result) {
1407
+ if (!isRecord(result)) return [];
1408
+ return (isRecord(result.asset) ? [result.asset] : Array.isArray(result.assets) ? result.assets.filter(isRecord) : []).flatMap((asset) => {
1409
+ const assetUri = readOptionalString(asset.uri);
1410
+ const url = readOptionalString(asset.url);
1411
+ if (!assetUri && !url) return [];
1412
+ return [{
1413
+ type: "file",
1414
+ ...readOptionalString(asset.name) ? { name: readOptionalString(asset.name) } : {},
1415
+ ...readOptionalString(asset.mimeType) ? { mimeType: readOptionalString(asset.mimeType) } : {},
1416
+ ...assetUri ? { assetUri } : {},
1417
+ ...url ? { url } : {},
1418
+ ...readOptionalNumber(asset.sizeBytes) != null ? { sizeBytes: readOptionalNumber(asset.sizeBytes) } : {}
1419
+ }];
1420
+ });
1421
+ }
1422
+ function projectToolResultParts(params) {
1423
+ if (params.toolName !== "asset_put" || !params.toolCallId || params.projectedToolCallIds.has(params.toolCallId)) return [];
1424
+ const projectedFiles = projectAssetPutFiles(params.content);
1425
+ if (projectedFiles.length > 0) params.projectedToolCallIds.add(params.toolCallId);
1426
+ return projectedFiles;
1427
+ }
1428
+ function projectChannelOutputParts(parts, projectedToolCallIds) {
1429
+ return parts.flatMap((part) => {
1430
+ if (part.type === "tool-invocation" && part.toolName === "asset_put" && part.result) {
1431
+ if (part.toolCallId && projectedToolCallIds.has(part.toolCallId)) return [];
1432
+ const projectedFiles = projectAssetPutFiles(part.result);
1433
+ if (projectedFiles.length > 0) {
1434
+ if (part.toolCallId) projectedToolCallIds.add(part.toolCallId);
1435
+ return projectedFiles;
1436
+ }
1437
+ }
1438
+ return [part];
1439
+ });
1440
+ }
1441
+ var NcpReplySession = class {
1442
+ activeText = "";
1443
+ fullText = "";
1444
+ sentText = "";
1445
+ typingStarted = false;
1446
+ closed = false;
1447
+ toolNameByCallId = /* @__PURE__ */ new Map();
1448
+ projectedToolCallIds = /* @__PURE__ */ new Set();
1449
+ constructor(chat, target) {
1450
+ this.chat = chat;
1451
+ this.target = target;
1452
+ }
1453
+ consume = async (eventStream) => {
1454
+ try {
1455
+ for await (const event of eventStream) {
1456
+ if (this.closed) break;
1457
+ await this.applyEvent(event);
1458
+ }
1459
+ } finally {
1460
+ await this.stopTyping();
1461
+ }
1462
+ };
1463
+ applyEvent = async (event) => {
1464
+ switch (event.type) {
1465
+ case NcpEventType.MessageTextStart:
1466
+ await this.handleTextStart();
1467
+ return;
1468
+ case NcpEventType.MessageTextDelta:
1469
+ await this.handleTextDelta(event.payload.delta);
1470
+ return;
1471
+ case NcpEventType.MessageTextEnd:
1472
+ await this.handleTextEnd();
1473
+ return;
1474
+ case NcpEventType.MessageToolCallStart:
1475
+ await this.handleToolCallStart(event.payload.toolCallId, event.payload.toolName);
1476
+ return;
1477
+ case NcpEventType.MessageReasoningStart:
1478
+ await this.flushTextPart();
1479
+ return;
1480
+ case NcpEventType.MessageToolCallResult:
1481
+ await this.handleToolCallResult(event.payload.toolCallId, event.payload.content);
1482
+ return;
1483
+ case NcpEventType.MessageCompleted:
1484
+ await this.handleCompleted(event.payload.message);
1485
+ return;
1486
+ case NcpEventType.MessageFailed:
1487
+ await this.handleReplyFailure(event.payload.error.message.trim());
1488
+ return;
1489
+ case NcpEventType.RunError:
1490
+ await this.handleReplyFailure(String(event.payload.error ?? "NCP run failed.").trim());
1491
+ return;
1492
+ default: return;
1493
+ }
1494
+ };
1495
+ handleTextStart = async () => {
1496
+ await this.ensureTypingStarted();
1497
+ if (this.activeText) await this.flushTextPart();
1498
+ this.activeText = "";
1499
+ };
1500
+ handleTextDelta = async (delta) => {
1501
+ await this.ensureTypingStarted();
1502
+ this.activeText += delta;
1503
+ this.fullText += delta;
1504
+ };
1505
+ handleTextEnd = async () => this.flushTextPart();
1506
+ handleToolCallStart = async (toolCallId, toolName) => {
1507
+ this.toolNameByCallId.set(toolCallId, toolName);
1508
+ await this.ensureTypingStarted();
1509
+ await this.flushTextPart();
1510
+ };
1511
+ handleToolCallResult = async (toolCallId, content) => {
1512
+ await this.ensureTypingStarted();
1513
+ const projectedParts = projectToolResultParts({
1514
+ toolCallId,
1515
+ toolName: this.toolNameByCallId.get(toolCallId),
1516
+ content,
1517
+ projectedToolCallIds: this.projectedToolCallIds
1518
+ });
1519
+ if (projectedParts.length === 0) return;
1520
+ await this.flushTextPart();
1521
+ for (const part of projectedParts) await this.chat.sendPart(this.target, part);
1522
+ };
1523
+ handleCompleted = async (message) => {
1524
+ await this.flushTextPart();
1525
+ const unsentParts = projectChannelOutputParts(resolveUnsentMessageParts(message, this.sentText, this.fullText), this.projectedToolCallIds);
1526
+ for (const part of unsentParts) {
1527
+ await this.ensureTypingStarted();
1528
+ await this.chat.sendPart(this.target, part);
1529
+ if (isTextLikePart(part)) this.sentText += part.text;
1530
+ }
1531
+ await this.stopTyping();
1532
+ };
1533
+ handleReplyFailure = async (message) => {
1534
+ if (message) {
1535
+ await this.ensureTypingStarted();
1536
+ await this.chat.sendError(this.target, message);
1537
+ }
1538
+ await this.stopTyping();
1539
+ };
1540
+ flushTextPart = async () => {
1541
+ if (!this.activeText.trim()) {
1542
+ this.activeText = "";
1543
+ return;
1544
+ }
1545
+ await this.ensureTypingStarted();
1546
+ await this.chat.sendPart(this.target, {
1547
+ type: "text",
1548
+ text: this.activeText
1549
+ });
1550
+ this.sentText += this.activeText;
1551
+ this.activeText = "";
1552
+ };
1553
+ ensureTypingStarted = async () => {
1554
+ if (this.typingStarted) return;
1555
+ await this.chat.startTyping(this.target);
1556
+ this.typingStarted = true;
1557
+ };
1558
+ stopTyping = async () => {
1559
+ if (this.closed || !this.typingStarted) return;
1560
+ await this.chat.stopTyping(this.target);
1561
+ this.closed = true;
1562
+ };
1563
+ };
1564
+ var NcpReplyConsumer = class {
1565
+ constructor(chat) {
1566
+ this.chat = chat;
1567
+ }
1568
+ consume = async (input) => {
1569
+ await new NcpReplySession(this.chat, input.target).consume(input.eventStream);
1570
+ };
1571
+ };
1572
+ //#endregion
1573
+ 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.5",
3
+ "version": "0.5.7",
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.0"
18
+ "@nextclaw/ncp": "0.5.2"
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.10"
25
+ "@nextclaw/ncp-agent-runtime": "0.3.12"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",