@nextclaw/ui 0.6.10 → 0.6.12

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 (90) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/CHANGELOG.md +16 -0
  3. package/dist/assets/ChannelsList-DBDjwf-X.js +1 -0
  4. package/dist/assets/ChatPage-C18sGGk1.js +36 -0
  5. package/dist/assets/DocBrowser-ZOplDEMS.js +1 -0
  6. package/dist/assets/LogoBadge-2LMzEMwe.js +1 -0
  7. package/dist/assets/MarketplacePage-D4JHYcB5.js +49 -0
  8. package/dist/assets/ModelConfig-DZVvdLFq.js +1 -0
  9. package/dist/assets/ProvidersList-Dum31480.js +1 -0
  10. package/dist/assets/{RuntimeConfig-BO6s-ls-.js → RuntimeConfig-4sb3mpkd.js} +1 -1
  11. package/dist/assets/SearchConfig-B4u_MxRG.js +1 -0
  12. package/dist/assets/{SecretsConfig-mayFdxpM.js → SecretsConfig-BQXblZvb.js} +2 -2
  13. package/dist/assets/SessionsConfig-Jk29xjQU.js +2 -0
  14. package/dist/assets/{card-BP5YnL-G.js → card-BekAnCgX.js} +1 -1
  15. package/dist/assets/config-layout-BHnOoweL.js +1 -0
  16. package/dist/assets/index-BXwjfCEO.css +1 -0
  17. package/dist/assets/index-Dl6t70wA.js +8 -0
  18. package/dist/assets/{input-B1D2QX0O.js → input-MMn_Na9q.js} +1 -1
  19. package/dist/assets/{label-DW0j-fXA.js → label-Dg2ydpN0.js} +1 -1
  20. package/dist/assets/{page-layout-Ch-H9gD-.js → page-layout-7K0rcz0I.js} +1 -1
  21. package/dist/assets/session-run-status-CAdjSqeb.js +3 -0
  22. package/dist/assets/{switch-_cZHlGKB.js → switch-DnDMlDVu.js} +1 -1
  23. package/dist/assets/{tabs-custom-ARxqYYjG.js → tabs-custom-khLM8lWj.js} +1 -1
  24. package/dist/assets/{useConfirmDialog-BaU7nIat.js → useConfirmDialog-BYA1XnVU.js} +2 -2
  25. package/dist/assets/{vendor-C--HHaLf.js → vendor-d7E8OgNx.js} +84 -84
  26. package/dist/index.html +3 -3
  27. package/package.json +4 -2
  28. package/src/App.tsx +3 -2
  29. package/src/api/config.ts +212 -200
  30. package/src/api/types.ts +93 -24
  31. package/src/components/chat/ChatConversationPanel.tsx +102 -121
  32. package/src/components/chat/ChatPage.tsx +165 -437
  33. package/src/components/chat/ChatSidebar.tsx +30 -36
  34. package/src/components/chat/ChatThread.tsx +73 -131
  35. package/src/components/chat/chat-input/ChatInputBarView.tsx +82 -0
  36. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +71 -0
  37. package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +39 -0
  38. package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +31 -0
  39. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +112 -0
  40. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +24 -0
  41. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +58 -0
  42. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +56 -0
  43. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +40 -0
  44. package/src/components/chat/chat-input/useChatInputBarController.ts +313 -0
  45. package/src/components/chat/chat-input.types.ts +15 -0
  46. package/src/components/chat/chat-page-data.ts +121 -0
  47. package/src/components/chat/chat-page-runtime.ts +221 -0
  48. package/src/components/chat/chat-session-route.ts +59 -0
  49. package/src/components/chat/chat-stream/nextbot-parsers.ts +52 -0
  50. package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +413 -0
  51. package/src/components/chat/chat-stream/stream-event-adapter.ts +98 -0
  52. package/src/components/chat/chat-stream/transport.ts +159 -0
  53. package/src/components/chat/chat-stream/types.ts +76 -0
  54. package/src/components/chat/managers/chat-input.manager.ts +142 -0
  55. package/src/components/chat/managers/chat-run-status.manager.ts +32 -0
  56. package/src/components/chat/managers/chat-session-list.manager.ts +77 -0
  57. package/src/components/chat/managers/chat-stream-actions.manager.ts +34 -0
  58. package/src/components/chat/managers/chat-thread.manager.ts +86 -0
  59. package/src/components/chat/managers/chat-ui.manager.ts +65 -0
  60. package/src/components/chat/presenter/chat-presenter-context.tsx +25 -0
  61. package/src/components/chat/presenter/chat.presenter.ts +32 -0
  62. package/src/components/chat/stores/chat-input.store.ts +62 -0
  63. package/src/components/chat/stores/chat-run-status.store.ts +30 -0
  64. package/src/components/chat/stores/chat-session-list.store.ts +34 -0
  65. package/src/components/chat/stores/chat-thread.store.ts +52 -0
  66. package/src/components/chat/useChatRuntimeController.ts +134 -0
  67. package/src/components/chat/useChatSessionTypeState.ts +148 -0
  68. package/src/components/common/MaskedInput.tsx +1 -1
  69. package/src/components/config/SearchConfig.tsx +297 -0
  70. package/src/components/layout/Sidebar.tsx +6 -1
  71. package/src/hooks/useConfig.ts +48 -1
  72. package/src/hooks/useObservable.ts +20 -0
  73. package/src/lib/chat-message.ts +2 -202
  74. package/src/lib/chat-runtime-utils.ts +250 -0
  75. package/src/lib/i18n.ts +31 -0
  76. package/tsconfig.json +2 -1
  77. package/vite.config.ts +2 -1
  78. package/dist/assets/ChannelsList-TyMb5Mgz.js +0 -1
  79. package/dist/assets/ChatPage-CQerYqvy.js +0 -34
  80. package/dist/assets/DocBrowser-CNtrA0ps.js +0 -1
  81. package/dist/assets/LogoBadge-BLqiOM5D.js +0 -1
  82. package/dist/assets/MarketplacePage-CotZxxNe.js +0 -49
  83. package/dist/assets/ModelConfig-CCsQ8KFq.js +0 -1
  84. package/dist/assets/ProvidersList-BYYX5K_g.js +0 -1
  85. package/dist/assets/SessionsConfig-DAIczdBj.js +0 -2
  86. package/dist/assets/index-BUiahmWm.css +0 -1
  87. package/dist/assets/index-D6_5HaDl.js +0 -7
  88. package/dist/assets/session-run-status-BUYsQeWs.js +0 -5
  89. package/src/components/chat/ChatInputBar.tsx +0 -590
  90. package/src/components/chat/useChatStreamController.ts +0 -591
