@nextclaw/ncp-http-agent-server 0.3.12 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +43 -40
  2. package/dist/index.js +297 -383
  3. package/package.json +3 -4
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { NcpStreamRequestPayload, NcpEndpointEvent, NcpAgentClientEndpoint } from '@nextclaw/ncp';
2
- import { Hono } from 'hono';
1
+ import { NcpAgentClientEndpoint, NcpEndpointEvent, NcpStreamRequestPayload } from "@nextclaw/ncp";
2
+ import { Hono } from "hono";
3
3
 
4
+ //#region src/types.d.ts
4
5
  /**
5
6
  * Streams live session events for `/stream`.
6
7
  *
@@ -18,57 +19,59 @@ import { Hono } from 'hono';
18
19
  * for that active session.
19
20
  */
20
21
  type NcpHttpAgentStreamProvider = {
21
- stream(params: {
22
- payload: NcpStreamRequestPayload;
23
- signal: AbortSignal;
24
- }): AsyncIterable<NcpEndpointEvent>;
22
+ stream(params: {
23
+ payload: NcpStreamRequestPayload;
24
+ signal: AbortSignal;
25
+ }): AsyncIterable<NcpEndpointEvent>;
25
26
  };
26
27
  type NcpHttpAgentServerOptions = {
27
- /** Client endpoint to forward requests to (in-process adapter or remote HTTP client). */
28
- agentClientEndpoint: NcpAgentClientEndpoint;
29
- basePath?: string;
30
- /**
31
- * Optional forward-stream timeout in milliseconds.
32
- * When omitted or set to a non-positive value, no server-side timeout is applied.
33
- */
34
- requestTimeoutMs?: number | null;
35
- /**
36
- * Optional. When set, `/stream` serves live session events from streamProvider instead of
37
- * forwarding to the agent.
38
- * When not set, forwards `message.stream-request` to the agent.
39
- */
40
- streamProvider?: NcpHttpAgentStreamProvider;
28
+ /** Client endpoint to forward requests to (in-process adapter or remote HTTP client). */agentClientEndpoint: NcpAgentClientEndpoint;
29
+ basePath?: string;
30
+ /**
31
+ * Optional forward-stream timeout in milliseconds.
32
+ * When omitted or set to a non-positive value, no server-side timeout is applied.
33
+ */
34
+ requestTimeoutMs?: number | null;
35
+ /**
36
+ * Optional. When set, `/stream` serves live session events from streamProvider instead of
37
+ * forwarding to the agent.
38
+ * When not set, forwards `message.stream-request` to the agent.
39
+ */
40
+ streamProvider?: NcpHttpAgentStreamProvider;
41
41
  };
42
-
42
+ //#endregion
43
+ //#region src/handler-interface.d.ts
43
44
  /** Framework-agnostic HTTP handler interface for NCP agent routes. */
44
45
  interface NcpHttpAgentHandler {
45
- handleSend(request: Request): Promise<Response>;
46
- handleStream(request: Request): Promise<Response>;
47
- handleAbort(request: Request): Promise<Response>;
46
+ handleSend(request: Request): Promise<Response>;
47
+ handleStream(request: Request): Promise<Response>;
48
+ handleAbort(request: Request): Promise<Response>;
48
49
  }
49
50
  type NcpHttpAgentHandlerOptions = {
50
- agentClientEndpoint: NcpAgentClientEndpoint;
51
- /**
52
- * Optional. When set, `/stream` serves stored events instead of forwarding to the agent.
53
- * See NcpHttpAgentStreamProvider for details.
54
- */
55
- streamProvider?: NcpHttpAgentStreamProvider;
56
- timeoutMs: number | null;
51
+ agentClientEndpoint: NcpAgentClientEndpoint;
52
+ /**
53
+ * Optional. When set, `/stream` serves stored events instead of forwarding to the agent.
54
+ * See NcpHttpAgentStreamProvider for details.
55
+ */
56
+ streamProvider?: NcpHttpAgentStreamProvider;
57
+ timeoutMs: number | null;
57
58
  };
