@nextclaw/ncp-toolkit 0.4.15 → 0.4.17

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +10 -13
  2. package/dist/index.js +377 -369
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -82,7 +82,6 @@ type AgentSessionRecord = {
82
82
  };
83
83
  type LiveSessionExecution = {
84
84
  controller: AbortController;
85
- publisher: EventPublisher;
86
85
  requestEnvelope: NcpRequestEnvelope;
87
86
  abortHandled: boolean;
88
87
  closed: boolean;
@@ -92,12 +91,14 @@ type LiveSessionState = {
92
91
  runtime: NcpAgentRuntime;
93
92
  stateManager: NcpAgentConversationStateManager;
94
93
  metadata: Record<string, unknown>;
94
+ publisher: EventPublisher;
95
95
  activeExecution: LiveSessionExecution | null;
96
96
  };
97
97
  interface AgentSessionStore {
98
98
  getSession(sessionId: string): Promise<AgentSessionRecord | null>;
99
99
  listSessions(): Promise<AgentSessionRecord[]>;
100
100
  saveSession(session: AgentSessionRecord): Promise<void>;
101
+ replaceSession(session: AgentSessionRecord): Promise<void>;
101
102
  deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
102
103
  }
103
104
 
@@ -123,12 +124,13 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
123
124
  private readonly sessionRegistry;
124
125
  private readonly executor;
125
126
  private readonly publisher;
127
+ private readonly sessionRealtime;
126
128
  private started;
127
129
  constructor(config: DefaultNcpAgentBackendConfig);
128
130
  start: () => Promise<void>;
129
131
  stop: () => Promise<void>;
130
132
  emit: (event: NcpEndpointEvent) => Promise<void>;
131
- subscribe: (listener: (event: NcpEndpointEvent) => void) => () => void;
133
+ subscribe: (listener: (event: NcpEndpointEvent) => void) => (() => void);
132
134
  send: (envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions) => AsyncIterable<NcpEndpointEvent>;
133
135
  abort: (payload: NcpMessageAbortPayload) => Promise<void>;
134
136
  stream: (payloadOrParams: NcpStreamRequestPayload | {
@@ -145,28 +147,23 @@ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessi
145
147
  private ensureStarted;
146
148
  private startSessionExecution;
147
149
  private finishSessionExecution;
148
- private closeExecution;
149
- private publishLiveEvent;
150
- private handleRequest;
151
- private streamToSubscribers;
150
+ private publishEndpointEvent;
152
151
  private handleAbort;
153
- private createAbortEvent;
154
152
  private persistSession;
155
153
  }
156
154
 
157
155
  declare class AgentRunExecutor {
158
- private readonly persistSession;
159
- constructor(persistSession: (sessionId: string) => Promise<void>);
160
156
  executeRun(session: LiveSessionState, envelope: NcpRequestEnvelope, controller: AbortController): AsyncGenerator<NcpEndpointEvent>;
161
157
  private publishFailure;
162
158
  }
163
159
 
164
160
  declare class InMemoryAgentSessionStore implements AgentSessionStore {
165
161
  private readonly sessions;
166
- getSession(sessionId: string): Promise<AgentSessionRecord | null>;
167
- listSessions(): Promise<AgentSessionRecord[]>;
168
- saveSession(session: AgentSessionRecord): Promise<void>;
169
- deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
162
+ getSession: (sessionId: string) => Promise<AgentSessionRecord | null>;
163
+ listSessions: () => Promise<AgentSessionRecord[]>;
164
+ saveSession: (session: AgentSessionRecord) => Promise<void>;
165
+ replaceSession: (session: AgentSessionRecord) => Promise<void>;
166
+ deleteSession: (sessionId: string) => Promise<AgentSessionRecord | null>;
170
167
  }
171
168
 
172
169
  /**
package/dist/index.js CHANGED
@@ -702,18 +702,51 @@ async function consume(events) {
702
702
 
703
703
  // src/agent/agent-backend/agent-backend.ts
704
704
  import {
705
- NcpEventType as NcpEventType8
705
+ NcpEventType as NcpEventType6
706
706
  } from "@nextclaw/ncp";
707
707
 
708
- // src/errors/ncp-error-exception.ts
709
- var NcpErrorException = class extends Error {
710
- code;
711
- details;
712
- constructor(code, message, details) {
713
- super(message);
714
- this.name = "NcpErrorException";
715
- this.code = code;
716
- this.details = details;
708
+ // src/agent/agent-backend/event-publisher.ts
709
+ var EventPublisher = class {
710
+ listeners = /* @__PURE__ */ new Set();
711
+ closeListeners = /* @__PURE__ */ new Set();
712
+ closed = false;
713
+ subscribe(listener) {
714
+ if (this.closed) {
715
+ return () => void 0;
716
+ }
717
+ this.listeners.add(listener);
718
+ return () => {
719
+ this.listeners.delete(listener);
720
+ };
721
+ }
722
+ onClose(listener) {
723
+ if (this.closed) {
724
+ listener();
725
+ return () => void 0;
726
+ }
727
+ this.closeListeners.add(listener);
728
+ return () => {
729
+ this.closeListeners.delete(listener);
730
+ };
731
+ }
732
+ publish(event) {
733
+ if (this.closed) {
734
+ return;
735
+ }
736
+ for (const listener of this.listeners) {
737
+ listener(structuredClone(event));
738
+ }
739
+ }
740
+ close() {
741
+ if (this.closed) {
742
+ return;
743
+ }
744
+ this.closed = true;
745
+ this.listeners.clear();
746
+ for (const listener of this.closeListeners) {
747
+ listener();
748
+ }
749
+ this.closeListeners.clear();
717
750
  }
718
751
  };
719
752
 
@@ -750,6 +783,7 @@ var AgentLiveSessionRegistry = class {
750
783
  stateManager,
751
784
  metadata: sessionMetadata,
752
785
  runtime: null,
786
+ publisher: new EventPublisher(),
753
787
  activeExecution: null
754
788
  };
755
789
  session.runtime = this.createRuntime({
@@ -786,15 +820,65 @@ function cloneMessages(messages) {
786
820
  return messages.map((message) => structuredClone(message));
787
821
  }
788
822
 
823
+ // src/errors/ncp-error-exception.ts
824
+ var NcpErrorException = class extends Error {
825
+ code;
826
+ details;
827
+ constructor(code, message, details) {
828
+ super(message);
829
+ this.name = "NcpErrorException";
830
+ this.code = code;
831
+ this.details = details;
832
+ }
833
+ };
834
+
835
+ // src/agent/agent-backend/agent-backend-execution-utils.ts
836
+ function startAgentBackendSessionExecution(params) {
837
+ const { session, envelope, signal, onStatusChanged } = params;
838
+ if (session.activeExecution && !session.activeExecution.closed) {
839
+ throw new NcpErrorException(
840
+ "runtime-error",
841
+ `Session ${session.sessionId} already has an active execution.`,
842
+ { sessionId: session.sessionId }
843
+ );
844
+ }
845
+ const controller = new AbortController();
846
+ if (signal) {
847
+ signal.addEventListener("abort", () => controller.abort(), {
848
+ once: true
849
+ });
850
+ }
851
+ const execution = {
852
+ controller,
853
+ requestEnvelope: structuredClone(envelope),
854
+ abortHandled: false,
855
+ closed: false
856
+ };
857
+ session.activeExecution = execution;
858
+ onStatusChanged?.({ sessionKey: session.sessionId, status: "running" });
859
+ return execution;
860
+ }
861
+ function finishAgentBackendSessionExecution(params) {
862
+ const { session, execution, onStatusChanged } = params;
863
+ if (session.activeExecution === execution) {
864
+ session.activeExecution = null;
865
+ onStatusChanged?.({ sessionKey: session.sessionId, status: "idle" });
866
+ }
867
+ closeAgentBackendSessionExecution(execution);
868
+ }
869
+ function closeAgentBackendSessionExecution(execution) {
870
+ if (execution.closed) {
871
+ return;
872
+ }
873
+ execution.closed = true;
874
+ }
875
+
789
876
  // src/agent/agent-backend/agent-run-executor.ts
790
877
  import {
791
878
  isHiddenNcpMessage
792
879
  } from "@nextclaw/ncp";
793
880
  import { NcpEventType as NcpEventType3 } from "@nextclaw/ncp";
794
881
  var AgentRunExecutor = class {
795
- constructor(persistSession) {
796
- this.persistSession = persistSession;
797
- }
798
882
  async *executeRun(session, envelope, controller) {
799
883
  if (!isHiddenNcpMessage(envelope.message)) {
800
884
  const messageSent = {
@@ -806,7 +890,6 @@ var AgentRunExecutor = class {
806
890
  }
807
891
  };
808
892
  await session.stateManager.dispatch(messageSent);
809
- await this.persistSession(envelope.sessionId);
810
893
  yield structuredClone(messageSent);
811
894
  }
812
895
  try {
@@ -819,16 +902,17 @@ var AgentRunExecutor = class {
819
902
  },
820
903
  { signal: controller.signal }
821
904
  )) {
822
- await this.persistSession(envelope.sessionId);
823
905
  yield structuredClone(event);
824
906
  }
825
907
  } catch (error) {
826
908
  if (!controller.signal.aborted) {
827
- const runErrorEvent = await this.publishFailure(error, envelope, session);
909
+ const runErrorEvent = await this.publishFailure(
910
+ error,
911
+ envelope,
912
+ session
913
+ );
828
914
  yield structuredClone(runErrorEvent);
829
915
  }
830
- } finally {
831
- await this.persistSession(envelope.sessionId);
832
916
  }
833
917
  }
834
918
  async publishFailure(error, envelope, session) {
@@ -841,13 +925,179 @@ var AgentRunExecutor = class {
841
925
  }
842
926
  };
843
927
  await session.stateManager.dispatch(runErrorEvent);
844
- await this.persistSession(envelope.sessionId);
845
928
  return runErrorEvent;
846
929
  }
847
930
  };
