@finos/legend-application-marketplace 0.2.14 → 0.2.16

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.
Files changed (120) hide show
  1. package/lib/__lib__/LegendMarketplaceNavigation.d.ts +2 -0
  2. package/lib/__lib__/LegendMarketplaceNavigation.d.ts.map +1 -1
  3. package/lib/__lib__/LegendMarketplaceNavigation.js +4 -0
  4. package/lib/__lib__/LegendMarketplaceNavigation.js.map +1 -1
  5. package/lib/application/LegendMarketplaceApplicationConfig.d.ts +9 -0
  6. package/lib/application/LegendMarketplaceApplicationConfig.d.ts.map +1 -1
  7. package/lib/application/LegendMarketplaceApplicationConfig.js +40 -22
  8. package/lib/application/LegendMarketplaceApplicationConfig.js.map +1 -1
  9. package/lib/application/LegendMarketplaceWebApplication.d.ts.map +1 -1
  10. package/lib/application/LegendMarketplaceWebApplication.js +4 -1
  11. package/lib/application/LegendMarketplaceWebApplication.js.map +1 -1
  12. package/lib/application/__test-utils__/LegendMarketplaceApplicationTestUtils.d.ts.map +1 -1
  13. package/lib/application/__test-utils__/LegendMarketplaceApplicationTestUtils.js +7 -0
  14. package/lib/application/__test-utils__/LegendMarketplaceApplicationTestUtils.js.map +1 -1
  15. package/lib/application/providers/LegendMarketplaceAIChatStoreProvider.d.ts +21 -0
  16. package/lib/application/providers/LegendMarketplaceAIChatStoreProvider.d.ts.map +1 -0
  17. package/lib/application/providers/LegendMarketplaceAIChatStoreProvider.js +29 -0
  18. package/lib/application/providers/LegendMarketplaceAIChatStoreProvider.js.map +1 -0
  19. package/lib/components/Header/LegendMarketplaceIconToolbar.d.ts.map +1 -1
  20. package/lib/components/Header/LegendMarketplaceIconToolbar.js +4 -3
  21. package/lib/components/Header/LegendMarketplaceIconToolbar.js.map +1 -1
  22. package/lib/index.css +2 -2
  23. package/lib/index.css.map +1 -1
  24. package/lib/package.json +1 -1
  25. package/lib/pages/Agents/LegendMarketplaceAgents.d.ts.map +1 -1
  26. package/lib/pages/Agents/LegendMarketplaceAgents.js +49 -17
  27. package/lib/pages/Agents/LegendMarketplaceAgents.js.map +1 -1
  28. package/lib/pages/Agents/MarketplaceAIChatView.d.ts +21 -0
  29. package/lib/pages/Agents/MarketplaceAIChatView.d.ts.map +1 -0
  30. package/lib/pages/Agents/MarketplaceAIChatView.js +141 -0
  31. package/lib/pages/Agents/MarketplaceAIChatView.js.map +1 -0
  32. package/lib/pages/Agents/MarketplaceAIInputBar.d.ts +22 -0
  33. package/lib/pages/Agents/MarketplaceAIInputBar.d.ts.map +1 -0
  34. package/lib/pages/Agents/MarketplaceAIInputBar.js +40 -0
  35. package/lib/pages/Agents/MarketplaceAIInputBar.js.map +1 -0
  36. package/lib/pages/Agents/MarketplaceAIProductAutosuggest.d.ts +25 -0
  37. package/lib/pages/Agents/MarketplaceAIProductAutosuggest.d.ts.map +1 -0
  38. package/lib/pages/Agents/MarketplaceAIProductAutosuggest.js +86 -0
  39. package/lib/pages/Agents/MarketplaceAIProductAutosuggest.js.map +1 -0
  40. package/lib/pages/Agents/MarketplaceAIProductCards.d.ts +23 -0
  41. package/lib/pages/Agents/MarketplaceAIProductCards.d.ts.map +1 -0
  42. package/lib/pages/Agents/MarketplaceAIProductCards.js +20 -0
  43. package/lib/pages/Agents/MarketplaceAIProductCards.js.map +1 -0
  44. package/lib/pages/Agents/MarketplaceAIScopeSelector.d.ts +19 -0
  45. package/lib/pages/Agents/MarketplaceAIScopeSelector.d.ts.map +1 -0
  46. package/lib/pages/Agents/MarketplaceAIScopeSelector.js +46 -0
  47. package/lib/pages/Agents/MarketplaceAIScopeSelector.js.map +1 -0
  48. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.d.ts.map +1 -1
  49. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.js +70 -56
  50. package/lib/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.js.map +1 -1
  51. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.d.ts.map +1 -1
  52. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.js +82 -62
  53. package/lib/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.js.map +1 -1
  54. package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.d.ts +17 -0
  55. package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.d.ts.map +1 -0
  56. package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js +126 -0
  57. package/lib/pages/Lakehouse/entitlements/PermitDataAccessRequest.js.map +1 -0
  58. package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.d.ts.map +1 -1
  59. package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.js +23 -65
  60. package/lib/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.js.map +1 -1
  61. package/lib/pages/Lakehouse/entitlements/showTaskActionAlert.d.ts +29 -0
  62. package/lib/pages/Lakehouse/entitlements/showTaskActionAlert.d.ts.map +1 -0
  63. package/lib/pages/Lakehouse/entitlements/showTaskActionAlert.js +60 -0
  64. package/lib/pages/Lakehouse/entitlements/showTaskActionAlert.js.map +1 -0
  65. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.d.ts.map +1 -1
  66. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js +3 -8
  67. package/lib/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.js.map +1 -1
  68. package/lib/stores/LegendMarketplaceBaseStore.d.ts +2 -1
  69. package/lib/stores/LegendMarketplaceBaseStore.d.ts.map +1 -1
  70. package/lib/stores/LegendMarketplaceBaseStore.js +8 -1
  71. package/lib/stores/LegendMarketplaceBaseStore.js.map +1 -1
  72. package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts +109 -0
  73. package/lib/stores/ai/LegendMarketplaceAIChatStore.d.ts.map +1 -0
  74. package/lib/stores/ai/LegendMarketplaceAIChatStore.js +1106 -0
  75. package/lib/stores/ai/LegendMarketplaceAIChatStore.js.map +1 -0
  76. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.d.ts.map +1 -1
  77. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js +3 -2
  78. package/lib/stores/lakehouse/LegendMarketplaceProductViewerStore.js.map +1 -1
  79. package/lib/stores/lakehouse/dataProducts/ProductCardState.d.ts +1 -1
  80. package/lib/stores/lakehouse/dataProducts/ProductCardState.d.ts.map +1 -1
  81. package/lib/stores/lakehouse/dataProducts/ProductCardState.js +1 -2
  82. package/lib/stores/lakehouse/dataProducts/ProductCardState.js.map +1 -1
  83. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts +4 -1
  84. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.d.ts.map +1 -1
  85. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js +22 -2
  86. package/lib/stores/lakehouse/entitlements/EntitlementsDashboardState.js.map +1 -1
  87. package/lib/utils/EntitlementsUtils.d.ts +36 -2
  88. package/lib/utils/EntitlementsUtils.d.ts.map +1 -1
  89. package/lib/utils/EntitlementsUtils.js +225 -46
  90. package/lib/utils/EntitlementsUtils.js.map +1 -1
  91. package/lib/utils/SearchUtils.d.ts.map +1 -1
  92. package/lib/utils/SearchUtils.js +7 -4
  93. package/lib/utils/SearchUtils.js.map +1 -1
  94. package/package.json +10 -10
  95. package/src/__lib__/LegendMarketplaceNavigation.ts +11 -0
  96. package/src/application/LegendMarketplaceApplicationConfig.ts +62 -24
  97. package/src/application/LegendMarketplaceWebApplication.tsx +15 -0
  98. package/src/application/__test-utils__/LegendMarketplaceApplicationTestUtils.ts +7 -0
  99. package/src/application/providers/LegendMarketplaceAIChatStoreProvider.tsx +47 -0
  100. package/src/components/Header/LegendMarketplaceIconToolbar.tsx +4 -3
  101. package/src/pages/Agents/LegendMarketplaceAgents.tsx +145 -23
  102. package/src/pages/Agents/MarketplaceAIChatView.tsx +555 -0
  103. package/src/pages/Agents/MarketplaceAIInputBar.tsx +91 -0
  104. package/src/pages/Agents/MarketplaceAIProductAutosuggest.tsx +181 -0
  105. package/src/pages/Agents/MarketplaceAIProductCards.tsx +111 -0
  106. package/src/pages/Agents/MarketplaceAIScopeSelector.tsx +84 -0
  107. package/src/pages/Lakehouse/entitlements/EntitlementsClosedContractsDashboard.tsx +124 -127
  108. package/src/pages/Lakehouse/entitlements/EntitlementsPendingContractsDashboard.tsx +151 -153
  109. package/src/pages/Lakehouse/entitlements/PermitDataAccessRequest.tsx +245 -0
  110. package/src/pages/Lakehouse/entitlements/WorkflowDataAccessRequest.tsx +25 -94
  111. package/src/pages/Lakehouse/entitlements/showTaskActionAlert.tsx +101 -0
  112. package/src/pages/Lakehouse/searchResults/LegendMarketplaceSearchResults.tsx +27 -31
  113. package/src/stores/LegendMarketplaceBaseStore.ts +12 -0
  114. package/src/stores/ai/LegendMarketplaceAIChatStore.ts +1720 -0
  115. package/src/stores/lakehouse/LegendMarketplaceProductViewerStore.ts +6 -0
  116. package/src/stores/lakehouse/dataProducts/ProductCardState.ts +3 -4
  117. package/src/stores/lakehouse/entitlements/EntitlementsDashboardState.ts +69 -30
  118. package/src/utils/EntitlementsUtils.tsx +341 -86
  119. package/src/utils/SearchUtils.tsx +7 -4
  120. package/tsconfig.json +9 -0
