@nextclaw/ncp 0.1.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/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/index.d.ts +663 -0
- package/dist/index.js +41 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NextClaw contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# @nextclaw/ncp
|
|
2
|
+
|
|
3
|
+
Core protocol types and endpoint abstractions for universal communication in NextClaw.
|
|
4
|
+
|
|
5
|
+
## Build
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm -C packages/nextclaw-ncp build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- NCP manifest, message, session, and error definitions
|
|
14
|
+
- Generic endpoint base class (`AbstractEndpoint`)
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
See [docs/USAGE.md](docs/USAGE.md) for real-world scenarios and example code (in-process endpoint, bridging two endpoints, agent adapter).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Participants in an NCP conversation.
|
|
3
|
+
*
|
|
4
|
+
* - `user` — A human end-user interacting with the system.
|
|
5
|
+
* - `assistant` — An AI model generating responses.
|
|
6
|
+
* - `system` — Configuration or instruction injected before the conversation.
|
|
7
|
+
* - `tool` — The result returned by a tool invocation.
|
|
8
|
+
* - `service` — A platform-side notification or event (e.g. a bot posting a card),
|
|
9
|
+
* distinct from an AI assistant response.
|
|
10
|
+
*/
|
|
11
|
+
type NcpMessageRole = "user" | "assistant" | "system" | "tool" | "service";
|
|
12
|
+
/**
|
|
13
|
+
* Lifecycle state of a message.
|
|
14
|
+
*
|
|
15
|
+
* Applies to both streaming and non-streaming endpoints:
|
|
16
|
+
* - `pending` — Created but not yet sent or received.
|
|
17
|
+
* - `streaming` — Actively receiving incremental deltas.
|
|
18
|
+
* - `final` — Fully received; no further updates expected.
|
|
19
|
+
* - `error` — Terminated with an error; `parts` may be incomplete.
|
|
20
|
+
*/
|
|
21
|
+
type NcpMessageStatus = "pending" | "streaming" | "final" | "error";
|
|
22
|
+
/** Plain text content. The most common part type. */
|
|
23
|
+
type NcpTextPart = {
|
|
24
|
+
type: "text";
|
|
25
|
+
text: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A file attachment, delivered either as a URL or inline Base64 content.
|
|
29
|
+
*
|
|
30
|
+
* `url` and `contentBase64` are mutually exclusive — populate exactly one.
|
|
31
|
+
*/
|
|
32
|
+
type NcpFilePart = {
|
|
33
|
+
type: "file";
|
|
34
|
+
name?: string;
|
|
35
|
+
mimeType?: string;
|
|
36
|
+
/** Remote URL pointing to the file. Mutually exclusive with `contentBase64`. */
|
|
37
|
+
url?: string;
|
|
38
|
+
/** Inline file content encoded as Base64. Mutually exclusive with `url`. */
|
|
39
|
+
contentBase64?: string;
|
|
40
|
+
sizeBytes?: number;
|
|
41
|
+
};
|
|
42
|
+
/** A cited source or reference, typically surfaced alongside AI-generated content. */
|
|
43
|
+
type NcpSourcePart = {
|
|
44
|
+
type: "source";
|
|
45
|
+
title?: string;
|
|
46
|
+
url?: string;
|
|
47
|
+
/** Short excerpt from the source relevant to the message. */
|
|
48
|
+
snippet?: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Marks the beginning of a logical reasoning step in a multi-step response.
|
|
52
|
+
* Used by chain-of-thought or agentic flows to delimit discrete steps.
|
|
53
|
+
*/
|
|
54
|
+
type NcpStepStartPart = {
|
|
55
|
+
type: "step-start";
|
|
56
|
+
stepId?: string;
|
|
57
|
+
/** Human-readable label for the step (e.g. "Searching the web"). */
|
|
58
|
+
title?: string;
|
|
59
|
+
};
|
|
60
|
+
/** Internal reasoning text produced by the model before its final response. */
|
|
61
|
+
type NcpReasoningPart = {
|
|
62
|
+
type: "reasoning";
|
|
63
|
+
text: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Represents a tool call and its lifecycle.
|
|
67
|
+
*
|
|
68
|
+
* State semantics (aligned with the Vercel AI SDK convention):
|
|
69
|
+
* - `"call"` — The model has emitted a complete tool call; `args` is populated.
|
|
70
|
+
* - `"partial-call"` — Arguments are still being streamed; `args` may be incomplete.
|
|
71
|
+
* - `"result"` — The tool has returned; `result` is populated.
|
|
72
|
+
*/
|
|
73
|
+
type NcpToolInvocationPart = {
|
|
74
|
+
type: "tool-invocation";
|
|
75
|
+
toolName: string;
|
|
76
|
+
toolCallId?: string;
|
|
77
|
+
state?: "call" | "partial-call" | "result";
|
|
78
|
+
/** Tool input arguments. May be partial when `state === "partial-call"`. */
|
|
79
|
+
args?: unknown;
|
|
80
|
+
/** Tool output. Populated when `state === "result"`. */
|
|
81
|
+
result?: unknown;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* A rich card with arbitrary structured data.
|
|
85
|
+
* Rendering is delegated to the platform (e.g. Slack block kit, Teams card).
|
|
86
|
+
*/
|
|
87
|
+
type NcpCardPart = {
|
|
88
|
+
type: "card";
|
|
89
|
+
payload: Record<string, unknown>;
|
|
90
|
+
};
|
|
91
|
+
/** Formatted text with an explicit markup format hint. */
|
|
92
|
+
type NcpRichTextPart = {
|
|
93
|
+
type: "rich-text";
|
|
94
|
+
format: "markdown" | "html" | "plain";
|
|
95
|
+
text: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* An interactive action button or trigger exposed to the user.
|
|
99
|
+
* The platform is responsible for rendering and routing the action.
|
|
100
|
+
*/
|
|
101
|
+
type NcpActionPart = {
|
|
102
|
+
type: "action";
|
|
103
|
+
actionId: string;
|
|
104
|
+
/** Display label shown to the user. */
|
|
105
|
+
label: string;
|
|
106
|
+
/** Arbitrary data forwarded to the action handler. */
|
|
107
|
+
payload?: unknown;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Escape hatch for custom or future part types not yet in the NCP spec.
|
|
111
|
+
*
|
|
112
|
+
* Use this to avoid forking the protocol during early iterations.
|
|
113
|
+
* Once a part type stabilizes, promote it to a first-class `NcpXxxPart`.
|
|
114
|
+
*/
|
|
115
|
+
type NcpExtensionPart = {
|
|
116
|
+
type: "extension";
|
|
117
|
+
/** Namespaced identifier for the custom part (e.g. `"myapp.highlight"`). */
|
|
118
|
+
extensionType: string;
|
|
119
|
+
data: unknown;
|
|
120
|
+
};
|
|
121
|
+
/** Union of all recognized NCP message part types. */
|
|
122
|
+
type NcpMessagePart = NcpTextPart | NcpFilePart | NcpSourcePart | NcpStepStartPart | NcpReasoningPart | NcpToolInvocationPart | NcpCardPart | NcpRichTextPart | NcpActionPart | NcpExtensionPart;
|
|
123
|
+
/**
|
|
124
|
+
* Canonical message envelope exchanged and persisted in NCP.
|
|
125
|
+
*
|
|
126
|
+
* A message is composed of an ordered list of typed `parts`, allowing
|
|
127
|
+
* heterogeneous content (text, files, tool calls, etc.) in a single message.
|
|
128
|
+
*/
|
|
129
|
+
type NcpMessage = {
|
|
130
|
+
/** Globally unique message identifier. */
|
|
131
|
+
id: string;
|
|
132
|
+
/** The session this message belongs to. */
|
|
133
|
+
sessionKey: string;
|
|
134
|
+
role: NcpMessageRole;
|
|
135
|
+
status: NcpMessageStatus;
|
|
136
|
+
parts: NcpMessagePart[];
|
|
137
|
+
/** ISO 8601 timestamp (e.g. `"2024-01-15T10:30:00.000Z"`). */
|
|
138
|
+
timestamp: string;
|
|
139
|
+
/** Arbitrary transport- or application-level metadata. */
|
|
140
|
+
metadata?: Record<string, unknown>;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* High-level category of an endpoint.
|
|
145
|
+
*
|
|
146
|
+
* Used for routing decisions, capability interpretation, and UI labeling.
|
|
147
|
+
* - `agent` — An AI agent that processes and responds to messages.
|
|
148
|
+
* - `platform` — A collaboration platform (e.g. Slack, Teams, Discord).
|
|
149
|
+
* - `email` — An email transport.
|
|
150
|
+
* - `human` — A human operator or approval gate.
|
|
151
|
+
* - `custom` — Any endpoint type not covered by the above.
|
|
152
|
+
*/
|
|
153
|
+
type NcpEndpointKind = "agent" | "platform" | "email" | "human" | "custom";
|
|
154
|
+
/**
|
|
155
|
+
* Expected response latency profile of an endpoint.
|
|
156
|
+
*
|
|
157
|
+
* Used by the runtime and product layer to set user expectations,
|
|
158
|
+
* configure timeouts, and choose appropriate UX patterns.
|
|
159
|
+
*/
|
|
160
|
+
type NcpEndpointLatency = "realtime" | "seconds" | "minutes" | "hours" | "days";
|
|
161
|
+
/**
|
|
162
|
+
* Capability manifest every endpoint adapter must expose.
|
|
163
|
+
*
|
|
164
|
+
* Consumers use the manifest for runtime capability discovery —
|
|
165
|
+
* e.g. whether to show a typing indicator, whether to offer file uploads,
|
|
166
|
+
* or whether session resume is available.
|
|
167
|
+
*/
|
|
168
|
+
type NcpEndpointManifest = {
|
|
169
|
+
/** Category of this endpoint. */
|
|
170
|
+
endpointKind: NcpEndpointKind;
|
|
171
|
+
/** Stable unique identifier for this endpoint instance. */
|
|
172
|
+
endpointId: string;
|
|
173
|
+
/** SemVer string for the adapter implementation. */
|
|
174
|
+
version: string;
|
|
175
|
+
/** Whether this endpoint supports incremental streaming of message content. */
|
|
176
|
+
supportsStreaming: boolean;
|
|
177
|
+
/** Whether in-flight requests can be cancelled via `AbortSignal`. */
|
|
178
|
+
supportsAbort: boolean;
|
|
179
|
+
/** Whether this endpoint can push messages without a prior user request. */
|
|
180
|
+
supportsProactiveMessages: boolean;
|
|
181
|
+
/** Whether a disconnected session can be resumed by the same `sessionKey`. */
|
|
182
|
+
supportsSessionResume: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* The subset of `NcpMessagePart` types this endpoint can send or receive.
|
|
185
|
+
* Using the literal union from `NcpMessagePart["type"]` prevents typos
|
|
186
|
+
* and keeps the manifest in sync with the protocol definition.
|
|
187
|
+
*/
|
|
188
|
+
supportedPartTypes: ReadonlyArray<NcpMessagePart["type"]>;
|
|
189
|
+
/** Expected response latency — informs timeouts and UX treatment. */
|
|
190
|
+
expectedLatency: NcpEndpointLatency;
|
|
191
|
+
/** Arbitrary adapter- or deployment-specific metadata. */
|
|
192
|
+
metadata?: Record<string, unknown>;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Protocol-level error categories.
|
|
197
|
+
*
|
|
198
|
+
* Uses kebab-case to be consistent with the rest of the NCP type literals
|
|
199
|
+
* (e.g. endpoint kinds, latency profiles).
|
|
200
|
+
*/
|
|
201
|
+
type NcpErrorCode = "config-error" | "auth-error" | "runtime-error" | "timeout-error" | "abort-error";
|
|
202
|
+
/**
|
|
203
|
+
* Structured error payload used on endpoint and stream boundaries.
|
|
204
|
+
*
|
|
205
|
+
* Prefer this over raw `Error` objects when crossing async/transport
|
|
206
|
+
* boundaries so that error metadata survives serialization.
|
|
207
|
+
*/
|
|
208
|
+
type NcpError = {
|
|
209
|
+
/** Machine-readable category. */
|
|
210
|
+
code: NcpErrorCode;
|
|
211
|
+
/** Human-readable description intended for developers, not end-users. */
|
|
212
|
+
message: string;
|
|
213
|
+
/** Optional structured context (request ids, field paths, etc.). */
|
|
214
|
+
details?: Record<string, unknown>;
|
|
215
|
+
/** Original cause — preserved for debugging but not guaranteed serializable. */
|
|
216
|
+
cause?: unknown;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Throwable form of `NcpError` for use in exception-based control flows.
|
|
220
|
+
*
|
|
221
|
+
* Bridges typed protocol errors with standard JS `Error` hierarchies so that
|
|
222
|
+
* `instanceof` checks and stack traces work as expected.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* throw new NcpErrorException("auth-error", "Missing API key");
|
|
226
|
+
*/
|
|
227
|
+
declare class NcpErrorException extends Error {
|
|
228
|
+
readonly code: NcpErrorCode;
|
|
229
|
+
readonly details?: Record<string, unknown>;
|
|
230
|
+
constructor(code: NcpErrorCode, message: string, details?: Record<string, unknown>);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Stable mapping between an NCP-internal session key and the endpoint's
|
|
235
|
+
* native session identifier.
|
|
236
|
+
*
|
|
237
|
+
* A single NCP session may span multiple endpoint sessions over time
|
|
238
|
+
* (e.g. after reconnects). This binding is the source of truth for that mapping.
|
|
239
|
+
*/
|
|
240
|
+
type NcpSessionBinding = {
|
|
241
|
+
/** The endpoint that owns this session. */
|
|
242
|
+
endpointId: string;
|
|
243
|
+
/** NCP-internal session identifier — stable across reconnects. */
|
|
244
|
+
sessionKey: string;
|
|
245
|
+
/**
|
|
246
|
+
* The session identifier used by the endpoint itself (e.g. an OpenAI thread id,
|
|
247
|
+
* a Slack channel + thread_ts pair, or a vendor-specific conversation id).
|
|
248
|
+
*/
|
|
249
|
+
endpointSessionId: string;
|
|
250
|
+
/** Arbitrary metadata for adapter-specific session context. */
|
|
251
|
+
metadata?: Record<string, unknown>;
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Lightweight snapshot of session state for orchestration and observability.
|
|
255
|
+
*
|
|
256
|
+
* Not a full history — use `NcpSessionContract.appendMessage` for the message log.
|
|
257
|
+
*/
|
|
258
|
+
type NcpSessionState = {
|
|
259
|
+
sessionKey: string;
|
|
260
|
+
endpointId: string;
|
|
261
|
+
/** Present once a binding has been established. */
|
|
262
|
+
endpointSessionId?: string;
|
|
263
|
+
/** Total number of messages appended to this session. */
|
|
264
|
+
messageCount: number;
|
|
265
|
+
/** ISO 8601 timestamp of the last state mutation. */
|
|
266
|
+
updatedAt: string;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* Minimal storage contract for NCP session management.
|
|
270
|
+
*
|
|
271
|
+
* Intentionally small for early protocol iterations — implementations can
|
|
272
|
+
* extend it with querying, TTL, or event hooks as requirements grow.
|
|
273
|
+
*/
|
|
274
|
+
interface NcpSessionContract {
|
|
275
|
+
/**
|
|
276
|
+
* Looks up the binding for a given session key.
|
|
277
|
+
* Returns `null` if no binding exists yet.
|
|
278
|
+
*/
|
|
279
|
+
resolveBinding(sessionKey: string): Promise<NcpSessionBinding | null>;
|
|
280
|
+
/**
|
|
281
|
+
* Creates or updates the binding for a session key.
|
|
282
|
+
* Implementations should treat this as an upsert (idempotent on `sessionKey`).
|
|
283
|
+
*/
|
|
284
|
+
upsertBinding(binding: NcpSessionBinding): Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Appends a message to the session's immutable message log.
|
|
287
|
+
*
|
|
288
|
+
* Implementations MUST treat this as append-only — existing messages
|
|
289
|
+
* should never be mutated or deleted through this method.
|
|
290
|
+
*/
|
|
291
|
+
appendMessage(message: NcpMessage): Promise<void>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* NCP event and payload definitions.
|
|
296
|
+
*
|
|
297
|
+
* Streaming content (text, reasoning, tool args) uses start → delta sequence → end.
|
|
298
|
+
* The same content can be sent as a single full event (e.g. message.incoming or message.completed)
|
|
299
|
+
* instead; endpoints or upper layers choose as needed.
|
|
300
|
+
*/
|
|
301
|
+
type NcpRequestEnvelope = {
|
|
302
|
+
sessionKey: string;
|
|
303
|
+
message: NcpMessage;
|
|
304
|
+
correlationId?: string;
|
|
305
|
+
metadata?: Record<string, unknown>;
|
|
306
|
+
};
|
|
307
|
+
/** Payload for message.incoming: message content from the other peer (partial or full). */
|
|
308
|
+
type NcpResponseEnvelope = {
|
|
309
|
+
sessionKey: string;
|
|
310
|
+
message: NcpMessage;
|
|
311
|
+
correlationId?: string;
|
|
312
|
+
metadata?: Record<string, unknown>;
|
|
313
|
+
};
|
|
314
|
+
type NcpCompletedEnvelope = {
|
|
315
|
+
sessionKey: string;
|
|
316
|
+
message: NcpMessage;
|
|
317
|
+
correlationId?: string;
|
|
318
|
+
metadata?: Record<string, unknown>;
|
|
319
|
+
};
|
|
320
|
+
type NcpFailedEnvelope = {
|
|
321
|
+
sessionKey: string;
|
|
322
|
+
messageId?: string;
|
|
323
|
+
error: NcpError;
|
|
324
|
+
correlationId?: string;
|
|
325
|
+
metadata?: Record<string, unknown>;
|
|
326
|
+
};
|
|
327
|
+
type NcpMessageAcceptedPayload = {
|
|
328
|
+
messageId: string;
|
|
329
|
+
correlationId?: string;
|
|
330
|
+
transportId?: string;
|
|
331
|
+
};
|
|
332
|
+
type NcpMessageAbortPayload = {
|
|
333
|
+
messageId?: string;
|
|
334
|
+
correlationId?: string;
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Payload for message.sent: the local peer has sent a message (outbound).
|
|
338
|
+
* Typically non-streaming; add the message to the local conversation state.
|
|
339
|
+
*/
|
|
340
|
+
type NcpMessageSentPayload = {
|
|
341
|
+
sessionKey: string;
|
|
342
|
+
message: NcpMessage;
|
|
343
|
+
metadata?: Record<string, unknown>;
|
|
344
|
+
};
|
|
345
|
+
type NcpTypingStartPayload = {
|
|
346
|
+
sessionKey: string;
|
|
347
|
+
/** Participant who is typing (human user or bot/assistant). */
|
|
348
|
+
userId?: string;
|
|
349
|
+
};
|
|
350
|
+
type NcpTypingEndPayload = {
|
|
351
|
+
sessionKey: string;
|
|
352
|
+
/** Participant who stopped typing (human user or bot/assistant). */
|
|
353
|
+
userId?: string;
|
|
354
|
+
};
|
|
355
|
+
type NcpPresenceUpdatedPayload = {
|
|
356
|
+
sessionKey: string;
|
|
357
|
+
/** Participant this presence applies to (human user or bot/assistant). */
|
|
358
|
+
userId?: string;
|
|
359
|
+
status: "online" | "offline" | "away";
|
|
360
|
+
};
|
|
361
|
+
type NcpMessageReadPayload = {
|
|
362
|
+
sessionKey: string;
|
|
363
|
+
messageId: string;
|
|
364
|
+
readAt?: string;
|
|
365
|
+
readerId?: string;
|
|
366
|
+
};
|
|
367
|
+
type NcpMessageDeliveredPayload = {
|
|
368
|
+
sessionKey: string;
|
|
369
|
+
messageId: string;
|
|
370
|
+
};
|
|
371
|
+
type NcpMessageRecalledPayload = {
|
|
372
|
+
sessionKey: string;
|
|
373
|
+
messageId: string;
|
|
374
|
+
};
|
|
375
|
+
type NcpMessageReactionPayload = {
|
|
376
|
+
sessionKey: string;
|
|
377
|
+
messageId: string;
|
|
378
|
+
reaction: string;
|
|
379
|
+
added: boolean;
|
|
380
|
+
/** Participant who added or removed the reaction (human user or bot/assistant). */
|
|
381
|
+
userId?: string;
|
|
382
|
+
};
|
|
383
|
+
type NcpRunStartedPayload = {
|
|
384
|
+
sessionKey?: string;
|
|
385
|
+
messageId?: string;
|
|
386
|
+
threadId?: string;
|
|
387
|
+
runId?: string;
|
|
388
|
+
};
|
|
389
|
+
type NcpRunFinishedPayload = {
|
|
390
|
+
sessionKey?: string;
|
|
391
|
+
messageId?: string;
|
|
392
|
+
threadId?: string;
|
|
393
|
+
runId?: string;
|
|
394
|
+
};
|
|
395
|
+
type NcpRunErrorPayload = {
|
|
396
|
+
sessionKey?: string;
|
|
397
|
+
messageId?: string;
|
|
398
|
+
error?: string;
|
|
399
|
+
threadId?: string;
|
|
400
|
+
runId?: string;
|
|
401
|
+
};
|
|
402
|
+
type NcpRunMetadataPayload = {
|
|
403
|
+
sessionKey?: string;
|
|
404
|
+
messageId?: string;
|
|
405
|
+
runId?: string;
|
|
406
|
+
metadata: Record<string, unknown>;
|
|
407
|
+
};
|
|
408
|
+
type NcpTextStartPayload = {
|
|
409
|
+
sessionKey: string;
|
|
410
|
+
messageId: string;
|
|
411
|
+
};
|
|
412
|
+
type NcpTextDeltaPayload = {
|
|
413
|
+
sessionKey: string;
|
|
414
|
+
messageId: string;
|
|
415
|
+
delta: string;
|
|
416
|
+
};
|
|
417
|
+
type NcpTextEndPayload = {
|
|
418
|
+
sessionKey: string;
|
|
419
|
+
messageId: string;
|
|
420
|
+
};
|
|
421
|
+
type NcpReasoningStartPayload = {
|
|
422
|
+
sessionKey: string;
|
|
423
|
+
messageId: string;
|
|
424
|
+
};
|
|
425
|
+
type NcpReasoningDeltaPayload = {
|
|
426
|
+
sessionKey: string;
|
|
427
|
+
messageId: string;
|
|
428
|
+
delta: string;
|
|
429
|
+
};
|
|
430
|
+
type NcpReasoningEndPayload = {
|
|
431
|
+
sessionKey: string;
|
|
432
|
+
messageId: string;
|
|
433
|
+
};
|
|
434
|
+
type NcpToolCallStartPayload = {
|
|
435
|
+
sessionKey: string;
|
|
436
|
+
messageId?: string;
|
|
437
|
+
toolCallId: string;
|
|
438
|
+
toolName: string;
|
|
439
|
+
};
|
|
440
|
+
type NcpToolCallArgsPayload = {
|
|
441
|
+
sessionKey: string;
|
|
442
|
+
toolCallId: string;
|
|
443
|
+
args: string;
|
|
444
|
+
};
|
|
445
|
+
type NcpToolCallArgsDeltaPayload = {
|
|
446
|
+
sessionKey: string;
|
|
447
|
+
messageId?: string;
|
|
448
|
+
toolCallId: string;
|
|
449
|
+
delta: string;
|
|
450
|
+
};
|
|
451
|
+
type NcpToolCallEndPayload = {
|
|
452
|
+
sessionKey: string;
|
|
453
|
+
toolCallId: string;
|
|
454
|
+
};
|
|
455
|
+
type NcpToolCallResultPayload = {
|
|
456
|
+
sessionKey: string;
|
|
457
|
+
toolCallId: string;
|
|
458
|
+
content: unknown;
|
|
459
|
+
};
|
|
460
|
+
type NcpEndpointEvent = {
|
|
461
|
+
type: "endpoint.ready";
|
|
462
|
+
} | {
|
|
463
|
+
type: "message.request";
|
|
464
|
+
payload: NcpRequestEnvelope;
|
|
465
|
+
} | {
|
|
466
|
+
type: "message.sent";
|
|
467
|
+
payload: NcpMessageSentPayload;
|
|
468
|
+
} | {
|
|
469
|
+
type: "message.accepted";
|
|
470
|
+
payload: NcpMessageAcceptedPayload;
|
|
471
|
+
} | {
|
|
472
|
+
type: "message.incoming";
|
|
473
|
+
payload: NcpResponseEnvelope;
|
|
474
|
+
} | {
|
|
475
|
+
type: "message.completed";
|
|
476
|
+
payload: NcpCompletedEnvelope;
|
|
477
|
+
} | {
|
|
478
|
+
type: "message.failed";
|
|
479
|
+
payload: NcpFailedEnvelope;
|
|
480
|
+
} | {
|
|
481
|
+
type: "message.abort";
|
|
482
|
+
payload: NcpMessageAbortPayload;
|
|
483
|
+
} | {
|
|
484
|
+
type: "endpoint.error";
|
|
485
|
+
payload: NcpError;
|
|
486
|
+
} | {
|
|
487
|
+
type: "run.started";
|
|
488
|
+
payload: NcpRunStartedPayload;
|
|
489
|
+
} | {
|
|
490
|
+
type: "run.finished";
|
|
491
|
+
payload: NcpRunFinishedPayload;
|
|
492
|
+
} | {
|
|
493
|
+
type: "run.error";
|
|
494
|
+
payload: NcpRunErrorPayload;
|
|
495
|
+
} | {
|
|
496
|
+
type: "run.metadata";
|
|
497
|
+
payload: NcpRunMetadataPayload;
|
|
498
|
+
} | {
|
|
499
|
+
type: "message.text-start";
|
|
500
|
+
payload: NcpTextStartPayload;
|
|
501
|
+
} | {
|
|
502
|
+
type: "message.text-delta";
|
|
503
|
+
payload: NcpTextDeltaPayload;
|
|
504
|
+
} | {
|
|
505
|
+
type: "message.text-end";
|
|
506
|
+
payload: NcpTextEndPayload;
|
|
507
|
+
} | {
|
|
508
|
+
type: "message.reasoning-start";
|
|
509
|
+
payload: NcpReasoningStartPayload;
|
|
510
|
+
} | {
|
|
511
|
+
type: "message.reasoning-delta";
|
|
512
|
+
payload: NcpReasoningDeltaPayload;
|
|
513
|
+
} | {
|
|
514
|
+
type: "message.reasoning-end";
|
|
515
|
+
payload: NcpReasoningEndPayload;
|
|
516
|
+
} | {
|
|
517
|
+
type: "message.tool-call-start";
|
|
518
|
+
payload: NcpToolCallStartPayload;
|
|
519
|
+
} | {
|
|
520
|
+
type: "message.tool-call-args";
|
|
521
|
+
payload: NcpToolCallArgsPayload;
|
|
522
|
+
} | {
|
|
523
|
+
type: "message.tool-call-args-delta";
|
|
524
|
+
payload: NcpToolCallArgsDeltaPayload;
|
|
525
|
+
} | {
|
|
526
|
+
type: "message.tool-call-end";
|
|
527
|
+
payload: NcpToolCallEndPayload;
|
|
528
|
+
} | {
|
|
529
|
+
type: "message.tool-call-result";
|
|
530
|
+
payload: NcpToolCallResultPayload;
|
|
531
|
+
} | {
|
|
532
|
+
type: "typing.start";
|
|
533
|
+
payload: NcpTypingStartPayload;
|
|
534
|
+
} | {
|
|
535
|
+
type: "typing.end";
|
|
536
|
+
payload: NcpTypingEndPayload;
|
|
537
|
+
} | {
|
|
538
|
+
type: "presence.updated";
|
|
539
|
+
payload: NcpPresenceUpdatedPayload;
|
|
540
|
+
} | {
|
|
541
|
+
type: "message.read";
|
|
542
|
+
payload: NcpMessageReadPayload;
|
|
543
|
+
} | {
|
|
544
|
+
type: "message.delivered";
|
|
545
|
+
payload: NcpMessageDeliveredPayload;
|
|
546
|
+
} | {
|
|
547
|
+
type: "message.recalled";
|
|
548
|
+
payload: NcpMessageRecalledPayload;
|
|
549
|
+
} | {
|
|
550
|
+
type: "message.reaction";
|
|
551
|
+
payload: NcpMessageReactionPayload;
|
|
552
|
+
};
|
|
553
|
+
type NcpEndpointSubscriber = (event: NcpEndpointEvent) => void;
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Core interface every NCP endpoint adapter must implement.
|
|
557
|
+
*
|
|
558
|
+
* Single primitive: emit(event) to send, subscribe(listener) to receive.
|
|
559
|
+
* Event types and payloads are defined in events.ts (aligned with agent-chat).
|
|
560
|
+
*/
|
|
561
|
+
interface NcpEndpoint {
|
|
562
|
+
readonly manifest: NcpEndpointManifest;
|
|
563
|
+
start(): Promise<void>;
|
|
564
|
+
stop(): Promise<void>;
|
|
565
|
+
emit(event: NcpEndpointEvent): void | Promise<void>;
|
|
566
|
+
subscribe(listener: (event: NcpEndpointEvent) => void): () => void;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Base class for NCP endpoint adapters.
|
|
571
|
+
*
|
|
572
|
+
* Lifecycle (start/stop) and subscribe/broadcast. Subclass must implement
|
|
573
|
+
* onStart, onStop, emit(); call broadcast() when an event is received from
|
|
574
|
+
* the transport. When the endpoint is ready to send/receive, call
|
|
575
|
+
* broadcast({ type: "endpoint.ready" }) — base class does not emit it.
|
|
576
|
+
*/
|
|
577
|
+
declare abstract class AbstractEndpoint implements NcpEndpoint {
|
|
578
|
+
abstract readonly manifest: NcpEndpointManifest;
|
|
579
|
+
private started;
|
|
580
|
+
private readonly listeners;
|
|
581
|
+
start(): Promise<void>;
|
|
582
|
+
stop(): Promise<void>;
|
|
583
|
+
/** Subclass must implement: send event to the other peer (wire, queue, or in-process broadcast). */
|
|
584
|
+
abstract emit(event: NcpEndpointEvent): void | Promise<void>;
|
|
585
|
+
subscribe(listener: NcpEndpointSubscriber): () => void;
|
|
586
|
+
/** Call when an event is received from the transport; delivers to local subscribers. */
|
|
587
|
+
protected broadcast(event: NcpEndpointEvent): void;
|
|
588
|
+
protected abstract onStart(): Promise<void>;
|
|
589
|
+
protected abstract onStop(): Promise<void>;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Read-only snapshot of conversation state maintained by a state manager.
|
|
594
|
+
*
|
|
595
|
+
* Consumed by UI or other layers; all updates flow through dispatch(event).
|
|
596
|
+
*/
|
|
597
|
+
interface NcpConversationSnapshot {
|
|
598
|
+
/** Ordered list of finalized messages. */
|
|
599
|
+
readonly messages: ReadonlyArray<NcpMessage>;
|
|
600
|
+
/**
|
|
601
|
+
* Message currently being streamed (deltas apply here); null when idle.
|
|
602
|
+
* When message.completed is dispatched, this is appended to messages and cleared.
|
|
603
|
+
*/
|
|
604
|
+
readonly streamingMessage: NcpMessage | null;
|
|
605
|
+
/** Latest error, if any (e.g. from message.failed or endpoint.error). */
|
|
606
|
+
readonly error: NcpError | null;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* State manager that holds conversation state and updates it from NCP events.
|
|
610
|
+
*
|
|
611
|
+
* Feed events via dispatch(); subscribe() to be notified on state changes.
|
|
612
|
+
* Typically used by agent UIs or runtimes to keep a single source of truth for messages.
|
|
613
|
+
*/
|
|
614
|
+
interface NcpConversationStateManager {
|
|
615
|
+
/** Returns the current snapshot. Call after dispatch or in subscribe callback. */
|
|
616
|
+
getSnapshot(): NcpConversationSnapshot;
|
|
617
|
+
/**
|
|
618
|
+
* Applies an NCP event to internal state (messages, streamingMessage, error).
|
|
619
|
+
* Notifies subscribers after the update.
|
|
620
|
+
*/
|
|
621
|
+
dispatch(event: NcpEndpointEvent): void;
|
|
622
|
+
/**
|
|
623
|
+
* Subscribes to state changes. Listener is called after each dispatch that mutates state.
|
|
624
|
+
* Returns an unsubscribe function.
|
|
625
|
+
*/
|
|
626
|
+
subscribe(listener: (snapshot: NcpConversationSnapshot) => void): () => void;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Agent-scenario state manager: extends the generic conversation state manager
|
|
631
|
+
* with explicit handle methods for each NCP event type that affects agent conversation state.
|
|
632
|
+
*
|
|
633
|
+
* Implementations may route dispatch(event) to the corresponding handleXxx(payload)
|
|
634
|
+
* and use these handlers to update messages, streamingMessage, and error.
|
|
635
|
+
*/
|
|
636
|
+
interface NcpAgentConversationStateManager extends NcpConversationStateManager {
|
|
637
|
+
handleMessageRequest(payload: NcpRequestEnvelope): void;
|
|
638
|
+
/** Local peer sent a message (outbound); typically non-streaming. Add to messages. */
|
|
639
|
+
handleMessageSent(payload: NcpMessageSentPayload): void;
|
|
640
|
+
handleMessageAccepted(payload: NcpMessageAcceptedPayload): void;
|
|
641
|
+
handleMessageIncoming(payload: NcpResponseEnvelope): void;
|
|
642
|
+
handleMessageCompleted(payload: NcpCompletedEnvelope): void;
|
|
643
|
+
handleMessageFailed(payload: NcpFailedEnvelope): void;
|
|
644
|
+
handleMessageAbort(payload: NcpMessageAbortPayload): void;
|
|
645
|
+
handleMessageTextStart(payload: NcpTextStartPayload): void;
|
|
646
|
+
handleMessageTextDelta(payload: NcpTextDeltaPayload): void;
|
|
647
|
+
handleMessageTextEnd(payload: NcpTextEndPayload): void;
|
|
648
|
+
handleMessageReasoningStart(payload: NcpReasoningStartPayload): void;
|
|
649
|
+
handleMessageReasoningDelta(payload: NcpReasoningDeltaPayload): void;
|
|
650
|
+
handleMessageReasoningEnd(payload: NcpReasoningEndPayload): void;
|
|
651
|
+
handleMessageToolCallStart(payload: NcpToolCallStartPayload): void;
|
|
652
|
+
handleMessageToolCallArgs(payload: NcpToolCallArgsPayload): void;
|
|
653
|
+
handleMessageToolCallArgsDelta(payload: NcpToolCallArgsDeltaPayload): void;
|
|
654
|
+
handleMessageToolCallEnd(payload: NcpToolCallEndPayload): void;
|
|
655
|
+
handleMessageToolCallResult(payload: NcpToolCallResultPayload): void;
|
|
656
|
+
handleRunStarted(payload: NcpRunStartedPayload): void;
|
|
657
|
+
handleRunFinished(payload: NcpRunFinishedPayload): void;
|
|
658
|
+
handleRunError(payload: NcpRunErrorPayload): void;
|
|
659
|
+
handleRunMetadata(payload: NcpRunMetadataPayload): void;
|
|
660
|
+
handleEndpointError(payload: NcpError): void;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export { AbstractEndpoint, type NcpActionPart, type NcpAgentConversationStateManager, type NcpCardPart, type NcpCompletedEnvelope, type NcpConversationSnapshot, type NcpConversationStateManager, type NcpEndpoint, type NcpEndpointEvent, type NcpEndpointKind, type NcpEndpointLatency, type NcpEndpointManifest, type NcpEndpointSubscriber, type NcpError, type NcpErrorCode, NcpErrorException, type NcpExtensionPart, type NcpFailedEnvelope, type NcpFilePart, type NcpMessage, type NcpMessageAbortPayload, type NcpMessageAcceptedPayload, type NcpMessageDeliveredPayload, type NcpMessagePart, type NcpMessageReactionPayload, type NcpMessageReadPayload, type NcpMessageRecalledPayload, type NcpMessageRole, type NcpMessageSentPayload, type NcpMessageStatus, type NcpPresenceUpdatedPayload, type NcpReasoningDeltaPayload, type NcpReasoningEndPayload, type NcpReasoningPart, type NcpReasoningStartPayload, type NcpRequestEnvelope, type NcpResponseEnvelope, type NcpRichTextPart, type NcpRunErrorPayload, type NcpRunFinishedPayload, type NcpRunMetadataPayload, type NcpRunStartedPayload, type NcpSessionBinding, type NcpSessionContract, type NcpSessionState, type NcpSourcePart, type NcpStepStartPart, type NcpTextDeltaPayload, type NcpTextEndPayload, type NcpTextPart, type NcpTextStartPayload, type NcpToolCallArgsDeltaPayload, type NcpToolCallArgsPayload, type NcpToolCallEndPayload, type NcpToolCallResultPayload, type NcpToolCallStartPayload, type NcpToolInvocationPart, type NcpTypingEndPayload, type NcpTypingStartPayload };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/types/errors.ts
|
|
2
|
+
var NcpErrorException = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
details;
|
|
5
|
+
constructor(code, message, details) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "NcpErrorException";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/endpoint/abstract-endpoint.ts
|
|
14
|
+
var AbstractEndpoint = class {
|
|
15
|
+
started = false;
|
|
16
|
+
listeners = /* @__PURE__ */ new Set();
|
|
17
|
+
async start() {
|
|
18
|
+
if (this.started) return;
|
|
19
|
+
await this.onStart();
|
|
20
|
+
this.started = true;
|
|
21
|
+
}
|
|
22
|
+
async stop() {
|
|
23
|
+
if (!this.started) return;
|
|
24
|
+
await this.onStop();
|
|
25
|
+
this.started = false;
|
|
26
|
+
}
|
|
27
|
+
subscribe(listener) {
|
|
28
|
+
this.listeners.add(listener);
|
|
29
|
+
return () => this.listeners.delete(listener);
|
|
30
|
+
}
|
|
31
|
+
/** Call when an event is received from the transport; delivers to local subscribers. */
|
|
32
|
+
broadcast(event) {
|
|
33
|
+
for (const listener of this.listeners) {
|
|
34
|
+
listener(event);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
AbstractEndpoint,
|
|
40
|
+
NcpErrorException
|
|
41
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextclaw/ncp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "NextClaw Communication Protocol core abstractions and types.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"development": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.17.6",
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
20
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
21
|
+
"eslint": "^8.57.1",
|
|
22
|
+
"eslint-config-prettier": "^9.1.0",
|
|
23
|
+
"prettier": "^3.3.3",
|
|
24
|
+
"tsup": "^8.3.5",
|
|
25
|
+
"typescript": "^5.6.3"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts --format esm --dts --out-dir dist",
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"tsc": "tsc -p tsconfig.json"
|
|
31
|
+
}
|
|
32
|
+
}
|