unreal-engine-mcp-server 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  // Consolidated tool handlers - maps 13 tools to all 36 operations
2
- import { handleToolCall } from './tool-handlers.js';
3
2
  import { cleanObject } from '../utils/safe-json.js';
4
3
  import { Logger } from '../utils/logger.js';
5
4
  const log = new Logger('ConsolidatedToolHandler');
@@ -12,8 +11,6 @@ export async function handleConsolidatedToolCall(name, args, tools) {
12
11
  if (args === null || args === undefined) {
13
12
  throw new Error('Invalid arguments: null or undefined');
14
13
  }
15
- let mappedName;
16
- let mappedArgs = { ...args };
17
14
  switch (name) {
18
15
  // 1. ASSET MANAGER
19
16
  case 'manage_asset':
@@ -26,72 +23,33 @@ export async function handleConsolidatedToolCall(name, args, tools) {
26
23
  throw new Error('Missing required parameter: action');
27
24
  }
28
25
  switch (args.action) {
29
- case 'list':
30
- // Directory is optional
31
- if (args.directory !== undefined && args.directory !== null) {
32
- if (typeof args.directory !== 'string') {
33
- throw new Error('Invalid directory: must be a string');
34
- }
35
- }
36
- mappedName = 'list_assets';
37
- mappedArgs = {
38
- directory: args.directory
39
- // recursive removed - always false internally
40
- };
41
- break;
42
- case 'import':
43
- // Validate required parameters
44
- if (args.sourcePath === undefined || args.sourcePath === null) {
45
- throw new Error('Missing required parameter: sourcePath');
46
- }
47
- if (typeof args.sourcePath !== 'string') {
48
- throw new Error('Invalid sourcePath: must be a string');
49
- }
50
- if (args.sourcePath.trim() === '') {
51
- throw new Error('Invalid sourcePath: cannot be empty');
52
- }
53
- if (args.destinationPath === undefined || args.destinationPath === null) {
54
- throw new Error('Missing required parameter: destinationPath');
26
+ case 'list': {
27
+ if (args.directory !== undefined && args.directory !== null && typeof args.directory !== 'string') {
28
+ throw new Error('Invalid directory: must be a string');
55
29
  }
56
- if (typeof args.destinationPath !== 'string') {
57
- throw new Error('Invalid destinationPath: must be a string');
58
- }
59
- if (args.destinationPath.trim() === '') {
60
- throw new Error('Invalid destinationPath: cannot be empty');
61
- }
62
- mappedName = 'import_asset';
63
- mappedArgs = {
64
- sourcePath: args.sourcePath,
65
- destinationPath: args.destinationPath
66
- };
67
- break;
68
- case 'create_material':
69
- // Validate required parameters
70
- if (args.name === undefined || args.name === null) {
71
- throw new Error('Missing required parameter: name');
72
- }
73
- if (typeof args.name !== 'string') {
74
- throw new Error('Invalid name: must be a string');
30
+ const res = await tools.assetResources.list(args.directory || '/Game', false);
31
+ return cleanObject({ success: true, ...res });
32
+ }
33
+ case 'import': {
34
+ if (typeof args.sourcePath !== 'string' || args.sourcePath.trim() === '') {
35
+ throw new Error('Invalid sourcePath');
75
36
  }
76
- if (args.name.trim() === '') {
77
- throw new Error('Invalid name: cannot be empty');
37
+ if (typeof args.destinationPath !== 'string' || args.destinationPath.trim() === '') {
38
+ throw new Error('Invalid destinationPath');
78
39
  }
79
- // Path is optional
80
- if (args.path !== undefined && args.path !== null) {
81
- if (typeof args.path !== 'string') {
82
- throw new Error('Invalid path: must be a string');
83
- }
40
+ const res = await tools.assetTools.importAsset(args.sourcePath, args.destinationPath);
41
+ return cleanObject(res);
42
+ }
43
+ case 'create_material': {
44
+ if (typeof args.name !== 'string' || args.name.trim() === '') {
45
+ throw new Error('Invalid name: must be a non-empty string');
84
46
  }
85
- mappedName = 'create_material';
86
- mappedArgs = {
87
- name: args.name,
88
- path: args.path
89
- };
90
- break;
47
+ const res = await tools.materialTools.createMaterial(args.name, args.path || '/Game/Materials');
48
+ return cleanObject(res);
49
+ }
91
50
  default:
92
51
  throw new Error(`Unknown asset action: ${args.action}`);
93
52
  }
94
- break;
95
53
  // 2. ACTOR CONTROL
96
54
  case 'control_actor':
97
55
  // Validate action exists
@@ -99,63 +57,41 @@ export async function handleConsolidatedToolCall(name, args, tools) {
99
57
  throw new Error('Missing required parameter: action');
100
58
  }
101
59
  switch (args.action) {
102
- case 'spawn':
103
- // Validate spawn parameters
104
- if (!args.classPath) {
105
- throw new Error('Missing required parameter: classPath');
106
- }
107
- if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
60
+ case 'spawn': {
61
+ if (!args.classPath || typeof args.classPath !== 'string' || args.classPath.trim() === '') {
108
62
  throw new Error('Invalid classPath: must be a non-empty string');
109
63
  }
110
- mappedName = 'spawn_actor';
111
- mappedArgs = {
64
+ const res = await tools.actorTools.spawn({
112
65
  classPath: args.classPath,
113
66
  location: args.location,
114
67
  rotation: args.rotation
115
- };
116
- break;
117
- case 'delete':
118
- // Validate delete parameters
119
- if (!args.actorName) {
120
- throw new Error('Missing required parameter: actorName');
121
- }
122
- if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
123
- throw new Error('Invalid actorName: must be a non-empty string');
124
- }
125
- mappedName = 'delete_actor';
126
- mappedArgs = {
127
- actorName: args.actorName
128
- };
129
- break;
130
- case 'apply_force':
131
- // Validate apply_force parameters
132
- if (!args.actorName) {
133
- throw new Error('Missing required parameter: actorName');
134
- }
135
- if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
136
- throw new Error('Invalid actorName: must be a non-empty string');
137
- }
138
- if (!args.force) {
139
- throw new Error('Missing required parameter: force');
68
+ });
69
+ return cleanObject(res);
70
+ }
71
+ case 'delete': {
72
+ if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
73
+ throw new Error('Invalid actorName');
140
74
  }
141
- if (typeof args.force !== 'object' || args.force === null) {
142
- throw new Error('Invalid force: must be an object with x, y, z properties');
75
+ const res = await tools.bridge.executeEditorFunction('DELETE_ACTOR', { actor_name: args.actorName });
76
+ return cleanObject(res);
77
+ }
78
+ case 'apply_force': {
79
+ if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
80
+ throw new Error('Invalid actorName');
143
81
  }
144
- if (typeof args.force.x !== 'number' ||
145
- typeof args.force.y !== 'number' ||
146
- typeof args.force.z !== 'number') {
147
- throw new Error('Invalid force: x, y, z must all be numbers');
82
+ if (!args.force || typeof args.force.x !== 'number' || typeof args.force.y !== 'number' || typeof args.force.z !== 'number') {
83
+ throw new Error('Invalid force: must have numeric x,y,z');
148
84
  }
149
- mappedName = 'apply_force';
150
- mappedArgs = {
85
+ const res = await tools.physicsTools.applyForce({
151
86
  actorName: args.actorName,
152
- force: args.force
153
- };
154
- break;
87
+ forceType: 'Force',
88
+ vector: [args.force.x, args.force.y, args.force.z]
89
+ });
90
+ return cleanObject(res);
91
+ }
155
92
  default:
156
93
  throw new Error(`Unknown actor action: ${args.action}`);
157
94
  }
158
- break;
159
95
  // 3. EDITOR CONTROL
160
96
  case 'control_editor':
161
97
  // Validate action exists
@@ -163,210 +99,91 @@ export async function handleConsolidatedToolCall(name, args, tools) {
163
99
  throw new Error('Missing required parameter: action');
164
100
  }
165
101
  switch (args.action) {
166
- case 'play':
167
- mappedName = 'play_in_editor';
168
- mappedArgs = {};
169
- break;
170
- case 'stop':
171
- mappedName = 'stop_play_in_editor';
172
- mappedArgs = {};
173
- break;
174
- case 'pause':
175
- mappedName = 'pause_play_in_editor';
176
- mappedArgs = {};
177
- break;
178
- case 'set_game_speed':
179
- // Validate game speed parameter
180
- if (args.speed === undefined || args.speed === null) {
181
- throw new Error('Missing required parameter: speed');
182
- }
183
- if (typeof args.speed !== 'number') {
184
- throw new Error('Invalid speed: must be a number');
185
- }
186
- if (isNaN(args.speed)) {
187
- throw new Error('Invalid speed: cannot be NaN');
188
- }
189
- if (!isFinite(args.speed)) {
190
- throw new Error('Invalid speed: must be finite');
191
- }
192
- if (args.speed <= 0) {
193
- throw new Error('Invalid speed: must be positive');
102
+ case 'play': {
103
+ const res = await tools.editorTools.playInEditor();
104
+ return cleanObject(res);
105
+ }
106
+ case 'stop': {
107
+ const res = await tools.editorTools.stopPlayInEditor();
108
+ return cleanObject(res);
109
+ }
110
+ case 'pause': {
111
+ const res = await tools.editorTools.pausePlayInEditor();
112
+ return cleanObject(res);
113
+ }
114
+ case 'set_game_speed': {
115
+ if (typeof args.speed !== 'number' || !isFinite(args.speed) || args.speed <= 0) {
116
+ throw new Error('Invalid speed: must be a positive number');
194
117
  }
195
- mappedName = 'set_game_speed';
196
- mappedArgs = {
197
- speed: args.speed
198
- };
199
- break;
200
- case 'eject':
201
- mappedName = 'eject_from_pawn';
202
- mappedArgs = {};
203
- break;
204
- case 'possess':
205
- mappedName = 'possess_pawn';
206
- mappedArgs = {};
207
- break;
208
- case 'set_camera':
209
- // Allow either location or rotation or both
210
- // Don't require both to be present
211
- mappedName = 'set_camera';
212
- mappedArgs = {
213
- location: args.location,
214
- rotation: args.rotation
215
- };
216
- break;
217
- case 'set_view_mode':
218
- // Validate view mode parameter
219
- if (!args.viewMode) {
118
+ // Use console command via bridge
119
+ const res = await tools.bridge.executeConsoleCommand(`slomo ${args.speed}`);
120
+ return cleanObject(res);
121
+ }
122
+ case 'eject': {
123
+ const res = await tools.bridge.executeConsoleCommand('eject');
124
+ return cleanObject(res);
125
+ }
126
+ case 'possess': {
127
+ const res = await tools.bridge.executeConsoleCommand('viewself');
128
+ return cleanObject(res);
129
+ }
130
+ case 'set_camera': {
131
+ const res = await tools.editorTools.setViewportCamera(args.location, args.rotation);
132
+ return cleanObject(res);
133
+ }
134
+ case 'set_view_mode': {
135
+ if (!args.viewMode || typeof args.viewMode !== 'string')
220
136
  throw new Error('Missing required parameter: viewMode');
221
- }
222
- if (typeof args.viewMode !== 'string' || args.viewMode.trim() === '') {
223
- throw new Error('Invalid viewMode: must be a non-empty string');
224
- }
225
- // Normalize view mode to match what debug.ts expects
226
- const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
227
- 'light_complexity', 'shader_complexity', 'lightmap_density',
228
- 'stationary_light_overlap', 'reflections', 'visualize_buffer',
229
- 'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
230
- const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
231
- // Map to proper case for debug.ts
232
- let mappedMode = '';
233
- switch (normalizedMode) {
234
- case 'lit':
235
- mappedMode = 'Lit';
236
- break;
237
- case 'unlit':
238
- mappedMode = 'Unlit';
239
- break;
240
- case 'wireframe':
241
- mappedMode = 'Wireframe';
242
- break;
243
- case 'detaillighting':
244
- mappedMode = 'DetailLighting';
245
- break;
246
- case 'lightingonly':
247
- mappedMode = 'LightingOnly';
248
- break;
249
- case 'lightcomplexity':
250
- mappedMode = 'LightComplexity';
251
- break;
252
- case 'shadercomplexity':
253
- mappedMode = 'ShaderComplexity';
254
- break;
255
- case 'lightmapdensity':
256
- mappedMode = 'LightmapDensity';
257
- break;
258
- case 'stationarylightoverlap':
259
- mappedMode = 'StationaryLightOverlap';
260
- break;
261
- case 'reflections':
262
- mappedMode = 'ReflectionOverride';
263
- break;
264
- case 'visualizebuffer':
265
- mappedMode = 'VisualizeBuffer';
266
- break;
267
- case 'collisionpawn':
268
- mappedMode = 'CollisionPawn';
269
- break;
270
- case 'collisionvisibility':
271
- mappedMode = 'CollisionVisibility';
272
- break;
273
- case 'lodcoloration':
274
- mappedMode = 'LODColoration';
275
- break;
276
- case 'quadoverdraw':
277
- mappedMode = 'QuadOverdraw';
278
- break;
279
- default:
280
- throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
281
- }
282
- mappedName = 'set_view_mode';
283
- mappedArgs = {
284
- mode: mappedMode
285
- };
286
- break;
137
+ const res = await tools.bridge.setSafeViewMode(args.viewMode);
138
+ return cleanObject(res);
139
+ }
287
140
  default:
288
141
  throw new Error(`Unknown editor action: ${args.action}`);
289
142
  }
290
- break;
291
143
  // 4. LEVEL MANAGER
292
144
  case 'manage_level':
145
+ if (!args.action)
146
+ throw new Error('Missing required parameter: action');
293
147
  switch (args.action) {
294
- case 'load':
295
- mappedName = 'load_level';
296
- mappedArgs = {
297
- levelPath: args.levelPath
298
- };
299
- if (args.streaming !== undefined) {
300
- mappedArgs.streaming = args.streaming;
301
- }
302
- break;
303
- case 'save':
304
- mappedName = 'save_level';
305
- mappedArgs = {
306
- levelName: args.levelName
307
- };
308
- if (args.savePath !== undefined) {
309
- mappedArgs.savePath = args.savePath;
310
- }
311
- break;
312
- case 'stream':
313
- mappedName = 'stream_level';
314
- mappedArgs = {
315
- levelName: args.levelName,
316
- shouldBeLoaded: args.shouldBeLoaded,
317
- shouldBeVisible: args.shouldBeVisible
318
- };
319
- break;
320
- case 'create_light':
321
- // Validate light type
322
- if (!args.lightType) {
148
+ case 'load': {
149
+ if (!args.levelPath || typeof args.levelPath !== 'string')
150
+ throw new Error('Missing required parameter: levelPath');
151
+ const res = await tools.levelTools.loadLevel({ levelPath: args.levelPath, streaming: !!args.streaming });
152
+ return cleanObject(res);
153
+ }
154
+ case 'save': {
155
+ const res = await tools.levelTools.saveLevel({ levelName: args.levelName, savePath: args.savePath });
156
+ return cleanObject(res);
157
+ }
158
+ case 'stream': {
159
+ if (!args.levelName || typeof args.levelName !== 'string')
160
+ throw new Error('Missing required parameter: levelName');
161
+ const res = await tools.levelTools.streamLevel({ levelName: args.levelName, shouldBeLoaded: !!args.shouldBeLoaded, shouldBeVisible: !!args.shouldBeVisible });
162
+ return cleanObject(res);
163
+ }
164
+ case 'create_light': {
165
+ if (!args.lightType)
323
166
  throw new Error('Missing required parameter: lightType');
324
- }
325
- const validLightTypes = ['directional', 'point', 'spot', 'rect', 'sky'];
326
- const normalizedLightType = String(args.lightType).toLowerCase();
327
- if (!validLightTypes.includes(normalizedLightType)) {
328
- throw new Error(`Invalid lightType: '${args.lightType}'. Valid types are: ${validLightTypes.join(', ')}`);
329
- }
330
- // Validate name
331
- if (!args.name) {
332
- throw new Error('Missing required parameter: name');
333
- }
334
- if (typeof args.name !== 'string' || args.name.trim() === '') {
335
- throw new Error('Invalid name: must be a non-empty string');
336
- }
337
- // Validate intensity if provided
338
- if (args.intensity !== undefined) {
339
- if (typeof args.intensity !== 'number' || !isFinite(args.intensity)) {
340
- throw new Error(`Invalid intensity: must be a finite number, got ${typeof args.intensity}`);
341
- }
342
- if (args.intensity < 0) {
343
- throw new Error('Invalid intensity: must be non-negative');
344
- }
345
- }
346
- // Validate location if provided
347
- if (args.location !== undefined && args.location !== null) {
348
- if (!Array.isArray(args.location) && typeof args.location !== 'object') {
349
- throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
350
- }
351
- }
352
- mappedName = 'create_light';
353
- mappedArgs = {
354
- lightType: args.lightType,
355
- name: args.name,
356
- location: args.location,
357
- intensity: args.intensity
358
- };
359
- break;
360
- case 'build_lighting':
361
- mappedName = 'build_lighting';
362
- mappedArgs = {
363
- quality: args.quality
364
- };
365
- break;
167
+ if (!args.name || typeof args.name !== 'string' || args.name.trim() === '')
168
+ throw new Error('Invalid name');
169
+ const t = String(args.lightType).toLowerCase();
170
+ if (t === 'directional')
171
+ return cleanObject(await tools.lightingTools.createDirectionalLight({ name: args.name, intensity: args.intensity }));
172
+ if (t === 'point')
173
+ return cleanObject(await tools.lightingTools.createPointLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], intensity: args.intensity }));
174
+ if (t === 'spot')
175
+ return cleanObject(await tools.lightingTools.createSpotLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], rotation: [0, 0, 0], intensity: args.intensity }));
176
+ if (t === 'rect')
177
+ return cleanObject(await tools.lightingTools.createRectLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], rotation: [0, 0, 0], intensity: args.intensity }));
178
+ throw new Error(`Unknown light type: ${args.lightType}`);
179
+ }
180
+ case 'build_lighting': {
181
+ const res = await tools.lightingTools.buildLighting({ quality: args.quality || 'High', buildReflectionCaptures: true });
182
+ return cleanObject(res);
183
+ }
366
184
  default:
