@vibexnpm/talkx 4.0.0 → 5.1.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/dist/index.d.ts CHANGED
@@ -91,6 +91,20 @@ export const ChatMessageType: {
91
91
  readonly SYSTEM: 'SYSTEM';
92
92
  };
93
93
 
94
+ /**
95
+ * AI 의사결정 카드 렌더 상한 — 서버가 강제하는 값의 사본(참고용, 클라 검증 불필요).
96
+ *
97
+ * <p>🔴 카드는 <b>새 {@code chatMessageType} 이 아니다</b> — 타입은 {@code TEXT} 이고 선택 요청은
98
+ * {@link ChatMessage#decisionRequest} 필드로 온다. 모르는 렌더러는 텍스트로 그리면 되고 그게 정상이다.
99
+ * 카드를 그린다면 {@code content} 를 다시 그리지 마라(같은 내용이 두 번 보인다).</p>
100
+ */
101
+ export const DecisionCardLimits: {
102
+ readonly MIN_OPTIONS: 2;
103
+ readonly MAX_OPTIONS: 5;
104
+ readonly LABEL_MAX_LENGTH: 80;
105
+ readonly QUESTION_MAX_LENGTH: 200;
106
+ };
107
+
94
108
  /**
95
109
  * 서버가 보낼 수 있는 메시지 타입 값 전체 — {@link ChatMessageType} 상수와 같은 집합.
96
110
  * <p>수신 {@link ChatMessage#chatMessageType} 비교 시 이 union 을 사용한다.</p>
@@ -728,6 +742,42 @@ export interface ChatMessage {
728
742
  translations?: Record<string, string> | null;
729
743
  /** 번역 당시 content 해시 — stale 판별용 (내부). */
730
744
  translationsOf?: string | null;
745
+ /**
746
+ * AI 의 구조화 선택 요청(카드). 어시스턴트 메시지에만 존재하고, 일반 메시지에는 부재.
747
+ *
748
+ * <p><b>모르면 그냥 텍스트로 그려라 — 그게 정상 동작이다.</b> {@code content} 에 질문과 후보가
749
+ * 사람이 읽는 형태로 들어 있어 카드를 못 그려도 대화가 그대로 읽힌다.</p>
750
+ *
751
+ * <p>🔴 반대로 카드를 그린다면 {@code content} 를 <b>다시 그리지 마라</b> — 질문과 후보가 두 번 보인다.</p>
752
+ */
753
+ decisionRequest?: DecisionRequest | null;
754
+ /** 사용자의 선택. 어느 카드인지는 {@code replyTo} 가 특정한다. */
755
+ decisionResponse?: DecisionResponse | null;
756
+ }
757
+
758
+ /**
759
+ * AI 가 낸 선택 요청 — 질문 + 후보 2~5개.
760
+ *
761
+ * <p>후보는 <b>서버가 방의 실제 첨부에서</b> 만든다(클라가 만들거나 편집하지 않는다).</p>
762
+ */
763
+ export interface DecisionRequest {
764
+ /** 사용자에게 보일 질문 — 최대 200자. */
765
+ question: string;
766
+ /** 선택지 2~5개. 라벨은 이 카드 안에서 유일하다. */
767
+ options: DecisionOption[];
768
+ }
769
+
770
+ export interface DecisionOption {
771
+ /** 불투명 식별자 — 선택 전송 시 {@code decisionResponse.optionId} 로 <b>그대로</b> 돌려보낸다. */
772
+ id: string;
773
+ /** 버튼에 보일 이름 — 최대 80자(초과분은 서버가 말줄임). */
774
+ label: string;
775
+ /** 보조 설명(업로드 시각 등). 없을 수 있다. */
776
+ hint?: string | null;
777
+ }
778
+
779
+ export interface DecisionResponse {
780
+ optionId: string;
731
781
  }
732
782
 
733
783
  /** 채팅방 참가자. */
