now sequential thinkgin

This commit is contained in:
dal 2025-08-15 15:04:03 -06:00
parent 2059988494
commit e2757c1ad0
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
8 changed files with 66 additions and 178 deletions

View File

@ -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,

View File

@ -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<SequentialThinkingOutput> {
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<SequentialThinkingOutput> => {
return await processSequentialThinking(input, state, context);
async (_input: SequentialThinkingInput): Promise<SequentialThinkingOutput> => {
if (!state.toolCallId) {
throw new Error('Tool call ID is required');
}
return await processSequentialThinking(state.toolCallId, context.messageId);
},
{ name: SEQUENTIAL_THINKING_TOOL_NAME }
);

View File

@ -19,7 +19,7 @@ export function createSequentialThinkingFinish(
options: { input: SequentialThinkingInput } & ToolCallOptions
): Promise<void> {
// 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;

View File

@ -16,7 +16,7 @@ export function createSequentialThinkingStart(
) {
return async function sequentialThinkingStart(options: ToolCallOptions): Promise<void> {
// 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

View File

@ -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,

View File

@ -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,

View File

@ -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',

View File

@ -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<typeof SequentialThinkingStateSche
export function createSequentialThinkingTool(context: SequentialThinkingContext) {
const state: SequentialThinkingState = {
entry_id: undefined,
toolCallId: undefined,
args: undefined,
thought: undefined,
nextThoughtNeeded: undefined,