(content, source, conversationId)
| 113 | // ─── ChatMessage (for RawGetChatMessage) ─────────────────── |
| 114 | |
| 115 | function buildChatMessage(content, source, conversationId) { |
| 116 | const parts = [ |
| 117 | writeStringField(1, randomUUID()), // message_id |
| 118 | writeVarintField(2, source), // source enum |
| 119 | writeMessageField(3, encodeTimestamp()), // timestamp |
| 120 | writeStringField(4, conversationId), // conversation_id |
| 121 | ]; |
| 122 | |
| 123 | if (source === SOURCE.ASSISTANT) { |
| 124 | // Assistant goes in ChatMessage.action (field 6), not .intent (field 5). |
| 125 | // Proto: ChatMessageAction { ChatMessageActionGeneric generic = 1; } |
| 126 | // ChatMessageActionGeneric { string text = 1; } |
| 127 | // Previous code wrote a raw string into field 5 which happens to share |
| 128 | // wire type (length-delimited) with the expected message, so short |
| 129 | // replies slipped through parsing by coincidence — real multi-turn |
| 130 | // conversations tripped the LS with "invalid wire-format data". |
| 131 | const actionGeneric = writeStringField(1, content); // ChatMessageActionGeneric.text |
| 132 | const action = writeMessageField(1, actionGeneric); // ChatMessageAction.generic |
| 133 | parts.push(writeMessageField(6, action)); |
| 134 | } else { |
| 135 | // User/System/Tool use ChatMessageIntent { IntentGeneric { text } } |
| 136 | const intentGeneric = writeStringField(1, content); // IntentGeneric.text |
| 137 | const intent = writeMessageField(1, intentGeneric); // ChatMessageIntent.generic |
| 138 | parts.push(writeMessageField(5, intent)); |
| 139 | } |
| 140 | |
| 141 | return Buffer.concat(parts); |
| 142 | } |
| 143 | |
| 144 | // ─── RawGetChatMessageRequest ────────────────────────────── |
| 145 |
no test coverage detected