@nextclaw/ncp-toolkit 0.5.6 → 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
@@ -972,7 +972,7 @@ function toLiveSessionSummary(session) {
972
972
  function now() {
973
973
  return (/* @__PURE__ */ new Date()).toISOString();
974
974
  }
975
- function readOptionalString(value) {
975
+ function readOptionalString$1(value) {
976
976
  if (typeof value !== "string") return null;
977
977
  const trimmed = value.trim();
978
978
  return trimmed.length > 0 ? trimmed : null;
@@ -986,14 +986,14 @@ function resolveAutoSessionLabelFromMessages(messages) {
986
986
  for (const message of messages) {
987
987
  if (message.role !== "user") continue;
988
988
  for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
989
- const text = readOptionalString(part.text);
989
+ const text = readOptionalString$1(part.text);
990
990
  if (text) return truncateLabel(text);
991
991
  }
992
992
  }
993
993
  return null;
994
994
  }
995
995
  function withAutoSessionLabel(params) {
996
- if (readOptionalString(params.metadata.label)) return params.metadata;
996
+ if (readOptionalString$1(params.metadata.label)) return params.metadata;
997
997
  const nextLabel = resolveAutoSessionLabelFromMessages(params.messages);
998
998
  if (!nextLabel) return params.metadata;
999
999
  return {
@@ -1334,4 +1334,240 @@ var InMemoryAgentSessionStore = class {
1334
1334
  };
1335
1335
  };
1336
1336
  //#endregion
1337
- 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.6",
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.1"
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.11"
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",