( toolName: string, payload: unknown, context?: ServerToolContext )
| 179 | } |
| 180 | |
| 181 | export async function routeExecution( |
| 182 | toolName: string, |
| 183 | payload: unknown, |
| 184 | context?: ServerToolContext |
| 185 | ): Promise<unknown> { |
| 186 | const tool = getServerToolRegistry()[toolName] |
| 187 | if (!tool) { |
| 188 | throw new Error(`Unknown server tool: ${toolName}`) |
| 189 | } |
| 190 | |
| 191 | logger.debug( |
| 192 | context?.messageId ? `Routing to tool [messageId:${context.messageId}]` : 'Routing to tool', |
| 193 | { toolName } |
| 194 | ) |
| 195 | |
| 196 | // Action-level permission enforcement for mixed read/write tools |
| 197 | if (WRITE_ACTIONS[toolName]) { |
| 198 | const p = payload as Record<string, unknown> |
| 199 | const action = (p?.operation ?? p?.action) as string | undefined |
| 200 | if (isWriteAction(toolName, action) && !isWritePermission(context?.userPermission ?? '')) { |
| 201 | const actionLabel = action ? `'${action}' on ` : '' |
| 202 | throw new Error( |
| 203 | `Permission denied: ${actionLabel}${toolName} requires write access. You have '${context?.userPermission ?? 'none'}' permission.` |
| 204 | ) |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | assertServerToolNotAborted( |
| 209 | context, |
| 210 | `User stop signal aborted ${toolName} before payload normalization` |
| 211 | ) |
| 212 | |
| 213 | // Go injects chatId/workspaceId and may wrap the model's args inside a |
| 214 | // nested "args" object. Unwrap that before validation so the generated |
| 215 | // JSON Schema sees the flat tool contract shape. |
| 216 | let normalizedPayload = payload ?? {} |
| 217 | if ( |
| 218 | normalizedPayload && |
| 219 | typeof normalizedPayload === 'object' && |
| 220 | !Array.isArray(normalizedPayload) |
| 221 | ) { |
| 222 | const raw = normalizedPayload as Record<string, unknown> |
| 223 | if (raw.args && typeof raw.args === 'object' && !raw.operation) { |
| 224 | const nested = raw.args as Record<string, unknown> |
| 225 | normalizedPayload = { ...nested, ...raw, args: undefined } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | const args = tool.inputSchema |
| 230 | ? tool.inputSchema.parse(normalizedPayload) |
| 231 | : validateGeneratedToolPayload(toolName, 'parameters', normalizedPayload) |
| 232 | |
| 233 | assertServerToolNotAborted(context, `User stop signal aborted ${toolName} after validation`) |
| 234 | |
| 235 | // Execute |
| 236 | const result = await tool.execute(args, context) |
| 237 | |
| 238 | // Validate output if tool declares a schema; otherwise fall back to the |
no test coverage detected