unreal-engine-mcp-server 0.4.0 → 0.4.4

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.
Files changed (135) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +3 -2
  4. package/README.md +21 -5
  5. package/dist/index.js +124 -31
  6. package/dist/prompts/index.d.ts +10 -3
  7. package/dist/prompts/index.js +186 -7
  8. package/dist/resources/actors.d.ts +19 -1
  9. package/dist/resources/actors.js +55 -64
  10. package/dist/resources/assets.js +46 -62
  11. package/dist/resources/levels.d.ts +21 -3
  12. package/dist/resources/levels.js +29 -54
  13. package/dist/tools/actors.d.ts +3 -14
  14. package/dist/tools/actors.js +246 -302
  15. package/dist/tools/animation.d.ts +57 -102
  16. package/dist/tools/animation.js +429 -450
  17. package/dist/tools/assets.d.ts +13 -2
  18. package/dist/tools/assets.js +52 -44
  19. package/dist/tools/audio.d.ts +22 -13
  20. package/dist/tools/audio.js +467 -121
  21. package/dist/tools/blueprint.d.ts +32 -13
  22. package/dist/tools/blueprint.js +699 -448
  23. package/dist/tools/build_environment_advanced.d.ts +0 -1
  24. package/dist/tools/build_environment_advanced.js +190 -45
  25. package/dist/tools/consolidated-tool-definitions.js +78 -252
  26. package/dist/tools/consolidated-tool-handlers.js +506 -133
  27. package/dist/tools/debug.d.ts +72 -10
  28. package/dist/tools/debug.js +167 -31
  29. package/dist/tools/editor.d.ts +9 -2
  30. package/dist/tools/editor.js +30 -44
  31. package/dist/tools/foliage.d.ts +34 -15
  32. package/dist/tools/foliage.js +97 -107
  33. package/dist/tools/introspection.js +19 -21
  34. package/dist/tools/landscape.d.ts +1 -2
  35. package/dist/tools/landscape.js +311 -168
  36. package/dist/tools/level.d.ts +3 -28
  37. package/dist/tools/level.js +642 -192
  38. package/dist/tools/lighting.d.ts +14 -3
  39. package/dist/tools/lighting.js +236 -123
  40. package/dist/tools/materials.d.ts +25 -7
  41. package/dist/tools/materials.js +102 -79
  42. package/dist/tools/niagara.d.ts +10 -12
  43. package/dist/tools/niagara.js +74 -94
  44. package/dist/tools/performance.d.ts +12 -4
  45. package/dist/tools/performance.js +38 -79
  46. package/dist/tools/physics.d.ts +34 -10
  47. package/dist/tools/physics.js +364 -292
  48. package/dist/tools/rc.js +97 -23
  49. package/dist/tools/sequence.d.ts +1 -0
  50. package/dist/tools/sequence.js +125 -22
  51. package/dist/tools/ui.d.ts +31 -4
  52. package/dist/tools/ui.js +83 -66
  53. package/dist/tools/visual.d.ts +11 -0
  54. package/dist/tools/visual.js +245 -30
  55. package/dist/types/tool-types.d.ts +0 -6
  56. package/dist/types/tool-types.js +1 -8
  57. package/dist/unreal-bridge.d.ts +32 -2
  58. package/dist/unreal-bridge.js +621 -127
  59. package/dist/utils/elicitation.d.ts +57 -0
  60. package/dist/utils/elicitation.js +104 -0
  61. package/dist/utils/error-handler.d.ts +0 -33
  62. package/dist/utils/error-handler.js +4 -111
  63. package/dist/utils/http.d.ts +2 -22
  64. package/dist/utils/http.js +12 -75
  65. package/dist/utils/normalize.d.ts +4 -4
  66. package/dist/utils/normalize.js +15 -7
  67. package/dist/utils/python-output.d.ts +18 -0
  68. package/dist/utils/python-output.js +290 -0
  69. package/dist/utils/python.d.ts +2 -0
  70. package/dist/utils/python.js +4 -0
  71. package/dist/utils/response-validator.js +28 -2
  72. package/dist/utils/result-helpers.d.ts +27 -0
  73. package/dist/utils/result-helpers.js +147 -0
  74. package/dist/utils/safe-json.d.ts +0 -2
  75. package/dist/utils/safe-json.js +0 -43
  76. package/dist/utils/validation.d.ts +16 -0
  77. package/dist/utils/validation.js +70 -7
  78. package/mcp-config-example.json +2 -2
  79. package/package.json +10 -9
  80. package/server.json +37 -14
  81. package/src/index.ts +130 -33
  82. package/src/prompts/index.ts +211 -13
  83. package/src/resources/actors.ts +59 -44
  84. package/src/resources/assets.ts +48 -51
  85. package/src/resources/levels.ts +35 -45
  86. package/src/tools/actors.ts +269 -313
  87. package/src/tools/animation.ts +556 -539
  88. package/src/tools/assets.ts +53 -43
  89. package/src/tools/audio.ts +507 -113
  90. package/src/tools/blueprint.ts +778 -462
  91. package/src/tools/build_environment_advanced.ts +266 -64
  92. package/src/tools/consolidated-tool-definitions.ts +90 -264
  93. package/src/tools/consolidated-tool-handlers.ts +630 -121
  94. package/src/tools/debug.ts +176 -33
  95. package/src/tools/editor.ts +35 -37
  96. package/src/tools/foliage.ts +110 -104
  97. package/src/tools/introspection.ts +24 -22
  98. package/src/tools/landscape.ts +334 -181
  99. package/src/tools/level.ts +683 -182
  100. package/src/tools/lighting.ts +244 -123
  101. package/src/tools/materials.ts +114 -83
  102. package/src/tools/niagara.ts +87 -81
  103. package/src/tools/performance.ts +49 -88
  104. package/src/tools/physics.ts +393 -299
  105. package/src/tools/rc.ts +102 -24
  106. package/src/tools/sequence.ts +136 -28
  107. package/src/tools/ui.ts +101 -70
  108. package/src/tools/visual.ts +250 -29
  109. package/src/types/tool-types.ts +0 -9
  110. package/src/unreal-bridge.ts +658 -140
  111. package/src/utils/elicitation.ts +129 -0
  112. package/src/utils/error-handler.ts +4 -159
  113. package/src/utils/http.ts +16 -115
  114. package/src/utils/normalize.ts +20 -10
  115. package/src/utils/python-output.ts +351 -0
  116. package/src/utils/python.ts +3 -0
  117. package/src/utils/response-validator.ts +25 -2
  118. package/src/utils/result-helpers.ts +193 -0
  119. package/src/utils/safe-json.ts +0 -50
  120. package/src/utils/validation.ts +94 -7
  121. package/tests/run-unreal-tool-tests.mjs +720 -0
  122. package/tsconfig.json +2 -2
  123. package/dist/python-utils.d.ts +0 -29
  124. package/dist/python-utils.js +0 -54
  125. package/dist/types/index.d.ts +0 -323
  126. package/dist/types/index.js +0 -28
  127. package/dist/utils/cache-manager.d.ts +0 -64
  128. package/dist/utils/cache-manager.js +0 -176
  129. package/dist/utils/errors.d.ts +0 -133
  130. package/dist/utils/errors.js +0 -256
  131. package/src/python/editor_compat.py +0 -181
  132. package/src/python-utils.ts +0 -57
  133. package/src/types/index.ts +0 -414
  134. package/src/utils/cache-manager.ts +0 -213
  135. package/src/utils/errors.ts +0 -312
