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