@kuralle-agents/hono-server 0.12.0 → 0.14.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 +4 -5
- package/dist/chatRouter.js +12 -28
- package/dist/index.d.ts +19 -17
- package/dist/index.js +88 -105
- package/dist/openaiCompat.js +16 -12
- package/dist/streamFilter.d.ts +4 -13
- package/dist/streamFilter.js +7 -17
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Peers: `@kuralle-agents/core`, `ai@^6`.
|
|
|
14
14
|
|
|
15
15
|
`createKuralleChatRouter` mounts a complete set of endpoints onto a Hono app — HTTP chat, SSE streaming, WebSocket widget, session management, audit, CSAT, and a manual compression trigger — wired to any `RuntimeLike` instance.
|
|
16
16
|
|
|
17
|
-
**As of 0.5.0, `POST /api/chat/sse` defaults to an AI SDK `UIMessageStream`** — a `useChat` client works with zero bridge code. Raw `
|
|
17
|
+
**As of 0.5.0, `POST /api/chat/sse` defaults to an AI SDK `UIMessageStream`** — a `useChat` client works with zero bridge code. Raw `StreamPart` JSON-SSE is opt-in via `?format=raw`.
|
|
18
18
|
|
|
19
19
|
**Key exports:**
|
|
20
20
|
|
|
@@ -98,11 +98,11 @@ const server = serve({ fetch: app.fetch, port: 3000 });
|
|
|
98
98
|
injectWebSocket(server);
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
`POST /api/chat/sse` accepts `useChat`-shaped bodies (`{ messages: UIMessage[] }`) and returns a native `UIMessageStream`. No `
|
|
101
|
+
`POST /api/chat/sse` accepts `useChat`-shaped bodies (`{ messages: UIMessage[] }`) and returns a native `UIMessageStream`. No `StreamPart` → `UIMessageChunk` bridge required.
|
|
102
102
|
|
|
103
103
|
## Raw JSON-SSE (`?format=raw`)
|
|
104
104
|
|
|
105
|
-
Non-UI consumers (curl, Studio, custom transports) that parsed raw `
|
|
105
|
+
Non-UI consumers (curl, Studio, custom transports) that parsed raw `StreamPart` JSON from 0.4.x should append `?format=raw`:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
curl -N -X POST 'http://localhost:3000/api/chat/sse?format=raw' \
|
|
@@ -117,7 +117,7 @@ Or use `createKuralleSseChatRouter` for a router that always emits raw JSON-SSE
|
|
|
117
117
|
| Method | Path | Description |
|
|
118
118
|
|--------|------|-------------|
|
|
119
119
|
| `POST` | `/api/chat` | Single-turn JSON response |
|
|
120
|
-
| `POST` | `/api/chat/sse` | **Default:** AI SDK `UIMessageStream` (`useChat`). **`?format=raw`:** legacy `
|
|
120
|
+
| `POST` | `/api/chat/sse` | **Default:** AI SDK `UIMessageStream` (`useChat`). **`?format=raw`:** legacy `StreamPart` JSON-SSE |
|
|
121
121
|
| `POST` | `/api/chat/stream` | Chunked plain-text stream |
|
|
122
122
|
| `GET` | `/agents/chat/:sessionId` | WebSocket widget endpoint |
|
|
123
123
|
| `GET` | `/ws/:sessionId` | WebSocket turn endpoint |
|
|
@@ -139,7 +139,6 @@ createKuralleChatRouter({
|
|
|
139
139
|
upgradeWebSocket,
|
|
140
140
|
widgetWelcomeMode: 'static',
|
|
141
141
|
widgetWelcomeMessage: "Hello, how can I help you today?",
|
|
142
|
-
widgetWelcomeSuggestions: ['Check order status', 'Request a refund'],
|
|
143
142
|
});
|
|
144
143
|
```
|
|
145
144
|
|
package/dist/chatRouter.js
CHANGED
|
@@ -23,45 +23,29 @@ export function createKuralleSseChatRouter({ runtime, streamFilter: streamFilter
|
|
|
23
23
|
input: body.message,
|
|
24
24
|
userId: body.userId,
|
|
25
25
|
});
|
|
26
|
-
const responseStream = handle.toResponseStream('sse');
|
|
27
|
-
const reader = responseStream.getReader();
|
|
28
26
|
const encoder = new TextEncoder();
|
|
29
27
|
const filtered = new ReadableStream({
|
|
30
28
|
async start(controller) {
|
|
31
29
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
if (!line.startsWith('data: '))
|
|
39
|
-
continue;
|
|
40
|
-
const raw = line.slice('data: '.length);
|
|
41
|
-
if (!raw.trim())
|
|
42
|
-
continue;
|
|
43
|
-
let part;
|
|
44
|
-
try {
|
|
45
|
-
part = JSON.parse(raw);
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
controller.enqueue(value);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
if (!shouldEmit(part, streamFilter))
|
|
52
|
-
continue;
|
|
53
|
-
const safe = sanitizeForClient(part);
|
|
54
|
-
const payload = `data: ${JSON.stringify(safe)}\n\n`;
|
|
55
|
-
controller.enqueue(encoder.encode(payload));
|
|
56
|
-
}
|
|
30
|
+
for await (const part of handle.events) {
|
|
31
|
+
if (!shouldEmit(part, streamFilter))
|
|
32
|
+
continue;
|
|
33
|
+
const safe = sanitizeForClient(part);
|
|
34
|
+
const payload = `data: ${JSON.stringify(safe)}\n\n`;
|
|
35
|
+
controller.enqueue(encoder.encode(payload));
|
|
57
36
|
}
|
|
37
|
+
await handle;
|
|
58
38
|
controller.close();
|
|
59
39
|
}
|
|
60
40
|
catch (error) {
|
|
61
41
|
const message = streamFilter === 'all'
|
|
62
42
|
? error.message
|
|
63
43
|
: 'An error occurred. Please try again.';
|
|
64
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify({
|
|
44
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify({
|
|
45
|
+
channel: 'client',
|
|
46
|
+
type: 'error',
|
|
47
|
+
payload: { error: message },
|
|
48
|
+
})}\n\n`));
|
|
65
49
|
controller.close();
|
|
66
50
|
}
|
|
67
51
|
finally {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import type { Context, Handler } from 'hono';
|
|
3
|
-
import type {
|
|
3
|
+
import type { StreamPart, RuntimeLike } from '@kuralle-agents/core';
|
|
4
4
|
export { createOpenAICompatRouter, type OpenAICompatRuntime, type CreateOpenAICompatRouterOptions, type ChatCompletionsRequest, } from './openaiCompat.js';
|
|
5
5
|
export { shouldEmit, sanitizeForClient, type StreamEventFilter, } from './streamFilter.js';
|
|
6
6
|
export { createKuralleSseChatRouter, type KuralleSseChatRouterOptions } from './chatRouter.js';
|
|
7
7
|
import { type StreamEventFilter } from './streamFilter.js';
|
|
8
|
-
type FlowStreamPart = {
|
|
9
|
-
type: string;
|
|
10
|
-
id?: string;
|
|
11
|
-
delta?: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
};
|
|
14
8
|
type FlowRouterManager = {
|
|
15
|
-
process: (input: string) => AsyncGenerator<
|
|
9
|
+
process: (input: string) => AsyncGenerator<StreamPart>;
|
|
16
10
|
currentNodeName: string;
|
|
17
11
|
nodeHistory: string[];
|
|
18
12
|
hasEnded: boolean;
|
|
@@ -57,6 +51,17 @@ export type FlowResponse = {
|
|
|
57
51
|
export type KuralleWebSocket = {
|
|
58
52
|
send: (data: string) => void;
|
|
59
53
|
};
|
|
54
|
+
export type WebSocketTransportFrame = {
|
|
55
|
+
type: 'connected';
|
|
56
|
+
sessionId: string;
|
|
57
|
+
timestamp: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'cancelled';
|
|
60
|
+
sessionId: string;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
} | {
|
|
63
|
+
type: 'pong';
|
|
64
|
+
};
|
|
60
65
|
export type WebSocketEvent = {
|
|
61
66
|
data?: unknown;
|
|
62
67
|
};
|
|
@@ -92,12 +97,8 @@ export type KuralleChatRouterOptions = {
|
|
|
92
97
|
*/
|
|
93
98
|
widgetWelcomeMessage?: string;
|
|
94
99
|
/**
|
|
95
|
-
*
|
|
96
|
-
|
|
97
|
-
widgetWelcomeSuggestions?: string[];
|
|
98
|
-
/**
|
|
99
|
-
* Controls which HarnessStreamPart events are sent to external clients (SSE + widget/flow WebSockets).
|
|
100
|
-
* - `'safe'`: only user-facing events (text-delta, done, sanitized error, suggested-questions, input).
|
|
100
|
+
* Controls which StreamPart events are sent to external clients (SSE + widget/flow WebSockets).
|
|
101
|
+
* - `'safe'`: events classified as `client` by core, with errors sanitized.
|
|
101
102
|
* - `'all'`: full stream (Studio / dev tooling).
|
|
102
103
|
* - function: custom predicate; return true to emit.
|
|
103
104
|
* @default 'safe'
|
|
@@ -108,7 +109,8 @@ export type KuralleRouterOptions = {
|
|
|
108
109
|
flowManager: FlowRouterManager;
|
|
109
110
|
sessionId: string;
|
|
110
111
|
upgradeWebSocket?: UpgradeWebSocket;
|
|
112
|
+
streamFilter?: StreamEventFilter;
|
|
111
113
|
};
|
|
112
|
-
export declare const createKuralleChatRouter: ({ runtime, upgradeWebSocket, widgetWelcomeMode, sendWidgetWelcomeMessage, widgetWelcomeMessage,
|
|
113
|
-
export type {
|
|
114
|
-
export declare const createKuralleRouter: ({ flowManager, sessionId, upgradeWebSocket, }: KuralleRouterOptions) => Hono;
|
|
114
|
+
export declare const createKuralleChatRouter: ({ runtime, upgradeWebSocket, widgetWelcomeMode, sendWidgetWelcomeMessage, widgetWelcomeMessage, streamFilter: streamFilterOption, }: KuralleChatRouterOptions) => Hono;
|
|
115
|
+
export type { StreamPart };
|
|
116
|
+
export declare const createKuralleRouter: ({ flowManager, sessionId, upgradeWebSocket, streamFilter: streamFilterOption, }: KuralleRouterOptions) => Hono;
|
package/dist/index.js
CHANGED
|
@@ -84,13 +84,13 @@ const collectResponse = async (runtime, message, sessionId, userId) => {
|
|
|
84
84
|
let resolvedSessionId = sessionId ?? '';
|
|
85
85
|
for await (const part of iterateRuntimeParts(runtime, { input: message, sessionId, userId })) {
|
|
86
86
|
if (part.type === 'text-delta') {
|
|
87
|
-
response += part.delta;
|
|
87
|
+
response += part.payload.delta;
|
|
88
88
|
}
|
|
89
89
|
if (part.type === 'error') {
|
|
90
|
-
throw new Error(part.error);
|
|
90
|
+
throw new Error(part.payload.error);
|
|
91
91
|
}
|
|
92
92
|
if (part.type === 'done') {
|
|
93
|
-
resolvedSessionId = part.sessionId;
|
|
93
|
+
resolvedSessionId = part.payload.sessionId;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
return {
|
|
@@ -104,6 +104,19 @@ const sendSSEPart = async (stream, part, filter) => {
|
|
|
104
104
|
const safe = sanitizeForClient(part);
|
|
105
105
|
await stream.writeSSE({ event: safe.type, data: JSON.stringify(safe) });
|
|
106
106
|
};
|
|
107
|
+
const sendStreamPartToWs = (ws, part, filter) => {
|
|
108
|
+
if (!shouldEmit(part, filter))
|
|
109
|
+
return;
|
|
110
|
+
ws.send(JSON.stringify(sanitizeForClient(part)));
|
|
111
|
+
};
|
|
112
|
+
const sendTransportFrameToWs = (ws, frame) => {
|
|
113
|
+
ws.send(JSON.stringify(frame));
|
|
114
|
+
};
|
|
115
|
+
const errorPart = (message) => ({
|
|
116
|
+
channel: 'client',
|
|
117
|
+
type: 'error',
|
|
118
|
+
payload: { error: message },
|
|
119
|
+
});
|
|
107
120
|
const formatSessionResponse = (session) => ({
|
|
108
121
|
sessionId: session.id,
|
|
109
122
|
currentAgent: session.currentAgent ?? session.activeAgentId,
|
|
@@ -165,15 +178,11 @@ const resolveWidgetWelcomeMode = (mode, sendWidgetWelcomeMessage, widgetWelcomeM
|
|
|
165
178
|
return 'static';
|
|
166
179
|
return 'model';
|
|
167
180
|
};
|
|
168
|
-
export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelcomeMode, sendWidgetWelcomeMessage = true, widgetWelcomeMessage,
|
|
181
|
+
export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelcomeMode, sendWidgetWelcomeMessage = true, widgetWelcomeMessage, streamFilter: streamFilterOption, }) => {
|
|
169
182
|
const streamFilter = streamFilterOption ?? 'safe';
|
|
170
183
|
const app = new Hono();
|
|
171
184
|
const effectiveWelcomeMode = resolveWidgetWelcomeMode(widgetWelcomeMode, sendWidgetWelcomeMessage, widgetWelcomeMessage);
|
|
172
|
-
const sendHarnessPartToWs = (ws, part) =>
|
|
173
|
-
if (!shouldEmit(part, streamFilter))
|
|
174
|
-
return;
|
|
175
|
-
ws.send(JSON.stringify(sanitizeForClient(part)));
|
|
176
|
-
};
|
|
185
|
+
const sendHarnessPartToWs = (ws, part) => sendStreamPartToWs(ws, part, streamFilter);
|
|
177
186
|
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
178
187
|
app.post('/api/chat', async (c) => {
|
|
179
188
|
const body = await parseJsonBody(c);
|
|
@@ -211,7 +220,7 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
211
220
|
userId: body.userId,
|
|
212
221
|
})) {
|
|
213
222
|
if (part.type === 'text-delta') {
|
|
214
|
-
controller.enqueue(encoder.encode(part.delta));
|
|
223
|
+
controller.enqueue(encoder.encode(part.payload.delta));
|
|
215
224
|
}
|
|
216
225
|
}
|
|
217
226
|
}
|
|
@@ -441,39 +450,38 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
441
450
|
onOpen: async (_event, ws) => {
|
|
442
451
|
debug(`[Kuralle] New widget connection: ${sessionId}`);
|
|
443
452
|
// Send connected message
|
|
444
|
-
ws
|
|
453
|
+
sendTransportFrameToWs(ws, {
|
|
445
454
|
type: 'connected',
|
|
446
455
|
sessionId,
|
|
447
456
|
timestamp: new Date().toISOString(),
|
|
448
|
-
})
|
|
457
|
+
});
|
|
449
458
|
if (effectiveWelcomeMode !== 'off') {
|
|
450
459
|
const staticWelcome = widgetWelcomeMessage?.trim();
|
|
451
460
|
if (effectiveWelcomeMode === 'static') {
|
|
452
461
|
const welcomeId = crypto.randomUUID();
|
|
453
|
-
ws
|
|
454
|
-
|
|
462
|
+
sendHarnessPartToWs(ws, {
|
|
463
|
+
channel: 'client',
|
|
464
|
+
type: 'text-start',
|
|
465
|
+
payload: { id: welcomeId },
|
|
466
|
+
});
|
|
467
|
+
sendHarnessPartToWs(ws, {
|
|
468
|
+
channel: 'client',
|
|
455
469
|
type: 'text-delta',
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
suggestions,
|
|
469
|
-
isPartial: false,
|
|
470
|
-
}));
|
|
471
|
-
}
|
|
472
|
-
ws.send(JSON.stringify({
|
|
470
|
+
payload: {
|
|
471
|
+
id: welcomeId,
|
|
472
|
+
delta: staticWelcome || 'Hello! How can I help you today?',
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
sendHarnessPartToWs(ws, {
|
|
476
|
+
channel: 'client',
|
|
477
|
+
type: 'text-end',
|
|
478
|
+
payload: { id: welcomeId },
|
|
479
|
+
});
|
|
480
|
+
sendHarnessPartToWs(ws, {
|
|
481
|
+
channel: 'client',
|
|
473
482
|
type: 'done',
|
|
474
|
-
sessionId,
|
|
475
|
-
|
|
476
|
-
}));
|
|
483
|
+
payload: { sessionId },
|
|
484
|
+
});
|
|
477
485
|
}
|
|
478
486
|
else {
|
|
479
487
|
// Optionally start conversation with a generated greeting.
|
|
@@ -491,18 +499,29 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
491
499
|
console.error(`[Kuralle] Failed to send greeting for session ${sessionId}:`, error);
|
|
492
500
|
// Send a simple greeting if streaming fails
|
|
493
501
|
const fallbackId = crypto.randomUUID();
|
|
494
|
-
ws
|
|
495
|
-
|
|
502
|
+
sendHarnessPartToWs(ws, {
|
|
503
|
+
channel: 'client',
|
|
504
|
+
type: 'text-start',
|
|
505
|
+
payload: { id: fallbackId },
|
|
506
|
+
});
|
|
507
|
+
sendHarnessPartToWs(ws, {
|
|
508
|
+
channel: 'client',
|
|
496
509
|
type: 'text-delta',
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
510
|
+
payload: {
|
|
511
|
+
id: fallbackId,
|
|
512
|
+
delta: 'Hello! How can I help you today?',
|
|
513
|
+
},
|
|
514
|
+
});
|
|
515
|
+
sendHarnessPartToWs(ws, {
|
|
516
|
+
channel: 'client',
|
|
517
|
+
type: 'text-end',
|
|
518
|
+
payload: { id: fallbackId },
|
|
519
|
+
});
|
|
520
|
+
sendHarnessPartToWs(ws, {
|
|
521
|
+
channel: 'client',
|
|
502
522
|
type: 'done',
|
|
503
|
-
sessionId,
|
|
504
|
-
|
|
505
|
-
}));
|
|
523
|
+
payload: { sessionId },
|
|
524
|
+
});
|
|
506
525
|
}
|
|
507
526
|
}
|
|
508
527
|
}
|
|
@@ -513,7 +532,7 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
513
532
|
// Handle the widget's message format
|
|
514
533
|
const input = payload.message;
|
|
515
534
|
if (typeof input !== 'string' || !input.trim()) {
|
|
516
|
-
ws
|
|
535
|
+
sendHarnessPartToWs(ws, errorPart('message content required'));
|
|
517
536
|
return;
|
|
518
537
|
}
|
|
519
538
|
for await (const part of iterateRuntimeParts(runtime, {
|
|
@@ -526,12 +545,9 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
526
545
|
}
|
|
527
546
|
catch (error) {
|
|
528
547
|
console.error('[Kuralle] Widget WebSocket error:', error);
|
|
529
|
-
ws
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
? error.message
|
|
533
|
-
: 'An error occurred. Please try again.',
|
|
534
|
-
}));
|
|
548
|
+
sendHarnessPartToWs(ws, errorPart(streamFilter === 'all'
|
|
549
|
+
? error.message
|
|
550
|
+
: 'An error occurred. Please try again.'));
|
|
535
551
|
}
|
|
536
552
|
},
|
|
537
553
|
};
|
|
@@ -542,11 +558,11 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
542
558
|
const sessionId = c.req.param('sessionId') ?? '';
|
|
543
559
|
return {
|
|
544
560
|
onOpen(_event, ws) {
|
|
545
|
-
ws
|
|
561
|
+
sendTransportFrameToWs(ws, {
|
|
546
562
|
type: 'connected',
|
|
547
563
|
sessionId,
|
|
548
564
|
timestamp: new Date().toISOString(),
|
|
549
|
-
})
|
|
565
|
+
});
|
|
550
566
|
},
|
|
551
567
|
async onMessage(event, ws) {
|
|
552
568
|
try {
|
|
@@ -554,17 +570,17 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
554
570
|
// Handle cancellation (barge-in)
|
|
555
571
|
if (payload.type === 'cancel') {
|
|
556
572
|
runtime.abortSession(sessionId, payload.reason ?? 'User interrupted');
|
|
557
|
-
ws
|
|
573
|
+
sendTransportFrameToWs(ws, {
|
|
558
574
|
type: 'cancelled',
|
|
559
575
|
sessionId,
|
|
560
576
|
timestamp: new Date().toISOString(),
|
|
561
|
-
})
|
|
577
|
+
});
|
|
562
578
|
return;
|
|
563
579
|
}
|
|
564
580
|
const input = payload.content ?? payload.message;
|
|
565
581
|
if (payload.type === 'message') {
|
|
566
582
|
if (typeof input !== 'string' || !input.trim()) {
|
|
567
|
-
ws
|
|
583
|
+
sendHarnessPartToWs(ws, errorPart('message content required'));
|
|
568
584
|
return;
|
|
569
585
|
}
|
|
570
586
|
for await (const part of iterateRuntimeParts(runtime, {
|
|
@@ -577,17 +593,14 @@ export const createKuralleChatRouter = ({ runtime, upgradeWebSocket, widgetWelco
|
|
|
577
593
|
return;
|
|
578
594
|
}
|
|
579
595
|
if (payload.type === 'ping') {
|
|
580
|
-
ws
|
|
596
|
+
sendTransportFrameToWs(ws, { type: 'pong' });
|
|
581
597
|
}
|
|
582
598
|
}
|
|
583
599
|
catch (error) {
|
|
584
600
|
console.error('[Kuralle] Flow WebSocket error:', error);
|
|
585
|
-
ws
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
? error.message
|
|
589
|
-
: 'An error occurred. Please try again.',
|
|
590
|
-
}));
|
|
601
|
+
sendHarnessPartToWs(ws, errorPart(streamFilter === 'all'
|
|
602
|
+
? error.message
|
|
603
|
+
: 'An error occurred. Please try again.'));
|
|
591
604
|
}
|
|
592
605
|
},
|
|
593
606
|
};
|
|
@@ -605,10 +618,10 @@ const collectFlowResponse = async (flowManager, message) => {
|
|
|
605
618
|
let response = '';
|
|
606
619
|
for await (const part of flowManager.process(message)) {
|
|
607
620
|
if (part.type === 'text-delta') {
|
|
608
|
-
response += part.delta;
|
|
621
|
+
response += part.payload.delta;
|
|
609
622
|
}
|
|
610
623
|
if (part.type === 'error') {
|
|
611
|
-
throw new Error(part.error);
|
|
624
|
+
throw new Error(part.payload.error);
|
|
612
625
|
}
|
|
613
626
|
}
|
|
614
627
|
return {
|
|
@@ -616,8 +629,9 @@ const collectFlowResponse = async (flowManager, message) => {
|
|
|
616
629
|
hasEnded: flowManager.hasEnded,
|
|
617
630
|
};
|
|
618
631
|
};
|
|
619
|
-
export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket, }) => {
|
|
632
|
+
export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket, streamFilter: streamFilterOption, }) => {
|
|
620
633
|
const app = new Hono();
|
|
634
|
+
const streamFilter = streamFilterOption ?? 'safe';
|
|
621
635
|
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
622
636
|
app.get('/info', (c) => c.json({
|
|
623
637
|
sessionId,
|
|
@@ -670,7 +684,7 @@ export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket,
|
|
|
670
684
|
try {
|
|
671
685
|
for await (const part of flowManager.process(input)) {
|
|
672
686
|
if (part.type === 'text-delta') {
|
|
673
|
-
controller.enqueue(encoder.encode(part.delta));
|
|
687
|
+
controller.enqueue(encoder.encode(part.payload.delta));
|
|
674
688
|
}
|
|
675
689
|
}
|
|
676
690
|
}
|
|
@@ -713,36 +727,8 @@ export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket,
|
|
|
713
727
|
}
|
|
714
728
|
});
|
|
715
729
|
}
|
|
716
|
-
async function* flowHarnessParts() {
|
|
717
|
-
for await (const part of flowManager.process(input)) {
|
|
718
|
-
if (part.type === 'text-start' ||
|
|
719
|
-
part.type === 'text-delta' ||
|
|
720
|
-
part.type === 'text-end' ||
|
|
721
|
-
part.type === 'text-cancel' ||
|
|
722
|
-
part.type === 'tool-call' ||
|
|
723
|
-
part.type === 'tool-result' ||
|
|
724
|
-
part.type === 'node-enter' ||
|
|
725
|
-
part.type === 'node-exit' ||
|
|
726
|
-
part.type === 'flow-enter' ||
|
|
727
|
-
part.type === 'flow-transition' ||
|
|
728
|
-
part.type === 'flow-end' ||
|
|
729
|
-
part.type === 'handoff' ||
|
|
730
|
-
part.type === 'interactive' ||
|
|
731
|
-
part.type === 'safety-blocked' ||
|
|
732
|
-
part.type === 'pipeline-validation-block' ||
|
|
733
|
-
part.type === 'conversation-outcome' ||
|
|
734
|
-
part.type === 'interrupted' ||
|
|
735
|
-
part.type === 'paused' ||
|
|
736
|
-
part.type === 'custom' ||
|
|
737
|
-
part.type === 'error' ||
|
|
738
|
-
part.type === 'done' ||
|
|
739
|
-
part.type === 'turn-end') {
|
|
740
|
-
yield part;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
730
|
return createUIMessageStreamResponse({
|
|
745
|
-
stream: harnessToUIMessageStream(
|
|
731
|
+
stream: harnessToUIMessageStream(flowManager.process(input), {
|
|
746
732
|
sessionId: bodySessionId ?? sessionId,
|
|
747
733
|
}),
|
|
748
734
|
});
|
|
@@ -752,11 +738,11 @@ export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket,
|
|
|
752
738
|
onOpen(_event, ws) {
|
|
753
739
|
// Generate sessionId for this connection
|
|
754
740
|
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
755
|
-
ws
|
|
741
|
+
sendTransportFrameToWs(ws, {
|
|
756
742
|
type: 'connected',
|
|
757
743
|
sessionId,
|
|
758
744
|
timestamp: new Date().toISOString(),
|
|
759
|
-
})
|
|
745
|
+
});
|
|
760
746
|
// Note: Automatic greeting would require async, but this is sync
|
|
761
747
|
// The greeting will be sent on first message instead
|
|
762
748
|
},
|
|
@@ -766,7 +752,7 @@ export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket,
|
|
|
766
752
|
const input = payload.content ?? payload.message;
|
|
767
753
|
if (payload.type === 'message') {
|
|
768
754
|
if (typeof input !== 'string' || !input.trim()) {
|
|
769
|
-
ws
|
|
755
|
+
sendStreamPartToWs(ws, errorPart('message content required'), streamFilter);
|
|
770
756
|
return;
|
|
771
757
|
}
|
|
772
758
|
// For the first message, prepend a greeting
|
|
@@ -776,19 +762,16 @@ export const createKuralleRouter = ({ flowManager, sessionId, upgradeWebSocket,
|
|
|
776
762
|
fullInput = greeting + input;
|
|
777
763
|
}
|
|
778
764
|
for await (const part of flowManager.process(fullInput)) {
|
|
779
|
-
ws
|
|
765
|
+
sendStreamPartToWs(ws, part, streamFilter);
|
|
780
766
|
}
|
|
781
767
|
return;
|
|
782
768
|
}
|
|
783
769
|
if (payload.type === 'ping') {
|
|
784
|
-
ws
|
|
770
|
+
sendTransportFrameToWs(ws, { type: 'pong' });
|
|
785
771
|
}
|
|
786
772
|
}
|
|
787
773
|
catch (error) {
|
|
788
|
-
ws
|
|
789
|
-
type: 'error',
|
|
790
|
-
error: error.message,
|
|
791
|
-
}));
|
|
774
|
+
sendStreamPartToWs(ws, errorPart(error.message), streamFilter);
|
|
792
775
|
}
|
|
793
776
|
}
|
|
794
777
|
})));
|
package/dist/openaiCompat.js
CHANGED
|
@@ -175,16 +175,18 @@ async function collectTurn(handle, clientTools) {
|
|
|
175
175
|
if (stopEarly)
|
|
176
176
|
continue;
|
|
177
177
|
if (part.type === 'text-delta') {
|
|
178
|
-
text += part.delta;
|
|
178
|
+
text += part.payload.delta;
|
|
179
179
|
}
|
|
180
180
|
else if (part.type === 'tool-call') {
|
|
181
|
-
if (!clientTools.has(part.toolName))
|
|
181
|
+
if (!clientTools.has(part.payload.toolName))
|
|
182
182
|
continue;
|
|
183
|
-
const argsStr = typeof part.args === 'string'
|
|
183
|
+
const argsStr = typeof part.payload.args === 'string'
|
|
184
|
+
? part.payload.args
|
|
185
|
+
: JSON.stringify(part.payload.args ?? {});
|
|
184
186
|
toolCalls.push({
|
|
185
187
|
index: toolCalls.length,
|
|
186
|
-
id: part.toolCallId ?? `call_${crypto.randomBytes(6).toString('hex')}`,
|
|
187
|
-
name: part.toolName,
|
|
188
|
+
id: part.payload.toolCallId ?? `call_${crypto.randomBytes(6).toString('hex')}`,
|
|
189
|
+
name: part.payload.toolName,
|
|
188
190
|
argsBuffer: argsStr,
|
|
189
191
|
nameSent: false,
|
|
190
192
|
argsSentLength: 0,
|
|
@@ -283,19 +285,21 @@ async function handleChatCompletions(c, body, opts) {
|
|
|
283
285
|
if (stopEarly)
|
|
284
286
|
continue;
|
|
285
287
|
if (part.type === 'text-delta') {
|
|
286
|
-
if (!part.delta)
|
|
288
|
+
if (!part.payload.delta)
|
|
287
289
|
continue;
|
|
288
|
-
completionText += part.delta;
|
|
289
|
-
await writeChunk({ content: part.delta });
|
|
290
|
+
completionText += part.payload.delta;
|
|
291
|
+
await writeChunk({ content: part.payload.delta });
|
|
290
292
|
}
|
|
291
293
|
else if (part.type === 'tool-call') {
|
|
292
|
-
if (!clientToolSet.has(part.toolName))
|
|
294
|
+
if (!clientToolSet.has(part.payload.toolName))
|
|
293
295
|
continue;
|
|
294
|
-
const argsStr = typeof part.args === 'string'
|
|
296
|
+
const argsStr = typeof part.payload.args === 'string'
|
|
297
|
+
? part.payload.args
|
|
298
|
+
: JSON.stringify(part.payload.args ?? {});
|
|
295
299
|
const acc = {
|
|
296
300
|
index: pendingToolCalls.length,
|
|
297
|
-
id: part.toolCallId ?? `call_${crypto.randomBytes(6).toString('hex')}`,
|
|
298
|
-
name: part.toolName,
|
|
301
|
+
id: part.payload.toolCallId ?? `call_${crypto.randomBytes(6).toString('hex')}`,
|
|
302
|
+
name: part.payload.toolName,
|
|
299
303
|
argsBuffer: argsStr,
|
|
300
304
|
nameSent: false,
|
|
301
305
|
argsSentLength: 0,
|
package/dist/streamFilter.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
5
|
-
type: string;
|
|
6
|
-
}, filter: StreamEventFilter): boolean;
|
|
7
|
-
export declare function sanitizeForClient(part: {
|
|
8
|
-
type: string;
|
|
9
|
-
error?: string;
|
|
10
|
-
}): {
|
|
11
|
-
type: string;
|
|
12
|
-
error?: string;
|
|
13
|
-
};
|
|
1
|
+
import type { StreamPart } from '@kuralle-agents/core';
|
|
2
|
+
export type StreamEventFilter = 'safe' | 'all' | ((part: StreamPart) => boolean);
|
|
3
|
+
export declare function shouldEmit(part: StreamPart, filter: StreamEventFilter): boolean;
|
|
4
|
+
export declare function sanitizeForClient(part: StreamPart): StreamPart;
|
package/dist/streamFilter.js
CHANGED
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
'text-start',
|
|
3
|
-
'text-delta',
|
|
4
|
-
'text-end',
|
|
5
|
-
'text-cancel',
|
|
6
|
-
'text-clear',
|
|
7
|
-
'done',
|
|
8
|
-
'error',
|
|
9
|
-
'suggested-questions',
|
|
10
|
-
'conversation-outcome',
|
|
11
|
-
'knowledge-citation',
|
|
12
|
-
'knowledge-no-results',
|
|
13
|
-
'input',
|
|
14
|
-
]);
|
|
1
|
+
import { PART_CHANNEL } from '@kuralle-agents/core';
|
|
15
2
|
export function shouldEmit(part, filter) {
|
|
16
3
|
if (filter === 'all')
|
|
17
4
|
return true;
|
|
18
5
|
if (typeof filter === 'function')
|
|
19
6
|
return filter(part);
|
|
20
|
-
return
|
|
7
|
+
return PART_CHANNEL[part.type] === 'client';
|
|
21
8
|
}
|
|
22
9
|
export function sanitizeForClient(part) {
|
|
23
10
|
if (part.type === 'error') {
|
|
24
|
-
console.error('[Kuralle] Client-facing error suppressed:', part.error);
|
|
25
|
-
return {
|
|
11
|
+
console.error('[Kuralle] Client-facing error suppressed:', part.payload.error);
|
|
12
|
+
return {
|
|
13
|
+
...part,
|
|
14
|
+
payload: { error: 'An error occurred. Please try again.' },
|
|
15
|
+
};
|
|
26
16
|
}
|
|
27
17
|
return part;
|
|
28
18
|
}
|
package/package.json
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/kuralle/kuralle-agents.git",
|
|
7
|
-
"directory": "packages/
|
|
7
|
+
"directory": "packages/hono-server"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.14.0",
|
|
10
10
|
"description": "Hono router for hosting Kuralle createRuntime() over HTTP, SSE, and WebSocket",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/index.js",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"ai": "^6.0.0",
|
|
22
22
|
"hono": "^4.0.0",
|
|
23
|
-
"@kuralle-agents/core": "0.
|
|
23
|
+
"@kuralle-agents/core": "0.14.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@kuralle-agents/core": "0.
|
|
26
|
+
"@kuralle-agents/core": "0.14.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@ai-sdk/openai": "^3.0.0",
|