@nextclaw/nextclaw-ncp-runtime-adapter-hermes-http 0.1.1

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.
@@ -0,0 +1,454 @@
1
+ import { HermesHttpAdapterConfigResolver } from "./hermes-http-adapter-config.utils.js";
2
+ import { HermesAssistantEventCollector, HermesInlineToolTraceTranslator, HermesReasoningDeltaTranslator, buildHermesMessages, createAssistantMessageFromParts, inferHermesProvider, normalizeHermesRequestedModel, readHermesProviderRoute, resolveHermesModel, toErrorSseFrame, toNcpError, toNcpSseFrame } from "./hermes-http-adapter-message.utils.js";
3
+ import { parseHermesOpenAIChatStream } from "./hermes-openai-stream-parser.utils.js";
4
+ import { HermesHttpAdapterSessionStore } from "./hermes-http-adapter-session-store.service.js";
5
+ import { createServer } from "node:http";
6
+ import { randomUUID } from "node:crypto";
7
+ import { DefaultNcpStreamEncoder } from "@nextclaw/ncp-agent-runtime";
8
+ import { NcpEventType } from "@nextclaw/ncp";
9
+ //#region src/hermes-http-adapter.service.ts
10
+ function isUserVisibleAssistantEvent(event) {
11
+ const eventType = event.type;
12
+ return eventType.startsWith("message.text-") || eventType.startsWith("message.reasoning-") || eventType.startsWith("message.tool-call-");
13
+ }
14
+ var HermesHttpAdapterRouteService = class {
15
+ sessions = new HermesHttpAdapterSessionStore();
16
+ streamEncoder = new DefaultNcpStreamEncoder({ reasoningNormalizationMode: "think-tags" });
17
+ fetchImpl;
18
+ constructor(config, buildAssistantMessage = createAssistantMessageFromParts) {
19
+ this.config = config;
20
+ this.buildAssistantMessage = buildAssistantMessage;
21
+ this.fetchImpl = config.fetchImpl ?? fetch;
22
+ }
23
+ handle = async (request, response) => {
24
+ const requestUrl = new URL(request.url ?? "/", this.baseOrigin);
25
+ const normalizedPath = requestUrl.pathname;
26
+ if (request.method === "GET" && normalizedPath === "/health") {
27
+ await this.handleHealth(response);
28
+ return;
29
+ }
30
+ if (request.method === "POST" && normalizedPath === `${this.config.basePath}/send`) {
31
+ await this.handleSend(request, response);
32
+ return;
33
+ }
34
+ if (request.method === "GET" && normalizedPath === `${this.config.basePath}/stream`) {
35
+ await this.handleStream(request, response, requestUrl);
36
+ return;
37
+ }
38
+ if (request.method === "POST" && normalizedPath === `${this.config.basePath}/abort`) {
39
+ await this.handleAbort(request, response);
40
+ return;
41
+ }
42
+ this.writeJson(response, 404, {
43
+ ok: false,
44
+ error: {
45
+ code: "NOT_FOUND",
46
+ message: `Unknown path: ${normalizedPath}`
47
+ }
48
+ });
49
+ };
50
+ dispose = () => {
51
+ this.sessions.dispose();
52
+ };
53
+ get baseOrigin() {
54
+ return `http://${this.config.host}:${this.config.port}`;
55
+ }
56
+ handleHealth = async (response) => {
57
+ const controller = new AbortController();
58
+ const timeout = setTimeout(() => controller.abort(), this.config.healthcheckTimeoutMs);
59
+ try {
60
+ const upstream = await this.fetchImpl(this.config.healthcheckUrl, {
61
+ method: "GET",
62
+ signal: controller.signal
63
+ });
64
+ if (!upstream.ok) {
65
+ this.writeJson(response, 503, {
66
+ status: "degraded",
67
+ adapter: "ok",
68
+ upstream: {
69
+ status: "error",
70
+ httpStatus: upstream.status
71
+ }
72
+ });
73
+ return;
74
+ }
75
+ this.writeJson(response, 200, {
76
+ status: "ok",
77
+ adapter: "ok",
78
+ upstream: "ok"
79
+ });
80
+ } catch (error) {
81
+ this.writeJson(response, 503, {
82
+ status: "degraded",
83
+ adapter: "ok",
84
+ upstream: {
85
+ status: "unreachable",
86
+ message: error instanceof Error ? error.message : String(error)
87
+ }
88
+ });
89
+ } finally {
90
+ clearTimeout(timeout);
91
+ }
92
+ };
93
+ handleSend = async (request, response) => {
94
+ try {
95
+ const envelope = await this.readJsonBody(request);
96
+ if (!envelope?.sessionId || !envelope.message) {
97
+ this.writeJson(response, 400, {
98
+ ok: false,
99
+ error: {
100
+ code: "INVALID_BODY",
101
+ message: "sessionId and message are required."
102
+ }
103
+ });
104
+ return;
105
+ }
106
+ const run = {
107
+ envelope,
108
+ messageId: randomUUID(),
109
+ runId: randomUUID()
110
+ };
111
+ this.sessions.setPendingRun(run);
112
+ this.writeJson(response, 200, { ok: true });
113
+ } catch (error) {
114
+ this.writeJson(response, 409, {
115
+ ok: false,
116
+ error: {
117
+ code: "RUN_CONFLICT",
118
+ message: error instanceof Error ? error.message : String(error)
119
+ }
120
+ });
121
+ }
122
+ };
123
+ handleAbort = async (request, response) => {
124
+ try {
125
+ const payload = await this.readJsonBody(request);
126
+ const sessionId = typeof payload?.sessionId === "string" ? payload.sessionId : "";
127
+ if (!sessionId.trim()) {
128
+ this.writeJson(response, 400, {
129
+ ok: false,
130
+ error: {
131
+ code: "INVALID_BODY",
132
+ message: "sessionId is required."
133
+ }
134
+ });
135
+ return;
136
+ }
137
+ const aborted = this.sessions.abortSession(sessionId);
138
+ this.writeJson(response, 200, {
139
+ ok: true,
140
+ aborted
141
+ });
142
+ } catch (error) {
143
+ this.writeJson(response, 500, {
144
+ ok: false,
145
+ error: {
146
+ code: "ABORT_FAILED",
147
+ message: error instanceof Error ? error.message : String(error)
148
+ }
149
+ });
150
+ }
151
+ };
152
+ handleStream = async (request, response, requestUrl) => {
153
+ const sessionId = requestUrl.searchParams.get("sessionId")?.trim();
154
+ if (!sessionId) {
155
+ this.writeJson(response, 400, {
156
+ ok: false,
157
+ error: {
158
+ code: "INVALID_QUERY",
159
+ message: "sessionId is required."
160
+ }
161
+ });
162
+ return;
163
+ }
164
+ const connectionController = new AbortController();
165
+ const runController = new AbortController();
166
+ response.once("close", () => {
167
+ connectionController.abort("client disconnected");
168
+ runController.abort("client disconnected");
169
+ });
170
+ response.writeHead(200, {
171
+ "content-type": "text/event-stream; charset=utf-8",
172
+ "cache-control": "no-cache, no-transform",
173
+ connection: "keep-alive",
174
+ "x-accel-buffering": "no"
175
+ });
176
+ try {
177
+ const run = await this.sessions.waitForPendingRun(sessionId, connectionController.signal, this.config.streamWaitTimeoutMs);
178
+ this.sessions.setActiveAbortController(sessionId, runController);
179
+ const streamResult = await this.startRun({
180
+ run,
181
+ signal: runController.signal
182
+ });
183
+ for await (const event of streamResult.events) {
184
+ if (connectionController.signal.aborted) break;
185
+ response.write(toNcpSseFrame(event));
186
+ }
187
+ } catch (error) {
188
+ if (!connectionController.signal.aborted) response.write(toErrorSseFrame({
189
+ code: "STREAM_FAILED",
190
+ message: error instanceof Error ? error.message : String(error)
191
+ }));
192
+ } finally {
193
+ this.sessions.clearActiveAbortController(sessionId);
194
+ response.end();
195
+ }
196
+ };
197
+ startRun = async (params) => {
198
+ const { run, signal } = params;
199
+ const providerRoute = this.readProviderRoute(run.envelope);
200
+ const requestedModel = normalizeHermesRequestedModel(resolveHermesModel({
201
+ envelope: run.envelope,
202
+ fallbackModel: this.config.model
203
+ }));
204
+ if (!providerRoute) await this.ensureHermesSessionModel({
205
+ sessionId: run.envelope.sessionId,
206
+ requestedModel,
207
+ signal
208
+ });
209
+ const response = await this.fetchHermesChatCompletion({
210
+ sessionId: run.envelope.sessionId,
211
+ signal,
212
+ stream: true,
213
+ model: requestedModel,
214
+ providerRoute,
215
+ messages: buildHermesMessages({
216
+ envelope: run.envelope,
217
+ systemPrompt: this.config.systemPrompt
218
+ }),
219
+ tools: run.envelope.tools
220
+ });
221
+ if (!response.body) throw new Error("[hermes-http-adapter] Hermes stream response body is missing.");
222
+ this.sessions.setSelectedModel(run.envelope.sessionId, requestedModel);
223
+ return { events: this.createRunEvents({
224
+ response,
225
+ run,
226
+ signal
227
+ }) };
228
+ };
229
+ createRunEvents = async function* (params) {
230
+ const { response, run, signal } = params;
231
+ const sessionId = run.envelope.sessionId;
232
+ this.readProviderRoute(run.envelope);
233
+ const metadata = { hermesModel: normalizeHermesRequestedModel(resolveHermesModel({
234
+ envelope: run.envelope,
235
+ fallbackModel: this.config.model
236
+ })) };
237
+ let emittedUserVisibleAssistantEvent = false;
238
+ const messageCollector = new HermesAssistantEventCollector();
239
+ const inlineToolTraceTranslator = new HermesInlineToolTraceTranslator();
240
+ const reasoningDeltaTranslator = new HermesReasoningDeltaTranslator();
241
+ yield {
242
+ type: NcpEventType.MessageAccepted,
243
+ payload: {
244
+ messageId: run.messageId,
245
+ correlationId: run.envelope.correlationId
246
+ }
247
+ };
248
+ yield {
249
+ type: NcpEventType.RunStarted,
250
+ payload: {
251
+ sessionId,
252
+ messageId: run.messageId,
253
+ runId: run.runId
254
+ }
255
+ };
256
+ try {
257
+ for await (const event of reasoningDeltaTranslator.translate(inlineToolTraceTranslator.translate(this.streamEncoder.encode(parseHermesOpenAIChatStream(response.body), {
258
+ sessionId,
259
+ messageId: run.messageId,
260
+ runId: run.runId,
261
+ correlationId: run.envelope.correlationId
262
+ })))) {
263
+ messageCollector.applyEvent(event);
264
+ if (isUserVisibleAssistantEvent(event)) emittedUserVisibleAssistantEvent = true;
265
+ yield event;
266
+ }
267
+ if (!emittedUserVisibleAssistantEvent || !messageCollector.hasParts()) throw new Error(`[hermes-http-adapter] Hermes completed without any assistant content for session ${sessionId}.`);
268
+ const message = this.buildAssistantMessage({
269
+ sessionId,
270
+ messageId: run.messageId,
271
+ parts: messageCollector.buildParts(),
272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
273
+ metadata
274
+ });
275
+ yield {
276
+ type: NcpEventType.MessageCompleted,
277
+ payload: {
278
+ sessionId,
279
+ message,
280
+ correlationId: run.envelope.correlationId,
281
+ metadata
282
+ }
283
+ };
284
+ yield {
285
+ type: NcpEventType.RunFinished,
286
+ payload: {
287
+ sessionId,
288
+ messageId: run.messageId,
289
+ runId: run.runId
290
+ }
291
+ };
292
+ } catch (error) {
293
+ if (signal.aborted) {
294
+ const abortError = toNcpError(error, "abort-error");
295
+ yield {
296
+ type: NcpEventType.MessageFailed,
297
+ payload: {
298
+ sessionId,
299
+ messageId: run.messageId,
300
+ correlationId: run.envelope.correlationId,
301
+ error: abortError
302
+ }
303
+ };
304
+ yield {
305
+ type: NcpEventType.RunError,
306
+ payload: {
307
+ sessionId,
308
+ messageId: run.messageId,
309
+ runId: run.runId,
310
+ error: abortError.message
311
+ }
312
+ };
313
+ return;
314
+ }
315
+ const runtimeError = toNcpError(error, "runtime-error");
316
+ yield {
317
+ type: NcpEventType.MessageFailed,
318
+ payload: {
319
+ sessionId,
320
+ messageId: run.messageId,
321
+ correlationId: run.envelope.correlationId,
322
+ error: runtimeError
323
+ }
324
+ };
325
+ yield {
326
+ type: NcpEventType.RunError,
327
+ payload: {
328
+ sessionId,
329
+ messageId: run.messageId,
330
+ runId: run.runId,
331
+ error: runtimeError.message
332
+ }
333
+ };
334
+ }
335
+ };
336
+ ensureHermesSessionModel = async (params) => {
337
+ const { requestedModel, sessionId, signal } = params;
338
+ if ((this.sessions.readSelectedModel(sessionId) ?? this.config.model) === requestedModel) return;
339
+ const response = await this.fetchHermesChatCompletion({
340
+ sessionId,
341
+ signal,
342
+ stream: false,
343
+ model: requestedModel,
344
+ messages: [{
345
+ role: "user",
346
+ content: `/model ${requestedModel}`
347
+ }]
348
+ });
349
+ const content = (await this.readJsonResponse(response)).choices?.[0]?.message?.content;
350
+ if (typeof content === "string" && content.trim().startsWith("Error:")) throw new Error(`[hermes-http-adapter] failed to switch Hermes model to ${requestedModel}: ${content.trim()}`);
351
+ this.sessions.setSelectedModel(sessionId, requestedModel);
352
+ };
353
+ fetchHermesChatCompletion = async (params) => {
354
+ const { messages, model, providerRoute, sessionId, signal, stream, tools } = params;
355
+ const headers = new Headers({
356
+ "content-type": "application/json",
357
+ accept: stream ? "text/event-stream" : "application/json"
358
+ });
359
+ if (this.config.hermesApiKey) headers.set("authorization", `Bearer ${this.config.hermesApiKey}`);
360
+ const hermesSessionId = this.sessions.readHermesSessionId(sessionId);
361
+ if (hermesSessionId) headers.set("x-hermes-session-id", hermesSessionId);
362
+ const response = await this.fetchImpl(this.config.chatCompletionsUrl, {
363
+ method: "POST",
364
+ headers,
365
+ signal,
366
+ body: JSON.stringify({
367
+ model,
368
+ stream,
369
+ messages,
370
+ ...providerRoute ? { nextclaw_provider_route: this.toHermesProviderRoutePayload(providerRoute) } : {},
371
+ ...tools && tools.length > 0 ? { tools } : {}
372
+ })
373
+ });
374
+ if (!response.ok) throw new Error(`[hermes-http-adapter] Hermes request failed with HTTP ${response.status}.`);
375
+ const returnedSessionId = response.headers.get("x-hermes-session-id")?.trim();
376
+ if (returnedSessionId) this.sessions.setHermesSessionId(sessionId, returnedSessionId);
377
+ return response;
378
+ };
379
+ readProviderRoute = (envelope) => {
380
+ const providerRoute = readHermesProviderRoute(envelope);
381
+ if (!providerRoute) return;
382
+ if (!providerRoute.apiBase) throw new Error(`[hermes-http-adapter] missing provider apiBase for model "${providerRoute.model}". Configure the selected NextClaw provider before using Hermes.`);
383
+ return providerRoute;
384
+ };
385
+ toHermesProviderRoutePayload = (providerRoute) => {
386
+ const inferredProvider = inferHermesProvider(providerRoute);
387
+ return {
388
+ model: providerRoute.model,
389
+ ...inferredProvider ? { provider: inferredProvider } : {},
390
+ ...providerRoute.apiKey ? { api_key: providerRoute.apiKey } : {},
391
+ ...providerRoute.apiBase ? { base_url: providerRoute.apiBase } : {},
392
+ ...providerRoute.apiMode ? { api_mode: providerRoute.apiMode } : {},
393
+ ...Object.keys(providerRoute.headers).length > 0 ? { extra_headers: providerRoute.headers } : {}
394
+ };
395
+ };
396
+ readJsonResponse = async (response) => {
397
+ try {
398
+ return await response.json();
399
+ } catch (error) {
400
+ throw new Error(`[hermes-http-adapter] expected JSON response from Hermes: ${error instanceof Error ? error.message : String(error)}`);
401
+ }
402
+ };
403
+ readJsonBody = async (request) => {
404
+ const chunks = [];
405
+ for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
406
+ const body = Buffer.concat(chunks).toString("utf8");
407
+ if (!body.trim()) throw new Error("request body is empty");
408
+ return JSON.parse(body);
409
+ };
410
+ writeJson = (response, statusCode, payload) => {
411
+ response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
412
+ response.end(JSON.stringify(payload));
413
+ };
414
+ };
415
+ var HermesHttpAdapterServer = class {
416
+ config;
417
+ routeService;
418
+ server = null;
419
+ constructor(configInput = {}) {
420
+ this.config = new HermesHttpAdapterConfigResolver(configInput).resolve();
421
+ this.routeService = new HermesHttpAdapterRouteService(this.config);
422
+ }
423
+ start = async () => {
424
+ if (this.server) return;
425
+ const nextServer = createServer((request, response) => {
426
+ this.routeService.handle(request, response);
427
+ });
428
+ await new Promise((resolve, reject) => {
429
+ nextServer.once("error", reject);
430
+ nextServer.listen(this.config.port, this.config.host, () => {
431
+ nextServer.off("error", reject);
432
+ resolve();
433
+ });
434
+ });
435
+ this.server = nextServer;
436
+ };
437
+ stop = async () => {
438
+ this.routeService.dispose();
439
+ const activeServer = this.server;
440
+ if (!activeServer) return;
441
+ await new Promise((resolve, reject) => {
442
+ activeServer.close((error) => {
443
+ if (error) {
444
+ reject(error);
445
+ return;
446
+ }
447
+ resolve();
448
+ });
449
+ });
450
+ this.server = null;
451
+ };
452
+ };
453
+ //#endregion
454
+ export { HermesHttpAdapterServer };
@@ -0,0 +1,26 @@
1
+ import { NcpEndpointEvent, NcpMessage, NcpMessagePart, NcpRequestEnvelope } from "@nextclaw/ncp";
2
+
3
+ //#region src/hermes-http-adapter.types.d.ts
4
+ type HermesHttpAdapterFetchLike = (input: URL | string | Request, init?: RequestInit) => Promise<Response>;
5
+ type HermesHttpAdapterConfig = {
6
+ host: string;
7
+ port: number;
8
+ basePath: string;
9
+ hermesBaseUrl: string;
10
+ hermesApiKey?: string;
11
+ model: string;
12
+ systemPrompt?: string;
13
+ streamWaitTimeoutMs: number;
14
+ healthcheckTimeoutMs: number;
15
+ fetchImpl?: HermesHttpAdapterFetchLike;
16
+ };
17
+ type HermesHttpAdapterResolvedConfig = HermesHttpAdapterConfig & {
18
+ chatCompletionsUrl: string;
19
+ healthcheckUrl: string;
20
+ };
21
+ type HermesOpenAIMessage = {
22
+ role: "system" | "user" | "assistant";
23
+ content: string;
24
+ };
25
+ //#endregion
26
+ export { HermesHttpAdapterConfig, HermesHttpAdapterFetchLike, HermesHttpAdapterResolvedConfig, HermesOpenAIMessage };
@@ -0,0 +1,6 @@
1
+ import { OpenAIChatChunk } from "@nextclaw/ncp";
2
+
3
+ //#region src/hermes-openai-stream-parser.utils.d.ts
4
+ declare function parseHermesOpenAIChatStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<OpenAIChatChunk>;
5
+ //#endregion
6
+ export { parseHermesOpenAIChatStream };
@@ -0,0 +1,54 @@
1
+ //#region src/hermes-openai-stream-parser.utils.ts
2
+ async function* parseHermesOpenAIChatStream(stream) {
3
+ for await (const frame of consumeSseFrames(stream)) {
4
+ const payload = frame.data.trim();
5
+ if (!payload || payload === "[DONE]") continue;
6
+ yield JSON.parse(payload);
7
+ }
8
+ }
9
+ async function* consumeSseFrames(stream) {
10
+ const reader = stream.getReader();
11
+ const decoder = new TextDecoder();
12
+ let buffer = "";
13
+ try {
14
+ while (true) {
15
+ const { value, done } = await reader.read();
16
+ if (done) break;
17
+ buffer += decoder.decode(value, { stream: true });
18
+ const drained = drainSseFrames(buffer);
19
+ buffer = drained.rest;
20
+ for (const frame of drained.frames) yield frame;
21
+ }
22
+ buffer += decoder.decode();
23
+ const drained = drainSseFrames(buffer, true);
24
+ for (const frame of drained.frames) yield frame;
25
+ } finally {
26
+ reader.releaseLock();
27
+ }
28
+ }
29
+ function drainSseFrames(rawBuffer, flush = false) {
30
+ const parts = rawBuffer.split(/\r?\n\r?\n/u);
31
+ const rest = parts.pop() ?? "";
32
+ const frames = parts.map(parseSseFrame).filter((frame) => Boolean(frame));
33
+ if (!flush || !rest.trim()) return {
34
+ frames,
35
+ rest
36
+ };
37
+ const finalFrame = parseSseFrame(rest);
38
+ return {
39
+ frames: finalFrame ? [...frames, finalFrame] : frames,
40
+ rest: ""
41
+ };
42
+ }
43
+ function parseSseFrame(frameText) {
44
+ const dataLines = [];
45
+ for (const rawLine of frameText.split(/\r?\n/u)) {
46
+ const line = rawLine.trimEnd();
47
+ if (!line || line.startsWith(":") || !line.startsWith("data:")) continue;
48
+ dataLines.push(line.slice(5).trimStart());
49
+ }
50
+ if (dataLines.length === 0) return null;
51
+ return { data: dataLines.join("\n") };
52
+ }
53
+ //#endregion
54
+ export { parseHermesOpenAIChatStream };
@@ -0,0 +1,6 @@
1
+ import { HermesHttpAdapterConfig, HermesHttpAdapterFetchLike, HermesHttpAdapterResolvedConfig } from "./hermes-http-adapter.types.js";
2
+ import { HermesHttpAdapterConfigInput, HermesHttpAdapterConfigResolver, normalizeBasePath, resolveHermesApiUrl, resolveHermesHealthcheckUrl } from "./hermes-http-adapter-config.utils.js";
3
+ import { HermesHttpAdapterServer } from "./hermes-http-adapter.service.js";
4
+ import { HermesAssistantEventCollector, HermesInlineToolTraceTranslator, HermesProviderRoute, HermesReasoningDeltaTranslator, buildHermesMessages, createAssistantMessage, createAssistantMessageFromParts, inferHermesProvider, normalizeHermesRequestedModel, readHermesProviderRoute, resolveHermesModel, toErrorSseFrame, toNcpError, toNcpSseFrame } from "./hermes-http-adapter-message.utils.js";
5
+ import { parseHermesOpenAIChatStream } from "./hermes-openai-stream-parser.utils.js";
6
+ export { HermesAssistantEventCollector, type HermesHttpAdapterConfig, type HermesHttpAdapterConfigInput, HermesHttpAdapterConfigResolver, type HermesHttpAdapterFetchLike, type HermesHttpAdapterResolvedConfig, HermesHttpAdapterServer, HermesInlineToolTraceTranslator, type HermesProviderRoute, HermesReasoningDeltaTranslator, buildHermesMessages, createAssistantMessage, createAssistantMessageFromParts, inferHermesProvider, normalizeBasePath, normalizeHermesRequestedModel, parseHermesOpenAIChatStream, readHermesProviderRoute, resolveHermesApiUrl, resolveHermesHealthcheckUrl, resolveHermesModel, toErrorSseFrame, toNcpError, toNcpSseFrame };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { HermesHttpAdapterConfigResolver, normalizeBasePath, resolveHermesApiUrl, resolveHermesHealthcheckUrl } from "./hermes-http-adapter-config.utils.js";
2
+ import { HermesAssistantEventCollector, HermesInlineToolTraceTranslator, HermesReasoningDeltaTranslator, buildHermesMessages, createAssistantMessage, createAssistantMessageFromParts, inferHermesProvider, normalizeHermesRequestedModel, readHermesProviderRoute, resolveHermesModel, toErrorSseFrame, toNcpError, toNcpSseFrame } from "./hermes-http-adapter-message.utils.js";
3
+ import { parseHermesOpenAIChatStream } from "./hermes-openai-stream-parser.utils.js";
4
+ import { HermesHttpAdapterServer } from "./hermes-http-adapter.service.js";
5
+ export { HermesAssistantEventCollector, HermesHttpAdapterConfigResolver, HermesHttpAdapterServer, HermesInlineToolTraceTranslator, HermesReasoningDeltaTranslator, buildHermesMessages, createAssistantMessage, createAssistantMessageFromParts, inferHermesProvider, normalizeBasePath, normalizeHermesRequestedModel, parseHermesOpenAIChatStream, readHermesProviderRoute, resolveHermesApiUrl, resolveHermesHealthcheckUrl, resolveHermesModel, toErrorSseFrame, toNcpError, toNcpSseFrame };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@nextclaw/nextclaw-ncp-runtime-adapter-hermes-http",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "description": "Standalone NCP HTTP adapter server that bridges NextClaw http-runtime sessions to Hermes API Server.",
6
+ "type": "module",
7
+ "bin": {
8
+ "nextclaw-hermes-http-adapter": "dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "development": "./src/index.ts",
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "dependencies": {
22
+ "@nextclaw/ncp-agent-runtime": "0.3.12",
23
+ "@nextclaw/ncp-http-agent-client": "0.3.14",
24
+ "@nextclaw/ncp": "0.5.2"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^20.17.6",
28
+ "typescript": "^5.6.3",
29
+ "vitest": "^4.1.2"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown src/index.ts src/cli.ts --dts --clean --target es2022 --no-fixedExtension --unbundle",
33
+ "lint": "eslint .",
34
+ "tsc": "tsc -p tsconfig.json",
35
+ "test": "vitest run"
36
+ }
37
+ }