@@ -0,0 +1,290 @@
1
+ import JSON5 from 'json5';
2
+ function escapeRegExp(value) {
3
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
4
+ }
5
+ function stringifyAny(value) {
6
+ if (typeof value === 'string') {
7
+ return value;
8
+ }
9
+ try {
10
+ return JSON.stringify(value ?? '');
11
+ }
12
+ catch {
13
+ return String(value);
14
+ }
15
+ }
16
+ function isTaggedResultLine(line, tag = 'RESULT:') {
17
+ if (typeof line !== 'string') {
18
+ return false;
19
+ }
20
+ return line.includes(tag);
21
+ }
22
+ function collectLogOutput(logs) {
23
+ if (!Array.isArray(logs))
24
+ return undefined;
25
+ let buffer = '';
26
+ for (const entry of logs) {
27
+ if (!entry)
28
+ continue;
29
+ if (typeof entry === 'string') {
30
+ buffer += entry.endsWith('\n') ? entry : `${entry}\n`;
31
+ continue;
32
+ }
33
+ const output = entry.Output ?? entry.output ?? entry.Message;
34
+ if (typeof output === 'string') {
35
+ buffer += output.endsWith('\n') ? output : `${output}\n`;
36
+ }
37
+ }
38
+ return buffer || undefined;
39
+ }
40
+ export function toPythonOutput(response) {
41
+ if (typeof response === 'string') {
42
+ return { raw: response, text: response };
43
+ }
44
+ if (response == null) {
45
+ return { raw: response, text: '' };
46
+ }
47
+ if (Array.isArray(response)) {
48
+ const lines = response.map(item => stringifyAny(item));
49
+ return { raw: response, text: lines.join('\n') };
50
+ }
51
+ if (typeof response === 'object') {
52
+ const obj = response;
53
+ const logOutput = collectLogOutput(obj.LogOutput ?? obj.logOutput ?? obj.logs);
54
+ if (logOutput) {
55
+ return { raw: response, text: logOutput };
56
+ }
57
+ if (typeof obj.result === 'string') {
58
+ return { raw: response, text: obj.result };
59
+ }
60
+ if (typeof obj.ReturnValue === 'string') {
61
+ return { raw: response, text: obj.ReturnValue };
62
+ }
63
+ if (obj.result !== undefined) {
64
+ return { raw: response, text: stringifyAny(obj.result) };
65
+ }
66
+ if (obj.ReturnValue !== undefined) {
67
+ return { raw: response, text: stringifyAny(obj.ReturnValue) };
68
+ }
69
+ return { raw: response, text: stringifyAny(obj) };
70
+ }
71
+ return { raw: response, text: String(response) };
72
+ }
73
+ function tryParseJson(candidate) {
74
+ try {
75
+ return JSON.parse(candidate);
76
+ }
77
+ catch {
78
+ try {
79
+ return JSON5.parse(candidate);
80
+ }
81
+ catch {
82
+ try {
83
+ const normalized = normalizeLegacyJson(candidate);
84
+ return JSON5.parse(normalized);
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ }
91
+ }
92
+ function normalizeLegacyJson(candidate) {
93
+ return candidate.replace(/\b(True|False|None)\b/g, (match) => {
94
+ switch (match) {
95
+ case 'True': return 'true';
96
+ case 'False': return 'false';
97
+ case 'None': return 'null';
98
+ default: return match;
99
+ }
100
+ });
101
+ }
102
+ function findJsonBlock(text, startIndex) {
103
+ const length = text.length;
104
+ let index = startIndex;
105
+ while (index < length) {
106
+ const currentChar = text[index];
107
+ if (!currentChar || !/\s/.test(currentChar)) {
108
+ break;
109
+ }
110
+ index += 1;
111
+ }
112
+ if (index >= length) {
113
+ return null;
114
+ }
115
+ const opening = text[index];
116
+ if (!opening) {
117
+ return null;
118
+ }
119
+ let closing = null;
120
+ if (opening === '{') {
121
+ closing = '}';
122
+ }
123
+ else if (opening === '[') {
124
+ closing = ']';
125
+ }
126
+ if (!closing) {
127
+ return null;
128
+ }
129
+ let depth = 0;
130
+ let inString = false;
131
+ let stringQuote = null;
132
+ let escaped = false;
133
+ for (let i = index; i < length; i++) {
134
+ const char = text[i];
135
+ if (!char) {
136
+ break;
137
+ }
138
+ if (inString) {
139
+ if (escaped) {
140
+ escaped = false;
141
+ continue;
142
+ }
143
+ if (char === '\\') {
144
+ escaped = true;
145
+ continue;
146
+ }
147
+ if (char === stringQuote) {
148
+ inString = false;
149
+ stringQuote = null;
150
+ }
151
+ continue;
152
+ }
153
+ if (char === '"' || char === '\'') {
154
+ inString = true;
155
+ stringQuote = char;
156
+ escaped = false;
157
+ continue;
158
+ }
159
+ if (char === opening) {
160
+ depth += 1;
161
+ }
162
+ else if (char === closing) {
163
+ depth -= 1;
164
+ if (depth === 0) {
165
+ return text.slice(index, i + 1);
166
+ }
167
+ }
168
+ }
169
+ return null;
170
+ }
171
+ function extractTaggedJson(response, tag = 'RESULT:') {
172
+ const output = toPythonOutput(response);
173
+ const pattern = escapeRegExp(tag);
174
+ let data = null;
175
+ const directMatch = output.text.match(new RegExp(`${pattern}\\s*(.+)$`, 'm'));
176
+ if (directMatch) {
177
+ const parsed = tryParseJson(directMatch[1]);
178
+ if (parsed !== null) {
179
+ data = parsed;
180
+ }
181
+ }
182
+ if (data === null) {
183
+ const tagIndex = output.text.indexOf(tag);
184
+ if (tagIndex >= 0) {
185
+ const jsonBlock = findJsonBlock(output.text, tagIndex + tag.length);
186
+ if (jsonBlock) {
187
+ const parsed = tryParseJson(jsonBlock);
188
+ if (parsed !== null) {
189
+ data = parsed;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ const sanitizedText = stripTaggedResultLines(output.text, tag);
195
+ const sanitizedRaw = sanitizePythonRaw(output.raw, tag);
196
+ return { raw: sanitizedRaw, text: sanitizedText, data };
197
+ }
198
+ export function parseStandardResult(response, tag = 'RESULT:') {
199
+ return extractTaggedJson(response, tag);
200
+ }
201
+ export function stripTaggedResultLines(text, tag = 'RESULT:') {
202
+ if (!text || !text.includes(tag)) {
203
+ return text;
204
+ }
205
+ const lines = text.split(/\r?\n/);
206
+ const cleaned = lines.filter(line => !isTaggedResultLine(line, tag));
207
+ const collapsed = cleaned.join('\n');
208
+ return collapsed.replace(/\n{3,}/g, '\n\n');
209
+ }
210
+ export function extractTaggedLine(output, prefix) {
211
+ const text = typeof output === 'string' ? output : output.text;
212
+ const pattern = new RegExp(`^${escapeRegExp(prefix)}\\s*(.+)$`, 'm');
213
+ const match = text.match(pattern);
214
+ return match ? match[1].trim() : null;
215
+ }
216
+ function sanitizePythonRaw(raw, tag = 'RESULT:') {
217
+ if (raw == null) {
218
+ return raw;
219
+ }
220
+ if (typeof raw === 'string') {
221
+ return stripTaggedResultLines(raw, tag);
222
+ }
223
+ if (Array.isArray(raw)) {
224
+ const sanitized = raw
225
+ .map(item => sanitizePythonRaw(item, tag))
226
+ .filter(item => {
227
+ if (typeof item === 'string') {
228
+ return item.length > 0 && !isTaggedResultLine(item, tag);
229
+ }
230
+ return item !== undefined;
231
+ });
232
+ return sanitized;
233
+ }
234
+ if (typeof raw === 'object') {
235
+ const obj = raw;
236
+ let mutated = false;
237
+ const clone = { ...obj };
238
+ const sanitizeLogArray = (value) => {
239
+ const filtered = value.filter(entry => {
240
+ if (entry == null) {
241
+ return false;
242
+ }
243
+ if (typeof entry === 'string') {
244
+ return !isTaggedResultLine(entry, tag);
245
+ }
246
+ const output = entry.Output ?? entry.output ?? entry.Message;
247
+ if (typeof output === 'string' && isTaggedResultLine(output, tag)) {
248
+ return false;
249
+ }
250
+ return true;
251
+ });
252
+ return filtered;
253
+ };
254
+ if (Array.isArray(obj.LogOutput)) {
255
+ const originalArray = obj.LogOutput;
256
+ const sanitizedLog = sanitizeLogArray(originalArray);
257
+ if (sanitizedLog.length !== originalArray.length ||
258
+ sanitizedLog.some((entry, idx) => entry !== originalArray[idx])) {
259
+ clone.LogOutput = sanitizedLog;
260
+ mutated = true;
261
+ }
262
+ }
263
+ if (Array.isArray(obj.logs)) {
264
+ const originalLogs = obj.logs;
265
+ const sanitizedLogs = sanitizeLogArray(originalLogs);
266
+ if (sanitizedLogs.length !== originalLogs.length ||
267
+ sanitizedLogs.some((entry, idx) => entry !== originalLogs[idx])) {
268
+ clone.logs = sanitizedLogs;
269
+ mutated = true;
270
+ }
271
+ }
272
+ if (typeof obj.Output === 'string') {
273
+ const stripped = stripTaggedResultLines(obj.Output, tag);
274
+ if (stripped !== obj.Output) {
275
+ clone.Output = stripped;
276
+ mutated = true;
277
+ }
278
+ }
279
+ if (typeof obj.result === 'string') {
280
+ const strippedResult = stripTaggedResultLines(obj.result, tag);
281
+ if (strippedResult !== obj.result) {
282
+ clone.result = strippedResult;
283
+ mutated = true;
284
+ }
285
+ }
286
+ return mutated ? clone : raw;
287
+ }
288
+ return raw;
289
+ }
290
+ //# sourceMappingURL=python-output.js.map
@@ -0,0 +1,2 @@
1
+ export declare function escapePythonString(value: string): string;
2
+ //# sourceMappingURL=python.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function escapePythonString(value) {
2
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
3
+ }
4
+ //# sourceMappingURL=python.js.map
@@ -98,13 +98,21 @@ export class ResponseValidator {
98
98
  const alreadyMcpShaped = response && typeof response === 'object' && Array.isArray(response.content);
99
99
  // Choose the payload to validate: if already MCP-shaped, validate the
100
100
  // structured content extracted from text; otherwise validate the object directly.
101
- const validationTarget = alreadyMcpShaped ? response : response;
102
- const validation = this.validateResponse(toolName, validationTarget);
101
+ const validation = this.validateResponse(toolName, response);
102
+ const structuredPayload = validation.structuredContent;
103
103
  if (!validation.valid) {
104
104
  log.warn(`Tool ${toolName} response validation failed:`, validation.errors);
105
105
  }
106
106
  // If it's already MCP-shaped, return as-is (optionally append validation meta)
107
107
  if (alreadyMcpShaped) {
108
+ if (structuredPayload !== undefined && response && typeof response === 'object' && response.structuredContent === undefined) {
109
+ try {
110
+ response.structuredContent = structuredPayload && typeof structuredPayload === 'object'
111
+ ? cleanObject(structuredPayload)
112
+ : structuredPayload;
113
+ }
114
+ catch { }
115
+ }
108
116
  if (!validation.valid) {
109
117
  try {
110
118
  response._validation = { valid: false, errors: validation.errors };
@@ -129,6 +137,24 @@ export class ResponseValidator {
129
137
  { type: 'text', text }
130
138
  ]
131
139
  };
140
+ if (structuredPayload !== undefined) {
141
+ try {
142
+ wrapped.structuredContent = structuredPayload && typeof structuredPayload === 'object'
143
+ ? cleanObject(structuredPayload)
144
+ : structuredPayload;
145
+ }
146
+ catch {
147
+ wrapped.structuredContent = structuredPayload;
148
+ }
149
+ }
150
+ else if (response && typeof response === 'object') {
151
+ try {
152
+ wrapped.structuredContent = cleanObject(response);
153
+ }
154
+ catch {
155
+ wrapped.structuredContent = response;
156
+ }
157
+ }
132
158
  if (!validation.valid) {
133
159
  wrapped._validation = { valid: false, errors: validation.errors };
134
160
  }
@@ -0,0 +1,27 @@
1
+ import { StandardResultPayload } from './python-output.js';
2
+ export interface InterpretedStandardResult {
3
+ success: boolean;
4
+ message: string;
5
+ error?: string;
6
+ warnings?: string[];
7
+ details?: string[];
8
+ payload: StandardResultPayload & Record<string, unknown>;
9
+ cleanText?: string;
10
+ rawText: string;
11
+ raw: unknown;
12
+ }
13
+ export declare function interpretStandardResult(response: unknown, defaults: {
14
+ successMessage: string;
15
+ failureMessage: string;
16
+ }): InterpretedStandardResult;
17
+ export declare function cleanResultText(text: string | undefined, options?: {
18
+ tag?: string;
19
+ fallback?: string;
20
+ }): string | undefined;
21
+ export declare function bestEffortInterpretedText(interpreted: Pick<InterpretedStandardResult, 'cleanText' | 'rawText'>, fallback?: string): string | undefined;
22
+ export declare function coerceString(value: unknown): string | undefined;
23
+ export declare function coerceStringArray(value: unknown): string[] | undefined;
24
+ export declare function coerceBoolean(value: unknown, fallback?: boolean): boolean | undefined;
25
+ export declare function coerceNumber(value: unknown): number | undefined;
26
+ export declare function coerceVector3(value: unknown): [number, number, number] | undefined;
27
+ //# sourceMappingURL=result-helpers.d.ts.map
@@ -0,0 +1,147 @@
1
+ import { parseStandardResult, stripTaggedResultLines } from './python-output.js';
2
+ export function interpretStandardResult(response, defaults) {
3
+ const parsed = parseStandardResult(response);
4
+ const payload = (parsed.data ?? {});
5
+ const success = payload.success === true;
6
+ const rawText = typeof parsed.text === 'string' ? parsed.text : String(parsed.text ?? '');
7
+ const cleanedText = cleanResultText(rawText, { fallback: undefined });
8
+ const messageFromPayload = typeof payload.message === 'string' ? payload.message.trim() : '';
9
+ const errorFromPayload = typeof payload.error === 'string' ? payload.error.trim() : '';
10
+ const message = messageFromPayload || (success ? defaults.successMessage : defaults.failureMessage);
11
+ const error = success ? undefined : errorFromPayload || messageFromPayload || defaults.failureMessage;
12
+ return {
13
+ success,
14
+ message,
15
+ error,
16
+ warnings: coerceStringArray(payload.warnings),
17
+ details: coerceStringArray(payload.details),
18
+ payload,
19
+ cleanText: cleanedText,
20
+ rawText,
21
+ raw: parsed.raw
22
+ };
23
+ }
24
+ export function cleanResultText(text, options = {}) {
25
+ const { tag = 'RESULT:', fallback } = options;
26
+ if (!text) {
27
+ return fallback;
28
+ }
29
+ const cleaned = stripTaggedResultLines(text, tag).trim();
30
+ if (cleaned.length > 0) {
31
+ return cleaned;
32
+ }
33
+ return fallback;
34
+ }
35
+ export function bestEffortInterpretedText(interpreted, fallback) {
36
+ const cleaned = interpreted.cleanText?.trim();
37
+ if (cleaned) {
38
+ return cleaned;
39
+ }
40
+ const raw = interpreted.rawText?.trim?.();
41
+ if (raw && !raw.startsWith('RESULT:')) {
42
+ return raw;
43
+ }
44
+ return fallback;
45
+ }
46
+ export function coerceString(value) {
47
+ if (typeof value === 'string') {
48
+ const trimmed = value.trim();
49
+ return trimmed.length > 0 ? trimmed : undefined;
50
+ }
51
+ return undefined;
52
+ }
53
+ export function coerceStringArray(value) {
54
+ if (!Array.isArray(value)) {
55
+ return undefined;
56
+ }
57
+ const items = [];
58
+ for (const entry of value) {
59
+ if (typeof entry !== 'string') {
60
+ continue;
61
+ }
62
+ const trimmed = entry.trim();
63
+ if (!trimmed) {
64
+ continue;
65
+ }
66
+ items.push(trimmed);
67
+ }
68
+ return items.length > 0 ? items : undefined;
69
+ }
70
+ export function coerceBoolean(value, fallback) {
71
+ if (typeof value === 'boolean') {
72
+ return value;
73
+ }
74
+ if (typeof value === 'string') {
75
+ const normalized = value.trim().toLowerCase();
76
+ if (['true', '1', 'yes', 'on'].includes(normalized)) {
77
+ return true;
78
+ }
79
+ if (['false', '0', 'no', 'off'].includes(normalized)) {
80
+ return false;
81
+ }
82
+ }
83
+ if (typeof value === 'number') {
84
+ if (value === 1) {
85
+ return true;
86
+ }
87
+ if (value === 0) {
88
+ return false;
89
+ }
90
+ }
91
+ return fallback;
92
+ }
93
+ export function coerceNumber(value) {
94
+ if (typeof value === 'number' && Number.isFinite(value)) {
95
+ return value;
96
+ }
97
+ if (typeof value === 'string') {
98
+ const parsed = Number(value);
99
+ if (Number.isFinite(parsed)) {
100
+ return parsed;
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ export function coerceVector3(value) {
106
+ const toNumber = (entry) => {
107
+ if (typeof entry === 'number' && Number.isFinite(entry)) {
108
+ return entry;
109
+ }
110
+ if (typeof entry === 'string') {
111
+ const parsed = Number(entry.trim());
112
+ if (Number.isFinite(parsed)) {
113
+ return parsed;
114
+ }
115
+ }
116
+ return undefined;
117
+ };
118
+ const fromArray = (arr) => {
119
+ if (arr.length !== 3) {
120
+ return undefined;
121
+ }
122
+ const mapped = arr.map(toNumber);
123
+ if (mapped.every((entry) => typeof entry === 'number')) {
124
+ return mapped;
125
+ }
126
+ return undefined;
127
+ };
128
+ if (Array.isArray(value)) {
129
+ const parsed = fromArray(value);
130
+ if (parsed) {
131
+ return parsed;
132
+ }
133
+ }
134
+ if (value && typeof value === 'object') {
135
+ const obj = value;
136
+ const candidate = fromArray([obj.x, obj.y, obj.z]);
137
+ if (candidate) {
138
+ return candidate;
139
+ }
140
+ const alternate = fromArray([obj.pitch, obj.yaw, obj.roll]);
141
+ if (alternate) {
142
+ return alternate;
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+ //# sourceMappingURL=result-helpers.js.map
@@ -1,4 +1,2 @@
1
- export declare function safeStringify(obj: any, space?: number): string;
2
- export declare function sanitizeResponse(response: any): any;
3
1
  export declare function cleanObject(obj: any, maxDepth?: number): any;
4
2
  //# sourceMappingURL=safe-json.d.ts.map
@@ -1,46 +1,3 @@
1
- // Utility to safely serialize objects with circular references
2
- export function safeStringify(obj, space) {
3
- const seen = new WeakSet();
4
- return JSON.stringify(obj, (_key, value) => {
5
- // Handle undefined, functions, symbols
6
- if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
7
- return undefined;
8
- }
9
- // Handle circular references
10
- if (typeof value === 'object' && value !== null) {
11
- if (seen.has(value)) {
12
- return '[Circular Reference]';
13
- }
14
- seen.add(value);
15
- }
16
- // Handle BigInt
17
- if (typeof value === 'bigint') {
18
- return value.toString();
19
- }
20
- return value;
21
- }, space);
22
- }
23
- export function sanitizeResponse(response) {
24
- if (!response || typeof response !== 'object') {
25
- return response;
26
- }
27
- // Create a clean copy without circular references
28
- try {
29
- const jsonStr = safeStringify(response);
30
- return JSON.parse(jsonStr);
31
- }
32
- catch (error) {
33
- console.error('Failed to sanitize response:', error);
34
- // Fallback: return a simple error response
35
- return {
36
- content: [{
37
- type: 'text',
38
- text: 'Response contained unserializable data'
39
- }],
40
- error: 'Serialization error'
41
- };
42
- }
43
- }
44
1
  // Remove circular references and non-serializable properties from an object
45
2
  export function cleanObject(obj, maxDepth = 10) {
46
3
  const seen = new WeakSet();
@@ -47,4 +47,20 @@ export declare function resolveSkeletalMeshPath(input: string): string | null;
47
47
  * @param ms Milliseconds to delay
48
48
  */
49
49
  export declare function concurrencyDelay(ms?: number): Promise<void>;
50
+ /**
51
+ * Ensure the provided value is a finite number within optional bounds.
52
+ * @throws if the value is not a finite number or violates bounds
53
+ */
54
+ export declare function validateNumber(value: unknown, label: string, { min, max, allowZero }?: {
55
+ min?: number;
56
+ max?: number;
57
+ allowZero?: boolean;
58
+ }): number;
59
+ /**
60
+ * Validate an array (tuple) of finite numbers, preserving the original shape.
61
+ * @throws if the tuple has the wrong length or contains invalid values
62
+ */
63
+ export declare function ensureVector3(value: unknown, label: string): [number, number, number];
64
+ export declare function ensureColorRGB(value: unknown, label: string): [number, number, number];
65
+ export declare function ensureRotation(value: unknown, label: string): [number, number, number];
50
66
  //# sourceMappingURL=validation.d.ts.map