58
-
59
+ //#endregion
60
+ //#region src/controller.d.ts
59
61
  /**
60
62
  * Framework-agnostic controller for NCP agent HTTP routes.
61
63
  * Forwards /send and /stream to agentClientEndpoint; /stream uses streamProvider when set.
62
64
  */
63
65
  declare class NcpHttpAgentController implements NcpHttpAgentHandler {
64
- private readonly options;
65
- constructor(options: NcpHttpAgentHandlerOptions);
66
- handleSend(request: Request): Promise<Response>;
67
- handleStream(request: Request): Promise<Response>;
68
- handleAbort(request: Request): Promise<Response>;
66
+ private readonly options;
67
+ constructor(options: NcpHttpAgentHandlerOptions);
68
+ handleSend(request: Request): Promise<Response>;
69
+ handleStream(request: Request): Promise<Response>;
70
+ handleAbort(request: Request): Promise<Response>;
69
71
  }
70
-
72
+ //#endregion
73
+ //#region src/router.d.ts
71
74
  declare function createNcpHttpAgentRouter(options: NcpHttpAgentServerOptions): Hono;
72
75
  declare function mountNcpHttpAgentRoutes(app: Hono, options: NcpHttpAgentServerOptions): void;
73
-
74
- export { NcpHttpAgentController, type NcpHttpAgentHandler, type NcpHttpAgentHandlerOptions, type NcpHttpAgentServerOptions, type NcpHttpAgentStreamProvider, createNcpHttpAgentRouter, mountNcpHttpAgentRoutes };
76
+ //#endregion
77
+ export { NcpHttpAgentController, type NcpHttpAgentHandler, type NcpHttpAgentHandlerOptions, type NcpHttpAgentServerOptions, type NcpHttpAgentStreamProvider, createNcpHttpAgentRouter, mountNcpHttpAgentRoutes };
package/dist/index.js CHANGED
@@ -1,436 +1,350 @@
1
- // src/controller.ts
2
1
  import { NcpEventType } from "@nextclaw/ncp";
3
-
4
- // src/types.ts
5
- var DEFAULT_BASE_PATH = "/ncp/agent";
6
-
7
- // src/parsers.ts
2
+ import { Hono } from "hono";
3
+ //#region src/types.ts
4
+ const DEFAULT_BASE_PATH = "/ncp/agent";
5
+ //#endregion
6
+ //#region src/parsers.ts
8
7
  function normalizeBasePath(basePath) {
9
- const raw = (basePath ?? DEFAULT_BASE_PATH).trim();
10
- if (!raw) {
11
- return DEFAULT_BASE_PATH;
12
- }
13
- const withLeadingSlash = raw.startsWith("/") ? raw : `/${raw}`;
14
- return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
8
+ const raw = (basePath ?? "/ncp/agent").trim();
9
+ if (!raw) return DEFAULT_BASE_PATH;
10
+ const withLeadingSlash = raw.startsWith("/") ? raw : `/${raw}`;
11
+ return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
15
12
  }
16
13
  function sanitizeTimeout(timeoutMs) {
17
- if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) {
18
- return null;
19
- }
20
- if (timeoutMs <= 0) {
21
- return null;
22
- }
23
- return Math.max(1e3, Math.trunc(timeoutMs));
14
+ if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs)) return null;
15
+ if (timeoutMs <= 0) return null;
16
+ return Math.max(1e3, Math.trunc(timeoutMs));
24
17
  }
