unreal-engine-mcp-server 0.2.1

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 (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Enhanced error types for better error handling and recovery
3
+ */
4
+
5
+ export enum ErrorCode {
6
+ // Connection errors
7
+ CONNECTION_FAILED = 'CONNECTION_FAILED',
8
+ CONNECTION_TIMEOUT = 'CONNECTION_TIMEOUT',
9
+ CONNECTION_REFUSED = 'CONNECTION_REFUSED',
10
+
11
+ // API errors
12
+ API_ERROR = 'API_ERROR',
13
+ INVALID_RESPONSE = 'INVALID_RESPONSE',
14
+ RATE_LIMITED = 'RATE_LIMITED',
15
+
16
+ // Validation errors
17
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
18
+ INVALID_PARAMETERS = 'INVALID_PARAMETERS',
19
+ MISSING_REQUIRED_FIELD = 'MISSING_REQUIRED_FIELD',
20
+
21
+ // Resource errors
22
+ RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
23
+ RESOURCE_LOCKED = 'RESOURCE_LOCKED',
24
+ RESOURCE_UNAVAILABLE = 'RESOURCE_UNAVAILABLE',
25
+
26
+ // Permission errors
27
+ UNAUTHORIZED = 'UNAUTHORIZED',
28
+ FORBIDDEN = 'FORBIDDEN',
29
+
30
+ // System errors
31
+ INTERNAL_ERROR = 'INTERNAL_ERROR',
32
+ CIRCUIT_BREAKER_OPEN = 'CIRCUIT_BREAKER_OPEN',
33
+ SERVICE_UNAVAILABLE = 'SERVICE_UNAVAILABLE'
34
+ }
35
+
36
+ export interface ErrorMetadata {
37
+ code: ErrorCode;
38
+ statusCode?: number;
39
+ retriable: boolean;
40
+ context?: Record<string, any>;
41
+ timestamp: Date;
42
+ correlationId?: string;
43
+ }
44
+
45
+ /**
46
+ * Base application error with metadata
47
+ */
48
+ export class AppError extends Error {
49
+ public readonly metadata: ErrorMetadata;
50
+
51
+ constructor(message: string, metadata: Partial<ErrorMetadata> = {}) {
52
+ super(message);
53
+ this.name = this.constructor.name;
54
+ this.metadata = {
55
+ code: metadata.code || ErrorCode.INTERNAL_ERROR,
56
+ retriable: metadata.retriable || false,
57
+ timestamp: new Date(),
58
+ ...metadata
59
+ };
60
+ Error.captureStackTrace(this, this.constructor);
61
+ }
62
+
63
+ toJSON() {
64
+ return {
65
+ name: this.name,
66
+ message: this.message,
67
+ ...this.metadata,
68
+ stack: this.stack
69
+ };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Connection-related errors
75
+ */
76
+ export class ConnectionError extends AppError {
77
+ constructor(message: string, metadata: Partial<ErrorMetadata> = {}) {
78
+ super(message, {
79
+ code: ErrorCode.CONNECTION_FAILED,
80
+ retriable: true,
81
+ statusCode: 503,
82
+ ...metadata
83
+ });
84
+ }
85
+ }
86
+
87
+ /**
88
+ * API-related errors
89
+ */
90
+ export class ApiError extends AppError {
91
+ constructor(message: string, statusCode: number, metadata: Partial<ErrorMetadata> = {}) {
92
+ super(message, {
93
+ code: ErrorCode.API_ERROR,
94
+ statusCode,
95
+ retriable: statusCode >= 500 || statusCode === 429,
96
+ ...metadata
97
+ });
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Validation errors
103
+ */
104
+ export class ValidationError extends AppError {
105
+ constructor(message: string, metadata: Partial<ErrorMetadata> = {}) {
106
+ super(message, {
107
+ code: ErrorCode.VALIDATION_ERROR,
108
+ statusCode: 400,
109
+ retriable: false,
110
+ ...metadata
111
+ });
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Resource errors
117
+ */
118
+ export class ResourceError extends AppError {
119
+ constructor(message: string, code: ErrorCode, metadata: Partial<ErrorMetadata> = {}) {
120
+ super(message, {
121
+ code,
122
+ statusCode: code === ErrorCode.RESOURCE_NOT_FOUND ? 404 : 409,
123
+ retriable: code === ErrorCode.RESOURCE_LOCKED,
124
+ ...metadata
125
+ });
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Circuit Breaker implementation for fault tolerance
131
+ */
132
+ export enum CircuitState {
133
+ CLOSED = 'CLOSED',
134
+ OPEN = 'OPEN',
135
+ HALF_OPEN = 'HALF_OPEN'
136
+ }
137
+
138
+ interface CircuitBreakerOptions {
139
+ threshold: number;
140
+ timeout: number;
141
+ resetTimeout: number;
142
+ onStateChange?: (oldState: CircuitState, newState: CircuitState) => void;
143
+ }
144
+
145
+ export class CircuitBreaker {
146
+ private state: CircuitState = CircuitState.CLOSED;
147
+ private failures = 0;
148
+ private successCount = 0;
149
+ private lastFailureTime?: Date;
150
+ private readonly options: CircuitBreakerOptions;
151
+
152
+ constructor(options: Partial<CircuitBreakerOptions> = {}) {
153
+ this.options = {
154
+ threshold: options.threshold || 5,
155
+ timeout: options.timeout || 60000, // 1 minute
156
+ resetTimeout: options.resetTimeout || 30000, // 30 seconds
157
+ onStateChange: options.onStateChange
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Execute function with circuit breaker protection
163
+ */
164
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
165
+ // Check circuit state
166
+ if (this.state === CircuitState.OPEN) {
167
+ if (this.shouldAttemptReset()) {
168
+ this.transitionTo(CircuitState.HALF_OPEN);
169
+ } else {
170
+ throw new AppError('Circuit breaker is open', {
171
+ code: ErrorCode.CIRCUIT_BREAKER_OPEN,
172
+ retriable: true,
173
+ context: {
174
+ failures: this.failures,
175
+ lastFailure: this.lastFailureTime
176
+ }
177
+ });
178
+ }
179
+ }
180
+
181
+ try {
182
+ const result = await fn();
183
+ this.onSuccess();
184
+ return result;
185
+ } catch (error) {
186
+ this.onFailure();
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ private onSuccess(): void {
192
+ this.failures = 0;
193
+ if (this.state === CircuitState.HALF_OPEN) {
194
+ this.successCount++;
195
+ if (this.successCount >= 3) {
196
+ this.transitionTo(CircuitState.CLOSED);
197
+ }
198
+ }
199
+ }
200
+
201
+ private onFailure(): void {
202
+ this.failures++;
203
+ this.lastFailureTime = new Date();
204
+ this.successCount = 0;
205
+
206
+ if (this.failures >= this.options.threshold) {
207
+ this.transitionTo(CircuitState.OPEN);
208
+ }
209
+ }
210
+
211
+ private shouldAttemptReset(): boolean {
212
+ return (
213
+ this.lastFailureTime !== undefined &&
214
+ Date.now() - this.lastFailureTime.getTime() >= this.options.resetTimeout
215
+ );
216
+ }
217
+
218
+ private transitionTo(newState: CircuitState): void {
219
+ const oldState = this.state;
220
+ this.state = newState;
221
+
222
+ if (newState === CircuitState.CLOSED) {
223
+ this.failures = 0;
224
+ this.successCount = 0;
225
+ }
226
+
227
+ if (this.options.onStateChange && oldState !== newState) {
228
+ this.options.onStateChange(oldState, newState);
229
+ }
230
+ }
231
+
232
+ getState(): CircuitState {
233
+ return this.state;
234
+ }
235
+
236
+ getMetrics() {
237
+ return {
238
+ state: this.state,
239
+ failures: this.failures,
240
+ successCount: this.successCount,
241
+ lastFailureTime: this.lastFailureTime
242
+ };
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Error recovery strategies
248
+ */
249
+ export class ErrorRecovery {
250
+ private static circuitBreakers = new Map<string, CircuitBreaker>();
251
+
252
+ /**
253
+ * Get or create circuit breaker for a service
254
+ */
255
+ static getCircuitBreaker(service: string, options?: Partial<CircuitBreakerOptions>): CircuitBreaker {
256
+ if (!this.circuitBreakers.has(service)) {
257
+ this.circuitBreakers.set(service, new CircuitBreaker(options));
258
+ }
259
+ const breaker = this.circuitBreakers.get(service);
260
+ if (!breaker) {
261
+ throw new Error(`Circuit breaker for service ${service} could not be created`);
262
+ }
263
+ return breaker;
264
+ }
265
+
266
+ /**
267
+ * Wrap function with error recovery
268
+ */
269
+ static async withRecovery<T>(
270
+ fn: () => Promise<T>,
271
+ options: {
272
+ service: string;
273
+ fallback?: () => T | Promise<T>;
274
+ onError?: (error: Error) => void;
275
+ }
276
+ ): Promise<T> {
277
+ const breaker = this.getCircuitBreaker(options.service);
278
+
279
+ try {
280
+ return await breaker.execute(fn);
281
+ } catch (error) {
282
+ if (options.onError) {
283
+ options.onError(error as Error);
284
+ }
285
+
286
+ // Try fallback if available
287
+ if (options.fallback) {
288
+ return await options.fallback();
289
+ }
290
+
291
+ throw error;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Check if error is retriable
297
+ */
298
+ static isRetriable(error: Error): boolean {
299
+ if (error instanceof AppError) {
300
+ return error.metadata.retriable;
301
+ }
302
+
303
+ // Check for network errors
304
+ const message = error.message.toLowerCase();
305
+ return (
306
+ message.includes('timeout') ||
307
+ message.includes('network') ||
308
+ message.includes('econnrefused') ||
309
+ message.includes('econnreset')
310
+ );
311
+ }
312
+ }
@@ -0,0 +1,184 @@
1
+ import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
2
+ import http from 'http';
3
+ import https from 'https';
4
+
5
+ // Connection pooling configuration for better performance
6
+ const httpAgent = new http.Agent({
7
+ keepAlive: true,
8
+ keepAliveMsecs: 30000,
9
+ maxSockets: 10,
10
+ maxFreeSockets: 5,
11
+ timeout: 30000
12
+ });
13
+
14
+ const httpsAgent = new https.Agent({
15
+ keepAlive: true,
16
+ keepAliveMsecs: 30000,
17
+ maxSockets: 10,
18
+ maxFreeSockets: 5,
19
+ timeout: 30000
20
+ });
21
+
22
+ // Retry configuration interface
23
+ interface RetryConfig {
24
+ maxRetries: number;
25
+ initialDelay: number;
26
+ maxDelay: number;
27
+ backoffMultiplier: number;
28
+ retryableStatuses: number[];
29
+ retryableErrors: string[];
30
+ }
31
+
32
+ const defaultRetryConfig: RetryConfig = {
33
+ maxRetries: 3,
34
+ initialDelay: 1000,
35
+ maxDelay: 10000,
36
+ backoffMultiplier: 2,
37
+ retryableStatuses: [408, 429, 500, 502, 503, 504],
38
+ retryableErrors: ['ECONNABORTED', 'ETIMEDOUT', 'ECONNRESET', 'ENOTFOUND']
39
+ };
40
+
41
+ /**
42
+ * Calculate exponential backoff delay with jitter
43
+ */
44
+ function calculateBackoff(attempt: number, config: RetryConfig): number {
45
+ const delay = Math.min(
46
+ config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1),
47
+ config.maxDelay
48
+ );
49
+ // Add jitter to prevent thundering herd
50
+ return delay + Math.random() * 1000;
51
+ }
52
+
53
+ /**
54
+ * Enhanced HTTP client factory with connection pooling and retry logic
55
+ */
56
+ export function createHttpClient(baseURL: string): AxiosInstance {
57
+ const client = axios.create({
58
+ baseURL,
59
+ headers: {
60
+ 'Content-Type': 'application/json',
61
+ 'Accept': 'application/json'
62
+ },
63
+ timeout: 15000,
64
+ httpAgent,
65
+ httpsAgent,
66
+ // Ensure proper handling of request body transformation
67
+ transformRequest: [(data, headers) => {
68
+ // Remove Content-Length if it's set incorrectly
69
+ delete headers['Content-Length'];
70
+ delete headers['content-length'];
71
+
72
+ // Properly stringify JSON data
73
+ if (data && typeof data === 'object') {
74
+ const jsonStr = JSON.stringify(data);
75
+ // Let axios set Content-Length automatically
76
+ return jsonStr;
77
+ }
78
+ return data;
79
+ }],
80
+ // Optimize response handling
81
+ maxContentLength: 50 * 1024 * 1024, // 50MB
82
+ maxBodyLength: 50 * 1024 * 1024,
83
+ decompress: true
84
+ });
85
+
86
+ // Add request interceptor for timing
87
+ client.interceptors.request.use(
88
+ (config) => {
89
+ // Add metadata for performance tracking
90
+ (config as any).metadata = { startTime: Date.now() };
91
+ return config;
92
+ },
93
+ (error) => {
94
+ return Promise.reject(error);
95
+ }
96
+ );
97
+
98
+ // Add response interceptor for timing and logging
99
+ client.interceptors.response.use(
100
+ (response) => {
101
+ const duration = Date.now() - ((response.config as any).metadata?.startTime || 0);
102
+ if (duration > 5000) {
103
+ console.warn(`[HTTP] Slow request: ${response.config.url} took ${duration}ms`);
104
+ }
105
+ return response;
106
+ },
107
+ (error) => {
108
+ return Promise.reject(error);
109
+ }
110
+ );
111
+
112
+ return client;
113
+ }
114
+
115
+ /**
116
+ * Execute request with retry logic for resilience
117
+ */
118
+ export async function requestWithRetry<T = any>(
119
+ client: AxiosInstance,
120
+ config: AxiosRequestConfig,
121
+ retryConfig: Partial<RetryConfig> = {}
122
+ ): Promise<T> {
123
+ const retry = { ...defaultRetryConfig, ...retryConfig };
124
+ let lastError: Error | null = null;
125
+
126
+ for (let attempt = 1; attempt <= retry.maxRetries; attempt++) {
127
+ try {
128
+ const response = await client.request<T>(config);
129
+
130
+ // Check if we should retry based on status
131
+ if (retry.retryableStatuses.includes(response.status)) {
132
+ throw new Error(`Retryable status: ${response.status}`);
133
+ }
134
+
135
+ return response.data;
136
+ } catch (error) {
137
+ lastError = error as Error;
138
+ const axiosError = error as AxiosError;
139
+
140
+ // Check if error is retryable
141
+ const isRetryable =
142
+ retry.retryableErrors.includes(axiosError.code || '') ||
143
+ (axiosError.response && retry.retryableStatuses.includes(axiosError.response.status));
144
+
145
+ if (!isRetryable || attempt === retry.maxRetries) {
146
+ throw error;
147
+ }
148
+
149
+ // Calculate delay and wait
150
+ const delay = calculateBackoff(attempt, retry);
151
+ console.error(`[HTTP] Retry attempt ${attempt}/${retry.maxRetries} after ${Math.round(delay)}ms`);
152
+ await new Promise(resolve => setTimeout(resolve, delay));
153
+ }
154
+ }
155
+
156
+ throw lastError || new Error('Request failed after retries');
157
+ }
158
+
159
+ /**
160
+ * Batch multiple requests for efficiency
161
+ */
162
+ export async function batchRequests<T = any>(
163
+ client: AxiosInstance,
164
+ requests: AxiosRequestConfig[],
165
+ options: { concurrency?: number; throwOnError?: boolean } = {}
166
+ ): Promise<(T | Error)[]> {
167
+ const { concurrency = 5, throwOnError = false } = options;
168
+ const results: (T | Error)[] = [];
169
+
170
+ // Process requests in batches
171
+ for (let i = 0; i < requests.length; i += concurrency) {
172
+ const batch = requests.slice(i, i + concurrency);
173
+ const batchPromises = batch.map(req =>
174
+ client.request<T>(req)
175
+ .then(res => res.data)
176
+ .catch(err => throwOnError ? Promise.reject(err) : err)
177
+ );
178
+
179
+ const batchResults = await Promise.all(batchPromises);
180
+ results.push(...batchResults);
181
+ }
182
+
183
+ return results;
184
+ }
@@ -0,0 +1,30 @@
1
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
2
+
3
+ export class Logger {
4
+ private level: LogLevel;
5
+
6
+ constructor(private scope: string, level: LogLevel = 'info') {
7
+ const envLevel = (process.env.LOG_LEVEL || process.env.LOGLEVEL || level).toString().toLowerCase();
8
+ this.level = (['debug', 'info', 'warn', 'error'] as LogLevel[]).includes(envLevel as LogLevel)
9
+ ? (envLevel as LogLevel)
10
+ : 'info';
11
+ }
12
+
13
+ private shouldLog(level: LogLevel) {
14
+ const order: LogLevel[] = ['debug', 'info', 'warn', 'error'];
15
+ return order.indexOf(level) >= order.indexOf(this.level);
16
+ }
17
+
18
+ debug(...args: any[]) {
19
+ if (this.shouldLog('debug')) console.error(`[${this.scope}]`, ...args);
20
+ }
21
+ info(...args: any[]) {
22
+ if (this.shouldLog('info')) console.error(`[${this.scope}]`, ...args);
23
+ }
24
+ warn(...args: any[]) {
25
+ if (this.shouldLog('warn')) console.error(`[${this.scope}]`, ...args);
26
+ }
27
+ error(...args: any[]) {
28
+ if (this.shouldLog('error')) console.error(`[${this.scope}]`, ...args);
29
+ }
30
+ }
@@ -0,0 +1,54 @@
1
+ export type Vec3Array = [number, number, number];
2
+ export type Rot3Array = [number, number, number];
3
+ export interface Vec3Obj { x: number; y: number; z: number; }
4
+ export interface Rot3Obj { pitch: number; yaw: number; roll: number; }
5
+
6
+ export function toVec3Object(input: any): Vec3Obj | null {
7
+ try {
8
+ if (Array.isArray(input) && input.length === 3) {
9
+ const [x, y, z] = input;
10
+ if ([x, y, z].every(v => typeof v === 'number' && isFinite(v))) {
11
+ return { x, y, z };
12
+ }
13
+ }
14
+ if (input && typeof input === 'object') {
15
+ const x = Number((input as any).x ?? (input as any).X);
16
+ const y = Number((input as any).y ?? (input as any).Y);
17
+ const z = Number((input as any).z ?? (input as any).Z);
18
+ if ([x, y, z].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
19
+ return { x, y, z };
20
+ }
21
+ }
22
+ } catch {}
23
+ return null;
24
+ }
25
+
26
+ export function toVec3Array(input: any): Vec3Array | null {
27
+ const obj = toVec3Object(input);
28
+ return obj ? [obj.x, obj.y, obj.z] : null;
29
+ }
30
+
31
+ export function toRotObject(input: any): Rot3Obj | null {
32
+ try {
33
+ if (Array.isArray(input) && input.length === 3) {
34
+ const [pitch, yaw, roll] = input;
35
+ if ([pitch, yaw, roll].every(v => typeof v === 'number' && isFinite(v))) {
36
+ return { pitch, yaw, roll };
37
+ }
38
+ }
39
+ if (input && typeof input === 'object') {
40
+ const pitch = Number((input as any).pitch ?? (input as any).Pitch);
41
+ const yaw = Number((input as any).yaw ?? (input as any).Yaw);
42
+ const roll = Number((input as any).roll ?? (input as any).Roll);
43
+ if ([pitch, yaw, roll].every(v => typeof v === 'number' && !isNaN(v) && isFinite(v))) {
44
+ return { pitch, yaw, roll };
45
+ }
46
+ }
47
+ } catch {}
48
+ return null;
49
+ }
50
+
51
+ export function toRotArray(input: any): Rot3Array | null {
52
+ const obj = toRotObject(input);
53
+ return obj ? [obj.pitch, obj.yaw, obj.roll] : null;
54
+ }