848
931
 
849
- // src/agent/agent-backend/agent-backend-session-utils.ts
932
+ // src/agent/agent-backend/agent-backend-session-realtime.ts
850
933
  import { NcpEventType as NcpEventType4 } from "@nextclaw/ncp";
934
+
935
+ // src/agent/agent-backend/async-queue.ts
936
+ function createAsyncQueue() {
937
+ const items = [];
938
+ let closed = false;
939
+ let pendingResolve = null;
940
+ const iterable = {
941
+ [Symbol.asyncIterator]() {
942
+ return {
943
+ next: () => {
944
+ if (items.length > 0) {
945
+ return Promise.resolve({
946
+ value: items.shift(),
947
+ done: false
948
+ });
949
+ }
950
+ if (closed) {
951
+ return Promise.resolve({
952
+ value: void 0,
953
+ done: true
954
+ });
955
+ }
956
+ return new Promise((resolve) => {
957
+ pendingResolve = resolve;
958
+ });
959
+ }
960
+ };
961
+ }
962
+ };
963
+ return {
964
+ push(item) {
965
+ if (closed) {
966
+ return;
967
+ }
968
+ if (pendingResolve) {
969
+ const resolve = pendingResolve;
970
+ pendingResolve = null;
971
+ resolve({ value: item, done: false });
972
+ return;
973
+ }
974
+ items.push(item);
975
+ },
976
+ close() {
977
+ if (closed) {
978
+ return;
979
+ }
980
+ closed = true;
981
+ if (pendingResolve) {
982
+ const resolve = pendingResolve;
983
+ pendingResolve = null;
984
+ resolve({
985
+ value: void 0,
986
+ done: true
987
+ });
988
+ }
989
+ },
990
+ iterable
991
+ };
992
+ }
993
+
994
+ // src/agent/agent-backend/agent-backend-session-realtime.ts
995
+ var AgentBackendSessionRealtime = class {
996
+ constructor(params) {
997
+ this.params = params;
998
+ }
999
+ publishSessionEvent = async (session, event, options = {}) => {
1000
+ if (options.dispatchToStateManager) {
1001
+ await session.stateManager.dispatch(event);
1002
+ }
1003
+ this.params.publishEndpointEvent(event);
1004
+ session.publisher.publish(event);
1005
+ if (options.persistSession !== false) {
1006
+ await this.params.persistSession(session.sessionId);
1007
+ }
1008
+ };
1009
+ streamSessionEvents = (payloadOrParams, opts) => {
1010
+ return (async function* (self) {
1011
+ const payload = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.payload : payloadOrParams;
1012
+ const signal = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.signal : opts?.signal ?? new AbortController().signal;
1013
+ const session = await self.params.sessionRegistry.ensureSession(
1014
+ payload.sessionId
1015
+ );
1016
+ const queue = createAsyncQueue();
1017
+ const unsubscribe = session.publisher.subscribe((event) => {
1018
+ queue.push(event);
1019
+ });
1020
+ const unsubscribeClose = session.publisher.onClose(() => {
1021
+ queue.close();
1022
+ });
1023
+ const stop = () => {
1024
+ unsubscribe();
1025
+ unsubscribeClose();
1026
+ queue.close();
1027
+ signal.removeEventListener("abort", stop);
1028
+ };
1029
+ signal.addEventListener("abort", stop, { once: true });
1030
+ try {
1031
+ for await (const event of queue.iterable) {
1032
+ if (signal.aborted) {
1033
+ break;
1034
+ }
1035
+ yield event;
1036
+ }
1037
+ } finally {
1038
+ stop();
1039
+ }
1040
+ })(this);
1041
+ };
1042
+ appendMessage = async (sessionId, message) => {
1043
+ const normalizedSessionId = sessionId.trim();
1044
+ if (!normalizedSessionId) {
1045
+ return null;
1046
+ }
1047
+ let liveSession = this.params.sessionRegistry.getSession(normalizedSessionId);
1048
+ if (!liveSession) {
1049
+ const storedSession = await this.params.sessionStore.getSession(normalizedSessionId);
1050
+ if (!storedSession) {
1051
+ return null;
1052
+ }
1053
+ liveSession = await this.params.sessionRegistry.ensureSession(normalizedSessionId);
1054
+ }
1055
+ const nextMessage = {
1056
+ ...structuredClone(message),
1057
+ sessionId: normalizedSessionId
1058
+ };
1059
+ await this.publishSessionEvent(
1060
+ liveSession,
1061
+ {
1062
+ type: NcpEventType4.MessageSent,
1063
+ payload: {
1064
+ sessionId: normalizedSessionId,
1065
+ message: nextMessage
1066
+ }
1067
+ },
1068
+ {
1069
+ dispatchToStateManager: true
1070
+ }
1071
+ );
1072
+ return this.params.getSessionSummary(normalizedSessionId);
1073
+ };
1074
+ updateToolCallResult = async (sessionId, toolCallId, content) => {
1075
+ const normalizedSessionId = sessionId.trim();
1076
+ const normalizedToolCallId = toolCallId.trim();
1077
+ if (!normalizedSessionId || !normalizedToolCallId) {
1078
+ return null;
1079
+ }
1080
+ const liveSession = await this.params.sessionRegistry.ensureSession(normalizedSessionId);
1081
+ await this.publishSessionEvent(
1082
+ liveSession,
1083
+ {
1084
+ type: NcpEventType4.MessageToolCallResult,
1085
+ payload: {
1086
+ sessionId: normalizedSessionId,
1087
+ toolCallId: normalizedToolCallId,
1088
+ content
1089
+ }
1090
+ },
1091
+ {
1092
+ dispatchToStateManager: true
1093
+ }
1094
+ );
1095
+ return this.params.getSessionSummary(normalizedSessionId);
1096
+ };
1097
+ };
1098
+
1099
+ // src/agent/agent-backend/agent-backend-session-utils.ts
1100
+ import { NcpEventType as NcpEventType5 } from "@nextclaw/ncp";
851
1101
  var AUTO_SESSION_LABEL_MAX_LENGTH = 64;