@@ -940,35 +990,103 @@ export interface TypingEvent {
940
990
  *
941
991
  * typing 과 별개의 가산 UX 레이어(설계 ASSISTANT_PROGRESS_STREAMING_DESIGN.md). 서버가 typing 도 이중발행하므로
942
992
  * 구버전 SDK 는 본 이벤트를 못 받아도 기존 typing 으로 정상 동작한다. UI 는 `active:true` 면 `label`(또는 `phase`)
943
- * 로 단계를 표시하고, `active:false`(DONE·어시 메시지 도착·TTL 만료) 면 해제한다.
993
+ * 로 단계를 표시하고, `active:false`(DONE·TTL 만료) 면 해제한다.
994
+ *
995
+ * ⚠️ **v5.0.0 breaking — 이 이벤트는 `streamId` 단위다.** 한 방에 동시 스트림이 실재하므로(PM 방에서 트리거가
996
+ * 겹치면 각각 streamId 를 받는다) `active:false` 는 **그 stream 하나**를 뜻한다. 방마다 버블 하나를 그리는
997
+ * 기존 코드에 그대로 물리면 **같은 방의 다른 살아 있는 버블까지 숨긴다**. 둘 중 하나를 택한다.
998
+ *
999
+ * - (권장) `roomId + streamId` 를 키로 버블을 관리한다 — 최종 목표 형태.
1000
+ * - (이행용 다리) {@link AssistantRoomProgressEvent} 를 대신 구독한다. 리스너 이름만 바꾸면 기존 단일 버블
1001
+ * 렌더가 그대로 동작한다.
944
1002
  *
945
1003
  * 현재는 PM_BACKSTAGE(방당 단일 PM) 만 phase 를 발행 — 멘션/버튼/14인 경로는 본 이벤트 없이 typing 만.
946
1004
  */
947
1005
  export interface AssistantProgressEvent {
948
1006
  /** 채팅방 ID. */
949
1007
  roomId: string;
950
- /** 어시 응답 1회분 식별(멀티 응답/중복 dedup). 일부 종료 emit 에선 null. */
951
- streamId: string | null;
1008
+ /**
1009
+ * 어시 응답 1회분 식별 — **v5.0.0 부터 항상 non-null**.
1010
+ *
1011
+ * UI 는 `roomId + streamId` 로 버블을 키잉한다. SDK 는 `streamId` 없는 서버 이벤트를 아예 버리므로
1012
+ * 이 값이 비어 오는 emit 은 없다(v4.x 는 일부 종료 emit 에서 null 이 올 수 있었다).
1013
+ */
1014
+ streamId: string;
952
1015
  /** 진행 주체 페르소나 ID. */
953
1016
  personaId: string | null;
954
1017
  /** 진행 단계 — `active:true` 일 때. 종료 시 null. */
955
1018
  phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
956
1019
  /** phase 의 기본 표시 라벨(예: "생각 중…"). SDK 매핑 — 커스텀하려면 `phase` 사용. 종료 시 null. */
957
1020
  label: string | null;
958
- /** true=진행중(PHASE/DELTA) / false=종료(DONE·메시지 도착·TTL). */
1021
+ /** true=진행중(PHASE/DELTA) / false=종료(DONE·TTL). */
959
1022
  active: boolean;
960
- /** 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더. PHASE-only/종료 시 null. */
1023
+ /**
1024
+ * 토큰 스트림 누적 텍스트(B) — DELTA 수신 시 지금까지 모인 전체. 버블에 그대로 렌더.
1025
+ * PHASE-only/종료 시 null. **`textAvailable:false` 면 항상 null** — 그 땐 `label` 을 그린다.
1026
+ */
961
1027
  text?: string | null;
962
- /** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. */
1028
+ /** 이번 토큰 청크(B) — append 렌더를 선호할 때. DELTA 수신 시만. `textAvailable:false` 면 null. */
963
1029
  delta?: string | null;
964
1030
  /** 토큰 청크 순서/누락 감지(B) — DELTA 수신 시만. */
965
1031
  seq?: number | null;
1032
+ /**
1033
+ * 본문을 신뢰할 수 있는가 (v4.2.0 추가).
1034
+ *
1035
+ * `false` = 앞부분을 받은 적이 없다 — 스트리밍 도중 재입장/새로고침(late join)했거나 WS 순단으로 `seq` 에
1036
+ * 구멍이 났다는 뜻. 이때 `text`/`delta` 는 null 로 오고 **이후 청크도 누적되지 않는다**.
1037
+ * UI 는 본문 대신 `label`(예: "작성 중…")을 표시하고 최종 메시지를 기다리면 된다.
1038
+ *
1039
+ * 예전 동작은 이 경우에도 받은 조각을 누적본처럼 넘겨서 **잘린 답변**이 화면에 그려졌다.
1040
+ */
1041
+ textAvailable?: boolean;
966
1042
  /** 종료(DONE) 결과 — DONE 수신 시만. */
967
1043
  status?: 'SUCCEEDED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT' | null;
968
1044
  /** 종료(DONE, SUCCEEDED) 시 영속된 최종 메시지 ID. */
969
1045
  messageId?: string | null;
970
- /** 종료 사유 — 'message'(어시 메시지 도착) / 'timeout'(TTL). DONE 수신 해제엔 없음. */
971
- reason?: 'message' | 'timeout' | null;
1046
+ /**
1047
+ * 종료 사유 — 'timeout'(TTL 만료). DONE 수신 해제엔 없음.
1048
+ *
1049
+ * v4.2.0 부터 `'message'` 는 emit 되지 않았고(어시 메시지 도착은 진행 표시를 끄지 않는다 —
1050
+ * 종료 SSOT = DONE + TTL), **v5.0.0 에서 유니온에서도 제거**했다.
1051
+ */
1052
+ reason?: 'timeout' | null;
1053
+ /** 이 stream 의 마지막 seq — 스냅샷 병합 커서(v5.0.0). DELTA 0건이면 -1. */
1054
+ lastSeq?: number;
1055
+ /**
1056
+ * `true` = 서버 스냅샷으로 **복원된** 진행분(v5.1.0).
1057
+ *
1058
+ * 스트리밍 도중 재입장했거나 새로 들어와서, 구독 전에 이미 발행된 앞부분을 REST 스냅샷으로 받아온
1059
+ * 경우다. UI 가 "이어보는 중" 같은 표시를 하고 싶을 때만 쓰면 되고, 렌더 로직은 평소와 같다.
1060
+ */
1061
+ resumed?: boolean;
1062
+ }
1063
+
1064
+ /**
1065
+ * 방 단위 AI 진행 표시 집계 (v5.0.0 신설).
1066
+ *
1067
+ * <p>{@link AssistantProgressEvent} 가 `streamId` 단위로 바뀌면서, **방마다 임시 버블 하나**를 그리는 기존
1068
+ * UI 를 위한 이행용 다리로 제공한다. 리스너 이름만 `assistantProgress` → `assistantRoomProgress` 로 바꾸면
1069
+ * 기존 렌더 코드가 그대로 동작한다.</p>
1070
+ *
1071
+ * <p>`active` 는 방에 살아 있는 stream 이 **하나라도** 있으면 true 다. 그래서 동시 응답 중 하나가 끝나도
1072
+ * 버블이 사라지지 않는다. 표시 내용(`text`/`label`/`phase`)은 **가장 최근에 갱신된** stream 을 따른다.</p>
1073
+ */
1074
+ export interface AssistantRoomProgressEvent {
1075
+ /** 채팅방 ID. */
1076
+ roomId: string;
1077
+ /** 방에 살아 있는 stream 이 하나라도 있으면 true. */
1078
+ active: boolean;
1079
+ /** 살아 있는 stream 개수. 2 이상이면 동시 응답 중이다. */
1080
+ streamCount: number;
1081
+ /** 표시 기준이 된 stream (가장 최근 갱신). 없으면 null. */
1082
+ streamId: string | null;
1083
+ personaId: string | null;
1084
+ phase: 'THINKING' | 'SEARCHING' | 'PLANNING' | 'WRITING' | null;
1085
+ label: string | null;
1086
+ /** 표시 기준 stream 의 누적 본문. `textAvailable:false` 거나 진행 중이 아니면 null. */
1087
+ text?: string | null;
1088
+ /** 표시 기준 stream 의 본문 신뢰 여부 — false 면 `label` 을 그린다. */
1089
+ textAvailable?: boolean;
972
1090
  }
973
1091
 
974
1092
  // ============================================================================
@@ -1011,6 +1129,13 @@ export interface TalkFlowClientOptions {
1011
1129
  * <p>복구된 메시지는 일반 `chatMessage` 이벤트로 재생되므로 수신 코드를 바꿀 필요가 없다.</p>
1012
1130
  */
1013
1131
  syncOnReconnect?: boolean;
1132
+ /**
1133
+ * 방 구독·재연결 시 진행 중 AI 스트림을 서버 스냅샷으로 이어받을지. 기본 true (v5.1.0).
1134
+ *
1135
+ * <p>스트림은 Pub/Sub 라 구독 시점 이후만 오므로, 스트리밍 도중 재입장하면 앞부분을 받을 방법이
1136
+ * 스냅샷뿐이다. `syncOnReconnect`(메시지 갭 복구)와 <b>별개 경로</b>다 — 그쪽을 꺼도 이건 돈다.</p>
1137
+ */
1138
+ assistantStreamReplay?: boolean;
1014
1139
  /** 갭 복구 첫 페이지에만 적용되는 되감기 폭 (ms). 기본 3000. 서버가 상한 clamp 후 1회 차감. */
1015
1140
  rewindMillis?: number;
1016
1141
  /** 갭 복구 페이지 상한. 기본 5. 초과 시 최신 페이지 재로드로 전환하고 `gapTruncated` 를 발행. */
@@ -1145,6 +1270,11 @@ export interface SendMessageData {
1145
1270
  */
1146
1271
  separateFiles?: boolean;
1147
1272
  replyToMessageId?: string;
1273
+ /**
1274
+ * 의사결정 카드의 선택 — 카드 버튼을 눌렀을 때만. {@link ChatClient#sendDecisionChoice} 가
1275
+ * {@code replyToMessageId}/{@code message}/{@code optionId} 셋을 한 번에 맞춰 준다.
1276
+ */
1277
+ decisionResponse?: DecisionResponse;
1148
1278
  }
1149
1279
 
1150
1280
  export interface SendMessageResult {
@@ -1383,6 +1513,7 @@ export interface TalkFlowClientEvents {
1383
1513
  messageRead: MessageReadEventPayload;
1384
1514
  typing: TypingEvent;
1385
1515
  assistantProgress: AssistantProgressEvent;
1516
+ assistantRoomProgress: AssistantRoomProgressEvent;
1386
1517
  /** Rolling 누적 요약 완료/실패 통지 (transient) — refreshSummary 후. */
1387
1518
  summaryUpdate: SummaryUpdateEvent;
1388
1519
  memberJoined: MemberChangePayload;
@@ -1518,6 +1649,7 @@ export interface ChatClientEvents {
1518
1649
  messageRead: MessageReadEventPayload;
1519
1650
  typing: TypingEvent;
1520
1651
  assistantProgress: AssistantProgressEvent;
1652
+ assistantRoomProgress: AssistantRoomProgressEvent;
1521
1653
  /** Rolling 누적 요약 완료/실패 통지 (transient) — refreshSummary 후. */
1522
1654
  summaryUpdate: SummaryUpdateEvent;
1523
1655
  }
@@ -1637,6 +1769,11 @@ export class ChatClient implements SdkEventEmitter<ChatClientEvents> {
1637
1769
  sendTextMessageOptimistic(roomId: string, message: string): OptimisticSendMessageHandle;
1638
1770
  sendReply(roomId: string, message: string, replyToMessageId: string): Promise<SendMessageResult>;
1639
1771
  sendReplyOptimistic(roomId: string, message: string, replyToMessageId: string): OptimisticSendMessageHandle;
1772
+ /**
1773
+ * 의사결정 카드의 선택 전송 — 답글 + 라벨 본문 + optionId 를 한 번에 맞춰 보낸다.
1774
+ * 직접 조립하지 말고 이 메서드를 쓰는 편이 안전하다(셋 중 하나만 빠져도 계약이 깨진다).
1775
+ */
1776
+ sendDecisionChoice(roomId: string, card: ChatMessage, option: DecisionOption): Promise<SendMessageResult>;
1640
1777
 
1641
1778
  uploadFile(roomId: string, file: File, options?: FileUploadOptions): Promise<SendFileInfo>;
1642
1779
  sendFileMessage(roomId: string, files: File | File[], options?: SendFileMessageOptions): Promise<SendMessageResult>;
@@ -2000,6 +2137,11 @@ export default class TalkFlowClient implements SdkEventEmitter<TalkFlowClientEve
2000
2137
  sendTextMessageOptimistic(roomId: string, message: string): OptimisticSendMessageHandle;
2001
2138
  sendReply(roomId: string, message: string, replyToMessageId: string): Promise<SendMessageResult>;
2002
2139
  sendReplyOptimistic(roomId: string, message: string, replyToMessageId: string): OptimisticSendMessageHandle;
2140
+ /**
2141
+ * 의사결정 카드의 선택 전송 — 답글 + 라벨 본문 + optionId 를 한 번에 맞춰 보낸다.
2142
+ * 직접 조립하지 말고 이 메서드를 쓰는 편이 안전하다(셋 중 하나만 빠져도 계약이 깨진다).
2143
+ */
2144
+ sendDecisionChoice(roomId: string, card: ChatMessage, option: DecisionOption): Promise<SendMessageResult>;
2003
2145
 
2004
2146
  uploadFile(roomId: string, file: File, options?: FileUploadOptions): Promise<SendFileInfo>;
2005
2147
  sendFileMessage(roomId: string, files: File | File[], options?: SendFileMessageOptions): Promise<SendMessageResult>;