367
185
  throw new Error(`Unknown level action: ${args.action}`);
368
186
  }
369
- break;
370
187
  // 5. ANIMATION & PHYSICS
371
188
  case 'animation_physics':
372
189
  // Validate action exists
@@ -374,438 +191,244 @@ export async function handleConsolidatedToolCall(name, args, tools) {
374
191
  throw new Error('Missing required parameter: action');
375
192
  }
376
193
  switch (args.action) {
377
- case 'create_animation_bp':
378
- // Validate required parameters
379
- if (args.name === undefined || args.name === null) {
380
- throw new Error('Missing required parameter: name');
381
- }
382
- if (typeof args.name !== 'string') {
383
- throw new Error('Invalid name: must be a string');
384
- }
385
- if (args.name.trim() === '') {
386
- throw new Error('Invalid name: cannot be empty');
387
- }
388
- if (args.skeletonPath === undefined || args.skeletonPath === null) {
389
- throw new Error('Missing required parameter: skeletonPath');
390
- }
391
- if (typeof args.skeletonPath !== 'string') {
392
- throw new Error('Invalid skeletonPath: must be a string');
393
- }
394
- if (args.skeletonPath.trim() === '') {
395
- throw new Error('Invalid skeletonPath: cannot be empty');
396
- }
397
- // Optional savePath validation
398
- if (args.savePath !== undefined && args.savePath !== null) {
399
- if (typeof args.savePath !== 'string') {
400
- throw new Error('Invalid savePath: must be a string');
401
- }
402
- }
403
- mappedName = 'create_animation_blueprint';
404
- mappedArgs = {
405
- name: args.name,
406
- skeletonPath: args.skeletonPath,
407
- savePath: args.savePath
408
- };
409
- break;
410
- case 'play_montage':
411
- // Validate required parameters
412
- if (args.actorName === undefined || args.actorName === null) {
413
- throw new Error('Missing required parameter: actorName');
414
- }
415
- if (typeof args.actorName !== 'string') {
416
- throw new Error('Invalid actorName: must be a string');
417
- }
418
- if (args.actorName.trim() === '') {
419
- throw new Error('Invalid actorName: cannot be empty');
420
- }
421
- // Check for montagePath or animationPath
194
+ case 'create_animation_bp': {
195
+ if (typeof args.name !== 'string' || args.name.trim() === '')
196
+ throw new Error('Invalid name');
197
+ if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '')
198
+ throw new Error('Invalid skeletonPath');
199
+ const res = await tools.animationTools.createAnimationBlueprint({ name: args.name, skeletonPath: args.skeletonPath, savePath: args.savePath });
200
+ return cleanObject(res);
201
+ }
202
+ case 'play_montage': {
203
+ if (typeof args.actorName !== 'string' || args.actorName.trim() === '')
204
+ throw new Error('Invalid actorName');
422
205
  const montagePath = args.montagePath || args.animationPath;
423
- if (montagePath === undefined || montagePath === null) {
424
- throw new Error('Missing required parameter: montagePath or animationPath');
425
- }
426
- if (typeof montagePath !== 'string') {
427
- throw new Error('Invalid montagePath: must be a string');
428
- }
429
- if (montagePath.trim() === '') {
430
- throw new Error('Invalid montagePath: cannot be empty');
431
- }
432
- // Optional playRate validation
433
- if (args.playRate !== undefined && args.playRate !== null) {
434
- if (typeof args.playRate !== 'number') {
435
- throw new Error('Invalid playRate: must be a number');
436
- }
437
- if (isNaN(args.playRate)) {
438
- throw new Error('Invalid playRate: cannot be NaN');
439
- }
440
- if (!isFinite(args.playRate)) {
441
- throw new Error('Invalid playRate: must be finite');
442
- }
443
- }
444
- mappedName = 'play_animation_montage';
445
- mappedArgs = {
446
- actorName: args.actorName,
447
- montagePath: montagePath,
448
- playRate: args.playRate
449
- };
450
- break;
451
- case 'setup_ragdoll':
452
- // Validate required parameters
453
- if (args.skeletonPath === undefined || args.skeletonPath === null) {
454
- throw new Error('Missing required parameter: skeletonPath');
455
- }
456
- if (typeof args.skeletonPath !== 'string') {
457
- throw new Error('Invalid skeletonPath: must be a string');
458
- }
459
- if (args.skeletonPath.trim() === '') {
460
- throw new Error('Invalid skeletonPath: cannot be empty');
461
- }
462
- if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
463
- throw new Error('Missing required parameter: physicsAssetName');
464
- }
465
- if (typeof args.physicsAssetName !== 'string') {
466
- throw new Error('Invalid physicsAssetName: must be a string');
467
- }
468
- if (args.physicsAssetName.trim() === '') {
469
- throw new Error('Invalid physicsAssetName: cannot be empty');
470
- }
471
- // Optional blendWeight validation
472
- if (args.blendWeight !== undefined && args.blendWeight !== null) {
473
- if (typeof args.blendWeight !== 'number') {
474
- throw new Error('Invalid blendWeight: must be a number');
475
- }
476
- if (isNaN(args.blendWeight)) {
477
- throw new Error('Invalid blendWeight: cannot be NaN');
478
- }
479
- if (!isFinite(args.blendWeight)) {
480
- throw new Error('Invalid blendWeight: must be finite');
481
- }
482
- }
483
- // Optional savePath validation
484
- if (args.savePath !== undefined && args.savePath !== null) {
485
- if (typeof args.savePath !== 'string') {
486
- throw new Error('Invalid savePath: must be a string');
487
- }
488
- }
489
- mappedName = 'setup_ragdoll';
490
- mappedArgs = {
491
- skeletonPath: args.skeletonPath,
492
- physicsAssetName: args.physicsAssetName,
493
- blendWeight: args.blendWeight,
494
- savePath: args.savePath
495
- };
496
- break;
206
+ if (typeof montagePath !== 'string' || montagePath.trim() === '')
207
+ throw new Error('Invalid montagePath');
208
+ const res = await tools.animationTools.playAnimation({ actorName: args.actorName, animationType: 'Montage', animationPath: montagePath, playRate: args.playRate });
209
+ return cleanObject(res);
210
+ }
211
+ case 'setup_ragdoll': {
212
+ if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '')
213
+ throw new Error('Invalid skeletonPath');
214
+ if (typeof args.physicsAssetName !== 'string' || args.physicsAssetName.trim() === '')
215
+ throw new Error('Invalid physicsAssetName');
216
+ const res = await tools.physicsTools.setupRagdoll({ skeletonPath: args.skeletonPath, physicsAssetName: args.physicsAssetName, blendWeight: args.blendWeight, savePath: args.savePath });
217
+ return cleanObject(res);
218
+ }
497
219
  default:
