@nextclaw/ui 0.6.10 → 0.6.11

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 (80) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/CHANGELOG.md +9 -0
  3. package/dist/assets/{ChannelsList-TyMb5Mgz.js → ChannelsList-C49JQ-Zt.js} +1 -1
  4. package/dist/assets/ChatPage-DIx05c6s.js +36 -0
  5. package/dist/assets/{DocBrowser-CNtrA0ps.js → DocBrowser-CpOosDEI.js} +1 -1
  6. package/dist/assets/{LogoBadge-BLqiOM5D.js → LogoBadge-CL_8ZPXU.js} +1 -1
  7. package/dist/assets/MarketplacePage-BOzko5s9.js +49 -0
  8. package/dist/assets/{ModelConfig-CCsQ8KFq.js → ModelConfig-BZ4ZfaQB.js} +1 -1
  9. package/dist/assets/ProvidersList-fPpJ5gl6.js +1 -0
  10. package/dist/assets/{RuntimeConfig-BO6s-ls-.js → RuntimeConfig-Dt9pLB9P.js} +1 -1
  11. package/dist/assets/{SecretsConfig-mayFdxpM.js → SecretsConfig-C1PU0Yy8.js} +2 -2
  12. package/dist/assets/{SessionsConfig-DAIczdBj.js → SessionsConfig-EskBOofQ.js} +2 -2
  13. package/dist/assets/{card-BP5YnL-G.js → card-C7Gtw2Vs.js} +1 -1
  14. package/dist/assets/index-Cn6_2To7.js +8 -0
  15. package/dist/assets/{index-BUiahmWm.css → index-nEYGCJTC.css} +1 -1
  16. package/dist/assets/{input-B1D2QX0O.js → input-oBvxsnV9.js} +1 -1
  17. package/dist/assets/{label-DW0j-fXA.js → label-C7F8lMpQ.js} +1 -1
  18. package/dist/assets/{page-layout-Ch-H9gD-.js → page-layout-DO8BlScF.js} +1 -1
  19. package/dist/assets/session-run-status-Kg0FwAPn.js +3 -0
  20. package/dist/assets/{switch-_cZHlGKB.js → switch-C6a5GyZB.js} +1 -1
  21. package/dist/assets/{tabs-custom-ARxqYYjG.js → tabs-custom-BatFap5k.js} +1 -1
  22. package/dist/assets/{useConfirmDialog-BaU7nIat.js → useConfirmDialog-zJzVKMdu.js} +2 -2
  23. package/dist/assets/{vendor-C--HHaLf.js → vendor-TlME1INH.js} +84 -84
  24. package/dist/index.html +3 -3
  25. package/package.json +4 -2
  26. package/src/App.tsx +1 -2
  27. package/src/api/config.ts +199 -200
  28. package/src/api/types.ts +36 -24
  29. package/src/components/chat/ChatConversationPanel.tsx +102 -121
  30. package/src/components/chat/ChatPage.tsx +165 -437
  31. package/src/components/chat/ChatSidebar.tsx +30 -36
  32. package/src/components/chat/ChatThread.tsx +73 -131
  33. package/src/components/chat/chat-input/ChatInputBarView.tsx +82 -0
  34. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +71 -0
  35. package/src/components/chat/chat-input/components/ChatInputModelStateHint.tsx +39 -0
  36. package/src/components/chat/chat-input/components/ChatInputSelectedSkillsSection.tsx +31 -0
  37. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +112 -0
  38. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputAttachButton.tsx +24 -0
  39. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputModelSelector.tsx +58 -0
  40. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSendControls.tsx +56 -0
  41. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputSessionTypeSelector.tsx +40 -0
  42. package/src/components/chat/chat-input/useChatInputBarController.ts +313 -0
  43. package/src/components/chat/chat-input.types.ts +15 -0
  44. package/src/components/chat/chat-page-data.ts +121 -0
  45. package/src/components/chat/chat-page-runtime.ts +221 -0
  46. package/src/components/chat/chat-session-route.ts +59 -0
  47. package/src/components/chat/chat-stream/nextbot-parsers.ts +52 -0
  48. package/src/components/chat/chat-stream/nextbot-runtime-agent.ts +413 -0
  49. package/src/components/chat/chat-stream/stream-event-adapter.ts +98 -0
  50. package/src/components/chat/chat-stream/transport.ts +159 -0
  51. package/src/components/chat/chat-stream/types.ts +76 -0
  52. package/src/components/chat/managers/chat-input.manager.ts +142 -0
  53. package/src/components/chat/managers/chat-run-status.manager.ts +32 -0
  54. package/src/components/chat/managers/chat-session-list.manager.ts +77 -0
  55. package/src/components/chat/managers/chat-stream-actions.manager.ts +34 -0
  56. package/src/components/chat/managers/chat-thread.manager.ts +86 -0
  57. package/src/components/chat/managers/chat-ui.manager.ts +65 -0
  58. package/src/components/chat/presenter/chat-presenter-context.tsx +25 -0
  59. package/src/components/chat/presenter/chat.presenter.ts +32 -0
  60. package/src/components/chat/stores/chat-input.store.ts +62 -0
  61. package/src/components/chat/stores/chat-run-status.store.ts +30 -0
  62. package/src/components/chat/stores/chat-session-list.store.ts +34 -0
  63. package/src/components/chat/stores/chat-thread.store.ts +52 -0
  64. package/src/components/chat/useChatRuntimeController.ts +134 -0
  65. package/src/components/chat/useChatSessionTypeState.ts +148 -0
  66. package/src/components/common/MaskedInput.tsx +1 -1
  67. package/src/hooks/useConfig.ts +31 -1
  68. package/src/hooks/useObservable.ts +20 -0
  69. package/src/lib/chat-message.ts +2 -202
  70. package/src/lib/chat-runtime-utils.ts +250 -0
  71. package/src/lib/i18n.ts +9 -0
  72. package/tsconfig.json +2 -1
  73. package/vite.config.ts +2 -1
  74. package/dist/assets/ChatPage-CQerYqvy.js +0 -34
  75. package/dist/assets/MarketplacePage-CotZxxNe.js +0 -49
  76. package/dist/assets/ProvidersList-BYYX5K_g.js +0 -1
  77. package/dist/assets/index-D6_5HaDl.js +0 -7
  78. package/dist/assets/session-run-status-BUYsQeWs.js +0 -5
  79. package/src/components/chat/ChatInputBar.tsx +0 -590
  80. package/src/components/chat/useChatStreamController.ts +0 -591
