unreal-engine-mcp-server 0.3.0 → 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.
- package/.env.production +6 -1
- package/Dockerfile +11 -28
- package/README.md +1 -2
- package/dist/index.js +120 -54
- package/dist/resources/actors.js +71 -13
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +96 -72
- package/dist/resources/levels.js +2 -2
- package/dist/tools/assets.js +6 -2
- package/dist/tools/build_environment_advanced.js +46 -42
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +173 -8
- package/dist/tools/consolidated-tool-handlers.js +331 -718
- package/dist/tools/debug.js +4 -6
- package/dist/tools/rc.js +2 -2
- package/dist/tools/sequence.js +21 -2
- package/dist/unreal-bridge.d.ts +4 -1
- package/dist/unreal-bridge.js +211 -53
- package/dist/utils/http.js +4 -2
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +43 -15
- package/package.json +5 -5
- package/server.json +2 -2
- package/src/index.ts +120 -56
- package/src/resources/actors.ts +51 -13
- package/src/resources/assets.ts +97 -73
- package/src/resources/levels.ts +2 -2
- package/src/tools/assets.ts +6 -2
- package/src/tools/build_environment_advanced.ts +46 -42
- package/src/tools/consolidated-tool-definitions.ts +173 -8
- package/src/tools/consolidated-tool-handlers.ts +318 -747
- package/src/tools/debug.ts +4 -6
- package/src/tools/rc.ts +2 -2
- package/src/tools/sequence.ts +21 -2
- package/src/unreal-bridge.ts +163 -60
- package/src/utils/http.ts +7 -4
- package/src/utils/response-validator.ts +48 -19
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1007
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/src/tools/tool-definitions.ts +0 -1023
- package/src/tools/tool-handlers.ts +0 -973
|
@@ -1,16 +1,16 @@
|
|
|
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';
|
|
3
|
+
import { Logger } from '../utils/logger.js';
|
|
4
|
+
const log = new Logger('ConsolidatedToolHandler');
|
|
4
5
|
export async function handleConsolidatedToolCall(name, args, tools) {
|
|
5
6
|
const startTime = Date.now();
|
|
6
|
-
|
|
7
|
+
// Use scoped logger (stderr) to avoid polluting stdout JSON
|
|
8
|
+
log.debug(`Starting execution of ${name} at ${new Date().toISOString()}`);
|
|
7
9
|
try {
|
|
8
10
|
// Validate args is not null/undefined
|
|
9
11
|
if (args === null || args === undefined) {
|
|
10
12
|
throw new Error('Invalid arguments: null or undefined');
|
|
11
13
|
}
|
|
12
|
-
let mappedName;
|
|
13
|
-
let mappedArgs = { ...args };
|
|
14
14
|
switch (name) {
|
|
15
15
|
// 1. ASSET MANAGER
|
|
16
16
|
case 'manage_asset':
|
|
@@ -23,72 +23,33 @@ export async function handleConsolidatedToolCall(name, args, tools) {
|
|
|
23
23
|
throw new Error('Missing required parameter: action');
|
|
24
24
|
}
|
|
25
25
|
switch (args.action) {
|
|
26
|
-
case 'list':
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (typeof args.directory !== 'string') {
|
|
30
|
-
throw new Error('Invalid directory: must be a string');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
mappedName = 'list_assets';
|
|
34
|
-
mappedArgs = {
|
|
35
|
-
directory: args.directory
|
|
36
|
-
// recursive removed - always false internally
|
|
37
|
-
};
|
|
38
|
-
break;
|
|
39
|
-
case 'import':
|
|
40
|
-
// Validate required parameters
|
|
41
|
-
if (args.sourcePath === undefined || args.sourcePath === null) {
|
|
42
|
-
throw new Error('Missing required parameter: sourcePath');
|
|
43
|
-
}
|
|
44
|
-
if (typeof args.sourcePath !== 'string') {
|
|
45
|
-
throw new Error('Invalid sourcePath: must be a string');
|
|
46
|
-
}
|
|
47
|
-
if (args.sourcePath.trim() === '') {
|
|
48
|
-
throw new Error('Invalid sourcePath: cannot be empty');
|
|
49
|
-
}
|
|
50
|
-
if (args.destinationPath === undefined || args.destinationPath === null) {
|
|
51
|
-
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');
|
|
52
29
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
mappedName = 'import_asset';
|
|
60
|
-
mappedArgs = {
|
|
61
|
-
sourcePath: args.sourcePath,
|
|
62
|
-
destinationPath: args.destinationPath
|
|
63
|
-
};
|
|
64
|
-
break;
|
|
65
|
-
case 'create_material':
|
|
66
|
-
// Validate required parameters
|
|
67
|
-
if (args.name === undefined || args.name === null) {
|
|
68
|
-
throw new Error('Missing required parameter: name');
|
|
69
|
-
}
|
|
70
|
-
if (typeof args.name !== 'string') {
|
|
71
|
-
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');
|
|
72
36
|
}
|
|
73
|
-
if (args.
|
|
74
|
-
throw new Error('Invalid
|
|
37
|
+
if (typeof args.destinationPath !== 'string' || args.destinationPath.trim() === '') {
|
|
38
|
+
throw new Error('Invalid destinationPath');
|
|
75
39
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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');
|
|
81
46
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
path: args.path
|
|
86
|
-
};
|
|
87
|
-
break;
|
|
47
|
+
const res = await tools.materialTools.createMaterial(args.name, args.path || '/Game/Materials');
|
|
48
|
+
return cleanObject(res);
|
|
49
|
+
}
|
|
88
50
|
default:
|
|
89
51
|
throw new Error(`Unknown asset action: ${args.action}`);
|
|
90
52
|
}
|
|
91
|
-
break;
|
|
92
53
|
// 2. ACTOR CONTROL
|
|
93
54
|
case 'control_actor':
|
|
94
55
|
// Validate action exists
|
|
@@ -96,63 +57,41 @@ export async function handleConsolidatedToolCall(name, args, tools) {
|
|
|
96
57
|
throw new Error('Missing required parameter: action');
|
|
97
58
|
}
|
|
98
59
|
switch (args.action) {
|
|
99
|
-
case 'spawn':
|
|
100
|
-
|
|
101
|
-
if (!args.classPath) {
|
|
102
|
-
throw new Error('Missing required parameter: classPath');
|
|
103
|
-
}
|
|
104
|
-
if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
|
|
60
|
+
case 'spawn': {
|
|
61
|
+
if (!args.classPath || typeof args.classPath !== 'string' || args.classPath.trim() === '') {
|
|
105
62
|
throw new Error('Invalid classPath: must be a non-empty string');
|
|
106
63
|
}
|
|
107
|
-
|
|
108
|
-
mappedArgs = {
|
|
64
|
+
const res = await tools.actorTools.spawn({
|
|
109
65
|
classPath: args.classPath,
|
|
110
66
|
location: args.location,
|
|
111
67
|
rotation: args.rotation
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (!args.actorName) {
|
|
117
|
-
throw new Error('
|
|
118
|
-
}
|
|
119
|
-
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
120
|
-
throw new Error('Invalid actorName: must be a non-empty string');
|
|
121
|
-
}
|
|
122
|
-
mappedName = 'delete_actor';
|
|
123
|
-
mappedArgs = {
|
|
124
|
-
actorName: args.actorName
|
|
125
|
-
};
|
|
126
|
-
break;
|
|
127
|
-
case 'apply_force':
|
|
128
|
-
// Validate apply_force parameters
|
|
129
|
-
if (!args.actorName) {
|
|
130
|
-
throw new Error('Missing required parameter: actorName');
|
|
131
|
-
}
|
|
132
|
-
if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
|
|
133
|
-
throw new Error('Invalid actorName: must be a non-empty string');
|
|
134
|
-
}
|
|
135
|
-
if (!args.force) {
|
|
136
|
-
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');
|
|
137
74
|
}
|
|
138
|
-
|
|
139
|
-
|
|
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');
|
|
140
81
|
}
|
|
141
|
-
if (typeof args.force.x !== 'number' ||
|
|
142
|
-
|
|
143
|
-
typeof args.force.z !== 'number') {
|
|
144
|
-
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');
|
|
145
84
|
}
|
|
146
|
-
|
|
147
|
-
mappedArgs = {
|
|
85
|
+
const res = await tools.physicsTools.applyForce({
|
|
148
86
|
actorName: args.actorName,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
87
|
+
forceType: 'Force',
|
|
88
|
+
vector: [args.force.x, args.force.y, args.force.z]
|
|
89
|
+
});
|
|
90
|
+
return cleanObject(res);
|
|
91
|
+
}
|
|
152
92
|
default:
|
|
153
93
|
throw new Error(`Unknown actor action: ${args.action}`);
|
|
154
94
|
}
|
|
155
|
-
break;
|
|
156
95
|
// 3. EDITOR CONTROL
|
|
157
96
|
case 'control_editor':
|
|
158
97
|
// Validate action exists
|
|
@@ -160,210 +99,91 @@ export async function handleConsolidatedToolCall(name, args, tools) {
|
|
|
160
99
|
throw new Error('Missing required parameter: action');
|
|
161
100
|
}
|
|
162
101
|
switch (args.action) {
|
|
163
|
-
case 'play':
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
case 'stop':
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
case 'pause':
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
case 'set_game_speed':
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw new Error('Missing required parameter: speed');
|
|
179
|
-
}
|
|
180
|
-
if (typeof args.speed !== 'number') {
|
|
181
|
-
throw new Error('Invalid speed: must be a number');
|
|
182
|
-
}
|
|
183
|
-
if (isNaN(args.speed)) {
|
|
184
|
-
throw new Error('Invalid speed: cannot be NaN');
|
|
185
|
-
}
|
|
186
|
-
if (!isFinite(args.speed)) {
|
|
187
|
-
throw new Error('Invalid speed: must be finite');
|
|
188
|
-
}
|
|
189
|
-
if (args.speed <= 0) {
|
|
190
|
-
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');
|
|
191
117
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
location: args.location,
|
|
211
|
-
rotation: args.rotation
|
|
212
|
-
};
|
|
213
|
-
break;
|
|
214
|
-
case 'set_view_mode':
|
|
215
|
-
// Validate view mode parameter
|
|
216
|
-
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')
|
|
217
136
|
throw new Error('Missing required parameter: viewMode');
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
// Normalize view mode to match what debug.ts expects
|
|
223
|
-
const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
|
|
224
|
-
'light_complexity', 'shader_complexity', 'lightmap_density',
|
|
225
|
-
'stationary_light_overlap', 'reflections', 'visualize_buffer',
|
|
226
|
-
'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
|
|
227
|
-
const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
|
|
228
|
-
// Map to proper case for debug.ts
|
|
229
|
-
let mappedMode = '';
|
|
230
|
-
switch (normalizedMode) {
|
|
231
|
-
case 'lit':
|
|
232
|
-
mappedMode = 'Lit';
|
|
233
|
-
break;
|
|
234
|
-
case 'unlit':
|
|
235
|
-
mappedMode = 'Unlit';
|
|
236
|
-
break;
|
|
237
|
-
case 'wireframe':
|
|
238
|
-
mappedMode = 'Wireframe';
|
|
239
|
-
break;
|
|
240
|
-
case 'detaillighting':
|
|
241
|
-
mappedMode = 'DetailLighting';
|
|
242
|
-
break;
|
|
243
|
-
case 'lightingonly':
|
|
244
|
-
mappedMode = 'LightingOnly';
|
|
245
|
-
break;
|
|
246
|
-
case 'lightcomplexity':
|
|
247
|
-
mappedMode = 'LightComplexity';
|
|
248
|
-
break;
|
|
249
|
-
case 'shadercomplexity':
|
|
250
|
-
mappedMode = 'ShaderComplexity';
|
|
251
|
-
break;
|
|
252
|
-
case 'lightmapdensity':
|
|
253
|
-
mappedMode = 'LightmapDensity';
|
|
254
|
-
break;
|
|
255
|
-
case 'stationarylightoverlap':
|
|
256
|
-
mappedMode = 'StationaryLightOverlap';
|
|
257
|
-
break;
|
|
258
|
-
case 'reflections':
|
|
259
|
-
mappedMode = 'ReflectionOverride';
|
|
260
|
-
break;
|
|
261
|
-
case 'visualizebuffer':
|
|
262
|
-
mappedMode = 'VisualizeBuffer';
|
|
263
|
-
break;
|
|
264
|
-
case 'collisionpawn':
|
|
265
|
-
mappedMode = 'CollisionPawn';
|
|
266
|
-
break;
|
|
267
|
-
case 'collisionvisibility':
|
|
268
|
-
mappedMode = 'CollisionVisibility';
|
|
269
|
-
break;
|
|
270
|
-
case 'lodcoloration':
|
|
271
|
-
mappedMode = 'LODColoration';
|
|
272
|
-
break;
|
|
273
|
-
case 'quadoverdraw':
|
|
274
|
-
mappedMode = 'QuadOverdraw';
|
|
275
|
-
break;
|
|
276
|
-
default:
|
|
277
|
-
throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
|
|
278
|
-
}
|
|
279
|
-
mappedName = 'set_view_mode';
|
|
280
|
-
mappedArgs = {
|
|
281
|
-
mode: mappedMode
|
|
282
|
-
};
|
|
283
|
-
break;
|
|
137
|
+
const res = await tools.bridge.setSafeViewMode(args.viewMode);
|
|
138
|
+
return cleanObject(res);
|
|
139
|
+
}
|
|
284
140
|
default:
|
|
285
141
|
throw new Error(`Unknown editor action: ${args.action}`);
|
|
286
142
|
}
|
|
287
|
-
break;
|
|
288
143
|
// 4. LEVEL MANAGER
|
|
289
144
|
case 'manage_level':
|
|
145
|
+
if (!args.action)
|
|
146
|
+
throw new Error('Missing required parameter: action');
|
|
290
147
|
switch (args.action) {
|
|
291
|
-
case 'load':
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
case 'stream':
|
|
310
|
-
mappedName = 'stream_level';
|
|
311
|
-
mappedArgs = {
|
|
312
|
-
levelName: args.levelName,
|
|
313
|
-
shouldBeLoaded: args.shouldBeLoaded,
|
|
314
|
-
shouldBeVisible: args.shouldBeVisible
|
|
315
|
-
};
|
|
316
|
-
break;
|
|
317
|
-
case 'create_light':
|
|
318
|
-
// Validate light type
|
|
319
|
-
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)
|
|
320
166
|
throw new Error('Missing required parameter: lightType');
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
if (args.intensity < 0) {
|
|
340
|
-
throw new Error('Invalid intensity: must be non-negative');
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
// Validate location if provided
|
|
344
|
-
if (args.location !== undefined && args.location !== null) {
|
|
345
|
-
if (!Array.isArray(args.location) && typeof args.location !== 'object') {
|
|
346
|
-
throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
mappedName = 'create_light';
|
|
350
|
-
mappedArgs = {
|
|
351
|
-
lightType: args.lightType,
|
|
352
|
-
name: args.name,
|
|
353
|
-
location: args.location,
|
|
354
|
-
intensity: args.intensity
|
|
355
|
-
};
|
|
356
|
-
break;
|
|
357
|
-
case 'build_lighting':
|
|
358
|
-
mappedName = 'build_lighting';
|
|
359
|
-
mappedArgs = {
|
|
360
|
-
quality: args.quality
|
|
361
|
-
};
|
|
362
|
-
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
|
+
}
|
|
363
184
|
default:
|
|
364
185
|
throw new Error(`Unknown level action: ${args.action}`);
|
|
365
186
|
}
|
|
366
|
-
break;
|
|
367
187
|
// 5. ANIMATION & PHYSICS
|
|
368
188
|
case 'animation_physics':
|
|
369
189
|
// Validate action exists
|
|
@@ -371,438 +191,244 @@ export async function handleConsolidatedToolCall(name, args, tools) {
|
|
|
371
191
|
throw new Error('Missing required parameter: action');
|
|
372
192
|
}
|
|
373
193
|
switch (args.action) {
|
|
374
|
-
case 'create_animation_bp':
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
386
|
-
throw new Error('Missing required parameter: skeletonPath');
|
|
387
|
-
}
|
|
388
|
-
if (typeof args.skeletonPath !== 'string') {
|
|
389
|
-
throw new Error('Invalid skeletonPath: must be a string');
|
|
390
|
-
}
|
|
391
|
-
if (args.skeletonPath.trim() === '') {
|
|
392
|
-
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
393
|
-
}
|
|
394
|
-
// Optional savePath validation
|
|
395
|
-
if (args.savePath !== undefined && args.savePath !== null) {
|
|
396
|
-
if (typeof args.savePath !== 'string') {
|
|
397
|
-
throw new Error('Invalid savePath: must be a string');
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
mappedName = 'create_animation_blueprint';
|
|
401
|
-
mappedArgs = {
|
|
402
|
-
name: args.name,
|
|
403
|
-
skeletonPath: args.skeletonPath,
|
|
404
|
-
savePath: args.savePath
|
|
405
|
-
};
|
|
406
|
-
break;
|
|
407
|
-
case 'play_montage':
|
|
408
|
-
// Validate required parameters
|
|
409
|
-
if (args.actorName === undefined || args.actorName === null) {
|
|
410
|
-
throw new Error('Missing required parameter: actorName');
|
|
411
|
-
}
|
|
412
|
-
if (typeof args.actorName !== 'string') {
|
|
413
|
-
throw new Error('Invalid actorName: must be a string');
|
|
414
|
-
}
|
|
415
|
-
if (args.actorName.trim() === '') {
|
|
416
|
-
throw new Error('Invalid actorName: cannot be empty');
|
|
417
|
-
}
|
|
418
|
-
// 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');
|
|
419
205
|
const montagePath = args.montagePath || args.animationPath;
|
|
420
|
-
if (montagePath
|
|
421
|
-
throw new Error('
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
throw new Error('Invalid
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
if (isNaN(args.playRate)) {
|
|
435
|
-
throw new Error('Invalid playRate: cannot be NaN');
|
|
436
|
-
}
|
|
437
|
-
if (!isFinite(args.playRate)) {
|
|
438
|
-
throw new Error('Invalid playRate: must be finite');
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
mappedName = 'play_animation_montage';
|
|
442
|
-
mappedArgs = {
|
|
443
|
-
actorName: args.actorName,
|
|
444
|
-
montagePath: montagePath,
|
|
445
|
-
playRate: args.playRate
|
|
446
|
-
};
|
|
447
|
-
break;
|
|
448
|
-
case 'setup_ragdoll':
|
|
449
|
-
// Validate required parameters
|
|
450
|
-
if (args.skeletonPath === undefined || args.skeletonPath === null) {
|
|
451
|
-
throw new Error('Missing required parameter: skeletonPath');
|
|
452
|
-
}
|
|
453
|
-
if (typeof args.skeletonPath !== 'string') {
|
|
454
|
-
throw new Error('Invalid skeletonPath: must be a string');
|
|
455
|
-
}
|
|
456
|
-
if (args.skeletonPath.trim() === '') {
|
|
457
|
-
throw new Error('Invalid skeletonPath: cannot be empty');
|
|
458
|
-
}
|
|
459
|
-
if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
|
|
460
|
-
throw new Error('Missing required parameter: physicsAssetName');
|
|
461
|
-
}
|
|
462
|
-
if (typeof args.physicsAssetName !== 'string') {
|
|
463
|
-
throw new Error('Invalid physicsAssetName: must be a string');
|
|
464
|
-
}
|
|
465
|
-
if (args.physicsAssetName.trim() === '') {
|
|
466
|
-
throw new Error('Invalid physicsAssetName: cannot be empty');
|
|
467
|
-
}
|
|
468
|
-
// Optional blendWeight validation
|
|
469
|
-
if (args.blendWeight !== undefined && args.blendWeight !== null) {
|
|
470
|
-
if (typeof args.blendWeight !== 'number') {
|
|
471
|
-
throw new Error('Invalid blendWeight: must be a number');
|
|
472
|
-
}
|
|
473
|
-
if (isNaN(args.blendWeight)) {
|
|
474
|
-
throw new Error('Invalid blendWeight: cannot be NaN');
|
|
475
|
-
}
|
|
476
|
-
if (!isFinite(args.blendWeight)) {
|
|
477
|
-
throw new Error('Invalid blendWeight: must be finite');
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
// Optional savePath validation
|
|
481
|
-
if (args.savePath !== undefined && args.savePath !== null) {
|
|
482
|
-
if (typeof args.savePath !== 'string') {
|
|
483
|
-
throw new Error('Invalid savePath: must be a string');
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
mappedName = 'setup_ragdoll';
|
|
487
|
-
mappedArgs = {
|
|
488
|
-
skeletonPath: args.skeletonPath,
|
|
489
|
-
physicsAssetName: args.physicsAssetName,
|
|
490
|
-
blendWeight: args.blendWeight,
|
|
491
|
-
savePath: args.savePath
|
|
492
|
-
};
|
|
493
|
-
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
|
+
}
|
|
494
219
|
default:
|
|
495
220
|
throw new Error(`Unknown animation/physics action: ${args.action}`);
|
|
496
221
|
}
|
|
497
|
-
break;
|
|
498
222
|
// 6. EFFECTS SYSTEM
|
|
499
223
|
case 'create_effect':
|
|
500
224
|
switch (args.action) {
|
|
501
|
-
case 'particle':
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
size
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
}
|
|
529
274
|
default:
|
|
530
275
|
throw new Error(`Unknown effect action: ${args.action}`);
|
|
531
276
|
}
|
|
532
|
-
break;
|
|
533
277
|
// 7. BLUEPRINT MANAGER
|
|
534
278
|
case 'manage_blueprint':
|
|
535
279
|
switch (args.action) {
|
|
536
|
-
case 'create':
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
case 'add_component':
|
|
545
|
-
mappedName = 'add_blueprint_component';
|
|
546
|
-
mappedArgs = {
|
|
547
|
-
blueprintName: args.name,
|
|
548
|
-
componentType: args.componentType,
|
|
549
|
-
componentName: args.componentName
|
|
550
|
-
};
|
|
551
|
-
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
|
+
}
|
|
552
288
|
default:
|
|
553
289
|
throw new Error(`Unknown blueprint action: ${args.action}`);
|
|
554
290
|
}
|
|
555
|
-
break;
|
|
556
291
|
// 8. ENVIRONMENT BUILDER
|
|
557
292
|
case 'build_environment':
|
|
558
293
|
switch (args.action) {
|
|
559
|
-
case 'create_landscape':
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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,
|
|
563
316
|
sizeX: args.sizeX,
|
|
564
317
|
sizeY: args.sizeY,
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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',
|
|
593
355
|
meshPath: args.meshPath,
|
|
594
|
-
density: args.density
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
throw new Error(`Invalid foliageType: '${args.foliageType}'`);
|
|
601
|
-
}
|
|
602
|
-
// Convert position object to array if needed
|
|
603
|
-
let positionArray;
|
|
604
|
-
if (args.position) {
|
|
605
|
-
if (Array.isArray(args.position)) {
|
|
606
|
-
positionArray = args.position;
|
|
607
|
-
}
|
|
608
|
-
else if (typeof args.position === 'object') {
|
|
609
|
-
positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
positionArray = [0, 0, 0];
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
positionArray = [0, 0, 0];
|
|
617
|
-
}
|
|
618
|
-
// Validate numbers in position
|
|
619
|
-
if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
|
|
620
|
-
throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
|
|
621
|
-
}
|
|
622
|
-
if (args.brushSize !== undefined) {
|
|
623
|
-
if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
|
|
624
|
-
throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
mappedName = 'paint_foliage';
|
|
628
|
-
mappedArgs = {
|
|
629
|
-
foliageType: args.foliageType,
|
|
630
|
-
position: positionArray,
|
|
631
|
-
brushSize: args.brushSize
|
|
632
|
-
};
|
|
633
|
-
break;
|
|
356
|
+
density: args.density,
|
|
357
|
+
minScale: args.minScale,
|
|
358
|
+
maxScale: args.maxScale
|
|
359
|
+
});
|
|
360
|
+
return cleanObject(res);
|
|
361
|
+
}
|
|
634
362
|
default:
|
|
635
363
|
throw new Error(`Unknown environment action: ${args.action}`);
|
|
636
364
|
}
|
|
637
|
-
break;
|
|
638
365
|
// 9. SYSTEM CONTROL
|
|
639
366
|
case 'system_control':
|
|
640
|
-
|
|
641
|
-
if (args === null || args === undefined) {
|
|
642
|
-
throw new Error('Invalid arguments: null or undefined');
|
|
643
|
-
}
|
|
644
|
-
if (typeof args !== 'object') {
|
|
645
|
-
throw new Error('Invalid arguments: must be an object');
|
|
646
|
-
}
|
|
647
|
-
// Validate action exists
|
|
648
|
-
if (!args.action) {
|
|
367
|
+
if (!args.action)
|
|
649
368
|
throw new Error('Missing required parameter: action');
|
|
650
|
-
}
|
|
651
369
|
switch (args.action) {
|
|
652
|
-
case 'profile':
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
case '
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
AntiAliasing: 'AntiAliasing',
|
|
694
|
-
PostProcessing: 'PostProcess',
|
|
695
|
-
PostProcess: 'PostProcess',
|
|
696
|
-
Shadows: 'Shadow',
|
|
697
|
-
Shadow: 'Shadow',
|
|
698
|
-
GlobalIllumination: 'GlobalIllumination',
|
|
699
|
-
Reflections: 'Reflection',
|
|
700
|
-
Reflection: 'Reflection',
|
|
701
|
-
Textures: 'Texture',
|
|
702
|
-
Texture: 'Texture',
|
|
703
|
-
Effects: 'Effects',
|
|
704
|
-
Foliage: 'Foliage',
|
|
705
|
-
Shading: 'Shading',
|
|
706
|
-
};
|
|
707
|
-
const categoryName = map[String(args.category)] || args.category;
|
|
708
|
-
mappedName = 'set_scalability';
|
|
709
|
-
mappedArgs = {
|
|
710
|
-
category: categoryName,
|
|
711
|
-
level: args.level
|
|
712
|
-
};
|
|
713
|
-
break;
|
|
714
|
-
case 'play_sound':
|
|
715
|
-
// Validate sound path
|
|
716
|
-
if (!args.soundPath || typeof args.soundPath !== 'string') {
|
|
717
|
-
throw new Error('Invalid soundPath: must be a non-empty string');
|
|
718
|
-
}
|
|
719
|
-
// Validate volume if provided
|
|
720
|
-
if (args.volume !== undefined) {
|
|
721
|
-
if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
|
|
722
|
-
throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
mappedName = 'play_sound';
|
|
726
|
-
mappedArgs = {
|
|
727
|
-
soundPath: args.soundPath,
|
|
728
|
-
location: args.location,
|
|
729
|
-
volume: args.volume,
|
|
730
|
-
is3D: args.is3D
|
|
731
|
-
};
|
|
732
|
-
break;
|
|
733
|
-
case 'create_widget':
|
|
734
|
-
// Validate widget name
|
|
735
|
-
if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
|
|
736
|
-
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
737
|
-
}
|
|
738
|
-
mappedName = 'create_widget';
|
|
739
|
-
mappedArgs = {
|
|
740
|
-
name: args.widgetName,
|
|
741
|
-
type: args.widgetType,
|
|
742
|
-
savePath: args.savePath
|
|
743
|
-
};
|
|
744
|
-
break;
|
|
745
|
-
case 'show_widget':
|
|
746
|
-
// Validate widget name
|
|
747
|
-
if (!args.widgetName || typeof args.widgetName !== 'string') {
|
|
748
|
-
throw new Error('Invalid widgetName: must be a non-empty string');
|
|
749
|
-
}
|
|
750
|
-
// Validate visible is boolean (default to true if not provided)
|
|
751
|
-
const isVisible = args.visible !== undefined ? args.visible : true;
|
|
752
|
-
if (typeof isVisible !== 'boolean') {
|
|
753
|
-
throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
|
|
754
|
-
}
|
|
755
|
-
mappedName = 'show_widget';
|
|
756
|
-
mappedArgs = {
|
|
757
|
-
widgetName: args.widgetName,
|
|
758
|
-
visible: isVisible
|
|
759
|
-
};
|
|
760
|
-
break;
|
|
761
|
-
case 'screenshot':
|
|
762
|
-
mappedName = 'take_screenshot';
|
|
763
|
-
mappedArgs = { resolution: args.resolution };
|
|
764
|
-
break;
|
|
765
|
-
case 'engine_start':
|
|
766
|
-
mappedName = 'launch_editor';
|
|
767
|
-
mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
|
|
768
|
-
break;
|
|
769
|
-
case 'engine_quit':
|
|
770
|
-
mappedName = 'quit_editor';
|
|
771
|
-
mappedArgs = {};
|
|
772
|
-
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
|
+
}
|
|
773
411
|
default:
|
|
774
412
|
throw new Error(`Unknown system action: ${args.action}`);
|
|
775
413
|
}
|
|
776
|
-
break;
|
|
777
414
|
// 10. CONSOLE COMMAND - handle validation here
|
|
778
415
|
case 'console_command':
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
success: true,
|
|
788
|
-
message: 'Empty command'
|
|
789
|
-
};
|
|
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' };
|
|
790
424
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
}],
|
|
798
|
-
isError: false,
|
|
799
|
-
success: true,
|
|
800
|
-
message: 'Empty command'
|
|
801
|
-
};
|
|
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) });
|
|
802
431
|
}
|
|
803
|
-
mappedName = 'console_command';
|
|
804
|
-
mappedArgs = args;
|
|
805
|
-
break;
|
|
806
432
|
// 11. REMOTE CONTROL PRESETS - Direct implementation
|
|
807
433
|
case 'manage_rc':
|
|
808
434
|
if (!args.action)
|
|
@@ -1001,34 +627,21 @@ export async function handleConsolidatedToolCall(name, args, tools) {
|
|
|
1001
627
|
if (!args.action)
|
|
1002
628
|
throw new Error('Missing required parameter: action');
|
|
1003
629
|
switch (args.action) {
|
|
1004
|
-
case 'inspect_object':
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
case 'set_property':
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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
|
+
}
|
|
1012
638
|
default:
|
|
1013
639
|
throw new Error(`Unknown inspect action: ${args.action}`);
|
|
1014
640
|
}
|
|
1015
|
-
break;
|
|
1016
641
|
default:
|
|
1017
642
|
throw new Error(`Unknown consolidated tool: ${name}`);
|
|
1018
643
|
}
|
|
1019
|
-
//
|
|
1020
|
-
const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
|
|
1021
|
-
const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
|
|
1022
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
1023
|
-
setTimeout(() => {
|
|
1024
|
-
reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
|
|
1025
|
-
}, TOOL_TIMEOUT);
|
|
1026
|
-
});
|
|
1027
|
-
const result = await Promise.race([toolPromise, timeoutPromise]);
|
|
1028
|
-
const duration = Date.now() - startTime;
|
|
1029
|
-
console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
|
|
1030
|
-
// Clean the result to prevent circular reference errors
|
|
1031
|
-
return cleanObject(result);
|
|
644
|
+
// All cases return (or throw) above; this is a type guard for exhaustiveness.
|
|
1032
645
|
}
|
|
1033
646
|
catch (err) {
|
|
1034
647
|
const duration = Date.now() - startTime;
|