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