@@ -0,0 +1,121 @@
1
+ import { useMemo } from 'react';
2
+ import type { Dispatch, SetStateAction } from 'react';
3
+ import type { SessionEntryView } from '@/api/types';
4
+ import type { ChatModelOption } from '@/components/chat/chat-input.types';
5
+ import { useChatSessionTypeState } from '@/components/chat/useChatSessionTypeState';
6
+ import { useSyncSelectedModel } from '@/components/chat/chat-page-runtime';
7
+ import {
8
+ useChatCapabilities,
9
+ useChatSessionTypes,
10
+ useConfig,
11
+ useConfigMeta,
12
+ useSessionHistory,
13
+ useSessions,
14
+ } from '@/hooks/useConfig';
15
+ import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
16
+ import { buildProviderModelCatalog, composeProviderModel } from '@/lib/provider-models';
17
+
18
+ type UseChatPageDataParams = {
19
+ query: string;
20
+ selectedSessionKey: string | null;
21
+ selectedAgentId: string;
22
+ pendingSessionType: string;
23
+ setPendingSessionType: Dispatch<SetStateAction<string>>;
24
+ setSelectedModel: Dispatch<SetStateAction<string>>;
25
+ };
26
+
27
+ export function useChatPageData(params: UseChatPageDataParams) {
28
+ const configQuery = useConfig();
29
+ const configMetaQuery = useConfigMeta();
30
+ const sessionsQuery = useSessions({ q: params.query.trim() || undefined, limit: 120, activeMinutes: 0 });
31
+ const installedSkillsQuery = useMarketplaceInstalled('skill');
32
+ const chatCapabilitiesQuery = useChatCapabilities({
33
+ sessionKey: params.selectedSessionKey,
34
+ agentId: params.selectedAgentId
35
+ });
36
+ const historyQuery = useSessionHistory(params.selectedSessionKey, 300);
37
+ const sessionTypesQuery = useChatSessionTypes();
38
+ const isProviderStateResolved =
39
+ (configQuery.isFetched || configQuery.isSuccess) &&
40
+ (configMetaQuery.isFetched || configMetaQuery.isSuccess);
41
+
42
+ const modelOptions = useMemo<ChatModelOption[]>(() => {
43
+ const providers = buildProviderModelCatalog({
44
+ meta: configMetaQuery.data,
45
+ config: configQuery.data,
46
+ onlyConfigured: true
47
+ });
48
+ const seen = new Set<string>();
49
+ const options: ChatModelOption[] = [];
50
+ for (const provider of providers) {
51
+ for (const localModel of provider.models) {
52
+ const value = composeProviderModel(provider.prefix, localModel);
53
+ if (!value || seen.has(value)) {
54
+ continue;
55
+ }
56
+ seen.add(value);
57
+ options.push({
58
+ value,
59
+ modelLabel: localModel,
60
+ providerLabel: provider.displayName
61
+ });
62
+ }
63
+ }
64
+ return options.sort((left, right) => {
65
+ const providerCompare = left.providerLabel.localeCompare(right.providerLabel);
66
+ if (providerCompare !== 0) {
67
+ return providerCompare;
68
+ }
69
+ return left.modelLabel.localeCompare(right.modelLabel);
70
+ });
71
+ }, [configMetaQuery.data, configQuery.data]);
72
+
73
+ const sessions = useMemo(() => sessionsQuery.data?.sessions ?? [], [sessionsQuery.data?.sessions]);
74
+ const skillRecords = useMemo(() => installedSkillsQuery.data?.records ?? [], [installedSkillsQuery.data?.records]);
75
+ const selectedSession = useMemo(
76
+ () => sessions.find((session) => session.key === params.selectedSessionKey) ?? null,
77
+ [params.selectedSessionKey, sessions]
78
+ );
79
+
80
+ const sessionTypeState = useChatSessionTypeState({
81
+ selectedSession,
82
+ selectedSessionKey: params.selectedSessionKey,
83
+ pendingSessionType: params.pendingSessionType,
84
+ setPendingSessionType: params.setPendingSessionType,
85
+ sessionTypesData: sessionTypesQuery.data
86
+ });
87
+
88
+ useSyncSelectedModel({
89
+ modelOptions,
90
+ selectedSessionPreferredModel: selectedSession?.preferredModel,
91
+ defaultModel: configQuery.data?.agents.defaults.model,
92
+ setSelectedModel: params.setSelectedModel
93
+ });
94
+
95
+ const historyMessages = useMemo(() => historyQuery.data?.messages ?? [], [historyQuery.data?.messages]);
96
+
97
+ return {
98
+ configQuery,
99
+ configMetaQuery,
100
+ sessionsQuery,
101
+ installedSkillsQuery,
102
+ chatCapabilitiesQuery,
103
+ historyQuery,
104
+ sessionTypesQuery,
105
+ isProviderStateResolved,
106
+ modelOptions,
107
+ sessions,
108
+ skillRecords,
109
+ selectedSession,
110
+ historyMessages,
111
+ ...sessionTypeState
112
+ };
113
+ }
114
+
115
+ export function sessionDisplayName(session: SessionEntryView): string {
116
+ if (session.label && session.label.trim()) {
117
+ return session.label.trim();
118
+ }
119
+ const chunks = session.key.split(':');
120
+ return chunks[chunks.length - 1] || session.key;
121
+ }
@@ -0,0 +1,221 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import type { Dispatch, SetStateAction } from 'react';
3
+ import type { ChatRunView } from '@/api/types';
4
+ import type { ChatModelOption } from '@/components/chat/chat-input.types';
5
+ import { useChatRuns } from '@/hooks/useConfig';
6
+ import { buildActiveRunBySessionKey, buildSessionRunStatusByKey } from '@/lib/session-run-status';
7
+
8
+ export type ChatMainPanelView = 'chat' | 'cron' | 'skills';
9
+
10
+ export function useSyncSelectedModel(params: {
11
+ modelOptions: ChatModelOption[];
12
+ selectedSessionPreferredModel?: string;
13
+ defaultModel?: string;
14
+ setSelectedModel: Dispatch<SetStateAction<string>>;
15
+ }) {
16
+ const { modelOptions, selectedSessionPreferredModel, defaultModel, setSelectedModel } = params;
17
+ useEffect(() => {
18
+ if (modelOptions.length === 0) {
19
+ setSelectedModel('');
20
+ return;
21
+ }
22
+ setSelectedModel((prev) => {
23
+ if (modelOptions.some((option) => option.value === prev)) {
24
+ return prev;
25
+ }
26
+ const sessionPreferred = selectedSessionPreferredModel?.trim();
27
+ if (sessionPreferred && modelOptions.some((option) => option.value === sessionPreferred)) {
28
+ return sessionPreferred;
29
+ }
30
+ const fallback = defaultModel?.trim();
31
+ if (fallback && modelOptions.some((option) => option.value === fallback)) {
32
+ return fallback;
33
+ }
34
+ return modelOptions[0]?.value ?? '';
35
+ });
36
+ }, [defaultModel, modelOptions, selectedSessionPreferredModel, setSelectedModel]);
37
+ }
38
+
39
+ export function useSessionRunStatus(params: {
40
+ view: ChatMainPanelView;
41
+ selectedSessionKey: string | null;
42
+ activeBackendRunId: string | null;
43
+ isLocallyRunning: boolean;
44
+ resumeRun: (run: ChatRunView) => Promise<void>;
45
+ }) {
46
+ const { view, selectedSessionKey, activeBackendRunId, isLocallyRunning, resumeRun } = params;
47
+ const [suppressedSessionState, setSuppressedSessionState] = useState<{
48
+ sessionKey: string;
49
+ runId?: string;
50
+ } | null>(null);
51
+ const wasLocallyRunningRef = useRef(false);
52
+ const resumedRunBySessionRef = useRef(new Map<string, string>());
53
+ const completedRunBySessionRef = useRef(new Map<string, string>());
54
+ const locallySettledAtBySessionRef = useRef(new Map<string, number>());
55
+ const latestBackendRunIdRef = useRef<string | null>(activeBackendRunId);
56
+ const autoResumeEligibleSessionsRef = useRef(new Set<string>());
57
+
58
+ useEffect(() => {
59
+ if (!selectedSessionKey) {
60
+ return;
61
+ }
62
+ autoResumeEligibleSessionsRef.current.add(selectedSessionKey);
63
+ }, [selectedSessionKey]);
64
+
65
+ useEffect(() => {
66
+ if (!selectedSessionKey) {
67
+ return;
68
+ }
69
+ if (isLocallyRunning) {
70
+ autoResumeEligibleSessionsRef.current.delete(selectedSessionKey);
71
+ }
72
+ }, [isLocallyRunning, selectedSessionKey]);
73
+
74
+ const sessionStatusRunsQuery = useChatRuns(
75
+ view === 'chat'
76
+ ? {
77
+ states: ['queued', 'running'],
78
+ limit: 200,
79
+ syncActiveStates: true,
80
+ isLocallyRunning
81
+ }
82
+ : undefined
83
+ );
84
+ const activeRunBySessionKey = useMemo(
85
+ () => buildActiveRunBySessionKey(sessionStatusRunsQuery.data?.runs ?? []),
86
+ [sessionStatusRunsQuery.data?.runs]
87
+ );
88
+ const sessionRunStatusByKey = useMemo(() => {
89
+ const next = buildSessionRunStatusByKey(activeRunBySessionKey);
90
+ if (suppressedSessionState) {
91
+ const activeRun = activeRunBySessionKey.get(suppressedSessionState.sessionKey) ?? null;
92
+ if (activeRun && (!suppressedSessionState.runId || activeRun.runId === suppressedSessionState.runId)) {
93
+ next.delete(suppressedSessionState.sessionKey);
94
+ }
95
+ }
96
+ return next;
97
+ }, [activeRunBySessionKey, suppressedSessionState]);
98
+ const activeRun = useMemo(() => {
99
+ if (!selectedSessionKey) {
100
+ return null;
101
+ }
102
+ const run = activeRunBySessionKey.get(selectedSessionKey) ?? null;
103
+ const shouldSuppress = (() => {
104
+ if (!run || !suppressedSessionState) {
105
+ return false;
106
+ }
107
+ if (suppressedSessionState.sessionKey !== selectedSessionKey) {
108
+ return false;
109
+ }
110
+ return !suppressedSessionState.runId || run.runId === suppressedSessionState.runId;
111
+ })();
112
+ if (shouldSuppress) {
113
+ return null;
114
+ }
115
+ return run;
116
+ }, [activeRunBySessionKey, selectedSessionKey, suppressedSessionState]);
117
+
118
+ useEffect(() => {
119
+ if (!activeBackendRunId) {
120
+ return;
121
+ }
122
+ latestBackendRunIdRef.current = activeBackendRunId;
123
+ }, [activeBackendRunId]);
124
+
125
+ useEffect(() => {
126
+ if (view !== 'chat' || !selectedSessionKey || !activeRun) {
127
+ return;
128
+ }
129
+ if (!autoResumeEligibleSessionsRef.current.has(selectedSessionKey)) {
130
+ return;
131
+ }
132
+ if (isLocallyRunning) {
133
+ return;
134
+ }
135
+ if (activeBackendRunId === activeRun.runId) {
136
+ return;
137
+ }
138
+ const resumedRunId = resumedRunBySessionRef.current.get(selectedSessionKey);
139
+ if (resumedRunId === activeRun.runId) {
140
+ return;
141
+ }
142
+ const completedRunId = completedRunBySessionRef.current.get(selectedSessionKey);
143
+ if (completedRunId && completedRunId === activeRun.runId) {
144
+ return;
145
+ }
146
+ const locallySettledAt = locallySettledAtBySessionRef.current.get(selectedSessionKey);
147
+ if (typeof locallySettledAt === 'number') {
148
+ const requestedAt = Date.parse(activeRun.requestedAt ?? '');
149
+ if (Number.isFinite(requestedAt)) {
150
+ if (requestedAt <= locallySettledAt + 2_000) {
151
+ return;
152
+ }
153
+ } else if (Date.now() - locallySettledAt <= 8_000) {
154
+ return;
155
+ }
156
+ }
157
+ resumedRunBySessionRef.current.set(selectedSessionKey, activeRun.runId);
158
+ autoResumeEligibleSessionsRef.current.delete(selectedSessionKey);
159
+ void resumeRun(activeRun);
160
+ }, [activeBackendRunId, activeRun, isLocallyRunning, resumeRun, selectedSessionKey, view]);
161
+
162
+ useEffect(() => {
163
+ if (!selectedSessionKey) {
164
+ resumedRunBySessionRef.current.clear();
165
+ completedRunBySessionRef.current.clear();
166
+ locallySettledAtBySessionRef.current.clear();
167
+ autoResumeEligibleSessionsRef.current.clear();
168
+ return;
169
+ }
170
+ if (!activeRunBySessionKey.has(selectedSessionKey)) {
171
+ resumedRunBySessionRef.current.delete(selectedSessionKey);
172
+ completedRunBySessionRef.current.delete(selectedSessionKey);
173
+ locallySettledAtBySessionRef.current.delete(selectedSessionKey);
174
+ }
175
+ }, [activeRunBySessionKey, selectedSessionKey]);
176
+
177
+ useEffect(() => {
178
+ const wasRunning = wasLocallyRunningRef.current;
179
+ wasLocallyRunningRef.current = isLocallyRunning;
180
+ if (isLocallyRunning) {
181
+ return;
182
+ }
183
+ if (wasRunning && selectedSessionKey) {
184
+ const completedRunId = latestBackendRunIdRef.current?.trim() || activeRunBySessionKey.get(selectedSessionKey)?.runId?.trim();
185
+ if (completedRunId) {
186
+ completedRunBySessionRef.current.set(selectedSessionKey, completedRunId);
187
+ }
188
+ locallySettledAtBySessionRef.current.set(selectedSessionKey, Date.now());
189
+ setSuppressedSessionState({
190
+ sessionKey: selectedSessionKey,
191
+ ...(completedRunId ? { runId: completedRunId } : {})
192
+ });
193
+ void sessionStatusRunsQuery.refetch();
194
+ }
195
+ }, [activeRunBySessionKey, isLocallyRunning, selectedSessionKey, sessionStatusRunsQuery]);
196
+
197
+ useEffect(() => {
198
+ if (!suppressedSessionState) {
199
+ return;
200
+ }
201
+ const activeRun = activeRunBySessionKey.get(suppressedSessionState.sessionKey) ?? null;
202
+ if (!activeRun) {
203
+ setSuppressedSessionState(null);
204
+ return;
205
+ }
206
+ if (suppressedSessionState.runId && activeRun.runId !== suppressedSessionState.runId) {
207
+ setSuppressedSessionState(null);
208
+ }
209
+ }, [activeRunBySessionKey, suppressedSessionState]);
210
+
211
+ useEffect(() => {
212
+ if (!isLocallyRunning) {
213
+ return;
214
+ }
215
+ if (suppressedSessionState?.sessionKey === selectedSessionKey) {
216
+ setSuppressedSessionState(null);
217
+ }
218
+ }, [isLocallyRunning, selectedSessionKey, suppressedSessionState]);
219
+
220
+ return { sessionRunStatusByKey };
221
+ }
@@ -0,0 +1,59 @@
1
+ const SESSION_ROUTE_PREFIX = 'sid_';
2
+
3
+ export function resolveAgentIdFromSessionKey(sessionKey: string): string | null {
4
+ const match = /^agent:([^:]+):/i.exec(sessionKey.trim());
5
+ if (!match) {
6
+ return null;
7
+ }
8
+ const value = match[1]?.trim();
9
+ return value ? value : null;
10
+ }
11
+
12
+ export function buildNewSessionKey(agentId: string): string {
13
+ const slug = Math.random().toString(36).slice(2, 8);
14
+ return `agent:${agentId}:ui:direct:web-${Date.now().toString(36)}${slug}`;
15
+ }
16
+
17
+ export function encodeSessionRouteId(sessionKey: string): string {
18
+ const bytes = new TextEncoder().encode(sessionKey);
19
+ let binary = '';
20
+ for (const byte of bytes) {
21
+ binary += String.fromCharCode(byte);
22
+ }
23
+ const base64 = btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
24
+ return `${SESSION_ROUTE_PREFIX}${base64}`;
25
+ }
26
+
27
+ export function decodeSessionRouteId(routeValue: string): string | null {
28
+ if (!routeValue.startsWith(SESSION_ROUTE_PREFIX)) {
29
+ return null;
30
+ }
31
+ const encoded = routeValue.slice(SESSION_ROUTE_PREFIX.length).replace(/-/g, '+').replace(/_/g, '/');
32
+ const padding = encoded.length % 4 === 0 ? '' : '='.repeat(4 - (encoded.length % 4));
33
+ try {
34
+ const binary = atob(encoded + padding);
35
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
36
+ return new TextDecoder().decode(bytes);
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ export function parseSessionKeyFromRoute(routeValue?: string): string | null {
43
+ if (!routeValue) {
44
+ return null;
45
+ }
46
+ const decodedToken = decodeSessionRouteId(routeValue);
47
+ if (decodedToken) {
48
+ return decodedToken;
49
+ }
50
+ try {
51
+ return decodeURIComponent(routeValue);
52
+ } catch {
53
+ return routeValue;
54
+ }
55
+ }
56
+
57
+ export function buildSessionPath(sessionKey: string): string {
58
+ return `/chat/${encodeSessionRouteId(sessionKey)}`;
59
+ }
@@ -0,0 +1,52 @@
1
+ import type { RunMetadataParsers } from '@nextclaw/agent-chat';
2
+ import type { ChatRunView } from '@/api/types';
3
+ import type { NextbotAgentRunMetadata, SendMessageParams } from '@/components/chat/chat-stream/types';
4
+
5
+ export const nextbotParsers: RunMetadataParsers = {
6
+ parseReady: (metadata) => {
7
+ if (metadata.driver !== 'nextbot-stream' || metadata.kind !== 'ready') {
8
+ return null;
9
+ }
10
+ return {
11
+ remoteRunId: typeof metadata.backendRunId === 'string' ? metadata.backendRunId : undefined,
12
+ sessionId: typeof metadata.sessionKey === 'string' ? metadata.sessionKey : undefined,
13
+ stopCapable: typeof metadata.stopSupported === 'boolean' ? metadata.stopSupported : undefined,
14
+ stopReason: typeof metadata.stopReason === 'string' ? metadata.stopReason : undefined
15
+ };
16
+ },
17
+ parseFinal: (metadata) => {
18
+ if (metadata.driver !== 'nextbot-stream' || metadata.kind !== 'final') {
19
+ return null;
20
+ }
21
+ return {
22
+ sessionId: typeof metadata.sessionKey === 'string' ? metadata.sessionKey : undefined,
23
+ hasOutput: Boolean(metadata.hasAssistantSessionEvent)
24
+ };
25
+ }
26
+ };
27
+
28
+ export function buildSendMetadata(payload: SendMessageParams, requestedSkills: string[]): NextbotAgentRunMetadata {
29
+ return {
30
+ driver: 'nextbot-stream',
31
+ mode: 'send',
32
+ payload,
33
+ requestedSkills
34
+ };
35
+ }
36
+
37
+ export function buildResumeMetadata(run: ChatRunView): NextbotAgentRunMetadata {
38
+ const fromEventIndex =
39
+ Number.isFinite(run.eventCount) && run.eventCount > 0
40
+ ? Math.max(0, Math.trunc(run.eventCount))
41
+ : undefined;
42
+ return {
43
+ driver: 'nextbot-stream',
44
+ mode: 'resume',
45
+ runId: run.runId!,
46
+ ...(typeof fromEventIndex === 'number' ? { fromEventIndex } : {}),
47
+ sessionKey: run.sessionKey,
48
+ ...(run.agentId ? { agentId: run.agentId } : {}),
49
+ stopSupported: run.stopSupported,
50
+ ...(run.stopReason ? { stopReason: run.stopReason } : {})
51
+ };
52
+ }