@vibexnpm/talkx 2.3.1 → 3.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibexnpm/talkx",
3
- "version": "2.3.1",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "TalkFlow SDK - Chat & WebRTC unified client",
6
6
  "main": "dist/talkflow-sdk.umd.js",
@@ -55,6 +55,21 @@ class ChatClient extends EventEmitter {
55
55
  this._assistantTypingUserId = '__assistant__';
56
56
  this._assistantTypingUserName = 'AI';
57
57
 
58
+ // AI 진행 표시(생각중/검색중/작성중) — assistant-stream 채널의 PHASE/DONE 수신 상태.
59
+ // roomId → { streamId, personaId, phase, timer }. typing 과 별개의 가산 레이어(서버가 둘 다 발행).
60
+ // 자동 해제 3경로: (1) DONE 수신 (2) 어시 메시지 도착 (3) TTL(expiresAt) 만료 — typing 타이머와 동형.
61
+ // 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행하므로 방 단위 1개 진행으로 추적한다.
62
+ this._assistantProgress = new Map();
63
+ // phase → 기본 표시 라벨(설계 §2 — 서버는 의미만, 라벨 텍스트는 SDK 매핑). 소비자는 payload.phase 로 커스텀 가능.
64
+ this._assistantPhaseLabels = {
65
+ THINKING: '생각 중…',
66
+ SEARCHING: '검색 중…',
67
+ PLANNING: '기획 중…',
68
+ WRITING: '작성 중…'
69
+ };
70
+ // PHASE 의 expiresAt 누락/이상 시 fallback TTL (ms) — 서버 ttl(최소 30s) 보다 약간 길게.
71
+ this._assistantProgressFallbackTtlMs = 35000;
72
+
58
73
  // 메시지 dedup 보호 — 같은 messageId 의 중복 수신 차단.
59
74
  // 서버측 멱등 race 복구 / 네트워크 재전송 / WebSocket 재연결 직후 등 다양한 중복 시나리오 커버.
60
75
  //
@@ -423,40 +438,37 @@ class ChatClient extends EventEmitter {
423
438
  }
424
439
 
425
440
  /**
426
- * 어시스턴트로 특정 메시지 번역 (버튼 진입점, 동기).
441
+ * 특정 메시지 온디맨드 번역 (버튼 진입점, 동기).
427
442
  *
428
- * <p>지정한 페르소나가 {@code sourceMessageId} 메시지를 {@code targetLang} 으로 번역해
429
- * 방에 어시스턴트 메시지로 broadcast 한다. 생성된 메시지는 일반 {@code chatMessage}
430
- * 이벤트로도 수신된다.</p>
443
+ * <p>{@code sourceMessageId} 메시지(텍스트/이미지/문서)를 {@code targetLang} 으로 번역해 요청자에게만
444
+ * 반환한다. 이미지는 이미지 속 텍스트를, 문서(PDF/DOCX/XLSX/PPTX 등)는 파일 안의 텍스트를 서버가 추출해
445
+ * 번역한다. 방에 메시지를 저장하거나 {@code chatMessage} 이벤트를 broadcast 하지 않는다. 읽을 텍스트가
446
+ * 없거나 문서가 너무 크면 에러를 반환한다.</p>
431
447
  *
432
448
  * @param {string} roomId - 채팅방 ID
433
449
  * @param {Object} options
434
- * @param {string} options.personaId - 번역을 수행할 어시스턴트 ID ({@link #getAssistants} 의 {@code id}). 필수.
435
- * @param {string} options.sourceMessageId - 번역 대상 메시지의 {@code messageId}. 필수.
436
- * @param {string} [options.targetLang] - 목표 언어 (예: {@code 'en'}, {@code '日本語'}). 미지정 시 서버 정책.
437
- * @returns {Promise<{outcome: string, messageId: (string|null), detail: (string|null)}>}
438
- * {@code outcome}: {@code 'EMITTED'} (생성됨, {@code messageId} 존재) |
439
- * {@code 'SKIPPED'} / {@code 'FAILED'} ({@code detail} 에 사유).
450
+ * @param {string} options.sourceMessageId - 번역 대상 메시지의 {@code messageId} (텍스트/이미지/문서). 필수.
451
+ * @param {string} options.targetLang - 목표 언어 (예: {@code 'en'}, {@code '日本語'}). 필수.
452
+ * @returns {Promise<{translatedText: string, sourceMessageId: string, targetLang: string}>}
440
453
  *
441
454
  * @example
442
455
  * const result = await chat.translateWithAssistant(roomId, {
443
- * personaId,
444
456
  * sourceMessageId: msg.messageId,
445
457
  * targetLang: 'en'
446
458
  * });
447
459
  */
448
460
  async translateWithAssistant(roomId, options = {}) {
449
- const { personaId, targetLang, sourceMessageId } = options;
450
- if (!personaId) {
451
- throw new Error('translateWithAssistant requires options.personaId');
452
- }
461
+ const { targetLang, sourceMessageId } = options;
453
462
  if (!sourceMessageId) {
454
463
  throw new Error('translateWithAssistant requires options.sourceMessageId');
455
464
  }
465
+ if (!targetLang) {
466
+ throw new Error('translateWithAssistant requires options.targetLang');
467
+ }
456
468
 
457
469
  const response = await this.apiClient.post(
458
470
  `/api/v1/rooms/${roomId}/assistant/tools/translate`,
459
- { personaId, targetLang, sourceMessageId }
471
+ { targetLang, sourceMessageId }
460
472
  );
461
473
  return this._unwrapSuccessResponse(response);
462
474
  }
@@ -1580,6 +1592,12 @@ class ChatClient extends EventEmitter {
1580
1592
  this._handleTypingEvent(roomId, event);
1581
1593
  });
