@nextclaw/ncp 0.1.0 → 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,13 +5,13 @@ Core protocol types and endpoint abstractions for universal communication in Nex
5
5
  ## Build
6
6
 
7
7
  ```bash
8
- pnpm -C packages/nextclaw-ncp build
8
+ pnpm -C packages/ncp-packages/nextclaw-ncp build
9
9
  ```
10
10
 
11
11
  ## Scope
12
12
 
13
13
  - NCP manifest, message, session, and error definitions
14
- - Generic endpoint base class (`AbstractEndpoint`)
14
+ - Endpoint interfaces (`NcpEndpoint`, `NcpAgentServerEndpoint`, `NcpAgentClientEndpoint`)
15
15
 
16
16
  ## Usage
17
17
 
package/dist/index.d.ts CHANGED
@@ -130,7 +130,7 @@ type NcpMessage = {
130
130
  /** Globally unique message identifier. */
131
131
  id: string;
132
132
  /** The session this message belongs to. */
133
- sessionKey: string;
133
+ sessionId: string;
134
134
  role: NcpMessageRole;
135
135
  status: NcpMessageStatus;
136
136
  parts: NcpMessagePart[];
@@ -163,7 +163,7 @@ type NcpEndpointLatency = "realtime" | "seconds" | "minutes" | "hours" | "days";
163
163
  *
164
164
  * Consumers use the manifest for runtime capability discovery —
165
165
  * e.g. whether to show a typing indicator, whether to offer file uploads,
166
- * or whether session resume is available.
166
+ * or whether an existing run can be streamed again.
167
167
  */
168
168
  type NcpEndpointManifest = {
169
169
  /** Category of this endpoint. */
@@ -178,8 +178,8 @@ type NcpEndpointManifest = {
178
178
  supportsAbort: boolean;
179
179
  /** Whether this endpoint can push messages without a prior user request. */
180
180
  supportsProactiveMessages: boolean;
181
- /** Whether a disconnected session can be resumed by the same `sessionKey`. */
182
- supportsSessionResume: boolean;
181
+ /** Whether this endpoint can stream the live events of an active session by `sessionId`. */
182
+ supportsLiveSessionStream: boolean;
183
183
  /**
184
184
  * The subset of `NcpMessagePart` types this endpoint can send or receive.
185
185
  * Using the literal union from `NcpMessagePart["type"]` prevents typos
@@ -215,80 +215,31 @@ type NcpError = {
215
215
  /** Original cause — preserved for debugging but not guaranteed serializable. */
216
216
  cause?: unknown;
217
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
218
 
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. */
219
+ type NcpSessionStatus = "idle" | "running";
220
+ type NcpSessionSummary = {
221
+ sessionId: string;
264
222
  messageCount: number;
265
- /** ISO 8601 timestamp of the last state mutation. */
266
223
  updatedAt: string;
224
+ status?: NcpSessionStatus;
225
+ };
226
+ type ListSessionsOptions = {
227
+ limit?: number;
228
+ cursor?: string;
229
+ };
230
+ type ListMessagesOptions = {
231
+ limit?: number;
232
+ cursor?: string;
267
233
  };
268
234
  /**
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.
235
+ * API for session list, message history, and session lifecycle.
236
+ * Implementations that support persistence can provide this alongside NcpAgentClientEndpoint.
273
237
  */
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>;
238
+ interface NcpSessionApi {
239
+ listSessions(options?: ListSessionsOptions): Promise<NcpSessionSummary[]>;
240
+ listSessionMessages(sessionId: string, options?: ListMessagesOptions): Promise<NcpMessage[]>;
241
+ getSession(sessionId: string): Promise<NcpSessionSummary | null>;
242
+ deleteSession(sessionId: string): Promise<void>;
292
243
  }
293
244
 
294
245
  /**
@@ -299,26 +250,26 @@ interface NcpSessionContract {
299
250
  * instead; endpoints or upper layers choose as needed.
300
251
  */
301
252
  type NcpRequestEnvelope = {
302
- sessionKey: string;
253
+ sessionId: string;
303
254
  message: NcpMessage;
304
255
  correlationId?: string;
305
256
  metadata?: Record<string, unknown>;
306
257
  };
307
258
  /** Payload for message.incoming: message content from the other peer (partial or full). */
308
259
  type NcpResponseEnvelope = {
309
- sessionKey: string;
260
+ sessionId: string;
310
261
  message: NcpMessage;
311
262
  correlationId?: string;
312
263
  metadata?: Record<string, unknown>;
313
264
  };
314
265
  type NcpCompletedEnvelope = {
315
- sessionKey: string;
266
+ sessionId: string;
316
267
  message: NcpMessage;
317
268
  correlationId?: string;
318
269
  metadata?: Record<string, unknown>;
319
270
  };
320
271
  type NcpFailedEnvelope = {
321
- sessionKey: string;
272
+ sessionId: string;
322
273
  messageId?: string;
323
274
  error: NcpError;
324
275
  correlationId?: string;
@@ -329,51 +280,60 @@ type NcpMessageAcceptedPayload = {
329
280
  correlationId?: string;
330
281
  transportId?: string;
331
282
  };
283
+ /** Payload for message.abort: identifies which session's active execution to cancel. */
332
284
  type NcpMessageAbortPayload = {
285
+ sessionId: string;
333
286
  messageId?: string;
334
- correlationId?: string;
287
+ };
288
+ /**
289
+ * Payload for message.stream-request: attach to the live event stream of a session.
290
+ * Used when reconnecting during an active response or attaching another live reader.
291
+ */
292
+ type NcpStreamRequestPayload = {
293
+ sessionId: string;
294
+ metadata?: Record<string, unknown>;
335
295
  };
336
296
  /**
337
297
  * Payload for message.sent: the local peer has sent a message (outbound).
338
298
  * Typically non-streaming; add the message to the local conversation state.
339
299
  */
340
300
  type NcpMessageSentPayload = {
341
- sessionKey: string;
301
+ sessionId: string;
342
302
  message: NcpMessage;
343
303
  metadata?: Record<string, unknown>;
344
304
  };
345
305
  type NcpTypingStartPayload = {
346
- sessionKey: string;
306
+ sessionId: string;
347
307
  /** Participant who is typing (human user or bot/assistant). */
348
308
  userId?: string;
349
309
  };
350
310
  type NcpTypingEndPayload = {
351
- sessionKey: string;
311
+ sessionId: string;
352
312
  /** Participant who stopped typing (human user or bot/assistant). */
353
313
  userId?: string;
354
314
  };
355
315
  type NcpPresenceUpdatedPayload = {
356
- sessionKey: string;
316
+ sessionId: string;
357
317
  /** Participant this presence applies to (human user or bot/assistant). */
358
318
  userId?: string;
359
319
  status: "online" | "offline" | "away";
360
320
  };
361
321
  type NcpMessageReadPayload = {
362
- sessionKey: string;
322
+ sessionId: string;
363
323
  messageId: string;
364
324
  readAt?: string;
365
325
  readerId?: string;
366
326
  };
367
327
  type NcpMessageDeliveredPayload = {
368
- sessionKey: string;
328
+ sessionId: string;
369
329
  messageId: string;
370
330
  };
371
331
  type NcpMessageRecalledPayload = {
372
- sessionKey: string;
332
+ sessionId: string;
373
333
  messageId: string;
374
334
  };
375
335
  type NcpMessageReactionPayload = {
376
- sessionKey: string;
336
+ sessionId: string;
377
337
  messageId: string;
378
338
  reaction: string;
379
339
  added: boolean;
@@ -381,214 +341,500 @@ type NcpMessageReactionPayload = {
381
341
  userId?: string;
382
342
  };
383
343
  type NcpRunStartedPayload = {
384
- sessionKey?: string;
344
+ sessionId?: string;
385
345
  messageId?: string;
386
346
  threadId?: string;
387
347
  runId?: string;
388
348
  };
389
349
  type NcpRunFinishedPayload = {
390
- sessionKey?: string;
350
+ sessionId?: string;
391
351
  messageId?: string;
392
352
  threadId?: string;
393
353
  runId?: string;
394
354
  };
395
355
  type NcpRunErrorPayload = {
396
- sessionKey?: string;
356
+ sessionId?: string;
397
357
  messageId?: string;
398
358
  error?: string;
399
359
  threadId?: string;
400
360
  runId?: string;
401
361
  };
402
362
  type NcpRunMetadataPayload = {
403
- sessionKey?: string;
363
+ sessionId?: string;
404
364
  messageId?: string;
405
365
  runId?: string;
406
366
  metadata: Record<string, unknown>;
407
367
  };
408
368
  type NcpTextStartPayload = {
409
- sessionKey: string;
369
+ sessionId: string;
410
370
  messageId: string;
411
371
  };
412
372
  type NcpTextDeltaPayload = {
413
- sessionKey: string;
373
+ sessionId: string;
414
374
  messageId: string;
415
375
  delta: string;
416
376
  };
417
377
  type NcpTextEndPayload = {
418
- sessionKey: string;
378
+ sessionId: string;
419
379
  messageId: string;
420
380
  };
421
381
  type NcpReasoningStartPayload = {
422
- sessionKey: string;
382
+ sessionId: string;
423
383
  messageId: string;
424
384
  };
425
385
  type NcpReasoningDeltaPayload = {
426
- sessionKey: string;
386
+ sessionId: string;
427
387
  messageId: string;
428
388
  delta: string;
429
389
  };
430
390
  type NcpReasoningEndPayload = {
431
- sessionKey: string;
391
+ sessionId: string;
432
392
  messageId: string;
433
393
  };
434
394
  type NcpToolCallStartPayload = {
435
- sessionKey: string;
395
+ sessionId: string;
436
396
  messageId?: string;
437
397
  toolCallId: string;
438
398
  toolName: string;
439
399
  };
440
400
  type NcpToolCallArgsPayload = {
441
- sessionKey: string;
401
+ sessionId: string;
442
402
  toolCallId: string;
443
403
  args: string;
444
404
  };
445
405
  type NcpToolCallArgsDeltaPayload = {
446
- sessionKey: string;
406
+ sessionId: string;
447
407
  messageId?: string;
448
408
  toolCallId: string;
449
409
  delta: string;
450
410
  };
451
411
  type NcpToolCallEndPayload = {
452
- sessionKey: string;
412
+ sessionId: string;
453
413
  toolCallId: string;
454
414
  };
455
415
  type NcpToolCallResultPayload = {
456
- sessionKey: string;
416
+ sessionId: string;
457
417
  toolCallId: string;
458
418
  content: unknown;
459
419
  };
420
+ declare enum NcpEventType {
421
+ EndpointReady = "endpoint.ready",
422
+ EndpointError = "endpoint.error",
423
+ MessageRequest = "message.request",
424
+ MessageStreamRequest = "message.stream-request",
425
+ MessageSent = "message.sent",
426
+ MessageAccepted = "message.accepted",
427
+ MessageIncoming = "message.incoming",
428
+ MessageCompleted = "message.completed",
429
+ MessageFailed = "message.failed",
430
+ MessageAbort = "message.abort",
431
+ MessageTextStart = "message.text-start",
432
+ MessageTextDelta = "message.text-delta",
433
+ MessageTextEnd = "message.text-end",
434
+ MessageReasoningStart = "message.reasoning-start",
435
+ MessageReasoningDelta = "message.reasoning-delta",
436
+ MessageReasoningEnd = "message.reasoning-end",
437
+ MessageToolCallStart = "message.tool-call-start",
438
+ MessageToolCallArgs = "message.tool-call-args",
439
+ MessageToolCallArgsDelta = "message.tool-call-args-delta",
440
+ MessageToolCallEnd = "message.tool-call-end",
441
+ MessageToolCallResult = "message.tool-call-result",
442
+ MessageRead = "message.read",
443
+ MessageDelivered = "message.delivered",
444
+ MessageRecalled = "message.recalled",
445
+ MessageReaction = "message.reaction",
446
+ RunStarted = "run.started",
447
+ RunFinished = "run.finished",
448
+ RunError = "run.error",
449
+ RunMetadata = "run.metadata",
450
+ TypingStart = "typing.start",
451
+ TypingEnd = "typing.end",
452
+ PresenceUpdated = "presence.updated"
453
+ }
460
454
  type NcpEndpointEvent = {
461
- type: "endpoint.ready";
455
+ type: NcpEventType.EndpointReady;
462
456
  } | {
463
- type: "message.request";
457
+ type: NcpEventType.MessageRequest;
464
458
  payload: NcpRequestEnvelope;
465
459
  } | {
466
- type: "message.sent";
460
+ type: NcpEventType.MessageStreamRequest;
461
+ payload: NcpStreamRequestPayload;
462
+ } | {
463
+ type: NcpEventType.MessageSent;
467
464
  payload: NcpMessageSentPayload;
468
465
  } | {
469
- type: "message.accepted";
466
+ type: NcpEventType.MessageAccepted;
470
467
  payload: NcpMessageAcceptedPayload;
471
468
  } | {
472
- type: "message.incoming";
469
+ type: NcpEventType.MessageIncoming;
473
470
  payload: NcpResponseEnvelope;
474
471
  } | {
475
- type: "message.completed";
472
+ type: NcpEventType.MessageCompleted;
476
473
  payload: NcpCompletedEnvelope;
477
474
  } | {
478
- type: "message.failed";
475
+ type: NcpEventType.MessageFailed;
479
476
  payload: NcpFailedEnvelope;
480
477
  } | {
481
- type: "message.abort";
478
+ type: NcpEventType.MessageAbort;
482
479
  payload: NcpMessageAbortPayload;
483
480
  } | {
484
- type: "endpoint.error";
481
+ type: NcpEventType.EndpointError;
485
482
  payload: NcpError;
486
483
  } | {
487
- type: "run.started";
484
+ type: NcpEventType.RunStarted;
488
485
  payload: NcpRunStartedPayload;
489
486
  } | {
490
- type: "run.finished";
487
+ type: NcpEventType.RunFinished;
491
488
  payload: NcpRunFinishedPayload;
492
489
  } | {
493
- type: "run.error";
490
+ type: NcpEventType.RunError;
494
491
  payload: NcpRunErrorPayload;
495
492
  } | {
496
- type: "run.metadata";
493
+ type: NcpEventType.RunMetadata;
497
494
  payload: NcpRunMetadataPayload;
498
495
  } | {
499
- type: "message.text-start";
496
+ type: NcpEventType.MessageTextStart;
500
497
  payload: NcpTextStartPayload;
501
498
  } | {
502
- type: "message.text-delta";
499
+ type: NcpEventType.MessageTextDelta;
503
500
  payload: NcpTextDeltaPayload;
504
501
  } | {
505
- type: "message.text-end";
502
+ type: NcpEventType.MessageTextEnd;
506
503
  payload: NcpTextEndPayload;
507
504
  } | {
508
- type: "message.reasoning-start";
505
+ type: NcpEventType.MessageReasoningStart;
509
506
  payload: NcpReasoningStartPayload;
510
507
  } | {
511
- type: "message.reasoning-delta";
508
+ type: NcpEventType.MessageReasoningDelta;
512
509
  payload: NcpReasoningDeltaPayload;
513
510
  } | {
514
- type: "message.reasoning-end";
511
+ type: NcpEventType.MessageReasoningEnd;
515
512
  payload: NcpReasoningEndPayload;
516
513
  } | {
517
- type: "message.tool-call-start";
514
+ type: NcpEventType.MessageToolCallStart;
518
515
  payload: NcpToolCallStartPayload;
519
516
  } | {
520
- type: "message.tool-call-args";
517
+ type: NcpEventType.MessageToolCallArgs;
521
518
  payload: NcpToolCallArgsPayload;
522
519
  } | {
523
- type: "message.tool-call-args-delta";
520
+ type: NcpEventType.MessageToolCallArgsDelta;
524
521
  payload: NcpToolCallArgsDeltaPayload;
525
522
  } | {
526
- type: "message.tool-call-end";
523
+ type: NcpEventType.MessageToolCallEnd;
527
524
  payload: NcpToolCallEndPayload;
528
525
  } | {
529
- type: "message.tool-call-result";
526
+ type: NcpEventType.MessageToolCallResult;
530
527
  payload: NcpToolCallResultPayload;
531
528
  } | {
532
- type: "typing.start";
529
+ type: NcpEventType.TypingStart;
533
530
  payload: NcpTypingStartPayload;
534
531
  } | {
535
- type: "typing.end";
532
+ type: NcpEventType.TypingEnd;
536
533
  payload: NcpTypingEndPayload;
537
534
  } | {
538
- type: "presence.updated";
535
+ type: NcpEventType.PresenceUpdated;
539
536
  payload: NcpPresenceUpdatedPayload;
540
537
  } | {
541
- type: "message.read";
538
+ type: NcpEventType.MessageRead;
542
539
  payload: NcpMessageReadPayload;
543
540
  } | {
544
- type: "message.delivered";
541
+ type: NcpEventType.MessageDelivered;
545
542
  payload: NcpMessageDeliveredPayload;
546
543
  } | {
547
- type: "message.recalled";
544
+ type: NcpEventType.MessageRecalled;
548
545
  payload: NcpMessageRecalledPayload;
549
546
  } | {
550
- type: "message.reaction";
547
+ type: NcpEventType.MessageReaction;
551
548
  payload: NcpMessageReactionPayload;
552
549
  };
553
550
  type NcpEndpointSubscriber = (event: NcpEndpointEvent) => void;
554
551
 
552
+ /**
553
+ * Run-related types: snapshot state and run.metadata schema conventions.
554
+ * Not event payloads — event payloads live in events.ts.
555
+ */
556
+ /** Current run state for agent snapshot. Used by UI for run status and abort. */
557
+ type NcpRunContext = {
558
+ runId: string | null;
559
+ sessionId?: string;
560
+ abortDisabledReason?: string | null;
561
+ };
562
+ /** Schema for run.metadata.metadata when kind is "ready" (run started, backend ready). */
563
+ type NcpRunReadyMetadata = {
564
+ kind: "ready";
565
+ runId?: string;
566
+ sessionId?: string;
567
+ supportsAbort?: boolean;
568
+ abortDisabledReason?: string;
569
+ };
570
+ /** Schema for run.metadata.metadata when kind is "final" (run finished). */
571
+ type NcpRunFinalMetadata = {
572
+ kind: "final";
573
+ sessionId?: string;
574
+ };
575
+
555
576
  /**
556
577
  * Core interface every NCP endpoint adapter must implement.
557
578
  *
579
+ * An endpoint is a named, lifecycle-managed communication channel.
558
580
  * Single primitive: emit(event) to send, subscribe(listener) to receive.
559
581
  * Event types and payloads are defined in events.ts (aligned with agent-chat).
582
+ *
583
+ * @example
584
+ * const endpoint: NcpEndpoint = new MyAgentEndpoint(options);
585
+ * await endpoint.start();
586
+ * endpoint.subscribe((event) => { ... });
587
+ * await endpoint.emit({ type: NcpEventType.MessageRequest, payload: envelope });
560
588
  */
561
589
  interface NcpEndpoint {
590
+ /** Static capability declaration — available before `start()` is called. */
562
591
  readonly manifest: NcpEndpointManifest;
592
+ /**
593
+ * Initializes the endpoint (opens connections, authenticates, etc.).
594
+ * Must be called before `emit`. Idempotent — safe to call more than once.
595
+ */
563
596
  start(): Promise<void>;
597
+ /**
598
+ * Gracefully shuts down the endpoint and releases resources.
599
+ * Idempotent — safe to call more than once.
600
+ */
564
601
  stop(): Promise<void>;
565
- emit(event: NcpEndpointEvent): void | Promise<void>;
566
- subscribe(listener: (event: NcpEndpointEvent) => void): () => void;
602
+ /**
603
+ * Sends an event to the remote participant (or broadcasts to local subscribers).
604
+ *
605
+ * For outbound events (e.g. message.request, message.abort), the implementation
606
+ * forwards to the wire. For symmetric in-process setups, it may broadcast locally.
607
+ */
608
+ emit(event: NcpEndpointEvent): Promise<void>;
609
+ /**
610
+ * Subscribes to endpoint events.
611
+ *
612
+ * @param listener - Called for every event emitted by this endpoint.
613
+ * @returns An unsubscribe function. Call it to stop receiving events.
614
+ *
615
+ * @example
616
+ * const unsubscribe = endpoint.subscribe((event) => {
617
+ * if (event.type === NcpEventType.MessageCompleted) handleReply(event.payload);
618
+ * });
619
+ * unsubscribe();
620
+ */
621
+ subscribe(listener: NcpEndpointSubscriber): () => void;
567
622
  }
568
623
 
569
624
  /**
570
- * Base class for NCP endpoint adapters.
625
+ * Client-side endpoint for agent chat: initiates requests and can cancel in-flight runs.
571
626
  *
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.
627
+ * Extends `NcpEndpoint` with role-specific methods. Use this on the caller side
628
+ * (e.g. frontend, CLI) that sends user messages and receives agent responses.
576
629
  */
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>;
630
+ interface NcpAgentClientEndpoint extends NcpEndpoint {
631
+ /** Sends a new message request to the agent. Emits `message.request`. */
632
+ send(envelope: NcpRequestEnvelope): Promise<void>;
633
+ /** Attaches to the live event stream of a session. Emits `message.stream-request`. */
634
+ stream(payload: NcpStreamRequestPayload): Promise<void>;
635
+ /** Aborts the active execution of a session. Emits `message.abort`. */
636
+ abort(payload: NcpMessageAbortPayload): Promise<void>;
590
637
  }
591
638
 
639
+ /**
640
+ * Agent server-side endpoint: receives requests and produces downstream events.
641
+ *
642
+ * Extends `NcpEndpoint` with a manifest constraint (`endpointKind: "agent"`).
643
+ * Use this on the server side that handles send/stream/abort requests and emits
644
+ * downstream events (`message.incoming`, streaming deltas, `message.completed`, etc.).
645
+ */
646
+ interface NcpAgentServerEndpoint extends NcpEndpoint {
647
+ readonly manifest: NcpEndpointManifest & {
648
+ endpointKind: "agent";
649
+ };
650
+ /** Handles a new message request from client side and yields produced events. */
651
+ send(envelope: NcpRequestEnvelope, options?: {
652
+ signal?: AbortSignal;
653
+ }): AsyncIterable<NcpEndpointEvent>;
654
+ /** Streams live events for an active session and yields produced events. */
655
+ stream(payload: NcpStreamRequestPayload, options?: {
656
+ signal?: AbortSignal;
657
+ }): AsyncIterable<NcpEndpointEvent>;
658
+ /** Aborts the active execution of a session on server side. */
659
+ abort(payload: NcpMessageAbortPayload): Promise<void>;
660
+ /**
661
+ * Publishes server-downstream events (typically sent to frontend subscribers/transports).
662
+ * For handling client requests, prefer `send` / `stream` / `abort`.
663
+ */
664
+ emit(event: NcpEndpointEvent): Promise<void>;
665
+ }
666
+
667
+ type NcpAgentRunInput = {
668
+ sessionId: string;
669
+ messages: ReadonlyArray<NcpMessage>;
670
+ correlationId?: string;
671
+ };
672
+ type NcpAgentRunOptions = {
673
+ signal?: AbortSignal;
674
+ };
675
+ interface NcpAgentRuntime {
676
+ run(input: NcpAgentRunInput, options?: NcpAgentRunOptions): AsyncIterable<NcpEndpointEvent>;
677
+ }
678
+
679
+ type OpenAIContentPart = {
680
+ type: "text";
681
+ text: string;
682
+ } | {
683
+ type: "image_url";
684
+ image_url: {
685
+ url: string;
686
+ detail?: "low" | "high" | "auto";
687
+ };
688
+ };
689
+ type OpenAIToolCall = {
690
+ id: string;
691
+ type: "function";
692
+ function: {
693
+ name: string;
694
+ arguments: string;
695
+ };
696
+ };
697
+ type OpenAIChatMessage = {
698
+ role: "system";
699
+ content: string;
700
+ } | {
701
+ role: "user";
702
+ content: string | OpenAIContentPart[];
703
+ } | {
704
+ role: "assistant";
705
+ content?: string | null;
706
+ tool_calls?: OpenAIToolCall[];
707
+ } | {
708
+ role: "tool";
709
+ content: string;
710
+ tool_call_id: string;
711
+ };
712
+ type OpenAITool = {
713
+ type: "function";
714
+ function: {
715
+ name: string;
716
+ description?: string;
717
+ parameters?: Record<string, unknown>;
718
+ };
719
+ };
720
+ type OpenAIToolCallDelta = {
721
+ index?: number;
722
+ id?: string;
723
+ type?: "function";
724
+ function?: {
725
+ name?: string;
726
+ arguments?: string;
727
+ };
728
+ };
729
+ type OpenAIChatChunk = {
730
+ id?: string;
731
+ choices?: Array<{
732
+ index?: number;
733
+ delta?: {
734
+ content?: string | null;
735
+ tool_calls?: OpenAIToolCallDelta[];
736
+ reasoning_content?: string;
737
+ reasoning?: string;
738
+ };
739
+ finish_reason?: string | null;
740
+ }>;
741
+ usage?: {
742
+ prompt_tokens?: number;
743
+ completion_tokens?: number;
744
+ total_tokens?: number;
745
+ };
746
+ };
747
+ type NcpLLMApiInput = {
748
+ messages: OpenAIChatMessage[];
749
+ tools?: OpenAITool[];
750
+ model?: string;
751
+ max_tokens?: number;
752
+ };
753
+ type NcpLLMApiOptions = {
754
+ signal?: AbortSignal;
755
+ temperature?: number;
756
+ };
757
+ interface NcpLLMApi {
758
+ generate(input: NcpLLMApiInput, options?: NcpLLMApiOptions): AsyncIterable<OpenAIChatChunk>;
759
+ }
760
+
761
+ type NcpContextPrepareOptions = {
762
+ sessionMessages?: ReadonlyArray<NcpMessage>;
763
+ systemPrompt?: string;
764
+ maxMessages?: number;
765
+ };
766
+ interface NcpContextBuilder {
767
+ prepare(input: NcpAgentRunInput, options?: NcpContextPrepareOptions): NcpLLMApiInput;
768
+ }
769
+
770
+ type NcpToolDefinition = {
771
+ name: string;
772
+ description?: string;
773
+ parameters?: Record<string, unknown>;
774
+ };
775
+ interface NcpTool {
776
+ readonly name: string;
777
+ readonly description?: string;
778
+ readonly parameters?: Record<string, unknown>;
779
+ execute(args: unknown): Promise<unknown>;
780
+ }
781
+ type NcpToolCallResult = {
782
+ toolCallId: string;
783
+ toolName: string;
784
+ args: unknown;
785
+ result: unknown;
786
+ };
787
+ interface NcpToolRegistry {
788
+ listTools(): ReadonlyArray<NcpTool>;
789
+ getTool(name: string): NcpTool | undefined;
790
+ getToolDefinitions(): ReadonlyArray<NcpToolDefinition>;
791
+ execute(toolCallId: string, toolName: string, args: unknown): Promise<unknown>;
792
+ }
793
+
794
+ type NcpEncodeContext = {
795
+ sessionId: string;
796
+ messageId: string;
797
+ runId: string;
798
+ correlationId?: string;
799
+ };
800
+ interface NcpStreamEncoder {
801
+ encode(stream: AsyncIterable<OpenAIChatChunk>, context: NcpEncodeContext): AsyncIterable<NcpEndpointEvent>;
802
+ }
803
+
804
+ type NcpPendingToolCall = {
805
+ toolCallId: string;
806
+ toolName: string;
807
+ args: unknown;
808
+ };
809
+ interface NcpRoundBuffer {
810
+ appendText(delta: string): void;
811
+ getText(): string;
812
+ appendToolCall(result: NcpToolCallResult): void;
813
+ getToolCalls(): ReadonlyArray<NcpToolCallResult>;
814
+ startToolCall(toolCallId: string, toolName: string): void;
815
+ appendToolCallArgs(args: unknown): void;
816
+ consumePendingToolCall(): NcpPendingToolCall | null;
817
+ clear(): void;
818
+ }
819
+
820
+ type NcpAgentRunSendOptions = {
821
+ signal?: AbortSignal;
822
+ };
823
+ type NcpAgentRunStreamOptions = {
824
+ signal?: AbortSignal;
825
+ };
826
+ interface NcpAgentRunApi {
827
+ send(envelope: NcpRequestEnvelope, options?: NcpAgentRunSendOptions): AsyncIterable<NcpEndpointEvent>;
828
+ stream(payload: NcpStreamRequestPayload, options?: NcpAgentRunStreamOptions): AsyncIterable<NcpEndpointEvent>;
829
+ abort(payload: NcpMessageAbortPayload): Promise<void>;
830
+ }
831
+ type NcpAgentStreamProvider = {
832
+ stream(params: {
833
+ payload: NcpStreamRequestPayload;
834
+ signal: AbortSignal;
835
+ }): AsyncIterable<NcpEndpointEvent>;
836
+ };
837
+
592
838
  /**
593
839
  * Read-only snapshot of conversation state maintained by a state manager.
594
840
  *
@@ -605,6 +851,13 @@ interface NcpConversationSnapshot {
605
851
  /** Latest error, if any (e.g. from message.failed or endpoint.error). */
606
852
  readonly error: NcpError | null;
607
853
  }
854
+ /**
855
+ * Agent snapshot: extends base snapshot with active run state.
856
+ * Use for UI run status, abort button enable/disable, and abort payload.
857
+ */
858
+ interface NcpAgentConversationSnapshot extends NcpConversationSnapshot {
859
+ readonly activeRun: NcpRunContext | null;
860
+ }
608
861
  /**
609
862
  * State manager that holds conversation state and updates it from NCP events.
610
863
  *
@@ -618,7 +871,7 @@ interface NcpConversationStateManager {
618
871
  * Applies an NCP event to internal state (messages, streamingMessage, error).
619
872
  * Notifies subscribers after the update.
620
873
  */
621
- dispatch(event: NcpEndpointEvent): void;
874
+ dispatch(event: NcpEndpointEvent): Promise<void>;
622
875
  /**
623
876
  * Subscribes to state changes. Listener is called after each dispatch that mutates state.
624
877
  * Returns an unsubscribe function.
@@ -626,6 +879,11 @@ interface NcpConversationStateManager {
626
879
  subscribe(listener: (snapshot: NcpConversationSnapshot) => void): () => void;
627
880
  }
628
881
 
882
+ type NcpAgentConversationHydrationParams = {
883
+ sessionId: string;
884
+ messages: ReadonlyArray<NcpMessage>;
885
+ activeRun?: NcpRunContext | null;
886
+ };
629
887
  /**
630
888
  * Agent-scenario state manager: extends the generic conversation state manager
631
889
  * with explicit handle methods for each NCP event type that affects agent conversation state.
@@ -634,13 +892,11 @@ interface NcpConversationStateManager {
634
892
  * and use these handlers to update messages, streamingMessage, and error.
635
893
  */
636
894
  interface NcpAgentConversationStateManager extends NcpConversationStateManager {
637
- handleMessageRequest(payload: NcpRequestEnvelope): void;
895
+ getSnapshot(): NcpAgentConversationSnapshot;
896
+ reset(): void;
897
+ hydrate(payload: NcpAgentConversationHydrationParams): void;
638
898
  /** Local peer sent a message (outbound); typically non-streaming. Add to messages. */
639
899
  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
900
  handleMessageAbort(payload: NcpMessageAbortPayload): void;
645
901
  handleMessageTextStart(payload: NcpTextStartPayload): void;
646
902
  handleMessageTextDelta(payload: NcpTextDeltaPayload): void;
@@ -660,4 +916,4 @@ interface NcpAgentConversationStateManager extends NcpConversationStateManager {
660
916
  handleEndpointError(payload: NcpError): void;
661
917
  }
662
918
 
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 };
919
+ export { type ListMessagesOptions, type ListSessionsOptions, type NcpActionPart, type NcpAgentClientEndpoint, type NcpAgentConversationHydrationParams, type NcpAgentConversationSnapshot, type NcpAgentConversationStateManager, type NcpAgentRunApi, type NcpAgentRunInput, type NcpAgentRunOptions, type NcpAgentRunSendOptions, type NcpAgentRunStreamOptions, type NcpAgentRuntime, type NcpAgentServerEndpoint, type NcpAgentStreamProvider, type NcpCardPart, type NcpCompletedEnvelope, type NcpContextBuilder, type NcpContextPrepareOptions, type NcpConversationSnapshot, type NcpConversationStateManager, type NcpEncodeContext, type NcpEndpoint, type NcpEndpointEvent, type NcpEndpointKind, type NcpEndpointLatency, type NcpEndpointManifest, type NcpEndpointSubscriber, type NcpError, type NcpErrorCode, NcpEventType, type NcpExtensionPart, type NcpFailedEnvelope, type NcpFilePart, type NcpLLMApi, type NcpLLMApiInput, type NcpLLMApiOptions, type NcpMessage, type NcpMessageAbortPayload, type NcpMessageAcceptedPayload, type NcpMessageDeliveredPayload, type NcpMessagePart, type NcpMessageReactionPayload, type NcpMessageReadPayload, type NcpMessageRecalledPayload, type NcpMessageRole, type NcpMessageSentPayload, type NcpMessageStatus, type NcpPendingToolCall, type NcpPresenceUpdatedPayload, type NcpReasoningDeltaPayload, type NcpReasoningEndPayload, type NcpReasoningPart, type NcpReasoningStartPayload, type NcpRequestEnvelope, type NcpResponseEnvelope, type NcpRichTextPart, type NcpRoundBuffer, type NcpRunContext, type NcpRunErrorPayload, type NcpRunFinalMetadata, type NcpRunFinishedPayload, type NcpRunMetadataPayload, type NcpRunReadyMetadata, type NcpRunStartedPayload, type NcpSessionApi, type NcpSessionStatus, type NcpSessionSummary, type NcpSourcePart, type NcpStepStartPart, type NcpStreamEncoder, type NcpStreamRequestPayload, type NcpTextDeltaPayload, type NcpTextEndPayload, type NcpTextPart, type NcpTextStartPayload, type NcpTool, type NcpToolCallArgsDeltaPayload, type NcpToolCallArgsPayload, type NcpToolCallEndPayload, type NcpToolCallResult, type NcpToolCallResultPayload, type NcpToolCallStartPayload, type NcpToolDefinition, type NcpToolInvocationPart, type NcpToolRegistry, type NcpTypingEndPayload, type NcpTypingStartPayload, type OpenAIChatChunk, type OpenAIChatMessage, type OpenAIContentPart, type OpenAITool, type OpenAIToolCall, type OpenAIToolCallDelta };
package/dist/index.js CHANGED
@@ -1,41 +1,39 @@
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
- };
1
+ // src/types/events.ts
2
+ var NcpEventType = /* @__PURE__ */ ((NcpEventType2) => {
3
+ NcpEventType2["EndpointReady"] = "endpoint.ready";
4
+ NcpEventType2["EndpointError"] = "endpoint.error";
5
+ NcpEventType2["MessageRequest"] = "message.request";
6
+ NcpEventType2["MessageStreamRequest"] = "message.stream-request";
7
+ NcpEventType2["MessageSent"] = "message.sent";
8
+ NcpEventType2["MessageAccepted"] = "message.accepted";
9
+ NcpEventType2["MessageIncoming"] = "message.incoming";
10
+ NcpEventType2["MessageCompleted"] = "message.completed";
11
+ NcpEventType2["MessageFailed"] = "message.failed";
12
+ NcpEventType2["MessageAbort"] = "message.abort";
13
+ NcpEventType2["MessageTextStart"] = "message.text-start";
14
+ NcpEventType2["MessageTextDelta"] = "message.text-delta";
15
+ NcpEventType2["MessageTextEnd"] = "message.text-end";
16
+ NcpEventType2["MessageReasoningStart"] = "message.reasoning-start";
17
+ NcpEventType2["MessageReasoningDelta"] = "message.reasoning-delta";
18
+ NcpEventType2["MessageReasoningEnd"] = "message.reasoning-end";
19
+ NcpEventType2["MessageToolCallStart"] = "message.tool-call-start";
20
+ NcpEventType2["MessageToolCallArgs"] = "message.tool-call-args";
21
+ NcpEventType2["MessageToolCallArgsDelta"] = "message.tool-call-args-delta";
22
+ NcpEventType2["MessageToolCallEnd"] = "message.tool-call-end";
23
+ NcpEventType2["MessageToolCallResult"] = "message.tool-call-result";
24
+ NcpEventType2["MessageRead"] = "message.read";
25
+ NcpEventType2["MessageDelivered"] = "message.delivered";
26
+ NcpEventType2["MessageRecalled"] = "message.recalled";
27
+ NcpEventType2["MessageReaction"] = "message.reaction";
28
+ NcpEventType2["RunStarted"] = "run.started";
29
+ NcpEventType2["RunFinished"] = "run.finished";
30
+ NcpEventType2["RunError"] = "run.error";
31
+ NcpEventType2["RunMetadata"] = "run.metadata";
32
+ NcpEventType2["TypingStart"] = "typing.start";
33
+ NcpEventType2["TypingEnd"] = "typing.end";
34
+ NcpEventType2["PresenceUpdated"] = "presence.updated";
35
+ return NcpEventType2;
36
+ })(NcpEventType || {});
38
37
  export {
39
- AbstractEndpoint,
40
- NcpErrorException
38
+ NcpEventType
41
39
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "NextClaw Communication Protocol core abstractions and types.",
6
6
  "type": "module",
@@ -16,10 +16,6 @@
16
16
  ],
17
17
  "devDependencies": {
18
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
19
  "prettier": "^3.3.3",
24
20
  "tsup": "^8.3.5",
25
21
  "typescript": "^5.6.3"