@sqlrooms/ai-core 0.26.0-rc.5 → 0.26.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 (85) hide show
  1. package/README.md +171 -2
  2. package/dist/AiSlice.d.ts +37 -7
  3. package/dist/AiSlice.d.ts.map +1 -1
  4. package/dist/AiSlice.js +148 -14
  5. package/dist/AiSlice.js.map +1 -1
  6. package/dist/agents/AgentUtils.d.ts +61 -0
  7. package/dist/agents/AgentUtils.d.ts.map +1 -0
  8. package/dist/agents/AgentUtils.js +90 -0
  9. package/dist/agents/AgentUtils.js.map +1 -0
  10. package/dist/chatTransport.d.ts +17 -4
  11. package/dist/chatTransport.d.ts.map +1 -1
  12. package/dist/chatTransport.js +244 -19
  13. package/dist/chatTransport.js.map +1 -1
  14. package/dist/components/AnalysisAnswer.d.ts +2 -0
  15. package/dist/components/AnalysisAnswer.d.ts.map +1 -1
  16. package/dist/components/AnalysisAnswer.js +4 -2
  17. package/dist/components/AnalysisAnswer.js.map +1 -1
  18. package/dist/components/AnalysisResult.d.ts +7 -0
  19. package/dist/components/AnalysisResult.d.ts.map +1 -1
  20. package/dist/components/AnalysisResult.js +42 -42
  21. package/dist/components/AnalysisResult.js.map +1 -1
  22. package/dist/components/AnalysisResultsContainer.d.ts +4 -0
  23. package/dist/components/AnalysisResultsContainer.d.ts.map +1 -1
  24. package/dist/components/AnalysisResultsContainer.js +11 -4
  25. package/dist/components/AnalysisResultsContainer.js.map +1 -1
  26. package/dist/components/DeleteConfirmationDialog.d.ts +14 -0
  27. package/dist/components/DeleteConfirmationDialog.d.ts.map +1 -0
  28. package/dist/components/DeleteConfirmationDialog.js +6 -0
  29. package/dist/components/DeleteConfirmationDialog.js.map +1 -0
  30. package/dist/components/GroupedMessageParts.d.ts +25 -0
  31. package/dist/components/GroupedMessageParts.d.ts.map +1 -0
  32. package/dist/components/GroupedMessageParts.js +34 -0
  33. package/dist/components/GroupedMessageParts.js.map +1 -0
  34. package/dist/components/MessagePartsList.d.ts +23 -0
  35. package/dist/components/MessagePartsList.d.ts.map +1 -0
  36. package/dist/components/MessagePartsList.js +27 -0
  37. package/dist/components/MessagePartsList.js.map +1 -0
  38. package/dist/components/PromptSuggestions.d.ts +32 -0
  39. package/dist/components/PromptSuggestions.d.ts.map +1 -0
  40. package/dist/components/PromptSuggestions.js +69 -0
  41. package/dist/components/PromptSuggestions.js.map +1 -0
  42. package/dist/components/QueryControls.d.ts.map +1 -1
  43. package/dist/components/QueryControls.js +11 -4
  44. package/dist/components/QueryControls.js.map +1 -1
  45. package/dist/components/ReasoningBox.d.ts +21 -0
  46. package/dist/components/ReasoningBox.d.ts.map +1 -0
  47. package/dist/components/ReasoningBox.js +29 -0
  48. package/dist/components/ReasoningBox.js.map +1 -0
  49. package/dist/components/ToolCallInfo.d.ts.map +1 -1
  50. package/dist/components/ToolCallInfo.js +1 -11
  51. package/dist/components/ToolCallInfo.js.map +1 -1
  52. package/dist/components/ToolPartRenderer.d.ts +20 -0
  53. package/dist/components/ToolPartRenderer.d.ts.map +1 -0
  54. package/dist/components/ToolPartRenderer.js +85 -0
  55. package/dist/components/ToolPartRenderer.js.map +1 -0
  56. package/dist/components/tools/ToolErrorMessage.d.ts.map +1 -1
  57. package/dist/components/tools/ToolErrorMessage.js +1 -2
  58. package/dist/components/tools/ToolErrorMessage.js.map +1 -1
  59. package/dist/components/tools/ToolResult.d.ts.map +1 -1
  60. package/dist/components/tools/ToolResult.js +1 -1
  61. package/dist/components/tools/ToolResult.js.map +1 -1
  62. package/dist/hooks/useAiChat.d.ts +2 -0
  63. package/dist/hooks/useAiChat.d.ts.map +1 -1
  64. package/dist/hooks/useAiChat.js +56 -10
  65. package/dist/hooks/useAiChat.js.map +1 -1
  66. package/dist/hooks/useAssistantMessageParts.d.ts +14 -0
  67. package/dist/hooks/useAssistantMessageParts.d.ts.map +1 -0
  68. package/dist/hooks/useAssistantMessageParts.js +44 -0
  69. package/dist/hooks/useAssistantMessageParts.js.map +1 -0
  70. package/dist/hooks/useScrollToBottom.d.ts.map +1 -1
  71. package/dist/hooks/useScrollToBottom.js +4 -3
  72. package/dist/hooks/useScrollToBottom.js.map +1 -1
  73. package/dist/hooks/useToolGrouping.d.ts +23 -0
  74. package/dist/hooks/useToolGrouping.d.ts.map +1 -0
  75. package/dist/hooks/useToolGrouping.js +290 -0
  76. package/dist/hooks/useToolGrouping.js.map +1 -0
  77. package/dist/index.d.ts +5 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +4 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/utils.d.ts +41 -1
  82. package/dist/utils.d.ts.map +1 -1
  83. package/dist/utils.js +91 -0
  84. package/dist/utils.js.map +1 -1
  85. package/package.json +8 -8
