@nextclaw/ncp-toolkit 0.1.1 → 0.3.0

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
@@ -5,10 +5,14 @@ Toolkit implementations built on top of `@nextclaw/ncp` protocol contracts.
5
5
  ## Build
6
6
 
7
7
  ```bash
8
- pnpm -C packages/nextclaw-ncp-toolkit build
8
+ pnpm -C packages/ncp-packages/nextclaw-ncp-toolkit build
9
9
  ```
10
10
 
11
11
  ## Scope
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
+ - Default in-memory adapter: `InMemoryAgentSessionStore`
17
+ - In-process adapter helper: `createAgentClientFromServer`
18
+ - Runtime throwable helper: `NcpErrorException`
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpAgentConversationStateManager, NcpAgentConversationSnapshot, NcpEndpointEvent, NcpRequestEnvelope, NcpMessageSentPayload, NcpMessageAcceptedPayload, NcpResponseEnvelope, NcpCompletedEnvelope, NcpFailedEnvelope, NcpMessageAbortPayload, NcpTextStartPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpReasoningStartPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpToolCallStartPayload, NcpToolCallArgsPayload, NcpToolCallArgsDeltaPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpRunStartedPayload, NcpRunFinishedPayload, NcpRunErrorPayload, NcpRunMetadataPayload, NcpError } from '@nextclaw/ncp';
1
+ import { NcpAgentConversationStateManager, NcpAgentConversationSnapshot, NcpAgentConversationHydrationParams, NcpEndpointEvent, NcpMessageSentPayload, NcpMessageAbortPayload, NcpTextStartPayload, NcpTextDeltaPayload, NcpTextEndPayload, NcpReasoningStartPayload, NcpReasoningDeltaPayload, NcpReasoningEndPayload, NcpToolCallStartPayload, NcpToolCallArgsPayload, NcpToolCallArgsDeltaPayload, NcpToolCallEndPayload, NcpToolCallResultPayload, NcpRunStartedPayload, NcpRunFinishedPayload, NcpRunErrorPayload, NcpRunMetadataPayload, NcpError, NcpAgentServerEndpoint, NcpAgentClientEndpoint, NcpEndpointSubscriber, NcpAgentRuntime, NcpRequestEnvelope, NcpMessage, NcpSessionApi, NcpAgentStreamProvider, NcpAgentRunApi, NcpEndpointManifest, NcpAgentRunSendOptions, NcpStreamRequestPayload, NcpAgentRunStreamOptions, NcpSessionSummary, NcpErrorCode } from '@nextclaw/ncp';
2
2
 
3
3
  declare class DefaultNcpAgentConversationStateManager implements NcpAgentConversationStateManager {
4
4
  private messages;
@@ -8,16 +8,15 @@ declare class DefaultNcpAgentConversationStateManager implements NcpAgentConvers
8
8
  private readonly listeners;
9
9
  private readonly toolCallMessageIdByCallId;
10
10
  private readonly toolCallArgsRawByCallId;
11
+ private snapshotCache;
12
+ private snapshotVersion;
11
13
  private stateVersion;
12
14
  getSnapshot(): NcpAgentConversationSnapshot;
13
15
  subscribe(listener: (snapshot: NcpAgentConversationSnapshot) => void): () => boolean;
16
+ reset(): void;
17
+ hydrate(payload: NcpAgentConversationHydrationParams): void;
14
18
  dispatch(event: NcpEndpointEvent): Promise<void>;
15
- handleMessageRequest(payload: NcpRequestEnvelope): void;
16
19
  handleMessageSent(payload: NcpMessageSentPayload): void;
17
- handleMessageAccepted(_payload: NcpMessageAcceptedPayload): void;
18
- handleMessageIncoming(payload: NcpResponseEnvelope): void;
19
- handleMessageCompleted(payload: NcpCompletedEnvelope): void;
20
- handleMessageFailed(payload: NcpFailedEnvelope): void;
21
20
  handleMessageAbort(payload: NcpMessageAbortPayload): void;
22
21
  handleMessageTextStart(payload: NcpTextStartPayload): void;
23
22
  handleMessageTextDelta(payload: NcpTextDeltaPayload): void;
@@ -42,11 +41,128 @@ declare class DefaultNcpAgentConversationStateManager implements NcpAgentConvers
42
41
  private findToolInvocationPart;
43
42
  private findToolNameByCallId;
44
43
  private upsertToolInvocationPart;
45
- private findLastReasoningPartIndex;
46
44
  private upsertMessage;
47
45
  private replaceStreamingMessage;
48
46
  private setError;
47
+ private clearActiveRun;
49
48
  private clearToolCallTrackingByMessageId;
49
+ private notifyListeners;
50
50
  }
51
51
 