852
1102
  function readMessages(snapshot) {
853
1103
  const messages = snapshot.messages.map((message) => structuredClone(message));
@@ -934,16 +1184,6 @@ function withAutoSessionLabel(params) {
934
1184
  label: nextLabel
935
1185
  };
936
1186
  }
937
- function isTerminalEvent(event) {
938
- switch (event.type) {
939
- case NcpEventType4.MessageAbort:
940
- case NcpEventType4.RunFinished:
941
- case NcpEventType4.RunError:
942
- return true;
943
- default:
944
- return false;
945
- }
946
- }
947
1187
 
948
1188
  // src/agent/agent-backend/agent-backend-session-persistence.ts
949
1189
  function buildUpdatedSessionRecord(params) {
@@ -975,222 +1215,19 @@ function buildPersistedLiveSessionRecord(params) {
975
1215
  };
976
1216
  }
977
1217
 
978
- // src/agent/agent-backend/agent-backend-update-tool-call-result.ts
979
- import { NcpEventType as NcpEventType5 } from "@nextclaw/ncp";
980
- async function updateAgentBackendToolCallResult(params) {
981
- const normalizedSessionId = params.sessionId.trim();
982
- const normalizedToolCallId = params.toolCallId.trim();
983
- if (!normalizedSessionId || !normalizedToolCallId) {
984
- return null;
985
- }
986
- const liveSession = await params.sessionRegistry.ensureSession(normalizedSessionId);
987
- const event = {
988
- type: NcpEventType5.MessageToolCallResult,
989
- payload: {
990
- sessionId: normalizedSessionId,
991
- toolCallId: normalizedToolCallId,
992
- content: params.content
993
- }
994
- };
995
- await liveSession.stateManager.dispatch(event);
996
- params.publisher.publish(event);
997
- if (liveSession.activeExecution && !liveSession.activeExecution.closed) {
998
- liveSession.activeExecution.publisher.publish(event);
999
- }
1000
- await params.persistSession(normalizedSessionId);
1001
- return params.getSession(normalizedSessionId);
1002
- }
1003
-
1004
- // src/agent/agent-backend/agent-backend-append-message.ts
1005
- import { NcpEventType as NcpEventType6 } from "@nextclaw/ncp";
1006
- async function appendAgentBackendMessage(params) {
1007
- const normalizedSessionId = params.sessionId.trim();
1008
- if (!normalizedSessionId) {
1009
- return null;
1010
- }
1011
- let liveSession = params.sessionRegistry.getSession(normalizedSessionId);
1012
- if (!liveSession) {
1013
- const storedSession = await params.sessionStore.getSession(normalizedSessionId);
1014
- if (!storedSession) {
1015
- return null;
1016
- }
1017
- liveSession = await params.sessionRegistry.ensureSession(normalizedSessionId);
1018
- }
1019
- const nextMessage = {
1020
- ...structuredClone(params.message),
1021
- sessionId: normalizedSessionId
1022
- };
1023
- const event = {
1024
- type: NcpEventType6.MessageSent,
1025
- payload: {
1026
- sessionId: normalizedSessionId,
1027
- message: nextMessage
1028
- }
1029
- };
1030
- await liveSession.stateManager.dispatch(event);
1031
- params.publisher.publish(event);
1032
- if (liveSession.activeExecution && !liveSession.activeExecution.closed) {
1033
- liveSession.activeExecution.publisher.publish(event);
1034
- }
1035
- await params.persistSession(normalizedSessionId);
1036
- return params.getSession(normalizedSessionId);
1037
- }
1038
-
1039
- // src/agent/agent-backend/agent-backend-stream.ts
1040
- import { NcpEventType as NcpEventType7 } from "@nextclaw/ncp";
1041
-
1042
- // src/agent/agent-backend/async-queue.ts
1043
- function createAsyncQueue() {
1044
- const items = [];
1045
- let closed = false;
1046
- let pendingResolve = null;
1047
- const iterable = {
1048
- [Symbol.asyncIterator]() {
1049
- return {
1050
- next: () => {
1051
- if (items.length > 0) {
1052
- return Promise.resolve({
1053
- value: items.shift(),
1054
- done: false
1055
- });
1056
- }
1057
- if (closed) {
1058
- return Promise.resolve({
1059
- value: void 0,
1060
- done: true
1061
- });
1062
- }
1063
- return new Promise((resolve) => {
1064
- pendingResolve = resolve;
1065
- });
1066
- }
1067
- };
1068
- }
1069
- };
1070
- return {
1071
- push(item) {
1072
- if (closed) {
1073
- return;
1074
- }
1075
- if (pendingResolve) {
1076
- const resolve = pendingResolve;
1077
- pendingResolve = null;
1078
- resolve({ value: item, done: false });
1079
- return;
1080
- }
1081
- items.push(item);
1082
- },
1083
- close() {
1084
- if (closed) {
1085
- return;
1086
- }
1087
- closed = true;
1088
- if (pendingResolve) {
1089
- const resolve = pendingResolve;
1090
- pendingResolve = null;
1091
- resolve({
1092
- value: void 0,
1093
- done: true
1094
- });
1095
- }
1096
- },
1097
- iterable
1098
- };
1099
- }
1100
-
1101
- // src/agent/agent-backend/agent-backend-stream.ts
1102
- async function* streamAgentBackendExecution(params) {
1103
- const payload = "payload" in params.payloadOrParams && "signal" in params.payloadOrParams ? params.payloadOrParams.payload : params.payloadOrParams;
1104
- const signal = "payload" in params.payloadOrParams && "signal" in params.payloadOrParams ? params.payloadOrParams.signal : params.opts?.signal ?? new AbortController().signal;
1105
- const session = params.sessionRegistry.getSession(payload.sessionId);
1106
- const execution = session?.activeExecution;
1107
- if (!session || !execution || execution.closed) {
1108
- if (session) {
1109
- yield {
1110
- type: NcpEventType7.RunFinished,
1111
- payload: {
1112
- sessionId: payload.sessionId
1113
- }
1114
- };
1115
- }
1116
- return;
1117
- }
1118
- const queue = createAsyncQueue();
1119
- const unsubscribe = execution.publisher.subscribe((event) => {
1120
- queue.push(event);
1121
- });
1122
- const unsubscribeClose = execution.publisher.onClose(() => {
1123
- queue.close();
1124
- });
1125
- const stop = () => {
1126
- unsubscribe();
1127
- unsubscribeClose();
1128
- queue.close();
1129
- signal.removeEventListener("abort", stop);
1130
- };
1131
- signal.addEventListener("abort", stop, { once: true });
1132
- try {
1133
- for await (const event of queue.iterable) {
1134
- if (signal.aborted) {
1135
- break;
1136
- }
1137
- yield event;
1138
- if (isTerminalEvent(event)) {
1139
- break;
1140
- }
1141
- }
1142
- } finally {
1143
- stop();
1144
- }
1145
- }
1146
-
1147
- // src/agent/agent-backend/event-publisher.ts
1148
- var EventPublisher = class {
1149
- listeners = /* @__PURE__ */ new Set();
1150
- closeListeners = /* @__PURE__ */ new Set();
1151
- closed = false;
1152
- subscribe(listener) {
1153
- if (this.closed) {
1154
- return () => void 0;
1155
- }
1156
- this.listeners.add(listener);
1157
- return () => {
1158
- this.listeners.delete(listener);
1159
- };
1160
- }
1161
- onClose(listener) {
1162
- if (this.closed) {
1163
- listener();
1164
- return () => void 0;
1165
- }
1166
- this.closeListeners.add(listener);
1167
- return () => {
1168
- this.closeListeners.delete(listener);
1169
- };
1170
- }
1171
- publish(event) {
1172
- if (this.closed) {
1173
- return;
1174
- }
1175
- for (const listener of this.listeners) {
1176
- listener(structuredClone(event));
1177
- }
1178
- }
1179
- close() {
1180
- if (this.closed) {
1181
- return;
1182
- }
1183
- this.closed = true;
1184
- this.listeners.clear();
1185
- for (const listener of this.closeListeners) {
1186
- listener();
1187
- }
1188
- this.closeListeners.clear();
1189
- }
1190
- };
1191
-
1192
1218
  // src/agent/agent-backend/agent-backend.ts
