From 37aa14c43b72a0ad037fefa3016279b1818a1d7a Mon Sep 17 00:00:00 2001 From: dal Date: Fri, 15 Aug 2025 14:58:46 -0600 Subject: [PATCH] respond without assetstool restuls --- ...without-asset-creation-transform-helper.ts | 4 +- .../respond-without-asset-creation-delta.ts | 4 +- .../respond-without-asset-creation-execute.ts | 49 ++++++-- .../respond-without-asset-creation-finish.ts | 9 +- .../respond-without-asset-creation-start.ts | 9 +- ...d-without-asset-creation-streaming.test.ts | 118 +++++++++--------- .../respond-without-asset-creation-tool.ts | 16 +-- 7 files changed, 123 insertions(+), 86 deletions(-) diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/helpers/respond-without-asset-creation-transform-helper.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/helpers/respond-without-asset-creation-transform-helper.ts index 152d306d7..dd64091eb 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/helpers/respond-without-asset-creation-transform-helper.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/helpers/respond-without-asset-creation-transform-helper.ts @@ -7,7 +7,7 @@ export function createRespondWithoutAssetCreationResponseMessage( toolCallId?: string ): ChatMessageResponseMessage_Text | null { // Use entry_id from state or fallback to provided toolCallId - const id = state.entry_id || toolCallId; + const id = state.toolCallId || toolCallId; if (!id) { return null; @@ -25,7 +25,7 @@ export function createRespondWithoutAssetCreationRawLlmMessageEntry( state: RespondWithoutAssetCreationState, toolCallId?: string ): ModelMessage | null { - const id = state.entry_id || toolCallId; + const id = state.toolCallId || toolCallId; if (!id) { return null; diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-delta.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-delta.ts index 6f6445897..b85a3ff68 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-delta.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-delta.ts @@ -19,8 +19,8 @@ const FINAL_RESPONSE_KEY = 'final_response' as const satisfies keyof RespondWithoutAssetCreationInput; export function createRespondWithoutAssetCreationDelta( - state: RespondWithoutAssetCreationState, - context: RespondWithoutAssetCreationContext + context: RespondWithoutAssetCreationContext, + state: RespondWithoutAssetCreationState ) { return async function respondWithoutAssetCreationDelta( options: { inputTextDelta: string } & ToolCallOptions diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-execute.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-execute.ts index dbb97c7b6..b7edec401 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-execute.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-execute.ts @@ -1,20 +1,53 @@ +import { updateMessageEntries } from '@buster/database'; import { wrapTraced } from 'braintrust'; -import type { - RespondWithoutAssetCreationContext, - RespondWithoutAssetCreationInput, - RespondWithoutAssetCreationOutput, +import { createRawToolResultEntry } from '../../shared/create-raw-llm-tool-result-entry'; +import { + RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME, + type RespondWithoutAssetCreationContext, + type RespondWithoutAssetCreationInput, + type RespondWithoutAssetCreationOutput, + type RespondWithoutAssetCreationState, } from './respond-without-asset-creation-tool'; -async function processRespondWithoutAssetCreation(): Promise { - return {}; +async function processRespondWithoutAssetCreation( + toolCallId: string, + messageId: string +): Promise { + const output: RespondWithoutAssetCreationOutput = { + success: true, + }; + + const rawToolResultEntry = createRawToolResultEntry( + toolCallId, + RESPOND_WITHOUT_ASSET_CREATION_TOOL_NAME, + output + ); + + try { + await updateMessageEntries({ + messageId, + rawLlmMessages: [rawToolResultEntry], + }); + } catch (error) { + console.error('[respond-without-asset-creation] Error updating message entries:', error); + } + + return output; } -export function createRespondWithoutAssetCreationExecute() { +export function createRespondWithoutAssetCreationExecute( + context: RespondWithoutAssetCreationContext, + state: RespondWithoutAssetCreationState +) { return wrapTraced( async ( _input: RespondWithoutAssetCreationInput ): Promise => { - return await processRespondWithoutAssetCreation(); + if (!state.toolCallId) { + throw new Error('Tool call ID is required'); + } + + return await processRespondWithoutAssetCreation(state.toolCallId, context.messageId); }, { name: 'Respond Without Asset Creation' } ); diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-finish.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-finish.ts index 61fcf088a..f968173af 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-finish.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-finish.ts @@ -10,13 +10,14 @@ import type { RespondWithoutAssetCreationState, } from './respond-without-asset-creation-tool'; -export function createRespondWithoutAssetCreationFinish< - TAgentContext extends RespondWithoutAssetCreationContext, ->(state: RespondWithoutAssetCreationState, context: TAgentContext) { +export function createRespondWithoutAssetCreationFinish( + context: RespondWithoutAssetCreationContext, + state: RespondWithoutAssetCreationState +) { return async function respondWithoutAssetCreationFinish( options: { input: RespondWithoutAssetCreationInput } & ToolCallOptions ): Promise { - state.entry_id = options.toolCallId; + state.toolCallId = options.toolCallId; state.final_response = options.input.final_response; const responseEntry = createRespondWithoutAssetCreationResponseMessage( diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-start.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-start.ts index c7061ab08..b1f17e570 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-start.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-start.ts @@ -10,13 +10,14 @@ import type { } from './respond-without-asset-creation-tool'; // Factory function that creates a type-safe callback for the specific agent context -export function createRespondWithoutAssetCreationStart< - TAgentContext extends RespondWithoutAssetCreationContext, ->(state: RespondWithoutAssetCreationState, context: TAgentContext) { +export function createRespondWithoutAssetCreationStart( + context: RespondWithoutAssetCreationContext, + state: RespondWithoutAssetCreationState +) { return async function respondWithoutAssetCreationStart( options: Pick ): Promise { - state.entry_id = options.toolCallId; + state.toolCallId = options.toolCallId; const responseEntry = createRespondWithoutAssetCreationResponseMessage( state, diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-streaming.test.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-streaming.test.ts index e2966ed2b..1bb67397a 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-streaming.test.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-streaming.test.ts @@ -22,12 +22,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { describe('createRespondWithoutAssetCreationStart', () => { test('should initialize state with entry_id on start', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const startHandler = createRespondWithoutAssetCreationStart(state, mockContext); + const startHandler = createRespondWithoutAssetCreationStart(mockContext, state); const options: ToolCallOptions = { toolCallId: 'tool-call-123', messages: [], @@ -35,7 +35,7 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { await startHandler(options); - expect(state.entry_id).toBe('tool-call-123'); + expect(state.toolCallId).toBe('tool-call-123'); }); test('should handle start without messageId in context', async () => { @@ -44,19 +44,19 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { workflowStartTime: Date.now(), }; const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const startHandler = createRespondWithoutAssetCreationStart(state, contextWithoutMessageId); + const startHandler = createRespondWithoutAssetCreationStart(contextWithoutMessageId, state); const options: ToolCallOptions = { toolCallId: 'tool-call-456', messages: [], }; await expect(startHandler(options)).resolves.not.toThrow(); - expect(state.entry_id).toBe('tool-call-456'); + expect(state.toolCallId).toBe('tool-call-456'); }); test('should handle empty messageId gracefully', async () => { @@ -65,31 +65,31 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { workflowStartTime: Date.now(), }; const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const startHandler = createRespondWithoutAssetCreationStart(state, contextWithEmptyMessageId); + const startHandler = createRespondWithoutAssetCreationStart(contextWithEmptyMessageId, state); const options: ToolCallOptions = { toolCallId: 'tool-call-789', messages: [], }; await expect(startHandler(options)).resolves.not.toThrow(); - expect(state.entry_id).toBe('tool-call-789'); + expect(state.toolCallId).toBe('tool-call-789'); }); }); describe('createRespondWithoutAssetCreationDelta', () => { test('should accumulate text deltas to args', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"final_', @@ -110,12 +110,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should extract partial final_response from incomplete JSON', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"final_response": "This is a partial response that is still being', @@ -129,12 +129,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should handle complete JSON in delta', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"final_response": "Complete response message"}', @@ -148,12 +148,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should handle markdown content in final_response', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); const markdownContent = `## Summary @@ -173,12 +173,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should handle escaped characters in JSON', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"final_response": "Line 1\\nLine 2\\n\\"Quoted text\\""}', @@ -191,12 +191,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should not update state when no final_response is extracted', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"other_field": "value"}', @@ -210,12 +210,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { test('should handle empty final_response gracefully', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await deltaHandler({ inputTextDelta: '{"final_response": ""}', @@ -233,12 +233,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { workflowStartTime: Date.now(), }; const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, contextWithoutMessageId); + const deltaHandler = createRespondWithoutAssetCreationDelta(contextWithoutMessageId, state); await expect( deltaHandler({ @@ -255,12 +255,12 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { describe('createRespondWithoutAssetCreationFinish', () => { test('should update state with final input on finish', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: '{"final_response": "Final message"}', final_response: 'Final message', }; - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); const input: RespondWithoutAssetCreationInput = { final_response: 'This is the final response message', @@ -272,18 +272,18 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { messages: [], }); - expect(state.entry_id).toBe('tool-call-123'); + expect(state.toolCallId).toBe('tool-call-123'); expect(state.final_response).toBe('This is the final response message'); }); test('should handle finish without prior entry_id', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); const input: RespondWithoutAssetCreationInput = { final_response: 'Response without prior start', @@ -295,18 +295,18 @@ describe('Respond Without Asset Creation Tool Streaming Tests', () => { messages: [], }); - expect(state.entry_id).toBe('tool-call-456'); + expect(state.toolCallId).toBe('tool-call-456'); expect(state.final_response).toBe('Response without prior start'); }); test('should handle markdown formatted final response', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); const markdownResponse = ` ## Analysis Complete @@ -332,7 +332,7 @@ The following items were processed: messages: [], }); - expect(state.entry_id).toBe('tool-call-789'); + expect(state.toolCallId).toBe('tool-call-789'); expect(state.final_response).toBe(markdownResponse); }); @@ -342,12 +342,12 @@ The following items were processed: workflowStartTime: Date.now(), }; const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const finishHandler = createRespondWithoutAssetCreationFinish(state, contextWithoutMessageId); + const finishHandler = createRespondWithoutAssetCreationFinish(contextWithoutMessageId, state); const input: RespondWithoutAssetCreationInput = { final_response: 'Test response without messageId', @@ -379,13 +379,13 @@ The following items were processed: }; const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const handler1 = createRespondWithoutAssetCreationStart(state, validContext); - const handler2 = createRespondWithoutAssetCreationStart(state, extendedContext); + const handler1 = createRespondWithoutAssetCreationStart(validContext, state); + const handler2 = createRespondWithoutAssetCreationStart(extendedContext, state); expect(handler1).toBeDefined(); expect(handler2).toBeDefined(); @@ -393,17 +393,17 @@ The following items were processed: test('should maintain state type consistency through streaming lifecycle', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const startHandler = createRespondWithoutAssetCreationStart(state, mockContext); - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const startHandler = createRespondWithoutAssetCreationStart(mockContext, state); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); await startHandler({ toolCallId: 'test-123' }); - expect(state.entry_id).toBeTypeOf('string'); + expect(state.toolCallId).toBeTypeOf('string'); await deltaHandler({ inputTextDelta: '{"final_response": "Testing"}', @@ -417,7 +417,7 @@ The following items were processed: final_response: 'Final test', }; await finishHandler({ input, toolCallId: 'test-123', messages: [] }); - expect(state.entry_id).toBeTypeOf('string'); + expect(state.toolCallId).toBeTypeOf('string'); expect(state.final_response).toBeTypeOf('string'); }); }); @@ -425,19 +425,19 @@ The following items were processed: describe('Streaming Flow Integration', () => { test('should handle complete streaming flow from start to finish', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const startHandler = createRespondWithoutAssetCreationStart(state, mockContext); - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const startHandler = createRespondWithoutAssetCreationStart(mockContext, state); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); const toolCallId = 'streaming-test-123'; await startHandler({ toolCallId }); - expect(state.entry_id).toBe(toolCallId); + expect(state.toolCallId).toBe(toolCallId); const chunks = [ '{"final_', @@ -468,7 +468,7 @@ The following items were processed: }; await finishHandler({ input, toolCallId, messages: [] }); - expect(state.entry_id).toBe(toolCallId); + expect(state.toolCallId).toBe(toolCallId); expect(state.final_response).toBe( 'This is a streaming response that comes in multiple chunks' ); @@ -476,12 +476,12 @@ The following items were processed: test('should handle streaming with special characters and formatting', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); const chunks = [ '{"final_response": "', @@ -509,12 +509,12 @@ The following items were processed: test('should handle streaming with very long responses', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); const longText = 'A'.repeat(1000); const jsonWithLongText = JSON.stringify({ final_response: longText }); @@ -533,12 +533,12 @@ The following items were processed: describe('Error Handling', () => { test('should handle malformed JSON gracefully', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: 'test-entry', + toolCallId: 'test-entry', args: '', final_response: undefined, }; - const deltaHandler = createRespondWithoutAssetCreationDelta(state, mockContext); + const deltaHandler = createRespondWithoutAssetCreationDelta(mockContext, state); await expect( deltaHandler({ @@ -554,12 +554,12 @@ The following items were processed: test('should handle null or undefined values', async () => { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const finishHandler = createRespondWithoutAssetCreationFinish(state, mockContext); + const finishHandler = createRespondWithoutAssetCreationFinish(mockContext, state); const input: RespondWithoutAssetCreationInput = { final_response: '', diff --git a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool.ts b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool.ts index bee80011a..7155958d7 100644 --- a/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool.ts +++ b/packages/ai/src/tools/communication-tools/respond-without-asset-creation/respond-without-asset-creation-tool.ts @@ -16,7 +16,9 @@ export const RespondWithoutAssetCreationInputSchema = z.object({ ), }); -export const RespondWithoutAssetCreationOutputSchema = z.object({}); +export const RespondWithoutAssetCreationOutputSchema = z.object({ + success: z.boolean().describe('Whether the operation was successful'), +}); const RespondWithoutAssetCreationContextSchema = z.object({ messageId: z @@ -28,7 +30,7 @@ const RespondWithoutAssetCreationContextSchema = z.object({ }); const RespondWithoutAssetCreationStateSchema = z.object({ - entry_id: z + toolCallId: z .string() .optional() .describe( @@ -58,15 +60,15 @@ export type RespondWithoutAssetCreationState = z.infer< export function createRespondWithoutAssetCreationTool(context: RespondWithoutAssetCreationContext) { const state: RespondWithoutAssetCreationState = { - entry_id: undefined, + toolCallId: undefined, args: undefined, final_response: undefined, }; - const execute = createRespondWithoutAssetCreationExecute(); - const onInputStart = createRespondWithoutAssetCreationStart(state, context); - const onInputDelta = createRespondWithoutAssetCreationDelta(state, context); - const onInputAvailable = createRespondWithoutAssetCreationFinish(state, context); + const execute = createRespondWithoutAssetCreationExecute(context, state); + const onInputStart = createRespondWithoutAssetCreationStart(context, state); + const onInputDelta = createRespondWithoutAssetCreationDelta(context, state); + const onInputAvailable = createRespondWithoutAssetCreationFinish(context, state); return tool({ description: