@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.
- package/README.md +171 -2
- package/dist/AiSlice.d.ts +37 -7
- package/dist/AiSlice.d.ts.map +1 -1
- package/dist/AiSlice.js +148 -14
- package/dist/AiSlice.js.map +1 -1
- package/dist/agents/AgentUtils.d.ts +61 -0
- package/dist/agents/AgentUtils.d.ts.map +1 -0
- package/dist/agents/AgentUtils.js +90 -0
- package/dist/agents/AgentUtils.js.map +1 -0
- package/dist/chatTransport.d.ts +17 -4
- package/dist/chatTransport.d.ts.map +1 -1
- package/dist/chatTransport.js +244 -19
- package/dist/chatTransport.js.map +1 -1
- package/dist/components/AnalysisAnswer.d.ts +2 -0
- package/dist/components/AnalysisAnswer.d.ts.map +1 -1
- package/dist/components/AnalysisAnswer.js +4 -2
- package/dist/components/AnalysisAnswer.js.map +1 -1
- package/dist/components/AnalysisResult.d.ts +7 -0
- package/dist/components/AnalysisResult.d.ts.map +1 -1
- package/dist/components/AnalysisResult.js +42 -42
- package/dist/components/AnalysisResult.js.map +1 -1
- package/dist/components/AnalysisResultsContainer.d.ts +4 -0
- package/dist/components/AnalysisResultsContainer.d.ts.map +1 -1
- package/dist/components/AnalysisResultsContainer.js +11 -4
- package/dist/components/AnalysisResultsContainer.js.map +1 -1
- package/dist/components/DeleteConfirmationDialog.d.ts +14 -0
- package/dist/components/DeleteConfirmationDialog.d.ts.map +1 -0
- package/dist/components/DeleteConfirmationDialog.js +6 -0
- package/dist/components/DeleteConfirmationDialog.js.map +1 -0
- package/dist/components/GroupedMessageParts.d.ts +25 -0
- package/dist/components/GroupedMessageParts.d.ts.map +1 -0
- package/dist/components/GroupedMessageParts.js +34 -0
- package/dist/components/GroupedMessageParts.js.map +1 -0
- package/dist/components/MessagePartsList.d.ts +23 -0
- package/dist/components/MessagePartsList.d.ts.map +1 -0
- package/dist/components/MessagePartsList.js +27 -0
- package/dist/components/MessagePartsList.js.map +1 -0
- package/dist/components/PromptSuggestions.d.ts +32 -0
- package/dist/components/PromptSuggestions.d.ts.map +1 -0
- package/dist/components/PromptSuggestions.js +69 -0
- package/dist/components/PromptSuggestions.js.map +1 -0
- package/dist/components/QueryControls.d.ts.map +1 -1
- package/dist/components/QueryControls.js +11 -4
- package/dist/components/QueryControls.js.map +1 -1
- package/dist/components/ReasoningBox.d.ts +21 -0
- package/dist/components/ReasoningBox.d.ts.map +1 -0
- package/dist/components/ReasoningBox.js +29 -0
- package/dist/components/ReasoningBox.js.map +1 -0
- package/dist/components/ToolCallInfo.d.ts.map +1 -1
- package/dist/components/ToolCallInfo.js +1 -11
- package/dist/components/ToolCallInfo.js.map +1 -1
- package/dist/components/ToolPartRenderer.d.ts +20 -0
- package/dist/components/ToolPartRenderer.d.ts.map +1 -0
- package/dist/components/ToolPartRenderer.js +85 -0
- package/dist/components/ToolPartRenderer.js.map +1 -0
- package/dist/components/tools/ToolErrorMessage.d.ts.map +1 -1
- package/dist/components/tools/ToolErrorMessage.js +1 -2
- package/dist/components/tools/ToolErrorMessage.js.map +1 -1
- package/dist/components/tools/ToolResult.d.ts.map +1 -1
- package/dist/components/tools/ToolResult.js +1 -1
- package/dist/components/tools/ToolResult.js.map +1 -1
- package/dist/hooks/useAiChat.d.ts +2 -0
- package/dist/hooks/useAiChat.d.ts.map +1 -1
- package/dist/hooks/useAiChat.js +56 -10
- package/dist/hooks/useAiChat.js.map +1 -1
- package/dist/hooks/useAssistantMessageParts.d.ts +14 -0
- package/dist/hooks/useAssistantMessageParts.d.ts.map +1 -0
- package/dist/hooks/useAssistantMessageParts.js +44 -0
- package/dist/hooks/useAssistantMessageParts.js.map +1 -0
- package/dist/hooks/useScrollToBottom.d.ts.map +1 -1
- package/dist/hooks/useScrollToBottom.js +4 -3
- package/dist/hooks/useScrollToBottom.js.map +1 -1
- package/dist/hooks/useToolGrouping.d.ts +23 -0
- package/dist/hooks/useToolGrouping.d.ts.map +1 -0
- package/dist/hooks/useToolGrouping.js +290 -0
- package/dist/hooks/useToolGrouping.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +41 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +91 -0
- package/dist/utils.js.map +1 -1
- 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 {
|
|
3
|
-
import { DefaultChatTransport, LanguageModel,
|
|
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:
|
|
7
|
-
addToolResult?:
|
|
8
|
-
}) => Promise<
|
|
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:
|
|
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
|
|
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
|
package/dist/AiSlice.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiSlice.d.ts","sourceRoot":"","sources":["../src/AiSlice.ts"],"names":[],"mappings":"AACA,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EAEtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,
|
|
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 {
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|