25
18
  async function parseRequestEnvelope(request) {
26
- try {
27
- const payload = await request.json();
28
- if (!isRecord(payload)) {
29
- return null;
30
- }
31
- if (typeof payload.sessionId !== "string" || !payload.sessionId.trim()) {
32
- return null;
33
- }
34
- if (!isRecord(payload.message)) {
35
- return null;
36
- }
37
- return payload;
38
- } catch {
39
- return null;
40
- }
19
+ try {
20
+ const payload = await request.json();
21
+ if (!isRecord$1(payload)) return null;
22
+ if (typeof payload.sessionId !== "string" || !payload.sessionId.trim()) return null;
23
+ if (!isRecord$1(payload.message)) return null;
24
+ return payload;
25
+ } catch {
26
+ return null;
27
+ }
41
28
  }
42
29
  function parseStreamPayloadFromUrl(url) {
43
- const query = new URL(url).searchParams;
44
- const sessionId = query.get("sessionId")?.trim();
45
- if (!sessionId) {
46
- return null;
47
- }
48
- return {
49
- sessionId
50
- };
30
+ const sessionId = new URL(url).searchParams.get("sessionId")?.trim();
31
+ if (!sessionId) return null;
32
+ return { sessionId };
51
33
  }
52
34
  async function parseAbortPayload(request) {
53
- try {
54
- const payload = await request.json();
55
- if (!isRecord(payload)) {
56
- return null;
57
- }
58
- const sessionId = readTrimmedString(payload.sessionId);
59
- if (!sessionId) {
60
- return null;
61
- }
62
- const messageId = readTrimmedString(payload.messageId);
63
- return {
64
- sessionId,
65
- ...messageId ? { messageId } : {}
66
- };
67
- } catch {
68
- return null;
69
- }
35
+ try {
36
+ const payload = await request.json();
37
+ if (!isRecord$1(payload)) return null;
38
+ const sessionId = readTrimmedString(payload.sessionId);
39
+ if (!sessionId) return null;
40
+ const messageId = readTrimmedString(payload.messageId);
41
+ return {
42
+ sessionId,
43
+ ...messageId ? { messageId } : {}
44
+ };
45
+ } catch {
46
+ return null;
47
+ }
70
48
  }
71
49
  function readTrimmedString(value) {
72
- return typeof value === "string" ? value.trim() : "";
50
+ return typeof value === "string" ? value.trim() : "";
73
51
  }
