@supericons/mcp 0.4.7 → 0.4.9

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.
@@ -26,6 +26,8 @@ const multilingualSearchAliases = existsSync(multilingualAliasesPath)
26
26
  ? JSON.parse(readFileSync(multilingualAliasesPath, 'utf8')).aliases || []
27
27
  : [];
28
28
  const multilingualExpansionTerms = [...cjkSearchTerms, ...multilingualSearchAliases];
29
+ const SLOT_SEARCH_CONCURRENCY = 2;
30
+ const SLOT_QUERY_CONCURRENCY = 1;
29
31
 
30
32
  const GENERIC_SLOT_WORDS = new Set([
31
33
  'action',
@@ -46,17 +48,109 @@ const GENERIC_SLOT_WORDS = new Set([
46
48
  'view',
47
49
  ]);
48
50
 
49
- const VARIANT_PENALTIES = Object.freeze([
50
- { pattern: /circle/i, penalty: 5 },
51
- { pattern: /square/i, penalty: 4 },
52
- { pattern: /dash/i, penalty: 5 },
53
- { pattern: /badge/i, penalty: 4 },
54
- { pattern: /off/i, penalty: 6 },
55
- { pattern: /slash/i, penalty: 6 },
56
- { pattern: /warning/i, penalty: 4 },
57
- ]);
51
+ const VARIANT_PENALTIES = Object.freeze([
52
+ { token: 'circle', pattern: /circle/i, penalty: 12 },
53
+ { token: 'square', pattern: /square/i, penalty: 12 },
54
+ { token: 'dash', pattern: /dash/i, penalty: 5 },
55
+ { token: 'badge', pattern: /badge/i, penalty: 4 },
56
+ { token: 'brand', pattern: /\bbrand\b/i, penalty: 30 },
57
+ { token: 'off', pattern: /\boff\b/i, penalty: 18 },
58
+ { token: 'slash', pattern: /slash/i, penalty: 8 },
59
+ { token: 'warning', pattern: /warning/i, penalty: 5 },
60
+ { token: 'ai', pattern: /\bai\b/i, penalty: 18 },
61
+ { token: 'add', pattern: /\badd\b/i, penalty: 12 },
62
+ { token: 'plus', pattern: /\bplus\b/i, penalty: 18 },
63
+ { token: 'edit', pattern: /\bedit\b/i, penalty: 12 },
64
+ { token: 'remove', pattern: /\bremove\b/i, penalty: 12 },
65
+ { token: 'delete', pattern: /\bdelete\b/i, penalty: 12 },
66
+ { token: 'minus', pattern: /\bminus\b/i, penalty: 24 },
67
+ { token: 'cancel', pattern: /\bcancel\b/i, penalty: 30 },
68
+ { token: 'x', pattern: /\bx\b/i, penalty: 30 },
69
+ { token: 'exclamation', pattern: /\bexclamation\b/i, penalty: 24 },
70
+ { token: 'discount', pattern: /\bdiscount\b/i, penalty: 24 },
71
+ { token: 'heart', pattern: /\bheart\b/i, penalty: 18 },
72
+ { token: 'zap', pattern: /\bzap\b/i, penalty: 30 },
73
+ { token: 'bolt', pattern: /\bbolt\b/i, penalty: 18 },
74
+ { token: 'wifi', pattern: /\bwifi\b/i, penalty: 12 },
75
+ { token: 'align', pattern: /\balign\b/i, penalty: 12 },
76
+ { token: 'fruit', pattern: /\bfruit\b/i, penalty: 12 },
77
+ { token: 'open', pattern: /\bopen\b/i, penalty: 28 },
78
+ { token: 'unlock', pattern: /\bunlock(?:ed)?\b/i, penalty: 28 },
79
+ { token: 'ban', pattern: /\bban\b/i, penalty: 24 },
80
+ { token: 'blocked', pattern: /\bblocked\b/i, penalty: 24 },
81
+ { token: 'rupee', pattern: /\brupee\b/i, penalty: 18 },
82
+ { token: 'ruble', pattern: /\bruble\b/i, penalty: 18 },
83
+ { token: 'franc', pattern: /\bfranc\b/i, penalty: 18 },
84
+ { token: 'lira', pattern: /\blira\b/i, penalty: 18 },
85
+ { token: 'bitcoin', pattern: /\bbitcoin\b/i, penalty: 18 },
86
+ { token: 'dollar', pattern: /\bdollar\b/i, penalty: 18 },
87
+ { token: 'cent', pattern: /\bcent\b/i, penalty: 18 },
88
+ { token: 'yen', pattern: /\byen\b/i, penalty: 18 },
89
+ { token: 'yuan', pattern: /\byuan\b/i, penalty: 18 },
90
+ { token: 'euro', pattern: /\beuro\b/i, penalty: 18 },
91
+ { token: 'pound', pattern: /\bpound\b/i, penalty: 18 },
92
+ { token: 'down', pattern: /\bdown\b/i, penalty: 8 },
93
+ { token: 'left', pattern: /\bleft\b/i, penalty: 8 },
94
+ { token: 'up', pattern: /\bup\b/i, penalty: 8 },
95
+ { token: 'corner', pattern: /\bcorner\b/i, penalty: 12 },
96
+ { token: 'break', pattern: /\bbreak\b/i, penalty: 18 },
97
+ { token: 'broken', pattern: /\bbroken\b/i, penalty: 18 },
98
+ { token: 'locked', pattern: /\blocked\b/i, penalty: 18 },
99
+ { token: 'orange', pattern: /\borange\b/i, penalty: 12 },
100
+ ]);
101
+
102
+ const VARIANT_TOKENS = new Set(VARIANT_PENALTIES.map((rule) => rule.token));
103
+
104
+ const REQUESTED_VARIANT_ALIASES = Object.freeze({
105
+ off: ['disabled', 'disable', 'muted', 'mute', 'off', 'broken'],
106
+ brand: ['brand', 'logo'],
107
+ open: ['open', 'unlock', 'unlocked'],
108
+ unlock: ['open', 'unlock', 'unlocked'],
109
+ ban: ['ban', 'banned', 'block', 'blocked'],
110
+ blocked: ['ban', 'banned', 'block', 'blocked'],
111
+ add: ['add', 'create', 'plus'],
112
+ plus: ['add', 'create', 'plus'],
113
+ edit: ['edit', 'editing', 'modify', 'pencil'],
114
+ remove: ['remove', 'removed', 'delete', 'minus'],
115
+ delete: ['delete', 'remove', 'trash'],
116
+ minus: ['minus', 'remove', 'removed', 'delete'],
117
+ cancel: ['cancel', 'canceled', 'cancelled', 'disabled', 'remove'],
118
+ x: ['x', 'close', 'remove', 'delete', 'blocked', 'broken', 'off'],
119
+ exclamation: ['alert', 'warning', 'exclamation'],
120
+ discount: ['discount', 'coupon', 'coupons', 'promo', 'promotion', 'deal'],
121
+ heart: ['heart', 'favorite', 'favourite', 'liked', 'wishlist'],
122
+ ai: ['ai', 'smart', 'assistant', 'automation'],
123
+ break: ['break', 'broken'],
124
+ broken: ['break', 'broken'],
125
+ locked: ['lock', 'locked', 'secure', 'security'],
126
+ ruble: ['ruble', 'rouble', 'rub'],
127
+ franc: ['franc', 'chf'],
128
+ lira: ['lira'],
129
+ bitcoin: ['bitcoin', 'btc'],
130
+ dollar: ['dollar', 'usd'],
131
+ yuan: ['yuan', 'cny'],
132
+ });
133
+
134
+ const DIRECT_LOCALIZED_INTENT_RULES = Object.freeze([
135
+ {
136
+ pattern: /通知|お知らせ|알림|notificaciones?|benachrichtigungen?|notifica(?:ç|c)[aã]o|notificações?/iu,
137
+ terms: ['notification', 'notifications'],
138
+ },
139
+ {
140
+ pattern: /关闭|關閉|オフ|꺼짐|끄기|desactivad[ao]s?|apagad[ao]s?|aus\b|deaktiviert|disabled|muted|mute|off/iu,
141
+ terms: ['off', 'disabled'],
142
+ },
143
+ ]);
58
144
 
59
145
  const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
146
+ {
147
+ slotPatterns: [/\busers\b/i, /team/i, /members?/i],
148
+ queryVariants: ['users', 'team', 'user group', 'people'],
149
+ iconPreferences: [
150
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user_circle|user-circle/i, bonus: 66 },
151
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
152
+ ],
153
+ },
60
154
  {
61
155
  slotPatterns: [
62
156
  /profile/i,
@@ -120,6 +214,15 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
120
214
  { pattern: /alarm|alert/i, bonus: 18 },
121
215
  ],
122
216
  },
217
+ {
218
+ priority: 90,
219
+ slotPatterns: [/notifications?\s+off/i, /disabled notifications?/i, /muted notifications?/i, /notification\s+off/i],
220
+ queryVariants: ['bell slash', 'bell off', 'notification off', 'muted bell'],
221
+ iconPreferences: [
222
+ { pattern: /^bell[_-]?(off|slash)$|^bell[_-]?simple[_-]?slash$|notification[_-]?off|notifications?[_-]?off/i, bonus: 180 },
223
+ { pattern: /bell|notification/i, bonus: 22 },
224
+ ],
225
+ },
123
226
  {
124
227
  slotPatterns: [
125
228
  /privacy/i,
@@ -140,8 +243,30 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
140
243
  ],
141
244
  queryVariants: ['shield lock', 'lock', 'shield', 'privacy security', 'security'],
142
245
  iconPreferences: [
143
- { pattern: /shield.*lock|lock.*shield|shield-check|shield-alert|shield/i, bonus: 58 },
144
- { pattern: /^lock$|(?:_|-)lock(?:_|-|$)|key|fingerprint/i, bonus: 28 },
246
+ { pattern: /^shield$|^shield[_-]?check$|shield-check|shield_check/i, bonus: 82 },
247
+ { pattern: /shield.*lock|lock.*shield|shield/i, bonus: 58 },
248
+ { pattern: /^lock$|^lock[_-]?keyhole$|(?:_|-)lock(?:_|-|$)|key|fingerprint/i, bonus: 34 },
249
+ { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -54 },
250
+ ],
251
+ },
252
+ {
253
+ priority: 120,
254
+ slotPatterns: [/unlock/i, /open account/i, /unlocked account/i],
255
+ queryVariants: ['lock open', 'unlock', 'lock keyhole open'],
256
+ iconPreferences: [
257
+ { pattern: /^lock[_-]?open$|^lock[_-]?keyhole[_-]?open$|^unlock(?:[_-]?keyhole)?$/i, bonus: 160 },
258
+ { pattern: /lock.*open|open.*lock|unlock/i, bonus: 80 },
259
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)|file-user/i, bonus: -70 },
260
+ ],
261
+ },
262
+ {
263
+ priority: 120,
264
+ slotPatterns: [/blocked user/i, /banned user/i, /user blocked/i, /user banned/i],
265
+ queryVariants: ['user x', 'user minus', 'ban user', 'blocked user'],
266
+ iconPreferences: [
267
+ { pattern: /^user[_-]?x$|^user[_-]?minus$|user-round-x|user-round-minus|shield-ban|ban/i, bonus: 150 },
268
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 24 },
269
+ { pattern: /^file-user$|^user-2$/i, bonus: -80 },
145
270
  ],