1582
1594
 
1595
+ // AI 진행 표시(생각중/검색중/작성중) 구독 — typing 과 별개 채널(가산 UX). 구버전 SDK 는 미구독 → 기존 typing 만.
1596
+ const assistantStreamDestination = WebSocketPaths.getChatAssistantStreamDestination(roomId);
1597
+ await this.connectionManager.subscribe(assistantStreamDestination, (event) => {
1598
+ this._handleAssistantStreamEvent(roomId, event);
1599
+ });
1600
+
1583
1601
  // 멤버 변경 이벤트 — roomList 이벤트를 방 수준으로 변환하여 자동 emit
1584
1602
  // 서버가 members 배열(입장/퇴장 사용자들) + participantCount(정확한 인원수) 포함
1585
1603
  const memberJoinedHandler = (event) => {
@@ -1609,6 +1627,7 @@ class ChatClient extends EventEmitter {
1609
1627
  chatDestination,
1610
1628
  readDestination,
1611
1629
  typingDestination,
1630
+ assistantStreamDestination,
1612
1631
  memberJoinedHandler,
1613
1632
  memberLeftHandler,
1614
1633
  subscribedAt: new Date()
@@ -1634,6 +1653,9 @@ class ChatClient extends EventEmitter {
1634
1653
  if (subscription.typingDestination) {
1635
1654
  this.connectionManager.unsubscribe(subscription.typingDestination);
1636
1655
  }
1656
+ if (subscription.assistantStreamDestination) {
1657
+ this.connectionManager.unsubscribe(subscription.assistantStreamDestination);
1658
+ }
1637
1659
 
1638
1660
  // 멤버 변경 리스너 해제
1639
1661
  if (subscription.memberJoinedHandler) {
@@ -1646,6 +1668,8 @@ class ChatClient extends EventEmitter {
1646
1668
  // 타이핑 타이머 정리 (outgoing + incoming assistant 둘 다)
1647
1669
  this._clearTypingTimer(roomId);
1648
1670
  this._clearAssistantTypingTimer(roomId);
1671
+ // AI 진행 표시 상태/타이머 정리 (emit 없이 — 구독 해제 중)
1672
+ this._clearAssistantProgress(roomId, false);
1649
1673
 
1650
1674
  // 현재 보고 있는 방이면 activeRoom 해제
1651
1675
  if (this._activeRoomId === roomId) {
@@ -1855,6 +1879,9 @@ class ChatClient extends EventEmitter {
1855
1879
  typing: false,
1856
1880
  senderType: 'ASSISTANT'
1857
1881
  });
1882
+ // AI 진행 표시도 해제 — 최종 메시지 도착 = 진행 종료(설계 §3.1·§4.1, 메시지=진실).
1883
+ // DONE 이 유실/지연돼도 메시지로 수렴. active:false emit.
1884
+ this._clearAssistantProgress(roomId, true, 'message');
1858
1885
  }
1859
1886
 
1860
1887
  // 상대방 메시지일 때만 newMessage + 자동 읽음 처리
@@ -2161,6 +2188,141 @@ class ChatClient extends EventEmitter {
2161
2188
  }
2162
2189
  }
2163
2190
 
2191
+ /**
2192
+ * AI 진행 표시(assistant-stream) 이벤트 수신 처리 — PHASE(단계)/DELTA(토큰 청크)/DONE(종료) 분기.
2193
+ *
2194
+ * <p>typing 과 별개의 가산 레이어 — UI 는 {@code active:true} 면 {@code text}(토큰 스트림 누적) 우선,
2195
+ * 없으면 {@code label}(단계) 표시, {@code active:false} 면 해제. PHASE→DELTA 흐름으로 "작성 중" 라벨이
2196
+ * 점진 렌더 텍스트로 자연 전환된다. 서버가 typing 도 이중발행하므로 구버전 SDK 는 본 채널 미구독 → 기존
2197
+ * typing 만으로 정상 동작(graceful).</p>
2198
+ * @private
2199
+ */
2200
+ _handleAssistantStreamEvent(roomId, event) {
2201
+ if (!event || !event.type) return;
2202
+
2203
+ if (event.type === 'PHASE') {
2204
+ const phase = event.phase || null;
2205
+ const label = (phase && this._assistantPhaseLabels[phase]) || null;
2206
+ // 같은 stream 의 PHASE 재발행(B-3 synthesis heartbeat 등)이면 누적 텍스트 보존, 새 stream 이면 리셋.
2207
+ const prev = this._assistantProgress.get(roomId);
2208
+ const text = (prev && prev.streamId === event.streamId) ? (prev.text || '') : '';
2209
+ // 진행 상태 갱신 + TTL 재무장(heartbeat 재수신마다 연장). 같은 방의 이전 타이머는 reset.
2210
+ this._startAssistantProgressTimer(roomId, event, { phase, text });
2211
+ this.emit('assistantProgress', {
2212
+ roomId,
2213
+ streamId: event.streamId || null,
2214
+ personaId: event.personaId || null,
2215
+ phase,
2216
+ label,
2217
+ active: true,
2218
+ text: text || null
2219
+ });
2220
+ return;
2221
+ }
2222
+
2223
+ if (event.type === 'DELTA') {
2224
+ // 토큰 청크(B) — 누적 텍스트에 이어붙여 점진 렌더용으로 emit. WRITING 단계에서 흐른다.
2225
+ const prev = this._assistantProgress.get(roomId);
2226
+ // stale DELTA 무시 — 이전 stream 의 늦은 토큰이 새 stream 표시를 오염시키지 않게(DONE 가드와 동형).
2227
+ if (prev?.streamId && event.streamId && prev.streamId !== event.streamId) {
2228
+ this.logger.debug(`Stale assistant DELTA ignored: room=${roomId}, delta=${event.streamId}, active=${prev.streamId}`);
2229
+ return;
2230
+ }
2231
+ const phase = (prev && prev.phase) || 'WRITING';
2232
+ const text = (prev ? (prev.text || '') : '') + (event.delta || '');
2233
+ this._startAssistantProgressTimer(roomId, event, { phase, text });
2234
+ this.emit('assistantProgress', {
2235
+ roomId,
2236
+ streamId: event.streamId || null,
2237
+ personaId: event.personaId || null,
2238
+ phase,
2239
+ label: this._assistantPhaseLabels[phase] || null,
2240
+ active: true,
2241
+ text, // 지금까지 누적된 전체 텍스트(버블 렌더용)
2242
+ delta: event.delta || null, // 이번 청크(append 렌더 선호 시)
2243
+ seq: typeof event.seq === 'number' ? event.seq : null
2244
+ });
2245
+ return;
2246
+ }
2247
+
2248
+ if (event.type === 'DONE') {
2249
+ // 종료(성공·실패 공통) — 상태/타이머 해제 + active:false emit(status/messageId 동봉, 권위 신호).
2250
+ const state = this._assistantProgress.get(roomId);
2251
+ // 늦게 도착한 이전 stream 의 DONE 무시 — 다음 stream 의 PHASE 이후 도착 시 새 진행 표시를 끄는 race 차단.
2252
+ // 메시지/assistant-stream 채널은 ordering 이 묶여 있지 않다. streamId 가 둘 다 있고 다를 때만 stale 판정
2253
+ // (UI 계약이 "active 만 보면 됨"이므로 SDK 가 stale false 를 막는다).
2254
+ if (state?.streamId && event.streamId && state.streamId !== event.streamId) {
2255
+ this.logger.debug(`Stale assistant DONE ignored: room=${roomId}, done=${event.streamId}, active=${state.streamId}`);
2256
+ return;
2257
+ }
2258
+ // 메시지 도착으로 먼저 해제됐어도 DONE 은 한 번 더 발행될 수 있으나 active:false 는 idempotent(설계 §4.1).
2259
+ if (state && state.timer) clearTimeout(state.timer);
2260
+ this._assistantProgress.delete(roomId);
2261
+ this.emit('assistantProgress', {
2262
+ roomId,
2263
+ streamId: event.streamId || null,
2264
+ personaId: event.personaId || null,
2265
+ phase: null,
2266
+ label: null,
2267
+ active: false,
2268
+ status: event.status || null,
2269
+ messageId: event.messageId || null
2270
+ });
2271
+ }
2272
+ // 그 외 미지 type 은 무시(forward-compat).
2273
+ }
2274
+
2275
+ /**
2276
+ * AI 진행 상태 갱신 + TTL 타이머 재무장 — {@code expiresAt}(epochMillis) 기반, 누락/이상 시 fallback.
2277
+ * {@code fields}={phase, text}(PHASE/DELTA 가 해소해 전달 — DELTA 는 직전 phase 유지 + 누적 text).
2278
+ * 만료 = DONE·메시지 유실로 진행이 끊긴 것 → UI 가 영구 표시에 갇히지 않게 {@code active:false} 자동 emit.
2279
+ * @private
2280
+ */
2281
+ _startAssistantProgressTimer(roomId, event, fields) {
2282
+ const prev = this._assistantProgress.get(roomId);
2283
+ if (prev && prev.timer) clearTimeout(prev.timer);
2284
+
2285
+ let ttl = this._assistantProgressFallbackTtlMs;
2286
+ if (typeof event.expiresAt === 'number') {
2287
+ // 비정상값 방어 — [5s, 120s] 로 clamp(과거 timestamp / 과대 TTL 모두 차단).
2288
+ ttl = Math.min(120000, Math.max(5000, event.expiresAt - Date.now()));
2289
+ }
2290
+ const timer = setTimeout(() => {
2291
+ this._clearAssistantProgress(roomId, true, 'timeout');
2292
+ this.logger.debug(`Assistant progress auto-cleared (timeout): room=${roomId}`);
2293
+ }, ttl);
2294
+ this._assistantProgress.set(roomId, {
2295
+ streamId: event.streamId || null,
2296
+ personaId: event.personaId || null,
2297
+ phase: fields.phase,
2298
+ text: fields.text || '',
2299
+ timer
2300
+ });
2301
+ }
2302
+
2303
+ /**
2304
+ * AI 진행 표시 상태/타이머 해제. {@code emit=true} 면 {@code active:false} 를 발행(메시지 도착/타임아웃 해제용).
2305
+ * 구독 해제 시엔 {@code emit=false} 로 조용히 정리.
2306
+ * @private
2307
+ */
2308
+ _clearAssistantProgress(roomId, emit, reason) {
2309
+ const state = this._assistantProgress.get(roomId);
2310
+ if (!state) return;
2311
+ if (state.timer) clearTimeout(state.timer);
2312
+ this._assistantProgress.delete(roomId);
2313
+ if (emit) {
2314
+ this.emit('assistantProgress', {
2315
+ roomId,
2316
+ streamId: state.streamId || null,
2317
+ personaId: state.personaId || null,
2318
+ phase: null,
2319
+ label: null,
2320
+ active: false,
2321
+ reason: reason || null
2322
+ });
2323
+ }
2324
+ }
2325
+
2164
2326
  // ==================== 유틸리티 ====================
2165
2327
 
2166
2328
  /**
@@ -2208,6 +2370,10 @@ class ChatClient extends EventEmitter {
2208
2370
  this._assistantTypingTimers.forEach((timer) => clearTimeout(timer));
2209
2371
  this._assistantTypingTimers.clear();
2210
2372
 
2373
+ // AI 진행 표시 상태/타이머도 전체 cleanup (구독 외 경로로 남은 roomId 방어).
2374
+ this._assistantProgress.forEach((state) => state.timer && clearTimeout(state.timer));
2375
+ this._assistantProgress.clear();
2376
+
2211
2377
  // dedup bucket 전체 clear — unsubscribeRoom 이 방별로 정리하지만 구독 외 경로로 누적된 bucket
2212
2378
  // (예: 구독 안 한 방의 push/listener) 이 있을 수 있어 안전하게 명시 정리.
2213
2379
  this._seenChatMessageIdsByRoom.clear();
package/src/constants.js CHANGED
@@ -319,6 +319,8 @@ export const WebSocketPaths = {
319
319
  getChatDestination: (roomId) => `/topic/chat/${roomId}`,
320
320
  getChatReadDestination: (roomId) => `/topic/chat/${roomId}/read`,
321
321
  getChatTypingDestination: (roomId) => `/topic/chat/${roomId}/typing`,
322
+ // AI 진행 표시(생각중/검색중/작성중) + 토큰 스트리밍 — typing 과 별개 채널(가산 UX 레이어).
323
+ getChatAssistantStreamDestination: (roomId) => `/topic/chat/${roomId}/assistant-stream`,
322
324
 
323
325
  // Room list (카톡 스타일 리스트 실시간 업데이트)
324
326
  // 서버가 convertAndSendToUser(userId, "/queue/rooms", event) 로 전송
@@ -15,6 +15,7 @@ const CHAT_EVENT_MAP = [
15
15
  ['messageTranslated', 'messageTranslated'],
16
16
  ['messageRead', 'messageRead'],
17
17
  ['typing', 'typing'],
18
+ ['assistantProgress', 'assistantProgress'],
18
19
  ['memberJoined', 'memberJoined'],
19
20
  ['memberLeft', 'memberLeft'],
20
21
  ['roomSubscribed', 'roomSubscribed'],
package/types/index.d.ts CHANGED
@@ -236,7 +236,7 @@ export interface AssistantPersonaResponse {
236
236
 
237
237
  /**
238
238
  * 어시스턴트 도구 실행 결과 — 서버 {@code AssistantToolResult}.
239
- * {@link ChatClient.summarizeWithAssistant} / {@link ChatClient.translateWithAssistant} 반환.
239
+ * {@link ChatClient.summarizeWithAssistant} 반환.
240
240
  */
241
241
  export interface AssistantToolResult {
242
242
  /**
@@ -254,6 +254,16 @@ export interface AssistantToolResult {
254
254
  detail: string | null;
255
255
  }
256
256
 
257
+ /** {@link ChatClient.translateWithAssistant} 반환 — 요청자 전용 온디맨드 번역 응답. */
258
+ export interface OnDemandTranslationResponse {
259
+ /** 번역된 텍스트. */
260
+ translatedText: string;
261
+ /** 번역 대상 원본 메시지의 {@code messageId}. */
262
+ sourceMessageId: string;
263
+ /** 요청한 목표 언어. */
264
+ targetLang: string;
265
+ }
266
+
257
267
  /** {@link ChatClient.summarizeWithAssistant} 옵션 — 서버 {@code SummarizeToolRequest}. */
258
268
  export interface SummarizeToolRequest {
259
269
  /** 요약을 수행할 어시스턴트 ID ({@link AssistantPersonaResponse.id}). 필수. */
@@ -266,12 +276,10 @@ export interface SummarizeToolRequest {
266
276
 
267
277
  /** {@link ChatClient.translateWithAssistant} 옵션 — 서버 {@code TranslateToolRequest}. */
268
278
  export interface TranslateToolRequest {
269
- /** 번역을 수행할 어시스턴트 ID ({@link AssistantPersonaResponse.id}). 필수. */
270
- personaId: string;
271
279
  /** 번역 대상 메시지의 {@code messageId}. 필수. */
272
280
  sourceMessageId: string;
273
- /** 목표 언어 (예: {@code 'en'}). 미지정 시 서버 정책. */
274
- targetLang?: string;
281
+ /** 목표 언어 (예: {@code 'en'}). 필수. */
282
+ targetLang: string;
275
283
  }
276
284
 
277
285
  /**
@@ -378,6 +386,7 @@ export const WebSocketPaths: {
378
386
  getChatDestination(roomId: string): string;
379
387
  getChatReadDestination(roomId: string): string;
380
388
  getChatTypingDestination(roomId: string): string;
389
+ getChatAssistantStreamDestination(roomId: string): string;
381
390
  readonly ROOM_LIST_USER_DESTINATION: string;
382
391
  getWebRTCDestination(roomId: string): string;
383
392
  getWebRTCUserDestination(): string;
@@ -735,6 +744,42 @@ export interface TypingEvent {
735
744
  senderType: 'USER' | 'ASSISTANT';
736
745
  }
737
746
 
747
+ /**
748
+ * AI 진행 표시 이벤트 — `/topic/chat/{roomId}/assistant-stream` 의 PHASE/DONE.
749
+ *
750
+ * typing 과 별개의 가산 UX 레이어(설계 ASSISTANT_PROGRESS_STREAMING_DESIGN.md). 서버가 typing 도 이중발행하므로
751
+ * 구버전 SDK 는 본 이벤트를 못 받아도 기존 typing 으로 정상 동작한다. UI 는 `active:true` 면 `label`(또는 `phase`)
752
+ * 로 단계를 표시하고, `active:false`(DONE·어시 메시지 도착·TTL 만료) 면 해제한다.
753
+ *
754
+ * 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행 — 멘션/버튼/14인 경로는 본 이벤트 없이 typing 만.
755
+ */
756
+ export interface AssistantProgressEvent {
757
+ /** 채팅방 ID. */
758
+ roomId: string;
759
+ /** 어시 응답 1회분 식별(멀티 응답/중복 dedup). 일부 종료 emit 에선 null. */
760
+ streamId: string | null;
761
+ /** 진행 주체 페르소나 ID. */
762
+ personaId: string | null;
763
+ /** 진행 단계 — `active:true` 일 때. 종료 시 null. */
764
+ phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
765
+ /** phase 의 기본 표시 라벨(예: "생각 중…"). SDK 매핑 — 커스텀하려면 `phase` 사용. 종료 시 null. */
766
+ label: string | null;
767
+ /** true=진행중(PHASE/DELTA) / false=종료(DONE·메시지 도착·TTL). */
768
+ active: boolean;
769
+ /** 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더. PHASE-only/종료 시 null. */
770
+ text?: string | null;
771
+ /** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. */
772
+ delta?: string | null;
773
+ /** 토큰 청크 순서/누락 감지(B) — DELTA 수신 시만. */
774
+ seq?: number | null;
775
+ /** 종료(DONE) 결과 — DONE 수신 시만. */
776
+ status?: 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT' | null;
777
+ /** 종료(DONE, SUCCEEDED) 시 영속된 최종 메시지 ID. */
778
+ messageId?: string | null;
779
+ /** 종료 사유 — 'message'(어시 메시지 도착) / 'timeout'(TTL). DONE 수신 해제엔 없음. */
780
+ reason?: 'message' | 'timeout' | null;
781
+ }
782
+
738
783
  // ============================================================================
739
784
  // Options — constructor & method
740
785
  // ============================================================================
@@ -1056,7 +1101,7 @@ export interface RetentionCleanupPayload {
1056
1101
  * TalkFlowClient 이벤트 맵 — 런타임 {@code TalkFlowClient.js} 의 {@code this.emit(...)} 호출 기반.
1057
1102
  *
1058
1103
  * <p><b>Connection 관련</b>: stateChange, connected, disconnected, reconnecting, connectionError, tokenSet, loggedOut</p>
1059
- * <p><b>Chat 관련 (ChatClient 이벤트를 이 이름으로 re-emit)</b>: chatMessage, newChatMessage, messageUpdated, messageDeleted, reactionChanged, linkPreviewAttached, messageRead, typing, memberJoined, memberLeft, roomSubscribed, roomUnsubscribed, roomListSubscribed, roomListUnsubscribed, roomListUpdate, roomListMessage, roomListCreated, roomListJoined, roomListLeft, roomListSelfLeft, roomListRoomUpdated, retentionCleanup</p>
1104
+ * <p><b>Chat 관련 (ChatClient 이벤트를 이 이름으로 re-emit)</b>: chatMessage, newChatMessage, messageUpdated, messageDeleted, reactionChanged, linkPreviewAttached, messageRead, typing, assistantProgress, memberJoined, memberLeft, roomSubscribed, roomUnsubscribed, roomListSubscribed, roomListUnsubscribed, roomListUpdate, roomListMessage, roomListCreated, roomListJoined, roomListLeft, roomListSelfLeft, roomListRoomUpdated, retentionCleanup</p>
1060
1105
  * <p><b>Push 관련</b>: pushEnabled, pushFailed, pushNotification</p>
1061
1106
  * <p><b>WebRTC 관련</b>: localStreamStarted, localStreamStopped, remoteTrack, screenShareStarted, screenShareEnded, deviceChange, mediaStateChanged, callStarted, callEnded, callRequested, callAccepted, callRejected, callCancelled, callInvitation, callBusy, incomingCall, incomingCallWhileBusy, userJoined, userLeft, participantLeft, participantMediaState, peerConnected, peerDisconnected, peerClosed, webrtcError</p>
1062
1107
  */
@@ -1085,6 +1130,7 @@ export interface TalkFlowClientEvents {
1085
1130
  messageTranslated: ChatMessageReceivedPayload;
1086
1131
  messageRead: MessageReadEventPayload;
1087
1132
  typing: TypingEvent;
1133
+ assistantProgress: AssistantProgressEvent;
1088
1134
  memberJoined: MemberChangePayload;
1089
1135
  memberLeft: MemberChangePayload;
1090
1136
  roomSubscribed: { roomId: string };
@@ -1198,6 +1244,7 @@ export interface ChatClientEvents {
1198
1244
  retentionCleanup: RetentionCleanupPayload;
1199
1245
  messageRead: MessageReadEventPayload;
1200
1246
  typing: TypingEvent;
1247
+ assistantProgress: AssistantProgressEvent;
1201
1248
  }
1202
1249
 
1203
1250
  /**
@@ -1261,8 +1308,8 @@ export class ChatClient implements SdkEventEmitter<ChatClientEvents> {
1261
1308
  rateAssistantMessage(roomId: string, messageId: string, rating: number, comment?: string): Promise<void>;
1262
1309
  /** 어시스턴트로 대화 요약 (버튼 진입점, 동기). */
1263
1310
  summarizeWithAssistant(roomId: string, options: SummarizeToolRequest): Promise<AssistantToolResult>;
1264
- /** 어시스턴트로 특정 메시지 번역 (버튼 진입점, 동기). */
1265
- translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<AssistantToolResult>;
1311
+ /** 특정 메시지 온디맨드 번역 (버튼 진입점, 동기, 요청자 전용 응답). */
1312
+ translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<OnDemandTranslationResponse>;
1266
1313
  /** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
1267
1314
  getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
1268
1315
  /** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */
@@ -1602,8 +1649,8 @@ export default class TalkFlowClient implements SdkEventEmitter<TalkFlowClientEve
1602
1649
  rateAssistantMessage(roomId: string, messageId: string, rating: number, comment?: string): Promise<void>;
1603
1650
  /** 어시스턴트로 대화 요약 (버튼 진입점, 동기). */
1604
1651
  summarizeWithAssistant(roomId: string, options: SummarizeToolRequest): Promise<AssistantToolResult>;
1605
- /** 어시스턴트로 특정 메시지 번역 (버튼 진입점, 동기). */
1606
- translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<AssistantToolResult>;
1652
+ /** 특정 메시지 온디맨드 번역 (버튼 진입점, 동기, 요청자 전용 응답). */
1653
+ translateWithAssistant(roomId: string, options: TranslateToolRequest): Promise<OnDemandTranslationResponse>;
1607
1654
  /** 방 PM 프롬프트 레이어 조회 — PM_BACKSTAGE 방 전용, 참여자 전체. 미등록 시 404. */
1608
1655
  getRoomPmPrompt(roomId: string): Promise<PmPromptLayerResponse>;
1609
1656
  /** 방 PM 프롬프트 레이어 등록/수정 (upsert, 새 버전 자동 기록) — 방 주인(OWNER) 전용. content 1~2,000자. */