| 1 | import { ToolMessage } from '@langchain/core/messages' |
| 2 | import { createMiddleware } from 'langchain' |
| 3 | |
| 4 | const THINKING_WORKFLOW_TOOL_NAMES = new Set([ |
| 5 | 'update_context_document', |
| 6 | 'update_thinking_document' |
| 7 | ]) |
| 8 | |
| 9 | function readErrorChain(error: unknown): string { |
| 10 | const messages: string[] = [] |
| 11 | const seen = new Set<object>() |
| 12 | let current: unknown = error |
| 13 | |
| 14 | while (current && typeof current === 'object' && !seen.has(current as object)) { |
| 15 | seen.add(current as object) |
| 16 | const record = current as Record<string, unknown> |
| 17 | const name = typeof record.name === 'string' ? record.name : '' |
| 18 | const message = typeof record.message === 'string' ? record.message : '' |
| 19 | if (name || message) messages.push(`${name}: ${message}`) |
| 20 | current = record.cause |
| 21 | } |
| 22 | |
| 23 | return messages.join('\n') |
| 24 | } |
| 25 | |
| 26 | export function isThinkingToolInputError(error: unknown): boolean { |
| 27 | return /ToolInvocationError|ToolInputParsingException|Received tool input did not match expected schema/i.test( |
| 28 | readErrorChain(error) |
| 29 | ) |
| 30 | } |
| 31 | |
| 32 | export function createThinkingToolInputRecoveryMiddleware() { |
| 33 | const recoveredToolNames = new Set<string>() |
| 34 | |
| 35 | return createMiddleware({ |
| 36 | name: 'thinkingToolInputRecovery', |
| 37 | wrapToolCall: async (request, handler) => { |
| 38 | try { |
| 39 | return await handler(request) |
| 40 | } catch (error) { |
| 41 | const toolName = String(request.toolCall?.name || '') |
| 42 | if ( |
| 43 | !THINKING_WORKFLOW_TOOL_NAMES.has(toolName) || |
| 44 | recoveredToolNames.has(toolName) || |
| 45 | !isThinkingToolInputError(error) |
| 46 | ) { |
| 47 | throw error |
| 48 | } |
| 49 | |
| 50 | recoveredToolNames.add(toolName) |
| 51 | return new ToolMessage({ |
| 52 | name: toolName, |
| 53 | tool_call_id: String(request.toolCall?.id || ''), |
| 54 | status: 'error', |
| 55 | content: [ |
| 56 | error instanceof Error ? error.message : String(error), |
| 57 | 'Fix the tool arguments so they match the declared schema, then call the same tool again.' |
| 58 | ].join('\n\n') |
| 59 | }) |
| 60 | } |
| 61 | } |
| 62 | }) |
| 63 | } |
| 64 |