498
220
  throw new Error(`Unknown animation/physics action: ${args.action}`);
499
221
  }
500
- break;
501
222
  // 6. EFFECTS SYSTEM
502
223
  case 'create_effect':
503
224
  switch (args.action) {
504
- case 'particle':
505
- mappedName = 'create_particle_effect';
506
- mappedArgs = {
507
- effectType: args.effectType,
508
- name: args.name,
509
- location: args.location
510
- };
511
- break;
512
- case 'niagara':
513
- mappedName = 'spawn_niagara_system';
514
- mappedArgs = {
515
- systemPath: args.systemPath,
516
- location: args.location,
517
- scale: args.scale
518
- };
519
- break;
520
- case 'debug_shape':
521
- mappedName = 'draw_debug_shape';
522
- // Convert location object to array for position
523
- const pos = args.location || args.position;
524
- mappedArgs = {
525
- shape: args.shape,
526
- position: pos ? [pos.x || 0, pos.y || 0, pos.z || 0] : [0, 0, 0],
527
- size: args.size,
528
- color: args.color,
529
- duration: args.duration
530
- };
531
- break;
225
+ case 'particle': {
226
+ const res = await tools.niagaraTools.createEffect({ effectType: args.effectType, name: args.name, location: args.location, scale: args.scale, customParameters: args.customParameters });
227
+ return cleanObject(res);
228
+ }
229
+ case 'niagara': {
230
+ if (typeof args.systemPath !== 'string' || args.systemPath.trim() === '')
231
+ throw new Error('Invalid systemPath');
232
+ // Create or ensure system exists (spawning in editor is not universally supported via RC)
233
+ const name = args.name || args.systemPath.split('/').pop();
234
+ const res = await tools.niagaraTools.createSystem({ name, savePath: args.savePath || '/Game/Effects/Niagara' });
235
+ return cleanObject(res);
236
+ }
237
+ case 'debug_shape': {
238
+ const shape = String(args.shape || 'Sphere').toLowerCase();
239
+ const loc = args.location || { x: 0, y: 0, z: 0 };
240
+ const size = args.size || 100;
241
+ const color = args.color || [255, 0, 0, 255];
242
+ const duration = args.duration || 5;
243
+ if (shape === 'line') {
244
+ const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
245
+ return cleanObject(await tools.debugTools.drawDebugLine({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
246
+ }
247
+ else if (shape === 'box') {
248
+ const extent = [size, size, size];
249
+ return cleanObject(await tools.debugTools.drawDebugBox({ center: [loc.x, loc.y, loc.z], extent, color, duration }));
250
+ }
251
+ else if (shape === 'sphere') {
252
+ return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
253
+ }
254
+ else if (shape === 'capsule') {
255
+ return cleanObject(await tools.debugTools.drawDebugCapsule({ center: [loc.x, loc.y, loc.z], halfHeight: size, radius: Math.max(10, size / 3), color, duration }));
256
+ }
257
+ else if (shape === 'cone') {
258
+ return cleanObject(await tools.debugTools.drawDebugCone({ origin: [loc.x, loc.y, loc.z], direction: [0, 0, 1], length: size, angleWidth: 0.5, angleHeight: 0.5, color, duration }));
259
+ }
260
+ else if (shape === 'arrow') {
261
+ const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
262
+ return cleanObject(await tools.debugTools.drawDebugArrow({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
263
+ }
264
+ else if (shape === 'point') {
265
+ return cleanObject(await tools.debugTools.drawDebugPoint({ location: [loc.x, loc.y, loc.z], size, color, duration }));
266
+ }
267
+ else if (shape === 'text' || shape === 'string') {
268
+ const text = args.text || 'Debug';
269
+ return cleanObject(await tools.debugTools.drawDebugString({ location: [loc.x, loc.y, loc.z], text, color, duration }));
270
+ }
271
+ // Default fallback
272
+ return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
273
+ }
532
274
  default:
533
275
  throw new Error(`Unknown effect action: ${args.action}`);
534
276
  }
535
- break;
536
277
  // 7. BLUEPRINT MANAGER
537
278
  case 'manage_blueprint':
538
279
  switch (args.action) {
539
- case 'create':
540
- mappedName = 'create_blueprint';
541
- mappedArgs = {
542
- name: args.name,
543
- blueprintType: args.blueprintType,
544
- savePath: args.savePath
545
- };
546
- break;
547
- case 'add_component':
548
- mappedName = 'add_blueprint_component';
549
- mappedArgs = {
550
- blueprintName: args.name,
551
- componentType: args.componentType,
552
- componentName: args.componentName
553
- };
554
- break;
280
+ case 'create': {
281
+ const res = await tools.blueprintTools.createBlueprint({ name: args.name, blueprintType: args.blueprintType || 'Actor', savePath: args.savePath });
282
+ return cleanObject(res);
283
+ }
284
+ case 'add_component': {
285
+ const res = await tools.blueprintTools.addComponent({ blueprintName: args.name, componentType: args.componentType, componentName: args.componentName });
286
+ return cleanObject(res);
287
+ }
555
288
  default:
556
289
  throw new Error(`Unknown blueprint action: ${args.action}`);
557
290
  }
558
- break;
559
291
  // 8. ENVIRONMENT BUILDER
560
292
  case 'build_environment':
561
293
  switch (args.action) {
562
- case 'create_landscape':
563
- mappedName = 'create_landscape';
564
- mappedArgs = {
565
- name: args.name,
294
+ case 'create_landscape': {
295
+ const res = await tools.landscapeTools.createLandscape({ name: args.name, sizeX: args.sizeX, sizeY: args.sizeY, materialPath: args.materialPath });
296
+ return cleanObject(res);
297
+ }
298
+ case 'sculpt': {
299
+ const res = await tools.landscapeTools.sculptLandscape({ landscapeName: args.name, tool: args.tool, brushSize: args.brushSize, strength: args.strength });
300
+ return cleanObject(res);
301
+ }
302
+ case 'add_foliage': {
303
+ const res = await tools.foliageTools.addFoliageType({ name: args.name, meshPath: args.meshPath, density: args.density });
304
+ return cleanObject(res);
305
+ }
306
+ case 'paint_foliage': {
307
+ const pos = args.position ? [args.position.x || 0, args.position.y || 0, args.position.z || 0] : [0, 0, 0];
308
+ const res = await tools.foliageTools.paintFoliage({ foliageType: args.foliageType, position: pos, brushSize: args.brushSize, paintDensity: args.paintDensity, eraseMode: args.eraseMode });
309
+ return cleanObject(res);
310
+ }
311
+ case 'create_procedural_terrain': {
312
+ const loc = args.location ? [args.location.x || 0, args.location.y || 0, args.location.z || 0] : [0, 0, 0];
313
+ const res = await tools.buildEnvAdvanced.createProceduralTerrain({
314
+ name: args.name || 'ProceduralTerrain',
315
+ location: loc,
566
316
  sizeX: args.sizeX,
567
317
  sizeY: args.sizeY,
568
- materialPath: args.materialPath
569
- };
570
- break;
571
- case 'sculpt':
572
- mappedName = 'sculpt_landscape';
573
- mappedArgs = {
574
- landscapeName: args.name,
575
- tool: args.tool,
576
- brushSize: args.brushSize,
577
- strength: args.strength
578
- };
579
- break;
580
- case 'add_foliage':
581
- // Validate foliage creation parameters to avoid bad console commands / engine warnings
582
- if (args.name === undefined || args.name === null || typeof args.name !== 'string' || args.name.trim() === '' || String(args.name).toLowerCase() === 'undefined' || String(args.name).toLowerCase() === 'any') {
583
- throw new Error(`Invalid foliage name: '${args.name}'`);
584
- }
585
- if (args.meshPath === undefined || args.meshPath === null || typeof args.meshPath !== 'string' || args.meshPath.trim() === '' || String(args.meshPath).toLowerCase() === 'undefined') {
586
- throw new Error(`Invalid meshPath: '${args.meshPath}'`);
587
- }
588
- if (args.density !== undefined) {
589
- if (typeof args.density !== 'number' || !isFinite(args.density) || args.density < 0) {
590
- throw new Error(`Invalid density: '${args.density}' (must be non-negative finite number)`);
591
- }
592
- }
593
- mappedName = 'add_foliage_type';
594
- mappedArgs = {
595
- name: args.name,
318
+ subdivisions: args.subdivisions,
319
+ heightFunction: args.heightFunction,
320
+ material: args.materialPath
321
+ });
322
+ return cleanObject(res);
323
+ }
324
+ case 'create_procedural_foliage': {
325
+ if (!args.bounds || !args.bounds.location || !args.bounds.size)
326
+ throw new Error('bounds.location and bounds.size are required');
327
+ const bounds = {
328
+ location: [args.bounds.location.x || 0, args.bounds.location.y || 0, args.bounds.location.z || 0],
329
+ size: [args.bounds.size.x || 1000, args.bounds.size.y || 1000, args.bounds.size.z || 100]
330
+ };
331
+ const res = await tools.buildEnvAdvanced.createProceduralFoliage({
332
+ name: args.name || 'ProceduralFoliage',
333
+ bounds,
334
+ foliageTypes: args.foliageTypes || [],
335
+ seed: args.seed
336
+ });
337
+ return cleanObject(res);
338
+ }
339
+ case 'add_foliage_instances': {
340
+ if (!args.foliageType)
341
+ throw new Error('foliageType is required');
342
+ if (!Array.isArray(args.transforms))
343
+ throw new Error('transforms array is required');
344
+ const transforms = args.transforms.map(t => ({
345
+ location: [t.location?.x || 0, t.location?.y || 0, t.location?.z || 0],
346
+ rotation: t.rotation ? [t.rotation.pitch || 0, t.rotation.yaw || 0, t.rotation.roll || 0] : undefined,
347
+ scale: t.scale ? [t.scale.x || 1, t.scale.y || 1, t.scale.z || 1] : undefined
348
+ }));
349
+ const res = await tools.buildEnvAdvanced.addFoliageInstances({ foliageType: args.foliageType, transforms });
350
+ return cleanObject(res);
351
+ }
352
+ case 'create_landscape_grass_type': {
353
+ const res = await tools.buildEnvAdvanced.createLandscapeGrassType({
354
+ name: args.name || 'GrassType',
596
355
  meshPath: args.meshPath,
597
- density: args.density
598
- };
599
- break;
600
- case 'paint_foliage':
601
- // Validate paint parameters
602
- if (args.foliageType === undefined || args.foliageType === null || typeof args.foliageType !== 'string' || args.foliageType.trim() === '' || String(args.foliageType).toLowerCase() === 'undefined' || String(args.foliageType).toLowerCase() === 'any') {
603
- throw new Error(`Invalid foliageType: '${args.foliageType}'`);
604
- }
605
- // Convert position object to array if needed
606
- let positionArray;
607
- if (args.position) {
608
- if (Array.isArray(args.position)) {
609
- positionArray = args.position;
610
- }
611
- else if (typeof args.position === 'object') {
612
- positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
613
- }
614
- else {
615
- positionArray = [0, 0, 0];
616
- }
617
- }
618
- else {
619
- positionArray = [0, 0, 0];
620
- }
621
- // Validate numbers in position
622
- if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
623
- throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
624
- }
625
- if (args.brushSize !== undefined) {
626
- if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
627
- throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
628
- }
629
- }
630
- mappedName = 'paint_foliage';
631
- mappedArgs = {
632
- foliageType: args.foliageType,
633
- position: positionArray,
634
- brushSize: args.brushSize
635
- };
636
- break;
356
+ density: args.density,
357
+ minScale: args.minScale,
358
+ maxScale: args.maxScale
359
+ });
360
+ return cleanObject(res);
361
+ }
637
362
  default:
638
363
  throw new Error(`Unknown environment action: ${args.action}`);
639
364
  }
640
- break;
641
365
  // 9. SYSTEM CONTROL
642
366
  case 'system_control':
643
- // Validate args is not null/undefined
644
- if (args === null || args === undefined) {
645
- throw new Error('Invalid arguments: null or undefined');
646
- }
647
- if (typeof args !== 'object') {
648
- throw new Error('Invalid arguments: must be an object');
649
- }
650
- // Validate action exists
651
- if (!args.action) {
367
+ if (!args.action)
652
368
  throw new Error('Missing required parameter: action');
653
- }
654
369
  switch (args.action) {
655
- case 'profile':
656
- // Validate profile type
657
- const validProfileTypes = ['CPU', 'GPU', 'Memory', 'RenderThread', 'GameThread', 'All'];
658
- if (!args.profileType || !validProfileTypes.includes(args.profileType)) {
659
- throw new Error(`Invalid profileType: '${args.profileType}'. Valid types: ${validProfileTypes.join(', ')}`);
660
- }
661
- mappedName = 'start_profiling';
662
- mappedArgs = {
663
- type: args.profileType,
664
- duration: args.duration
665
- };
666
- break;
667
- case 'show_fps':
668
- // Validate enabled is boolean
669
- if (args.enabled !== undefined && typeof args.enabled !== 'boolean') {
670
- throw new Error(`Invalid enabled: must be boolean, got ${typeof args.enabled}`);
671
- }
672
- mappedName = 'show_fps';
673
- mappedArgs = {
674
- enabled: args.enabled,
675
- verbose: args.verbose
676
- };
677
- break;
678
- case 'set_quality':
679
- // Validate category - normalize aliases and singular forms used by sg.*Quality
680
- const validCategories = ['ViewDistance', 'AntiAliasing', 'PostProcessing', 'PostProcess',
681
- 'Shadows', 'Shadow', 'GlobalIllumination', 'Reflections', 'Reflection', 'Textures', 'Texture',
682
- 'Effects', 'Foliage', 'Shading'];
683
- if (!args.category || !validCategories.includes(args.category)) {
684
- throw new Error(`Invalid category: '${args.category}'. Valid categories: ${validCategories.join(', ')}`);
685
- }
686
- // Validate level
687
- if (args.level === undefined || args.level === null) {
688
- throw new Error('Missing required parameter: level');
689
- }
690
- if (typeof args.level !== 'number' || !Number.isInteger(args.level) || args.level < 0 || args.level > 4) {
691
- throw new Error(`Invalid level: must be integer 0-4, got ${args.level}`);
692
- }
693
- // Normalize category to sg.<Base>Quality base (singular where needed)
694
- const map = {
695
- ViewDistance: 'ViewDistance',
696
- AntiAliasing: 'AntiAliasing',
697
- PostProcessing: 'PostProcess',
698
- PostProcess: 'PostProcess',
699
- Shadows: 'Shadow',
700
- Shadow: 'Shadow',
701
- GlobalIllumination: 'GlobalIllumination',
702
- Reflections: 'Reflection',
703
- Reflection: 'Reflection',
704
- Textures: 'Texture',
705
- Texture: 'Texture',
706
- Effects: 'Effects',
707
- Foliage: 'Foliage',
708
- Shading: 'Shading',
709
- };
710
- const categoryName = map[String(args.category)] || args.category;
711
- mappedName = 'set_scalability';
712
- mappedArgs = {
713
- category: categoryName,
714
- level: args.level
715
- };
716
- break;
717
- case 'play_sound':
718
- // Validate sound path
719
- if (!args.soundPath || typeof args.soundPath !== 'string') {
720
- throw new Error('Invalid soundPath: must be a non-empty string');
721
- }
722
- // Validate volume if provided
723
- if (args.volume !== undefined) {
724
- if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
725
- throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
726
- }
727
- }
728
- mappedName = 'play_sound';
729
- mappedArgs = {
730
- soundPath: args.soundPath,
731
- location: args.location,
732
- volume: args.volume,
733
- is3D: args.is3D
734
- };
735
- break;
736
- case 'create_widget':
737
- // Validate widget name
738
- if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
739
- throw new Error('Invalid widgetName: must be a non-empty string');
740
- }
741
- mappedName = 'create_widget';
742
- mappedArgs = {
743
- name: args.widgetName,
744
- type: args.widgetType,
745
- savePath: args.savePath
746
- };
747
- break;
748
- case 'show_widget':
749
- // Validate widget name
750
- if (!args.widgetName || typeof args.widgetName !== 'string') {
751
- throw new Error('Invalid widgetName: must be a non-empty string');
752
- }
753
- // Validate visible is boolean (default to true if not provided)
754
- const isVisible = args.visible !== undefined ? args.visible : true;
755
- if (typeof isVisible !== 'boolean') {
756
- throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
757
- }
758
- mappedName = 'show_widget';
759
- mappedArgs = {
760
- widgetName: args.widgetName,
761
- visible: isVisible
762
- };
763
- break;
764
- case 'screenshot':
765
- mappedName = 'take_screenshot';
766
- mappedArgs = { resolution: args.resolution };
767
- break;
768
- case 'engine_start':
769
- mappedName = 'launch_editor';
770
- mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
771
- break;
772
- case 'engine_quit':
773
- mappedName = 'quit_editor';
774
- mappedArgs = {};
775
- break;
370
+ case 'profile': {
371
+ const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
372
+ return cleanObject(res);
373
+ }
374
+ case 'show_fps': {
375
+ const res = await tools.performanceTools.showFPS({ enabled: !!args.enabled, verbose: !!args.verbose });
376
+ return cleanObject(res);
377
+ }
378
+ case 'set_quality': {
379
+ const res = await tools.performanceTools.setScalability({ category: args.category, level: args.level });
380
+ return cleanObject(res);
381
+ }
382
+ case 'play_sound': {
383
+ if (args.location && typeof args.location === 'object') {
384
+ const loc = [args.location.x || 0, args.location.y || 0, args.location.z || 0];
385
+ const res = await tools.audioTools.playSoundAtLocation({ soundPath: args.soundPath, location: loc, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
386
+ return cleanObject(res);
387
+ }
388
+ const res = await tools.audioTools.playSound2D({ soundPath: args.soundPath, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
389
+ return cleanObject(res);
390
+ }
391
+ case 'create_widget': {
392
+ const res = await tools.uiTools.createWidget({ name: args.widgetName, type: args.widgetType, savePath: args.savePath });
393
+ return cleanObject(res);
394
+ }
395
+ case 'show_widget': {
396
+ const res = await tools.uiTools.setWidgetVisibility({ widgetName: args.widgetName, visible: args.visible !== false });
397
+ return cleanObject(res);
398
+ }
399
+ case 'screenshot': {
400
+ const res = await tools.visualTools.takeScreenshot({ resolution: args.resolution });
401
+ return cleanObject(res);
402
+ }
403
+ case 'engine_start': {
404
+ const res = await tools.engineTools.launchEditor({ editorExe: args.editorExe, projectPath: args.projectPath });
405
+ return cleanObject(res);
406
+ }
407
+ case 'engine_quit': {
408
+ const res = await tools.engineTools.quitEditor();
409
+ return cleanObject(res);
410
+ }
776
411
  default:
777
412
  throw new Error(`Unknown system action: ${args.action}`);
778
413
  }
779
- break;
780
414
  // 10. CONSOLE COMMAND - handle validation here
781
415
  case 'console_command':
782
- // Handle empty/invalid commands gracefully
783
- if (args.command === undefined || args.command === null || args.command === '' || typeof args.command !== 'string') {
784
- return {
785
- content: [{
786
- type: 'text',
787
- text: 'Empty command ignored'
788
- }],
789
- isError: false,
790
- success: true,
791
- message: 'Empty command'
792
- };
416
+ if (!args.command || typeof args.command !== 'string' || args.command.trim() === '') {
417
+ return { success: true, message: 'Empty command' };
418
+ }
419
+ // Basic safety filter
420
+ const cmd = String(args.command).trim();
421
+ const blocked = [/\bquit\b/i, /\bexit\b/i, /debugcrash/i];
422
+ if (blocked.some(r => r.test(cmd))) {
423
+ return { success: false, error: 'Command blocked for safety' };
793
424
  }
794
- const cmdTrimmed = args.command.trim();
795
- if (cmdTrimmed.length === 0) {
796
- return {
797
- content: [{
798
- type: 'text',
799
- text: 'Empty command ignored'
800
- }],
801
- isError: false,
802
- success: true,
803
- message: 'Empty command'
804
- };
425
+ try {
426
+ const res = await tools.bridge.executeConsoleCommand(cmd);
427
+ return cleanObject({ success: true, command: cmd, result: res });
428
+ }
429
+ catch (e) {
430
+ return cleanObject({ success: false, command: cmd, error: e?.message || String(e) });
805
431
  }
806
- mappedName = 'console_command';
807
- mappedArgs = args;
808
- break;
809
432
  // 11. REMOTE CONTROL PRESETS - Direct implementation
810
433
  case 'manage_rc':
811
434
  if (!args.action)
@@ -1004,34 +627,21 @@ export async function handleConsolidatedToolCall(name, args, tools) {
1004
627
  if (!args.action)
1005
628
  throw new Error('Missing required parameter: action');
1006
629
  switch (args.action) {
1007
- case 'inspect_object':
1008
- mappedName = 'inspect_object';
1009
- mappedArgs = { objectPath: args.objectPath };
1010
- break;
1011
- case 'set_property':
1012
- mappedName = 'inspect_set_property';
1013
- mappedArgs = { objectPath: args.objectPath, propertyName: args.propertyName, value: args.value };
1014
- break;
630
+ case 'inspect_object': {
631
+ const res = await tools.introspectionTools.inspectObject({ objectPath: args.objectPath, detailed: args.detailed });
632
+ return cleanObject(res);
633
+ }
634
+ case 'set_property': {
635
+ const res = await tools.introspectionTools.setProperty({ objectPath: args.objectPath, propertyName: args.propertyName, value: args.value });
636
+ return cleanObject(res);
637
+ }
1015
638
  default:
1016
639
  throw new Error(`Unknown inspect action: ${args.action}`);
1017
640
  }
1018
- break;
1019
641
  default:
1020
642
  throw new Error(`Unknown consolidated tool: ${name}`);
1021
643
  }
1022
- // Call the original handler with mapped name and args with timeout
1023
- const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
1024
- const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
1025
- const timeoutPromise = new Promise((_, reject) => {
1026
- setTimeout(() => {
1027
- reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
1028
- }, TOOL_TIMEOUT);
1029
- });
1030
- const result = await Promise.race([toolPromise, timeoutPromise]);
1031
- const duration = Date.now() - startTime;
1032
- console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
1033
- // Clean the result to prevent circular reference errors
1034
- return cleanObject(result);
644
+ // All cases return (or throw) above; this is a type guard for exhaustiveness.
1035
645
  }
1036
646
  catch (err) {
1037
647
  const duration = Date.now() - startTime;