@kyyinfinite/lumina 1.0.0 → 1.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.
@@ -1,42 +1,5 @@
1
- /**
2
- * @file inline-entity.js
3
- * @module lumina/parsers/inline-entity
4
- *
5
- * extractInlineEntities — parses three markdown-like inline syntaxes inside
6
- * AI-rich text content and returns the rewritten text plus proto-ready
7
- * inline-entity metadata.
8
- *
9
- * Supported syntaxes:
10
- *
11
- * [text](url) → hyperlink (untrusted if `url` starts with `!`)
12
- * [](url) → citation (renders as a numeric superscript)
13
- * [text]<url|w|h|fh|p> → latex (math image with dimensions)
14
- *
15
- * The rewritten text replaces each `[...]` span with `{{KEY}}...{{/KEY}}`
16
- * placeholders that WhatsApp's rich-response renderer substitutes back with
17
- * the entity's interactive UI element.
18
- *
19
- * This is the modernised, de-obfuscated successor to the legacy `extractIE`
20
- * function in `_build-m.js`. Key changes:
21
- *
22
- * 1. The `NIXEL_` prefix (obfuscated as `\u004E\u0049\u0058\u0045\u004C_`)
23
- * is replaced by the configurable `LUMINA_` prefix.
24
- * 2. The off-by-one bug on citation keys is fixed: every counter is now
25
- * zero-based and keys match `reference_id` consistently.
26
- * 3. Magic numbers (font_height, padding, default width/height) are pulled
27
- * into named constants.
28
- * 4. The `createIE` switch-by-if is replaced by a typed lookup table.
29
- * 5. Pure function, no side-effects — easily unit-testable.
30
- */
31
-
32
1
  import { entityKey } from '../utils/id.js'
33
2
 