package/dist/index.html CHANGED
@@ -6,9 +6,9 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw - 系统配置</title>
9
- <script type="module" crossorigin src="/assets/index-D6_5HaDl.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-C--HHaLf.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-BUiahmWm.css">
9
+ <script type="module" crossorigin src="/assets/index-Dl6t70wA.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-d7E8OgNx.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-BXwjfCEO.css">
12
12
  </head>
13
13
 
14
14
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,10 +24,12 @@
24
24
  "react-router-dom": "^7.13.0",
25
25
  "rehype-sanitize": "^6.0.0",
26
26
  "remark-gfm": "^4.0.1",
27
+ "rxjs": "^7.8.2",
27
28
  "sonner": "^1.7.1",
28
29
  "tailwind-merge": "^2.5.4",
29
30
  "zod": "^3.23.8",
30
- "zustand": "^5.0.2"
31
+ "zustand": "^5.0.2",
32
+ "@nextclaw/agent-chat": "0.1.1"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@types/react": "^18.3.12",
package/src/App.tsx CHANGED
@@ -16,6 +16,7 @@ const queryClient = new QueryClient({
16
16
 
17
17
  const ModelConfigPage = lazy(async () => ({ default: (await import('@/components/config/ModelConfig')).ModelConfig }));
18
18
  const ChatPage = lazy(async () => ({ default: (await import('@/components/chat/ChatPage')).ChatPage }));
19
+ const SearchConfigPage = lazy(async () => ({ default: (await import('@/components/config/SearchConfig')).SearchConfig }));
19
20
  const ProvidersListPage = lazy(async () => ({ default: (await import('@/components/config/ProvidersList')).ProvidersList }));
20
21
  const ChannelsListPage = lazy(async () => ({ default: (await import('@/components/config/ChannelsList')).ChannelsList }));
21
22
  const RuntimeConfigPage = lazy(async () => ({ default: (await import('@/components/config/RuntimeConfig')).RuntimeConfig }));
@@ -41,11 +42,11 @@ function AppContent() {
41
42
  <Routes>
42
43
  <Route path="/chat/skills" element={<Navigate to="/skills" replace />} />
43
44
  <Route path="/chat/cron" element={<Navigate to="/cron" replace />} />
44
- <Route path="/chat/:sessionId" element={<LazyRoute><ChatPage view="chat" /></LazyRoute>} />
45
- <Route path="/chat" element={<LazyRoute><ChatPage view="chat" /></LazyRoute>} />
45
+ <Route path="/chat/:sessionId?" element={<LazyRoute><ChatPage view="chat" /></LazyRoute>} />
46
46
  <Route path="/skills" element={<LazyRoute><ChatPage view="skills" /></LazyRoute>} />
47
47
  <Route path="/cron" element={<LazyRoute><ChatPage view="cron" /></LazyRoute>} />
48
48
  <Route path="/model" element={<LazyRoute><ModelConfigPage /></LazyRoute>} />
49
+ <Route path="/search" element={<LazyRoute><SearchConfigPage /></LazyRoute>} />
49
50
  <Route path="/providers" element={<LazyRoute><ProvidersListPage /></LazyRoute>} />
50
51
  <Route path="/channels" element={<LazyRoute><ChannelsListPage /></LazyRoute>} />
51
52
  <Route path="/runtime" element={<LazyRoute><RuntimeConfigPage /></LazyRoute>} />
package/src/api/config.ts CHANGED
@@ -14,6 +14,8 @@ import type {
14
14
  ProviderAuthPollRequest,
15
15
  ProviderAuthPollResult,
16
16
  ProviderAuthImportResult,
17
+ SearchConfigUpdate,
18
+ SearchConfigView,
17
19
  ProviderCreateRequest,
18
20
  ProviderCreateResult,
19
21
  ProviderDeleteResult,
@@ -27,7 +29,12 @@ import type {
27
29
  SessionPatchUpdate,
28
30
  ChatTurnRequest,
29
31
  ChatTurnView,
32
+ ChatTurnStreamDeltaEvent,
33
+ ChatTurnStreamErrorEvent,
34
+ ChatTurnStreamReadyEvent,
35
+ ChatTurnStreamSessionEvent,
30
36
  ChatCapabilitiesView,
37
+ ChatSessionTypesView,
31
38
  ChatTurnStopRequest,
32
39
  ChatTurnStopResult,
33
40
  ChatRunListView,
@@ -36,10 +43,7 @@ import type {
36
43
  CronListView,
37
44
  CronEnableRequest,
38
45
  CronRunRequest,
39
- CronActionResult,
40
- ChatTurnStreamReadyEvent,
41
- ChatTurnStreamDeltaEvent,
42
- ChatTurnStreamSessionEvent
46
+ CronActionResult
43
47
  } from './types';
44
48
 
45
49
  // GET /api/app/meta
@@ -89,6 +93,17 @@ export async function updateModel(data: {
89
93
  return response.data;
90
94
  }
91
95
 
96
+ // PUT /api/config/search
97
+ export async function updateSearch(
98
+ data: SearchConfigUpdate
99
+ ): Promise<SearchConfigView> {
100
+ const response = await api.put<SearchConfigView>('/api/config/search', data);
101
+ if (!response.ok) {
102
+ throw new Error(response.error.message);
103
+ }
104
+ return response.data;
105
+ }
106
+
92
107
  // PUT /api/config/providers/:provider
93
108
  export async function updateProvider(
94
109
  provider: string,
@@ -304,91 +319,24 @@ export async function sendChatTurn(data: ChatTurnRequest): Promise<ChatTurnView>
304
319
  return response.data;
305
320
  }
306
321
 
307
- // GET /api/chat/capabilities
308
- export async function fetchChatCapabilities(params?: { sessionKey?: string; agentId?: string }): Promise<ChatCapabilitiesView> {
309
- const query = new URLSearchParams();
310
- if (params?.sessionKey?.trim()) {
311
- query.set('sessionKey', params.sessionKey.trim());
312
- }
313
- if (params?.agentId?.trim()) {
314
- query.set('agentId', params.agentId.trim());
315
- }
316
- const suffix = query.toString();
317
- const response = await api.get<ChatCapabilitiesView>(suffix ? `/api/chat/capabilities?${suffix}` : '/api/chat/capabilities');
318
- if (!response.ok) {
319
- throw new Error(response.error.message);
320
- }
321
- return response.data;
322
- }
323
-
324
- // POST /api/chat/turn/stop
325
- export async function stopChatTurn(data: ChatTurnStopRequest): Promise<ChatTurnStopResult> {
326
- const response = await api.post<ChatTurnStopResult>('/api/chat/turn/stop', data);
327
- if (!response.ok) {
328
- throw new Error(response.error.message);
329
- }
330
- return response.data;
331
- }
332
-
333
- // GET /api/chat/runs
334
- export async function fetchChatRuns(params?: {
335
- sessionKey?: string;
336
- states?: ChatRunState[];
337
- limit?: number;
338
- }): Promise<ChatRunListView> {
339
- const query = new URLSearchParams();
340
- if (params?.sessionKey?.trim()) {
341
- query.set('sessionKey', params.sessionKey.trim());
342
- }
343
- if (Array.isArray(params?.states) && params.states.length > 0) {
344
- query.set('states', params.states.join(','));
345
- }
346
- if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
347
- query.set('limit', String(Math.max(0, Math.trunc(params.limit))));
348
- }
349
- const suffix = query.toString();
350
- const response = await api.get<ChatRunListView>(suffix ? `/api/chat/runs?${suffix}` : '/api/chat/runs');
351
- if (!response.ok) {
352
- throw new Error(response.error.message);
353
- }
354
- return response.data;
355
- }
356
-
357
- // GET /api/chat/runs/:runId
358
- export async function fetchChatRun(runId: string): Promise<ChatRunView> {
359
- const response = await api.get<ChatRunView>(`/api/chat/runs/${encodeURIComponent(runId)}`);
360
- if (!response.ok) {
361
- throw new Error(response.error.message);
362
- }
363
- return response.data;
364
- }
365
-
366
- type ChatTurnStreamOptions = {
367
- signal?: AbortSignal;
368
- onReady?: (event: ChatTurnStreamReadyEvent) => void;
369
- onDelta?: (event: ChatTurnStreamDeltaEvent) => void;
370
- onSessionEvent?: (event: ChatTurnStreamSessionEvent) => void;
371
- };
372
-
373
- type SseParsedEvent = {
374
- event: string;
375
- data: string;
376
- };
377
-
378
- function parseSseFrame(frame: string): SseParsedEvent | null {
379
- const lines = frame.split(/\r?\n/);
380
- let event = 'message';
322
+ function parseSseFrame(frame: string): { event: string; data: string } | null {
323
+ const lines = frame.split('\n');
324
+ let event = '';
381
325
  const dataLines: string[] = [];
382
- for (const line of lines) {
326
+ for (const raw of lines) {
327
+ const line = raw.trimEnd();
328
+ if (!line || line.startsWith(':')) {
329
+ continue;
330
+ }
383
331
  if (line.startsWith('event:')) {
384
- event = line.slice('event:'.length).trim() || 'message';
332
+ event = line.slice(6).trim();
385
333
  continue;
386
334
  }
387
335
  if (line.startsWith('data:')) {
388
- dataLines.push(line.slice('data:'.length).trimStart());
336
+ dataLines.push(line.slice(5).trimStart());
389
337
  }
390
338
  }
391
- if (dataLines.length === 0) {
339
+ if (!event) {
392
340
  return null;
393
341
  }
394
342
  return {
@@ -397,178 +345,242 @@ function parseSseFrame(frame: string): SseParsedEvent | null {
397
345
  };
398
346
  }
399
347
 
400
- async function consumeChatTurnSseStream(
401
- response: Response,
402
- options: ChatTurnStreamOptions = {}
403
- ): Promise<ChatTurnView> {
348
+ async function readSseStream(params: {
349
+ path: string;
350
+ method: 'GET' | 'POST';
351
+ body?: unknown;
352
+ signal?: AbortSignal;
353
+ onReady: (event: ChatTurnStreamReadyEvent) => void;
354
+ onDelta: (event: ChatTurnStreamDeltaEvent) => void;
355
+ onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
356
+ }): Promise<{ sessionKey: string; reply: string }> {
357
+ const response = await fetch(`${API_BASE}${params.path}`, {
358
+ method: params.method,
359
+ headers: {
360
+ 'Content-Type': 'application/json',
361
+ Accept: 'text/event-stream'
362
+ },
363
+ ...(params.body !== undefined ? { body: JSON.stringify(params.body) } : {}),
364
+ ...(params.signal ? { signal: params.signal } : {})
365
+ });
366
+
404
367
  if (!response.ok) {
405
- let message = `chat stream failed (${response.status} ${response.statusText})`;
406
- try {
407
- const payload = await response.json() as { ok?: boolean; error?: { message?: string } };
408
- if (payload?.error?.message) {
409
- message = payload.error.message;
410
- }
411
- } catch {
412
- const text = await response.text().catch(() => '');
413
- if (text.trim()) {
414
- message = text.trim();
415
- }
416
- }
417
- throw new Error(message);
368
+ const text = await response.text();
369
+ const fallback = `HTTP ${response.status}`;
370
+ const trimmed = text.trim();
371
+ throw new Error(trimmed || fallback);
418
372
  }
419
373
 
420
- if (!response.body) {
421
- throw new Error('chat stream is not readable');
374
+ const reader = response.body?.getReader();
375
+ if (!reader) {
376
+ throw new Error('SSE response body unavailable');
422
377
  }
423
378
 
424
- const reader = response.body.getReader();
425
379
  const decoder = new TextDecoder();
426
380
  let buffer = '';
427
- let finalView: ChatTurnView | null = null;
381
+ let finalResult: { sessionKey: string; reply: string } | null = null;
382
+ let readySessionKey: string | null = null;
428
383
 
429
- const handleFrame = (frame: string) => {
384
+ const consumeFrame = (frame: string) => {
430
385
  const parsed = parseSseFrame(frame);
431
386
  if (!parsed) {
432
387
  return;
433
388
  }
434
- if (parsed.event === 'ready') {
389
+
390
+ let payload: unknown = undefined;
391
+ if (parsed.data) {
435
392
  try {
436
- const readyPayload = JSON.parse(parsed.data) as {
437
- sessionKey?: string;
438
- requestedAt?: string;
439
- runId?: string;
440
- stopSupported?: boolean;
441
- stopReason?: string;
442
- };
443
- options.onReady?.({
444
- event: 'ready',
445
- sessionKey: String(readyPayload.sessionKey ?? ''),
446
- requestedAt: String(readyPayload.requestedAt ?? ''),
447
- ...(typeof readyPayload.runId === 'string' && readyPayload.runId.trim().length > 0
448
- ? { runId: readyPayload.runId.trim() }
449
- : {}),
450
- ...(typeof readyPayload.stopSupported === 'boolean'
451
- ? { stopSupported: readyPayload.stopSupported }
452
- : {}),
453
- ...(typeof readyPayload.stopReason === 'string' && readyPayload.stopReason.trim().length > 0
454
- ? { stopReason: readyPayload.stopReason.trim() }
455
- : {})
456
- });
393
+ payload = JSON.parse(parsed.data);
457
394
  } catch {
458
- // ignore malformed ready event payload
395
+ payload = undefined;
459
396
  }
397
+ }
398
+
399
+ if (parsed.event === 'ready') {
400
+ const ready = (payload ?? {}) as ChatTurnStreamReadyEvent;
401
+ readySessionKey = typeof ready.sessionKey === 'string' && ready.sessionKey.trim() ? ready.sessionKey : readySessionKey;
402
+ params.onReady(ready);
460
403
  return;
461
404
  }
405
+
462
406
  if (parsed.event === 'delta') {
463
- try {
464
- const deltaPayload = JSON.parse(parsed.data) as { delta?: string };
465
- if (typeof deltaPayload.delta === 'string' && deltaPayload.delta.length > 0) {
466
- options.onDelta?.({
467
- event: 'delta',
468
- delta: deltaPayload.delta
469
- });
470
- }
471
- } catch {
472
- // ignore malformed delta event payload
473
- }
407
+ params.onDelta((payload ?? { delta: '' }) as ChatTurnStreamDeltaEvent);
474
408
  return;
475
409
  }
410
+
476
411
  if (parsed.event === 'session_event') {
477
- try {
478
- options.onSessionEvent?.({
479
- event: 'session_event',
480
- data: JSON.parse(parsed.data)
481
- });
482
- } catch {
483
- // ignore malformed session_event payload
484
- }
412
+ params.onSessionEvent({ data: payload as ChatTurnStreamSessionEvent['data'] });
485
413
  return;
486
414
  }
415
+
487
416
  if (parsed.event === 'final') {
488
- finalView = JSON.parse(parsed.data) as ChatTurnView;
417
+ const result = payload as ChatTurnView;
418
+ finalResult = {
419
+ sessionKey: typeof result?.sessionKey === 'string' && result.sessionKey.trim()
420
+ ? result.sessionKey
421
+ : (readySessionKey ?? ''),
422
+ reply: typeof result?.reply === 'string' ? result.reply : ''
423
+ };
489
424
  return;
490
425
  }
426
+
491
427
  if (parsed.event === 'error') {
492
- try {
493
- const errPayload = JSON.parse(parsed.data) as { message?: string };
494
- throw new Error(typeof errPayload.message === 'string' && errPayload.message ? errPayload.message : 'chat stream failed');
495
- } catch (error) {
496
- if (error instanceof Error) {
497
- throw error;
498
- }
499
- throw new Error('chat stream failed');
500
- }
428
+ const errorPayload = (payload ?? {}) as ChatTurnStreamErrorEvent;
429
+ throw new Error((errorPayload.message ?? '').trim() || 'chat stream failed');
501
430
  }
502
431
  };
503
432
 
504
- let streamDone = false;
505
- while (!streamDone) {
506
- const { done, value } = await reader.read();
507
- if (done) {
508
- streamDone = true;
509
- continue;
510
- }
511
- buffer += decoder.decode(value, { stream: true });
512
-
513
- let boundary = buffer.indexOf('\n\n');
514
- while (boundary !== -1) {
515
- const frame = buffer.slice(0, boundary).trim();
516
- buffer = buffer.slice(boundary + 2);
517
- if (frame) {
518
- handleFrame(frame);
433
+ try {
434
+ let isReading = true;
435
+ while (isReading) {
436
+ const { value, done } = await reader.read();
437
+ if (done) {
438
+ isReading = false;
439
+ continue;
440
+ }
441
+ buffer += decoder.decode(value, { stream: true });
442
+ let boundary = buffer.indexOf('\n\n');
443
+ while (boundary !== -1) {
444
+ const frame = buffer.slice(0, boundary);
445
+ buffer = buffer.slice(boundary + 2);
446
+ consumeFrame(frame);
447
+ boundary = buffer.indexOf('\n\n');
519
448
  }
520
- boundary = buffer.indexOf('\n\n');
521
449
  }
450
+ if (buffer.trim()) {
451
+ consumeFrame(buffer);
452
+ }
453
+ } finally {
454
+ reader.releaseLock();
522
455
  }
523
456
 
524
- const trailing = buffer.trim();
525
- if (trailing) {
526
- handleFrame(trailing);
457
+ if (finalResult) {
458
+ return finalResult;
527
459
  }
528
460
 
529
- if (!finalView) {
530
- throw new Error('chat stream ended without final result');
461
+ if (readySessionKey) {
462
+ return { sessionKey: readySessionKey, reply: '' };
531
463
  }
532
- return finalView;
464
+
465
+ throw new Error('chat stream ended without final event');
533
466
  }
534
467
 
468
+ // POST /api/chat/turn/stream
535
469
  export async function sendChatTurnStream(
536
470
  data: ChatTurnRequest,
537
- options: ChatTurnStreamOptions = {}
538
- ): Promise<ChatTurnView> {
539
- const response = await fetch(`${API_BASE}/api/chat/turn/stream`, {
471
+ params: {
472
+ signal?: AbortSignal;
473
+ onReady: (event: ChatTurnStreamReadyEvent) => void;
474
+ onDelta: (event: ChatTurnStreamDeltaEvent) => void;
475
+ onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
476
+ }
477
+ ): Promise<{ sessionKey: string; reply: string }> {
478
+ return readSseStream({
479
+ path: '/api/chat/turn/stream',
540
480
  method: 'POST',
541
- headers: {
542
- 'Content-Type': 'application/json',
543
- Accept: 'text/event-stream'
544
- },
545
- body: JSON.stringify(data),
546
- signal: options.signal
481
+ body: data,
482
+ signal: params.signal,
483
+ onReady: params.onReady,
484
+ onDelta: params.onDelta,
485
+ onSessionEvent: params.onSessionEvent
547
486
  });
548
- return consumeChatTurnSseStream(response, options);
549
487
  }
550
488
 
489
+ // GET /api/chat/runs/:runId/stream
551
490
  export async function streamChatRun(
552
- params: {
491
+ data: {
553
492
  runId: string;
554
493
  fromEventIndex?: number;
555
494
  },
556
- options: ChatTurnStreamOptions = {}
557
- ): Promise<ChatTurnView> {
495
+ params: {
496
+ signal?: AbortSignal;
497
+ onReady: (event: ChatTurnStreamReadyEvent) => void;
498
+ onDelta: (event: ChatTurnStreamDeltaEvent) => void;
499
+ onSessionEvent: (event: ChatTurnStreamSessionEvent) => void;
500
+ }
501
+ ): Promise<{ sessionKey: string; reply: string }> {
558
502
  const query = new URLSearchParams();
559
- if (typeof params.fromEventIndex === 'number' && Number.isFinite(params.fromEventIndex)) {
560
- query.set('fromEventIndex', String(Math.max(0, Math.trunc(params.fromEventIndex))));
503
+ if (typeof data.fromEventIndex === 'number' && Number.isFinite(data.fromEventIndex)) {
504
+ query.set('fromEventIndex', String(Math.max(0, Math.trunc(data.fromEventIndex))));
561
505
  }
562
506
  const suffix = query.toString();
563
- const url = `${API_BASE}/api/chat/runs/${encodeURIComponent(params.runId)}/stream${suffix ? `?${suffix}` : ''}`;
564
- const response = await fetch(url, {
507
+ const path = `/api/chat/runs/${encodeURIComponent(data.runId)}/stream${suffix ? `?${suffix}` : ''}`;
508
+ return readSseStream({
509
+ path,
565
510
  method: 'GET',
566
- headers: {
567
- Accept: 'text/event-stream'
568
- },
569
- signal: options.signal
511
+ signal: params.signal,
512
+ onReady: params.onReady,
513
+ onDelta: params.onDelta,
514
+ onSessionEvent: params.onSessionEvent
570
515
  });
571
- return consumeChatTurnSseStream(response, options);
516
+ }
517
+
518
+ // GET /api/chat/capabilities
519
+ export async function fetchChatCapabilities(params?: { sessionKey?: string; agentId?: string }): Promise<ChatCapabilitiesView> {
520
+ const query = new URLSearchParams();
521
+ if (params?.sessionKey?.trim()) {
522
+ query.set('sessionKey', params.sessionKey.trim());
523
+ }
524
+ if (params?.agentId?.trim()) {
525
+ query.set('agentId', params.agentId.trim());
526
+ }
527
+ const suffix = query.toString();
528
+ const response = await api.get<ChatCapabilitiesView>(suffix ? `/api/chat/capabilities?${suffix}` : '/api/chat/capabilities');
529
+ if (!response.ok) {
530
+ throw new Error(response.error.message);
531
+ }
532
+ return response.data;
533
+ }
534
+
535
+ // GET /api/chat/session-types
536
+ export async function fetchChatSessionTypes(): Promise<ChatSessionTypesView> {
537
+ const response = await api.get<ChatSessionTypesView>('/api/chat/session-types');
538
+ if (!response.ok) {
539
+ throw new Error(response.error.message);
540
+ }
541
+ return response.data;
542
+ }
543
+
544
+ // POST /api/chat/turn/stop
545
+ export async function stopChatTurn(data: ChatTurnStopRequest): Promise<ChatTurnStopResult> {
546
+ const response = await api.post<ChatTurnStopResult>('/api/chat/turn/stop', data);
547
+ if (!response.ok) {
548
+ throw new Error(response.error.message);
549
+ }
550
+ return response.data;
551
+ }
552
+
553
+ // GET /api/chat/runs
554
+ export async function fetchChatRuns(params?: {
555
+ sessionKey?: string;
556
+ states?: ChatRunState[];
557
+ limit?: number;
558
+ }): Promise<ChatRunListView> {
559
+ const query = new URLSearchParams();
560
+ if (params?.sessionKey?.trim()) {
561
+ query.set('sessionKey', params.sessionKey.trim());
562
+ }
563
+ if (Array.isArray(params?.states) && params.states.length > 0) {
564
+ query.set('states', params.states.join(','));
565
+ }
566
+ if (typeof params?.limit === 'number' && Number.isFinite(params.limit)) {
567
+ query.set('limit', String(Math.max(0, Math.trunc(params.limit))));
568
+ }
569
+ const suffix = query.toString();
570
+ const response = await api.get<ChatRunListView>(suffix ? `/api/chat/runs?${suffix}` : '/api/chat/runs');
571
+ if (!response.ok) {
572
+ throw new Error(response.error.message);
573
+ }
574
+ return response.data;
575
+ }
576
+
577
+ // GET /api/chat/runs/:runId
578
+ export async function fetchChatRun(runId: string): Promise<ChatRunView> {
579
+ const response = await api.get<ChatRunView>(`/api/chat/runs/${encodeURIComponent(runId)}`);
580
+ if (!response.ok) {
581
+ throw new Error(response.error.message);
582
+ }
583
+ return response.data;
572
584
  }
573
585
 
574
586
  // GET /api/cron