146
271
  },
147
272
  {
@@ -252,14 +377,33 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
252
377
  { pattern: /person|contact/i, bonus: 10 },
253
378
  ],
254
379
  },
255
- {
256
- slotPatterns: [/model/i, /\bai\b/i, /\bml\b/i, /machine learning/i],
257
- queryVariants: ['brain circuit', 'brain cog', 'neural network', 'model'],
380
+ {
381
+ slotPatterns: [/model/i, /\bai\b/i, /\bml\b/i, /machine learning/i],
382
+ queryVariants: ['brain circuit', 'brain cog', 'neural network', 'model'],
258
383
  iconPreferences: [
259
384
  { pattern: /brain-circuit|brain_circuit/i, bonus: 44 },
260
- { pattern: /brain|circuit|nodes/i, bonus: 24 },
261
- ],
262
- },
385
+ { pattern: /brain|circuit|nodes/i, bonus: 24 },
386
+ ],
387
+ },
388
+ {
389
+ priority: 120,
390
+ slotPatterns: [/\bai search\b/i, /smart search/i, /semantic search/i, /assistant search/i],
391
+ queryVariants: ['search ai', 'ai search', 'smart search'],
392
+ iconPreferences: [
393
+ { pattern: /^search.*ai|ai.*search|search[_-]?[23]?[_-]?ai/i, bonus: 150 },
394
+ { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 34 },
395
+ { pattern: /brain|robot|spark/i, bonus: 24 },
396
+ ],
397
+ },
398
+ {
399
+ priority: 90,
400
+ slotPatterns: [/automation/i, /workflow/i, /automate/i, /smart action/i],
401
+ queryVariants: ['automation', 'workflow', 'robot', 'refresh', 'sparkles'],
402
+ iconPreferences: [
403
+ { pattern: /workflow|automation|robot|sparkles?|refresh|settings|adjustments/i, bonus: 90 },
404
+ { pattern: /hand|finger|train/i, bonus: -90 },
405
+ ],
406
+ },
263
407
  {
264
408
  slotPatterns: [/prompt/i],
265
409
  queryVariants: ['message text', 'text input', 'terminal', 'text cursor'],
@@ -300,49 +444,430 @@ const COMMON_SLOT_PREFERENCE_RULES = Object.freeze([
300
444
  { pattern: /chart|signal|radar/i, bonus: 16 },
301
445
  ],
302
446
  },
303
- {
304
- slotPatterns: [/billing/i, /payment/i, /invoice/i, /subscription/i],
305
- queryVariants: ['credit card', 'receipt', 'invoice', 'payment', 'wallet'],
306
- iconPreferences: [
307
- { pattern: /credit-card|receipt|wallet|invoice/i, bonus: 44 },
308
- { pattern: /card|banknote|currency|dollar/i, bonus: 18 },
309
- ],
310
- },
311
- {
312
- slotPatterns: [/reports?/i, /analytics/i, /insights?/i],
313
- queryVariants: ['bar chart', 'file chart', 'analytics chart', 'report document'],
447
+ {
448
+ slotPatterns: [/billing/i, /payment/i, /invoice/i, /subscription/i],
449
+ queryVariants: ['credit card', 'receipt', 'invoice', 'payment', 'wallet'],
450
+ iconPreferences: [
451
+ { pattern: /credit-card|receipt|wallet|invoice/i, bonus: 44 },
452
+ { pattern: /card|banknote|currency|dollar/i, bonus: 18 },
453
+ { pattern: /ruble|franc|lira|bitcoin|yuan/i, bonus: -70 },
454
+ ],
455
+ },
456
+ {
457
+ priority: 90,
458
+ slotPatterns: [/storefront/i, /\bstore\b/i, /\bshop\b/i],
459
+ queryVariants: ['store', 'shop', 'building store', 'shopping bag'],
460
+ iconPreferences: [
461
+ { pattern: /^store$|^storefront$|building-store|shop[_-]?line|store[_-]?\d?[_-]?line|shopping-bag/i, bonus: 120 },
462
+ { pattern: /brand-appstore|restore|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
463
+ ],
464
+ },
465
+ {
466
+ priority: 130,
467
+ slotPatterns: [/(store|shop|storefront)\s+(off|disabled|closed|cancelled|canceled)/i, /(off|disabled|closed|cancelled|canceled)\s+(store|shop|storefront)/i],
468
+ queryVariants: ['store off', 'shopping bag x', 'shopping cart off', 'store disabled'],
469
+ iconPreferences: [
470
+ { pattern: /(store|shop|shopping|bag|cart).*(off|\bx\b|cancel|disabled)|(off|\bx\b|cancel|disabled).*(store|shop|shopping|bag|cart)/i, bonus: 220 },
471
+ { pattern: /^building-store$|^store$|^shopping-bag$/i, bonus: -70 },
472
+ ],
473
+ },
474
+ {
475
+ priority: 90,
476
+ slotPatterns: [/checkout/i],
477
+ queryVariants: ['shopping cart', 'credit card', 'payment', 'receipt checkout'],
478
+ iconPreferences: [
479
+ { pattern: /^shopping[_-]?cart$|shopping-cart$|credit-card|card-pay|payment|receipt/i, bonus: 140 },
480
+ { pattern: /fork|knife|git|branch|merge|forklift|grill|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -140 },
481
+ ],
482
+ },
483
+ {
484
+ priority: 120,
485
+ slotPatterns: [/^cart$/i, /shopping cart/i, /\bbasket\b/i],
486
+ queryVariants: ['shopping cart', 'cart', 'basket'],
487
+ iconPreferences: [
488
+ { pattern: /^shopping[_-]?cart$|^basket$/i, bonus: 220 },
489
+ { pattern: /shopping-cart-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -260 },
490
+ { pattern: /basket-(cog|x|off|discount|dollar|exclamation|heart|minus|pause|plus|question|share|star|bolt|copy|down|up|search|pin)/i, bonus: -180 },
491
+ { pattern: /garden-cart/i, bonus: -180 },
492
+ ],
493
+ },
494
+ {
495
+ priority: 90,
496
+ slotPatterns: [/customers?/i, /shoppers?/i, /buyers?/i],
497
+ queryVariants: ['users', 'customers', 'user group'],
498
+ iconPreferences: [
499
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|users-group|user-group|user-circle|user_circle|^user(?:_|-|$)/i, bonus: 110 },
500
+ { pattern: /ticket|plane|caret|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -120 },
501
+ ],
502
+ },
503
+ {
504
+ priority: 90,
505
+ slotPatterns: [/coupons?/i, /discounts?/i, /promo/i, /promotion/i],
506
+ queryVariants: ['coupon', 'tag percent', 'discount', 'percentage'],
507
+ iconPreferences: [
508
+ { pattern: /coupon|ticket-percent|badge-percent|percent|percentage|tag|shopping-cart-discount|shopping-bag-discount|seal-percent/i, bonus: 120 },
509
+ { pattern: /^percentage-\d+$|bean|candy|cannabis|off|slash|disabled|alert/i, bonus: -180 },
510
+ ],
511
+ },
512
+ {
513
+ priority: 130,
514
+ slotPatterns: [/(cancel|cancelled|canceled|remove|delete)\s+orders?/i, /orders?\s+(cancel|cancelled|canceled|remove|delete)/i],
515
+ queryVariants: ['shopping cart cancel', 'basket cancel', 'cancel order', 'order x'],
516
+ iconPreferences: [
517
+ { pattern: /cancel|\bx\b|remove|delete|minus|trash/i, bonus: 220 },
518
+ { pattern: /shopping|cart|basket|package|receipt|clipboard|list/i, bonus: 20 },
519
+ ],
520
+ },
521
+ {
522
+ priority: 90,
523
+ slotPatterns: [/orders?/i, /purchases?/i],
524
+ queryVariants: ['package', 'receipt', 'clipboard list', 'shopping bag', 'ordered list'],
525
+ iconPreferences: [
526
+ { pattern: /^package$|packages|receipt|shopping-bag$|clipboard|list-ordered|file-invoice|file-text/i, bonus: 115 },
527
+ { pattern: /border|sort|align|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
528
+ ],
529
+ },
530
+ {
531
+ priority: 120,
532
+ slotPatterns: [/shipping/i, /delivery/i, /fulfillment/i, /dispatch/i],
533
+ queryVariants: ['truck delivery', 'delivery', 'shipping', 'package delivery'],
534
+ iconPreferences: [
535
+ { pattern: /^truck[_-]?delivery$/i, bonus: 260 },
536
+ { pattern: /^truck$|package[_-]?export|package[_-]?import/i, bonus: 180 },
537
+ { pattern: /^truck[_-]?return$/i, bonus: -120 },
538
+ { pattern: /cloud-upload|upload|ship-off|cloud|arrow-up/i, bonus: -220 },
539
+ ],
540
+ },
541
+ {
542
+ priority: 120,
543
+ slotPatterns: [/returns?/i, /refunds?/i, /reverse logistics/i],
544
+ queryVariants: ['return', 'refund', 'truck return', 'receipt refund'],
545
+ iconPreferences: [
546
+ { pattern: /^truck[_-]?return$|receipt[_-]?refund|credit-card[_-]?refund|arrow-back|cash.*move.*back/i, bonus: 220 },
547
+ { pattern: /player|chevron|stack|upload|cloud|ship-off/i, bonus: -160 },
548
+ ],
549
+ },
550
+ {
551
+ priority: 90,
552
+ slotPatterns: [/products?/i, /catalog/i, /inventory/i],
553
+ queryVariants: ['package', 'box', 'tag', 'shopping bag', 'products'],
554
+ iconPreferences: [
555
+ { pattern: /^package$|packages|package[_-]?\d?|^box$|boxes|tag$|shopping-bag$|warehouse|building-warehouse/i, bonus: 115 },
556
+ { pattern: /brand-producthunt|brand-stocktwits|border|sort|cancel|\bx\b|off|discount|heart|exclamation|minus|plus|search|share|star|question|bolt|code|copy|dollar|down|up|pin|pause/i, bonus: -130 },
557
+ ],
558
+ },
559
+ {
560
+ slotPatterns: [/reports?/i, /analytics/i, /insights?/i],
561
+ queryVariants: ['bar chart', 'file chart', 'analytics chart', 'report document'],
314
562
  iconPreferences: [
315
563
  { pattern: /file-.*chart|chart-bar|bar-chart-3|bar-chart|chart-line|line-chart/i, bonus: 40 },
316
- { pattern: /chart|report|analytics/i, bonus: 16 },
317
- ],
318
- },
319
- {
320
- slotPatterns: [/settings?/i, /preferences?/i, /configure/i],
321
- queryVariants: ['settings', 'cog', 'sliders'],
564
+ { pattern: /chart|report|analytics/i, bonus: 16 },
565
+ ],
566
+ },
567
+ {
568
+ priority: 100,
569
+ slotPatterns: [/^quotes?$/i, /blockquote/i, /quotation/i, /citation/i],
570
+ queryVariants: ['quotes', 'quotation', 'quote', 'blockquote'],
571
+ iconPreferences: [
572
+ { pattern: /^quotes?$|quotation|blockquote/i, bonus: 180 },
573
+ { pattern: /indent|text-align|receipt|ticket/i, bonus: -120 },
574
+ ],
575
+ },
576
+ {
577
+ slotPatterns: [/settings?/i, /preferences?/i, /configure/i],
578
+ queryVariants: ['settings', 'cog', 'sliders'],
322
579
  iconPreferences: [
323
580
  { pattern: /^settings$|^cog$|settings-2|sliders/i, bonus: 34 },
324
- { pattern: /settings|cog|adjustments/i, bonus: 16 },
325
- ],
326
- },
327
- {
328
- slotPatterns: [/database/i, /storage/i],
329
- queryVariants: ['database', 'server database', 'data storage'],
330
- iconPreferences: [
331
- { pattern: /^database$|database-stack/i, bonus: 36 },
332
- { pattern: /database|server/i, bonus: 16 },
333
- ],
334
- },
335
- ]);
581
+ { pattern: /settings|cog|adjustments/i, bonus: 16 },
582
+ ],
583
+ },
584
+ {
585
+ priority: 90,
586
+ slotPatterns: [/permissions?/i, /access control/i, /roles?/i],
587
+ queryVariants: ['user key', 'shield lock', 'key', 'settings permissions'],
588
+ iconPreferences: [
589
+ { pattern: /user-key|user-lock|user-check|key|lock|shield|adjustments|settings/i, bonus: 120 },
590
+ { pattern: /free-rights|premium-rights|icons$/i, bonus: -100 },
591
+ ],
592
+ },
593
+ {
594
+ slotPatterns: [/database/i, /storage/i],
595
+ queryVariants: ['database', 'server database', 'data storage'],
596
+ iconPreferences: [
597
+ { pattern: /^database$|database-stack/i, bonus: 36 },
598
+ { pattern: /database|server/i, bonus: 16 },
599
+ ],
600
+ },
601
+ {
602
+ slotPatterns: [/search/i, /find/i, /lookup/i],
603
+ queryVariants: ['search', 'find', 'magnifier', 'magnifying glass'],
604
+ iconPreferences: [
605
+ { pattern: /^search$/i, bonus: 140 },
606
+ { pattern: /^search(?:_|-|$)|(?:_|-)search(?:_|-|$)/i, bonus: 64 },
607
+ { pattern: /magnifier|magnifying/i, bonus: 36 },
608
+ { pattern: /file-search|folder-search|scan-search|mail-search|calendar-search/i, bonus: -90 },
609
+ ],
610
+ },
611
+ {
612
+ priority: 115,
613
+ slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
614
+ queryVariants: ['bookmark add', 'add bookmark', 'bookmark plus'],
615
+ iconPreferences: [
616
+ { pattern: /^bookmark[_-]?(add|plus)(?:_|-|$)|(?:_|-)bookmark[_-]?(add|plus)(?:_|-|$)/i, bonus: 220 },
617
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
618
+ { pattern: /^(add|plus)(?:_|-|$)/i, bonus: -80 },
619
+ ],
620
+ },
621
+ {
622
+ priority: 115,
623
+ slotPatterns: [/\bedit\s+bookmark\b/i, /\bbookmark\s+edit\b/i],
624
+ queryVariants: ['bookmark edit', 'edit bookmark', 'bookmark pencil'],
625
+ iconPreferences: [
626
+ { pattern: /^bookmark[_-]?edit(?:_|-|$)|(?:_|-)bookmark[_-]?edit(?:_|-|$)/i, bonus: 190 },
627
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
628
+ { pattern: /^edit(?:_|-|$)|pencil/i, bonus: -70 },
629
+ ],
630
+ },
631
+ {
632
+ priority: 115,
633
+ slotPatterns: [/\b(remove|delete)\s+bookmark\b/i, /\bbookmark\s+(remove|delete)\b/i],
634
+ queryVariants: ['bookmark remove', 'remove bookmark', 'bookmark minus'],
635
+ iconPreferences: [
636
+ { pattern: /^bookmark[_-]?(remove|minus|x)(?:_|-|$)|(?:_|-)bookmark[_-]?(remove|minus|x)(?:_|-|$)/i, bonus: 190 },
637
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 46 },
638
+ { pattern: /^(remove|delete|minus)(?:_|-|$)/i, bonus: -70 },
639
+ ],
640
+ },
641
+ {
642
+ slotPatterns: [/bookmark/i, /saved?/i, /save article/i],
643
+ queryVariants: ['bookmark', 'saved', 'save'],
644
+ iconPreferences: [
645
+ { pattern: /^bookmarks?(?:_|-|$)|(?:_|-)bookmarks?(?:_|-|$)/i, bonus: 66 },
646
+ { pattern: /save|favorite|star/i, bonus: 14 },
647
+ ],
648
+ },
649
+ {
650
+ slotPatterns: [/share/i, /send article/i, /forward/i],
651
+ queryVariants: ['share', 'send', 'forward'],
652
+ iconPreferences: [
653
+ { pattern: /^share(?:_|-|$)|(?:_|-)share(?:_|-|$)/i, bonus: 58 },
654
+ { pattern: /send|forward/i, bonus: 18 },
655
+ ],
656
+ },
657
+ {
658
+ priority: 110,
659
+ slotPatterns: [/previous page/i, /previous/i, /\bback\b/i, /go back/i],
660
+ queryVariants: ['arrow left', 'chevron left', 'back arrow', 'previous'],
661
+ iconPreferences: [
662
+ { pattern: /^arrow[_-]?left$|^chevron[_-]?left$|^caret[_-]?left$|arrow[_-]?back$|back[_-]?line|arrow[_-]?to[_-]?left|left(?:_|-|$)/i, bonus: 140 },
663
+ { pattern: /skip-back|step-back/i, bonus: 48 },
664
+ { pattern: /send-to-back|file|archive|audio|floppy|cash|banknote|brand|copy/i, bonus: -140 },
665
+ ],
666
+ },
667
+ {
668
+ priority: 90,
669
+ slotPatterns: [/read more/i, /more link/i, /continue/i, /open article/i, /next page/i, /^next$/i],
670
+ queryVariants: ['arrow right', 'move right', 'chevron right', 'read more', 'next'],
671
+ iconPreferences: [
672
+ { pattern: /^arrow[_-]?right$|^move[_-]?right$|arrow[_-]?to[_-]?right|chevron[_-]?right|right(?:_|-|$)/i, bonus: 90 },
673
+ { pattern: /square|circle|corner|up|down|left|banknote|archive/i, bonus: -70 },
674
+ ],
675
+ },
676
+ {
677
+ slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i, /tags?/i],
678
+ queryVariants: ['filter', 'category', 'tag', 'grid'],
679
+ iconPreferences: [
680
+ { pattern: /^filter(?:_|-|$)|(?:_|-)filter(?:_|-|$)/i, bonus: 56 },
681
+ { pattern: /^tag(?:_|-|$)|(?:_|-)tag(?:_|-|$)|category|grid/i, bonus: 26 },
682
+ ],
683
+ },
684
+ {
685
+ slotPatterns: [/trending/i, /popular/i, /top stories/i, /hot/i],
686
+ queryVariants: ['trending up', 'chart up', 'fire', 'popular'],
687
+ iconPreferences: [
688
+ { pattern: /^trending[_-]?up(?:_|-|$)|chart.*up|up.*chart/i, bonus: 62 },
689
+ { pattern: /^fire(?:_|-|$)|flame|hot/i, bonus: 22 },
690
+ ],
691
+ },
692
+ {
693
+ slotPatterns: [/news/i, /article/i, /headline/i, /story/i, /publisher/i, /logo/i, /title/i],
694
+ queryVariants: ['news', 'article', 'newspaper', 'headline'],
695
+ iconPreferences: [
696
+ { pattern: /^news(?:_|-|$)|(?:_|-)news(?:_|-|$)|newspaper|article/i, bonus: 66 },
697
+ { pattern: /file|document|paper/i, bonus: 16 },
698
+ ],
699
+ },
700
+ {
701
+ slotPatterns: [/dashboard/i],
702
+ queryVariants: ['dashboard', 'layout dashboard', 'grid dashboard'],
703
+ iconPreferences: [
704
+ { pattern: /^dashboard$|layout-dashboard|dashboard/i, bonus: 50 },
705
+ { pattern: /grid|layout/i, bonus: 12 },
706
+ ],
707
+ },
708
+ {
709
+ slotPatterns: [/projects?/i],
710
+ queryVariants: ['folder', 'folders', 'project folder'],
711
+ iconPreferences: [
712
+ { pattern: /^folders?$|(?:_|-)folders?(?:_|-|$)/i, bonus: 56 },
713
+ { pattern: /briefcase|project/i, bonus: 12 },
714
+ ],
715
+ },
716
+ {
717
+ slotPatterns: [/tasks?/i, /todo/i, /to do/i, /checklist/i],
718
+ queryVariants: ['list check', 'checklist', 'checkbox', 'task'],
719
+ iconPreferences: [
720
+ { pattern: /list-check|list_check|checkbox|checklist|clipboard-check/i, bonus: 56 },
721
+ { pattern: /check|task/i, bonus: 16 },
722
+ ],
723
+ },
724
+ {
725
+ slotPatterns: [/team/i, /\busers\b/i, /members?/i],
726
+ queryVariants: ['users', 'team', 'user group'],
727
+ iconPreferences: [
728
+ { pattern: /^users(?:_|-|$)|(?:_|-)users(?:_|-|$)|user-group|user_circle|user-circle/i, bonus: 64 },
729
+ { pattern: /^user(?:_|-|$)|(?:_|-)user(?:_|-|$)/i, bonus: 18 },
730
+ ],
731
+ },
732
+ {
733
+ slotPatterns: [/calendar/i, /schedule/i, /events?/i],
734
+ queryVariants: ['calendar', 'calendar event', 'schedule'],
735
+ iconPreferences: [
736
+ { pattern: /^calendar(?:_|-|$)|(?:_|-)calendar(?:_|-|$)/i, bonus: 54 },
737
+ { pattern: /event|schedule/i, bonus: 18 },
738
+ ],
739
+ },
740
+ {
741
+ slotPatterns: [/\bbold\b/i],
742
+ queryVariants: ['text b', 'bold', 'text bold'],
743
+ iconPreferences: [
744
+ { pattern: /^text[_-]?b$|text-bold|bold/i, bonus: 66 },
745
+ ],
746
+ },
747
+ {
748
+ slotPatterns: [/italic/i],
749
+ queryVariants: ['text italic', 'italic'],
750
+ iconPreferences: [
751
+ { pattern: /^text[_-]?italic$|italic/i, bonus: 66 },
752
+ ],
753
+ },
754
+ {
755
+ priority: 130,
756
+ slotPatterns: [/broken\s+link/i, /link\s+(broken|break|disabled|off)/i],
757
+ queryVariants: ['broken link', 'link break', 'link slash'],
758
+ iconPreferences: [
759
+ { pattern: /link.*(break|broken|slash|off)|(break|broken|slash|off).*link/i, bonus: 180 },
760
+ { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 10 },
761
+ ],
762
+ },
763
+ {
764
+ priority: 130,
765
+ slotPatterns: [/(broken|disabled|off)\s+(image|photo|picture)/i, /(image|photo|picture)\s+(broken|disabled|off)/i],
766
+ queryVariants: ['photo off', 'image off', 'broken image', 'image broken'],
767
+ iconPreferences: [
768
+ { pattern: /(image|photo|picture).*(broken|off|slash)|(broken|off|slash).*(image|photo|picture)/i, bonus: 240 },
769
+ { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 10 },
770
+ ],
771
+ },
772
+ {
773
+ priority: 130,
774
+ slotPatterns: [/(comments?|chat|discussion)\s+(off|disabled|muted|slash)/i, /(off|disabled|muted|slash)\s+(comments?|chat|discussion)/i],
775
+ queryVariants: ['chat slash', 'comment off', 'comments off', 'message slash'],
776
+ iconPreferences: [
777
+ { pattern: /(chat|comment|message).*(slash|off|x)|(slash|off|x).*(chat|comment|message)/i, bonus: 180 },
778
+ { pattern: /chat|comment|message/i, bonus: 10 },
779
+ ],
780
+ },
781
+ {
782
+ slotPatterns: [/\blink\b/i, /hyperlink/i],
783
+ queryVariants: ['link', 'link simple', 'hyperlink'],
784
+ iconPreferences: [
785
+ { pattern: /^link(?:_|-|$)|(?:_|-)link(?:_|-|$)/i, bonus: 56 },
786
+ { pattern: /chain/i, bonus: 14 },
787
+ { pattern: /break|broken|slash|unlink|brand/i, bonus: -120 },
788
+ ],
789
+ },
790
+ {
791
+ slotPatterns: [/image/i, /photo/i, /picture/i],
792
+ queryVariants: ['image', 'picture', 'photo'],
793
+ iconPreferences: [
794
+ { pattern: /^image(?:_|-|$)|(?:_|-)image(?:_|-|$)|picture|photo/i, bonus: 56 },
795
+ { pattern: /broken|off|slash|brand/i, bonus: -120 },
796
+ ],
797
+ },
798
+ {
799
+ priority: 80,
800
+ slotPatterns: [/comments?/i, /chat/i, /discussion/i],
801
+ queryVariants: ['chat text', 'comments', 'message dots'],
802
+ iconPreferences: [
803
+ { pattern: /chat|comment|message/i, bonus: 76 },
804
+ { pattern: /slash|off|x$|brand/i, bonus: -120 },
805
+ ],
806
+ },
807
+ {
808
+ slotPatterns: [/undo/i],
809
+ queryVariants: ['undo', 'arrow counter clockwise', 'rotate left'],
810
+ iconPreferences: [
811
+ { pattern: /^undo$|arrow-counter-clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: 66 },
812
+ { pattern: /^redo$|^arrows?[_-]clockwise$|clock[_-]clockwise|arrow_clockwise|rotate.*right/i, bonus: -120 },
813
+ ],
814
+ },
815
+ {
816
+ slotPatterns: [/redo/i],
817
+ queryVariants: ['redo', 'arrow clockwise', 'rotate right'],
818
+ iconPreferences: [
819
+ { pattern: /^redo$|arrow-clockwise|arrow_clockwise|rotate.*right/i, bonus: 66 },
820
+ { pattern: /^undo$|^arrows?[_-]counter[_-]clockwise$|clock[_-]counter[_-]clockwise|arrow_counter_clockwise|rotate.*left/i, bonus: -120 },
821
+ ],
822
+ },
823
+ ]);
336
824
 