@@ -0,0 +1,1106 @@
1
+ /**
2
+ * Copyright (c) 2026-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { action, computed, flow, makeObservable, observable, runInAction, } from 'mobx';
17
+ import { ActionState, assertErrorThrown, guaranteeNonNullable, } from '@finos/legend-shared';
18
+ import { LegendAIMessageRole, LegendAIQuestionIntent, LegendAIResolvedEntities, TDSServiceSourceType, findLegendAIPlugin, processQuestionViaOrchestrator, handleMetadataQuestion, generateAndJudgeSql, executeSqlAndReport, analyzeOrchestratorResults, addThinkingStep, completeThinkingSteps, finishWithThinkingError, classifyError, updateLastAssistant, buildConversationHistory, createMessagePair, elapsedSeconds, LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID, } from '@finos/legend-lego/legend-ai';
19
+ import { QueryExplicitExecutionContextInfo } from '@finos/legend-graph';
20
+ import { V1_deserializeDataSpace, } from '@finos/legend-extension-dsl-data-space/graph';
21
+ import { convertAutosuggestResultToSearchResult } from '../../utils/SearchUtils.js';
22
+ import { DataProductSearchResult, DataProductSearchResponse, DataProductDetailsType, DataProductSearchResultDetailsType, FieldSearchType, GroupedFieldSearchResponse, LakehouseDataProductSearchResultDetails, LakehouseSDLCDataProductSearchResultOrigin, LegacyDataProductSearchResultDetails, EntitySearchResponse, } from '@finos/legend-server-marketplace';
23
+ export var MarketplaceAIChatStage;
24
+ (function (MarketplaceAIChatStage) {
25
+ MarketplaceAIChatStage["IDLE"] = "idle";
26
+ MarketplaceAIChatStage["SEARCHING"] = "searching";
27
+ MarketplaceAIChatStage["PRODUCT_SELECTION"] = "product-selection";
28
+ MarketplaceAIChatStage["QUERYING"] = "querying";
29
+ MarketplaceAIChatStage["RESULTS"] = "results";
30
+ })(MarketplaceAIChatStage || (MarketplaceAIChatStage = {}));
31
+ const FIELD_COVERAGE_BOOST = 0.6;
32
+ const MAX_PRODUCT_SUGGESTIONS = 3;
33
+ const MERGED_CANDIDATE_LIMIT = 6;
34
+ const PRODUCT_SEARCH_PAGE_SIZE = 6;
35
+ const FIELD_SEARCH_PAGE_SIZE = 5;
36
+ const MAX_RELEVANT_SERVICES = 5;
37
+ const DESCRIPTION_PREVIEW_LENGTH = 200;
38
+ const DATASET_SEARCH_PAGE_SIZE = 20;
39
+ const DEFAULT_SUGGESTED_QUERIES = [
40
+ 'What BVAL bond pricing data is available?',
41
+ 'Show me credit risk data products',
42
+ 'Find FX rates and currency data',
43
+ 'What equity analytics data do we have?',
44
+ ];
45
+ export function unwrapProductDetails(product) {
46
+ const details = product.dataProductDetails;
47
+ if (details instanceof LegacyDataProductSearchResultDetails) {
48
+ return {
49
+ groupId: details.groupId,
50
+ artifactId: details.artifactId,
51
+ versionId: details.versionId,
52
+ path: details.path,
53
+ };
54
+ }
55
+ if (details instanceof LakehouseDataProductSearchResultDetails) {
56
+ const origin = details.origin;
57
+ if (origin instanceof LakehouseSDLCDataProductSearchResultOrigin) {
58
+ return {
59
+ groupId: origin.groupId ?? '',
60
+ artifactId: origin.artifactId ?? '',
61
+ versionId: origin.versionId ?? '',
62
+ path: origin.path ?? '',
63
+ };
64
+ }
65
+ }
66
+ return { groupId: '', artifactId: '', versionId: '', path: '' };
67
+ }
68
+ export class LegendMarketplaceAIChatStore {
69
+ baseStore;
70
+ stage = MarketplaceAIChatStage.IDLE;
71
+ questionText = '';
72
+ messages = [];
73
+ isSending = false;
74
+ suggestedProducts = [];
75
+ scoredCandidates = [];
76
+ scopeProducts = [];
77
+ selectedProduct = undefined;
78
+ selectedProductCoordinates = undefined;
79
+ selectedProductMetadata = undefined;
80
+ pureExecutionContext = undefined;
81
+ pendingFallbackQuestion = undefined;
82
+ resolvedProductServices = [];
83
+ lastResolvedEntities = undefined;
84
+ lastEntityCandidates = [];
85
+ selectedDataProductId = undefined;
86
+ searchState = ActionState.create();
87
+ constructor(baseStore) {
88
+ makeObservable(this, {
89
+ stage: observable,
90
+ questionText: observable,
91
+ messages: observable,
92
+ isSending: observable,
93
+ suggestedProducts: observable,
94
+ scoredCandidates: observable,
95
+ scopeProducts: observable,
96
+ selectedProduct: observable,
97
+ selectedProductCoordinates: observable,
98
+ selectedProductMetadata: observable,
99
+ pureExecutionContext: observable,
100
+ pendingFallbackQuestion: observable,
101
+ resolvedProductServices: observable,
102
+ lastResolvedEntities: observable,
103
+ lastEntityCandidates: observable,
104
+ selectedDataProductId: observable,
105
+ setQuestionText: action,
106
+ setStage: action,
107
+ clearChat: action,
108
+ selectDataProduct: action,
109
+ selectAutosuggestProduct: action,
110
+ deselectProduct: action,
111
+ addScopeProduct: action,
112
+ removeScopeProduct: action,
113
+ submitQuery: flow,
114
+ askFollowUp: flow,
115
+ runOrchestratorFallback: flow,
116
+ config: computed,
117
+ plugin: computed,
118
+ isEnabled: computed,
119
+ lastUserMessageText: computed,
120
+ welcomeSuggestedQueries: computed,
121
+ });
122
+ this.baseStore = baseStore;
123
+ }
124
+ get config() {
125
+ return this.baseStore.applicationStore.config.legendAIConfig;
126
+ }
127
+ get plugin() {
128
+ return findLegendAIPlugin(this.baseStore.pluginManager.getApplicationPlugins());
129
+ }
130
+ get isEnabled() {
131
+ return this.config.enabled && this.plugin !== undefined;
132
+ }
133
+ get lastUserMessageText() {
134
+ return (this.messages.findLast((m) => m.role === LegendAIMessageRole.USER)
135
+ ?.text ?? '');
136
+ }
137
+ get welcomeSuggestedQueries() {
138
+ return (this.baseStore.applicationStore.config.options
139
+ .defaultAISuggestedQueries ?? DEFAULT_SUGGESTED_QUERIES);
140
+ }
141
+ setQuestionText(text) {
142
+ this.questionText = text;
143
+ }
144
+ setStage(stage) {
145
+ this.stage = stage;
146
+ }
147
+ clearChat() {
148
+ this.messages = [];
149
+ this.suggestedProducts = [];
150
+ this.scoredCandidates = [];
151
+ this.selectedProduct = undefined;
152
+ const firstScope = this.scopeProducts[0];
153
+ this.selectedProductCoordinates = firstScope?.coordinates;
154
+ this.selectedProductMetadata = firstScope
155
+ ? {
156
+ name: firstScope.name,
157
+ coordinates: `${firstScope.coordinates.group_id}:${firstScope.coordinates.artifact_id}:${firstScope.coordinates.version}`,
158
+ serviceSummaries: [],
159
+ }
160
+ : undefined;
161
+ this.pureExecutionContext = undefined;
162
+ this.pendingFallbackQuestion = undefined;
163
+ this.resolvedProductServices = [];
164
+ this.lastResolvedEntities = undefined;
165
+ this.lastEntityCandidates = [];
166
+ this.selectedDataProductId = undefined;
167
+ this.stage = MarketplaceAIChatStage.IDLE;
168
+ this.questionText = '';
169
+ this.isSending = false;
170
+ }
171
+ createMessageSetter() {
172
+ return (updater) => {
173
+ runInAction(() => {
174
+ if (typeof updater === 'function') {
175
+ this.messages = updater(this.messages);
176
+ }
177
+ else {
178
+ this.messages = updater;
179
+ }
180
+ });
181
+ };
182
+ }
183
+ buildContextPromise(question, metadata, setMessages) {
184
+ if (!this.plugin) {
185
+ return Promise.resolve();
186
+ }
187
+ return this.plugin
188
+ .buildDataContextSummary(question, metadata, this.config)
189
+ .then((contextText) => {
190
+ if (contextText) {
191
+ updateLastAssistant(setMessages, () => ({
192
+ dataContext: contextText,
193
+ }));
194
+ }
195
+ })
196
+ .catch(() => {
197
+ /* Non-fatal */
198
+ });
199
+ }
200
+ buildConversationHistory() {
201
+ return buildConversationHistory(this.messages);
202
+ }
203
+ extractMetadata(result, coordinates) {
204
+ const metadata = {
205
+ name: result.dataProductTitle ?? 'Unknown',
206
+ coordinates: `${coordinates.group_id}:${coordinates.artifact_id}:${coordinates.version}`,
207
+ serviceSummaries: [],
208
+ accessPointGroups: [],
209
+ };
210
+ if (result.dataProductDescription !== null) {
211
+ metadata.description = result.dataProductDescription;
212
+ }
213
+ const tags1 = result.tags1;
214
+ const tags2 = result.tags2;
215
+ if (tags1.length > 0 || tags2.length > 0) {
216
+ metadata.tags = [...tags1, ...tags2].map((t) => ({
217
+ profile: 'tag',
218
+ value: t,
219
+ }));
220
+ }
221
+ return metadata;
222
+ }
223
+ buildTitleFromPath(path, artifactId) {
224
+ const parts = path.split('::');
225
+ const filtered = parts.filter((p) => p.toLowerCase() !== 'dataspace' &&
226
+ p.toLowerCase() !== 'model' &&
227
+ !p.toLowerCase().endsWith('dataspace'));
228
+ if (filtered.length > 0) {
229
+ return filtered
230
+ .map((p) => p
231
+ .replaceAll(/(?<lower>[a-z])(?<upper>[A-Z])/g, '$<lower> $<upper>')
232
+ .replace(/^./, (c) => c.toUpperCase()))
233
+ .join(' ');
234
+ }
235
+ return artifactId
236
+ .split('-')
237
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
238
+ .join(' ');
239
+ }
240
+ async multiSignalSearch(question, setMessages) {
241
+ const env = this.baseStore.envState.lakehouseEnvironment;
242
+ addThinkingStep(setMessages, 'Searching products and fields in parallel...');
243
+ const [productRaw, fieldRaw] = await Promise.all([
244
+ this.baseStore.marketplaceServerClient.dataProductSearch(question, env, FieldSearchType.HYBRID, [], PRODUCT_SEARCH_PAGE_SIZE, 1, false),
245
+ this.baseStore.marketplaceServerClient
246
+ .fieldSearch(env, {
247
+ query: question,
248
+ searchType: FieldSearchType.HYBRID,
249
+ pageSize: FIELD_SEARCH_PAGE_SIZE,
250
+ pageNumber: 1,
251
+ })
252
+ .catch(() => null),
253
+ ]);
254
+ const productResponse = DataProductSearchResponse.serialization.fromJson(productRaw);
255
+ const productResults = productResponse.results.filter((r) => r.dataProductDetails instanceof
256
+ LakehouseDataProductSearchResultDetails ||
257
+ r.dataProductDetails instanceof LegacyDataProductSearchResultDetails);
258
+ let fieldResults = [];
259
+ if (fieldRaw) {
260
+ try {
261
+ const fieldResponse = GroupedFieldSearchResponse.serialization.fromJson(fieldRaw);
262
+ fieldResults = fieldResponse.results;
263
+ }
264
+ catch {
265
+ /* Non-fatal: field search is best-effort */
266
+ }
267
+ }
268
+ if (fieldResults.length > 0) {
269
+ addThinkingStep(setMessages, `Found ${fieldResults.length} matching field${fieldResults.length > 1 ? 's' : ''} across products`);
270
+ }
271
+ return { productResults, fieldResults };
272
+ }
273
+ deriveProductsFromFieldResults(fieldResults, existingProducts) {
274
+ const existingKeys = new Set(existingProducts.map((p) => {
275
+ const { groupId, artifactId } = unwrapProductDetails(p);
276
+ return `${groupId}:${artifactId}`;
277
+ }));
278
+ const productFieldCounts = new Map();
279
+ for (const dp of fieldResults.flatMap((entry) => entry.dataProducts)) {
280
+ if (!dp.groupId || !dp.artifactId || !dp.versionId) {
281
+ continue;
282
+ }
283
+ const key = `${dp.groupId}:${dp.artifactId}`;
284
+ if (existingKeys.has(key)) {
285
+ continue;
286
+ }
287
+ const existing = productFieldCounts.get(key);
288
+ if (existing) {
289
+ existing.fieldCount += 1;
290
+ }
291
+ else {
292
+ productFieldCounts.set(key, {
293
+ path: dp.path,
294
+ productType: dp.productType,
295
+ groupId: dp.groupId,
296
+ artifactId: dp.artifactId,
297
+ versionId: dp.versionId,
298
+ ...(dp.dataProductId === undefined
299
+ ? {}
300
+ : { dataProductId: dp.dataProductId }),
301
+ fieldCount: 1,
302
+ });
303
+ }
304
+ }
305
+ const sorted = [...productFieldCounts.values()].sort((a, b) => b.fieldCount - a.fieldCount);
306
+ return sorted
307
+ .slice(0, MAX_PRODUCT_SUGGESTIONS)
308
+ .map((entry) => this.buildDerivedProduct(entry));
309
+ }
310
+ buildDerivedProduct(entry) {
311
+ const product = new DataProductSearchResult();
312
+ product.dataProductTitle = this.buildTitleFromPath(entry.path, entry.artifactId);
313
+ product.dataProductDescription = null;
314
+ product.tags1 = [];
315
+ product.tags2 = [];
316
+ product.tag_score = 0;
317
+ product.similarity = 0;
318
+ if (entry.productType === DataProductSearchResultDetailsType.LEGACY) {
319
+ const details = new LegacyDataProductSearchResultDetails();
320
+ details.groupId = entry.groupId;
321
+ details.artifactId = entry.artifactId;
322
+ details.versionId = entry.versionId;
323
+ details.path = entry.path;
324
+ product.dataProductDetails = details;
325
+ }
326
+ else {
327
+ const origin = new LakehouseSDLCDataProductSearchResultOrigin();
328
+ origin.groupId = entry.groupId;
329
+ origin.artifactId = entry.artifactId;
330
+ origin.versionId = entry.versionId;
331
+ origin.path = entry.path;
332
+ const details = new LakehouseDataProductSearchResultDetails();
333
+ details.dataProductId = entry.dataProductId ?? '';
334
+ details.deploymentId = 0;
335
+ details.producerEnvironmentName = '';
336
+ details.producerEnvironmentType = undefined;
337
+ details.origin = origin;
338
+ product.dataProductDetails = details;
339
+ }
340
+ return product;
341
+ }
342
+ computeScoredCandidates(productResults, fieldResults) {
343
+ const allFieldNames = fieldResults.map((f) => f.fieldName);
344
+ const maxSimilarity = productResults.length > 0
345
+ ? Math.max(...productResults.map((p) => p.similarity))
346
+ : 1;
347
+ const scoreProduct = (product) => {
348
+ const { groupId, artifactId, path: productPath, } = unwrapProductDetails(product);
349
+ const matchedFields = [];
350
+ const missingFields = [];
351
+ for (const fieldEntry of fieldResults) {
352
+ const inProduct = fieldEntry.dataProducts.some((dp) => dp.path === productPath ||
353
+ (dp.groupId &&
354
+ dp.artifactId &&
355
+ groupId === dp.groupId &&
356
+ artifactId === dp.artifactId) ||
357
+ (productPath.length > 0 && dp.path.includes(productPath)) ||
358
+ (dp.path.length > 0 && productPath.includes(dp.path)));
359
+ if (inProduct) {
360
+ matchedFields.push(fieldEntry.fieldName);
361
+ }
362
+ else {
363
+ missingFields.push(fieldEntry.fieldName);
364
+ }
365
+ }
366
+ const productSimilarity = product.similarity;
367
+ const normalizedSimilarity = maxSimilarity > 0 ? productSimilarity / maxSimilarity : 0;
368
+ const fieldCoverage = allFieldNames.length > 0
369
+ ? matchedFields.length / allFieldNames.length
370
+ : 0;
371
+ const fieldIntersection = allFieldNames.length > 0 && missingFields.length === 0 ? 1 : 0;
372
+ const compositeScore = allFieldNames.length > 0
373
+ ? normalizedSimilarity + FIELD_COVERAGE_BOOST * fieldCoverage
374
+ : normalizedSimilarity;
375
+ return {
376
+ product,
377
+ productSimilarity,
378
+ fieldCoverage,
379
+ fieldIntersection,
380
+ matchedFields,
381
+ missingFields,
382
+ compositeScore,
383
+ };
384
+ };
385
+ // Score product search results
386
+ const productCandidates = productResults.map(scoreProduct);
387
+ productCandidates.sort((a, b) => b.compositeScore - a.compositeScore);
388
+ // Score field-derived products (discovered from field search, not in product search)
389
+ const fieldDerived = this.deriveProductsFromFieldResults(fieldResults, productResults);
390
+ const fieldCandidates = fieldDerived.map(scoreProduct);
391
+ fieldCandidates.sort((a, b) => b.fieldCoverage - a.fieldCoverage);
392
+ // Merge: interleave top product results with top field-derived results
393
+ // so both signals are represented in the final list
394
+ return this.mergeInterleaved(productCandidates, fieldCandidates, MERGED_CANDIDATE_LIMIT);
395
+ }
396
+ mergeInterleaved(productCandidates, fieldCandidates, limit) {
397
+ const merged = [];
398
+ const seenKeys = new Set();
399
+ let pIdx = 0;
400
+ let fIdx = 0;
401
+ const tryAdd = (candidate) => {
402
+ const { groupId, artifactId } = unwrapProductDetails(candidate.product);
403
+ const key = `${groupId}:${artifactId}`;
404
+ if (!seenKeys.has(key)) {
405
+ seenKeys.add(key);
406
+ merged.push(candidate);
407
+ }
408
+ };
409
+ while (merged.length < limit &&
410
+ (pIdx < productCandidates.length || fIdx < fieldCandidates.length)) {
411
+ // Add 2 from product search, then 1 from field-derived, repeat
412
+ const fromProduct = merged.length % 3 !== 2;
413
+ if (pIdx < productCandidates.length &&
414
+ (fromProduct || fIdx >= fieldCandidates.length)) {
415
+ tryAdd(guaranteeNonNullable(productCandidates[pIdx]));
416
+ pIdx++;
417
+ }
418
+ else if (fIdx < fieldCandidates.length) {
419
+ tryAdd(guaranteeNonNullable(fieldCandidates[fIdx]));
420
+ fIdx++;
421
+ }
422
+ else {
423
+ break;
424
+ }
425
+ }
426
+ return merged;
427
+ }
428
+ async llmRerankProducts(question, candidates, fieldResults, setMessages) {
429
+ const plugin = this.plugin;
430
+ if (!plugin || candidates.length <= MAX_PRODUCT_SUGGESTIONS) {
431
+ return candidates.slice(0, MAX_PRODUCT_SUGGESTIONS);
432
+ }
433
+ addThinkingStep(setMessages, 'Using AI to rank best matching products...');
434
+ const candidateInputs = candidates.map((c) => ({
435
+ title: c.product.dataProductTitle ?? 'Unknown',
436
+ description: c.product.dataProductDescription
437
+ ? c.product.dataProductDescription.slice(0, DESCRIPTION_PREVIEW_LENGTH)
438
+ : '',
439
+ matchedFields: c.matchedFields,
440
+ }));
441
+ const allFieldNames = fieldResults.map((f) => f.fieldName);
442
+ const indices = await plugin.rerankProducts(question, candidateInputs, allFieldNames, MAX_PRODUCT_SUGGESTIONS, this.config);
443
+ if (indices && indices.length > 0) {
444
+ return this.buildRankedList(indices, candidates, MAX_PRODUCT_SUGGESTIONS);
445
+ }
446
+ return candidates.slice(0, MAX_PRODUCT_SUGGESTIONS);
447
+ }
448
+ buildRankedList(indices, candidates, limit) {
449
+ const ranked = [];
450
+ for (const idx of indices) {
451
+ if (ranked.length >= limit) {
452
+ break;
453
+ }
454
+ if (idx >= 0 && idx < candidates.length) {
455
+ ranked.push(guaranteeNonNullable(candidates[idx]));
456
+ }
457
+ }
458
+ for (const c of candidates) {
459
+ if (ranked.length >= limit) {
460
+ break;
461
+ }
462
+ if (!ranked.includes(c)) {
463
+ ranked.push(c);
464
+ }
465
+ }
466
+ return ranked;
467
+ }
468
+ *submitQuery(text) {
469
+ const trimmed = text.trim();
470
+ if (!trimmed || this.isSending || !this.plugin) {
471
+ return;
472
+ }
473
+ this.isSending = true;
474
+ this.questionText = '';
475
+ this.messages = [...this.messages, ...createMessagePair(trimmed)];
476
+ const setMessages = this.createMessageSetter();
477
+ try {
478
+ if (this.selectedProductCoordinates) {
479
+ this.stage = MarketplaceAIChatStage.QUERYING;
480
+ const relevantDatasets = (yield this.enrichWithEntitySearch(trimmed, setMessages));
481
+ yield this.dispatchWithSql2(trimmed, relevantDatasets, setMessages);
482
+ this.stage = MarketplaceAIChatStage.RESULTS;
483
+ return;
484
+ }
485
+ this.stage = MarketplaceAIChatStage.SEARCHING;
486
+ const { productResults, fieldResults } = (yield this.multiSignalSearch(trimmed, setMessages));
487
+ const candidates = this.computeScoredCandidates(productResults, fieldResults);
488
+ if (candidates.length === 0) {
489
+ completeThinkingSteps(setMessages);
490
+ updateLastAssistant(setMessages, () => ({
491
+ textAnswer: 'I could not find any data products matching your query. Please try rephrasing or use more specific terms.',
492
+ isProcessing: false,
493
+ }));
494
+ this.stage = MarketplaceAIChatStage.IDLE;
495
+ }
496
+ else {
497
+ const topCandidates = (yield this.llmRerankProducts(trimmed, candidates, fieldResults, setMessages));
498
+ const top = guaranteeNonNullable(topCandidates[0]);
499
+ addThinkingStep(setMessages, `Top candidate: ${top.product.dataProductTitle ?? 'Unknown'} (${(top.compositeScore * 100).toFixed(0)}% composite)`);
500
+ completeThinkingSteps(setMessages);
501
+ this.suggestedProducts = topCandidates.map((c) => c.product);
502
+ this.scoredCandidates = topCandidates;
503
+ const hasFieldInfo = fieldResults.length > 0 &&
504
+ topCandidates.some((c) => c.matchedFields.length > 0);
505
+ let message = `I found ${candidates.length} data product${candidates.length > 1 ? 's' : ''} that may contain the data you need.`;
506
+ if (hasFieldInfo) {
507
+ message += ' Field availability is shown for each product.';
508
+ }
509
+ message += ' Please select one to continue:';
510
+ updateLastAssistant(setMessages, () => ({
511
+ textAnswer: message,
512
+ isProcessing: false,
513
+ }));
514
+ this.stage = MarketplaceAIChatStage.PRODUCT_SELECTION;
515
+ }
516
+ }
517
+ catch (error) {
518
+ assertErrorThrown(error);
519
+ finishWithThinkingError(setMessages, error.message, Date.now(), classifyError(error));
520
+ this.stage = MarketplaceAIChatStage.IDLE;
521
+ }
522
+ finally {
523
+ this.isSending = false;
524
+ }
525
+ }
526
+ selectDataProduct(result) {
527
+ const { groupId, artifactId, versionId, path } = unwrapProductDetails(result);
528
+ if (!groupId || !artifactId || !versionId || !path) {
529
+ return;
530
+ }
531
+ const coordinates = {
532
+ data_product: path,
533
+ group_id: groupId,
534
+ artifact_id: artifactId,
535
+ version: versionId,
536
+ };
537
+ this.selectedProduct = result;
538
+ this.selectedProductCoordinates = coordinates;
539
+ this.selectedProductMetadata = this.extractMetadata(result, coordinates);
540
+ this.suggestedProducts = [];
541
+ const details = result.dataProductDetails;
542
+ if (details instanceof LakehouseDataProductSearchResultDetails) {
543
+ this.selectedDataProductId = details.dataProductId;
544
+ }
545
+ else {
546
+ this.selectedDataProductId = undefined;
547
+ }
548
+ }
549
+ selectAutosuggestProduct(result) {
550
+ const searchResult = convertAutosuggestResultToSearchResult(result);
551
+ this.selectDataProduct(searchResult);
552
+ }
553
+ deselectProduct() {
554
+ this.selectedProduct = undefined;
555
+ this.selectedProductCoordinates = undefined;
556
+ this.selectedProductMetadata = undefined;
557
+ this.pureExecutionContext = undefined;
558
+ this.resolvedProductServices = [];
559
+ this.lastResolvedEntities = undefined;
560
+ this.lastEntityCandidates = [];
561
+ this.selectedDataProductId = undefined;
562
+ this.stage = MarketplaceAIChatStage.PRODUCT_SELECTION;
563
+ }
564
+ addScopeProduct(result) {
565
+ const details = result.dataProductDetails;
566
+ let groupId;
567
+ let artifactId;
568
+ let versionId;
569
+ let path;
570
+ if (details._type === DataProductDetailsType.LAKEHOUSE &&
571
+ details.origin !== undefined) {
572
+ groupId = details.origin.groupId;
573
+ artifactId = details.origin.artifactId;
574
+ versionId = details.origin.versionId;
575
+ path = details.origin.path;
576
+ }
577
+ else {
578
+ groupId = details.groupId;
579
+ artifactId = details.artifactId;
580
+ versionId = details.versionId;
581
+ path = details.path;
582
+ }
583
+ if (!groupId || !artifactId || !versionId || !path) {
584
+ return;
585
+ }
586
+ const key = `${groupId}:${artifactId}:${versionId}`;
587
+ if (this.scopeProducts.some((p) => `${p.coordinates.group_id}:${p.coordinates.artifact_id}:${p.coordinates.version}` ===
588
+ key)) {
589
+ return;
590
+ }
591
+ if (this.scopeProducts.length >= 3) {
592
+ return;
593
+ }
594
+ const coords = {
595
+ data_product: path,
596
+ group_id: groupId,
597
+ artifact_id: artifactId,
598
+ version: versionId,
599
+ };
600
+ this.scopeProducts = [
601
+ ...this.scopeProducts,
602
+ { name: result.dataProductName, coordinates: coords },
603
+ ];
604
+ if (this.scopeProducts.length === 1) {
605
+ this.selectedProductCoordinates = coords;
606
+ this.selectedProductMetadata = {
607
+ name: result.dataProductName,
608
+ description: result.dataProductDescription,
609
+ coordinates: key,
610
+ serviceSummaries: [],
611
+ };
612
+ this.selectedDataProductId = details.dataProductId;
613
+ }
614
+ }
615
+ removeScopeProduct(index) {
616
+ this.scopeProducts = this.scopeProducts.filter((_, i) => i !== index);
617
+ if (this.selectedProduct === undefined) {
618
+ const firstScope = this.scopeProducts[0];
619
+ this.selectedProductCoordinates = firstScope?.coordinates;
620
+ this.selectedProductMetadata = firstScope
621
+ ? {
622
+ name: firstScope.name,
623
+ coordinates: `${firstScope.coordinates.group_id}:${firstScope.coordinates.artifact_id}:${firstScope.coordinates.version}`,
624
+ serviceSummaries: [],
625
+ }
626
+ : undefined;
627
+ this.pureExecutionContext = undefined;
628
+ this.resolvedProductServices = [];
629
+ this.lastResolvedEntities = undefined;
630
+ this.lastEntityCandidates = [];
631
+ this.selectedDataProductId = undefined;
632
+ }
633
+ }
634
+ async resolveExecutionContext(setMessages) {
635
+ const product = this.selectedProduct;
636
+ const coordinates = this.selectedProductCoordinates;
637
+ if (!coordinates) {
638
+ return;
639
+ }
640
+ addThinkingStep(setMessages, 'Resolving execution context...');
641
+ try {
642
+ let dataSpace;
643
+ if (product) {
644
+ const details = product.dataProductDetails;
645
+ if (details instanceof LegacyDataProductSearchResultDetails) {
646
+ const entity = await this.baseStore.depotServerClient.getVersionEntity(details.groupId, details.artifactId, details.versionId, details.path);
647
+ dataSpace = V1_deserializeDataSpace(entity.content);
648
+ }
649
+ else if (details instanceof LakehouseDataProductSearchResultDetails &&
650
+ details.origin instanceof
651
+ LakehouseSDLCDataProductSearchResultOrigin &&
652
+ details.origin.groupId &&
653
+ details.origin.artifactId &&
654
+ details.origin.versionId &&
655
+ details.origin.path) {
656
+ const entity = await this.baseStore.depotServerClient.getVersionEntity(details.origin.groupId, details.origin.artifactId, details.origin.versionId, details.origin.path);
657
+ dataSpace = V1_deserializeDataSpace(entity.content);
658
+ }
659
+ }
660
+ else {
661
+ const entity = await this.baseStore.depotServerClient.getVersionEntity(coordinates.group_id, coordinates.artifact_id, coordinates.version, coordinates.data_product);
662
+ dataSpace = V1_deserializeDataSpace(entity.content);
663
+ }
664
+ if (dataSpace && dataSpace.executionContexts.length > 0) {
665
+ const defaultCtxName = dataSpace.defaultExecutionContext;
666
+ const execCtx = dataSpace.executionContexts.find((c) => c.name === defaultCtxName) ??
667
+ guaranteeNonNullable(dataSpace.executionContexts[0]);
668
+ const ctx = new QueryExplicitExecutionContextInfo();
669
+ ctx.mapping = execCtx.mapping.path;
670
+ ctx.runtime = execCtx.defaultRuntime.path;
671
+ runInAction(() => {
672
+ this.pureExecutionContext = ctx;
673
+ });
674
+ }
675
+ }
676
+ catch (error) {
677
+ assertErrorThrown(error);
678
+ addThinkingStep(setMessages, `Warning: Could not resolve execution context — ${error.message}`);
679
+ }
680
+ }
681
+ *askFollowUp(text) {
682
+ const trimmed = text.trim();
683
+ if (!trimmed ||
684
+ this.isSending ||
685
+ !this.plugin ||
686
+ !this.selectedProductCoordinates) {
687
+ return;
688
+ }
689
+ this.isSending = true;
690
+ this.questionText = '';
691
+ this.messages = [...this.messages, ...createMessagePair(trimmed)];
692
+ const setMessages = this.createMessageSetter();
693
+ try {
694
+ this.stage = MarketplaceAIChatStage.QUERYING;
695
+ const relevantDatasets = (yield this.enrichWithEntitySearch(trimmed, setMessages));
696
+ yield this.dispatchWithSql2(trimmed, relevantDatasets, setMessages);
697
+ this.stage = MarketplaceAIChatStage.RESULTS;
698
+ }
699
+ catch (error) {
700
+ assertErrorThrown(error);
701
+ finishWithThinkingError(setMessages, error.message, Date.now(), classifyError(error));
702
+ }
703
+ finally {
704
+ this.isSending = false;
705
+ }
706
+ }
707
+ async enrichWithEntitySearch(question, setMessages) {
708
+ const coordinates = this.selectedProductCoordinates;
709
+ if (!coordinates) {
710
+ return [];
711
+ }
712
+ addThinkingStep(setMessages, 'Searching for relevant datasets and fields...');
713
+ try {
714
+ const env = this.baseStore.envState.lakehouseEnvironment;
715
+ const entitySearchOptions = {
716
+ groupId: coordinates.group_id,
717
+ artifactId: coordinates.artifact_id,
718
+ versionId: coordinates.version,
719
+ path: coordinates.data_product,
720
+ ...(this.selectedDataProductId === undefined
721
+ ? {}
722
+ : { dataProductId: this.selectedDataProductId }),
723
+ searchType: FieldSearchType.HYBRID,
724
+ pageSize: DATASET_SEARCH_PAGE_SIZE,
725
+ };
726
+ const [primaryRaw, diversityRaw] = await Promise.all([
727
+ this.baseStore.marketplaceServerClient.entitySearch(env, question, entitySearchOptions),
728
+ this.baseStore.marketplaceServerClient
729
+ .entitySearch(env, coordinates.data_product.split('::').pop() ?? 'data', entitySearchOptions)
730
+ .catch(() => undefined),
731
+ ]);
732
+ const primaryResponse = EntitySearchResponse.serialization.fromJson(primaryRaw);
733
+ const results = primaryResponse.results;
734
+ this.mergeDiversityResults(results, diversityRaw);
735
+ if (results.length > 0) {
736
+ const topDataset = guaranteeNonNullable(results[0]);
737
+ addThinkingStep(setMessages, `Found ${results.length} relevant dataset${results.length > 1 ? 's' : ''} — top: ${topDataset.datasetName}`);
738
+ if (this.selectedProductMetadata) {
739
+ const datasetSummaries = results
740
+ .slice(0, MAX_RELEVANT_SERVICES)
741
+ .map((r) => ({
742
+ title: r.datasetName,
743
+ ...(r.datasetDescription === undefined
744
+ ? {}
745
+ : { description: r.datasetDescription }),
746
+ }));
747
+ const existingTitles = new Set(this.selectedProductMetadata.serviceSummaries.map((s) => s.title));
748
+ const newSummaries = datasetSummaries.filter((s) => !existingTitles.has(s.title));
749
+ const currentMetadata = this.selectedProductMetadata;
750
+ runInAction(() => {
751
+ this.selectedProductMetadata = {
752
+ ...currentMetadata,
753
+ serviceSummaries: [
754
+ ...currentMetadata.serviceSummaries,
755
+ ...newSummaries,
756
+ ],
757
+ };
758
+ });
759
+ }
760
+ this.buildServicesFromEntitySearch(results, setMessages);
761
+ await this.resolveEntityCandidates(question, results, coordinates);
762
+ }
763
+ return results.map((r) => r.datasetName);
764
+ }
765
+ catch (error) {
766
+ assertErrorThrown(error);
767
+ addThinkingStep(setMessages, `Warning: Dataset search unavailable — ${error.message}`);
768
+ return [];
769
+ }
770
+ }
771
+ async resolveEntityCandidates(question, results, coordinates) {
772
+ const entitiesWithPaths = results.filter((r) => r.datasetDetails?.modelPath);
773
+ if (entitiesWithPaths.length === 0 || !this.plugin) {
774
+ return;
775
+ }
776
+ const candidates = entitiesWithPaths.map((r) => ({
777
+ datasetName: r.datasetName,
778
+ ...(r.datasetDescription === undefined
779
+ ? {}
780
+ : { description: r.datasetDescription }),
781
+ modelPath: guaranteeNonNullable(r.datasetDetails).modelPath,
782
+ similarityScore: r.similarityScore,
783
+ }));
784
+ runInAction(() => {
785
+ this.lastEntityCandidates = candidates
786
+ .slice(0, MAX_PRODUCT_SUGGESTIONS)
787
+ .map((c) => ({
788
+ datasetName: c.datasetName,
789
+ modelPath: c.modelPath,
790
+ ...(c.description === undefined
791
+ ? {}
792
+ : { description: c.description }),
793
+ }));
794
+ });
795
+ try {
796
+ const resolved = await this.plugin.disambiguateEntity(question, candidates, this.config, this.pureExecutionContext, coordinates);
797
+ runInAction(() => {
798
+ this.lastResolvedEntities = resolved;
799
+ });
800
+ }
801
+ catch {
802
+ const topEntity = entitiesWithPaths[0];
803
+ if (topEntity) {
804
+ const resolved = new LegendAIResolvedEntities();
805
+ resolved.rootEntity =
806
+ topEntity.datasetDetails?.modelPath ?? topEntity.datasetName;
807
+ resolved.relatedEntities = entitiesWithPaths
808
+ .slice(1, MAX_RELEVANT_SERVICES + 1)
809
+ .map((r) => r.datasetDetails?.modelPath)
810
+ .filter((p) => p !== undefined);
811
+ runInAction(() => {
812
+ this.lastResolvedEntities = resolved;
813
+ });
814
+ }
815
+ }
816
+ }
817
+ mergeDiversityResults(results, diversityRaw) {
818
+ if (!diversityRaw) {
819
+ return;
820
+ }
821
+ const diversityResponse = EntitySearchResponse.serialization.fromJson(diversityRaw);
822
+ const existingPaths = new Set(results
823
+ .filter((r) => r.datasetDetails?.modelPath)
824
+ .map((r) => guaranteeNonNullable(r.datasetDetails).modelPath));
825
+ for (const r of diversityResponse.results) {
826
+ if (r.datasetDetails?.modelPath &&
827
+ !existingPaths.has(r.datasetDetails.modelPath)) {
828
+ results.push(r);
829
+ existingPaths.add(r.datasetDetails.modelPath);
830
+ }
831
+ }
832
+ }
833
+ buildServicesFromEntitySearch(results, setMessages) {
834
+ const coordinates = this.selectedProductCoordinates;
835
+ if (!coordinates) {
836
+ return;
837
+ }
838
+ // Skip service building for legacy dataspaces so the flow routes to the orchestrator instead.
839
+ const firstResult = results[0];
840
+ if (firstResult?.dataProductDetails?._type === DataProductDetailsType.LEGACY) {
841
+ return;
842
+ }
843
+ const fallbackPath = coordinates.data_product;
844
+ const services = [];
845
+ let totalColumns = 0;
846
+ for (const result of results) {
847
+ const fields = result.relatedFields ?? [];
848
+ const columns = fields.map((f) => ({
849
+ name: f.fieldName,
850
+ type: f.fieldType ?? 'String',
851
+ ...(f.fieldDescription === undefined
852
+ ? {}
853
+ : { documentation: f.fieldDescription }),
854
+ }));
855
+ totalColumns += columns.length;
856
+ services.push({
857
+ title: result.datasetName,
858
+ pattern: `/${result.datasetName}`,
859
+ columns,
860
+ parameters: [],
861
+ sourceType: TDSServiceSourceType.ACCESS_POINT,
862
+ dataProductPath: result.datasetDetails?.modelPath ?? fallbackPath,
863
+ });
864
+ }
865
+ if (services.length > 0) {
866
+ runInAction(() => {
867
+ this.resolvedProductServices = services;
868
+ });
869
+ addThinkingStep(setMessages, `Loaded ${services.length} relevant dataset${services.length > 1 ? 's' : ''} with ${totalColumns} fields`);
870
+ }
871
+ }
872
+ getServicesForQuery(relevantDatasetNames) {
873
+ if (this.resolvedProductServices.length === 0) {
874
+ return [];
875
+ }
876
+ if (relevantDatasetNames.length === 0) {
877
+ return this.resolvedProductServices.slice(0, MAX_RELEVANT_SERVICES);
878
+ }
879
+ const relevantSet = new Set(relevantDatasetNames.map((n) => n.toLowerCase()));
880
+ const relevant = [];
881
+ for (const service of this.resolvedProductServices) {
882
+ if (relevantSet.has(service.title.toLowerCase())) {
883
+ relevant.push(service);
884
+ }
885
+ }
886
+ relevant.sort((a, b) => {
887
+ const aIdx = relevantDatasetNames.findIndex((n) => n.toLowerCase() === a.title.toLowerCase());
888
+ const bIdx = relevantDatasetNames.findIndex((n) => n.toLowerCase() === b.title.toLowerCase());
889
+ return aIdx - bIdx;
890
+ });
891
+ return relevant;
892
+ }
893
+ async dispatchWithSql2(question, relevantDatasetNames, setMessages) {
894
+ const plugin = this.plugin;
895
+ const coordinates = this.selectedProductCoordinates;
896
+ const metadata = this.selectedProductMetadata;
897
+ if (!plugin || !coordinates || !metadata) {
898
+ return;
899
+ }
900
+ const config = this.config;
901
+ const history = this.buildConversationHistory();
902
+ const context = { config, plugin, history, setMessages };
903
+ const intent = await plugin.classifyQuestionIntent(question, false, config);
904
+ if (intent === LegendAIQuestionIntent.METADATA) {
905
+ await handleMetadataQuestion(question, metadata, context, Date.now(), true);
906
+ return;
907
+ }
908
+ const services = this.getServicesForQuery(relevantDatasetNames);
909
+ const coordinatesStr = `${coordinates.group_id}:${coordinates.artifact_id}:${coordinates.version}`;
910
+ const startTime = Date.now();
911
+ const contextPromise = this.buildContextPromise(question, metadata, setMessages);
912
+ if (services.length === 0) {
913
+ addThinkingStep(setMessages, 'No dataset schemas available — entity search did not return results for this data product.');
914
+ completeThinkingSteps(setMessages);
915
+ updateLastAssistant(setMessages, () => ({
916
+ textAnswer: 'Could not resolve dataset schemas for this data product. You can try the Legend AI Orchestrator to generate a Pure query instead.',
917
+ isProcessing: false,
918
+ }));
919
+ this.offerOrchestratorFallback(question, setMessages, startTime);
920
+ await contextPromise;
921
+ return;
922
+ }
923
+ const totalColumns = services.reduce((sum, s) => sum + s.columns.length, 0);
924
+ addThinkingStep(setMessages, `Generating Alloy SQL 2.0 query with ${services.length} relevant dataset${services.length > 1 ? 's' : ''} (${totalColumns} columns)...`);
925
+ try {
926
+ const judgedSql = await generateAndJudgeSql(question, services, coordinatesStr, context, startTime);
927
+ if (!judgedSql) {
928
+ this.offerOrchestratorFallback(question, setMessages, startTime);
929
+ await contextPromise;
930
+ return;
931
+ }
932
+ const sqlGenTime = elapsedSeconds(startTime, 2);
933
+ completeThinkingSteps(setMessages);
934
+ updateLastAssistant(setMessages, () => ({
935
+ sql: judgedSql,
936
+ sqlGenTime,
937
+ isExecuting: true,
938
+ }));
939
+ const sqlResult = await executeSqlAndReport(judgedSql, services, config, plugin, setMessages, startTime, coordinates);
940
+ if (!sqlResult) {
941
+ this.offerOrchestratorFallback(question, setMessages, startTime);
942
+ await contextPromise;
943
+ return;
944
+ }
945
+ if (sqlResult.rows.length === 0) {
946
+ const corrected = await this.attemptZeroRowCorrection(judgedSql, question, services, coordinatesStr, setMessages, coordinates);
947
+ if (corrected) {
948
+ await contextPromise;
949
+ await this.safeAnalyzeResults(question, corrected.sql, corrected.result, metadata, context, startTime);
950
+ return;
951
+ }
952
+ const datasetList = services
953
+ .slice(0, MAX_RELEVANT_SERVICES)
954
+ .map((s) => s.title)
955
+ .join(', ');
956
+ const datasetSuffix = services.length > MAX_RELEVANT_SERVICES
957
+ ? ` and ${services.length - MAX_RELEVANT_SERVICES} more`
958
+ : '';
959
+ updateLastAssistant(setMessages, () => ({
960
+ textAnswer: `The SQL 2.0 query executed successfully but returned **0 rows**. The applied filters may not match any records in the available datasets, or the specific values may not exist.\n\n**Queried datasets:** ${datasetList}${datasetSuffix}`,
961
+ }));
962
+ this.offerOrchestratorFallback(question, setMessages, startTime);
963
+ await contextPromise;
964
+ return;
965
+ }
966
+ await contextPromise;
967
+ await this.safeAnalyzeResults(question, judgedSql, sqlResult, metadata, context, startTime);
968
+ }
969
+ catch (error) {
970
+ assertErrorThrown(error);
971
+ addThinkingStep(setMessages, `SQL 2.0 failed: ${error.message}`);
972
+ const datasetContext = services.length > 0
973
+ ? `\n\nAvailable datasets: ${services.map((s) => s.title).join(', ')}`
974
+ : '';
975
+ finishWithThinkingError(setMessages, `Alloy SQL 2.0 encountered an error: ${error.message}${datasetContext}`, startTime, classifyError(error));
976
+ this.offerOrchestratorFallback(question, setMessages, startTime);
977
+ await contextPromise;
978
+ }
979
+ }
980
+ async safeAnalyzeResults(question, sql, result, metadata, context, startTime) {
981
+ try {
982
+ await analyzeOrchestratorResults(question, sql, result, metadata, context, startTime);
983
+ }
984
+ catch {
985
+ completeThinkingSteps(context.setMessages);
986
+ updateLastAssistant(context.setMessages, () => ({
987
+ isProcessing: false,
988
+ thinkingDuration: elapsedSeconds(startTime),
989
+ }));
990
+ }
991
+ }
992
+ async attemptZeroRowCorrection(currentSql, question, services, coordinatesStr, setMessages, dataProductCoordinates) {
993
+ const config = this.config;
994
+ const plugin = this.plugin;
995
+ if (!plugin) {
996
+ return undefined;
997
+ }
998
+ addThinkingStep(setMessages, 'Query returned 0 rows, attempting filter correction...');
999
+ const prompt = plugin.buildZeroRowCorrectionPrompt(currentSql, question, services, coordinatesStr);
1000
+ if (!prompt) {
1001
+ return undefined;
1002
+ }
1003
+ try {
1004
+ const raw = await plugin.callLLM(prompt, config);
1005
+ const trimmed = raw
1006
+ .trim()
1007
+ .replace(/^```\w*\n?/, '')
1008
+ .replace(/\n?```$/, '')
1009
+ .replace(/;\s*$/, '')
1010
+ .trim();
1011
+ if (trimmed.length === 0 ||
1012
+ !trimmed.toLowerCase().startsWith('select') ||
1013
+ trimmed === currentSql) {
1014
+ return undefined;
1015
+ }
1016
+ addThinkingStep(setMessages, 'Retrying with corrected filters...');
1017
+ updateLastAssistant(setMessages, () => ({ sql: trimmed }));
1018
+ const retryResult = await plugin.executeLakehouseSql(trimmed, dataProductCoordinates, config);
1019
+ if (retryResult.rows.length > 0) {
1020
+ const sqlGenTime = elapsedSeconds(Date.now(), 2);
1021
+ completeThinkingSteps(setMessages);
1022
+ updateLastAssistant(setMessages, () => ({
1023
+ sql: trimmed,
1024
+ sqlGenTime,
1025
+ isExecuting: false,
1026
+ }));
1027
+ return { sql: trimmed, result: retryResult };
1028
+ }
1029
+ }
1030
+ catch {
1031
+ /* empty */
1032
+ }
1033
+ return undefined;
1034
+ }
1035
+ offerOrchestratorFallback(question, setMessages, startTime) {
1036
+ this.pendingFallbackQuestion = question;
1037
+ completeThinkingSteps(setMessages);
1038
+ updateLastAssistant(setMessages, () => ({
1039
+ fallbackAction: {
1040
+ label: 'Ask Legend AI Orchestrator to generate Pure query',
1041
+ actionId: LEGEND_AI_ORCHESTRATOR_FALLBACK_ACTION_ID,
1042
+ },
1043
+ isProcessing: false,
1044
+ thinkingDuration: elapsedSeconds(startTime),
1045
+ }));
1046
+ }
1047
+ *runOrchestratorFallback(messageId) {
1048
+ const question = this.pendingFallbackQuestion;
1049
+ const plugin = this.plugin;
1050
+ const coordinates = this.selectedProductCoordinates;
1051
+ const metadata = this.selectedProductMetadata;
1052
+ if (!question || !plugin || !coordinates || !metadata) {
1053
+ return;
1054
+ }
1055
+ this.isSending = true;
1056
+ const setMessages = this.createMessageSetter();
1057
+ setMessages((prev) => prev.map((m) => m.id === messageId && m.role === LegendAIMessageRole.ASSISTANT
1058
+ ? {
1059
+ ...m,
1060
+ fallbackAction: null,
1061
+ error: null,
1062
+ isProcessing: true,
1063
+ }
1064
+ : m));
1065
+ try {
1066
+ this.stage = MarketplaceAIChatStage.QUERYING;
1067
+ const history = this.buildConversationHistory();
1068
+ const context = {
1069
+ config: this.config,
1070
+ plugin,
1071
+ history,
1072
+ setMessages,
1073
+ };
1074
+ addThinkingStep(setMessages, 'Switching to Legend AI Orchestrator...');
1075
+ if (this.lastEntityCandidates.length > 0) {
1076
+ const numbered = this.lastEntityCandidates
1077
+ .map((c, i) => `${i + 1}. ${c.modelPath}`)
1078
+ .join(' ');
1079
+ addThinkingStep(setMessages, `Found potential root entity classes: ${numbered}`);
1080
+ const defaultEntity = this.lastResolvedEntities?.rootEntity ??
1081
+ this.lastEntityCandidates[0]?.modelPath;
1082
+ if (defaultEntity) {
1083
+ addThinkingStep(setMessages, `Picking ${defaultEntity} as root entity to generate Pure query`);
1084
+ }
1085
+ }
1086
+ if (!this.pureExecutionContext) {
1087
+ yield this.resolveExecutionContext(setMessages);
1088
+ }
1089
+ const contextPromise = this.buildContextPromise(question, metadata, setMessages);
1090
+ yield Promise.all([
1091
+ processQuestionViaOrchestrator(question, coordinates, metadata, context, this.pureExecutionContext, this.lastResolvedEntities),
1092
+ contextPromise,
1093
+ ]);
1094
+ this.stage = MarketplaceAIChatStage.RESULTS;
1095
+ this.pendingFallbackQuestion = undefined;
1096
+ }
1097
+ catch (error) {
1098
+ assertErrorThrown(error);
1099
+ finishWithThinkingError(setMessages, error.message, Date.now(), classifyError(error));
1100
+ }
1101
+ finally {
1102
+ this.isSending = false;
1103
+ }
1104
+ }
1105
+ }
1106
+ //# sourceMappingURL=LegendMarketplaceAIChatStore.js.map