@metatell/bot-sdk 0.0.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 (45) hide show
  1. package/dist/client.d.ts +127 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +552 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/index.d.ts +12 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/pcm-utils.d.ts +19 -0
  10. package/dist/pcm-utils.d.ts.map +1 -0
  11. package/dist/pcm-utils.js +98 -0
  12. package/dist/pcm-utils.js.map +1 -0
  13. package/dist/sdk/AgentClient.d.ts +201 -0
  14. package/dist/sdk/AgentClient.d.ts.map +1 -0
  15. package/dist/sdk/AgentClient.js +404 -0
  16. package/dist/sdk/AgentClient.js.map +1 -0
  17. package/dist/sdk/errors.d.ts +36 -0
  18. package/dist/sdk/errors.d.ts.map +1 -0
  19. package/dist/sdk/errors.js +61 -0
  20. package/dist/sdk/errors.js.map +1 -0
  21. package/dist/sdk/logging/LogEventEmitter.d.ts +38 -0
  22. package/dist/sdk/logging/LogEventEmitter.d.ts.map +1 -0
  23. package/dist/sdk/logging/LogEventEmitter.js +54 -0
  24. package/dist/sdk/logging/LogEventEmitter.js.map +1 -0
  25. package/dist/sdk/logging/index.d.ts +22 -0
  26. package/dist/sdk/logging/index.d.ts.map +1 -0
  27. package/dist/sdk/logging/index.js +10 -0
  28. package/dist/sdk/logging/index.js.map +1 -0
  29. package/dist/sdk/logging/providers/default.d.ts +21 -0
  30. package/dist/sdk/logging/providers/default.d.ts.map +1 -0
  31. package/dist/sdk/logging/providers/default.js +171 -0
  32. package/dist/sdk/logging/providers/default.js.map +1 -0
  33. package/dist/sdk/logging/spi.d.ts +35 -0
  34. package/dist/sdk/logging/spi.d.ts.map +1 -0
  35. package/dist/sdk/logging/spi.js +30 -0
  36. package/dist/sdk/logging/spi.js.map +1 -0
  37. package/dist/sdk/rate.d.ts +36 -0
  38. package/dist/sdk/rate.d.ts.map +1 -0
  39. package/dist/sdk/rate.js +81 -0
  40. package/dist/sdk/rate.js.map +1 -0
  41. package/dist/types.d.ts +111 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +24 -0
  44. package/dist/types.js.map +1 -0
  45. package/package.json +34 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * MetatellClient implementation - main facade for SDK