52
- export { DefaultNcpAgentConversationStateManager };
52
+ /**
53
+ * Creates an NcpAgentClientEndpoint that forwards to an in-process NcpAgentServerEndpoint.
54
+ * Use when the agent runs in-process and you need to pass a client endpoint to the HTTP server.
55
+ */
56
+ declare function createAgentClientFromServer(server: NcpAgentServerEndpoint): NcpAgentClientEndpoint;
57
+
58
+ declare class EventPublisher {
59
+ private readonly listeners;
60
+ private readonly closeListeners;
61
+ private closed;
62
+ subscribe(listener: NcpEndpointSubscriber): () => void;
63
+ onClose(listener: () => void): () => void;
64
+ publish(event: NcpEndpointEvent): void;
65
+ close(): void;
66
+ }
67
+
68
+ type RuntimeFactoryParams = {
69
+ sessionId: string;
70
+ stateManager: NcpAgentConversationStateManager;
71
+ };
72
+ type CreateRuntimeFn = (params: RuntimeFactoryParams) => NcpAgentRuntime;
73
+ type AgentSessionRecord = {
74
+ sessionId: string;
75
+ messages: NcpMessage[];
76
+ updatedAt: string;
77
+ metadata?: Record<string, unknown>;
78
+ };
79
+ type LiveSessionExecution = {
80
+ controller: AbortController;
81
+ publisher: EventPublisher;
82
+ requestEnvelope: NcpRequestEnvelope;
83
+ abortHandled: boolean;
84
+ closed: boolean;
85
+ };
86
+ type LiveSessionState = {
87
+ sessionId: string;
88
+ runtime: NcpAgentRuntime;
89
+ stateManager: NcpAgentConversationStateManager;
90
+ activeExecution: LiveSessionExecution | null;
91
+ };
92
+ interface AgentSessionStore {
93
+ getSession(sessionId: string): Promise<AgentSessionRecord | null>;
94
+ listSessions(): Promise<AgentSessionRecord[]>;
95
+ saveSession(session: AgentSessionRecord): Promise<void>;
96
+ deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
97
+ }
98
+
99
+ type DefaultNcpAgentBackendConfig = {
100
+ createRuntime: CreateRuntimeFn;
101
+ sessionStore: AgentSessionStore;
102
+ endpointId?: string;
103
+ version?: string;
104
+ metadata?: Record<string, unknown>;
105
+ supportedPartTypes?: NcpEndpointManifest["supportedPartTypes"];
106
+ expectedLatency?: NcpEndpointManifest["expectedLatency"];
107
+ };
108
+ declare class DefaultNcpAgentBackend implements NcpAgentServerEndpoint, NcpSessionApi, NcpAgentStreamProvider, NcpAgentRunApi {
109
+ readonly manifest: NcpEndpointManifest & {
110
+ endpointKind: "agent";
111
+ };
112
+ private readonly sessionStore;
113
+ private readonly sessionRegistry;
114
+ private readonly executor;
115
+ private readonly publisher;
116
+ private started;
117
+ constructor(config: DefaultNcpAgentBackendConfig);
118
+ start(): Promise<void>;
119
+ stop(): Promise<void>;
120
+ emit(event: NcpEndpointEvent): Promise<void>;
121
+ subscribe(listener: (event: NcpEndpointEvent) => void): () => void;
122
+ send(envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions): AsyncIterable<NcpEndpointEvent>;
123
+ abort(payload: NcpMessageAbortPayload): Promise<void>;
124
+ stream(payloadOrParams: NcpStreamRequestPayload | {
125
+ payload: NcpStreamRequestPayload;
126
+ signal: AbortSignal;
127
+ }, opts?: NcpAgentRunStreamOptions): AsyncIterable<NcpEndpointEvent>;
128
+ listSessions(): Promise<NcpSessionSummary[]>;
129
+ listSessionMessages(sessionId: string): Promise<NcpMessage[]>;
130
+ getSession(sessionId: string): Promise<NcpSessionSummary | null>;
131
+ deleteSession(sessionId: string): Promise<void>;
132
+ private ensureStarted;
133
+ private startSessionExecution;
134
+ private finishSessionExecution;
135
+ private closeExecution;
136
+ private publishLiveEvent;
137
+ private handleRequest;
138
+ private streamToSubscribers;
139
+ private handleAbort;
140
+ private createAbortEvent;
141
+ private persistSession;
142
+ }
143
+
144
+ declare class AgentRunExecutor {
145
+ private readonly persistSession;
146
+ constructor(persistSession: (sessionId: string) => Promise<void>);
147
+ executeRun(session: LiveSessionState, envelope: NcpRequestEnvelope, controller: AbortController): AsyncGenerator<NcpEndpointEvent>;
148
+ private publishFailure;
149
+ }
150
+
151
+ declare class InMemoryAgentSessionStore implements AgentSessionStore {
152
+ private readonly sessions;
153
+ getSession(sessionId: string): Promise<AgentSessionRecord | null>;
154
+ listSessions(): Promise<AgentSessionRecord[]>;
155
+ saveSession(session: AgentSessionRecord): Promise<void>;
156
+ deleteSession(sessionId: string): Promise<AgentSessionRecord | null>;
157
+ }
158
+
159
+ /**
160
+ * Throwable form of protocol error for exception-based control flows.
161
+ */
162
+ declare class NcpErrorException extends Error {
163
+ readonly code: NcpErrorCode;
164
+ readonly details?: Record<string, unknown>;
165
+ constructor(code: NcpErrorCode, message: string, details?: Record<string, unknown>);
166
+ }
167
+
168
+ export { AgentRunExecutor, type AgentSessionRecord, type AgentSessionStore, type CreateRuntimeFn, DefaultNcpAgentBackend, type DefaultNcpAgentBackendConfig, DefaultNcpAgentConversationStateManager, EventPublisher, InMemoryAgentSessionStore, type LiveSessionExecution, type LiveSessionState, NcpErrorException, type RuntimeFactoryParams, createAgentClientFromServer };
package/dist/index.js CHANGED
@@ -1,12 +1,23 @@
1
1
  // src/agent/agent-conversation-state-manager.ts