package/README.md CHANGED
@@ -10,6 +10,7 @@ An AI integration package for SQLRooms that provides components and utilities fo
10
10
  - 🧩 **UI Components**: Ready-to-use components for AI interactions
11
11
  - 📝 **Query History**: Track and manage AI query history
12
12
  - 🎯 **Tool Integration**: Framework for AI tools and actions
13
+ - 🤖 **Agent Framework**: Framework for building AI agents
13
14
 
14
15
  ## Installation
15
16
 
@@ -290,7 +291,6 @@ const weatherTool: OpenAssistantTool = {
290
291
 
291
292
  ### Transfer Additional Tool Output Data to Client
292
293
 
293
-
294
294
  #### The Problem
295
295
 
296
296
  When tools execute, they often generate additional data (like detailed search results, charts, metadata) that needs to be sent to the client for UI rendering, but should NOT be included in the conversation history sent back to the LLM.
@@ -322,7 +322,7 @@ writer.write({
322
322
  Backend (route.ts)
323
323
 
324
324
  ├─> Tool executes
325
- │ └─> writer.write({
325
+ │ └─> writer.write({
326
326
  │ type: 'data-tool-additional-output',
327
327
  │ transient: true, // ✅ SDK handles exclusion
328
328
  │ data: {...}
@@ -366,6 +366,175 @@ const toolData = currentSession?.toolAdditionalData?.[toolCallId];
366
366
 
367
367
  AI SDK v5 supports message annotations, but these are still part of the message structure. The `transient` flag is specifically designed for data that should only be sent once and not persist in conversation history.
368
368
 
369
+ ## Agent Framework
370
+
371
+ The agent framework enables building autonomous AI agents that can use tools and make multi-step decisions. Built on top of AI SDK v5's `Experimental_Agent` class, it provides utilities for integrating agents as tools within the main chat interface.
372
+
373
+ ### What are Agents?
374
+
375
+ Agents differ from regular tools in that they:
376
+ - Can autonomously decide which tools to call and when
377
+ - Execute multiple steps to accomplish a goal
378
+ - Maintain their own reasoning loop until completion
379
+ - Can be embedded as tools within the main conversation
380
+
381
+ ### Creating an Agent Tool
382
+
383
+ Agent tools follow the same OpenAssistantTool pattern but use the `processAgentStream` utility to handle streaming and progress tracking:
384
+
385
+ ```typescript
386
+ import {Experimental_Agent as Agent, tool} from 'ai';
387
+ import {processAgentStream} from '@sqlrooms/ai';
388
+ import {z} from 'zod';
389
+
390
+ export function weatherAgentTool(store: StoreApi<RoomState>) {
391
+ return {
392
+ name: 'agent-weather',
393
+ description: 'A specialized agent for weather-related queries',
394
+ parameters: z.object({
395
+ prompt: z.string().describe('The user\'s weather question')
396
+ }),
397
+ execute: async ({prompt}, options) => {
398
+ const state = store.getState();
399
+ const currentSession = state.ai.getCurrentSession();
400
+
401
+ // Create the agent with its own set of tools
402
+ const weatherAgent = new Agent({
403
+ model: getModel(state),
404
+ tools: {
405
+ weather: tool({
406
+ description: 'Get current weather in a location',
407
+ inputSchema: z.object({
408
+ location: z.string()
409
+ }),
410
+ execute: async ({location}) => ({
411
+ location,
412
+ temperature: 72 + Math.floor(Math.random() * 21) - 10
413
+ })
414
+ }),
415
+ convertTemperature: tool({
416
+ description: 'Convert temperature units',
417
+ inputSchema: z.object({
418
+ temperature: z.number(),
419
+ from: z.enum(['F', 'C']),
420
+ to: z.enum(['F', 'C'])
421
+ }),
422
+ execute: async ({temperature, from, to}) => {
423
+ // Conversion logic
424
+ }
425
+ })
426
+ },
427
+ stopWhen: stepCountIs(10) // Limit agent steps
428
+ });
429
+
430
+ // Stream the agent's execution
431
+ const agentResult = await weatherAgent.stream({prompt});
432
+
433
+ // Process the stream and track progress
434
+ const resultText = await processAgentStream(
435
+ agentResult,
436
+ store,
437
+ options.toolCallId
438
+ );
439
+
440
+ return {
441
+ llmResult: {
442
+ success: true,
443
+ details: resultText
444
+ }
445
+ };
446
+ }
447
+ };
448
+ }
449
+ ```
450
+
451
+ Use the agent as a tool in your main LLM:
452
+
453
+ ```typescript
454
+ createAiSlice({
455
+ tools: {
456
+ query: QueryTool,
457
+ 'agent-weather': weatherAgentTool(store) // ⚡ Agent as tool
458
+ }
459
+ })
460
+ ```
461
+
462
+ ### The `processAgentStream` Utility
463
+
464
+ The `processAgentStream` function handles the complexity of integrating agent execution into the main conversation:
465
+
466
+ ```typescript
467
+ await processAgentStream(agentResult, store, parentToolCallId)
468
+ ```
469
+
470
+ **What it handles:**
471
+
472
+ 1. **Progress Tracking**: Monitors all tool calls made by the agent in real-time
473
+ 2. **State Updates**: Updates `toolAdditionalData` with agent progress for UI rendering
474
+ 3. **Error Handling**: Captures and reports tool execution errors
475
+ 4. **Result Aggregation**: Collects the final text output from the agent
476
+
477
+ **Stored Data Structure:**
478
+
479
+ Each agent tool call stores structured progress data:
480
+
481
+ ```typescript
482
+ interface AgentToolCallAdditionalData {
483
+ agentToolCalls: Array<{
484
+ toolCallId: string;
485
+ toolName: string;
486
+ output?: unknown;
487
+ errorText?: string;
488
+ state: 'pending' | 'success' | 'error';
489
+ }>;
490
+ finalOutput?: string;
491
+ timestamp: string;
492
+ }
493
+ ```
494
+
495
+ ### Rendering Agent Progress
496
+
497
+ If you are using the `sqlrooms/ai-core` package, the rendering of agent progress is handled automatically.
498
+
499
+ However, you can also use the `useRoomStore` hook to access the agent progress data in your components to show real-time execution:
500
+
501
+ ```tsx
502
+ const currentSession = useRoomStore((state) => state.ai.getCurrentSession());
503
+ const agentData = currentSession?.toolAdditionalData?.[toolCallId] as AgentToolCallAdditionalData;
504
+
505
+ return (
506
+ <div>
507
+ <h3>Agent Progress:</h3>
508
+ {agentData?.agentToolCalls.map((call) => (
509
+ <div key={call.toolCallId}>
510
+ {call.toolName}: {call.state}
511
+ {call.output && <pre>{JSON.stringify(call.output, null, 2)}</pre>}
512
+ {call.errorText && <span className="error">{call.errorText}</span>}
513
+ </div>
514
+ ))}
515
+ {agentData?.finalOutput && (
516
+ <div className="final-result">{agentData.finalOutput}</div>
517
+ )}
518
+ </div>
519
+ );
520
+ ```
521
+
522
+ ### Best Practices
523
+
524
+ 1. **Limit Steps**: Always use `stopWhen` to prevent infinite loops
525
+ 2. **Specific Tools**: Give agents focused, domain-specific tools
526
+ 3. **Clear Descriptions**: Write precise tool descriptions for better agent reasoning
527
+ 4. **Error Handling**: Handle agent errors gracefully with try-catch in execute
528
+ 5. **Progress UI**: Use `toolAdditionalData` to show agent progress to users
529
+
530
+ ### Use Cases
531
+
532
+ - **Multi-step Analysis**: Agents that need to gather data from multiple sources
533
+ - **Decision Trees**: Complex workflows requiring conditional logic
534
+ - **Specialized Domains**: Weather, finance, or data analysis agents with domain-specific tools
535
+ - **Autonomous Tasks**: Tasks that require planning and execution without human intervention
536
+
537
+
369
538
  ## Advanced Features
370
539
 
371
540
  - **Custom AI Tools**: Define custom tools for AI to use with the tool() function
package/dist/AiSlice.d.ts CHANGED
@@ -1,21 +1,51 @@
1
1
  import { AiSliceConfig, AnalysisResultSchema, AnalysisSessionSchema } from '@sqlrooms/ai-config';
2
- import { BaseRoomConfig, type StateCreator } from '@sqlrooms/room-store';
3
- import { DefaultChatTransport, LanguageModel, UIMessage } from 'ai';
2
+ import { type StateCreator } from '@sqlrooms/room-store';
3
+ import { UIMessage, DefaultChatTransport, LanguageModel, UITools, ChatOnDataCallback, UIDataTypes } from 'ai';
4
+ import { ToolCall } from './chatTransport';
4
5
  import { OpenAssistantToolSet } from '@openassistant/utils';
6
+ import { AddToolResult } from './hooks/useAiChat';
5
7
  type ExtendedChatOnToolCallCallback = (args: {
6
- toolCall: any;
7
- addToolResult?: any;
8
- }) => Promise<any> | any;
8
+ toolCall: ToolCall;
9
+ addToolResult?: AddToolResult;
10
+ }) => Promise<void> | void;
9
11
  export type AiSliceState = {
10
12
  ai: {
11
13
  config: AiSliceConfig;
12
14
  analysisPrompt: string;
13
15
  isRunningAnalysis: boolean;
16
+ promptSuggestionsVisible: boolean;
14
17
  tools: OpenAssistantToolSet;
15
18
  analysisAbortController?: AbortController;
16
19
  setConfig: (config: AiSliceConfig) => void;
20
+ setPromptSuggestionsVisible: (visible: boolean) => void;
21
+ /** Latest stop function from useChat to immediately halt local streaming */
22
+ chatStop?: () => void;
23
+ /** Register/replace the current chat stop function */
24
+ setChatStop: (stop: (() => void) | undefined) => void;
25
+ /** Latest sendMessage function from useChat to send messages */
26
+ chatSendMessage?: (message: {
27
+ text: string;
28
+ }) => void;
29
+ /** Register/replace the current chat sendMessage function */
30
+ setChatSendMessage: (sendMessage: ((message: {
31
+ text: string;
32
+ }) => void) | undefined) => void;
33
+ /** Latest addToolResult function from useChat to add tool results */
34
+ addToolResult?: AddToolResult;
35
+ /** Register/replace the current addToolResult function */
36
+ setAddToolResult: (addToolResult: AddToolResult | undefined) => void;
37
+ /** Wait for a tool result to be added by UI component */
38
+ waitForToolResult: (toolCallId: string, abortSignal?: AbortSignal) => Promise<void>;
17
39
  setAnalysisPrompt: (prompt: string) => void;
18
40
  addAnalysisResult: (message: UIMessage) => void;
41
+ sendPrompt: (prompt: string, options?: {
42
+ systemInstructions?: string;
43
+ modelProvider?: string;
44
+ modelName?: string;
45
+ baseUrl?: string;
46
+ abortSignal?: AbortSignal;
47
+ useTools?: boolean;
48
+ }) => Promise<string>;
19
49
  startAnalysis: (sendMessage: (message: {
20
50
  text: string;
21
51
  }) => void) => Promise<void>;
@@ -42,7 +72,7 @@ export type AiSliceState = {
42
72
  chatHeaders: Record<string, string>;
43
73
  getRemoteChatTransport: (endpoint: string, headers?: Record<string, string>) => DefaultChatTransport<UIMessage>;
44
74
  onChatToolCall: ExtendedChatOnToolCallCallback;
45
- onChatData: (dataPart: any) => void;
75
+ onChatData: ChatOnDataCallback<UIMessage<unknown, UIDataTypes, UITools>>;
46
76
  onChatFinish: (args: {
47
77
  message: UIMessage;
48
78
  messages: UIMessage[];
@@ -77,7 +107,7 @@ export interface AiSliceOptions {
77
107
  /** Optional headers to send with remote endpoint */
78
108
  chatHeaders?: Record<string, string>;
79
109
  }
80
- export declare function createAiSlice<PC extends BaseRoomConfig>(params: AiSliceOptions): StateCreator<AiSliceState>;
110
+ export declare function createAiSlice(params: AiSliceOptions): StateCreator<AiSliceState>;
81
111
  export declare function useStoreWithAi<T>(selector: (state: AiSliceState) => T): T;
82
112
  export {};
83
113
  //# sourceMappingURL=AiSlice.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AiSlice.d.ts","sourceRoot":"","sources":["../src/AiSlice.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AiSlice.d.ts","sourceRoot":"","sources":["../src/AiSlice.ts"],"names":[],"mappings":"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"}
package/dist/AiSlice.js CHANGED
@@ -2,22 +2,112 @@ import { createId } from '@paralleldrive/cuid2';
2
2
  import { createDefaultAiConfig, } from '@sqlrooms/ai-config';
3
3
  import { createSlice, useBaseRoomStore, } from '@sqlrooms/room-store';
4
4
  import { produce } from 'immer';
5
- import { createChatHandlers, createLocalChatTransportFactory, createRemoteChatTransportFactory, } from './chatTransport';
5
+ import { generateText, } from 'ai';
6
+ import { createChatHandlers, createLocalChatTransportFactory, createRemoteChatTransportFactory, convertToAiSDKTools, completeIncompleteToolCalls, } from './chatTransport';
6
7
  import { hasAiSettingsConfig } from './hasAiSettingsConfig';
8
+ import { cleanupPendingAnalysisResults } from './utils';
9
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
7
10
  export function createAiSlice(params) {
8
11
  const { initialAnalysisPrompt = '', tools, getApiKey, getBaseUrl, maxSteps = 50, getInstructions, defaultProvider = 'openai', defaultModel = 'gpt-4.1', getCustomModel, chatEndPoint = '', chatHeaders = {}, } = params;
9
12
  return createSlice((set, get, store) => {
13
+ // Clean up pending analysis results from persisted config
14
+ const cleanedConfig = params.config?.sessions
15
+ ? {
16
+ ...params.config,
17
+ sessions: params.config.sessions.map((session) => {
18
+ const cleaned = cleanupPendingAnalysisResults(session);
19
+ const completedUiMessages = Array.isArray(cleaned.uiMessages)
20
+ ? completeIncompleteToolCalls(cleaned.uiMessages || [])
21
+ : [];
22
+ return {
23
+ ...cleaned,
24
+ uiMessages: completedUiMessages,
25
+ };
26
+ }),
27
+ }
28
+ : params.config;
29
+ // Create a persistent Map for pending tool call resolvers (outside of immer draft)
30
+ const pendingToolCallResolvers = new Map();
10
31
  return {
11
32
  ai: {
12
- config: createDefaultAiConfig(params.config),
33
+ config: createDefaultAiConfig(cleanedConfig),
13
34
  analysisPrompt: initialAnalysisPrompt,
14
35
  isRunningAnalysis: false,
36
+ promptSuggestionsVisible: true,
15
37
  tools,
38
+ waitForToolResult: (toolCallId, abortSignal) => {
39
+ return new Promise((resolve, reject) => {
40
+ // Set up abort handler
41
+ const abortHandler = () => {
42
+ const resolver = pendingToolCallResolvers.get(toolCallId);
43
+ if (resolver) {
44
+ pendingToolCallResolvers.delete(toolCallId);
45
+ resolver.reject(new Error('Tool call cancelled by user'));
46
+ }
47
+ };
48
+ if (abortSignal) {
49
+ if (abortSignal.aborted) {
50
+ reject(new Error('Tool call cancelled by user'));
51
+ return;
52
+ }
53
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
54
+ }
55
+ // Store resolver (overwrites any existing one, which is fine for our use case)
56
+ pendingToolCallResolvers.set(toolCallId, {
57
+ resolve: () => {
58
+ if (abortSignal) {
59
+ abortSignal.removeEventListener('abort', abortHandler);
60
+ }
61
+ pendingToolCallResolvers.delete(toolCallId);
62
+ resolve();
63
+ },
64
+ reject: (error) => {
65
+ if (abortSignal) {
66
+ abortSignal.removeEventListener('abort', abortHandler);
67
+ }
68
+ pendingToolCallResolvers.delete(toolCallId);
69
+ reject(error);
70
+ },
71
+ });
72
+ });
73
+ },
74
+ setChatStop: (stopFn) => {
75
+ set((state) => produce(state, (draft) => {
76
+ draft.ai.chatStop = stopFn;
77
+ }));
78
+ },
16
79
  setConfig: (config) => {
17
80
  set((state) => produce(state, (draft) => {
18
81
  draft.ai.config = config;
19
82
  }));
20
83
  },
84
+ setPromptSuggestionsVisible: (visible) => {
85
+ set((state) => produce(state, (draft) => {
86
+ draft.ai.promptSuggestionsVisible = visible;
87
+ }));
88
+ },
89
+ setChatSendMessage: (sendMessageFn) => {
90
+ set((state) => produce(state, (draft) => {
91
+ draft.ai.chatSendMessage = sendMessageFn;
92
+ }));
93
+ },
94
+ setAddToolResult: (addToolResultFn) => {
95
+ // Wrap addToolResult to intercept calls and resolve pending promises
96
+ const wrappedAddToolResult = addToolResultFn
97
+ ? (options) => {
98
+ // Call the original addToolResult
99
+ addToolResultFn(options);
100
+ // Resolve the promise if there's a pending waiter for this toolCallId
101
+ const resolver = pendingToolCallResolvers.get(options.toolCallId);
102
+ if (resolver) {
103
+ resolver.resolve();
104
+ }
105
+ }
106
+ : undefined;
107
+ set((state) => produce(state, (draft) => {
108
+ draft.ai.addToolResult = wrappedAddToolResult;
109
+ }));
110
+ },
21
111
  setAnalysisPrompt: (prompt) => {
22
112
  set((state) => produce(state, (draft) => {
23
113
  draft.ai.analysisPrompt = prompt;
@@ -228,6 +318,41 @@ export function createAiSlice(params) {
228
318
  }
229
319
  return instructions;
230
320
  },
321
+ sendPrompt: async (prompt, options = {}) => {
322
+ // One-shot generateText path with explicit abort lifecycle management
323
+ const state = get();
324
+ const currentSession = state.ai.getCurrentSession();
325
+ const { systemInstructions, modelProvider, modelName, baseUrl, abortSignal, useTools = false, } = options;
326
+ const provider = modelProvider || currentSession?.modelProvider || defaultProvider;
327
+ const modelId = modelName || currentSession?.model || defaultModel;
328
+ const baseURL = baseUrl ||
329
+ state.ai.getBaseUrlFromSettings() ||
330
+ 'https://api.openai.com/v1';
331
+ const tools = state.ai.tools;
332
+ // remove execute from tools
333
+ const toolsWithoutExecute = Object.fromEntries(Object.entries(tools).filter(([, tool]) => !tool.execute));
334
+ const model = createOpenAICompatible({
335
+ apiKey: state.ai.getApiKeyFromSettings(),
336
+ name: provider,
337
+ baseURL,
338
+ }).chatModel(modelId);
339
+ try {
340
+ const response = await generateText({
341
+ model,
342
+ messages: [{ role: 'user', content: prompt }],
343
+ system: systemInstructions || state.ai.getFullInstructions(),
344
+ abortSignal: abortSignal,
345
+ ...(useTools
346
+ ? { tools: convertToAiSDKTools(toolsWithoutExecute) }
347
+ : {}),
348
+ });
349
+ return response.text;
350
+ }
351
+ catch (error) {
352
+ console.error('Error generating text:', error);
353
+ return 'error: can not generate response';
354
+ }
355
+ },
231
356
  /**
232
357
  * Start the analysis
233
358
  * TODO: how to pass the history analysisResults?
@@ -243,9 +368,13 @@ export function createAiSlice(params) {
243
368
  set((state) => produce(state, (draft) => {
244
369
  draft.ai.analysisAbortController = abortController;
245
370
  draft.ai.isRunningAnalysis = true;
371
+ draft.ai.analysisPrompt = '';
372
+ draft.ai.promptSuggestionsVisible = false;
246
373
  // Add incomplete analysis result to session immediately for instant UI rendering
247
374
  const session = draft.ai.config.sessions.find((s) => s.id === currentSession.id);
248
375
  if (session) {
376
+ // Remove any existing pending results (safety check for page refresh scenarios)
377
+ session.analysisResults = session.analysisResults.filter((result) => result.id !== '__pending__');
249
378
  // Add incomplete analysis result with a temporary ID
250
379
  // This will be updated in onChatFinish with the actual user message ID
251
380
  session.analysisResults.push({
@@ -259,21 +388,26 @@ export function createAiSlice(params) {
259
388
  sendMessage({ text: promptText });
260
389
  },
261
390
  cancelAnalysis: () => {
262
- const currentSession = get().ai.getCurrentSession();
391
+ const abortController = get().ai.analysisAbortController;
392
+ // Stop local chat streaming immediately if available
393
+ try {
394
+ get().ai.chatStop?.();
395
+ }
396
+ catch {
397
+ // no-op
398
+ }
399
+ // Call abort to signal cancellation
400
+ // Keep the abort controller in state so that async handlers (onChatToolCall, onChatFinish)
401
+ // can check if it was aborted. The onChatFinish handler will clean it up.
402
+ abortController?.abort('Analysis cancelled');
263
403
  set((state) => produce(state, (draft) => {
404
+ // Set isRunningAnalysis to false to update UI
264
405
  draft.ai.isRunningAnalysis = false;
265
- // Remove pending analysis result if it exists
266
- if (currentSession) {
267
- const session = draft.ai.config.sessions.find((s) => s.id === currentSession.id);
268
- if (session) {
269
- const pendingIndex = session.analysisResults.findIndex((result) => result.id === '__pending__');
270
- if (pendingIndex !== -1) {
271
- session.analysisResults.splice(pendingIndex, 1);
272
- }
273
- }
274
- }
406
+ // Keep analysisAbortController so handlers can check signal.aborted
407
+ // It will be cleared by onChatFinish
408
+ // Intentionally preserve any pending analysis result so the
409
+ // conversation row remains visible until onChatFinish runs.
275
410
  }));
276
- get().ai.analysisAbortController?.abort('Analysis cancelled');
277
411
  },
278
412
  /**
279
413
  * Get the assistant message parts for a given analysis result ID