34
- /** @typedef {'hyperlink'|'citation'|'latex'} EntityType */
35
-
36
- /**
37
- * Default LaTeX rendering parameters. Sourced from the legacy implementation.
38
- * Adjust if WhatsApp updates its rendering defaults.
39
- */
40
3
  const LATEX_DEFAULTS = Object.freeze({
41
4
  width: 100,
42
5
  height: 100,
@@ -44,11 +7,6 @@ const LATEX_DEFAULTS = Object.freeze({
44
7
  padding: 15,
45
8
  })
46
9
 
47
- /**
48
- * Per-type metadata shaper. Replaces the legacy `createIE` switch.
49
- *
50
- * @type {Record<EntityType, (item: any) => object>}
51
- */
52
10
  const SHAPERS = {
53
11
  hyperlink: (item) => ({
54
12
  key: item.key,
@@ -86,76 +44,35 @@ const SHAPERS = {
86
44
  }),
87
45
  }
88
46
 
89
- /**
90
- * @typedef {object} ExtractOptions
91
- * @property {boolean} [extract=true] Master switch — when false, returns the text unchanged.
92
- * @property {boolean} [hyperlink=true] Parse `[text](url)` syntax.
93
- * @property {boolean} [citation=true] Parse `[](url)` syntax.
94
- * @property {boolean} [latex=true] Parse `[text]<url|w|h|fh|p>` syntax.
95
- * @property {string} [prefix='LUMINA'] Override the entity-key prefix.
96
- */
97
-
98
- /**
99
- * @typedef {object} ExtractResult
100
- * @property {string} text Rewritten text with `{{KEY}}…{{/KEY}}` placeholders.
101
- * @property {Array<{ type: EntityType, key: string, [k: string]: any }>} entities
102
- * Raw per-entity descriptors (one per matched span).
103
- * @property {Array<{ key: string, metadata: object }>} metadata
104
- * Proto-ready inline-entity metadata, ready to attach to a primitive.
105
- */
106
-
107
- /**
108
- * Parse inline entities from text.
109
- *
110
- * @param {string} text
111
- * @param {ExtractOptions} [opts]
112
- * @returns {ExtractResult}
113
- */
114
47
  export function extractInlineEntities(text, opts = {}) {
115
- const {
116
- extract = true,
117
- hyperlink = true,
118
- citation = true,
119
- latex = true,
120
- prefix = 'LUMINA',
121
- } = opts
48
+ const { extract = true, hyperlink = true, citation = true, latex = true } = opts
122
49
 
123
50
  if (!extract || typeof text !== 'string' || text.length === 0) {
124
51
  return { text: text ?? '', entities: [], metadata: [] }
125
52
  }
126
53
 
127
- /** @type {Array<{ type: EntityType, key: string, [k: string]: any }>} */
128
54
  const entities = []
129
- /** @type {Array<{ key: string, metadata: object }>} */
130
55
  const metadata = []
131
56
  let rewritten = ''
132
57
  let last = 0
133
58
  let citationCount = 0
134
59
  let hyperlinkCount = 0
135
60
  let latexCount = 0
136
-
137
- /** Bracket stack — supports nested brackets inside the link text. */
138
61
  const stack = []
139
62
 
140
63
  for (let i = 0; i < text.length; i++) {
141
64
  const ch = text[i]
142
65
 
143
- // Push `[` (escaped `\[` ignored).
144
- if (ch === '[' && text[i - 1] !== '\\') {
145
- stack.push(i)
146
- continue
147
- }
66
+ if (ch === '[' && text[i - 1] !== '\\') { stack.push(i); continue }
148
67
 
149
- // `]` only triggers when followed by `(` (link) or `<` (latex).
150
68
  if (ch === ']' && (text[i + 1] === '(' || text[i + 1] === '<')) {
151
69
  const start = stack.pop()
152
70
  if (start == null) continue
153
71
 
154
72
  const open = text[i + 1]
155
73
  const close = open === '(' ? ')' : '>'
156
- const type = /** @type {EntityType} */ (open === '(' ? 'hyperlink-or-citation' : 'latex')
74
+ const type = open === '(' ? 'hyperlink-or-citation' : 'latex'
157
75
 
158
- // Walk to the matching close, respecting backslash escapes & nesting.
159
76
  let end = i + 2
160
77
  let depth = 1
161
78
  while (end < text.length && depth) {
@@ -163,61 +80,35 @@ export function extractInlineEntities(text, opts = {}) {
163
80
  else if (text[end] === close && text[end - 1] !== '\\') depth--
164
81
  end++
165
82
  }
166
- if (depth) continue // unbalanced — skip.
83
+ if (depth) continue
167
84
 
168
85
  const raw = text.slice(start + 1, i).trim()
169
86
  const url = text.slice(i + 2, end - 1).trim()
170
-
171
- /** @type {{ type: EntityType, key: string, [k: string]: any } | null} */
172
87
  let entry = null
173
88
 
174
89
  if (type === 'latex') {
175
90
  if (!latex) continue
176
- const [txt = '', width = null, height = null, fontHeight = null, padding = null] =
177
- raw.split('|')
91
+ const [txt = '', width = null, height = null, fontHeight = null, padding = null] = raw.split('|')
178
92
  const key = entityKey('LATEX', latexCount++)
179
- entry = {
180
- type: 'latex',
181
- key,
182
- text: txt,
183
- url,
184
- width,
185
- height,
186
- fontHeight,
187
- padding,
188
- }
93
+ entry = { type: 'latex', key, text: txt, url, width, height, fontHeight, padding }
189
94
  rewritten += text.slice(last, start) + `{{${key}}}${txt || 'image'}{{/${key}}}`
190
95
  } else if (raw) {
191
96
  if (!hyperlink) continue
192
- // Untrusted link → URL prefixed with `!`.
193
97
  const trusted = !url.startsWith('!')
194
98
  const cleanUrl = trusted ? url : url.slice(1)
195
99
  const key = entityKey('HYPERLINK', hyperlinkCount++)
196
- entry = {
197
- type: 'hyperlink',
198
- key,
199
- text: raw,
200
- url: cleanUrl,
201
- isTrusted: trusted,
202
- }
100
+ entry = { type: 'hyperlink', key, text: raw, url: cleanUrl, isTrusted: trusted }
203
101
  rewritten += text.slice(last, start) + `{{${key}}}${cleanUrl}{{/${key}}}`
204
102
  } else {
205
103
  if (!citation) continue
206
104
  const key = entityKey('CITATION', citationCount)
207
105
  citationCount++
208
- entry = {
209
- type: 'citation',
210
- key,
211
- referenceId: citationCount, // 1-based, matches key index (zero-based +1)
212
- text: '',
213
- url,
214
- }
106
+ entry = { type: 'citation', key, referenceId: citationCount, text: '', url }
215
107
  rewritten += text.slice(last, start) + `{{${key}}}${url}{{/${key}}}`
216
108
  }
217
109
 
218
110
  last = end
219
111
  i = end - 1
220
-
221
112
  entities.push(entry)
222
113
  const shaped = SHAPERS[entry.type](entry)
223
114
  if (shaped) metadata.push(shaped)
@@ -1,43 +1,13 @@
1
- /**
2
- * @file table-metadata.js
3
- * @module lumina/parsers/table-metadata
4
- *
5
- * Convert a 2-D array of strings into the metadata shape WhatsApp expects
6
- * inside a `richResponseMessage.submessages[].tableMetadata`.
7
- *
8
- * The legacy `AIRich.toTableMetadata` always returned `title: ''` with no
9
- * way to set it. Lumina accepts a `title` option.
10
- */
11
-
12
1
  import { extractInlineEntities } from './inline-entity.js'
13
2
 
14
- /**
15
- * @typedef {object} TableMetadata
16
- * @property {string} title
17
- * @property {Array<{ items: string[], isHeading?: boolean }>} rows Submessage metadata.
18
- * @property {Array<{ is_header: boolean, cells: string[], markdown_cells?: Array<{ text: string, inline_entities?: object[] }> }>} unifiedRows Section primitive rows.
19
- */
20
-
21
- /**
22
- * @param {string[][]} table 2-D array of strings. First row is the header.
23
- * @param {object} [opts]
24
- * @param {string} [opts.title='']
25
- * @param {boolean} [opts.hyperlink=true]
26
- * @param {boolean} [opts.citation=true]
27
- * @param {boolean} [opts.latex=true]
28
- * @returns {TableMetadata}
29
- */
30
3
  export function toTableMetadata(table, opts = {}) {
31
4
  if (!Array.isArray(table) || !table.every((row) => Array.isArray(row) && row.every((c) => typeof c === 'string'))) {
32
5
  throw new TypeError('Table must be a 2-D array of strings')
33
6
  }
34
7
 
35
8
  const { title = '', hyperlink = true, citation = true, latex = true } = opts
36
-
37
9
  const [header, ...rows] = table
38
10
  const maxLen = Math.max(header.length, ...rows.map((r) => r.length))
39
-
40
- /** Pad a row to the table's max column count. */
41
11
  const normalize = (r) => [...r, ...Array(Math.max(0, maxLen - r.length)).fill('')]
42
12
 
43
13
  const unifiedRows = [
@@ -50,12 +20,8 @@ export function toTableMetadata(table, opts = {}) {
50
20
  if (extracted.metadata.length) out.inline_entities = extracted.metadata
51
21
  return out
52
22
  })
53
-
54
23
  const hasInline = markdownCells.some((c) => c.inline_entities?.length)
55
- return {
56
- ...row,
57
- ...(hasInline ? { markdown_cells: markdownCells } : {}),
58
- }
24
+ return { ...row, ...(hasInline ? { markdown_cells: markdownCells } : {}) }
59
25
  })
60
26
 
61
27
  const rowsMeta = unifiedRows.map((r) => ({
@@ -1,93 +1,41 @@
1
- /**
2
- * @file enums.js
3
- * @module lumina/proto/enums
4
- *
5
- * Single source of truth for every magic number / magic string the WhatsApp
6
- * protocol expects inside interactive messages, rich responses, and relay
7
- * nodes. Update the wire format here once, and the whole framework follows.
8
- *
9
- * Replaces the 20+ scattered magic values documented in Tahap-1 analysis:
10
- * - messageType: 1, 2, 4, 5, 9
11
- * - forwardOrigin: 4
12
- * - headerType: 0,1,3,4,5,6
13
- * - native_flow v: '9'
14
- * - botJid: '0@bot'
15
- * - 12x __typename string literals
16
- * - highlightType: 0-5
17
- * - imagine_type: 'IMAGE' | 'ANIMATE'
18
- * - etc.
19
- */
20
-
21
- /**
22
- * Rich-response submessage type. Stored in `richResponseMessage.submessages[].messageType`.
23
- */
24
1
  export const MessageType = Object.freeze({
25
- /** Grid-image rich response. */
26
2
  RICH_RESPONSE: 1,
27
- /** Plain text submessage (also used as fallback for video/product/post). */
28
3
  TEXT: 2,
29
- /** Table metadata submessage. */
30
4
  TABLE: 4,
31
- /** Code-block metadata submessage. */
32
5
  CODE: 5,
33
- /** Reels / contentItems metadata submessage. */
34
6
  REELS: 9,
35
7
  })
36
8
 
37
- /**
38
- * `contextInfo.forwardOrigin` enum used by WhatsApp for AI bot messages.
39
- */
40
9
  export const ForwardOrigin = Object.freeze({
41
10
  BOT: 4,
42
11
  })
43
12
 
44
- /**
45
- * ButtonsMessage headerType enum. The legacy ButtonV2 fallback used 6
46
- * (LOCATION_THUMBNAIL) without documenting it — now explicit.
47
- */
48
13
  export const HeaderType = Object.freeze({
49
14
  EMPTY: 0,
50
15
  TEXT: 1,
51
16
  IMAGE: 3,
52
17
  VIDEO: 4,
53
18
  DOCUMENT: 5,
54
- /** Pre-existing hack: location message with a JPEG thumbnail. */
55
19
  LOCATION_THUMBNAIL: 6,
56
20
  })
57
21
 
58
- /**
59
- * Native-flow biz/interactive node versions.
60
- */
61
22
  export const NativeFlow = Object.freeze({
62
- /** Outer `<interactive v="1">` version. */
63
23
  OUTER_VERSION: '1',
64
- /** Inner `<native_flow v="9">` version. */
65
24
  FLOW_VERSION: '9',
66
- /** Default flow name when mixing button types. */
67
25
  FLOW_NAME_MIXED: 'mixed',
68
26
  TYPE: 'native_flow',
69
27
  BIZ_TAG: 'biz',
70
28
  INTERACTIVE_TAG: 'interactive',
71
29
  })
72
30
 
73
- /**
74
- * Hardcoded bot JID that WhatsApp expects inside `forwardedAiBotMessageInfo`.
75
- */
76
31
  export const BOT_JID = '0@bot'
77
32
 
78
- /**
79
- * Layout view-model kinds. Each becomes `GenAI{Kind}LayoutViewModel`.
80
- */
81
33
  export const LayoutKind = Object.freeze({
82
34
  SINGLE: 'Single',
83
35
  HSCROLL: 'HScroll',
84
36
  ACTION_ROW: 'ActionRow',
85
37
  })
86
38
 
87
- /**
88
- * Code-tokenizer highlight type numbers → string labels. Matches the
89
- * `TYPE_MAP` constant in the legacy AIRich.tokenizer.
90
- */
91
39
  export const HighlightType = Object.freeze({
92
40
  DEFAULT: 0,
93
41
  KEYWORD: 1,
@@ -97,7 +45,6 @@ export const HighlightType = Object.freeze({
97
45
  COMMENT: 5,
98
46
  })
99
47
 
100
- /** String labels corresponding to {@link HighlightType}. */
101
48
  export const HighlightLabel = Object.freeze({
102
49
  0: 'DEFAULT',
103
50
  1: 'KEYWORD',
@@ -107,34 +54,23 @@ export const HighlightLabel = Object.freeze({
107
54
  5: 'COMMENT',
108
55
  })
109
56
 
110
- /** `imagine_type` field on GenAIImaginePrimitive. */
111
57
  export const ImagineType = Object.freeze({
112
58
  IMAGE: 'IMAGE',
113
59
  ANIMATE: 'ANIMATE',
114
60
  })
115
61
 
116
- /** `source_type` field on GenAISearchResultPrimitive. */
117
62
  export const SourceType = Object.freeze({
118
63
  THIRD_PARTY: 'THIRD_PARTY',
119
64
  })
120
65
 
121
- /** `prompt_type` field on GenAIFollowUpSuggestionPillPrimitive. */
122
66
  export const PromptType = Object.freeze({
123
67
  SUGGESTED_PROMPT: 'SUGGESTED_PROMPT',
124
68
  })
125
69
 
126
- /** `sessionTransparencyType` field on botMetadata. */
127
70
  export const SessionTransparencyType = Object.freeze({
128
71
  DEFAULT: 1,
129
72
  })
130
73
 
131
- /**
132
- * All GenAI `__typename` strings. Single source of truth — typo-proof.
133
- *
134
- * NOTE: legacy code had `GenATableUXPrimitive` (missing the second 'I').
135
- * Verified against current WhatsApp Web wire format → the correct spelling
136
- * is `GenAITableUXPrimitive`. Lumina corrects this.
137
- */
138
74
  export const TYPENAME = Object.freeze({
139
75
  MARKDOWN_TEXT: 'GenAIMarkdownTextUXPrimitive',
140
76
  CODE: 'GenAICodeUXPrimitive',
@@ -147,12 +83,19 @@ export const TYPENAME = Object.freeze({
147
83
  METADATA_TEXT: 'GenAIMetadataTextPrimitive',
148
84
  FOLLOW_UP_PILL: 'GenAIFollowUpSuggestionPillPrimitive',
149
85
  UNIFIED_SECTION: 'GenAIUnifiedResponseSection',
150
- /** Layout view-models are suffixed `GenAI{Kind}LayoutViewModel`. */
151
86
  layout(kind) {
152
87
  return `GenAI${kind}LayoutViewModel`
153
88
  },
154
89
  })
155
90
 
91
+ export const SimpleButtonType = Object.freeze({
92
+ reply: 'quick_reply',
93
+ call: 'cta_call',
94
+ reminder: 'cta_reminder',
95
+ cancelReminder: 'cta_cancel_reminder',
96
+ address: 'address_message',
97
+ })
98
+
156
99
  export default {
157
100
  MessageType,
158
101
  ForwardOrigin,
@@ -167,4 +110,5 @@ export default {
167
110
  PromptType,
168
111
  SessionTransparencyType,
169
112
  TYPENAME,
113
+ SimpleButtonType,
170
114
  }
@@ -1,78 +1,17 @@
1
- /**
2
- * @file layouts.js
3
- * @module lumina/proto/layouts
4
- *
5
- * Layout view-model factories. Each layout wraps one or more UX primitives
6
- * inside `view_model`, with the proper `GenAI{Kind}LayoutViewModel` typename.
7
- *
8
- * Replaces the implicit `Array.isArray(data) ? 'primitives' : 'primitive'`
9
- * branching in the legacy `AIRich.newLayout` static method — explicit
10
- * single/hscroll/action-row variants instead.
11
- */
12
-
13
1
  import { LayoutKind, TYPENAME } from './enums.js'
14
2
 
15
- /**
16
- * Wrap a single primitive in a "Single" layout view-model.
17
- *
18
- * @param {object} primitive
19
- * @param {object} [extra] Additional fields merged at top-level.
20
- * @returns {object}
21
- */
22
3
  export function singleLayout(primitive, extra = {}) {
23
- return {
24
- ...extra,
25
- view_model: {
26
- primitive,
27
- __typename: TYPENAME.layout(LayoutKind.SINGLE),
28
- },
29
- }
4
+ return { ...extra, view_model: { primitive, __typename: TYPENAME.layout(LayoutKind.SINGLE) } }
30
5
  }
31
6
 
32
- /**
33
- * Wrap multiple primitives in an "HScroll" (horizontal scroll) layout.
34
- *
35
- * @param {object[]} primitives
36
- * @param {object} [extra]
37
- * @returns {object}
38
- */
39
7
  export function hscrollLayout(primitives, extra = {}) {
40
- return {
41
- ...extra,
42
- view_model: {
43
- primitives,
44
- __typename: TYPENAME.layout(LayoutKind.HSCROLL),
45
- },
46
- }
8
+ return { ...extra, view_model: { primitives, __typename: TYPENAME.layout(LayoutKind.HSCROLL) } }
47
9
  }
48
10
 
49
- /**
50
- * Wrap multiple primitives in an "ActionRow" layout (used for follow-up
51
- * suggestion pills that should appear as a horizontal row of buttons).
52
- *
53
- * @param {object[]} primitives
54
- * @param {object} [extra]
55
- * @returns {object}
56
- */
57
11
  export function actionRowLayout(primitives, extra = {}) {
58
- return {
59
- ...extra,
60
- view_model: {
61
- primitives,
62
- __typename: TYPENAME.layout(LayoutKind.ACTION_ROW),
63
- },
64
- }
12
+ return { ...extra, view_model: { primitives, __typename: TYPENAME.layout(LayoutKind.ACTION_ROW) } }
65
13
  }
66
14
 
67
- /**
68
- * Auto-pick helper. Picks `singleLayout` for a single primitive and
69
- * `hscrollLayout` for arrays — useful for API surfaces that accept both.
70
- *
71
- * @param {LayoutKind} kind Forced kind; overrides auto-detection.
72
- * @param {object|object[]} data
73
- * @param {object} [extra]
74
- * @returns {object}
75
- */
76
15
  export function layoutFor(kind, data, extra = {}) {
77
16
  switch (kind) {
78
17
  case LayoutKind.SINGLE:
@@ -1,85 +1,27 @@
1
- /**
2
- * @file primitives.js
3
- * @module lumina/proto/primitives
4
- *
5
- * Pure factory functions for every GenAI "UX primitive" used by the AI Rich
6
- * Response envelope. Each factory returns a plain object ready to be wrapped
7
- * by a layout view-model (see {@link module:proto/layouts}).
8
- *
9
- * All `__typename` strings are sourced from {@link module:proto/enums.TYPENAME}
10
- * — never hardcode them here.
11
- */
1
+ import { TYPENAME, SourceType, PromptType } from './enums.js'
12
2
 
13
- import { TYPENAME, ImagineType, SourceType, PromptType } from './enums.js'
14
-
15
- /**
16
- * Markdown-text primitive. May carry inline entities extracted by
17
- * `extractInlineEntities`.
18
- *
19
- * @param {string} text
20
- * @param {Array<object>} [inlineEntities]
21
- */
22
3
  export function markdownTextPrimitive(text, inlineEntities) {
23
4
  const primitive = { text, __typename: TYPENAME.MARKDOWN_TEXT }
24
5
  if (inlineEntities?.length) primitive.inline_entities = inlineEntities
25
6
  return primitive
26
7
  }
27
8
 
28
- /**
29
- * Code primitive.
30
- *
31
- * @param {string} language
32
- * @param {Array<{ content: string, type: string }>} unifiedBlocks
33
- */
34
9
  export function codePrimitive(language, unifiedBlocks) {
35
- return {
36
- language,
37
- code_blocks: unifiedBlocks,
38
- __typename: TYPENAME.CODE,
39
- }
10
+ return { language, code_blocks: unifiedBlocks, __typename: TYPENAME.CODE }
40
11
  }
41
12
 
42
- /**
43
- * Table primitive.
44
- *
45
- * @param {Array<object>} unifiedRows Output of `toTableMetadata().unifiedRows`.
46
- */
47
13
  export function tablePrimitive(unifiedRows) {
48
- return {
49
- rows: unifiedRows,
50
- __typename: TYPENAME.TABLE,
51
- }
14
+ return { rows: unifiedRows, __typename: TYPENAME.TABLE }
52
15
  }
53
16
 
54
- /**
55
- * Search-result (sources) primitive.
56
- *
57
- * @param {Array<object>} sources Pre-shaped source objects.
58
- */
59
17
  export function searchResultPrimitive(sources) {
60
- return {
61
- sources,
62
- __typename: TYPENAME.SEARCH_RESULT,
63
- }
18
+ return { sources, __typename: TYPENAME.SEARCH_RESULT }
64
19
  }
65
20
 
66
- /**
67
- * Reel primitive (one per reel item).
68
- *
69
- * @param {object} item Pre-shaped reel descriptor.
70
- */
71
21
  export function reelPrimitive(item) {
72
22
  return { ...item, __typename: TYPENAME.REEL }
73
23
  }
74
24
 
75
- /**
76
- * Imagine primitive — used for both images and videos (animate).
77
- *
78
- * @param {{ url: string, mime_type: string, file_length?: number, duration?: number }} media
79
- * @param {'IMAGE'|'ANIMATE'} kind
80
- * @param {string} [status='READY']
81
- * @param {string} [thumbnailBase64] Base64 JPEG thumbnail (video only).
82
- */
83
25
  export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64) {
84
26
  const primitive = {
85
27
  media,
@@ -91,39 +33,18 @@ export function imaginePrimitive(media, kind, status = 'READY', thumbnailBase64)
91
33
  return primitive
92
34
  }
93
35
 
94
- /**
95
- * Product-card primitive.
96
- *
97
- * @param {object} item Pre-shaped product descriptor (already media-resolved).
98
- */
99
36
  export function productCardPrimitive(item) {
100
37
  return { ...item, __typename: TYPENAME.PRODUCT_CARD }
101
38
  }
102
39
 
103
- /**
104
- * Post primitive.
105
- *
106
- * @param {object} p Pre-shaped post descriptor (already media-resolved).
107
- * @param {boolean} isCarousel
108
- */
109
40
  export function postPrimitive(p, isCarousel) {
110
41
  return { ...p, is_carousel: isCarousel, __typename: TYPENAME.POST }
111
42
  }
112
43
 
113
- /**
114
- * Metadata-text primitive (used for footer / tip).
115
- *
116
- * @param {string} text
117
- */
118
44
  export function metadataTextPrimitive(text) {
119
45
  return { text, __typename: TYPENAME.METADATA_TEXT }
120
46
  }
121
47
 
122
- /**
123
- * Follow-up suggestion pill primitive.
124
- *
125
- * @param {string} text
126
- */
127
48
  export function followUpSuggestionPillPrimitive(text) {
128
49
  return {
129
50
  prompt_text: text,
@@ -132,11 +53,6 @@ export function followUpSuggestionPillPrimitive(text) {
132
53
  }
133
54
  }
134
55
 
135
- /**
136
- * Helper: shape a source entry for `searchResultPrimitive`.
137
- *
138
- * @param {{ iconUrl?: string, url?: string, text?: string, subtitle?: string }} src
139
- */
140
56
  export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle = 'AI' } = {}) {
141
57
  return {
142
58
  source_type: SourceType.THIRD_PARTY,
@@ -147,9 +63,6 @@ export function shapeSourceEntry({ iconUrl = '', url = '', text = '', subtitle =
147
63
  }
148
64
  }
149
65
 
150
- /**
151
- * Helper: shape a single reel item for `reelPrimitive`.
152
- */
153
66
  export function shapeReelEntry(item) {
154
67
  return {
155
68
  reels_url: item.videoUrl ?? item.url ?? '',
@@ -1,26 +1,5 @@
1
- /**
2
- * @file relay-nodes.js
3
- * @module lumina/proto/relay-nodes
4
- *
5
- * Single source of truth for the `additionalNodes` block that Baileys
6
- * `relayMessage` expects when sending interactive (native-flow) messages.
7
- *
8
- * The legacy code duplicated this 12-line block three times (Button.send,
9
- * ButtonV2.send, Carousel.send). Lumina centralises it here.
10
- */
11
-
12
1
  import { NativeFlow } from './enums.js'
13
2
 
14
- /**
15
- * Build the standard biz/interactive/native_flow additional-nodes block.
16
- *
17
- * @param {object} [opts]
18
- * @param {string} [opts.outerVersion=NativeFlow.OUTER_VERSION]
19
- * @param {string} [opts.flowVersion=NativeFlow.FLOW_VERSION]
20
- * @param {string} [opts.flowName=NativeFlow.FLOW_NAME_MIXED]
21
- * @param {boolean} [opts.includeBiz=true] Whether to wrap in the `<biz>` node.
22
- * @returns {Array<object>} Array suitable for `relayMessage`'s `additionalNodes`.
23
- */
24
3
  export function createInteractiveNodes(opts = {}) {
25
4
  const outerVersion = opts.outerVersion ?? NativeFlow.OUTER_VERSION
26
5
  const flowVersion = opts.flowVersion ?? NativeFlow.FLOW_VERSION
@@ -35,19 +14,9 @@ export function createInteractiveNodes(opts = {}) {
35
14
 
36
15
  if (!includeBiz) return [interactive]
37
16
 
38
- return [
39
- {
40
- tag: NativeFlow.BIZ_TAG,
41
- attrs: {},
42
- content: [interactive],
43
- },
44
- ]
17
+ return [{ tag: NativeFlow.BIZ_TAG, attrs: {}, content: [interactive] }]
45
18
  }
46
19
 
47
- /**
48
- * Convenience: returns the same block but with `name: 'native_flow'` only
49
- * (no biz wrapper). Useful for non-business accounts.
50
- */
51
20
  export function createBareInteractiveNodes(opts = {}) {
52
21
  return createInteractiveNodes({ ...opts, includeBiz: false })
53
22
  }