2
- var DEFAULT_ASSISTANT_ROLE = "assistant";
3
- var cloneMessage = (message) => {
2
+ import {
3
+ NcpEventType
4
+ } from "@nextclaw/ncp";
5
+
6
+ // src/agent/agent-conversation-message-normalizer.ts
7
+ import { sanitizeAssistantReplyTags } from "@nextclaw/ncp";
8
+ function cloneConversationMessage(message) {
4
9
  return {
5
10
  ...message,
6
11
  parts: [...message.parts],
7
12
  metadata: message.metadata ? { ...message.metadata } : void 0
8
13
  };
9
- };
14
+ }
15
+ function normalizeConversationMessage(message) {
16
+ return cloneConversationMessage(sanitizeAssistantReplyTags(message));
17
+ }
18
+
19
+ // src/agent/agent-conversation-state-manager.ts
20
+ var DEFAULT_ASSISTANT_ROLE = "assistant";
10
21
  var buildRuntimeError = (payload) => {
11
22
  const message = payload.error?.trim();
12
23
  return {
@@ -28,193 +39,137 @@ var DefaultNcpAgentConversationStateManager = class {
28
39
  listeners = /* @__PURE__ */ new Set();
29
40
  toolCallMessageIdByCallId = /* @__PURE__ */ new Map();
30
41
  toolCallArgsRawByCallId = /* @__PURE__ */ new Map();
42
+ snapshotCache = null;
43
+ snapshotVersion = -1;
31
44
  stateVersion = 0;
32
45
  getSnapshot() {
33
- return {
34
- messages: this.messages.map((message) => cloneMessage(message)),
35
- streamingMessage: this.streamingMessage ? cloneMessage(this.streamingMessage) : null,
46
+ if (this.snapshotCache && this.snapshotVersion === this.stateVersion) {
47
+ return this.snapshotCache;
48
+ }
49
+ const snapshot = {
50
+ messages: this.messages.map((message) => cloneConversationMessage(message)),
51
+ streamingMessage: this.streamingMessage ? cloneConversationMessage(this.streamingMessage) : null,
36
52
  error: this.error ? { ...this.error, details: this.error.details ? { ...this.error.details } : void 0 } : null,
37
53
  activeRun: this.activeRun ? { ...this.activeRun } : null
38
54
  };
55
+ this.snapshotCache = snapshot;
56
+ this.snapshotVersion = this.stateVersion;
57
+ return snapshot;
39
58
  }
40
59
  subscribe(listener) {
41
60
  this.listeners.add(listener);
42
61
  return () => this.listeners.delete(listener);
43
62
  }
63
+ reset() {
64
+ if (this.messages.length === 0 && !this.streamingMessage && !this.error && !this.activeRun && this.toolCallMessageIdByCallId.size === 0 && this.toolCallArgsRawByCallId.size === 0) {
65
+ return;
66
+ }
67
+ this.messages = [];
68
+ this.streamingMessage = null;
69
+ this.error = null;
70
+ this.activeRun = null;
71
+ this.toolCallMessageIdByCallId.clear();
72
+ this.toolCallArgsRawByCallId.clear();
73
+ this.stateVersion += 1;
74
+ this.notifyListeners();
75
+ }
76
+ hydrate(payload) {
77
+ this.messages = payload.messages.map((message) => normalizeConversationMessage(message));
78
+ this.streamingMessage = null;
79
+ this.error = null;
80
+ this.activeRun = payload.activeRun ? {
81
+ ...payload.activeRun,
82
+ sessionId: payload.activeRun.sessionId ?? payload.sessionId,
83
+ abortDisabledReason: payload.activeRun.abortDisabledReason ?? null
84
+ } : null;
85
+ this.toolCallMessageIdByCallId.clear();
86
+ this.toolCallArgsRawByCallId.clear();
87
+ this.stateVersion += 1;
88
+ this.notifyListeners();
89
+ }
44
90
  async dispatch(event) {
45
91
  const versionBeforeDispatch = this.stateVersion;
46
92
  switch (event.type) {
47
- case "message.request":
48
- this.handleMessageRequest(event.payload);
49
- break;
50
- case "message.resume-request":
51
- break;
52
- case "message.sent":
93
+ case NcpEventType.MessageSent:
53
94
  this.handleMessageSent(event.payload);
54
95
  break;
55
- case "message.accepted":
56
- this.handleMessageAccepted(event.payload);
57
- break;
58
- case "message.incoming":
59
- this.handleMessageIncoming(event.payload);
60
- break;
61
- case "message.completed":
62
- this.handleMessageCompleted(event.payload);
63
- break;
64
- case "message.failed":
65
- this.handleMessageFailed(event.payload);
66
- break;
67
- case "message.abort":
96
+ case NcpEventType.MessageAbort:
68
97
  this.handleMessageAbort(event.payload);
69
98
  break;
70
- case "message.text-start":
99
+ case NcpEventType.MessageTextStart:
71
100
  this.handleMessageTextStart(event.payload);
72
101
  break;
73
- case "message.text-delta":
102
+ case NcpEventType.MessageTextDelta:
74
103
  this.handleMessageTextDelta(event.payload);
75
104
  break;
76
- case "message.text-end":
105
+ case NcpEventType.MessageTextEnd:
77
106
  this.handleMessageTextEnd(event.payload);
78
107
  break;
79
- case "message.reasoning-start":
108
+ case NcpEventType.MessageReasoningStart:
80
109
  this.handleMessageReasoningStart(event.payload);
81
110
  break;
82
- case "message.reasoning-delta":
111
+ case NcpEventType.MessageReasoningDelta:
83
112
  this.handleMessageReasoningDelta(event.payload);
84
113
  break;
85
- case "message.reasoning-end":
114
+ case NcpEventType.MessageReasoningEnd:
86
115
  this.handleMessageReasoningEnd(event.payload);
87
116
  break;
88
- case "message.tool-call-start":
117
+ case NcpEventType.MessageToolCallStart:
89
118
  this.handleMessageToolCallStart(event.payload);
90
119
  break;
91
- case "message.tool-call-args":
120
+ case NcpEventType.MessageToolCallArgs:
92
121
  this.handleMessageToolCallArgs(event.payload);
93
122
  break;
94
- case "message.tool-call-args-delta":
123
+ case NcpEventType.MessageToolCallArgsDelta:
95
124
  this.handleMessageToolCallArgsDelta(event.payload);
96
125
  break;
97
- case "message.tool-call-end":
126
+ case NcpEventType.MessageToolCallEnd:
98
127
  this.handleMessageToolCallEnd(event.payload);
99
128
  break;
100
- case "message.tool-call-result":
129
+ case NcpEventType.MessageToolCallResult:
101
130
  this.handleMessageToolCallResult(event.payload);
102
131
  break;
103
- case "run.started":
132
+ case NcpEventType.RunStarted:
104
133
  this.handleRunStarted(event.payload);
105
134
  break;
106
- case "run.finished":
135
+ case NcpEventType.RunFinished:
107
136
  this.handleRunFinished(event.payload);
108
137
  break;
109
- case "run.error":
138
+ case NcpEventType.RunError:
110
139
  this.handleRunError(event.payload);
111
140
  break;
112
- case "run.metadata":
141
+ case NcpEventType.RunMetadata:
113
142
  this.handleRunMetadata(event.payload);
114
143
  break;
115
- case "endpoint.error":
144
+ case NcpEventType.EndpointError:
116
145
  this.handleEndpointError(event.payload);
117
146
  break;
118
- case "endpoint.ready":
119
- case "typing.start":
120
- case "typing.end":
121
- case "presence.updated":
122
- case "message.read":
123
- case "message.delivered":
124
- case "message.recalled":
125
- case "message.reaction":
126
- break;
127
147
  default:
128
148
  break;
129
149
  }
130
150
  if (this.stateVersion !== versionBeforeDispatch) {
131
- const snapshot = this.getSnapshot();
132
- for (const listener of this.listeners) {
133
- listener(snapshot);
134
- }
151
+ this.notifyListeners();
135
152
  }
136
153
  }
137
- handleMessageRequest(payload) {
138
- this.upsertMessage(payload.message);
139
- this.setError(null);
140
- }
141
154
  handleMessageSent(payload) {
142
155
  this.upsertMessage(payload.message);
143
156
  this.setError(null);
144
157
  }
145
- handleMessageAccepted(_payload) {
146
- }
147
- handleMessageIncoming(payload) {
148
- const incomingMessage = cloneMessage(payload.message);
149
- if (incomingMessage.status === "streaming" || incomingMessage.status === "pending") {
150
- this.replaceStreamingMessage(incomingMessage);
151
- this.setError(null);
152
- return;
153
- }
154
- this.upsertMessage(incomingMessage);
155
- if (this.streamingMessage?.id === incomingMessage.id) {
156
- this.replaceStreamingMessage(null);
157
- this.clearToolCallTrackingByMessageId(incomingMessage.id);
158
- }
159
- this.setError(null);
160
- }
161
- handleMessageCompleted(payload) {
162
- const completedMessage = {
163
- ...cloneMessage(payload.message),
164
- status: "final"
165
- };
166
- this.upsertMessage(completedMessage);
167
- if (this.streamingMessage?.id === completedMessage.id) {
168
- this.replaceStreamingMessage(null);
169
- }
170
- this.clearToolCallTrackingByMessageId(completedMessage.id);
171
- this.setError(null);
172
- }
173
- handleMessageFailed(payload) {
174
- this.setError(payload.error);
175
- const targetMessageId = payload.messageId?.trim();
176
- if (!targetMessageId) {
177
- return;
178
- }
179
- if (this.streamingMessage?.id === targetMessageId) {
180
- this.upsertMessage({
181
- ...this.streamingMessage,
182
- status: "error"
183
- });
184
- this.replaceStreamingMessage(null);
185
- this.clearToolCallTrackingByMessageId(targetMessageId);
186
- return;
187
- }
188
- const messageIndex = this.messages.findIndex((message) => message.id === targetMessageId);
189
- if (messageIndex < 0) {
190
- return;
191
- }
192
- const nextMessages = [...this.messages];
193
- nextMessages[messageIndex] = {
194
- ...nextMessages[messageIndex],
195
- status: "error"
196
- };
197
- this.messages = nextMessages;
198
- this.stateVersion += 1;
199
- }
200
158
  handleMessageAbort(payload) {
201
159
  const targetMessageId = payload.messageId?.trim();
202
- this.setError({
203
- code: "abort-error",
204
- message: "Message aborted.",
205
- details: {
206
- messageId: targetMessageId,
207
- correlationId: payload.correlationId
208
- }
209
- });
160
+ this.clearActiveRun();
161
+ this.setError(null);
210
162
  if (this.streamingMessage && (!targetMessageId || this.streamingMessage.id === targetMessageId)) {
163
+ const streamingMessageId = this.streamingMessage.id;
211
164
  this.upsertMessage({
212
165
  ...this.streamingMessage,
213
- status: "error"
166
+ status: "final"
214
167
  });
215
168
  this.replaceStreamingMessage(null);
216
169
  if (targetMessageId) {
217
170
  this.clearToolCallTrackingByMessageId(targetMessageId);
171
+ } else {
172
+ this.clearToolCallTrackingByMessageId(streamingMessageId);
218
173
  }
219
174
  }
220
175
  }
@@ -264,15 +219,12 @@ var DefaultNcpAgentConversationStateManager = class {
264
219
  }
265
220
  const targetMessage = this.ensureStreamingMessage(payload.sessionId, payload.messageId, "streaming");
266
221
  const nextParts = [...targetMessage.parts];
267
- const reasoningPartIndex = this.findLastReasoningPartIndex(nextParts);
268
- if (reasoningPartIndex >= 0) {
269
- const existingPart = nextParts[reasoningPartIndex];
270
- if (existingPart?.type === "reasoning") {
271
- nextParts[reasoningPartIndex] = {
272
- type: "reasoning",
273
- text: `${existingPart.text}${payload.delta}`
274
- };
275
- }
222
+ const lastPart = nextParts[nextParts.length - 1];
223
+ if (lastPart?.type === "reasoning") {
224
+ nextParts[nextParts.length - 1] = {
225
+ type: "reasoning",
226
+ text: `${lastPart.text}${payload.delta}`
227
+ };
276
228
  } else {
277
229
  nextParts.push({ type: "reasoning", text: payload.delta });
278
230
  }
@@ -371,13 +323,30 @@ var DefaultNcpAgentConversationStateManager = class {
371
323
  this.stateVersion += 1;
372
324
  }
373
325
  handleRunFinished(_payload) {
374
- this.activeRun = null;
375
- this.stateVersion += 1;
326
+ if (this.streamingMessage) {
327
+ const finalizedMessage = {
328
+ ...this.streamingMessage,
329
+ status: "final"
330
+ };
331
+ this.upsertMessage(finalizedMessage);
332
+ this.replaceStreamingMessage(null);
333
+ this.clearToolCallTrackingByMessageId(finalizedMessage.id);
334
+ }
335
+ this.setError(null);
336
+ this.clearActiveRun();
376
337
  }
377
338
  handleRunError(payload) {
339
+ if (this.streamingMessage) {
340
+ const failedMessage = {
341
+ ...this.streamingMessage,
342
+ status: "error"
343
+ };
344
+ this.upsertMessage(failedMessage);
345
+ this.replaceStreamingMessage(null);
346
+ this.clearToolCallTrackingByMessageId(failedMessage.id);
347
+ }
378
348
  this.setError(buildRuntimeError(payload));
379
- this.activeRun = null;
380
- this.stateVersion += 1;
349
+ this.clearActiveRun();
381
350
  }
382
351
  handleRunMetadata(payload) {
383
352
  const m = payload.metadata;
@@ -390,8 +359,7 @@ var DefaultNcpAgentConversationStateManager = class {
390
359
  };
391
360
  this.stateVersion += 1;
392
361
  } else if (m?.kind === "final") {
393
- this.activeRun = null;
394
- this.stateVersion += 1;
362
+ this.clearActiveRun();
395
363
  }
396
364
  }
397
365
  handleEndpointError(payload) {
@@ -425,6 +393,21 @@ var DefaultNcpAgentConversationStateManager = class {
425
393
  this.replaceStreamingMessage(nextStreamingMessage2);
426
394
  return nextStreamingMessage2;
427
395
  }
396
+ const messageIndex = this.messages.findIndex((message) => message.id === messageId);
397
+ if (messageIndex >= 0) {
398
+ const existingMessage = cloneConversationMessage(this.messages[messageIndex]);
399
+ const nextMessages = [...this.messages];
400
+ nextMessages.splice(messageIndex, 1);
401
+ this.messages = nextMessages;
402
+ this.stateVersion += 1;
403
+ const nextStreamingMessage2 = {
404
+ ...existingMessage,
405
+ sessionId,
406
+ status
407
+ };
408
+ this.replaceStreamingMessage(nextStreamingMessage2);
409
+ return nextStreamingMessage2;
410
+ }
428
411
  const nextStreamingMessage = {
429
412
  id: messageId,
430
413
  sessionId,
@@ -495,16 +478,8 @@ var DefaultNcpAgentConversationStateManager = class {
495
478
  nextParts.push(toolPart);
496
479
  return nextParts;
497
480
  }
498
- findLastReasoningPartIndex(parts) {
499
- for (let index = parts.length - 1; index >= 0; index -= 1) {
500
- if (parts[index]?.type === "reasoning") {
501
- return index;
502
- }
503
- }
504
- return -1;
505
- }
506
481
  upsertMessage(message) {
507
- const normalizedMessage = cloneMessage(message);
482
+ const normalizedMessage = normalizeConversationMessage(message);
508
483
  const messageIndex = this.messages.findIndex((item) => item.id === normalizedMessage.id);
509
484
  if (messageIndex < 0) {
510
485
  this.messages = [...this.messages, normalizedMessage];
@@ -520,7 +495,7 @@ var DefaultNcpAgentConversationStateManager = class {
520
495
  if (!nextStreamingMessage && !this.streamingMessage) {
521
496
  return;
522
497
  }
523
- this.streamingMessage = nextStreamingMessage ? cloneMessage(nextStreamingMessage) : null;
498
+ this.streamingMessage = nextStreamingMessage ? normalizeConversationMessage(nextStreamingMessage) : null;
524
499
  this.stateVersion += 1;
525
500
  }
526
501
  setError(nextError) {
@@ -531,6 +506,13 @@ var DefaultNcpAgentConversationStateManager = class {
531
506
  this.error = nextError ? { ...nextError, details: nextError.details ? { ...nextError.details } : void 0 } : null;
532
507
  this.stateVersion += 1;
533
508
  }
509
+ clearActiveRun() {
510
+ if (!this.activeRun) {
511
+ return;
512
+ }
513
+ this.activeRun = null;
514
+ this.stateVersion += 1;
515
+ }
534
516
  clearToolCallTrackingByMessageId(messageId) {
535
517
  for (const [toolCallId, trackedMessageId] of this.toolCallMessageIdByCallId) {
536
518
  if (trackedMessageId !== messageId) {
@@ -540,7 +522,639 @@ var DefaultNcpAgentConversationStateManager = class {
540
522
  this.toolCallArgsRawByCallId.delete(toolCallId);
541
523
  }
542
524
  }
525
+ notifyListeners() {
526
+ const snapshot = this.getSnapshot();
527
+ for (const listener of this.listeners) {
528
+ listener(snapshot);
529
+ }
530
+ }
531
+ };
532
+
533
+ // src/agent/agent-client-from-server.ts
534
+ import {
535
+ NcpEventType as NcpEventType2
536
+ } from "@nextclaw/ncp";
537
+ function createAgentClientFromServer(server) {
538
+ return {
539
+ get manifest() {
540
+ return server.manifest;
541
+ },
542
+ async start() {
543
+ await server.start();
544
+ },
545
+ async stop() {
546
+ await server.stop();
547
+ },
548
+ async emit(event) {
549
+ switch (event.type) {
550
+ case NcpEventType2.MessageRequest:
551
+ await consume(server.send(event.payload));
552
+ return;
553
+ case NcpEventType2.MessageStreamRequest:
554
+ await consume(server.stream(event.payload));
555
+ return;
556
+ case NcpEventType2.MessageAbort:
557
+ await server.abort(event.payload);
558
+ return;
559
+ default:
560
+ await server.emit(event);
561
+ }
562
+ },
563
+ subscribe(listener) {
564
+ return server.subscribe(listener);
565
+ },
566
+ async send(envelope) {
567
+ await consume(server.send(envelope));
568
+ },
569
+ async stream(payload) {
570
+ await consume(server.stream(payload));
571
+ },
572
+ async abort(payload) {
573
+ await server.abort(payload);
574
+ }
575
+ };
576
+ }
577
+ async function consume(events) {
578
+ for await (const event of events) {
579
+ void event;
580
+ }
581
+ }
582
+
583
+ // src/agent/agent-backend/agent-backend.ts
584
+ import {
585
+ NcpEventType as NcpEventType4
586
+ } from "@nextclaw/ncp";
587
+
588
+ // src/errors/ncp-error-exception.ts
589
+ var NcpErrorException = class extends Error {
590
+ code;
591
+ details;
592
+ constructor(code, message, details) {
593
+ super(message);
594
+ this.name = "NcpErrorException";
595
+ this.code = code;
596
+ this.details = details;
597
+ }
598
+ };
599
+
600
+ // src/agent/agent-backend/agent-live-session-registry.ts
601
+ var AgentLiveSessionRegistry = class {
602
+ constructor(sessionStore, createRuntime) {
603
+ this.sessionStore = sessionStore;
604
+ this.createRuntime = createRuntime;
605
+ }
606
+ sessions = /* @__PURE__ */ new Map();
607
+ async ensureSession(sessionId) {
608
+ const existing = this.sessions.get(sessionId);
609
+ if (existing) {
610
+ return existing;
611
+ }
612
+ const storedSession = await this.sessionStore.getSession(sessionId);
613
+ const stateManager = new DefaultNcpAgentConversationStateManager();
614
+ stateManager.hydrate({
615
+ sessionId,
616
+ messages: cloneMessages(storedSession?.messages ?? [])
617
+ });
618
+ const session = {
619
+ sessionId,
620
+ stateManager,
621
+ runtime: this.createRuntime({ sessionId, stateManager }),
622
+ activeExecution: null
623
+ };
624
+ this.sessions.set(sessionId, session);
625
+ return session;
626
+ }
627
+ getSession(sessionId) {
628
+ return this.sessions.get(sessionId) ?? null;
629
+ }
630
+ deleteSession(sessionId) {
631
+ const session = this.sessions.get(sessionId) ?? null;
632
+ if (session) {
633
+ this.sessions.delete(sessionId);
634
+ }
635
+ return session;
636
+ }
637
+ clear() {
638
+ this.sessions.clear();
639
+ }
640
+ listSessions() {
641
+ return [...this.sessions.values()];
642
+ }
643
+ };
644
+ function cloneMessages(messages) {
645
+ return messages.map((message) => structuredClone(message));
646
+ }
647
+
648
+ // src/agent/agent-backend/agent-run-executor.ts
649
+ import { NcpEventType as NcpEventType3 } from "@nextclaw/ncp";
650
+ var AgentRunExecutor = class {
651
+ constructor(persistSession) {
652
+ this.persistSession = persistSession;
653
+ }
654
+ async *executeRun(session, envelope, controller) {
655
+ const messageSent = {
656
+ type: NcpEventType3.MessageSent,
657
+ payload: {
658
+ sessionId: envelope.sessionId,
659
+ message: structuredClone(envelope.message),
660
+ metadata: envelope.metadata
661
+ }
662
+ };
663
+ await session.stateManager.dispatch(messageSent);
664
+ await this.persistSession(envelope.sessionId);
665
+ yield structuredClone(messageSent);
666
+ try {
667
+ for await (const event of session.runtime.run(
668
+ {
669
+ sessionId: envelope.sessionId,
670
+ messages: [envelope.message],
671
+ correlationId: envelope.correlationId,
672
+ metadata: envelope.metadata
673
+ },
674
+ { signal: controller.signal }
675
+ )) {
676
+ await this.persistSession(envelope.sessionId);
677
+ yield structuredClone(event);
678
+ }
679
+ } catch (error) {
680
+ if (!controller.signal.aborted) {
681
+ const runErrorEvent = await this.publishFailure(error, envelope, session);
682
+ yield structuredClone(runErrorEvent);
683
+ }
684
+ } finally {
685
+ await this.persistSession(envelope.sessionId);
686
+ }
687
+ }
688
+ async publishFailure(error, envelope, session) {
689
+ const message = error instanceof Error ? error.message : String(error);
690
+ const runErrorEvent = {
691
+ type: NcpEventType3.RunError,
692
+ payload: {
693
+ sessionId: envelope.sessionId,
694
+ error: message
695
+ }
696
+ };
697
+ await session.stateManager.dispatch(runErrorEvent);
698
+ await this.persistSession(envelope.sessionId);
699
+ return runErrorEvent;
700
+ }
701
+ };
702
+
703
+ // src/agent/agent-backend/async-queue.ts
704
+ function createAsyncQueue() {
705
+ const items = [];
706
+ let closed = false;
707
+ let pendingResolve = null;
708
+ const iterable = {
709
+ [Symbol.asyncIterator]() {
710
+ return {
711
+ next: () => {
712
+ if (items.length > 0) {
713
+ return Promise.resolve({
714
+ value: items.shift(),
715
+ done: false
716
+ });
717
+ }
718
+ if (closed) {
719
+ return Promise.resolve({
720
+ value: void 0,
721
+ done: true
722
+ });
723
+ }
724
+ return new Promise((resolve) => {
725
+ pendingResolve = resolve;
726
+ });
727
+ }
728
+ };
729
+ }
730
+ };
731
+ return {
732
+ push(item) {
733
+ if (closed) {
734
+ return;
735
+ }
736
+ if (pendingResolve) {
737
+ const resolve = pendingResolve;
738
+ pendingResolve = null;
739
+ resolve({ value: item, done: false });
740
+ return;
741
+ }
742
+ items.push(item);
743
+ },
744
+ close() {
745
+ if (closed) {
746
+ return;
747
+ }
748
+ closed = true;
749
+ if (pendingResolve) {
750
+ const resolve = pendingResolve;
751
+ pendingResolve = null;
752
+ resolve({
753
+ value: void 0,
754
+ done: true
755
+ });
756
+ }
757
+ },
758
+ iterable
759
+ };
760
+ }
761
+
762
+ // src/agent/agent-backend/event-publisher.ts
763
+ var EventPublisher = class {
764
+ listeners = /* @__PURE__ */ new Set();
765
+ closeListeners = /* @__PURE__ */ new Set();
766
+ closed = false;
767
+ subscribe(listener) {
768
+ if (this.closed) {
769
+ return () => void 0;
770
+ }
771
+ this.listeners.add(listener);
772
+ return () => {
773
+ this.listeners.delete(listener);
774
+ };
775
+ }
776
+ onClose(listener) {
777
+ if (this.closed) {
778
+ listener();
779
+ return () => void 0;
780
+ }
781
+ this.closeListeners.add(listener);
782
+ return () => {
783
+ this.closeListeners.delete(listener);
784
+ };
785
+ }
786
+ publish(event) {
787
+ if (this.closed) {
788
+ return;
789
+ }
790
+ for (const listener of this.listeners) {
791
+ listener(structuredClone(event));
792
+ }
793
+ }
794
+ close() {
795
+ if (this.closed) {
796
+ return;
797
+ }
798
+ this.closed = true;
799
+ this.listeners.clear();
800
+ for (const listener of this.closeListeners) {
801
+ listener();
802
+ }
803
+ this.closeListeners.clear();
804
+ }
805
+ };
806
+
807
+ // src/agent/agent-backend/agent-backend.ts
808
+ var DEFAULT_SUPPORTED_PART_TYPES = [
809
+ "text",
810
+ "file",
811
+ "source",
812
+ "step-start",
813
+ "reasoning",
814
+ "tool-invocation",
815
+ "card",
816
+ "rich-text",
817
+ "action",
818
+ "extension"
819
+ ];
820
+ var DefaultNcpAgentBackend = class {
821
+ manifest;
822
+ sessionStore;
823
+ sessionRegistry;
824
+ executor;
825
+ publisher;
826
+ started = false;
827
+ constructor(config) {
828
+ this.sessionStore = config.sessionStore;
829
+ this.sessionRegistry = new AgentLiveSessionRegistry(
830
+ this.sessionStore,
831
+ config.createRuntime
832
+ );
833
+ this.executor = new AgentRunExecutor(async (sessionId) => this.persistSession(sessionId));
834
+ this.publisher = new EventPublisher();
835
+ this.manifest = {
836
+ endpointKind: "agent",
837
+ endpointId: config.endpointId?.trim() || "ncp-agent-backend",
838
+ version: config.version?.trim() || "0.1.0",
839
+ supportsStreaming: true,
840
+ supportsAbort: true,
841
+ supportsProactiveMessages: false,
842
+ supportsLiveSessionStream: true,
843
+ supportedPartTypes: config.supportedPartTypes ?? DEFAULT_SUPPORTED_PART_TYPES,
844
+ expectedLatency: config.expectedLatency ?? "seconds",
845
+ metadata: config.metadata
846
+ };
847
+ }
848
+ async start() {
849
+ if (this.started) {
850
+ return;
851
+ }
852
+ this.started = true;
853
+ this.publisher.publish({ type: NcpEventType4.EndpointReady });
854
+ }
855
+ async stop() {
856
+ if (!this.started) {
857
+ return;
858
+ }
859
+ this.started = false;
860
+ for (const session of this.sessionRegistry.listSessions()) {
861
+ const execution = session.activeExecution;
862
+ if (!execution) {
863
+ continue;
864
+ }
865
+ execution.abortHandled = true;
866
+ execution.controller.abort();
867
+ this.finishSessionExecution(session, execution);
868
+ }
869
+ this.sessionRegistry.clear();
870
+ }
871
+ async emit(event) {
872
+ await this.ensureStarted();
873
+ switch (event.type) {
874
+ case NcpEventType4.MessageRequest:
875
+ await this.handleRequest(event.payload);
876
+ return;
877
+ case NcpEventType4.MessageStreamRequest:
878
+ await this.streamToSubscribers(event.payload);
879
+ return;
880
+ case NcpEventType4.MessageAbort:
881
+ await this.handleAbort(event.payload);
882
+ return;
883
+ default:
884
+ this.publisher.publish(event);
885
+ }
886
+ }
887
+ subscribe(listener) {
888
+ return this.publisher.subscribe(listener);
889
+ }
890
+ async *send(envelope, options) {
891
+ await this.ensureStarted();
892
+ const session = await this.sessionRegistry.ensureSession(envelope.sessionId);
893
+ const execution = this.startSessionExecution(session, envelope, options?.signal);
894
+ try {
895
+ for await (const event of this.executor.executeRun(session, envelope, execution.controller)) {
896
+ this.publishLiveEvent(execution, event);
897
+ yield event;
898
+ }
899
+ if (execution.controller.signal.aborted && !execution.abortHandled) {
900
+ const abortEvent = await this.createAbortEvent(session.sessionId);
901
+ execution.abortHandled = true;
902
+ this.publishLiveEvent(execution, abortEvent);
903
+ yield abortEvent;
904
+ }
905
+ } finally {
906
+ this.finishSessionExecution(session, execution);
907
+ }
908
+ }
909
+ async abort(payload) {
910
+ await this.handleAbort(payload);
911
+ }
912
+ async *stream(payloadOrParams, opts) {
913
+ const payload = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.payload : payloadOrParams;
914
+ const signal = "payload" in payloadOrParams && "signal" in payloadOrParams ? payloadOrParams.signal : opts?.signal ?? new AbortController().signal;
915
+ const session = this.sessionRegistry.getSession(payload.sessionId);
916
+ const execution = session?.activeExecution;
917
+ if (!session || !execution || execution.closed) {
918
+ return;
919
+ }
920
+ const queue = createAsyncQueue();
921
+ const unsubscribe = execution.publisher.subscribe((event) => {
922
+ queue.push(event);
923
+ });
924
+ const unsubscribeClose = execution.publisher.onClose(() => {
925
+ queue.close();
926
+ });
927
+ const stop = () => {
928
+ unsubscribe();
929
+ unsubscribeClose();
930
+ queue.close();
931
+ signal.removeEventListener("abort", stop);
932
+ };
933
+ signal.addEventListener("abort", stop, { once: true });
934
+ try {
935
+ for await (const event of queue.iterable) {
936
+ if (signal.aborted) {
937
+ break;
938
+ }
939
+ yield event;
940
+ if (isTerminalEvent(event)) {
941
+ break;
942
+ }
943
+ }
944
+ } finally {
945
+ stop();
946
+ }
947
+ }
948
+ async listSessions() {
949
+ const storedSessions = await this.sessionStore.listSessions();
950
+ const summaries = storedSessions.map(
951
+ (session) => toSessionSummary(session, this.sessionRegistry.getSession(session.sessionId))
952
+ );
953
+ for (const liveSession of this.sessionRegistry.listSessions()) {
954
+ if (summaries.some((session) => session.sessionId === liveSession.sessionId)) {
955
+ continue;
956
+ }
957
+ summaries.push(toLiveSessionSummary(liveSession));
958
+ }
959
+ return summaries.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
960
+ }
961
+ async listSessionMessages(sessionId) {
962
+ const liveSession = this.sessionRegistry.getSession(sessionId);
963
+ if (liveSession) {
964
+ return readMessages(liveSession.stateManager.getSnapshot());
965
+ }
966
+ const session = await this.sessionStore.getSession(sessionId);
967
+ return session ? session.messages.map((message) => structuredClone(message)) : [];
968
+ }
969
+ async getSession(sessionId) {
970
+ const liveSession = this.sessionRegistry.getSession(sessionId);
971
+ const storedSession = await this.sessionStore.getSession(sessionId);
972
+ if (storedSession) {
973
+ return toSessionSummary(storedSession, liveSession);
974
+ }
975
+ if (liveSession) {
976
+ return toLiveSessionSummary(liveSession);
977
+ }
978
+ return null;
979
+ }
980
+ async deleteSession(sessionId) {
981
+ const liveSession = this.sessionRegistry.deleteSession(sessionId);
982
+ const execution = liveSession?.activeExecution;
983
+ if (execution) {
984
+ execution.abortHandled = true;
985
+ execution.controller.abort();
986
+ this.closeExecution(execution);
987
+ }
988
+ await this.sessionStore.deleteSession(sessionId);
989
+ }
990
+ async ensureStarted() {
991
+ if (!this.started) {
992
+ await this.start();
993
+ }
994
+ }
995
+ startSessionExecution(session, envelope, signal) {
996
+ if (session.activeExecution && !session.activeExecution.closed) {
997
+ throw new NcpErrorException(
998
+ "runtime-error",
999
+ `Session ${session.sessionId} already has an active execution.`,
1000
+ { sessionId: session.sessionId }
1001
+ );
1002
+ }
1003
+ const controller = new AbortController();
1004
+ if (signal) {
1005
+ signal.addEventListener("abort", () => controller.abort(), {
1006
+ once: true
1007
+ });
1008
+ }
1009
+ const execution = {
1010
+ controller,
1011
+ publisher: new EventPublisher(),
1012
+ requestEnvelope: structuredClone(envelope),
1013
+ abortHandled: false,
1014
+ closed: false
1015
+ };
1016
+ session.activeExecution = execution;
1017
+ return execution;
1018
+ }
1019
+ finishSessionExecution(session, execution) {
1020
+ if (session.activeExecution === execution) {
1021
+ session.activeExecution = null;
1022
+ }
1023
+ this.closeExecution(execution);
1024
+ }
1025
+ closeExecution(execution) {
1026
+ if (execution.closed) {
1027
+ return;
1028
+ }
1029
+ execution.closed = true;
1030
+ execution.publisher.close();
1031
+ }
1032
+ publishLiveEvent(execution, event) {
1033
+ this.publisher.publish(event);
1034
+ if (!execution.closed) {
1035
+ execution.publisher.publish(event);
1036
+ }
1037
+ }
1038
+ async handleRequest(envelope) {
1039
+ for await (const event of this.send(envelope)) {
1040
+ void event;
1041
+ }
1042
+ }
1043
+ async streamToSubscribers(payload) {
1044
+ const signal = new AbortController().signal;
1045
+ for await (const event of this.stream({ payload, signal })) {
1046
+ void event;
1047
+ }
1048
+ }
1049
+ async handleAbort(payload) {
1050
+ const session = this.sessionRegistry.getSession(payload.sessionId);
1051
+ const execution = session?.activeExecution;
1052
+ if (!session || !execution || execution.closed) {
1053
+ return;
1054
+ }
1055
+ execution.abortHandled = true;
1056
+ execution.controller.abort();
1057
+ const abortEvent = await this.createAbortEvent(payload.sessionId, payload.messageId);
1058
+ this.publishLiveEvent(execution, abortEvent);
1059
+ this.finishSessionExecution(session, execution);
1060
+ }
1061
+ async createAbortEvent(sessionId, messageId) {
1062
+ const abortEvent = {
1063
+ type: NcpEventType4.MessageAbort,
1064
+ payload: {
1065
+ sessionId,
1066
+ ...messageId ? { messageId } : {}
1067
+ }
1068
+ };
1069
+ const liveSession = this.sessionRegistry.getSession(sessionId);
1070
+ if (liveSession) {
1071
+ await liveSession.stateManager.dispatch(abortEvent);
1072
+ }
1073
+ await this.persistSession(sessionId);
1074
+ return abortEvent;
1075
+ }
1076
+ async persistSession(sessionId) {
1077
+ const session = this.sessionRegistry.getSession(sessionId);
1078
+ if (!session) {
1079
+ return;
1080
+ }
1081
+ const snapshot = session.stateManager.getSnapshot();
1082
+ await this.sessionStore.saveSession({
1083
+ sessionId,
1084
+ messages: readMessages(snapshot),
1085
+ updatedAt: now(),
1086
+ metadata: session.activeExecution?.requestEnvelope.metadata
1087
+ });
1088
+ }
1089
+ };
1090
+ function readMessages(snapshot) {
1091
+ const messages = snapshot.messages.map((message) => structuredClone(message));
1092
+ if (snapshot.streamingMessage) {
1093
+ messages.push(structuredClone(snapshot.streamingMessage));
1094
+ }
1095
+ return messages;
1096
+ }
1097
+ function toSessionSummary(session, liveSession) {
1098
+ return {
1099
+ sessionId: session.sessionId,
1100
+ messageCount: session.messages.length,
1101
+ updatedAt: session.updatedAt,
1102
+ status: liveSession?.activeExecution ? "running" : "idle",
1103
+ ...session.metadata ? { metadata: structuredClone(session.metadata) } : {}
1104
+ };
1105
+ }
1106
+ function toLiveSessionSummary(session) {
1107
+ const snapshot = session.stateManager.getSnapshot();
1108
+ return {
1109
+ sessionId: session.sessionId,
1110
+ messageCount: readMessages(snapshot).length,
1111
+ updatedAt: now(),
1112
+ status: session.activeExecution ? "running" : "idle",
1113
+ ...session.activeExecution?.requestEnvelope.metadata ? { metadata: structuredClone(session.activeExecution.requestEnvelope.metadata) } : {}
1114
+ };
1115
+ }
1116
+ function now() {
1117
+ return (/* @__PURE__ */ new Date()).toISOString();
1118
+ }
1119
+ function isTerminalEvent(event) {
1120
+ switch (event.type) {
1121
+ case NcpEventType4.MessageAbort:
1122
+ case NcpEventType4.RunFinished:
1123
+ case NcpEventType4.RunError:
1124
+ return true;
1125
+ default:
1126
+ return false;
1127
+ }
1128
+ }
1129
+
1130
+ // src/agent/agent-backend/in-memory-agent-session-store.ts
1131
+ var InMemoryAgentSessionStore = class {
1132
+ sessions = /* @__PURE__ */ new Map();
1133
+ async getSession(sessionId) {
1134
+ const session = this.sessions.get(sessionId);
1135
+ return session ? structuredClone(session) : null;
1136
+ }
1137
+ async listSessions() {
1138
+ return [...this.sessions.values()].map((session) => structuredClone(session));
1139
+ }
1140
+ async saveSession(session) {
1141
+ this.sessions.set(session.sessionId, structuredClone(session));
1142
+ }
1143
+ async deleteSession(sessionId) {
1144
+ const session = this.sessions.get(sessionId);
1145
+ if (!session) {
1146
+ return null;
1147
+ }
1148
+ this.sessions.delete(sessionId);
1149
+ return structuredClone(session);
1150
+ }
543
1151
  };
544
1152
  export {
545
- DefaultNcpAgentConversationStateManager
1153
+ AgentRunExecutor,
1154
+ DefaultNcpAgentBackend,
1155
+ DefaultNcpAgentConversationStateManager,
1156
+ EventPublisher,
1157
+ InMemoryAgentSessionStore,
1158
+ NcpErrorException,
1159
+ createAgentClientFromServer
546
1160
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-toolkit",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Toolkit implementations built on top of the NextClaw Communication Protocol.",
6
6
  "type": "module",
@@ -15,18 +15,15 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.1.1"
18
+ "@nextclaw/ncp": "0.3.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",
22
- "@typescript-eslint/eslint-plugin": "^7.18.0",
23
- "@typescript-eslint/parser": "^7.18.0",
24
- "eslint": "^8.57.1",
25
- "eslint-config-prettier": "^9.1.0",
26
22
  "prettier": "^3.3.3",
27
23
  "tsup": "^8.3.5",
28
24
  "typescript": "^5.6.3",
29
- "vitest": "^2.1.2"
25
+ "vitest": "^2.1.2",
26
+ "@nextclaw/ncp-agent-runtime": "0.2.0"
30
27
  },
31
28
  "scripts": {
32
29
  "build": "tsup src/index.ts --format esm --dts --out-dir dist",