diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.test.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.test.ts index 255477669..c9465deae 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.test.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/helpers/sequential-thinking-tool-transform-helper.test.ts @@ -9,7 +9,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { describe('createSequentialThinkingReasoningMessage', () => { test('should create reasoning message with loading status by default', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-123', + toolCallId: 'test-entry-123', args: undefined, thought: 'This is my thinking process', nextThoughtNeeded: true, @@ -31,7 +31,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should create reasoning message with completed status', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-456', + toolCallId: 'test-entry-456', args: undefined, thought: 'Final thought completed', nextThoughtNeeded: false, @@ -53,7 +53,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should use toolCallId when entry_id is not set', () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: 'Thinking with toolCallId', nextThoughtNeeded: true, @@ -75,7 +75,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should return null when no id is available', () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: 'No ID available', nextThoughtNeeded: true, @@ -89,7 +89,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should handle empty thought', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-empty', + toolCallId: 'test-entry-empty', args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -113,7 +113,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { describe('createSequentialThinkingRawLlmMessageEntry', () => { test('should create raw LLM message with all state values', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-123', + toolCallId: 'test-entry-123', args: undefined, thought: 'Complete thinking process', nextThoughtNeeded: false, @@ -141,7 +141,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should create raw LLM message with partial state values', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-456', + toolCallId: 'test-entry-456', args: undefined, thought: 'Partial thought', nextThoughtNeeded: undefined, @@ -167,7 +167,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should use toolCallId when entry_id is not set', () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: 'Using toolCallId', nextThoughtNeeded: true, @@ -195,7 +195,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should return null when no id is available', () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: 'No ID available', nextThoughtNeeded: true, @@ -209,7 +209,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should create empty input object when no state values are defined', () => { const state: SequentialThinkingState = { - entry_id: 'test-entry-empty', + toolCallId: 'test-entry-empty', args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -233,7 +233,7 @@ describe('Sequential Thinking Tool Transform Helper', () => { test('should handle all field types correctly', () => { const state: SequentialThinkingState = { - entry_id: 'test-types', + toolCallId: 'test-types', args: '{"thought": "test"}', // This should not be included in output thought: 'String value', nextThoughtNeeded: true, diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts index c472bcc29..43e372044 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-execute.ts @@ -1,6 +1,7 @@ import { updateMessageEntries } from '@buster/database'; import { wrapTraced } from 'braintrust'; import { normalizeEscapedText } from '../../../utils/streaming/escape-normalizer'; +import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; import { createSequentialThinkingRawLlmMessageEntry, createSequentialThinkingReasoningMessage, @@ -15,76 +16,31 @@ import { // Process sequential thinking execution async function processSequentialThinking( - input: SequentialThinkingInput, - state: SequentialThinkingState, - context: SequentialThinkingContext + toolCallId: string, + messageId: string ): Promise { + const output: SequentialThinkingOutput = { + success: true, + }; + + const rawToolResultEntry = createRawToolResultEntry( + toolCallId, + SEQUENTIAL_THINKING_TOOL_NAME, + output + ); + try { - // Log the thinking step for debugging - if (context.messageId) { - console.info('[sequential-thinking] Processing thought:', { - messageId: context.messageId, - thoughtNumber: input.thoughtNumber, - nextThoughtNeeded: input.nextThoughtNeeded, - }); - } - - // Since we have the full input object, create the reasoning entries directly - const toolCallId = state.entry_id; - - if (toolCallId && context.messageId) { - // Create reasoning entry with completed status using the input values directly - const reasoningEntry = createSequentialThinkingReasoningMessage( - { - entry_id: toolCallId, - thought: normalizeEscapedText(input.thought), - nextThoughtNeeded: input.nextThoughtNeeded, - thoughtNumber: input.thoughtNumber, - }, - toolCallId, - 'completed' // Mark as completed in execute - ); - - // Create raw LLM message entry using the input values directly - const rawLlmMessage = createSequentialThinkingRawLlmMessageEntry( - { - entry_id: toolCallId, - thought: normalizeEscapedText(input.thought), - nextThoughtNeeded: input.nextThoughtNeeded, - thoughtNumber: input.thoughtNumber, - }, - toolCallId - ); - - const reasoningMessages = reasoningEntry ? [reasoningEntry] : []; - const rawLlmMessages = rawLlmMessage ? [rawLlmMessage] : []; - - if (reasoningMessages.length > 0 || rawLlmMessages.length > 0) { - await updateMessageEntries({ - messageId: context.messageId, - reasoningMessages, - rawLlmMessages, - }); - - console.info('[sequential-thinking] Completed sequential thinking in execute:', { - messageId: context.messageId, - toolCallId, - thoughtNumber: input.thoughtNumber, - nextThoughtNeeded: input.nextThoughtNeeded, - }); - } - } - - return { - success: true, - }; + await updateMessageEntries({ + messageId, + rawLlmMessages: [rawToolResultEntry], + }); } catch (error) { - console.error('[sequential-thinking] Error in sequential thinking:', error); - - throw new Error( - `Sequential thinking processing failed: ${error instanceof Error ? error.message : 'Unknown error'}` - ); + console.error('[sequential-thinking] Error updating message entries:', error); } + + return { + success: true, + }; } // Factory function that creates the execute function with proper context typing @@ -93,8 +49,12 @@ export function createSequentialThinkingExecute( context: SequentialThinkingContext ) { return wrapTraced( - async (input: SequentialThinkingInput): Promise => { - return await processSequentialThinking(input, state, context); + async (_input: SequentialThinkingInput): Promise => { + if (!state.toolCallId) { + throw new Error('Tool call ID is required'); + } + + return await processSequentialThinking(state.toolCallId, context.messageId); }, { name: SEQUENTIAL_THINKING_TOOL_NAME } ); diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-finish.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-finish.ts index 642d189c3..3052ff9c0 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-finish.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-finish.ts @@ -19,7 +19,7 @@ export function createSequentialThinkingFinish( options: { input: SequentialThinkingInput } & ToolCallOptions ): Promise { // Update state with the final input values - sequentialThinkingState.entry_id = options.toolCallId; + sequentialThinkingState.toolCallId = options.toolCallId; sequentialThinkingState.thought = normalizeEscapedText(options.input.thought); sequentialThinkingState.nextThoughtNeeded = options.input.nextThoughtNeeded; sequentialThinkingState.thoughtNumber = options.input.thoughtNumber; diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-start.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-start.ts index 198036627..eb1b9fec5 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-start.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-start.ts @@ -16,7 +16,7 @@ export function createSequentialThinkingStart( ) { return async function sequentialThinkingStart(options: ToolCallOptions): Promise { // Set the entry ID and start time in state - sequentialThinkingState.entry_id = options.toolCallId; + sequentialThinkingState.toolCallId = options.toolCallId; sequentialThinkingState.startTime = Date.now(); // Create initial reasoning entry with loading status diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-streaming.test.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-streaming.test.ts index 8624f92c7..980d9c738 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-streaming.test.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool-streaming.test.ts @@ -21,7 +21,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { describe('createSequentialThinkingStart', () => { test('should initialize state with entry_id on start', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -36,35 +36,14 @@ describe('Sequential Thinking Tool Streaming Tests', () => { await startHandler(options); - expect(state.entry_id).toBe('tool-call-123'); - }); - - test('should handle start without messageId in context', async () => { - const state: SequentialThinkingState = { - entry_id: undefined, - args: undefined, - thought: undefined, - nextThoughtNeeded: undefined, - thoughtNumber: undefined, - }; - - const contextWithoutMessageId: SequentialThinkingContext = {}; - const startHandler = createSequentialThinkingStart(state, contextWithoutMessageId); - const options: ToolCallOptions = { - toolCallId: 'tool-call-456', - messages: [], - }; - - await startHandler(options); - - expect(state.entry_id).toBe('tool-call-456'); + expect(state.toolCallId).toBe('tool-call-123'); }); }); describe('createSequentialThinkingDelta', () => { test('should accumulate delta text to args', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -85,7 +64,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should parse partial JSON and extract thought value', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -105,7 +84,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should handle nextThoughtNeeded boolean value', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -126,7 +105,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should handle thoughtNumber value', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -148,7 +127,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should accumulate multiple deltas correctly', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -185,7 +164,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should handle escaped characters in thought', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -208,7 +187,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { vi.mocked(updateMessageEntries).mockClear(); const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '{"thought": "Already set"}', thought: 'Already set', nextThoughtNeeded: undefined, @@ -230,7 +209,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { describe('createSequentialThinkingFinish', () => { test('should update state with final input values', async () => { const state: SequentialThinkingState = { - entry_id: 'tool-call-123', + toolCallId: 'tool-call-123', args: '{"thought": "Partial"}', thought: 'Partial', nextThoughtNeeded: undefined, @@ -258,7 +237,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { test('should normalize escaped text in final thought', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -280,36 +259,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { }); expect(state.thought).toBe('Final thought with "escaped quotes" and \\\\backslashes'); - expect(state.entry_id).toBe('tool-call-789'); - }); - - test('should handle finish without messageId in context', async () => { - const state: SequentialThinkingState = { - entry_id: undefined, - args: undefined, - thought: undefined, - nextThoughtNeeded: undefined, - thoughtNumber: undefined, - }; - - const contextWithoutMessageId: SequentialThinkingContext = {}; - const finishHandler = createSequentialThinkingFinish(state, contextWithoutMessageId); - - const input: SequentialThinkingInput = { - thought: 'Thinking without message context', - nextThoughtNeeded: true, - thoughtNumber: 1, - }; - - await finishHandler({ - input, - toolCallId: 'no-message-id', - messages: [], - }); - - expect(state.thought).toBe('Thinking without message context'); - expect(state.nextThoughtNeeded).toBe(true); - expect(state.thoughtNumber).toBe(1); + expect(state.toolCallId).toBe('tool-call-789'); }); test('should update database with completed status on finish', async () => { @@ -317,7 +267,7 @@ describe('Sequential Thinking Tool Streaming Tests', () => { vi.mocked(updateMessageEntries).mockClear(); const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: undefined, nextThoughtNeeded: undefined, diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.int.test.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.int.test.ts index 9ddf2002e..61c68da7f 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.int.test.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.int.test.ts @@ -45,7 +45,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { describe('Database Message Updates', () => { test('should create initial entries when sequential thinking starts', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -58,7 +58,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { await startHandler({ toolCallId, messages: [] }); // Verify state was updated - expect(state.entry_id).toBe(toolCallId); + expect(state.toolCallId).toBe(toolCallId); // Verify database was updated const [message] = await db @@ -71,7 +71,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { test('should update state during streaming delta', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -98,7 +98,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { test('should update state with complete JSON during delta', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -127,7 +127,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { test('should finalize state when sequential thinking finishes', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -158,12 +158,12 @@ describe('Sequential Thinking Tool Integration Tests', () => { ); expect(state.nextThoughtNeeded).toBe(false); expect(state.thoughtNumber).toBe(3); - expect(state.entry_id).toBe(toolCallId); + expect(state.toolCallId).toBe(toolCallId); }); test('should handle multiple sequential thoughts in sequence', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: '', thought: undefined, nextThoughtNeeded: undefined, @@ -214,7 +214,7 @@ describe('Sequential Thinking Tool Integration Tests', () => { test('should handle error gracefully when database update fails', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, thought: undefined, nextThoughtNeeded: undefined, @@ -233,12 +233,12 @@ describe('Sequential Thinking Tool Integration Tests', () => { await expect(startHandler({ toolCallId, messages: [] })).resolves.not.toThrow(); // State should still be updated even if database fails - expect(state.entry_id).toBe(toolCallId); + expect(state.toolCallId).toBe(toolCallId); }); test('should handle streaming with escaped characters', async () => { const state: SequentialThinkingState = { - entry_id: undefined, + toolCallId: undefined, args: '', thought: undefined, nextThoughtNeeded: undefined, diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.test.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.test.ts index 9d97f0bd1..190fd02aa 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.test.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.test.ts @@ -54,27 +54,6 @@ describe('Sequential Thinking Tool', () => { expect(result).toEqual({ success: true }); }); - test('should handle execution without messageId', async () => { - const context: SequentialThinkingContext = {}; - - const tool = createSequentialThinkingTool(context); - - expect(tool.execute).toBeDefined(); - const execute = tool.execute; - if (!execute) throw new Error('execute is undefined'); - - const result = await execute( - { - thought: 'Thinking without message context', - nextThoughtNeeded: false, - thoughtNumber: 1, - }, - { toolCallId: 'test', messages: [] } - ); - - expect(result).toEqual({ success: true }); - }); - test('should have required streaming callbacks', () => { const context: SequentialThinkingContext = { messageId: 'test-message-123', diff --git a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.ts b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.ts index 34cbb79f4..6b90892cd 100644 --- a/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.ts +++ b/packages/ai/src/tools/planning-thinking-tools/sequential-thinking-tool/sequential-thinking-tool.ts @@ -25,12 +25,11 @@ const SequentialThinkingOutputSchema = z.object({ const SequentialThinkingContextSchema = z.object({ messageId: z .string() - .optional() .describe('The message ID of the message that triggered the sequential thinking'), }); const SequentialThinkingStateSchema = z.object({ - entry_id: z + toolCallId: z .string() .optional() .describe( @@ -63,7 +62,7 @@ export type SequentialThinkingState = z.infer