337
- const SLOT_PREFERENCE_RULES = Object.freeze({
338
- mingcute: [
825
+ const SLOT_PREFERENCE_RULES = Object.freeze({
826
+ lucide: [
827
+ {
828
+ slotPatterns: [/\b(add|new|create)\s+bookmark\b/i, /\bbookmark\s+(add|new|create)\b/i],
829
+ iconPreferences: [
830
+ { pattern: /^bookmark-plus$/i, bonus: 500 },
831
+ { pattern: /^bookmark$/i, bonus: 40 },
832
+ { pattern: /waves-ladder|map-pin/i, bonus: -220 },
833
+ ],
834
+ },
835
+ {
836
+ slotPatterns: [/users/i, /team/i],
837
+ iconPreferences: [
838
+ { pattern: /^users$/i, bonus: 28 },
839
+ { pattern: /^users-2$/i, bonus: 20 },
840
+ { pattern: /^user-2$/i, bonus: -12 },
841
+ ],
842
+ },
843
+ {
844
+ slotPatterns: [/database/i, /storage/i],
845
+ iconPreferences: [
846
+ { pattern: /^database$/i, bonus: 48 },
847
+ { pattern: /^database-(backup|search)$/i, bonus: 28 },
848
+ { pattern: /^database-zap$/i, bonus: -34 },
849
+ ],
850
+ },
851
+ {
852
+ slotPatterns: [/security/i, /privacy/i, /safe/i, /protection/i],
853
+ iconPreferences: [
854
+ { pattern: /^shield$/i, bonus: 80 },
855
+ { pattern: /^shield-check$/i, bonus: 76 },
856
+ { pattern: /^lock$/i, bonus: 52 },
857
+ { pattern: /^lock-keyhole$/i, bonus: 48 },
858
+ { pattern: /open|unlock|ban|minus|off|slash/i, bonus: -80 },
859
+ ],
860
+ },
861
+ ],
862
+ mingcute: [
339
863
  {
340
- slotPatterns: [/home/i],
341
- iconPreferences: [
342
- { pattern: /^home_3_line$/i, bonus: 14 },
343
- { pattern: /^home_2_line$/i, bonus: 8 },
344
- { pattern: /^home_1_line$/i, bonus: 4 },
345
- ],
864
+ slotPatterns: [/home/i],
865
+ iconPreferences: [
866
+ { pattern: /^home_3_line$/i, bonus: 160 },
867
+ { pattern: /^home_2_line$/i, bonus: 8 },
868
+ { pattern: /^home_1_line$/i, bonus: 4 },
869
+ { pattern: /^home_wifi_line$/i, bonus: -24 },
870
+ ],
346
871
  },
347
872
  {
348
873
  slotPatterns: [/create/i, /add/i, /plus/i, /compose/i],
@@ -352,36 +877,132 @@ const SLOT_PREFERENCE_RULES = Object.freeze({
352
877
  { pattern: /^add_circle_line$/i, bonus: 6 },
353
878
  ],
354
879
  },
355
- {
356
- slotPatterns: [/alerts?/i, /notification/i],
357
- iconPreferences: [{ pattern: /^notification_line$/i, bonus: 12 }],
358
- },
359
- {
360
- slotPatterns: [/profile/i, /user/i, /account/i],
361
- iconPreferences: [{ pattern: /^user_1_line$/i, bonus: 12 }],
362
- },
363
- ],
364
- });
880
+ {
881
+ slotPatterns: [/alerts?/i, /notification/i],
882
+ iconPreferences: [
883
+ { pattern: /^notification_line$/i, bonus: 36 },
884
+ { pattern: /^notification_off_line$/i, bonus: -28 },
885
+ ],
886
+ },
887
+ {
888
+ slotPatterns: [/profile/i, /user/i, /account/i],
889
+ iconPreferences: [
890
+ { pattern: /^user_1_line$/i, bonus: 44 },
891
+ { pattern: /^user_4_line$/i, bonus: -16 },
892
+ ],
893
+ },
894
+ {
895
+ slotPatterns: [/search/i],
896
+ iconPreferences: [
897
+ { pattern: /^search_line$/i, bonus: 70 },
898
+ { pattern: /^search_[23]_line$/i, bonus: 18 },
899
+ { pattern: /^search_.*_ai_line$/i, bonus: -70 },
900
+ ],
901
+ },
902
+ {
903
+ slotPatterns: [/bookmark/i, /saved?/i],
904
+ iconPreferences: [
905
+ { pattern: /^bookmark_line$/i, bonus: 34 },
906
+ { pattern: /^bookmarks_line$/i, bonus: 28 },
907
+ { pattern: /^bookmark_(add|edit|remove)_line$/i, bonus: -20 },
908
+ ],
909
+ },
910
+ {
911
+ slotPatterns: [/trending/i, /popular/i, /top stories/i],
912
+ iconPreferences: [
913
+ { pattern: /^trending_up_line$/i, bonus: 150 },
914
+ { pattern: /^trending_down_line$/i, bonus: -30 },
915
+ ],
916
+ },
917
+ {
918
+ slotPatterns: [/read more/i, /continue/i, /open article/i],
919
+ iconPreferences: [
920
+ { pattern: /^arrow_right_line$/i, bonus: 72 },
921
+ { pattern: /^arrow_to_right_line$/i, bonus: 40 },
922
+ { pattern: /^align_arrow_right_line$/i, bonus: -30 },
923
+ ],
924
+ },
925
+ {
926
+ slotPatterns: [/categor(?:y|ies)/i, /chips?/i, /filter/i, /topics?/i],
927
+ iconPreferences: [
928
+ { pattern: /^filter_line$/i, bonus: 34 },
929
+ { pattern: /^filter_[23]_line$/i, bonus: 22 },
930
+ { pattern: /^tag_line$/i, bonus: 16 },
931
+ ],
932
+ },
933
+ {
934
+ slotPatterns: [/news/i, /article/i, /headline/i, /logo/i, /title/i],
935
+ iconPreferences: [
936
+ { pattern: /^news_line$/i, bonus: 76 },
937
+ { pattern: /^news_2_line$/i, bonus: 46 },
938
+ { pattern: /^appstore_line$/i, bonus: -44 },
939
+ { pattern: /^apple_fruit_line$/i, bonus: -44 },
940
+ ],
941
+ },
942
+ {
943
+ slotPatterns: [/projects?/i],
944
+ iconPreferences: [
945
+ { pattern: /^folder_locked_line$/i, bonus: -70 },
946
+ ],
947
+ },
948
+ ],
949
+ });
365
950
 
366
- function normalizeText(value) {
367
- return String(value || '')
368
- .toLowerCase()
951
+ function normalizeText(value) {
952
+ return String(value || '')
953
+ .toLowerCase()
369
954
  .replace(/[_:]+/g, ' ')
370
955
  .replace(/[^a-z0-9\s-]/g, ' ')
371
956
  .replace(/-/g, ' ')
372
957
  .replace(/\s+/g, ' ')
373
- .trim();
374
- }
375
-
376
- function tokenizeText(value) {
377
- const normalized = normalizeText(value);
378
- return normalized ? normalized.split(' ') : [];
379
- }
380
-
958
+ .trim();
959
+ }
960
+
961
+ function normalizeToken(token) {
962
+ const value = String(token || '').toLowerCase();
963
+ if (value.length > 4 && value.endsWith('ies')) return `${value.slice(0, -3)}y`;
964
+ if (value.length > 3 && value.endsWith('es')) return value.slice(0, -2);
965
+ if (value.length > 3 && value.endsWith('s')) return value.slice(0, -1);
966
+ return value;
967
+ }
968
+
969
+ function tokenizeText(value) {
970
+ const normalized = normalizeText(value);
971
+ if (!normalized) return [];
972
+ const tokens = normalized.split(' ');
973
+ return dedupe([...tokens, ...tokens.map(normalizeToken)]);
974
+ }
975
+
381
976
  function dedupe(values) {
382
977
  return [...new Set(values.filter(Boolean))];
383
978
  }
384
979
 
980
+ function buildDirectLocalizedIntentTerms(value) {
981
+ const text = String(value || '');
982
+ return DIRECT_LOCALIZED_INTENT_RULES
983
+ .filter((rule) => rule.pattern.test(text))
984
+ .flatMap((rule) => rule.terms);
985
+ }
986
+
987
+ function buildRequestedTermSet(intentTerms = []) {
988
+ return new Set(intentTerms.map(normalizeToken).filter(Boolean));
989
+ }
990
+
991
+ function isVariantTokenRequested(token, requestedTerms) {
992
+ const normalizedToken = normalizeToken(token);
993
+ if (requestedTerms.has(normalizedToken)) return true;
994
+ const aliases = REQUESTED_VARIANT_ALIASES[normalizedToken] || [];
995
+ return aliases.some((alias) => requestedTerms.has(normalizeToken(alias)));
996
+ }
997
+
998
+ function isIconVariantExplicitlyRequested(icon, intentTerms = []) {
999
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1000
+ return tokenizeText(icon.id).some((token) => (
1001
+ VARIANT_TOKENS.has(normalizeToken(token)) &&
1002
+ isVariantTokenRequested(token, requestedTerms)
1003
+ ));
1004
+ }
1005
+
385
1006
  function buildLocalizedVariants(value, locale) {
386
1007
  if (!locale) return [];
387
1008
  const expanded = expandCjkQuery(value, {
@@ -433,13 +1054,17 @@ function buildSlotIntentTerms(task, slot, locale = null) {
433
1054
  const taskTokens = tokenizeText(task);
434
1055
  const slotTokens = tokenizeText(slot);
435
1056
  const usefulSlotTokens = slotTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
1057
+ const localizedSlotTokens = buildLocalizedVariants(slot, locale).flatMap(tokenizeText);
1058
+ const localizedTaskTokens = buildLocalizedVariants(task, locale).flatMap(tokenizeText);
1059
+ const directSlotTokens = buildDirectLocalizedIntentTerms(slot);
436
1060
 
437
1061
  const expanded = [...usefulSlotTokens];
438
1062
 
439
1063
  const usefulTaskTokens = taskTokens.filter((token) => !GENERIC_SLOT_WORDS.has(token));
440
1064
  expanded.push(...usefulTaskTokens);
441
- expanded.push(...buildLocalizedVariants(slot, locale).flatMap(tokenizeText));
442
- expanded.push(...buildLocalizedVariants(task, locale).flatMap(tokenizeText));
1065
+ expanded.push(...localizedSlotTokens);
1066
+ expanded.push(...localizedTaskTokens);
1067
+ expanded.push(...directSlotTokens);
443
1068
 
444
1069
  const variants = buildIntentQueryVariants(`${slot} ${task}`, {
445
1070
  baseQuery: slot,
@@ -449,18 +1074,20 @@ function buildSlotIntentTerms(task, slot, locale = null) {
449
1074
  expanded.push(...tokenizeText(variant));
450
1075
  }
451
1076
 
452
- for (const rule of getMatchingSlotRules(slot, expanded)) {
453
- for (const variant of rule.queryVariants || []) {
454
- expanded.push(...tokenizeText(variant));
455
- }
1077
+ const slotRuleTerms = dedupe([...usefulSlotTokens, ...localizedSlotTokens, ...directSlotTokens]);
1078
+ for (const rule of getMatchingSlotRules(slot, slotRuleTerms)) {
1079
+ for (const variant of rule.queryVariants || []) {
1080
+ expanded.push(...tokenizeText(variant));
1081
+ }
456
1082
  }
457
1083
 
458
1084
  return dedupe(expanded);
459
1085
  }
460
1086
 
461
1087
  function buildSlotQueryVariants(task, slot, locale = null) {
1088
+ const localizedSlotVariants = buildLocalizedVariants(slot, locale);
462
1089
  const localizedVariants = [
463
- ...buildLocalizedVariants(slot, locale),
1090
+ ...localizedSlotVariants,
464
1091
  ...buildLocalizedVariants(`${slot} ${task}`, locale),
465
1092
  ];
466
1093
  const variants = buildIntentQueryVariants(`${slot} ${task}`, {
@@ -470,82 +1097,115 @@ function buildSlotQueryVariants(task, slot, locale = null) {
470
1097
  variants.unshift(...localizedVariants);
471
1098
  const usefulSlotTokens = tokenizeText(slot).filter((token) => !GENERIC_SLOT_WORDS.has(token));
472
1099
  variants.push(...usefulSlotTokens);
473
- const intentTerms = tokenizeText(`${slot} ${task} ${variants.join(' ')}`);
474
- const ruleVariants = getMatchingSlotRules(slot, intentTerms)
1100
+ const slotRuleTerms = [
1101
+ ...tokenizeText(`${slot} ${localizedSlotVariants.join(' ')}`),
1102
+ ...buildDirectLocalizedIntentTerms(slot),
1103
+ ]
1104
+ .filter((token) => !GENERIC_SLOT_WORDS.has(token));
1105
+ const ruleVariants = getMatchingSlotRules(slot, slotRuleTerms)
475
1106
  .flatMap((rule) => rule.queryVariants || []);
476
1107
  variants.unshift(...ruleVariants);
477
1108
  return dedupe(variants).slice(0, 12);
478
1109
  }
479
1110
 
480
- function scoreLexicalFit(icon, intentTerms, slotLabel) {
481
- const tokens = new Set([
482
- ...tokenizeText(icon.id),
483
- ...tokenizeText(icon.name),
484
- ...tokenizeText(`${icon.lib}:${icon.id}`),
485
- ]);
486
- const normalizedId = normalizeText(icon.id);
487
- const normalizedName = normalizeText(icon.name);
488
- const normalizedSlot = normalizeText(slotLabel);
489
-
490
- let score = 0;
491
- for (const term of intentTerms) {
492
- if (tokens.has(term)) score += 14;
493
- else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 8;
494
-
495
- if (normalizedId === term || normalizedName === term) {
496
- score += 20;
497
- }
498
- }
499
-
500
- if (normalizedSlot && (normalizedId === normalizedSlot || normalizedName === normalizedSlot)) {
501
- score += 20;
502
- }
503
-
504
- return score;
505
- }
506
-
507
- function getVariantPenalty(icon) {
508
- const normalizedId = normalizeText(icon.id);
509
- let penalty = 0;
510
- for (const rule of VARIANT_PENALTIES) {
511
- if (rule.pattern.test(normalizedId)) {
512
- penalty += rule.penalty;
513
- }
514
- }
515
- return penalty;
516
- }
517
-
1111
+ function scoreLexicalFit(icon, intentTerms, slotLabel, taskLabel = '') {
1112
+ const tokens = new Set([
1113
+ ...tokenizeText(icon.id),
1114
+ ...tokenizeText(icon.name),
1115
+ ...tokenizeText(`${icon.lib}:${icon.id}`),
1116
+ ]);
1117
+ const normalizedId = normalizeText(icon.id);
1118
+ const normalizedName = normalizeText(icon.name);
1119
+ const normalizedSlot = normalizeText(slotLabel);
1120
+ const slotTerms = tokenizeText(slotLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1121
+ const taskTerms = tokenizeText(taskLabel).filter((token) => !GENERIC_SLOT_WORDS.has(token));
1122
+
1123
+ let score = 0;
1124
+ for (const term of slotTerms) {
1125
+ if (tokens.has(term)) score += 22;
1126
+ else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 14;
1127
+
1128
+ if (normalizedId === term || normalizedName === term) {
1129
+ score += 24;
1130
+ }
1131
+ }
1132
+
1133
+ for (const term of intentTerms) {
1134
+ if (tokens.has(term)) score += 12;
1135
+ else if (normalizedId.includes(term) || normalizedName.includes(term)) score += 7;
1136
+
1137
+ if (normalizedId === term || normalizedName === term) {
1138
+ score += 14;
1139
+ }
1140
+ }
1141
+
1142
+ for (const term of taskTerms) {
1143
+ if (tokens.has(term)) score += 3;
1144
+ }
1145
+
1146
+ if (normalizedSlot && (normalizedId === normalizedSlot || normalizedName === normalizedSlot)) {
1147
+ score += 26;
1148
+ }
1149
+
1150
+ return score;
1151
+ }
1152
+
1153
+ function getVariantPenalty(icon, intentTerms = []) {
1154
+ const normalizedId = normalizeText(icon.id);
1155
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1156
+ let penalty = 0;
1157
+ for (const rule of VARIANT_PENALTIES) {
1158
+ if (!rule.pattern.test(normalizedId)) continue;
1159
+ if (isVariantTokenRequested(rule.token, requestedTerms)) continue;
1160
+ penalty += rule.penalty;
1161
+ }
1162
+ return penalty;
1163
+ }
1164
+
1165
+ function getBrandPenalty(icon, intentTerms = []) {
1166
+ const requestedTerms = buildRequestedTermSet(intentTerms);
1167
+ if (isVariantTokenRequested('brand', requestedTerms)) return 0;
1168
+ return icon.lib === 'simpleicons' ? 80 : 0;
1169
+ }
1170
+
518
1171
  function getMatchingSlotRules(slotLabel, intentTerms = []) {
519
1172
  const rawSlotText = String(slotLabel || '');
520
1173
  const slotText = normalizeText(slotLabel);
521
- return COMMON_SLOT_PREFERENCE_RULES.filter((rule) => rule.slotPatterns.some((pattern) => (
522
- pattern.test(slotText) || pattern.test(rawSlotText)
523
- )));
1174
+ const intentText = normalizeText(intentTerms.join(' '));
1175
+ return COMMON_SLOT_PREFERENCE_RULES
1176
+ .filter((rule) => rule.slotPatterns.some((pattern) => (
1177
+ pattern.test(slotText) ||
1178
+ pattern.test(rawSlotText) ||
1179
+ pattern.test(intentText)
1180
+ )))
1181
+ .sort((left, right) => (right.priority || 0) - (left.priority || 0));
524
1182
  }
525
1183
 
526
- function scoreSlotPreferenceRules(icon, rules = [], slotText = '') {
527
- let bonus = 0;
528
-
529
- for (const rule of rules) {
530
- for (const preference of rule.iconPreferences) {
531
- if (preference.pattern.test(icon.id)) {
532
- bonus += preference.bonus;
533
- }
534
- }
1184
+ function scoreSlotPreferenceRules(icon, rules = [], intentTerms = []) {
1185
+ let bonus = 0;
1186
+ const explicitlyRequestedVariant = isIconVariantExplicitlyRequested(icon, intentTerms);
1187
+
1188
+ for (const rule of rules) {
1189
+ for (const preference of rule.iconPreferences) {
1190
+ if (preference.pattern.test(icon.id)) {
1191
+ if (preference.bonus < 0 && explicitlyRequestedVariant) continue;
1192
+ bonus += preference.bonus;
1193
+ }
1194
+ }
535
1195
  }
536
1196
 
537
1197
  return bonus;
538
1198
  }
539
1199
 
540
- function getSlotPreferenceBonus(icon, slotLabel, intentTerms, library) {
541
- const slotText = `${slotLabel} ${intentTerms.join(' ')}`;
542
- const commonRules = getMatchingSlotRules(slotLabel, intentTerms);
543
- const libraryRules = (SLOT_PREFERENCE_RULES[library] || [])
544
- .filter((rule) => rule.slotPatterns.some((pattern) => pattern.test(slotText)));
545
-
546
- return scoreSlotPreferenceRules(icon, commonRules, slotText) +
547
- scoreSlotPreferenceRules(icon, libraryRules, slotText);
548
- }
1200
+ function getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms = intentTerms) {
1201
+ const slotText = `${slotLabel} ${requestedVariantTerms.join(' ')}`;
1202
+ const commonRules = getMatchingSlotRules(slotLabel, requestedVariantTerms);
1203
+ const libraryRules = (SLOT_PREFERENCE_RULES[library] || [])
1204
+ .filter((rule) => rule.slotPatterns.some((pattern) => pattern.test(slotText)));
1205
+
1206
+ return scoreSlotPreferenceRules(icon, commonRules, requestedVariantTerms) +
1207
+ scoreSlotPreferenceRules(icon, libraryRules, requestedVariantTerms);
1208
+ }
549
1209
 
550
1210
  function summarizeSemanticFit(slotLabel, semanticRecord, intentTerms) {
551
1211
  if (semanticRecord?.depicts && semanticRecord?.use_when) {
@@ -563,7 +1223,7 @@ function summarizeSemanticFit(slotLabel, semanticRecord, intentTerms) {
563
1223
  return `Best available match for ${slotLabel}.`;
564
1224
  }
565
1225
 
566
- function buildWhySelected(slotLabel, semanticRecord, iconResult) {
1226
+ function buildWhySelected(slotLabel, semanticRecord, iconResult) {
567
1227
  const label = semanticRecord?.label || iconResult.name;
568
1228
  if (semanticRecord?.depicts) {
569
1229
  return `${label} matches ${slotLabel} and visually reads as ${String(semanticRecord.depicts).toLowerCase()}.`;
@@ -571,24 +1231,43 @@ function buildWhySelected(slotLabel, semanticRecord, iconResult) {
571
1231
  if (semanticRecord?.use_when) {
572
1232
  return `${label} matches ${slotLabel}. ${semanticRecord.use_when}`;
573
1233
  }
574
- return `${label} is the clearest match for ${slotLabel} from the current library.`;
575
- }
576
-
577
- function buildCandidatePayload(slotLabel, iconResult, semanticRecord, intentTerms) {
578
- return {
579
- id: iconResult.id,
580
- library: iconResult.library,
581
- name: iconResult.name,
582
- style: iconResult.style || 'outline',
583
- label: semanticRecord?.label || iconResult.semantic?.label || iconResult.name,
584
- semantic_fit: summarizeSemanticFit(slotLabel, semanticRecord, intentTerms),
585
- why_selected: buildWhySelected(slotLabel, semanticRecord, iconResult),
586
- svg: iconResult.svg,
587
- semantic: buildPublicSemanticPayload(semanticRecord) || iconResult.semantic || null,
588
- };
589
- }
1234
+ return `${label} is the clearest match for ${slotLabel} from the current library.`;
1235
+ }
1236
+
1237
+ function buildCandidatePayload(
1238
+ slotLabel,
1239
+ iconResult,
1240
+ semanticRecord,
1241
+ intentTerms,
1242
+ responseMode = 'plan',
1243
+ includeSvg = false,
1244
+ includeReason = true
1245
+ ) {
1246
+ const payload = {
1247
+ id: iconResult.id,
1248
+ library: iconResult.library,
1249
+ name: iconResult.name,
1250
+ style: iconResult.style || 'outline',
1251
+ label: semanticRecord?.label || iconResult.semantic?.label || iconResult.name,
1252
+ };
1253
+
1254
+ if (includeReason) {
1255
+ payload.semantic_fit = summarizeSemanticFit(slotLabel, semanticRecord, intentTerms);
1256
+ payload.why_selected = buildWhySelected(slotLabel, semanticRecord, iconResult);
1257
+ }
1258
+
1259
+ if (includeSvg) {
1260
+ payload.svg = iconResult.svg;
1261
+ }
1262
+
1263
+ if (responseMode === 'full') {
1264
+ payload.semantic = buildPublicSemanticPayload(semanticRecord) || iconResult.semantic || null;
1265
+ }
1266
+
1267
+ return payload;
1268
+ }
590
1269
 
591
- async function mapWithConcurrency(items, limit, mapper) {
1270
+ async function mapWithConcurrency(items, limit, mapper) {
592
1271
  const results = new Array(items.length);
593
1272
  let nextIndex = 0;
594
1273
 
@@ -601,28 +1280,58 @@ async function mapWithConcurrency(items, limit, mapper) {
601
1280
  }
602
1281
 
603
1282
  const workerCount = Math.min(Math.max(1, limit), items.length);
604
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
605
- return results;
606
- }
607
-
608
- export async function recommendIconsForTask({
609
- task,
610
- library,
1283
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
1284
+ return results;
1285
+ }
1286
+
1287
+ function getConfidence(topScore, nextScore = 0) {
1288
+ if (topScore >= 90 && topScore - nextScore >= 20) {
1289
+ return { level: 'high', score: topScore };
1290
+ }
1291
+ if (topScore >= 45) {
1292
+ return { level: 'medium', score: topScore };
1293
+ }
1294
+ return { level: 'low', score: topScore };
1295
+ }
1296
+
1297
+ function buildLowConfidenceHint(slotLabel, queriesUsed) {
1298
+ return `Low confidence for ${slotLabel}. Try search_icons with: ${queriesUsed.slice(0, 3).join(', ')}.`;
1299
+ }
1300
+
1301
+ function normalizeResponseMode(responseMode) {
1302
+ if (responseMode === 'assets' || responseMode === 'full') return responseMode;
1303
+ return 'plan';
1304
+ }
1305
+
1306
+ function isNoisyAlternative(entry) {
1307
+ return entry.variantPenalty >= 12 || entry.brandPenalty >= 12 || entry.slotPreferenceBonus < 0;
1308
+ }
1309
+
1310
+ export async function recommendIconsForTask({
1311
+ task,
1312
+ library,
611
1313
  style = 'any',
612
1314
  locale = null,
613
1315
  slots,
614
1316
  limitPerSlot = 3,
615
- searchIconsForQuery,
616
- buildIconResult,
617
- semanticMap,
618
- }) {
619
- const slotResults = await mapWithConcurrency(slots, 6, async (slotLabel) => {
1317
+ responseMode = 'plan',
1318
+ searchIconsForQuery,
1319
+ buildIconResult,
1320
+ semanticMap,
1321
+ }) {
1322
+ const normalizedResponseMode = normalizeResponseMode(responseMode);
1323
+ const scoredSlotResults = await mapWithConcurrency(slots, SLOT_SEARCH_CONCURRENCY, async (slotLabel) => {
620
1324
  const intentTerms = buildSlotIntentTerms(task, slotLabel, locale);
621
- const queryVariants = buildSlotQueryVariants(task, slotLabel, locale).slice(0, locale ? 8 : 2);
1325
+ const requestedVariantTerms = dedupe([
1326
+ ...tokenizeText(slotLabel),
1327
+ ...buildLocalizedVariants(slotLabel, locale).flatMap(tokenizeText),
1328
+ ...buildDirectLocalizedIntentTerms(slotLabel),
1329
+ ]);
1330
+ const queryVariants = buildSlotQueryVariants(task, slotLabel, locale).slice(0, locale ? 8 : 4);
622
1331
  const pooledIcons = [];
623
- const seen = new Set();
624
-
625
- const resultGroups = await mapWithConcurrency(queryVariants, 2, async (queryVariant) => {
1332
+ const seen = new Set();
1333
+
1334
+ const resultGroups = await mapWithConcurrency(queryVariants, SLOT_QUERY_CONCURRENCY, async (queryVariant) => {
626
1335
  try {
627
1336
  return await searchIconsForQuery({
628
1337
  query: queryVariant,
@@ -646,62 +1355,137 @@ export async function recommendIconsForTask({
646
1355
  }
647
1356
 
648
1357
  const scored = pooledIcons
649
- .map((icon, index) => {
650
- const semanticRecord = getSemanticRecordForIcon(semanticMap, icon);
651
- const semanticQuery = queryVariants.join(' ');
652
- const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
653
- const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel);
654
- const semanticBonus = semanticRecord ? 6 : 0;
655
- const variantPenalty = getVariantPenalty(icon);
656
- const slotPreferenceBonus = getSlotPreferenceBonus(icon, slotLabel, intentTerms, library);
657
- const intentProfile = buildSearchIntentProfile(`${slotLabel} ${task}`);
658
- const intentAdjustment = getIntentCandidateAdjustment(icon, intentProfile);
659
-
1358
+ .map((icon, index) => {
1359
+ const semanticRecord = getSemanticRecordForIcon(semanticMap, icon);
1360
+ const semanticQuery = queryVariants.join(' ');
1361
+ const semanticScore = semanticRecord ? scoreSemanticAlignment(semanticQuery, semanticRecord) * 3 : 0;
1362
+ const lexicalScore = scoreLexicalFit(icon, intentTerms, slotLabel, task);
1363
+ const semanticBonus = semanticRecord ? 6 : 0;
1364
+ const variantPenalty = getVariantPenalty(icon, requestedVariantTerms);
1365
+ const brandPenalty = getBrandPenalty(icon, requestedVariantTerms);
1366
+ const slotPreferenceBonus = getSlotPreferenceBonus(icon, slotLabel, intentTerms, library, requestedVariantTerms);
1367
+ const intentProfile = buildSearchIntentProfile(`${slotLabel} ${task}`);
1368
+ const intentAdjustment = getIntentCandidateAdjustment(icon, intentProfile);
1369
+
660
1370
  return {
661
- icon,
662
- index,
663
- semanticRecord,
664
- slotPreferenceBonus,
665
- totalScore:
1371
+ icon,
1372
+ index,
1373
+ semanticRecord,
1374
+ variantPenalty,
1375
+ brandPenalty,
1376
+ slotPreferenceBonus,
1377
+ totalScore:
666
1378
  semanticScore +
667
1379
  lexicalScore +
668
1380
  semanticBonus +
669
- slotPreferenceBonus +
670
- intentAdjustment.boost -
671
- intentAdjustment.penalty -
672
- variantPenalty,
673
- };
674
- })
675
- .filter((entry) => entry.totalScore > 0)
676
- .sort((left, right) => {
677
- if (right.slotPreferenceBonus !== left.slotPreferenceBonus) {
678
- return right.slotPreferenceBonus - left.slotPreferenceBonus;
679
- }
680
- if (right.totalScore !== left.totalScore) return right.totalScore - left.totalScore;
681
- return left.index - right.index;
682
- })
683
- .slice(0, limitPerSlot);
684
-
685
- const preparedCandidates = [];
686
- for (const entry of scored) {
687
- const iconResult = await buildIconResult(entry.icon, { style });
688
- if (!iconResult?.svg) continue;
689
- preparedCandidates.push(buildCandidatePayload(slotLabel, iconResult, entry.semanticRecord, intentTerms));
690
- }
691
-
692
- return {
693
- slot: slotLabel,
694
- queries_used: queryVariants,
695
- recommended: preparedCandidates[0] || null,
696
- alternatives: preparedCandidates.slice(1),
697
- };
698
- });
699
-
700
- return {
701
- task,
702
- library: library || 'all',
703
- style,
704
- slot_count: slots.length,
705
- results: slotResults,
706
- };
707
- }
1381
+ slotPreferenceBonus +
1382
+ intentAdjustment.boost -
1383
+ intentAdjustment.penalty -
1384
+ variantPenalty -
1385
+ brandPenalty,
1386
+ };
1387
+ })
1388
+ .filter((entry) => entry.totalScore > 0)
1389
+ .sort((left, right) => {
1390
+ if (right.totalScore !== left.totalScore) return right.totalScore - left.totalScore;
1391
+ if (right.slotPreferenceBonus !== left.slotPreferenceBonus) {
1392
+ return right.slotPreferenceBonus - left.slotPreferenceBonus;
1393
+ }
1394
+ return left.index - right.index;
1395
+ })
1396
+ .slice(0, Math.max(limitPerSlot * 3, 8));
1397
+
1398
+ return {
1399
+ slot: slotLabel,
1400
+ queries_used: queryVariants,
1401
+ intentTerms,
1402
+ requestedVariantTerms,
1403
+ scored,
1404
+ };
1405
+ });
1406
+
1407
+ const usedIconKeys = new Set();
1408
+ const slotResults = [];
1409
+ for (const slotResult of scoredSlotResults) {
1410
+ const sorted = [...slotResult.scored].sort((left, right) => {
1411
+ const leftKey = `${left.icon.lib}:${left.icon.id}`;
1412
+ const rightKey = `${right.icon.lib}:${right.icon.id}`;
1413
+ const leftDuplicatePenalty = usedIconKeys.has(leftKey) ? 80 : 0;
1414
+ const rightDuplicatePenalty = usedIconKeys.has(rightKey) ? 80 : 0;
1415
+ const leftScore = left.totalScore - leftDuplicatePenalty;
1416
+ const rightScore = right.totalScore - rightDuplicatePenalty;
1417
+
1418
+ if (rightScore !== leftScore) return rightScore - leftScore;
1419
+ return left.index - right.index;
1420
+ });
1421
+ const selectedEntries = [];
1422
+ const primaryEntry = sorted.find((entry) => (
1423
+ !isNoisyAlternative(entry) &&
1424
+ !usedIconKeys.has(`${entry.icon.lib}:${entry.icon.id}`)
1425
+ )) || sorted.find((entry) => !isNoisyAlternative(entry)) || sorted[0];
1426
+ if (primaryEntry) {
1427
+ selectedEntries.push(primaryEntry);
1428
+ }
1429
+ for (const entry of sorted) {
1430
+ if (selectedEntries.length >= limitPerSlot) break;
1431
+ if (selectedEntries.includes(entry)) continue;
1432
+ if (isNoisyAlternative(entry)) continue;
1433
+ selectedEntries.push(entry);
1434
+ }
1435
+ const preparedCandidates = [];
1436
+ const preparedEntries = [];
1437
+ for (const [candidateIndex, entry] of selectedEntries.entries()) {
1438
+ const iconResult = await buildIconResult(entry.icon, { style });
1439
+ if (!iconResult?.svg) continue;
1440
+ const includeSvg = normalizedResponseMode === 'full' || (normalizedResponseMode === 'assets' && candidateIndex === 0);
1441
+ preparedCandidates.push(buildCandidatePayload(
1442
+ slotResult.slot,
1443
+ iconResult,
1444
+ entry.semanticRecord,
1445
+ slotResult.intentTerms,
1446
+ normalizedResponseMode,
1447
+ includeSvg,
1448
+ normalizedResponseMode !== 'plan' || candidateIndex === 0
1449
+ ));
1450
+ preparedEntries.push(entry);
1451
+ }
1452
+ const chosen = preparedEntries[0] || primaryEntry || null;
1453
+ if (chosen) {
1454
+ usedIconKeys.add(`${chosen.icon.lib}:${chosen.icon.id}`);
1455
+ }
1456
+ const confidence = chosen
1457
+ ? getConfidence(chosen.totalScore, sorted[1]?.totalScore || 0)
1458
+ : { level: 'low', score: 0 };
1459
+
1460
+ const slotPayload = {
1461
+ slot: slotResult.slot,
1462
+ confidence,
1463
+ recommended: preparedCandidates[0] || null,
1464
+ alternatives: preparedCandidates.slice(1),
1465
+ };
1466
+ if (confidence.level === 'low') {
1467
+ slotPayload.guidance = buildLowConfidenceHint(slotResult.slot, slotResult.queries_used);
1468
+ }
1469
+ if (normalizedResponseMode !== 'plan') {
1470
+ slotPayload.queries_used = slotResult.queries_used;
1471
+ }
1472
+ slotResults.push(slotPayload);
1473
+ }
1474
+
1475
+ const lowConfidenceSlots = slotResults
1476
+ .filter((slot) => !slot.recommended || slot.confidence?.level === 'low')
1477
+ .map((slot) => slot.slot);
1478
+ const allSlotsResolved = slotResults.every((slot) => Boolean(slot.recommended));
1479
+
1480
+ return {
1481
+ task,
1482
+ library: library || 'all',
1483
+ style,
1484
+ response_mode: normalizedResponseMode,
1485
+ slot_count: slots.length,
1486
+ all_slots_resolved: allSlotsResolved,
1487
+ low_confidence_slots: lowConfidenceSlots,
1488
+ fallback_recommended: !allSlotsResolved || lowConfidenceSlots.length > 0,
1489
+ results: slotResults,
1490
+ };
1491
+ }