@nextclaw/ncp-toolkit 0.5.17 → 0.5.18

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/README.md CHANGED
@@ -12,7 +12,6 @@ pnpm -C packages/ncp-packages/nextclaw-ncp-toolkit build
12
12
 
13
13
  - Reference conversation-state manager implementations
14
14
  - Protocol-level helper logic that depends on `@nextclaw/ncp` contracts
15
- - Composable agent backend building block: `DefaultNcpAgentBackend`
16
15
  - Default in-memory adapter: `InMemoryAgentSessionStore`
17
16
  - In-process adapter helper: `createAgentClientFromServer`
18
17
  - Runtime throwable helper: `NcpErrorException`
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpAgentClientEndpoint, NcpAgentConversationHydrationParams, NcpAgentConversationSnapshot, NcpAgentConversationStateManager, NcpAgentRunApi, NcpAgentRunInput, NcpAgentRunSendOptions, NcpAgentRunStreamOptions, NcpAgentRuntime, NcpAgentServerEndpoint, NcpAgentStreamProvider, NcpContextWindowUpdatedPayload, 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";
1
+ import { NcpAgentClientEndpoint, NcpAgentConversationHydrationParams, NcpAgentConversationSnapshot, NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRuntime, NcpAgentServerEndpoint, NcpContextWindowUpdatedPayload, NcpEndpointEvent, NcpEndpointSubscriber, NcpError, NcpErrorCode, NcpMessage, NcpMessageAbortPayload, NcpMessagePart, NcpMessageSentPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpReasoningStartPayload, NcpRequestEnvelope, NcpRunErrorPayload, NcpRunFinishedPayload, NcpRunMetadataPayload, NcpRunStartedPayload, NcpSessionSummary, 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 {
@@ -134,71 +134,6 @@ interface AgentSessionStore {
134
134
  deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
135
135
  }
136
136
  //#endregion
137
- //#region src/agent/agent-backend/agent-backend-session.utils.d.ts
138
- type SessionContextWindowResolver = (params: {
139
- messages: readonly NcpMessage[];
140
- metadata: Record<string, unknown>;
141
- sessionId: string;
142
- }) => Record<string, unknown> | null;
143
- //#endregion
144
- //#region src/agent/agent-backend/agent-backend.service.d.ts
145
- type DefaultNcpAgentBackendConfig = {
146
- createRuntime: CreateRuntimeFn;
147
- sessionStore: AgentSessionStore;
148
- resolveSessionContextWindow?: SessionContextWindowResolver;
149
- onSessionRunStatusChanged?: (payload: {
150
- sessionKey: string;
151
- status: "running" | "idle";
152
- }) => void;
153
- endpointId?: string;
154
- version?: string;
155
- metadata?: Record<string, unknown>;
156
- supportedPartTypes?: NcpEndpointManifest["supportedPartTypes"];
157
- expectedLatency?: NcpEndpointManifest["expectedLatency"];
158
- };
159
- declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessionApi, NcpAgentStreamProvider, NcpAgentRunApi {
160
- readonly manifest: NcpEndpointManifest & {
161
- endpointKind: "agent";
162
- };
163
- private readonly sessionStore;
164
- private readonly onSessionRunStatusChanged;
165
- private readonly sessionRegistry;
166
- private readonly executor;
167
- private readonly publisher;
168
- private readonly sessionRealtime;
169
- private readonly resolveSessionContextWindow;
170
- private started;
171
- constructor(config: DefaultNcpAgentBackendConfig);
172
- start: () => Promise<void>;
173
- stop: () => Promise<void>;
174
- emit: (event: NcpEndpointEvent) => Promise<void>;
175
- subscribe: (listener: (event: NcpEndpointEvent) => void) => (() => void);
176
- send: (envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions) => AsyncIterable<NcpEndpointEvent>;
177
- abort: (payload: NcpMessageAbortPayload) => Promise<void>;
178
- stream: (payloadOrParams: NcpStreamRequestPayload | {
179
- payload: NcpStreamRequestPayload;
180
- signal: AbortSignal;
181
- }, opts?: NcpAgentRunStreamOptions) => AsyncIterable<NcpEndpointEvent>;
182
- listSessions: () => Promise<NcpSessionSummary[]>;
183
- isLiveSessionRunning: (sessionId: string) => boolean;
184
- listSessionMessages: (sessionId: string) => Promise<NcpMessage[]>;
185
- getSession: (sessionId: string) => Promise<NcpSessionSummary | null>;
186
- appendMessage: (sessionId: string, message: NcpMessage) => Promise<NcpSessionSummary | null>;
187
- updateToolCallResult: (sessionId: string, toolCallId: string, content: unknown) => Promise<NcpSessionSummary | null>;
188
- updateSession: (sessionId: string, patch: NcpSessionPatch) => Promise<NcpSessionSummary | null>;
189
- deleteSession: (sessionId: string) => Promise<void>;
190
- private ensureStarted;
191
- private startSessionExecution;
192
- private finishSessionExecution;
193
- private handleAbort;
194
- }
195
- //#endregion
196
- //#region src/agent/agent-backend/agent-run-executor.service.d.ts
197
- declare class AgentRunExecutor {
198
- executeRun: (session: LiveSessionState, envelope: NcpRequestEnvelope, controller: AbortController) => AsyncGenerator<NcpEndpointEvent>;
199
- private publishFailure;
200
- }
201
- //#endregion
202
137
  //#region src/agent/agent-backend/in-memory-agent-session.store.d.ts
203
138
  declare class InMemoryAgentSessionStore implements AgentSessionStore {
204
139
  private readonly sessions;
@@ -249,4 +184,4 @@ declare class NcpReplyConsumer {
249
184
  consume: (input: NcpReplyInput) => Promise<void>;
250
185
  }
251
186
  //#endregion
252
- export { AgentRunExecutor, type AgentSessionEventRecord, 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 };
187
+ export { type AgentSessionEventRecord, type AgentSessionRecord, type AgentSessionStore, Chat, ChatTarget, type CreateRuntimeFn, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, NcpEventStream, NcpReplyConsumer, NcpReplyInput, type RuntimeFactoryParams, createAgentClientFromServer };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpEventType, consumeNcpRunHandle, createNcpRunHandle, isHiddenNcpMessage, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
1
+ import { NcpEventType, consumeNcpRunHandle, createNcpRunHandle, sanitizeAssistantReplyTags } from "@nextclaw/ncp";
2
2
  //#region src/agent/agent-conversation-message-normalizer.ts
3
3
  function cloneConversationMessage(message) {
4
4
  return {
@@ -662,822 +662,6 @@ var EventPublisher = class {
662
662
  }
663
663
  };
664
664
  //#endregion
665
- //#region src/agent/agent-backend/agent-live-session-registry.service.ts
666
- function readOptionalAgentId$2(value) {
667
- if (typeof value !== "string") return;
668
- const trimmed = value.trim().toLowerCase();
669
- return trimmed.length > 0 ? trimmed : void 0;
670
- }
671
- function readAgentIdFromMetadata$1(metadata) {
672
- return readOptionalAgentId$2(metadata?.agent_id) ?? readOptionalAgentId$2(metadata?.agentId);
673
- }
674
- var AgentLiveSessionRegistry = class {
675
- sessions = /* @__PURE__ */ new Map();
676
- constructor(sessionStore, createRuntime) {
677
- this.sessionStore = sessionStore;
678
- this.createRuntime = createRuntime;
679
- }
680
- ensureSession = async (sessionId, initialMetadata) => {
681
- const existing = this.sessions.get(sessionId);
682
- if (existing) {
683
- if (!existing.agentId) existing.agentId = readAgentIdFromMetadata$1(initialMetadata) ?? existing.agentId;
684
- if (initialMetadata && Object.keys(initialMetadata).length > 0) existing.metadata = {
685
- ...existing.metadata,
686
- ...structuredClone(initialMetadata)
687
- };
688
- return existing;
689
- }
690
- const storedSession = await this.sessionStore.getSession(sessionId);
691
- const stateManager = new DefaultNcpAgentConversationStateManager();
692
- stateManager.hydrate({
693
- sessionId,
694
- messages: cloneMessages(storedSession?.messages ?? [])
695
- });
696
- const sessionMetadata = {
697
- ...storedSession?.metadata ? structuredClone(storedSession.metadata) : {},
698
- ...initialMetadata ? structuredClone(initialMetadata) : {}
699
- };
700
- const sessionAgentId = readOptionalAgentId$2(storedSession?.agentId) ?? readAgentIdFromMetadata$1(initialMetadata);
701
- const session = {
702
- sessionId,
703
- ...sessionAgentId ? { agentId: sessionAgentId } : {},
704
- createdAt: storedSession?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
705
- stateManager,
706
- metadata: sessionMetadata,
707
- runtime: null,
708
- publisher: new EventPublisher(),
709
- activeExecution: null
710
- };
711
- session.runtime = this.createRuntime({
712
- sessionId,
713
- ...sessionAgentId ? { agentId: sessionAgentId } : {},
714
- stateManager,
715
- sessionMetadata,
716
- setSessionMetadata: (nextMetadata) => {
717
- session.metadata = { ...structuredClone(nextMetadata) };
718
- }
719
- });
720
- this.sessions.set(sessionId, session);
721
- return session;
722
- };
723
- getSession = (sessionId) => {
724
- return this.sessions.get(sessionId) ?? null;
725
- };
726
- deleteSession = (sessionId) => {
727
- const session = this.sessions.get(sessionId) ?? null;
728
- if (session) this.sessions.delete(sessionId);
729
- return session;
730
- };
731
- clear = () => {
732
- this.sessions.clear();
733
- };
734
- listSessions = () => {
735
- return [...this.sessions.values()];
736
- };
737
- };
738
- function cloneMessages(messages) {
739
- return messages.map((message) => structuredClone(message));
740
- }
741
- //#endregion
742
- //#region src/errors/ncp-error-exception.ts
743
- /**
744
- * Throwable form of protocol error for exception-based control flows.
745
- */
746
- var NcpErrorException = class extends Error {
747
- code;
748
- details;
749
- constructor(code, message, details) {
750
- super(message);
751
- this.name = "NcpErrorException";
752
- this.code = code;
753
- this.details = details;
754
- }
755
- };
756
- //#endregion
757
- //#region src/agent/agent-backend/agent-backend-execution.utils.ts
758
- function startAgentBackendSessionExecution(params) {
759
- const { session, envelope, signal, onStatusChanged } = params;
760
- if (session.activeExecution && !session.activeExecution.closed) throw new NcpErrorException("runtime-error", `Session ${session.sessionId} already has an active execution.`, { sessionId: session.sessionId });
761
- const controller = new AbortController();
762
- if (signal) signal.addEventListener("abort", () => controller.abort(), { once: true });
763
- const execution = {
764
- controller,
765
- requestEnvelope: structuredClone(envelope),
766
- abortHandled: false,
767
- closed: false
768
- };
769
- session.activeExecution = execution;
770
- onStatusChanged?.({
771
- sessionKey: session.sessionId,
772
- status: "running"
773
- });
774
- return execution;
775
- }
776
- function finishAgentBackendSessionExecution(params) {
777
- const { session, execution, onStatusChanged } = params;
778
- if (session.activeExecution === execution) {
779
- session.activeExecution = null;
780
- onStatusChanged?.({
781
- sessionKey: session.sessionId,
782
- status: "idle"
783
- });
784
- }
785
- }
786
- //#endregion
787
- //#region src/agent/agent-backend/agent-run-executor.service.ts
788
- var AgentRunExecutor = class {
789
- executeRun = (session, envelope, controller) => (async function* (self) {
790
- if (!isHiddenNcpMessage(envelope.message)) {
791
- const messageSent = {
792
- type: NcpEventType.MessageSent,
793
- payload: {
794
- sessionId: envelope.sessionId,
795
- message: structuredClone(envelope.message),
796
- ...envelope.correlationId ? { correlationId: envelope.correlationId } : {},
797
- metadata: envelope.metadata
798
- }
799
- };
800
- await session.stateManager.dispatch(messageSent);
801
- yield structuredClone(messageSent);
802
- }
803
- try {
804
- for await (const event of session.runtime.run({
805
- sessionId: envelope.sessionId,
806
- messages: [envelope.message],
807
- correlationId: envelope.correlationId,
808
- metadata: envelope.metadata
809
- }, { signal: controller.signal })) yield normalizeRunEvent(event, envelope);
810
- } catch (error) {
811
- if (!controller.signal.aborted) {
812
- const runErrorEvent = await self.publishFailure(error, envelope, session);
813
- yield structuredClone(runErrorEvent);
814
- }
815
- }
816
- })(this);
817
- publishFailure = async (error, envelope, session) => {
818
- const message = error instanceof Error ? error.message : String(error);
819
- const runErrorEvent = {
820
- type: NcpEventType.RunError,
821
- payload: {
822
- sessionId: envelope.sessionId,
823
- ...envelope.correlationId ? { correlationId: envelope.correlationId } : {},
824
- error: message
825
- }
826
- };
827
- await session.stateManager.dispatch(runErrorEvent);
828
- return runErrorEvent;
829
- };
830
- };
831
- function normalizeRunEvent(event, envelope) {
832
- if (!("payload" in event) || !event.payload || typeof event.payload !== "object") return structuredClone(event);
833
- return structuredClone({
834
- ...event,
835
- payload: {
836
- ...event.payload,
837
- sessionId: "sessionId" in event.payload && typeof event.payload.sessionId === "string" ? event.payload.sessionId : envelope.sessionId,
838
- ...envelope.correlationId && (!("correlationId" in event.payload) || typeof event.payload.correlationId !== "string") ? { correlationId: envelope.correlationId } : {}
839
- }
840
- });
841
- }
842
- //#endregion
843
- //#region src/agent/agent-backend/async-queue.ts
844
- function createAsyncQueue() {
845
- const items = [];
846
- let closed = false;
847
- let pendingResolve = null;
848
- return {
849
- push(item) {
850
- if (closed) return;
851
- if (pendingResolve) {
852
- const resolve = pendingResolve;
853
- pendingResolve = null;
854
- resolve({
855
- value: item,
856
- done: false
857
- });
858
- return;
859
- }
860
- items.push(item);
861
- },
862
- close() {
863
- if (closed) return;
864
- closed = true;
865
- if (pendingResolve) {
866
- const resolve = pendingResolve;
867
- pendingResolve = null;
868
- resolve({
869
- value: void 0,
870
- done: true
871
- });
872
- }
873
- },
874
- iterable: { [Symbol.asyncIterator]() {
875
- return { next: () => {
876
- if (items.length > 0) return Promise.resolve({
877
- value: items.shift(),
878
- done: false
879
- });
880
- if (closed) return Promise.resolve({
881
- value: void 0,
882
- done: true
883
- });
884
- return new Promise((resolve) => {
885
- pendingResolve = resolve;
886
- });
887
- } };
888
- } }
889
- };
890
- }
891
- //#endregion
892
- //#region src/agent/agent-backend/agent-backend-session-realtime.service.ts
893
- function readEventSessionId(event) {
894
- const payload = "payload" in event ? event.payload : null;
895
- if (!payload || typeof payload !== "object") return null;
896
- return "sessionId" in payload && typeof payload.sessionId === "string" ? payload.sessionId : null;
897
- }
898
- var AgentBackendSessionRealtime = class {
899
- constructor(params) {
900
- this.params = params;
901
- }
902
- publishSessionEvent = async (session, event, options = {}) => {
903
- if (options.dispatchToStateManager) await session.stateManager.dispatch(event);
904
- if (options.persistSession !== false) await this.params.persistSessionEvent(session, event);
905
- this.params.publishEndpointEvent(event);
906
- session.publisher.publish(event);
907
- };
908
- streamSessionEvents = (payloadOrParams, opts) => {
909
- return (async function* (self) {
910
- const payload = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.payload : payloadOrParams;
911
- const signal = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.signal : opts?.signal ?? new AbortController().signal;
912
- const queue = createAsyncQueue();
913
- const unsubscribe = self.params.subscribeEndpointEvent((event) => {
914
- if (readEventSessionId(event) !== payload.sessionId) return;
915
- queue.push(event);
916
- });
917
- const stop = () => {
918
- unsubscribe();
919
- queue.close();
920
- signal.removeEventListener("abort", stop);
921
- };
922
- const liveSession = self.params.sessionRegistry.getSession(payload.sessionId);
923
- const unsubscribeClose = liveSession ? liveSession.publisher.onClose(() => {
924
- queue.close();
925
- }) : () => void 0;
926
- signal.addEventListener("abort", stop, { once: true });
927
- try {
928
- for await (const event of queue.iterable) {
929
- if (signal.aborted) break;
930
- yield event;
931
- }
932
- } finally {
933
- unsubscribeClose();
934
- stop();
935
- }
936
- })(this);
937
- };
938
- appendMessage = async (sessionId, message) => {
939
- const normalizedSessionId = sessionId.trim();
940
- if (!normalizedSessionId) return null;
941
- let liveSession = this.params.sessionRegistry.getSession(normalizedSessionId);
942
- if (!liveSession) {
943
- if (!await this.params.sessionStore.getSession(normalizedSessionId)) return null;
944
- liveSession = await this.params.sessionRegistry.ensureSession(normalizedSessionId);
945
- }
946
- const nextMessage = {
947
- ...structuredClone(message),
948
- sessionId: normalizedSessionId
949
- };
950
- await this.publishSessionEvent(liveSession, {
951
- type: NcpEventType.MessageSent,
952
- payload: {
953
- sessionId: normalizedSessionId,
954
- message: nextMessage
955
- }
956
- }, { dispatchToStateManager: true });
957
- return this.params.getSessionSummary(normalizedSessionId);
958
- };
959
- updateToolCallResult = async (sessionId, toolCallId, content) => {
960
- const normalizedSessionId = sessionId.trim();
961
- const normalizedToolCallId = toolCallId.trim();
962
- if (!normalizedSessionId || !normalizedToolCallId) return null;
963
- const liveSession = await this.params.sessionRegistry.ensureSession(normalizedSessionId);
964
- await this.publishSessionEvent(liveSession, {
965
- type: NcpEventType.MessageToolCallResult,
966
- payload: {
967
- sessionId: normalizedSessionId,
968
- toolCallId: normalizedToolCallId,
969
- content
970
- }
971
- }, { dispatchToStateManager: true });
972
- return this.params.getSessionSummary(normalizedSessionId);
973
- };
974
- };
975
- //#endregion
976
- //#region src/agent/agent-backend/agent-backend-session.utils.ts
977
- const AUTO_SESSION_LABEL_MAX_LENGTH = 64;
978
- function readOptionalAgentId$1(value) {
979
- if (typeof value !== "string") return;
980
- const trimmed = value.trim().toLowerCase();
981
- return trimmed.length > 0 ? trimmed : void 0;
982
- }
983
- function readMessages(snapshot) {
984
- const messages = snapshot.messages.map((message) => structuredClone(message));
985
- if (snapshot.streamingMessage && !messages.some((message) => message.id === snapshot.streamingMessage?.id)) messages.push(structuredClone(snapshot.streamingMessage));
986
- return messages;
987
- }
988
- function readSessionActivityAt(summary) {
989
- return summary.lastMessageAt ?? summary.createdAt ?? summary.updatedAt;
990
- }
991
- function toSessionSummary(session, liveSession) {
992
- const metadata = withAutoSessionLabel({
993
- metadata: session.metadata ? structuredClone({
994
- ...session.metadata,
995
- ...liveSession?.metadata ? liveSession.metadata : {}
996
- }) : liveSession?.metadata ? structuredClone(liveSession.metadata) : {},
997
- messages: session.messages
998
- });
999
- return {
1000
- sessionId: session.sessionId,
1001
- ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
1002
- messageCount: session.messages.length,
1003
- ...session.createdAt ? { createdAt: session.createdAt } : {},
1004
- updatedAt: session.updatedAt,
1005
- ...session.messages.length > 0 ? { lastMessageAt: session.messages[session.messages.length - 1]?.timestamp ?? session.updatedAt } : {},
1006
- status: liveSession?.activeExecution ? "running" : "idle",
1007
- ...Object.keys(metadata).length > 0 ? { metadata } : {}
1008
- };
1009
- }
1010
- function toLiveSessionSummary(session) {
1011
- const messages = readMessages(session.stateManager.getSnapshot());
1012
- const updatedAt = now();
1013
- const metadata = withAutoSessionLabel({
1014
- metadata: Object.keys(session.metadata).length > 0 ? structuredClone(session.metadata) : session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {},
1015
- messages
1016
- });
1017
- return {
1018
- sessionId: session.sessionId,
1019
- ...readOptionalAgentId$1(session.agentId) ? { agentId: readOptionalAgentId$1(session.agentId) } : {},
1020
- messageCount: messages.length,
1021
- createdAt: session.createdAt,
1022
- updatedAt,
1023
- ...messages.length > 0 ? { lastMessageAt: messages[messages.length - 1]?.timestamp ?? updatedAt } : {},
1024
- status: session.activeExecution ? "running" : "idle",
1025
- ...Object.keys(metadata).length > 0 ? { metadata } : {}
1026
- };
1027
- }
1028
- function withSessionContextWindow(summary, messages, resolver) {
1029
- const contextWindow = resolver?.({
1030
- messages,
1031
- metadata: summary.metadata ?? {},
1032
- sessionId: summary.sessionId
1033
- });
1034
- return contextWindow ? {
1035
- ...summary,
1036
- contextWindow
1037
- } : summary;
1038
- }
1039
- function toLiveSessionSummaryWithContextWindow(session, resolver) {
1040
- const messages = readMessages(session.stateManager.getSnapshot());
1041
- return withSessionContextWindow(toLiveSessionSummary(session), messages, resolver);
1042
- }
1043
- async function listBackendSessionSummaries(params) {
1044
- const { liveSessions, sessionStore } = params;
1045
- const summaries = sessionStore.listSessionSummaries ? await sessionStore.listSessionSummaries() : (await sessionStore.listSessions()).map((session) => toSessionSummary(session, liveSessions.find((liveSession) => liveSession.sessionId === session.sessionId) ?? null));
1046
- for (const liveSession of liveSessions) {
1047
- const existingIndex = summaries.findIndex((session) => session.sessionId === liveSession.sessionId);
1048
- if (existingIndex >= 0) {
1049
- summaries[existingIndex] = {
1050
- ...summaries[existingIndex],
1051
- status: liveSession.activeExecution ? "running" : "idle"
1052
- };
1053
- continue;
1054
- }
1055
- summaries.push(toLiveSessionSummary(liveSession));
1056
- }
1057
- return summaries.sort((left, right) => readSessionActivityAt(right).localeCompare(readSessionActivityAt(left)));
1058
- }
1059
- async function listBackendSessionMessages(params) {
1060
- const { liveSession, sessionId, sessionStore } = params;
1061
- if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
1062
- if (sessionStore.listSessionMessages) return sessionStore.listSessionMessages(sessionId);
1063
- const session = await sessionStore.getSession(sessionId);
1064
- return session ? session.messages.map((message) => structuredClone(message)) : [];
1065
- }
1066
- async function getBackendSessionSummary(params) {
1067
- const { liveSession, resolveSessionContextWindow, sessionId, sessionStore } = params;
1068
- if (!liveSession && sessionStore.getSessionSummary) return sessionStore.getSessionSummary(sessionId);
1069
- const storedSession = await sessionStore.getSession(sessionId);
1070
- const liveMessages = liveSession ? readMessages(liveSession.stateManager.getSnapshot()) : null;
1071
- return storedSession ? withSessionContextWindow(toSessionSummary(storedSession, liveSession), liveMessages ?? storedSession.messages, resolveSessionContextWindow) : liveSession ? toLiveSessionSummaryWithContextWindow(liveSession, resolveSessionContextWindow) : null;
1072
- }
1073
- function now() {
1074
- return (/* @__PURE__ */ new Date()).toISOString();
1075
- }
1076
- function readOptionalString$1(value) {
1077
- if (typeof value !== "string") return null;
1078
- const trimmed = value.trim();
1079
- return trimmed.length > 0 ? trimmed : null;
1080
- }
1081
- function truncateLabel(value) {
1082
- const characters = Array.from(value);
1083
- if (characters.length <= AUTO_SESSION_LABEL_MAX_LENGTH) return value;
1084
- return `${characters.slice(0, AUTO_SESSION_LABEL_MAX_LENGTH).join("")}…`;
1085
- }
1086
- function resolveAutoSessionLabelFromMessages(messages) {
1087
- for (const message of messages) {
1088
- if (message.role !== "user") continue;
1089
- for (const part of message.parts) if (part.type === "text" || part.type === "rich-text") {
1090
- const text = readOptionalString$1(part.text);
1091
- if (text) return truncateLabel(text);
1092
- }
1093
- }
1094
- return null;
1095
- }
1096
- function withAutoSessionLabel(params) {
1097
- const { metadata, messages } = params;
1098
- if (readOptionalString$1(metadata.label)) return metadata;
1099
- const nextLabel = resolveAutoSessionLabelFromMessages(messages);
1100
- if (!nextLabel) return metadata;
1101
- return {
1102
- ...metadata,
1103
- label: nextLabel
1104
- };
1105
- }
1106
- function findFinalAssistantMessageById(session, messageId) {
1107
- const normalizedMessageId = messageId.trim();
1108
- if (!normalizedMessageId) return null;
1109
- const snapshot = session.stateManager.getSnapshot();
1110
- for (let index = snapshot.messages.length - 1; index >= 0; index -= 1) {
1111
- const message = snapshot.messages[index];
1112
- if (message?.id === normalizedMessageId && message.role === "assistant" && message.status === "final") return message;
1113
- }
1114
- return null;
1115
- }
1116
- function buildCompletedEventForFinishedRun(session, event) {
1117
- const messageId = event.payload.messageId?.trim();
1118
- if (!messageId) throw new Error(`Run finished without messageId for session "${session.sessionId}".`);
1119
- const completedMessage = findFinalAssistantMessageById(session, messageId);
1120
- if (!completedMessage) throw new Error(`Run finished without a final assistant message for session "${session.sessionId}" and message "${messageId}".`);
1121
- return {
1122
- type: NcpEventType.MessageCompleted,
1123
- payload: {
1124
- sessionId: session.sessionId,
1125
- message: structuredClone(completedMessage)
1126
- }
1127
- };
1128
- }
1129
- function normalizeSendRunEvent(params) {
1130
- const { session, event } = params;
1131
- if (event.type === NcpEventType.MessageCompleted) {
1132
- if (params.completedMessageSeen) throw new Error(`Multiple final assistant messages were emitted for session "${session.sessionId}".`);
1133
- return {
1134
- eventsToPublish: [event],
1135
- completedMessageSeen: true
1136
- };
1137
- }
1138
- if (event.type === NcpEventType.RunFinished) {
1139
- if (params.completedMessageSeen) return {
1140
- eventsToPublish: [event],
1141
- completedMessageSeen: true
1142
- };
1143
- return {
1144
- eventsToPublish: [buildCompletedEventForFinishedRun(session, event), event],
1145
- completedMessageSeen: true
1146
- };
1147
- }
1148
- return {
1149
- eventsToPublish: [event],
1150
- completedMessageSeen: params.completedMessageSeen
1151
- };
1152
- }
1153
- //#endregion
1154
- //#region src/agent/agent-backend/agent-backend-session-persistence.utils.ts
1155
- function readOptionalAgentId(value) {
1156
- if (typeof value !== "string") return;
1157
- const trimmed = value.trim().toLowerCase();
1158
- return trimmed.length > 0 ? trimmed : void 0;
1159
- }
1160
- function readAgentIdFromMetadata(metadata) {
1161
- return readOptionalAgentId(metadata?.agent_id) ?? readOptionalAgentId(metadata?.agentId);
1162
- }
1163
- function buildPersistedLiveSessionRecord(params) {
1164
- const { session, sessionId, updatedAt } = params;
1165
- const messages = readMessages(session.stateManager.getSnapshot());
1166
- const metadata = withAutoSessionLabel({
1167
- metadata: {
1168
- ...session.metadata ? structuredClone(session.metadata) : {},
1169
- ...session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {}
1170
- },
1171
- messages
1172
- });
1173
- const agentId = readOptionalAgentId(session.agentId) ?? readAgentIdFromMetadata(session.metadata) ?? readAgentIdFromMetadata(session.activeExecution?.requestEnvelope.metadata);
1174
- return {
1175
- sessionId,
1176
- ...agentId ? { agentId } : {},
1177
- messages,
1178
- createdAt: session.createdAt,
1179
- updatedAt,
1180
- metadata
1181
- };
1182
- }
1183
- function buildLiveSessionEventRecord(params) {
1184
- const { session, updatedAt } = params;
1185
- const metadata = {
1186
- ...session.metadata ? structuredClone(session.metadata) : {},
1187
- ...session.activeExecution?.requestEnvelope.metadata ? structuredClone(session.activeExecution.requestEnvelope.metadata) : {}
1188
- };
1189
- const agentId = readOptionalAgentId(session.agentId) ?? readAgentIdFromMetadata(session.metadata) ?? readAgentIdFromMetadata(session.activeExecution?.requestEnvelope.metadata);
1190
- return {
1191
- sessionId: session.sessionId,
1192
- ...agentId ? { agentId } : {},
1193
- createdAt: session.createdAt,
1194
- updatedAt,
1195
- metadata
1196
- };
1197
- }
1198
- function shouldPersistRunEndSnapshot(sessionStore) {
1199
- return !sessionStore.appendSessionEvent;
1200
- }
1201
- async function persistLiveSession(params) {
1202
- const { session, sessionId, sessionStore, updatedAt } = params;
1203
- if (!session) return;
1204
- await sessionStore.saveSession(buildPersistedLiveSessionRecord({
1205
- sessionId,
1206
- session,
1207
- updatedAt
1208
- }));
1209
- }
1210
- async function persistLiveSessionEvent(params) {
1211
- const { event, session, sessionStore, updatedAt } = params;
1212
- if (sessionStore.appendSessionEvent) {
1213
- await sessionStore.appendSessionEvent({
1214
- session: buildLiveSessionEventRecord({
1215
- session,
1216
- updatedAt
1217
- }),
1218
- event,
1219
- updatedAt
1220
- });
1221
- return;
1222
- }
1223
- await sessionStore.saveSession(buildPersistedLiveSessionRecord({
1224
- sessionId: session.sessionId,
1225
- session,
1226
- updatedAt
1227
- }));
1228
- }
1229
- //#endregion
1230
- //#region src/agent/agent-backend/agent-backend.service.ts
1231
- const DEFAULT_SUPPORTED_PART_TYPES = [
1232
- "text",
1233
- "file",
1234
- "source",
1235
- "step-start",
1236
- "reasoning",
1237
- "tool-invocation",
1238
- "card",
1239
- "rich-text",
1240
- "action",
1241
- "extension"
1242
- ];
1243
- const disposeRuntime = async (runtime) => {
1244
- await runtime.dispose?.();
1245
- };
1246
- function buildUpdatedMetadata(params) {
1247
- const { liveSession, patch, storedSession } = params;
1248
- if (patch.metadata === null) return {};
1249
- if (patch.metadata) return structuredClone(patch.metadata);
1250
- return structuredClone(liveSession?.metadata ?? storedSession?.metadata ?? {});
1251
- }
1252
- var DefaultNcpAgentBackend = class {
1253
- manifest;
1254
- sessionStore;
1255
- onSessionRunStatusChanged;
1256
- sessionRegistry;
1257
- executor;
1258
- publisher;
1259
- sessionRealtime;
1260
- resolveSessionContextWindow;
1261
- started = false;
1262
- constructor(config) {
1263
- this.sessionStore = config.sessionStore;
1264
- this.resolveSessionContextWindow = config.resolveSessionContextWindow;
1265
- this.onSessionRunStatusChanged = config.onSessionRunStatusChanged;
1266
- this.sessionRegistry = new AgentLiveSessionRegistry(this.sessionStore, config.createRuntime);
1267
- this.executor = new AgentRunExecutor();
1268
- this.publisher = new EventPublisher();
1269
- this.sessionRealtime = new AgentBackendSessionRealtime({
1270
- sessionRegistry: this.sessionRegistry,
1271
- sessionStore: this.sessionStore,
1272
- publishEndpointEvent: (event) => this.publisher.publish(event),
1273
- subscribeEndpointEvent: (listener) => this.publisher.subscribe(listener),
1274
- persistSession: (sessionId) => persistLiveSession({
1275
- sessionStore: this.sessionStore,
1276
- session: this.sessionRegistry.getSession(sessionId),
1277
- sessionId,
1278
- updatedAt: now()
1279
- }),
1280
- persistSessionEvent: (session, event) => persistLiveSessionEvent({
1281
- sessionStore: this.sessionStore,
1282
- session,
1283
- event,
1284
- updatedAt: now()
1285
- }),
1286
- getSessionSummary: (sessionId) => this.getSession(sessionId)
1287
- });
1288
- this.manifest = {
1289
- endpointKind: "agent",
1290
- endpointId: config.endpointId?.trim() || "ncp-agent-backend",
1291
- version: config.version?.trim() || "0.1.0",
1292
- supportsStreaming: true,
1293
- supportsAbort: true,
1294
- supportsProactiveMessages: false,
1295
- supportsLiveSessionStream: true,
1296
- supportedPartTypes: config.supportedPartTypes ?? DEFAULT_SUPPORTED_PART_TYPES,
1297
- expectedLatency: config.expectedLatency ?? "seconds",
1298
- metadata: config.metadata
1299
- };
1300
- }
1301
- start = async () => {
1302
- if (this.started) return;
1303
- this.started = true;
1304
- this.publisher.publish({ type: NcpEventType.EndpointReady });
1305
- };
1306
- stop = async () => {
1307
- if (!this.started) return;
1308
- this.started = false;
1309
- for (const session of this.sessionRegistry.listSessions()) {
1310
- const execution = session.activeExecution;
1311
- if (!execution) {
1312
- session.publisher.close();
1313
- await disposeRuntime(session.runtime);
1314
- continue;
1315
- }
1316
- execution.abortHandled = true;
1317
- execution.controller.abort();
1318
- this.finishSessionExecution(session, execution);
1319
- session.publisher.close();
1320
- await disposeRuntime(session.runtime);
1321
- }
1322
- this.sessionRegistry.clear();
1323
- };
1324
- emit = async (event) => {
1325
- await this.ensureStarted();
1326
- switch (event.type) {
1327
- case NcpEventType.MessageRequest:
1328
- if (!event.payload.sessionId) throw new Error("MessageRequest requires a sessionId before it reaches the agent backend.");
1329
- for await (const emittedEvent of this.send({
1330
- ...event.payload,
1331
- sessionId: event.payload.sessionId,
1332
- message: {
1333
- ...event.payload.message,
1334
- sessionId: event.payload.sessionId
1335
- }
1336
- }));
1337
- return;
1338
- case NcpEventType.MessageStreamRequest:
1339
- await this.ensureStarted();
1340
- return;
1341
- case NcpEventType.MessageAbort:
1342
- await this.handleAbort(event.payload);
1343
- return;
1344
- default: this.publisher.publish(event);
1345
- }
1346
- };
1347
- subscribe = (listener) => this.publisher.subscribe(listener);
1348
- send = (envelope, options) => {
1349
- return (async function* (self) {
1350
- await self.ensureStarted();
1351
- const session = await self.sessionRegistry.ensureSession(envelope.sessionId, envelope.metadata);
1352
- const execution = self.startSessionExecution(session, envelope, options?.signal);
1353
- let completedMessageSeen = false;
1354
- try {
1355
- for await (const event of self.executor.executeRun(session, envelope, execution.controller)) {
1356
- const normalized = normalizeSendRunEvent({
1357
- session,
1358
- event,
1359
- completedMessageSeen
1360
- });
1361
- completedMessageSeen = normalized.completedMessageSeen;
1362
- for (const normalizedEvent of normalized.eventsToPublish) {
1363
- await self.sessionRealtime.publishSessionEvent(session, normalizedEvent);
1364
- yield normalizedEvent;
1365
- }
1366
- }
1367
- if (execution.controller.signal.aborted && !execution.abortHandled) {
1368
- const abortEvent = {
1369
- type: NcpEventType.MessageAbort,
1370
- payload: { sessionId: session.sessionId }
1371
- };
1372
- execution.abortHandled = true;
1373
- await self.sessionRealtime.publishSessionEvent(session, abortEvent, { dispatchToStateManager: true });
1374
- yield abortEvent;
1375
- }
1376
- } finally {
1377
- self.finishSessionExecution(session, execution);
1378
- if (shouldPersistRunEndSnapshot(self.sessionStore)) await persistLiveSession({
1379
- sessionStore: self.sessionStore,
1380
- session,
1381
- sessionId: session.sessionId,
1382
- updatedAt: now()
1383
- });
1384
- }
1385
- })(this);
1386
- };
1387
- abort = async (payload) => this.handleAbort(payload);
1388
- stream = (payloadOrParams, opts) => this.sessionRealtime.streamSessionEvents(payloadOrParams, opts);
1389
- listSessions = async () => {
1390
- return listBackendSessionSummaries({
1391
- sessionStore: this.sessionStore,
1392
- liveSessions: this.sessionRegistry.listSessions()
1393
- });
1394
- };
1395
- isLiveSessionRunning = (sessionId) => Boolean(this.sessionRegistry.getSession(sessionId)?.activeExecution);
1396
- listSessionMessages = async (sessionId) => {
1397
- return listBackendSessionMessages({
1398
- sessionStore: this.sessionStore,
1399
- liveSession: this.sessionRegistry.getSession(sessionId),
1400
- sessionId
1401
- });
1402
- };
1403
- getSession = async (sessionId) => {
1404
- return getBackendSessionSummary({
1405
- sessionStore: this.sessionStore,
1406
- liveSession: this.sessionRegistry.getSession(sessionId),
1407
- sessionId,
1408
- resolveSessionContextWindow: this.resolveSessionContextWindow
1409
- });
1410
- };
1411
- appendMessage = async (sessionId, message) => {
1412
- await this.ensureStarted();
1413
- return this.sessionRealtime.appendMessage(sessionId, message);
1414
- };
1415
- updateToolCallResult = async (sessionId, toolCallId, content) => {
1416
- await this.ensureStarted();
1417
- return this.sessionRealtime.updateToolCallResult(sessionId, toolCallId, content);
1418
- };
1419
- updateSession = async (sessionId, patch) => {
1420
- const liveSession = this.sessionRegistry.getSession(sessionId);
1421
- const storedSession = await this.sessionStore.getSession(sessionId);
1422
- if (!liveSession && !storedSession) return null;
1423
- const metadata = buildUpdatedMetadata({
1424
- liveSession,
1425
- patch,
1426
- storedSession
1427
- });
1428
- const updatedAt = now();
1429
- if (liveSession) liveSession.metadata = structuredClone(metadata);
1430
- await this.sessionStore.updateSessionMetadata({
1431
- sessionId,
1432
- metadata,
1433
- updatedAt
1434
- });
1435
- return this.getSession(sessionId);
1436
- };
1437
- deleteSession = async (sessionId) => {
1438
- const liveSession = this.sessionRegistry.deleteSession(sessionId);
1439
- const execution = liveSession?.activeExecution;
1440
- if (execution) {
1441
- execution.abortHandled = true;
1442
- execution.controller.abort();
1443
- execution.closed = true;
1444
- }
1445
- liveSession?.publisher.close();
1446
- if (liveSession) await disposeRuntime(liveSession.runtime);
1447
- await this.sessionStore.deleteSession(sessionId);
1448
- };
1449
- ensureStarted = async () => {
1450
- if (!this.started) await this.start();
1451
- };
1452
- startSessionExecution = (session, envelope, signal) => startAgentBackendSessionExecution({
1453
- session,
1454
- envelope,
1455
- signal,
1456
- onStatusChanged: this.onSessionRunStatusChanged
1457
- });
1458
- finishSessionExecution = (session, execution) => finishAgentBackendSessionExecution({
1459
- session,
1460
- execution,
1461
- onStatusChanged: this.onSessionRunStatusChanged
1462
- });
1463
- handleAbort = async (payload) => {
1464
- const session = this.sessionRegistry.getSession(payload.sessionId);
1465
- const execution = session?.activeExecution;
1466
- if (!session || !execution || execution.closed) return;
1467
- execution.abortHandled = true;
1468
- execution.controller.abort();
1469
- const abortEvent = {
1470
- type: NcpEventType.MessageAbort,
1471
- payload: {
1472
- sessionId: payload.sessionId,
1473
- ...payload.messageId ? { messageId: payload.messageId } : {}
1474
- }
1475
- };
1476
- await this.sessionRealtime.publishSessionEvent(session, abortEvent, { dispatchToStateManager: true });
1477
- this.finishSessionExecution(session, execution);
1478
- };
1479
- };
1480
- //#endregion
1481
665
  //#region src/agent/agent-backend/in-memory-agent-session.store.ts
1482
666
  var InMemoryAgentSessionStore = class {
1483
667
  sessions = /* @__PURE__ */ new Map();
@@ -1510,6 +694,21 @@ var InMemoryAgentSessionStore = class {
1510
694
  };
1511
695
  };
1512
696
  //#endregion
697
+ //#region src/errors/ncp-error-exception.ts
698
+ /**
699
+ * Throwable form of protocol error for exception-based control flows.
700
+ */
701
+ var NcpErrorException = class extends Error {
702
+ code;
703
+ details;
704
+ constructor(code, message, details) {
705
+ super(message);
706
+ this.name = "NcpErrorException";
707
+ this.code = code;
708
+ this.details = details;
709
+ }
710
+ };
711
+ //#endregion
1513
712
  //#region src/chat/ncp-reply-consumer.ts
1514
713
  function isTextLikePart(part) {
1515
714
  return part.type === "text" || part.type === "rich-text";
@@ -1746,4 +945,4 @@ var NcpReplyConsumer = class {
1746
945
  };
1747
946
  };
1748
947
  //#endregion
1749
- export { AgentRunExecutor, DefaultNcpAgentBackend, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, NcpErrorException, NcpReplyConsumer, createAgentClientFromServer };
948
+ export { 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.17",
3
+ "version": "0.5.18",
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.12"
18
+ "@nextclaw/ncp": "0.5.13"
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.22"
25
+ "@nextclaw/ncp-agent-runtime": "0.3.23"
26
26
  },
27
27
  "scripts": {
28
28
  "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",