@4djs/assistant 0.0.0 → 0.1.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.
Files changed (189) hide show
  1. package/README.md +49 -68
  2. package/dist/core/chat-activity.d.ts +19 -0
  3. package/dist/core/chat-activity.d.ts.map +1 -0
  4. package/dist/core/chat-commands.d.ts +33 -0
  5. package/dist/core/chat-commands.d.ts.map +1 -0
  6. package/dist/core/chat-history.d.ts +14 -0
  7. package/dist/core/chat-history.d.ts.map +1 -0
  8. package/dist/core/chat-reply-suggestions-parse.d.ts +20 -0
  9. package/dist/core/chat-reply-suggestions-parse.d.ts.map +1 -0
  10. package/dist/core/code-highlight.d.ts +3 -0
  11. package/dist/core/code-highlight.d.ts.map +1 -0
  12. package/dist/core/create-assistant-store.d.ts +33 -0
  13. package/dist/core/create-assistant-store.d.ts.map +1 -0
  14. package/dist/core/fetch-suggested-prompts.d.ts +11 -0
  15. package/dist/core/fetch-suggested-prompts.d.ts.map +1 -0
  16. package/dist/core/index.d.ts +19 -0
  17. package/dist/core/index.d.ts.map +1 -0
  18. package/dist/core/index.js +2876 -0
  19. package/dist/core/interactive-tools/choices.d.ts +22 -0
  20. package/dist/core/interactive-tools/choices.d.ts.map +1 -0
  21. package/dist/core/interactive-tools/confirmation.d.ts +15 -0
  22. package/dist/core/interactive-tools/confirmation.d.ts.map +1 -0
  23. package/dist/core/interactive-tools/constants.d.ts +6 -0
  24. package/dist/core/interactive-tools/constants.d.ts.map +1 -0
  25. package/dist/core/interactive-tools/execute.d.ts +11 -0
  26. package/dist/core/interactive-tools/execute.d.ts.map +1 -0
  27. package/dist/core/interactive-tools/index.d.ts +7 -0
  28. package/dist/core/interactive-tools/index.d.ts.map +1 -0
  29. package/dist/core/interactive-tools/suggestions.d.ts +13 -0
  30. package/dist/core/interactive-tools/suggestions.d.ts.map +1 -0
  31. package/dist/core/interactive-tools/waiters.d.ts +4 -0
  32. package/dist/core/interactive-tools/waiters.d.ts.map +1 -0
  33. package/dist/core/llm-chat.d.ts +96 -0
  34. package/dist/core/llm-chat.d.ts.map +1 -0
  35. package/dist/core/llm-config.d.ts +24 -0
  36. package/dist/core/llm-config.d.ts.map +1 -0
  37. package/dist/core/llm-models.d.ts +14 -0
  38. package/dist/core/llm-models.d.ts.map +1 -0
  39. package/dist/core/llm-provider.d.ts +13 -0
  40. package/dist/core/llm-provider.d.ts.map +1 -0
  41. package/dist/core/llm-settings-storage.d.ts +47 -0
  42. package/dist/core/llm-settings-storage.d.ts.map +1 -0
  43. package/dist/core/llm-sse.d.ts +13 -0
  44. package/dist/core/llm-sse.d.ts.map +1 -0
  45. package/dist/core/llm-types.d.ts +49 -0
  46. package/dist/core/llm-types.d.ts.map +1 -0
  47. package/dist/core/markdown-utils.d.ts +3 -0
  48. package/dist/core/markdown-utils.d.ts.map +1 -0
  49. package/dist/core/prepare-markdown.d.ts +7 -0
  50. package/dist/core/prepare-markdown.d.ts.map +1 -0
  51. package/dist/core/types.d.ts +74 -0
  52. package/dist/core/types.d.ts.map +1 -0
  53. package/dist/index.css +1195 -0
  54. package/dist/index.js +184948 -0
  55. package/dist/react/Assistant.d.ts +10 -0
  56. package/dist/react/Assistant.d.ts.map +1 -0
  57. package/dist/react/components/HighlightedJsonCode.d.ts +6 -0
  58. package/dist/react/components/HighlightedJsonCode.d.ts.map +1 -0
  59. package/dist/react/components/MarkdownContent.d.ts +10 -0
  60. package/dist/react/components/MarkdownContent.d.ts.map +1 -0
  61. package/dist/react/components/MarkdownEditor.d.ts +11 -0
  62. package/dist/react/components/MarkdownEditor.d.ts.map +1 -0
  63. package/dist/react/components/MermaidDiagram.d.ts +8 -0
  64. package/dist/react/components/MermaidDiagram.d.ts.map +1 -0
  65. package/dist/react/components/ModelSelector.d.ts +8 -0
  66. package/dist/react/components/ModelSelector.d.ts.map +1 -0
  67. package/dist/react/components/chat/AssistantErrorCallout.d.ts +11 -0
  68. package/dist/react/components/chat/AssistantErrorCallout.d.ts.map +1 -0
  69. package/dist/react/components/chat/ChatActivity.d.ts +8 -0
  70. package/dist/react/components/chat/ChatActivity.d.ts.map +1 -0
  71. package/dist/react/components/chat/ChatComposer.d.ts +36 -0
  72. package/dist/react/components/chat/ChatComposer.d.ts.map +1 -0
  73. package/dist/react/components/chat/ChatEmptyState.d.ts +10 -0
  74. package/dist/react/components/chat/ChatEmptyState.d.ts.map +1 -0
  75. package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts +7 -0
  76. package/dist/react/components/chat/ChatInteractivePrompt/choices-prompt.d.ts.map +1 -0
  77. package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts +7 -0
  78. package/dist/react/components/chat/ChatInteractivePrompt/confirmation-prompt.d.ts.map +1 -0
  79. package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts +7 -0
  80. package/dist/react/components/chat/ChatInteractivePrompt/index.d.ts.map +1 -0
  81. package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts +13 -0
  82. package/dist/react/components/chat/ChatInteractivePrompt/shell.d.ts.map +1 -0
  83. package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts +4 -0
  84. package/dist/react/components/chat/ChatInteractivePrompt/utils.d.ts.map +1 -0
  85. package/dist/react/components/chat/ChatMessage.d.ts +11 -0
  86. package/dist/react/components/chat/ChatMessage.d.ts.map +1 -0
  87. package/dist/react/components/chat/ChatMessageScroll.d.ts +8 -0
  88. package/dist/react/components/chat/ChatMessageScroll.d.ts.map +1 -0
  89. package/dist/react/components/chat/ChatReplySuggestions.d.ts +9 -0
  90. package/dist/react/components/chat/ChatReplySuggestions.d.ts.map +1 -0
  91. package/dist/react/components/chat/ComposerCommandMenu.d.ts +10 -0
  92. package/dist/react/components/chat/ComposerCommandMenu.d.ts.map +1 -0
  93. package/dist/react/components/chat/LlmSettingsStrip.d.ts +7 -0
  94. package/dist/react/components/chat/LlmSettingsStrip.d.ts.map +1 -0
  95. package/dist/react/components/chat/LlmSetupPrompt.d.ts +7 -0
  96. package/dist/react/components/chat/LlmSetupPrompt.d.ts.map +1 -0
  97. package/dist/react/components/chat/LlmUnavailableBanner.d.ts +6 -0
  98. package/dist/react/components/chat/LlmUnavailableBanner.d.ts.map +1 -0
  99. package/dist/react/components/chat/SuggestedPromptsList.d.ts +14 -0
  100. package/dist/react/components/chat/SuggestedPromptsList.d.ts.map +1 -0
  101. package/dist/react/components/chat/SuggestedPromptsStrip.d.ts +11 -0
  102. package/dist/react/components/chat/SuggestedPromptsStrip.d.ts.map +1 -0
  103. package/dist/react/components/chat/SystemPromptField.d.ts +10 -0
  104. package/dist/react/components/chat/SystemPromptField.d.ts.map +1 -0
  105. package/dist/react/components/highlighted-code.d.ts +8 -0
  106. package/dist/react/components/highlighted-code.d.ts.map +1 -0
  107. package/dist/react/context.d.ts +11 -0
  108. package/dist/react/context.d.ts.map +1 -0
  109. package/dist/react/hooks/use-composer-commands.d.ts +21 -0
  110. package/dist/react/hooks/use-composer-commands.d.ts.map +1 -0
  111. package/dist/react/hooks/use-suggested-prompts.d.ts +29 -0
  112. package/dist/react/hooks/use-suggested-prompts.d.ts.map +1 -0
  113. package/dist/react/index.d.ts +17 -0
  114. package/dist/react/index.d.ts.map +1 -0
  115. package/dist/react/lib/parse-assistant-error.d.ts +9 -0
  116. package/dist/react/lib/parse-assistant-error.d.ts.map +1 -0
  117. package/dist/react/lib/prompt-icons.d.ts +5 -0
  118. package/dist/react/lib/prompt-icons.d.ts.map +1 -0
  119. package/dist/react/types.d.ts +69 -0
  120. package/dist/react/types.d.ts.map +1 -0
  121. package/dist/react/utils/cn.d.ts +2 -0
  122. package/dist/react/utils/cn.d.ts.map +1 -0
  123. package/package.json +16 -5
  124. package/src/core/chat-activity.ts +0 -107
  125. package/src/core/chat-commands.ts +0 -173
  126. package/src/core/chat-history.ts +0 -113
  127. package/src/core/chat-reply-suggestions-parse.ts +0 -119
  128. package/src/core/code-highlight.ts +0 -20
  129. package/src/core/create-assistant-store.ts +0 -639
  130. package/src/core/fetch-suggested-prompts.ts +0 -53
  131. package/src/core/index.ts +0 -125
  132. package/src/core/interactive-tools/choices.ts +0 -155
  133. package/src/core/interactive-tools/confirmation.ts +0 -63
  134. package/src/core/interactive-tools/constants.ts +0 -22
  135. package/src/core/interactive-tools/execute.ts +0 -70
  136. package/src/core/interactive-tools/index.ts +0 -41
  137. package/src/core/interactive-tools/suggestions.ts +0 -87
  138. package/src/core/interactive-tools/waiters.ts +0 -55
  139. package/src/core/llm-chat.ts +0 -686
  140. package/src/core/llm-config.ts +0 -101
  141. package/src/core/llm-models.ts +0 -96
  142. package/src/core/llm-provider.ts +0 -99
  143. package/src/core/llm-settings-storage.ts +0 -331
  144. package/src/core/llm-sse.ts +0 -166
  145. package/src/core/llm-types.ts +0 -52
  146. package/src/core/markdown-utils.ts +0 -11
  147. package/src/core/prepare-markdown.ts +0 -38
  148. package/src/core/types.ts +0 -86
  149. package/src/css.d.ts +0 -1
  150. package/src/react/Assistant.tsx +0 -358
  151. package/src/react/components/HighlightedJsonCode.tsx +0 -24
  152. package/src/react/components/MarkdownContent.tsx +0 -98
  153. package/src/react/components/MarkdownEditor.tsx +0 -60
  154. package/src/react/components/MermaidDiagram.tsx +0 -139
  155. package/src/react/components/ModelSelector.tsx +0 -243
  156. package/src/react/components/chat/AssistantErrorCallout.tsx +0 -79
  157. package/src/react/components/chat/ChatActivity.tsx +0 -274
  158. package/src/react/components/chat/ChatComposer.tsx +0 -189
  159. package/src/react/components/chat/ChatEmptyState.tsx +0 -145
  160. package/src/react/components/chat/ChatInteractivePrompt/choices-prompt.tsx +0 -262
  161. package/src/react/components/chat/ChatInteractivePrompt/confirmation-prompt.tsx +0 -97
  162. package/src/react/components/chat/ChatInteractivePrompt/index.tsx +0 -60
  163. package/src/react/components/chat/ChatInteractivePrompt/shell.tsx +0 -60
  164. package/src/react/components/chat/ChatInteractivePrompt/utils.ts +0 -14
  165. package/src/react/components/chat/ChatMessage.tsx +0 -150
  166. package/src/react/components/chat/ChatMessageScroll.tsx +0 -116
  167. package/src/react/components/chat/ChatReplySuggestions.tsx +0 -231
  168. package/src/react/components/chat/ComposerCommandMenu.tsx +0 -69
  169. package/src/react/components/chat/LlmSettingsStrip.tsx +0 -348
  170. package/src/react/components/chat/LlmSetupPrompt.tsx +0 -58
  171. package/src/react/components/chat/LlmUnavailableBanner.tsx +0 -11
  172. package/src/react/components/chat/SuggestedPromptsList.tsx +0 -121
  173. package/src/react/components/chat/SuggestedPromptsStrip.tsx +0 -72
  174. package/src/react/components/chat/SystemPromptField.tsx +0 -107
  175. package/src/react/components/highlighted-code.tsx +0 -107
  176. package/src/react/context.tsx +0 -72
  177. package/src/react/hooks/use-composer-commands.ts +0 -129
  178. package/src/react/hooks/use-suggested-prompts.ts +0 -128
  179. package/src/react/index.ts +0 -39
  180. package/src/react/lib/parse-assistant-error.ts +0 -96
  181. package/src/react/lib/prompt-icons.ts +0 -40
  182. package/src/react/types.ts +0 -83
  183. package/src/react/utils/cn.ts +0 -5
  184. package/test/buildLlmHistory.test.ts +0 -95
  185. package/test/llm-config.test.ts +0 -72
  186. package/test/llmSettingsStorage.test.ts +0 -121
  187. package/test/parse-assistant-error.test.ts +0 -24
  188. package/tsconfig.json +0 -8
  189. /package/{src/styles/assistant.css → dist/styles.css} +0 -0