3
+ */
4
+ import type { Animation, AvatarAsset, BotInfo, CreateClientOptions, Euler, MetatellClientEvents, PcmInput, PcmInputOptions, PlaybackControls, User, Vec3 } from './types.js';
5
+ /**
6
+ * MetatellClient - メインのfacadeインターフェース
7
+ */
8
+ export interface MetatellClient {
9
+ /**
10
+ * Metatellサーバーに接続し、指定されたルームに参加します。
11
+ * @throws {AuthError} 認証トークンが無効な場合。
12
+ * @throws {NetworkError} ネットワーク接続に失敗した場合。
13
+ */
14
+ connect(): Promise<void>;
15
+ /**
16
+ * サーバーから切断します。
17
+ */
18
+ disconnect(): Promise<void>;
19
+ /** ルーム関連の操作 */
20
+ readonly room: {
21
+ /** 現在ルームに参加しているユーザーの一覧を取得します。 */
22
+ getUsers(): Promise<User[]>;
23
+ /** 指定した半径内のユーザーを取得します。 */
24
+ getNearbyUsers(radius?: number): Promise<User[]>;
25
+ };
26
+ /** 現在ルームに参加しているユーザーの一覧を取得します(同期版)。 */
27
+ getUsers(): User[];
28
+ /** チャット関連の操作 */
29
+ readonly chat: {
30
+ /** ルーム全体にメッセージを送信します。 */
31
+ send(text: string): Promise<void>;
32
+ /**
33
+ * すべてのチャットメッセージを購読します。
34
+ * メンションの有無に関わらず、すべてのメッセージを受信します。
35
+ */
36
+ onMessage(handler: (event: {
37
+ from: User;
38
+ text: string;
39
+ mention?: {
40
+ sessionId: string;
41
+ name: string;
42
+ };
43
+ /** 受信したメッセージに簡易返信するユーティリティ関数 */
44
+ reply: (text: string) => Promise<void>;
45
+ }) => void): void;
46
+ };
47
+ /** ボットアバターの操作 */
48
+ readonly avatar: {
49
+ /**
50
+ * アバターを選択・変更します。
51
+ * @param assetId 組織アバターのIDなど
52
+ */
53
+ select(assetId: string): Promise<void>;
54
+ /**
55
+ * アニメーションを再生します。
56
+ * @param animation 再生するアニメーションの仕様
57
+ * @throws {NotFoundError} 指定されたアニメーションが存在しない場合。
58
+ */
59
+ play(animation: Animation): Promise<void>;
60
+ /**
61
+ * 指定された座標に移動します。
62
+ * @param position 移動先の座標(メートル)
63
+ */
64
+ moveTo(position: Vec3): Promise<void>;
65
+ /**
66
+ * 指定された角度に回転します。
67
+ * @param rotation 回転角度(オイラー角・度数法)
68
+ */
69
+ rotateTo(rotation: Euler): Promise<void>;
70
+ /**
71
+ * 指定された座標を見るように回転します。
72
+ * @param target 見る対象の座標(メートル)
73
+ */
74
+ lookAt(target: Vec3): Promise<void>;
75
+ /** 現在の位置を取得します。 */
76
+ getPosition(): Vec3 | null;
77
+ /** 利用可能なアバターアセットの一覧を取得します。 */
78
+ getAvailableAssets(): Promise<AvatarAsset[]>;
79
+ /** 現在のアバターで利用可能なアニメーションの一覧を取得します。 */
80
+ getAvailableAnimations(): Promise<Animation[]>;
81
+ };
82
+ /** 音声関連の操作 */
83
+ readonly voice: {
84
+ /**
85
+ * 16-bit PCMデータを注入し、ボットに発話させます。
86
+ * SDKは内部で48kHz/monoにリサンプリングし、10msのフレームに分割して送信します。
87
+ * @param input Int16Array, AsyncIterable<Int16Array>, またはNodeJS.ReadableStream
88
+ * @param options 入力PCMのフォーマット
89
+ * @returns 再生を制御するためのオブジェクト
90
+ * @throws {UnsupportedAudioFormatError} サポート外のフォーマットが指定された場合。
91
+ */
92
+ playPcm(input: PcmInput, options: PcmInputOptions): Promise<PlaybackControls>;
93
+ };
94
+ /** ボット自身の情報を取得します。 */
95
+ getInfo(): Promise<BotInfo>;
96
+ /** 接続状態を取得します。 */
97
+ getStatus(): {
98
+ connected: boolean;
99
+ connecting: boolean;
100
+ };
101
+ /** レート制限設定を取得します。 */
102
+ getRateLimit(key: 'messages' | 'moves' | 'looks'): number | undefined;
103
+ /** レート制限を設定します。 */
104
+ setRateLimit(key: 'messages' | 'moves' | 'looks', perSecond: number): void;
105
+ /**
106
+ * 現在のセッションIDを取得します。
107
+ */
108
+ getSessionId(): string | null;
109
+ /**
110
+ * SDKのイベントを購読します。
111
+ * @param event イベント名
112
+ * @param listener イベントハンドラ
113
+ */
114
+ on<E extends keyof MetatellClientEvents>(event: E, listener: MetatellClientEvents[E]): this;
115
+ /**
116
+ * SDKのイベント購読を解除します。
117
+ */
118
+ off<E extends keyof MetatellClientEvents>(event: E, listener: MetatellClientEvents[E]): this;
119
+ }
120
+ /**
121
+ * MetatellClientのインスタンスを生成し、初期化します。
122
+ * @param options クライアント設定
123
+ * @returns MetatellClientのインスタンス
124
+ * @throws {MetatellError} 設定が不正な場合にスローされます。
125
+ */
126
+ export declare function createMetatellClient(options: CreateClientOptions): MetatellClient;
127
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,552 @@
1
+ /**
2
+ * MetatellClient implementation - main facade for SDK
3
+ */
4
+ import { EventEmitter } from 'node:events';
5
+ import { AnimationService, AppSettings, AvatarController, ConfigurationProvider, ConnectionManager,
6
+ // Core logging provider utilities (to ensure provider is set)
7
+ DefaultLoggerProvider as CoreDefaultLoggerProvider, CoreServiceFactory, EventBus, MessageService, OrganizationService, PresenceManager, registerLoggerProvider as registerCoreLoggerProvider, SystemEvents, UserAvatarManager, } from '@metatell/bot-core';
8
+ import { getLogger } from './sdk/logging/index.js';
9
+ import { RateLimitedQueue } from './sdk/rate.js';
10
+ import { AuthError, MetatellError, NetworkError, NotFoundError } from './types.js';
11
+ /**
12
+ * MetatellClient実装
13
+ */
14
+ class MetatellClientImpl extends EventEmitter {
15
+ options;
16
+ serviceFactory;
17
+ connectionManager;
18
+ messageService;
19
+ avatarController;
20
+ presenceManager;
21
+ organizationService;
22
+ animationService;
23
+ eventBus;
24
+ configProvider;
25
+ userAvatarManager;
26
+ rateLimiter = new RateLimitedQueue();
27
+ logger = getLogger('MetatellClient');
28
+ constructor(options) {
29
+ super();
30
+ this.options = options;
31
+ // Ensure Core logging provider is registered to avoid runtime error
32
+ try {
33
+ // CoreServiceFactoryがプロバイダーを必要とする場合のみ登録
34
+ registerCoreLoggerProvider(new CoreDefaultLoggerProvider(), { allowOverwrite: true });
35
+ }
36
+ catch {
37
+ // すでに登録されている場合はエラーを無視
38
+ }
39
+ // デバッグモードの場合、ロギングを有効化
40
+ if (options.debug) {
41
+ process.env.DEBUG = 'metatell:*';
42
+ }
43
+ // CoreServiceFactoryを初期化
44
+ this.serviceFactory = new CoreServiceFactory({
45
+ serverUrl: options.serverUrl,
46
+ hubUrl: options.serverUrl.replace(/^ws/, 'http'), // WebSocket URLからHTTP URLに変換
47
+ hubId: options.roomId,
48
+ profile: {
49
+ displayName: options.username || 'MetatellBot',
50
+ avatarId: options.avatarId || '', // 後で組織アバターから取得
51
+ },
52
+ debug: options.debug || false,
53
+ });
54
+ // 必要なサービスを取得
55
+ const container = this.serviceFactory.getContainer();
56
+ this.connectionManager = container.get(ConnectionManager);
57
+ this.messageService = container.get(MessageService);
58
+ this.avatarController = container.get(AvatarController);
59
+ this.presenceManager = container.get(PresenceManager);
60
+ this.userAvatarManager = container.get(UserAvatarManager);
61
+ this.organizationService = container.get(OrganizationService);
62
+ this.animationService = container.get(AnimationService);
63
+ this.eventBus = container.get(EventBus);
64
+ this.configProvider = container.get(ConfigurationProvider);
65
+ // デバッグモードの場合、AppSettingsのログレベルを変更
66
+ if (options.debug) {
67
+ const appSettings = container.get(AppSettings);
68
+ appSettings.setLogLevel('debug');
69
+ appSettings.setDebugMode(true);
70
+ }
71
+ // イベントのプロキシ設定
72
+ this.setupEventProxies();
73
+ }
74
+ /**
75
+ * Parse mention from message body
76
+ * Format: [@displayName](session-id) message
77
+ * Example: [@MetatellCLI](b754ca96-d395-4b80-adb1-77cb0240a43d) hello
78
+ */
79
+ parseMessageMention(body) {
80
+ const mentionPattern = /\[@([^\]]+)\]\(([^)]+)\)\s*(.*)$/;
81
+ const match = body.match(mentionPattern);
82
+ if (match) {
83
+ return {
84
+ text: match[3].trim(),
85
+ mention: {
86
+ name: match[1],
87
+ sessionId: match[2],
88
+ },
89
+ };
90
+ }
91
+ return { text: body };
92
+ }
93
+ setupEventProxies() {
94
+ // Coreのイベントをクライアントイベントにマッピング
95
+ this.eventBus.on(SystemEvents.CONNECTION_ESTABLISHED, () => {
96
+ this.emit('connected');
97
+ });
98
+ this.eventBus.on(SystemEvents.CONNECTION_LOST, () => {
99
+ this.emit('disconnected');
100
+ });
101
+ this.eventBus.on(SystemEvents.MESSAGE_RECEIVED, (data) => {
102
+ const messageData = data;
103
+ this.emit('message', messageData);
104
+ // チャットメッセージの場合、詳細な情報を解析して別イベントを発火
105
+ if (messageData.type === 'chat' && messageData.body) {
106
+ const parsed = this.parseMessageMention(messageData.body);
107
+ // PresenceManagerから送信者の情報を取得
108
+ const users = this.presenceManager.getUsers();
109
+ const sender = users.find((u) => u.id === messageData.senderId);
110
+ // デバッグ情報
111
+ if (this.options.debug) {
112
+ this.logger.debug('Message sender lookup:', {
113
+ senderId: messageData.senderId,
114
+ foundSender: !!sender,
115
+ senderData: sender,
116
+ allUsers: users.map((u) => ({ id: u.id, name: u.profile?.displayName })),
117
+ });
118
+ }
119
+ const senderName = sender?.profile?.displayName || sender?.id.split('#')[0] || 'Unknown';
120
+ this.emit('chat-message', {
121
+ from: {
122
+ id: messageData.senderId || '',
123
+ name: senderName,
124
+ isBot: false,
125
+ },
126
+ text: parsed.text,
127
+ mention: parsed.mention,
128
+ });
129
+ }
130
+ });
131
+ this.eventBus.on(SystemEvents.USER_JOINED, (data) => {
132
+ // PresenceUserデータをUser型に変換
133
+ const presenceUser = data;
134
+ // UserAvatarManagerからアバター情報を取得
135
+ const avatar = this.userAvatarManager.getUser(presenceUser.id);
136
+ const user = {
137
+ id: presenceUser.id,
138
+ name: presenceUser.profile?.displayName || presenceUser.id.split('#')[0] || presenceUser.id,
139
+ isBot: false,
140
+ position: avatar?.position,
141
+ rotation: avatar?.rotation,
142
+ };
143
+ this.emit('user-join', user);
144
+ // 新しいユーザーが入室したときにアバターを再同期
145
+ this.resyncAvatarForNewUser();
146
+ });
147
+ this.eventBus.on(SystemEvents.USER_LEFT, (data) => {
148
+ // PresenceUserデータをUser型に変換
149
+ const presenceUser = data;
150
+ // UserAvatarManagerからアバター情報を取得
151
+ const avatar = this.userAvatarManager.getUser(presenceUser.id);
152
+ const user = {
153
+ id: presenceUser.id,
154
+ name: presenceUser.profile?.displayName || presenceUser.id.split('#')[0] || presenceUser.id,
155
+ isBot: false,
156
+ position: avatar?.position,
157
+ rotation: avatar?.rotation,
158
+ };
159
+ this.emit('user-leave', user);
160
+ });
161
+ }
162
+ async resyncAvatarForNewUser() {
163
+ try {
164
+ // アバターがスポーンされている場合のみ再同期
165
+ if (this.avatarController.getState()) {
166
+ await this.avatarController.resyncAvatar();
167
+ this.logger.debug('Avatar resynced for new user');
168
+ }
169
+ }
170
+ catch (error) {
171
+ this.logger.warn('Failed to resync avatar for new user', error);
172
+ }
173
+ }
174
+ async connect() {
175
+ try {
176
+ await this.connectionManager.connect({
177
+ serverUrl: this.options.serverUrl,
178
+ hubId: this.options.roomId,
179
+ });
180
+ // 組織情報を取得
181
+ const orgInfo = await this.organizationService.getOrganizationInfo(this.options.serverUrl.replace(/^ws/, 'http'), this.options.roomId);
182
+ // アバターIDが指定されていない場合、組織アバターから選択
183
+ let avatarId = this.options.avatarId;
184
+ let avatarUrl;
185
+ if (!avatarId && orgInfo.organizationId) {
186
+ try {
187
+ // 組織アバター一覧を取得
188
+ const avatars = await this.organizationService.fetchOrganizationAvatars(this.options.serverUrl.replace(/^ws/, 'http'), orgInfo.organizationId);
189
+ if (avatars.length > 0) {
190
+ // 最初のアバターを使用
191
+ const defaultAvatar = avatars[0];
192
+ avatarId = defaultAvatar.id;
193
+ avatarUrl = defaultAvatar.gltf.avatar;
194
+ }
195
+ }
196
+ catch (error) {
197
+ // 組織アバター取得に失敗した場合はスキップ
198
+ this.logger.debug('Failed to fetch organization avatars', error);
199
+ }
200
+ }
201
+ // アバターIDが取得できない場合はエラー
202
+ if (!avatarId) {
203
+ throw new MetatellError('NO_AVATAR_AVAILABLE', 'No avatar available. Organization avatars not found and no avatar ID specified.');
204
+ }
205
+ // アバターをスポーン
206
+ if (avatarId) {
207
+ // 設定を更新
208
+ const config = this.configProvider.getConfiguration();
209
+ config.profile.avatarId = avatarId;
210
+ if (avatarUrl) {
211
+ config.organizationAvatarUrl = avatarUrl;
212
+ }
213
+ await this.avatarController.spawn(avatarId, undefined, avatarUrl);
214
+ }
215
+ }
216
+ catch (error) {
217
+ // エラーを適切なタイプに変換
218
+ if (error instanceof Error && error.message.includes('auth')) {
219
+ throw new AuthError('AUTH_FAILED', 'Authentication failed', error);
220
+ }
221
+ throw new NetworkError('CONNECTION_FAILED', 'Failed to connect', error);
222
+ }
223
+ }
224
+ async disconnect() {
225
+ await this.connectionManager.disconnect();
226
+ }
227
+ room = {
228
+ getUsers: async () => {
229
+ const users = this.presenceManager.getUsers();
230
+ const currentSessionId = this.connectionManager.getSessionId();
231
+ // PresenceUserをUser型に変換
232
+ return users.map((u) => {
233
+ // 自分自身の場合はAvatarControllerから位置情報を取得
234
+ if (u.id === currentSessionId) {
235
+ const avatarState = this.avatarController.getState();
236
+ return {
237
+ id: u.id,
238
+ name: u.profile?.displayName || u.id.split('#')[0] || u.id,
239
+ isBot: false,
240
+ position: avatarState?.position,
241
+ rotation: avatarState?.rotation
242
+ ? {
243
+ x: (avatarState.rotation.x * 180) / Math.PI,
244
+ y: (avatarState.rotation.y * 180) / Math.PI,
245
+ z: (avatarState.rotation.z * 180) / Math.PI,
246
+ w: 1, // 簡略化
247
+ }
248
+ : undefined,
249
+ };
250
+ }
251
+ // UserAvatarManagerからアバター情報を取得
252
+ const avatar = this.userAvatarManager.getUser(u.id);
253
+ return {
254
+ id: u.id,
255
+ name: u.profile?.displayName || u.id.split('#')[0] || u.id,
256
+ isBot: false,
257
+ position: avatar?.position,
258
+ rotation: avatar?.rotation,
259
+ };
260
+ });
261
+ },
262
+ getNearbyUsers: async (radius = 10) => {
263
+ // 現在のアバター位置を取得
264
+ const currentPosition = this.avatar.getPosition();
265
+ if (!currentPosition) {
266
+ // アバターがスポーンされていない場合は全ユーザーを返す
267
+ return this.room.getUsers();
268
+ }
269
+ // UserAvatarManagerから距離内のユーザーを取得
270
+ const nearbyAvatars = this.userAvatarManager.getUsersInRange(currentPosition, radius);
271
+ // UserAvatarをUser型に変換
272
+ return nearbyAvatars.map((avatar) => ({
273
+ id: avatar.id,
274
+ name: avatar.nickname || avatar.id.split('#')[0] || avatar.id,
275
+ isBot: false,
276
+ position: avatar.position,
277
+ rotation: avatar.rotation,
278
+ }));
279
+ },
280
+ };
281
+ chat = {
282
+ send: async (text) => {
283
+ await this.rateLimiter.execute('messages', async () => {
284
+ await this.messageService.sendMessage(text);
285
+ });
286
+ },
287
+ onMessage: (handler) => {
288
+ // メッセージ受信イベントをサブスクライブ
289
+ this.eventBus.on(SystemEvents.MESSAGE_RECEIVED, async (data) => {
290
+ const messageData = data;
291
+ if (messageData.type === 'chat' && messageData.body) {
292
+ const parsed = this.parseMessageMention(messageData.body);
293
+ // PresenceManagerから送信者の情報を取得
294
+ const users = this.presenceManager.getUsers();
295
+ const sender = users.find((u) => u.id === messageData.senderId);
296
+ const senderName = sender?.profile?.displayName || sender?.id.split('#')[0] || 'Unknown';
297
+ const user = {
298
+ id: messageData.senderId || '',
299
+ name: senderName,
300
+ isBot: false,
301
+ };
302
+ handler({
303
+ from: user,
304
+ text: parsed.text,
305
+ mention: parsed.mention,
306
+ reply: async (replyText) => {
307
+ await this.messageService.sendMessage(replyText);
308
+ },
309
+ });
310
+ }
311
+ });
312
+ },
313
+ };
314
+ avatar = {
315
+ select: async (assetId) => {
316
+ // アバターを変更するには再度spawnを呼び出す
317
+ const state = this.avatarController.getState();
318
+ await this.avatarController.spawn(assetId, state?.position);
319
+ },
320
+ play: async (animation) => {
321
+ try {
322
+ // アニメーションオプションを変換
323
+ const playOptions = {
324
+ loop: animation.loop || false,
325
+ duration: animation.duration,
326
+ transitionDuration: animation.transitionDuration,
327
+ };
328
+ if ('id' in animation && animation.id) {
329
+ await this.avatarController.playAnimation(animation.id, playOptions);
330
+ }
331
+ else if ('url' in animation && animation.url) {
332
+ // URLベースのアニメーションは現在のインターフェースではサポートされていない
333
+ // カスタムアニメーションとして扱う必要がある
334
+ throw new NotFoundError('ANIMATION_NOT_FOUND', 'URL-based animations are not yet supported');
335
+ }
336
+ else {
337
+ throw new NotFoundError('ANIMATION_NOT_FOUND', 'Animation must have either id or url');
338
+ }
339
+ }
340
+ catch (error) {
341
+ if (error instanceof Error && error.message.includes('not found')) {
342
+ throw new NotFoundError('ANIMATION_NOT_FOUND', error.message, error);
343
+ }
344
+ throw error;
345
+ }
346
+ },
347
+ moveTo: async (position) => {
348
+ await this.rateLimiter.execute('moves', async () => {
349
+ await this.avatarController.move(position);
350
+ });
351
+ },
352
+ rotateTo: async (rotation) => {
353
+ // Euler角(度数法)をラジアンに変換
354
+ const xRad = (rotation.x * Math.PI) / 180;
355
+ const yRad = (rotation.y * Math.PI) / 180;
356
+ const zRad = (rotation.z * Math.PI) / 180;
357
+ // オイラー角からクォータニオンに変換(ZYX順)
358
+ const cx = Math.cos(xRad / 2);
359
+ const sx = Math.sin(xRad / 2);
360
+ const cy = Math.cos(yRad / 2);
361
+ const sy = Math.sin(yRad / 2);
362
+ const cz = Math.cos(zRad / 2);
363
+ const sz = Math.sin(zRad / 2);
364
+ const quaternion = {
365
+ x: sx * cy * cz - cx * sy * sz,
366
+ y: cx * sy * cz + sx * cy * sz,
367
+ z: cx * cy * sz - sx * sy * cz,
368
+ w: cx * cy * cz + sx * sy * sz,
369
+ };
370
+ // AvatarControllerのrotateメソッドを使用
371
+ await this.avatarController.rotate(quaternion);
372
+ },
373
+ lookAt: async (target) => {
374
+ await this.rateLimiter.execute('looks', async () => {
375
+ // 現在位置を取得
376
+ const state = this.avatarController.getState();
377
+ if (!state) {
378
+ throw new MetatellError('AVATAR_NOT_SPAWNED', 'Avatar is not spawned');
379
+ }
380
+ // ターゲットへの方向ベクトルを計算
381
+ const dx = target.x - state.position.x;
382
+ const dz = target.z - state.position.z;
383
+ // Y軸周りの回転角度を計算(ラジアン)
384
+ const yRotation = Math.atan2(dx, dz);
385
+ // ラジアンからクォータニオンに変換(Y軸回転のみ)
386
+ const halfAngle = yRotation / 2;
387
+ const quaternion = {
388
+ x: 0,
389
+ y: Math.sin(halfAngle),
390
+ z: 0,
391
+ w: Math.cos(halfAngle),
392
+ };
393
+ // 回転を適用
394
+ await this.avatarController.rotate(quaternion);
395
+ });
396
+ },
397
+ getPosition: () => {
398
+ const state = this.avatarController.getState();
399
+ return state ? { ...state.position } : null;
400
+ },
401
+ getAvailableAssets: async () => {
402
+ // organizationInfoを取得するには、hubUrlとhubIdが必要
403
+ const config = this.configProvider.getConfiguration();
404
+ const orgInfo = await this.organizationService.getOrganizationInfo(config.hubUrl, config.hubId);
405
+ if (!orgInfo.organizationId) {
406
+ // 組織IDがない場合は空配列を返す
407
+ return [];
408
+ }
409
+ const avatars = await this.organizationService.fetchOrganizationAvatars(config.hubUrl, orgInfo.organizationId);
410
+ return avatars.map((avatar) => ({
411
+ id: avatar.id,
412
+ name: avatar.name,
413
+ thumbnailUrl: avatar.images?.preview?.url || avatar.thumbnail_url || '',
414
+ modelUrl: avatar.gltf.avatar,
415
+ }));
416
+ },
417
+ getAvailableAnimations: async () => {
418
+ // 現在のアバターIDを取得
419
+ const state = this.avatarController.getState();
420
+ if (!state) {
421
+ return [];
422
+ }
423
+ this.logger.debug('Getting available animations for avatar', { avatarId: state.avatarId });
424
+ const animations = await this.animationService.getAvailableAnimations(state.avatarId);
425
+ this.logger.debug('Retrieved animations', {
426
+ avatarId: state.avatarId,
427
+ animationCount: animations.length,
428
+ animations: animations.map((a) => ({ id: a.id, name: a.name })),
429
+ });
430
+ return animations.map((anim) => ({
431
+ id: anim.id,
432
+ name: anim.name || 'Unknown Animation',
433
+ duration: anim.duration,
434
+ }));
435
+ },
436
+ };
437
+ voice = {
438
+ playPcm: async (_input, _options) => {
439
+ // 音声機能は別パッケージ(@metatell/realtime)で実装
440
+ // ここではプレースホルダーを返す
441
+ const finishedPromise = new Promise((resolve) => {
442
+ setTimeout(resolve, 1000);
443
+ });
444
+ return {
445
+ stop: async () => {
446
+ // 音声停止の実装は別パッケージで行う
447
+ },
448
+ finished: finishedPromise,
449
+ };
450
+ },
451
+ };
452
+ async getInfo() {
453
+ const config = this.configProvider.getConfiguration();
454
+ const sessionId = this.getSessionId();
455
+ return {
456
+ name: config.profile?.displayName || 'MetatellBot',
457
+ version: '1.0.0',
458
+ roomId: config.hubId,
459
+ sessionId: sessionId || undefined,
460
+ };
461
+ }
462
+ getStatus() {
463
+ // 簡易的な接続状態を返す
464
+ return {
465
+ connected: true,
466
+ connecting: false,
467
+ };
468
+ }
469
+ getUsers() {
470
+ const users = this.presenceManager.getUsers();
471
+ const currentSessionId = this.connectionManager.getSessionId();
472
+ // PresenceUserをUser型に変換(room.getUsersと同じ実装)
473
+ return users.map((u) => {
474
+ // 自分自身の場合はAvatarControllerから位置情報を取得
475
+ if (u.id === currentSessionId) {
476
+ const avatarState = this.avatarController.getState();
477
+ return {
478
+ id: u.id,
479
+ name: u.profile?.displayName || u.id.split('#')[0] || u.id,
480
+ isBot: false,
481
+ position: avatarState?.position,
482
+ rotation: avatarState?.rotation
483
+ ? {
484
+ x: (avatarState.rotation.x * 180) / Math.PI,
485
+ y: (avatarState.rotation.y * 180) / Math.PI,
486
+ z: (avatarState.rotation.z * 180) / Math.PI,
487
+ w: 1, // 簡略化
488
+ }
489
+ : undefined,
490
+ };
491
+ }
492
+ // UserAvatarManagerからアバター情報を取得
493
+ const avatar = this.userAvatarManager.getUser(u.id);
494
+ return {
495
+ id: u.id,
496
+ name: u.profile?.displayName || u.id.split('#')[0] || u.id,
497
+ isBot: false,
498
+ position: avatar?.position,
499
+ rotation: avatar?.rotation,
500
+ };
501
+ });
502
+ }
503
+ getRateLimit(key) {
504
+ return this.rateLimiter.getRate(key);
505
+ }
506
+ setRateLimit(key, perSecond) {
507
+ this.rateLimiter.setRate(key, perSecond);
508
+ }
509
+ getSessionId() {
510
+ // 接続マネージャーからセッションIDを取得
511
+ return this.connectionManager.getSessionId();
512
+ }
513
+ // EventEmitterのメソッドをオーバーライド
514
+ on(event, listener) {
515
+ // EventEmitterは汎用的な型を期待するため、型変換が必要
516
+ // string型にキャストして、リスナーは関数型として扱う
517
+ return super.on(event, listener);
518
+ }
519
+ off(event, listener) {
520
+ // EventEmitterは汎用的な型を期待するため、型変換が必要
521
+ return super.off(event, listener);
522
+ }
523
+ }
524
+ /**
525
+ * MetatellClientのインスタンスを生成し、初期化します。
526
+ * @param options クライアント設定
527
+ * @returns MetatellClientのインスタンス
528
+ * @throws {MetatellError} 設定が不正な場合にスローされます。
529
+ */
530
+ export function createMetatellClient(options) {
531
+ // 設定バリデーション
532
+ if (!options.serverUrl || !options.roomId) {
533
+ throw new MetatellError('INVALID_CONFIG', 'serverUrl and roomId are required');
534
+ }
535
+ // サブドメインを除いたサーバーURLを生成
536
+ const processedOptions = { ...options };
537
+ try {
538
+ const url = new URL(options.serverUrl.replace(/^ws/, 'http'));
539
+ const hostParts = url.hostname.split('.');
540
+ if (hostParts.length >= 2) {
541
+ // サブドメインがある場合は除去(例: urth.metatell.app -> metatell.app)
542
+ const mainDomain = hostParts.slice(-2).join('.');
543
+ processedOptions.serverUrl = options.serverUrl.replace(url.hostname, mainDomain);
544
+ }
545
+ }
546
+ catch (_error) {
547
+ // URL解析に失敗した場合はそのまま使用
548
+ // エラーログなどは出さず、元のURLを使用
549
+ }
550
+ return new MetatellClientImpl(processedOptions);
551
+ }
552
+ //# sourceMappingURL=client.js.map