1193
- var DEFAULT_SUPPORTED_PART_TYPES = ["text", "file", "source", "step-start", "reasoning", "tool-invocation", "card", "rich-text", "action", "extension"];
1219
+ var DEFAULT_SUPPORTED_PART_TYPES = [
1220
+ "text",
1221
+ "file",
1222
+ "source",
1223
+ "step-start",
1224
+ "reasoning",
1225
+ "tool-invocation",
1226
+ "card",
1227
+ "rich-text",
1228
+ "action",
1229
+ "extension"
1230
+ ];
1194
1231
  var DefaultNcpAgentBackend = class {
1195
1232
  manifest;
1196
1233
  sessionStore;
@@ -1198,13 +1235,24 @@ var DefaultNcpAgentBackend = class {
1198
1235
  sessionRegistry;
1199
1236
  executor;
1200
1237
  publisher;
1238
+ sessionRealtime;
1201
1239
  started = false;
1202
1240
  constructor(config) {
1203
1241
  this.sessionStore = config.sessionStore;
1204
1242
  this.onSessionRunStatusChanged = config.onSessionRunStatusChanged;
1205
- this.sessionRegistry = new AgentLiveSessionRegistry(this.sessionStore, config.createRuntime);
1206
- this.executor = new AgentRunExecutor(async (sessionId) => this.persistSession(sessionId));
1243
+ this.sessionRegistry = new AgentLiveSessionRegistry(
1244
+ this.sessionStore,
1245
+ config.createRuntime
1246
+ );
1247
+ this.executor = new AgentRunExecutor();
1207
1248
  this.publisher = new EventPublisher();
1249
+ this.sessionRealtime = new AgentBackendSessionRealtime({
1250
+ sessionRegistry: this.sessionRegistry,
1251
+ sessionStore: this.sessionStore,
1252
+ publishEndpointEvent: (event) => this.publishEndpointEvent(event),
1253
+ persistSession: (sessionId) => this.persistSession(sessionId),
1254
+ getSessionSummary: (sessionId) => this.getSession(sessionId)
1255
+ });
1208
1256
  this.manifest = {
1209
1257
  endpointKind: "agent",
1210
1258
  endpointId: config.endpointId?.trim() || "ncp-agent-backend",
@@ -1223,7 +1271,7 @@ var DefaultNcpAgentBackend = class {
1223
1271
  return;
1224
1272
  }
1225
1273
  this.started = true;
1226
- this.publisher.publish({ type: NcpEventType8.EndpointReady });
1274
+ this.publisher.publish({ type: NcpEventType6.EndpointReady });
1227
1275
  };
1228
1276
  stop = async () => {
1229
1277
  if (!this.started) {
@@ -1233,24 +1281,28 @@ var DefaultNcpAgentBackend = class {
1233
1281
  for (const session of this.sessionRegistry.listSessions()) {
1234
1282
  const execution = session.activeExecution;
1235
1283
  if (!execution) {
1284
+ session.publisher.close();
1236
1285
  continue;
1237
1286
  }
1238
1287
  execution.abortHandled = true;
1239
1288
  execution.controller.abort();
1240
1289
  this.finishSessionExecution(session, execution);
1290
+ session.publisher.close();
1241
1291
  }
1242
1292
  this.sessionRegistry.clear();
1243
1293
  };
1244
1294
  emit = async (event) => {
1245
1295
  await this.ensureStarted();
1246
1296
  switch (event.type) {
1247
- case NcpEventType8.MessageRequest:
1248
- await this.handleRequest(event.payload);
1297
+ case NcpEventType6.MessageRequest:
1298
+ for await (const emittedEvent of this.send(event.payload)) {
1299
+ void emittedEvent;
1300
+ }
1249
1301
  return;
1250
- case NcpEventType8.MessageStreamRequest:
1251
- await this.streamToSubscribers(event.payload);
1302
+ case NcpEventType6.MessageStreamRequest:
1303
+ await this.ensureStarted();
1252
1304
  return;
1253
- case NcpEventType8.MessageAbort:
1305
+ case NcpEventType6.MessageAbort:
1254
1306
  await this.handleAbort(event.payload);
1255
1307
  return;
1256
1308
  default:
@@ -1278,13 +1330,20 @@ var DefaultNcpAgentBackend = class {
1278
1330
  envelope,
1279
1331
  execution.controller
1280
1332
  )) {
1281
- self.publishLiveEvent(execution, event);
1333
+ await self.sessionRealtime.publishSessionEvent(session, event);
1282
1334
  yield event;
1283
1335
  }
1284
1336
  if (execution.controller.signal.aborted && !execution.abortHandled) {
1285
- const abortEvent = await self.createAbortEvent(session.sessionId);
1337
+ const abortEvent = {
1338
+ type: NcpEventType6.MessageAbort,
1339
+ payload: {
1340
+ sessionId: session.sessionId
1341
+ }
1342
+ };
1286
1343
  execution.abortHandled = true;
1287
- self.publishLiveEvent(execution, abortEvent);
1344
+ await self.sessionRealtime.publishSessionEvent(session, abortEvent, {
1345
+ dispatchToStateManager: true
1346
+ });
1288
1347
  yield abortEvent;
1289
1348
  }
1290
1349
  } finally {
@@ -1296,15 +1355,14 @@ var DefaultNcpAgentBackend = class {
1296
1355
  abort = async (payload) => {
1297
1356
  await this.handleAbort(payload);
1298
1357
  };
1299
- stream = (payloadOrParams, opts) => streamAgentBackendExecution({
1300
- payloadOrParams,
1301
- opts,
1302
- sessionRegistry: this.sessionRegistry
1303
- });
1358
+ stream = (payloadOrParams, opts) => this.sessionRealtime.streamSessionEvents(payloadOrParams, opts);
1304
1359
  listSessions = async () => {
1305
1360
  const storedSessions = await this.sessionStore.listSessions();
1306
1361
  const summaries = storedSessions.map(
1307
- (session) => toSessionSummary(session, this.sessionRegistry.getSession(session.sessionId))
1362
+ (session) => toSessionSummary(
1363
+ session,
1364
+ this.sessionRegistry.getSession(session.sessionId)
1365
+ )
1308
1366
  );
1309
1367
  for (const liveSession of this.sessionRegistry.listSessions()) {
1310
1368
  if (summaries.some((session) => session.sessionId === liveSession.sessionId)) {
@@ -1312,11 +1370,14 @@ var DefaultNcpAgentBackend = class {
1312
1370
  }
1313
1371
  summaries.push(toLiveSessionSummary(liveSession));
1314
1372
  }
1315
- return summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1373
+ return summaries.sort(
1374
+ (left, right) => right.updatedAt.localeCompare(left.updatedAt)
1375
+ );
1316
1376
  };
1317
1377
  listSessionMessages = async (sessionId) => {
1318
1378
  const liveSession = this.sessionRegistry.getSession(sessionId);
1319
- if (liveSession) return readMessages(liveSession.stateManager.getSnapshot());
1379
+ if (liveSession)
1380
+ return readMessages(liveSession.stateManager.getSnapshot());
1320
1381
  const session = await this.sessionStore.getSession(sessionId);
1321
1382
  return session ? session.messages.map((message) => structuredClone(message)) : [];
1322
1383
  };
@@ -1327,39 +1388,29 @@ var DefaultNcpAgentBackend = class {
1327
1388
  };
1328
1389
  appendMessage = async (sessionId, message) => {
1329
1390
  await this.ensureStarted();
1330
- return appendAgentBackendMessage({
1331
- sessionId,
1332
- message,
1333
- sessionRegistry: this.sessionRegistry,
1334
- sessionStore: this.sessionStore,
1335
- publisher: this.publisher,
1336
- persistSession: async (nextSessionId) => this.persistSession(nextSessionId),
1337
- getSession: async (nextSessionId) => this.getSession(nextSessionId)
1338
- });
1391
+ return this.sessionRealtime.appendMessage(sessionId, message);
1339
1392
  };
1340
1393
  updateToolCallResult = async (sessionId, toolCallId, content) => {
1341
1394
  await this.ensureStarted();
1342
- return updateAgentBackendToolCallResult({
1395
+ return this.sessionRealtime.updateToolCallResult(
1343
1396
  sessionId,
1344
1397
  toolCallId,
1345
- content,
1346
- sessionRegistry: this.sessionRegistry,
1347
- publisher: this.publisher,
1348
- persistSession: async (nextSessionId) => this.persistSession(nextSessionId),
1349
- getSession: async (nextSessionId) => this.getSession(nextSessionId)
1350
- });
1398
+ content
1399
+ );
1351
1400
  };
1352
1401
  updateSession = async (sessionId, patch) => {
1353
1402
  const liveSession = this.sessionRegistry.getSession(sessionId);
1354
1403
  const storedSession = await this.sessionStore.getSession(sessionId);
1355
1404
  if (!liveSession && !storedSession) return null;
1356
- await this.sessionStore.saveSession(buildUpdatedSessionRecord({
1357
- sessionId,
1358
- patch,
1359
- liveSession,
1360
- storedSession,
1361
- updatedAt: now()
1362
- }));
1405
+ await this.sessionStore.replaceSession(
1406
+ buildUpdatedSessionRecord({
1407
+ sessionId,
1408
+ patch,
1409
+ liveSession,
1410
+ storedSession,
1411
+ updatedAt: now()
1412
+ })
1413
+ );
1363
1414
  return this.getSession(sessionId);
1364
1415
  };
1365
1416
  deleteSession = async (sessionId) => {
@@ -1368,8 +1419,9 @@ var DefaultNcpAgentBackend = class {
1368
1419
  if (execution) {
1369
1420
  execution.abortHandled = true;
1370
1421
  execution.controller.abort();
1371
- this.closeExecution(execution);
1422
+ closeAgentBackendSessionExecution(execution);
1372
1423
  }
1424
+ liveSession?.publisher.close();
1373
1425
  await this.sessionStore.deleteSession(sessionId);
1374
1426
  };
1375
1427
  ensureStarted = async () => {
@@ -1377,61 +1429,19 @@ var DefaultNcpAgentBackend = class {
1377
1429
  await this.start();
1378
1430
  }
1379
1431
  };
1380
- startSessionExecution = (session, envelope, signal) => {
1381
- if (session.activeExecution && !session.activeExecution.closed) {
1382
- throw new NcpErrorException(
1383
- "runtime-error",
1384
- `Session ${session.sessionId} already has an active execution.`,
1385
- { sessionId: session.sessionId }
1386
- );
1387
- }
1388
- const controller = new AbortController();
1389
- if (signal) {
1390
- signal.addEventListener("abort", () => controller.abort(), {
1391
- once: true
1392
- });
1393
- }
1394
- const execution = {
1395
- controller,
1396
- publisher: new EventPublisher(),
1397
- requestEnvelope: structuredClone(envelope),
1398
- abortHandled: false,
1399
- closed: false
1400
- };
1401
- session.activeExecution = execution;
1402
- this.onSessionRunStatusChanged?.({ sessionKey: session.sessionId, status: "running" });
1403
- return execution;
1404
- };
1405
- finishSessionExecution = (session, execution) => {
1406
- if (session.activeExecution === execution) {
1407
- session.activeExecution = null;
1408
- this.onSessionRunStatusChanged?.({ sessionKey: session.sessionId, status: "idle" });
1409
- }
1410
- this.closeExecution(execution);
1411
- };
1412
- closeExecution = (execution) => {
1413
- if (execution.closed) {
1414
- return;
1415
- }
1416
- execution.closed = true;
1417
- execution.publisher.close();
1418
- };
1419
- publishLiveEvent = (execution, event) => {
1432
+ startSessionExecution = (session, envelope, signal) => startAgentBackendSessionExecution({
1433
+ session,
1434
+ envelope,
1435
+ signal,
1436
+ onStatusChanged: this.onSessionRunStatusChanged
1437
+ });
1438
+ finishSessionExecution = (session, execution) => finishAgentBackendSessionExecution({
1439
+ session,
1440
+ execution,
1441
+ onStatusChanged: this.onSessionRunStatusChanged
1442
+ });
1443
+ publishEndpointEvent = (event) => {
1420
1444
  this.publisher.publish(event);
1421
- if (!execution.closed) {
1422
- execution.publisher.publish(event);
1423
- }
1424
- };
1425
- handleRequest = async (envelope) => {
1426
- for await (const event of this.send(envelope)) {
1427
- void event;
1428
- }
1429
- };
1430
- streamToSubscribers = async (payload) => {
1431
- const signal = new AbortController().signal;
1432
- for await (const event of this.stream({ payload, signal })) {
1433
- void event;
1434
- }
1435
1445
  };
1436
1446
  handleAbort = async (payload) => {
1437
1447
  const session = this.sessionRegistry.getSession(payload.sessionId);
@@ -1441,57 +1451,55 @@ var DefaultNcpAgentBackend = class {
1441
1451
  }
1442
1452
  execution.abortHandled = true;
1443
1453
  execution.controller.abort();
1444
- const abortEvent = await this.createAbortEvent(payload.sessionId, payload.messageId);
1445
- this.publishLiveEvent(execution, abortEvent);
1446
- this.finishSessionExecution(session, execution);
1447
- };
1448
- createAbortEvent = async (sessionId, messageId) => {
1449
1454
  const abortEvent = {
1450
- type: NcpEventType8.MessageAbort,
1455
+ type: NcpEventType6.MessageAbort,
1451
1456
  payload: {
1452
- sessionId,
1453
- ...messageId ? { messageId } : {}
1457
+ sessionId: payload.sessionId,
1458
+ ...payload.messageId ? { messageId: payload.messageId } : {}
1454
1459
  }
1455
1460
  };
1456
- const liveSession = this.sessionRegistry.getSession(sessionId);
1457
- if (liveSession) {
1458
- await liveSession.stateManager.dispatch(abortEvent);
1459
- }
1460
- await this.persistSession(sessionId);
1461
- return abortEvent;
1461
+ await this.sessionRealtime.publishSessionEvent(session, abortEvent, {
1462
+ dispatchToStateManager: true
1463
+ });
1464
+ this.finishSessionExecution(session, execution);
1462
1465
  };
1463
1466
  persistSession = async (sessionId) => {
1464
1467
  const session = this.sessionRegistry.getSession(sessionId);
1465
1468
  if (!session) return;
1466
- await this.sessionStore.saveSession(buildPersistedLiveSessionRecord({
1467
- sessionId,
1468
- session,
1469
- updatedAt: now()
1470
- }));
1469
+ await this.sessionStore.saveSession(
1470
+ buildPersistedLiveSessionRecord({
1471
+ sessionId,
1472
+ session,
1473
+ updatedAt: now()
1474
+ })
1475
+ );
1471
1476
  };
1472
1477
  };
1473
1478
 
1474
1479
  // src/agent/agent-backend/in-memory-agent-session-store.ts
1475
1480
  var InMemoryAgentSessionStore = class {
1476
1481
  sessions = /* @__PURE__ */ new Map();
1477
- async getSession(sessionId) {
1482
+ getSession = async (sessionId) => {
1478
1483
  const session = this.sessions.get(sessionId);
1479
1484
  return session ? structuredClone(session) : null;
1480
- }
1481
- async listSessions() {
1485
+ };
1486
+ listSessions = async () => {
1482
1487
  return [...this.sessions.values()].map((session) => structuredClone(session));
1483
- }
1484
- async saveSession(session) {
1488
+ };
1489
+ saveSession = async (session) => {
1485
1490
  this.sessions.set(session.sessionId, structuredClone(session));
1486
- }
1487
- async deleteSession(sessionId) {
1491
+ };
1492
+ replaceSession = async (session) => {
1493
+ this.sessions.set(session.sessionId, structuredClone(session));
1494
+ };
1495
+ deleteSession = async (sessionId) => {
1488
1496
  const session = this.sessions.get(sessionId);
1489
1497
  if (!session) {
1490
1498
  return null;
1491
1499
  }
1492
1500
  this.sessions.delete(sessionId);
1493
1501
  return structuredClone(session);
1494
- }
1502
+ };
1495
1503
  };
1496
1504
  export {
1497
1505
  AgentRunExecutor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",