@@ -1,639 +0,0 @@
1
- import { create } from "zustand";
2
- import type { ChatActivityStep } from "./chat-activity.ts";
3
- import { summarizeActivityResult } from "./chat-activity.ts";
4
- import { createChatHistoryHelpers } from "./chat-history.ts";
5
- import {
6
- rejectAllInteractiveToolWaiters,
7
- resolveInteractiveToolResult,
8
- } from "./interactive-tools/index.ts";
9
- import {
10
- buildLlmHistory,
11
- ChatAbortedError,
12
- fetchLlmStatus,
13
- runLlmAgent,
14
- } from "./llm-chat.ts";
15
- import {
16
- DEFAULT_ASSISTANT_SYSTEM_PROMPT,
17
- DEFAULT_LLM_MODEL,
18
- isLocalLlmBaseUrl,
19
- LLM_UNAVAILABLE_MESSAGE,
20
- } from "./llm-config.ts";
21
- import {
22
- configureAssistantLlm,
23
- resolveAssistantLlmSettings,
24
- testLlmConnection,
25
- } from "./llm-provider.ts";
26
- import {
27
- buildDefaultLlmSettings,
28
- createDefaultStoredSettings,
29
- createLlmSettingsFormState,
30
- createLlmSettingsFormStateFromStored,
31
- createLlmSettingsStorage,
32
- type LlmSettingsFormState,
33
- type LlmSettingsFormValues,
34
- mergeLlmSettings,
35
- migrateLegacyModelStorage,
36
- normalizeStoredSystemPrompt,
37
- peekStoredModel,
38
- persistStoredModelSelection,
39
- resolveSelectedModel,
40
- storedSettingsHaveOverrides,
41
- toStoredSettings,
42
- } from "./llm-settings-storage.ts";
43
- import type {
44
- AssistantLlmSettings,
45
- AssistantMessage,
46
- AssistantStoreDependencies,
47
- } from "./types.ts";
48
-
49
- export interface AssistantState {
50
- messages: AssistantMessage[];
51
- chatLoading: boolean;
52
- llmEnabled: boolean;
53
- llmModel: string | null;
54
- llmModels: string[];
55
- llmModelsLoading: boolean;
56
- selectedModel: string | null;
57
-
58
- loadLlmStatus: () => Promise<void>;
59
- getLlmSettingsForm: () => Promise<LlmSettingsFormState>;
60
- saveLlmSettings: (values: LlmSettingsFormValues) => Promise<void>;
61
- clearLlmSettings: () => Promise<void>;
62
- testLlmSettings: (
63
- values: LlmSettingsFormValues,
64
- ) => Promise<{ ok: true; model: string } | { ok: false; error: string }>;
65
- llmSettingsHasOverrides: () => Promise<boolean>;
66
- setSelectedModel: (model: string) => void;
67
- sendChat: (message: string) => Promise<void>;
68
- retryLastChat: () => Promise<void>;
69
- clearChatHistory: () => void;
70
- stopChat: () => void;
71
- submitInteractiveToolResult: (callId: string, result: unknown) => void;
72
- cancelInteractiveToolResult: (callId: string) => void;
73
- }
74
-
75
- function patchWelcomeMessage(
76
- messages: AssistantMessage[],
77
- welcomeMessage: AssistantStoreDependencies["welcomeMessage"],
78
- model: string | null,
79
- llmEnabled: boolean,
80
- ): AssistantMessage[] {
81
- const welcome = welcomeMessage({ llmEnabled, model });
82
- return messages.map((message) =>
83
- message.id === "welcome"
84
- ? { ...message, content: welcome.content }
85
- : message,
86
- );
87
- }
88
-
89
- function patchTurnMessage(
90
- messages: AssistantMessage[],
91
- turnId: string,
92
- patch: (message: AssistantMessage) => AssistantMessage,
93
- ): AssistantMessage[] {
94
- return messages.map((message) =>
95
- message.id === turnId ? patch(message) : message,
96
- );
97
- }
98
-
99
- function createTurnMessage(turnId: string): AssistantMessage {
100
- return {
101
- id: turnId,
102
- role: "assistant",
103
- content: "",
104
- activity: [],
105
- streaming: true,
106
- timestamp: Date.now(),
107
- };
108
- }
109
-
110
- function appendActivityStep(
111
- messages: AssistantMessage[],
112
- turnId: string,
113
- step: ChatActivityStep,
114
- ): AssistantMessage[] {
115
- return patchTurnMessage(messages, turnId, (message) => ({
116
- ...message,
117
- activity: [...(message.activity ?? []), step],
118
- }));
119
- }
120
-
121
- function finishActivityStep(
122
- messages: AssistantMessage[],
123
- turnId: string,
124
- stepId: string,
125
- update: Pick<ChatActivityStep, "status" | "result" | "error">,
126
- ): AssistantMessage[] {
127
- return patchTurnMessage(messages, turnId, (message) => ({
128
- ...message,
129
- activity: (message.activity ?? []).map((step) =>
130
- step.id === stepId ? { ...step, ...update } : step,
131
- ),
132
- }));
133
- }
134
-
135
- function finalizeStoppedMessages(
136
- messages: AssistantMessage[],
137
- ): AssistantMessage[] {
138
- return messages.map((message) => {
139
- if (!message.streaming) return message;
140
- const activity = (message.activity ?? []).map((step) =>
141
- step.status === "active"
142
- ? { ...step, status: "error" as const, error: "Stopped" }
143
- : step,
144
- );
145
- return {
146
- ...message,
147
- streaming: false,
148
- activity,
149
- content: message.content.trim() || "_(stopped)_",
150
- };
151
- });
152
- }
153
-
154
- function finalizeErroredMessages(
155
- messages: AssistantMessage[],
156
- errorMessage: string,
157
- ): AssistantMessage[] {
158
- const hasStreaming = messages.some((message) => message.streaming);
159
- if (!hasStreaming) {
160
- return [
161
- ...messages,
162
- {
163
- id: crypto.randomUUID(),
164
- role: "assistant",
165
- content: errorMessage,
166
- isError: true,
167
- timestamp: Date.now(),
168
- },
169
- ];
170
- }
171
-
172
- return messages.map((message) => {
173
- if (!message.streaming) return message;
174
- const activity = (message.activity ?? []).map((step) =>
175
- step.status === "active"
176
- ? { ...step, status: "error" as const, error: errorMessage }
177
- : step,
178
- );
179
- return {
180
- ...message,
181
- streaming: false,
182
- isError: true,
183
- activity,
184
- content: errorMessage,
185
- };
186
- });
187
- }
188
-
189
- function clearReplySuggestions(
190
- messages: AssistantMessage[],
191
- ): AssistantMessage[] {
192
- return messages.map((message) =>
193
- message.replySuggestions
194
- ? { ...message, replySuggestions: undefined }
195
- : message,
196
- );
197
- }
198
-
199
- export function createAssistantStore(deps: AssistantStoreDependencies) {
200
- const historyKey = deps.storageKeys?.history ?? "assistant-chat-history";
201
- const llmSettingsKey =
202
- deps.storageKeys?.llmSettings ?? "assistant-llm-settings";
203
- const llmSettingsStorage = createLlmSettingsStorage(llmSettingsKey);
204
-
205
- async function resolveBaseLlmSettings(): Promise<AssistantLlmSettings> {
206
- if (!deps.llm) return buildDefaultLlmSettings();
207
- return typeof deps.llm === "function" ? await deps.llm() : deps.llm;
208
- }
209
-
210
- async function ensureLlmSettingsStored(): Promise<void> {
211
- const base = await resolveBaseLlmSettings();
212
- migrateLegacyModelStorage(llmSettingsKey, deps.storageKeys?.model, base);
213
- if (llmSettingsStorage.hasStored()) return;
214
- llmSettingsStorage.save(createDefaultStoredSettings(base));
215
- }
216
-
217
- async function resolveMergedLlmSettings(): Promise<AssistantLlmSettings> {
218
- const base = await resolveBaseLlmSettings();
219
- const stored = llmSettingsStorage.load(base);
220
- return mergeLlmSettings(base, stored);
221
- }
222
-
223
- configureAssistantLlm(resolveMergedLlmSettings);
224
-
225
- const history = createChatHistoryHelpers({
226
- storageKey: historyKey,
227
- welcomeMessage: deps.welcomeMessage,
228
- });
229
- const initialSelectedModel = peekStoredModel(llmSettingsKey);
230
-
231
- let chatAbortController: AbortController | null = null;
232
- const messages = history.loadInitial({
233
- llmEnabled: false,
234
- model: initialSelectedModel,
235
- });
236
-
237
- const store = create<AssistantState>((set, get) => {
238
- async function runLlmChatTurn(message: string) {
239
- const tools = await deps.listTools();
240
- const storedMessages = get().messages.filter((m) => m.id !== "welcome");
241
- // sendChat already appended the current user message; runLlmAgent adds it again.
242
- const priorMessages =
243
- storedMessages.at(-1)?.role === "user"
244
- ? storedMessages.slice(0, -1)
245
- : storedMessages;
246
- const llmHistory = buildLlmHistory(priorMessages);
247
- const turnId = crypto.randomUUID();
248
-
249
- set((state) => ({
250
- messages: [...state.messages, createTurnMessage(turnId)],
251
- }));
252
-
253
- await runLlmAgent({
254
- userMessage: message,
255
- history: llmHistory,
256
- tools,
257
- model: get().selectedModel ?? undefined,
258
- signal: chatAbortController?.signal,
259
- stream: {
260
- turnId,
261
- onUpdate: (content) => {
262
- set((state) => ({
263
- messages: patchTurnMessage(state.messages, turnId, (msg) => ({
264
- ...msg,
265
- content,
266
- })),
267
- }));
268
- },
269
- },
270
- toolHandlers: {
271
- onStart: ({ id, name, args, callId }) => {
272
- set((state) => ({
273
- messages: appendActivityStep(state.messages, turnId, {
274
- id,
275
- kind: "tool",
276
- name,
277
- args,
278
- callId,
279
- status: "active",
280
- }),
281
- }));
282
- },
283
- onFinish: (stepId, update) => {
284
- set((state) => ({
285
- messages: finishActivityStep(state.messages, turnId, stepId, {
286
- status: update.status,
287
- result: summarizeActivityResult(update.result),
288
- error: update.error,
289
- }),
290
- }));
291
- },
292
- },
293
- onPostResponseTool: (suggestions) => {
294
- set((state) => ({
295
- messages: patchTurnMessage(state.messages, turnId, (msg) => ({
296
- ...msg,
297
- replySuggestions: suggestions,
298
- })),
299
- }));
300
- },
301
- invokeTool: async (name, args) => {
302
- const result = await deps.invokeTool(name, args);
303
- deps.onToolInvoked?.(result);
304
- return result;
305
- },
306
- });
307
-
308
- set((state) => ({
309
- messages: state.messages.map((msg) =>
310
- msg.id === turnId
311
- ? {
312
- ...msg,
313
- streaming: false,
314
- content:
315
- msg.content.trim() ||
316
- (msg.activity?.length
317
- ? ""
318
- : "I couldn't produce a response."),
319
- }
320
- : msg,
321
- ),
322
- chatLoading: false,
323
- }));
324
- }
325
-
326
- return {
327
- messages,
328
- chatLoading: false,
329
- llmEnabled: false,
330
- llmModel: null,
331
- llmModels: [],
332
- llmModelsLoading: false,
333
- selectedModel: initialSelectedModel,
334
-
335
- loadLlmStatus: async () => {
336
- set({ llmModelsLoading: true });
337
- try {
338
- await ensureLlmSettingsStored();
339
- const base = await resolveBaseLlmSettings();
340
- const stored =
341
- llmSettingsStorage.load(base) ?? createDefaultStoredSettings(base);
342
- const status = await fetchLlmStatus();
343
- const defaultModel =
344
- status.model ?? stored.model ?? DEFAULT_LLM_MODEL;
345
- const baseModels = status.models.length
346
- ? status.models
347
- : [defaultModel];
348
- const selectedModel = resolveSelectedModel(
349
- stored.model,
350
- defaultModel,
351
- baseModels,
352
- );
353
- const llmModels =
354
- selectedModel && !baseModels.includes(selectedModel)
355
- ? [selectedModel, ...baseModels]
356
- : baseModels;
357
- set((state) => ({
358
- llmEnabled: status.enabled,
359
- llmModel: defaultModel,
360
- llmModels,
361
- selectedModel,
362
- messages: patchWelcomeMessage(
363
- state.messages,
364
- deps.welcomeMessage,
365
- selectedModel,
366
- status.enabled,
367
- ),
368
- }));
369
- } finally {
370
- set({ llmModelsLoading: false });
371
- }
372
- },
373
-
374
- getLlmSettingsForm: async () => {
375
- await ensureLlmSettingsStored();
376
- const base = await resolveBaseLlmSettings();
377
- const defaultSystemPrompt =
378
- base.systemPrompt?.trim() || DEFAULT_ASSISTANT_SYSTEM_PROMPT;
379
- const stored = llmSettingsStorage.load(base);
380
- if (stored) {
381
- return createLlmSettingsFormStateFromStored(
382
- {
383
- ...stored,
384
- systemPrompt: normalizeStoredSystemPrompt(
385
- stored.systemPrompt,
386
- base,
387
- ),
388
- },
389
- defaultSystemPrompt,
390
- );
391
- }
392
- return createLlmSettingsFormState(
393
- base,
394
- Boolean(base.apiKey),
395
- defaultSystemPrompt,
396
- );
397
- },
398
-
399
- saveLlmSettings: async (values) => {
400
- const base = await resolveBaseLlmSettings();
401
- const defaultSystemPrompt =
402
- base.systemPrompt?.trim() || DEFAULT_ASSISTANT_SYSTEM_PROMPT;
403
- const current = await resolveAssistantLlmSettings();
404
- const normalizedValues = {
405
- ...values,
406
- systemPrompt:
407
- values.systemPrompt.trim() === defaultSystemPrompt
408
- ? ""
409
- : values.systemPrompt,
410
- };
411
- llmSettingsStorage.save(
412
- toStoredSettings(normalizedValues, current.apiKey),
413
- );
414
- await get().loadLlmStatus();
415
- },
416
-
417
- clearLlmSettings: async () => {
418
- llmSettingsStorage.clear();
419
- const base = await resolveBaseLlmSettings();
420
- llmSettingsStorage.save(createDefaultStoredSettings(base));
421
- await get().loadLlmStatus();
422
- },
423
-
424
- testLlmSettings: async (values) => {
425
- const current = await resolveAssistantLlmSettings();
426
- const stored = toStoredSettings(values, current.apiKey);
427
- if (!stored.apiKey && !isLocalLlmBaseUrl(stored.baseUrl)) {
428
- return {
429
- ok: false,
430
- error: "API key is required for remote LLM providers.",
431
- };
432
- }
433
- return testLlmConnection({
434
- baseUrl: stored.baseUrl,
435
- apiKey: stored.apiKey,
436
- model: stored.model,
437
- });
438
- },
439
-
440
- llmSettingsHasOverrides: async () => {
441
- await ensureLlmSettingsStored();
442
- const base = await resolveBaseLlmSettings();
443
- const stored = llmSettingsStorage.load(base);
444
- if (!stored) return false;
445
- return storedSettingsHaveOverrides(stored, base);
446
- },
447
-
448
- setSelectedModel: (model) => {
449
- const trimmed = model.trim();
450
- if (!trimmed) return;
451
- const { llmEnabled, llmModels } = get();
452
- const models = llmModels.includes(trimmed)
453
- ? llmModels
454
- : [trimmed, ...llmModels];
455
- persistStoredModelSelection(llmSettingsKey, trimmed);
456
- set((state) => ({
457
- selectedModel: trimmed,
458
- llmModels: models,
459
- messages: patchWelcomeMessage(
460
- state.messages,
461
- deps.welcomeMessage,
462
- trimmed,
463
- llmEnabled,
464
- ),
465
- }));
466
- },
467
-
468
- clearChatHistory: () => {
469
- if (get().chatLoading) return;
470
- const { llmEnabled, selectedModel } = get();
471
- history.clear();
472
- set({
473
- messages: [deps.welcomeMessage({ llmEnabled, model: selectedModel })],
474
- });
475
- },
476
-
477
- stopChat: () => {
478
- chatAbortController?.abort();
479
- rejectAllInteractiveToolWaiters(
480
- new DOMException("Aborted", "AbortError"),
481
- );
482
- set((state) => ({
483
- messages: finalizeStoppedMessages(state.messages),
484
- chatLoading: false,
485
- }));
486
- },
487
-
488
- submitInteractiveToolResult: (callId, result) => {
489
- resolveInteractiveToolResult(callId, result);
490
- },
491
-
492
- cancelInteractiveToolResult: (callId) => {
493
- resolveInteractiveToolResult(callId, {
494
- cancelled: true,
495
- confirmed: false,
496
- selected: [],
497
- });
498
- },
499
-
500
- sendChat: async (message) => {
501
- const userMsg: AssistantMessage = {
502
- id: crypto.randomUUID(),
503
- role: "user",
504
- content: message,
505
- timestamp: Date.now(),
506
- };
507
- chatAbortController?.abort();
508
- chatAbortController = new AbortController();
509
- set((state) => ({
510
- messages: [...clearReplySuggestions(state.messages), userMsg],
511
- chatLoading: true,
512
- }));
513
-
514
- try {
515
- if (!get().llmEnabled) {
516
- set((state) => ({
517
- messages: [
518
- ...state.messages,
519
- {
520
- id: crypto.randomUUID(),
521
- role: "assistant",
522
- content: LLM_UNAVAILABLE_MESSAGE,
523
- llmSetupRequired: true,
524
- timestamp: Date.now(),
525
- },
526
- ],
527
- chatLoading: false,
528
- }));
529
- return;
530
- }
531
- await runLlmChatTurn(message);
532
- } catch (error) {
533
- if (
534
- error instanceof ChatAbortedError ||
535
- (error instanceof DOMException && error.name === "AbortError") ||
536
- chatAbortController?.signal.aborted
537
- ) {
538
- set((state) => ({
539
- messages: finalizeStoppedMessages(state.messages),
540
- chatLoading: false,
541
- }));
542
- return;
543
- }
544
- set((state) => ({
545
- messages: finalizeErroredMessages(
546
- state.messages,
547
- `Chat failed: ${error instanceof Error ? error.message : String(error)}`,
548
- ),
549
- chatLoading: false,
550
- }));
551
- } finally {
552
- chatAbortController = null;
553
- }
554
- },
555
-
556
- retryLastChat: async () => {
557
- if (get().chatLoading) return;
558
-
559
- const { messages } = get();
560
- let errorIndex = -1;
561
- for (let i = messages.length - 1; i >= 0; i--) {
562
- const message = messages[i];
563
- if (message?.role === "assistant" && message.isError) {
564
- errorIndex = i;
565
- break;
566
- }
567
- }
568
- if (errorIndex < 0) return;
569
-
570
- let userContent: string | null = null;
571
- for (let i = errorIndex - 1; i >= 0; i--) {
572
- const message = messages[i];
573
- if (message?.role === "user") {
574
- userContent = message.content;
575
- break;
576
- }
577
- }
578
- if (!userContent) return;
579
-
580
- chatAbortController?.abort();
581
- chatAbortController = new AbortController();
582
- set((state) => ({
583
- messages: state.messages.filter((_, index) => index !== errorIndex),
584
- chatLoading: true,
585
- }));
586
-
587
- try {
588
- if (!get().llmEnabled) {
589
- set((state) => ({
590
- messages: [
591
- ...state.messages,
592
- {
593
- id: crypto.randomUUID(),
594
- role: "assistant",
595
- content: LLM_UNAVAILABLE_MESSAGE,
596
- llmSetupRequired: true,
597
- timestamp: Date.now(),
598
- },
599
- ],
600
- chatLoading: false,
601
- }));
602
- return;
603
- }
604
- await runLlmChatTurn(userContent);
605
- } catch (error) {
606
- if (
607
- error instanceof ChatAbortedError ||
608
- (error instanceof DOMException && error.name === "AbortError") ||
609
- chatAbortController?.signal.aborted
610
- ) {
611
- set((state) => ({
612
- messages: finalizeStoppedMessages(state.messages),
613
- chatLoading: false,
614
- }));
615
- return;
616
- }
617
- set((state) => ({
618
- messages: finalizeErroredMessages(
619
- state.messages,
620
- `Chat failed: ${error instanceof Error ? error.message : String(error)}`,
621
- ),
622
- chatLoading: false,
623
- }));
624
- } finally {
625
- chatAbortController = null;
626
- }
627
- },
628
- };
629
- });
630
-
631
- store.subscribe((state, previousState) => {
632
- if (state.messages === previousState.messages) return;
633
- history.persist(state.messages);
634
- });
635
-
636
- return store;
637
- }
638
-
639
- export type AssistantStore = ReturnType<typeof createAssistantStore>;
@@ -1,53 +0,0 @@
1
- export interface SuggestedPromptsResponse {
2
- prompts: Array<{
3
- id: string;
4
- label: string;
5
- description?: string;
6
- prompt: string;
7
- icon?: string;
8
- }>;
9
- }
10
-
11
- export function parseSuggestedPromptsResponse(
12
- data: unknown,
13
- ): SuggestedPromptsResponse["prompts"] {
14
- if (!data || typeof data !== "object") return [];
15
- const root = data as Record<string, unknown>;
16
- const raw = Array.isArray(root.prompts)
17
- ? root.prompts
18
- : Array.isArray(root.suggestions)
19
- ? root.suggestions
20
- : [];
21
-
22
- const prompts: SuggestedPromptsResponse["prompts"] = [];
23
- for (const [index, entry] of raw.entries()) {
24
- if (!entry || typeof entry !== "object") continue;
25
- const item = entry as Record<string, unknown>;
26
- const label = typeof item.label === "string" ? item.label.trim() : "";
27
- const prompt =
28
- typeof item.prompt === "string"
29
- ? item.prompt.trim()
30
- : typeof item.message === "string"
31
- ? item.message.trim()
32
- : "";
33
- if (!label || !prompt) continue;
34
-
35
- prompts.push({
36
- id:
37
- typeof item.id === "string" && item.id.trim()
38
- ? item.id.trim()
39
- : `prompt-${index + 1}`,
40
- label,
41
- description:
42
- typeof item.description === "string"
43
- ? item.description.trim()
44
- : typeof item.hint === "string"
45
- ? item.hint.trim()
46
- : undefined,
47
- prompt,
48
- icon: typeof item.icon === "string" ? item.icon.trim() : undefined,
49
- });
50
- }
51
-
52
- return prompts.slice(0, 6);
53
- }