74
- function isRecord(value) {
75
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
52
+ function isRecord$1(value) {
53
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
76
54
  }
77
-
78
- // src/scope.ts
55
+ //#endregion
56
+ //#region src/scope.ts
79
57
  function extractScopeFromEvent(event) {
80
- const payload = readPayload(event);
81
- if (!payload) {
82
- return {};
83
- }
84
- return {
85
- sessionId: readString(payload.sessionId),
86
- correlationId: readString(payload.correlationId),
87
- runId: readString(payload.runId)
88
- };
58
+ const payload = readPayload(event);
59
+ if (!payload) return {};
60
+ return {
61
+ sessionId: readString(payload.sessionId),
62
+ correlationId: readString(payload.correlationId),
63
+ runId: readString(payload.runId)
64
+ };
89
65
  }
90
66
  function matchesScope(scope, event) {
91
- const eventScope = extractScopeFromEvent(event);
92
- if (eventScope.sessionId && eventScope.sessionId !== scope.sessionId) {
93
- return false;
94
- }
95
- if (scope.correlationId && eventScope.correlationId && eventScope.correlationId !== scope.correlationId) {
96
- return false;
97
- }
98
- if (scope.runId && eventScope.runId && eventScope.runId !== scope.runId) {
99
- return false;
100
- }
101
- return true;
67
+ const eventScope = extractScopeFromEvent(event);
68
+ if (eventScope.sessionId && eventScope.sessionId !== scope.sessionId) return false;
69
+ if (scope.correlationId && eventScope.correlationId && eventScope.correlationId !== scope.correlationId) return false;
70
+ if (scope.runId && eventScope.runId && eventScope.runId !== scope.runId) return false;
71
+ return true;
102
72
  }
103
73
  function readPayload(event) {
104
- if (!("payload" in event)) {
105
- return null;
106
- }
107
- return isRecord2(event.payload) ? event.payload : null;
74
+ if (!("payload" in event)) return null;
75
+ return isRecord(event.payload) ? event.payload : null;
108
76
  }
109
77
  function readString(value) {
110
- return typeof value === "string" && value ? value : void 0;
78
+ return typeof value === "string" && value ? value : void 0;
111
79
  }
112
- function isRecord2(value) {
113
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
80
+ function isRecord(value) {
81
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
114
82
  }
115
-
116
- // src/sse-stream.ts
83
+ //#endregion
84
+ //#region src/sse-stream.ts
117
85
  function createSseEventStream(events, signal) {
118
- const encoder = new TextEncoder();
119
- return new ReadableStream({
120
- start: async (controller) => {
121
- let closed = false;
122
- const close = () => {
123
- if (closed) {
124
- return;
125
- }
126
- closed = true;
127
- controller.close();
128
- };
129
- const onAbort = () => {
130
- close();
131
- };
132
- signal?.addEventListener("abort", onAbort, { once: true });
133
- try {
134
- for await (const event of events) {
135
- if (closed || signal?.aborted) {
136
- break;
137
- }
138
- controller.enqueue(encoder.encode(toSseFrame(event.event, event.data)));
139
- }
140
- } finally {
141
- signal?.removeEventListener("abort", onAbort);
142
- close();
143
- }
144
- }
145
- });
86
+ const encoder = new TextEncoder();
87
+ return new ReadableStream({ start: async (controller) => {
88
+ let closed = false;
89
+ const close = () => {
90
+ if (closed) return;
91
+ closed = true;
92
+ controller.close();
93
+ };
94
+ const onAbort = () => {
95
+ close();
96
+ };
97
+ signal?.addEventListener("abort", onAbort, { once: true });
98
+ try {
99
+ for await (const event of events) {
100
+ if (closed || signal?.aborted) break;
101
+ controller.enqueue(encoder.encode(toSseFrame(event.event, event.data)));
102
+ }
103
+ } finally {
104
+ signal?.removeEventListener("abort", onAbort);
105
+ close();
106
+ }
107
+ } });
146
108
  }
147
109
  function buildSseResponse(stream) {
148
- return new Response(stream, {
149
- status: 200,
150
- headers: {
151
- "Content-Type": "text/event-stream; charset=utf-8",
152
- "Cache-Control": "no-cache, no-transform",
153
- Connection: "keep-alive",
154
- "X-Accel-Buffering": "no"
155
- }
156
- });
110
+ return new Response(stream, {
111
+ status: 200,
112
+ headers: {
113
+ "Content-Type": "text/event-stream; charset=utf-8",
114
+ "Cache-Control": "no-cache, no-transform",
115
+ Connection: "keep-alive",
116
+ "X-Accel-Buffering": "no"
117
+ }
118
+ });
157
119
  }
158
120
  function toSseFrame(eventName, data) {
159
- return `event: ${eventName}
160
- data: ${JSON.stringify(data)}
161
-
162
- `;
121
+ return `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
163
122
  }
164
123
  function toNcpEventFrame(event) {
165
- return {
166
- event: "ncp-event",
167
- data: event
168
- };
124
+ return {
125
+ event: "ncp-event",
126
+ data: event
127
+ };
169
128
  }
170
129
  function toErrorFrame(code, message) {
171
- return {
172
- event: "error",
173
- data: {
174
- code,
175
- message
176
- }
177
- };
130
+ return {
131
+ event: "error",
132
+ data: {
133
+ code,
134
+ message
135
+ }
136
+ };
178
137
  }
179
-
180
- // src/async-queue.ts
138
+ //#endregion
139
+ //#region src/async-queue.ts
181
140
  function createAsyncQueue() {
182
- const items = [];
183
- let closed = false;
184
- let pendingResolve = null;
185
- const iterable = {
186
- [Symbol.asyncIterator]() {
187
- return {
188
- next: () => {
189
- if (items.length > 0) {
190
- return Promise.resolve({
191
- value: items.shift(),
192
- done: false
193
- });
194
- }
195
- if (closed) {
196
- return Promise.resolve({
197
- value: void 0,
198
- done: true
199
- });
200
- }
201
- return new Promise((resolve) => {
202
- pendingResolve = resolve;
203
- });
204
- }
205
- };
206
- }
207
- };
208
- return {
209
- push(item) {
210
- if (closed) {
211
- return;
212
- }
213
- if (pendingResolve) {
214
- const resolve = pendingResolve;
215
- pendingResolve = null;
216
- resolve({ value: item, done: false });
217
- return;
218
- }
219
- items.push(item);
220
- },
221
- close() {
222
- if (closed) {
223
- return;
224
- }
225
- closed = true;
226
- if (pendingResolve) {
227
- const resolve = pendingResolve;
228
- pendingResolve = null;
229
- resolve({
230
- value: void 0,
231
- done: true
232
- });
233
- }
234
- },
235
- iterable
236
- };
141
+ const items = [];
142
+ let closed = false;
143
+ let pendingResolve = null;
144
+ return {
145
+ push(item) {
146
+ if (closed) return;
147
+ if (pendingResolve) {
148
+ const resolve = pendingResolve;
149
+ pendingResolve = null;
150
+ resolve({
151
+ value: item,
152
+ done: false
153
+ });
154
+ return;
155
+ }
156
+ items.push(item);
157
+ },
158
+ close() {
159
+ if (closed) return;
160
+ closed = true;
161
+ if (pendingResolve) {
162
+ const resolve = pendingResolve;
163
+ pendingResolve = null;
164
+ resolve({
165
+ value: void 0,
166
+ done: true
167
+ });
168
+ }
169
+ },
170
+ iterable: { [Symbol.asyncIterator]() {
171
+ return { next: () => {
172
+ if (items.length > 0) return Promise.resolve({
173
+ value: items.shift(),
174
+ done: false
175
+ });
176
+ if (closed) return Promise.resolve({
177
+ value: void 0,
178
+ done: true
179
+ });
180
+ return new Promise((resolve) => {
181
+ pendingResolve = resolve;
182
+ });
183
+ } };
184
+ } }
185
+ };
237
186
  }
238
-
239
- // src/utils.ts
187
+ //#endregion
188
+ //#region src/utils.ts
240
189
  function errorMessage(error) {
241
- if (error instanceof Error && error.message) {
242
- return error.message;
243
- }
244
- return String(error ?? "Unknown error");
190
+ if (error instanceof Error && error.message) return error.message;
191
+ return String(error ?? "Unknown error");
245
192
  }
246
193
  function jsonResponse(body, status = 200) {
247
- return new Response(JSON.stringify(body), {
248
- status,
249
- headers: { "Content-Type": "application/json" }
250
- });
194
+ return new Response(JSON.stringify(body), {
195
+ status,
196
+ headers: { "Content-Type": "application/json" }
197
+ });
251
198
  }
252
-
253
- // src/stream-handlers.ts
199
+ //#endregion
200
+ //#region src/stream-handlers.ts
254
201
  function createForwardResponse(options) {
255
- const { requestSignal } = options;
256
- return buildSseResponse(
257
- createSseEventStream(createForwardSseEvents(options), requestSignal)
258
- );
202
+ const { requestSignal } = options;
203
+ return buildSseResponse(createSseEventStream(createForwardSseEvents(options), requestSignal));
259
204
  }
260
205
  async function* createForwardSseEvents(options) {
261
- const { endpoint, requestEvent, requestSignal, timeoutMs, scope } = options;
262
- const queue = createAsyncQueue();
263
- let timeoutId = null;
264
- let unsubscribe = null;
265
- let stopped = false;
266
- const push = (frame) => {
267
- if (!stopped) {
268
- queue.push(frame);
269
- }
270
- };
271
- const stop = () => {
272
- if (stopped) {
273
- return;
274
- }
275
- stopped = true;
276
- if (timeoutId) {
277
- clearTimeout(timeoutId);
278
- timeoutId = null;
279
- }
280
- if (unsubscribe) {
281
- unsubscribe();
282
- unsubscribe = null;
283
- }
284
- requestSignal.removeEventListener("abort", stop);
285
- queue.close();
286
- };
287
- requestSignal.addEventListener("abort", stop, { once: true });
288
- if (timeoutMs !== null) {
289
- timeoutId = setTimeout(() => {
290
- push(
291
- toErrorFrame(
292
- "TIMEOUT",
293
- "NCP HTTP stream timed out before terminal event."
294
- )
295
- );
296
- stop();
297
- }, timeoutMs);
298
- }
299
- unsubscribe = endpoint.subscribe((event) => {
300
- if (!matchesScope(scope, event)) {
301
- return;
302
- }
303
- push(toNcpEventFrame(event));
304
- });
305
- void endpoint.emit(requestEvent).catch((error) => {
306
- push(toErrorFrame("EMIT_FAILED", errorMessage(error)));
307
- stop();
308
- }).finally(() => {
309
- if (!requestSignal.aborted) {
310
- queueMicrotask(stop);
311
- }
312
- });
313
- try {
314
- for await (const frame of queue.iterable) {
315
- yield frame;
316
- }
317
- } finally {
318
- stop();
319
- }
206
+ const { endpoint, requestEvent, requestSignal, timeoutMs, scope } = options;
207
+ const queue = createAsyncQueue();
208
+ let timeoutId = null;
209
+ let unsubscribe = null;
210
+ let stopped = false;
211
+ const push = (frame) => {
212
+ if (!stopped) queue.push(frame);
213
+ };
214
+ const stop = () => {
215
+ if (stopped) return;
216
+ stopped = true;
217
+ if (timeoutId) {
218
+ clearTimeout(timeoutId);
219
+ timeoutId = null;
220
+ }
221
+ if (unsubscribe) {
222
+ unsubscribe();
223
+ unsubscribe = null;
224
+ }
225
+ requestSignal.removeEventListener("abort", stop);
226
+ queue.close();
227
+ };
228
+ requestSignal.addEventListener("abort", stop, { once: true });
229
+ if (timeoutMs !== null) timeoutId = setTimeout(() => {
230
+ push(toErrorFrame("TIMEOUT", "NCP HTTP stream timed out before terminal event."));
231
+ stop();
232
+ }, timeoutMs);
233
+ unsubscribe = endpoint.subscribe((event) => {
234
+ if (!matchesScope(scope, event)) return;
235
+ push(toNcpEventFrame(event));
236
+ });
237
+ endpoint.emit(requestEvent).catch((error) => {
238
+ push(toErrorFrame("EMIT_FAILED", errorMessage(error)));
239
+ stop();
240
+ }).finally(() => {
241
+ if (!requestSignal.aborted) queueMicrotask(stop);
242
+ });
243
+ try {
244
+ for await (const frame of queue.iterable) yield frame;
245
+ } finally {
246
+ stop();
247
+ }
320
248
  }
321
249
  function createLiveStreamResponse(options) {
322
- const { signal } = options;
323
- return buildSseResponse(
324
- createSseEventStream(createLiveStreamSseEvents(options), signal)
325
- );
250
+ const { signal } = options;
251
+ return buildSseResponse(createSseEventStream(createLiveStreamSseEvents(options), signal));
326
252
  }
327
253
  async function* createLiveStreamSseEvents(options) {
328
- const { streamProvider, payload, signal } = options;
329
- try {
330
- for await (const event of streamProvider.stream({ payload, signal })) {
331
- if (signal.aborted) {
332
- break;
333
- }
334
- yield toNcpEventFrame(event);
335
- }
336
- } catch (error) {
337
- yield toErrorFrame("STREAM_SOURCE_FAILED", errorMessage(error));
338
- }
254
+ const { streamProvider, payload, signal } = options;
255
+ try {
256
+ for await (const event of streamProvider.stream({
257
+ payload,
258
+ signal
259
+ })) {
260
+ if (signal.aborted) break;
261
+ yield toNcpEventFrame(event);
262
+ }
263
+ } catch (error) {
264
+ yield toErrorFrame("STREAM_SOURCE_FAILED", errorMessage(error));
265
+ }
339
266
  }
340
-
341
- // src/controller.ts
267
+ //#endregion
268
+ //#region src/controller.ts
269
+ /**
270
+ * Framework-agnostic controller for NCP agent HTTP routes.
271
+ * Forwards /send and /stream to agentClientEndpoint; /stream uses streamProvider when set.
272
+ */
342
273
  var NcpHttpAgentController = class {
343
- constructor(options) {
344
- this.options = options;
345
- }
346
- async handleSend(request) {
347
- const { agentClientEndpoint } = this.options;
348
- const envelope = await parseRequestEnvelope(request);
349
- if (!envelope) {
350
- return jsonResponse(
351
- {
352
- ok: false,
353
- error: {
354
- code: "INVALID_BODY",
355
- message: "Invalid NCP request envelope."
356
- }
357
- },
358
- 400
359
- );
360
- }
361
- await agentClientEndpoint.send(envelope);
362
- return jsonResponse({ ok: true });
363
- }
364
- async handleStream(request) {
365
- const { agentClientEndpoint, streamProvider } = this.options;
366
- const streamPayload = parseStreamPayloadFromUrl(request.url);
367
- if (!streamPayload) {
368
- return jsonResponse(
369
- {
370
- ok: false,
371
- error: { code: "INVALID_QUERY", message: "sessionId is required." }
372
- },
373
- 400
374
- );
375
- }
376
- if (streamProvider) {
377
- return createLiveStreamResponse({
378
- streamProvider,
379
- payload: streamPayload,
380
- signal: request.signal
381
- });
382
- }
383
- return createForwardResponse({
384
- endpoint: agentClientEndpoint,
385
- requestEvent: {
386
- type: NcpEventType.MessageStreamRequest,
387
- payload: streamPayload
388
- },
389
- requestSignal: request.signal,
390
- timeoutMs: null,
391
- scope: {
392
- sessionId: streamPayload.sessionId
393
- }
394
- });
395
- }
396
- async handleAbort(request) {
397
- const { agentClientEndpoint } = this.options;
398
- const payload = await parseAbortPayload(request);
399
- if (!payload) {
400
- return jsonResponse(
401
- {
402
- ok: false,
403
- error: { code: "INVALID_BODY", message: "sessionId is required." }
404
- },
405
- 400
406
- );
407
- }
408
- await agentClientEndpoint.abort(payload);
409
- return jsonResponse({ ok: true });
410
- }
274
+ constructor(options) {
275
+ this.options = options;
276
+ }
277
+ async handleSend(request) {
278
+ const { agentClientEndpoint } = this.options;
279
+ const envelope = await parseRequestEnvelope(request);
280
+ if (!envelope) return jsonResponse({
281
+ ok: false,
282
+ error: {
283
+ code: "INVALID_BODY",
284
+ message: "Invalid NCP request envelope."
285
+ }
286
+ }, 400);
287
+ await agentClientEndpoint.send(envelope);
288
+ return jsonResponse({ ok: true });
289
+ }
290
+ async handleStream(request) {
291
+ const { agentClientEndpoint, streamProvider } = this.options;
292
+ const streamPayload = parseStreamPayloadFromUrl(request.url);
293
+ if (!streamPayload) return jsonResponse({
294
+ ok: false,
295
+ error: {
296
+ code: "INVALID_QUERY",
297
+ message: "sessionId is required."
298
+ }
299
+ }, 400);
300
+ if (streamProvider) return createLiveStreamResponse({
301
+ streamProvider,
302
+ payload: streamPayload,
303
+ signal: request.signal
304
+ });
305
+ return createForwardResponse({
306
+ endpoint: agentClientEndpoint,
307
+ requestEvent: {
308
+ type: NcpEventType.MessageStreamRequest,
309
+ payload: streamPayload
310
+ },
311
+ requestSignal: request.signal,
312
+ timeoutMs: null,
313
+ scope: { sessionId: streamPayload.sessionId }
314
+ });
315
+ }
316
+ async handleAbort(request) {
317
+ const { agentClientEndpoint } = this.options;
318
+ const payload = await parseAbortPayload(request);
319
+ if (!payload) return jsonResponse({
320
+ ok: false,
321
+ error: {
322
+ code: "INVALID_BODY",
323
+ message: "sessionId is required."
324
+ }
325
+ }, 400);
326
+ await agentClientEndpoint.abort(payload);
327
+ return jsonResponse({ ok: true });
328
+ }
411
329
  };
412
-
413
- // src/router.ts
414
- import { Hono } from "hono";
330
+ //#endregion
331
+ //#region src/router.ts
415
332
  function createNcpHttpAgentRouter(options) {
416
- const app = new Hono();
417
- mountNcpHttpAgentRoutes(app, options);
418
- return app;
333
+ const app = new Hono();
334
+ mountNcpHttpAgentRoutes(app, options);
335
+ return app;
419
336
  }
420
337
  function mountNcpHttpAgentRoutes(app, options) {
421
- const { basePath: rawBasePath, agentClientEndpoint, streamProvider, requestTimeoutMs } = options;
422
- const basePath = normalizeBasePath(rawBasePath);
423
- const controller = new NcpHttpAgentController({
424
- agentClientEndpoint,
425
- streamProvider,
426
- timeoutMs: sanitizeTimeout(requestTimeoutMs)
427
- });
428
- app.post(`${basePath}/send`, (c) => controller.handleSend(c.req.raw));
429
- app.get(`${basePath}/stream`, (c) => controller.handleStream(c.req.raw));
430
- app.post(`${basePath}/abort`, (c) => controller.handleAbort(c.req.raw));
338
+ const { basePath: rawBasePath, agentClientEndpoint, streamProvider, requestTimeoutMs } = options;
339
+ const basePath = normalizeBasePath(rawBasePath);
340
+ const controller = new NcpHttpAgentController({
341
+ agentClientEndpoint,
342
+ streamProvider,
343
+ timeoutMs: sanitizeTimeout(requestTimeoutMs)
344
+ });
345
+ app.post(`${basePath}/send`, (c) => controller.handleSend(c.req.raw));
346
+ app.get(`${basePath}/stream`, (c) => controller.handleStream(c.req.raw));
347
+ app.post(`${basePath}/abort`, (c) => controller.handleAbort(c.req.raw));
431
348
  }
432
- export {
433
- NcpHttpAgentController,
434
- createNcpHttpAgentRouter,
435
- mountNcpHttpAgentRoutes
436
- };
349
+ //#endregion
350
+ export { NcpHttpAgentController, createNcpHttpAgentRouter, mountNcpHttpAgentRoutes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-http-agent-server",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "private": false,
5
5
  "description": "HTTP/SSE server transport adapter for NCP agent endpoints.",
6
6
  "type": "module",
@@ -16,17 +16,16 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "hono": "^4.9.8",
19
- "@nextclaw/ncp": "0.5.0"
19
+ "@nextclaw/ncp": "0.5.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.17.6",
23
23
  "prettier": "^3.3.3",
24
- "tsup": "^8.3.5",
25
24
  "typescript": "^5.6.3",
26
25
  "vitest": "^4.1.2"
27
26
  },
28
27
  "scripts": {
29
- "build": "tsup src/index.ts --format esm --dts --out-dir dist",
28
+ "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension",
30
29
  "lint": "eslint .",
31
30
  "tsc": "tsc -p tsconfig.json",
32
31
  "test": "vitest run"