@memberjunction/server 2.111.0 → 2.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/dist/agents/skip-agent.d.ts +4 -4
  2. package/dist/agents/skip-agent.d.ts.map +1 -1
  3. package/dist/agents/skip-agent.js +808 -951
  4. package/dist/agents/skip-agent.js.map +1 -1
  5. package/dist/agents/skip-sdk.d.ts +1 -1
  6. package/dist/agents/skip-sdk.d.ts.map +1 -1
  7. package/dist/agents/skip-sdk.js +53 -43
  8. package/dist/agents/skip-sdk.js.map +1 -1
  9. package/dist/apolloServer/index.js +1 -1
  10. package/dist/auth/AuthProviderFactory.d.ts +1 -1
  11. package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
  12. package/dist/auth/AuthProviderFactory.js +1 -3
  13. package/dist/auth/AuthProviderFactory.js.map +1 -1
  14. package/dist/auth/BaseAuthProvider.d.ts +1 -1
  15. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  16. package/dist/auth/BaseAuthProvider.js +3 -2
  17. package/dist/auth/BaseAuthProvider.js.map +1 -1
  18. package/dist/auth/IAuthProvider.d.ts +1 -1
  19. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  20. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  21. package/dist/auth/exampleNewUserSubClass.js +1 -1
  22. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  23. package/dist/auth/index.d.ts +1 -1
  24. package/dist/auth/index.d.ts.map +1 -1
  25. package/dist/auth/index.js +6 -6
  26. package/dist/auth/index.js.map +1 -1
  27. package/dist/auth/initializeProviders.js +1 -1
  28. package/dist/auth/initializeProviders.js.map +1 -1
  29. package/dist/auth/newUsers.d.ts +1 -1
  30. package/dist/auth/newUsers.d.ts.map +1 -1
  31. package/dist/auth/newUsers.js +7 -7
  32. package/dist/auth/newUsers.js.map +1 -1
  33. package/dist/auth/providers/Auth0Provider.d.ts +1 -1
  34. package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
  35. package/dist/auth/providers/Auth0Provider.js +1 -1
  36. package/dist/auth/providers/Auth0Provider.js.map +1 -1
  37. package/dist/auth/providers/CognitoProvider.d.ts +1 -1
  38. package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
  39. package/dist/auth/providers/CognitoProvider.js +3 -6
  40. package/dist/auth/providers/CognitoProvider.js.map +1 -1
  41. package/dist/auth/providers/GoogleProvider.d.ts +1 -1
  42. package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
  43. package/dist/auth/providers/GoogleProvider.js +1 -1
  44. package/dist/auth/providers/GoogleProvider.js.map +1 -1
  45. package/dist/auth/providers/MSALProvider.d.ts +1 -1
  46. package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
  47. package/dist/auth/providers/MSALProvider.js +1 -1
  48. package/dist/auth/providers/MSALProvider.js.map +1 -1
  49. package/dist/auth/providers/OktaProvider.d.ts +1 -1
  50. package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
  51. package/dist/auth/providers/OktaProvider.js +1 -1
  52. package/dist/auth/providers/OktaProvider.js.map +1 -1
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +22 -10
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts +1 -1
  57. package/dist/context.d.ts.map +1 -1
  58. package/dist/context.js +9 -7
  59. package/dist/context.js.map +1 -1
  60. package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
  61. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  62. package/dist/entitySubclasses/entityPermissions.server.js +1 -1
  63. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  64. package/dist/generated/generated.d.ts +648 -648
  65. package/dist/generated/generated.d.ts.map +1 -1
  66. package/dist/generated/generated.js +2986 -1133
  67. package/dist/generated/generated.js.map +1 -1
  68. package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
  69. package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
  70. package/dist/generic/KeyInputOutputTypes.js +1 -1
  71. package/dist/generic/KeyInputOutputTypes.js.map +1 -1
  72. package/dist/generic/ResolverBase.d.ts +1 -1
  73. package/dist/generic/ResolverBase.d.ts.map +1 -1
  74. package/dist/generic/ResolverBase.js +15 -10
  75. package/dist/generic/ResolverBase.js.map +1 -1
  76. package/dist/generic/RunViewResolver.d.ts +1 -1
  77. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  78. package/dist/generic/RunViewResolver.js +15 -15
  79. package/dist/generic/RunViewResolver.js.map +1 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +18 -9
  82. package/dist/index.js.map +1 -1
  83. package/dist/resolvers/ActionResolver.d.ts +2 -2
  84. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  85. package/dist/resolvers/ActionResolver.js +28 -30
  86. package/dist/resolvers/ActionResolver.js.map +1 -1
  87. package/dist/resolvers/AskSkipResolver.d.ts +2 -2
  88. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  89. package/dist/resolvers/AskSkipResolver.js +60 -50
  90. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  91. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  92. package/dist/resolvers/ComponentRegistryResolver.js +36 -38
  93. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  94. package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
  95. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  96. package/dist/resolvers/CreateQueryResolver.js +43 -40
  97. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  98. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  99. package/dist/resolvers/DatasetResolver.js +1 -1
  100. package/dist/resolvers/DatasetResolver.js.map +1 -1
  101. package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
  102. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  103. package/dist/resolvers/EntityRecordNameResolver.js +1 -1
  104. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  105. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  106. package/dist/resolvers/EntityResolver.js +1 -1
  107. package/dist/resolvers/EntityResolver.js.map +1 -1
  108. package/dist/resolvers/FileCategoryResolver.js +1 -1
  109. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  110. package/dist/resolvers/FileResolver.js +1 -1
  111. package/dist/resolvers/FileResolver.js.map +1 -1
  112. package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
  113. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  114. package/dist/resolvers/GetDataContextDataResolver.js +5 -5
  115. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  116. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  117. package/dist/resolvers/GetDataResolver.js +8 -6
  118. package/dist/resolvers/GetDataResolver.js.map +1 -1
  119. package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
  120. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  121. package/dist/resolvers/MergeRecordsResolver.js +3 -3
  122. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  123. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
  124. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  125. package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
  126. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  127. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  128. package/dist/resolvers/QueryResolver.js +11 -11
  129. package/dist/resolvers/QueryResolver.js.map +1 -1
  130. package/dist/resolvers/ReportResolver.js +1 -1
  131. package/dist/resolvers/ReportResolver.js.map +1 -1
  132. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  133. package/dist/resolvers/RunAIAgentResolver.js +27 -28
  134. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  135. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  136. package/dist/resolvers/RunAIPromptResolver.js +31 -31
  137. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  138. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  139. package/dist/resolvers/RunTemplateResolver.js +9 -9
  140. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  141. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  142. package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
  143. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  144. package/dist/resolvers/SyncDataResolver.d.ts +1 -1
  145. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  146. package/dist/resolvers/SyncDataResolver.js +15 -14
  147. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  148. package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
  149. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  150. package/dist/resolvers/SyncRolesUsersResolver.js +48 -44
  151. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  152. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  153. package/dist/resolvers/TaskResolver.js +7 -7
  154. package/dist/resolvers/TaskResolver.js.map +1 -1
  155. package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
  156. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  157. package/dist/resolvers/TransactionGroupResolver.js +12 -12
  158. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  159. package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
  160. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  161. package/dist/resolvers/UserFavoriteResolver.js +1 -1
  162. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  163. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  164. package/dist/resolvers/UserViewResolver.js.map +1 -1
  165. package/dist/rest/EntityCRUDHandler.d.ts +1 -1
  166. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  167. package/dist/rest/EntityCRUDHandler.js +14 -16
  168. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  169. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  170. package/dist/rest/RESTEndpointHandler.js +23 -25
  171. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  172. package/dist/rest/ViewOperationsHandler.d.ts +1 -1
  173. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  174. package/dist/rest/ViewOperationsHandler.js +17 -21
  175. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  176. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
  177. package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
  178. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  179. package/dist/services/ScheduledJobsService.js +4 -6
  180. package/dist/services/ScheduledJobsService.js.map +1 -1
  181. package/dist/services/TaskOrchestrator.d.ts +1 -1
  182. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  183. package/dist/services/TaskOrchestrator.js +30 -30
  184. package/dist/services/TaskOrchestrator.js.map +1 -1
  185. package/dist/types.d.ts +3 -3
  186. package/dist/types.d.ts.map +1 -1
  187. package/dist/types.js +0 -1
  188. package/dist/types.js.map +1 -1
  189. package/dist/util.d.ts +1 -1
  190. package/dist/util.d.ts.map +1 -1
  191. package/dist/util.js +2 -2
  192. package/dist/util.js.map +1 -1
  193. package/package.json +36 -37
  194. package/src/agents/skip-agent.ts +1067 -1200
  195. package/src/agents/skip-sdk.ts +877 -851
  196. package/src/apolloServer/index.ts +2 -2
  197. package/src/auth/AuthProviderFactory.ts +8 -14
  198. package/src/auth/BaseAuthProvider.ts +5 -4
  199. package/src/auth/IAuthProvider.ts +2 -2
  200. package/src/auth/exampleNewUserSubClass.ts +9 -2
  201. package/src/auth/index.ts +31 -26
  202. package/src/auth/initializeProviders.ts +3 -3
  203. package/src/auth/newUsers.ts +166 -134
  204. package/src/auth/providers/Auth0Provider.ts +5 -5
  205. package/src/auth/providers/CognitoProvider.ts +7 -10
  206. package/src/auth/providers/GoogleProvider.ts +4 -5
  207. package/src/auth/providers/MSALProvider.ts +5 -5
  208. package/src/auth/providers/OktaProvider.ts +6 -7
  209. package/src/config.ts +63 -54
  210. package/src/context.ts +42 -30
  211. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  212. package/src/generated/generated.ts +48130 -39930
  213. package/src/generic/KeyInputOutputTypes.ts +3 -6
  214. package/src/generic/ResolverBase.ts +119 -78
  215. package/src/generic/RunViewResolver.ts +27 -23
  216. package/src/index.ts +66 -42
  217. package/src/resolvers/ActionResolver.ts +46 -57
  218. package/src/resolvers/AskSkipResolver.ts +607 -533
  219. package/src/resolvers/ComponentRegistryResolver.ts +547 -562
  220. package/src/resolvers/CreateQueryResolver.ts +683 -655
  221. package/src/resolvers/DatasetResolver.ts +5 -6
  222. package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
  223. package/src/resolvers/EntityRecordNameResolver.ts +9 -5
  224. package/src/resolvers/EntityResolver.ts +9 -7
  225. package/src/resolvers/FileCategoryResolver.ts +2 -2
  226. package/src/resolvers/FileResolver.ts +4 -4
  227. package/src/resolvers/GetDataContextDataResolver.ts +106 -118
  228. package/src/resolvers/GetDataResolver.ts +194 -205
  229. package/src/resolvers/MergeRecordsResolver.ts +5 -5
  230. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  231. package/src/resolvers/QueryResolver.ts +95 -78
  232. package/src/resolvers/ReportResolver.ts +2 -2
  233. package/src/resolvers/RunAIAgentResolver.ts +818 -828
  234. package/src/resolvers/RunAIPromptResolver.ts +693 -709
  235. package/src/resolvers/RunTemplateResolver.ts +105 -103
  236. package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
  237. package/src/resolvers/SyncDataResolver.ts +386 -352
  238. package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
  239. package/src/resolvers/TaskResolver.ts +110 -115
  240. package/src/resolvers/TransactionGroupResolver.ts +143 -138
  241. package/src/resolvers/UserFavoriteResolver.ts +17 -8
  242. package/src/resolvers/UserViewResolver.ts +17 -12
  243. package/src/rest/EntityCRUDHandler.ts +291 -268
  244. package/src/rest/RESTEndpointHandler.ts +782 -776
  245. package/src/rest/ViewOperationsHandler.ts +191 -195
  246. package/src/scheduler/LearningCycleScheduler.ts +8 -52
  247. package/src/services/ScheduledJobsService.ts +129 -132
  248. package/src/services/TaskOrchestrator.ts +792 -776
  249. package/src/types.ts +15 -9
  250. package/src/util.ts +112 -109
@@ -7,26 +7,35 @@
7
7
  */
8
8
 
9
9
  import {
10
- SkipAPIRequest,
11
- SkipAPIResponse,
12
- SkipMessage,
13
- SkipAPIAnalysisCompleteResponse,
14
- SkipAPIClarifyingQuestionResponse,
15
- SkipRequestPhase,
16
- SkipAPIRequestAPIKey,
17
- SkipQueryInfo,
18
- SkipEntityInfo,
19
- SkipEntityFieldInfo,
20
- SkipEntityFieldValueInfo,
21
- SkipEntityRelationshipInfo,
22
- SkipAPIAgentNote,
23
- SkipAPIAgentNoteType,
24
- SkipAPIArtifact,
25
- SkipAPIArtifactVersion,
26
- SkipAPIArtifactType
10
+ SkipAPIRequest,
11
+ SkipAPIResponse,
12
+ SkipMessage,
13
+ SkipAPIAnalysisCompleteResponse,
14
+ SkipAPIClarifyingQuestionResponse,
15
+ SkipRequestPhase,
16
+ SkipAPIRequestAPIKey,
17
+ SkipQueryInfo,
18
+ SkipEntityInfo,
19
+ SkipEntityFieldInfo,
20
+ SkipEntityFieldValueInfo,
21
+ SkipEntityRelationshipInfo,
22
+ SkipAPIAgentNote,
23
+ SkipAPIAgentNoteType,
24
+ SkipAPIArtifact,
25
+ SkipAPIArtifactVersion,
26
+ SkipAPIArtifactType,
27
27
  } from '@memberjunction/skip-types';
28
28
  import { DataContext } from '@memberjunction/data-context';
29
- import { UserInfo, LogStatus, LogError, Metadata, RunQuery, EntityInfo, EntityFieldInfo, EntityRelationshipInfo } from '@memberjunction/core';
29
+ import {
30
+ UserInfo,
31
+ LogStatus,
32
+ LogError,
33
+ Metadata,
34
+ RunQuery,
35
+ EntityInfo,
36
+ EntityFieldInfo,
37
+ EntityRelationshipInfo,
38
+ } from '@memberjunction/global';
30
39
  import { sendPostRequest } from '../util.js';
31
40
  import { configInfo, baseUrl, publicUrl, graphqlPort, graphqlRootPath, apiKey as callbackAPIKey } from '../config.js';
32
41
  import { GetAIAPIKey } from '@memberjunction/ai';
@@ -41,125 +50,125 @@ import { take } from 'rxjs/operators';
41
50
  * Configuration options for Skip SDK
42
51
  */
43
52
  export interface SkipSDKConfig {
44
- /**
45
- * Skip API base URL (e.g., 'https://skip.memberjunction.com')
46
- */
47
- apiUrl?: string;
48
-
49
- /**
50
- * Skip API key for authentication
51
- */
52
- apiKey?: string;
53
-
54
- /**
55
- * Organization ID
56
- */
57
- organizationId?: string;
58
-
59
- /**
60
- * Optional organization context information
61
- */
62
- organizationInfo?: string;
53
+ /**
54
+ * Skip API base URL (e.g., 'https://skip.memberjunction.com')
55
+ */
56
+ apiUrl?: string;
57
+
58
+ /**
59
+ * Skip API key for authentication
60
+ */
61
+ apiKey?: string;
62
+
63
+ /**
64
+ * Organization ID
65
+ */
66
+ organizationId?: string;
67
+
68
+ /**
69
+ * Optional organization context information
70
+ */
71
+ organizationInfo?: string;
63
72
  }
64
73
 
65
74
  /**
66
75
  * Options for making a Skip API call
67
76
  */
68
77
  export interface SkipCallOptions {
69
- /**
70
- * Conversation messages (user/assistant)
71
- */
72
- messages: SkipMessage[];
73
-
74
- /**
75
- * Conversation ID for tracking
76
- */
77
- conversationId: string;
78
-
79
- /**
80
- * Data context to provide to Skip
81
- */
82
- dataContext?: DataContext;
83
-
84
- /**
85
- * Request phase (initial_request, clarify_question_response, etc.)
86
- */
87
- requestPhase?: SkipRequestPhase;
88
-
89
- /**
90
- * Context user for permissions and metadata
91
- */
92
- contextUser: UserInfo;
93
-
94
- /**
95
- * Database connection for metadata queries
96
- */
97
- dataSource: mssql.ConnectionPool;
98
-
99
- /**
100
- * Include entity metadata in request
101
- */
102
- includeEntities?: boolean;
103
-
104
- /**
105
- * Include saved queries in request
106
- */
107
- includeQueries?: boolean;
108
-
109
- /**
110
- * Include agent notes in request
111
- */
112
- includeNotes?: boolean;
113
-
114
- /**
115
- * Include agent requests in request
116
- */
117
- includeRequests?: boolean;
118
-
119
- /**
120
- * Force refresh of entity metadata cache
121
- */
122
- forceEntityRefresh?: boolean;
123
-
124
- /**
125
- * Include callback API key and access token for Skip to call back to MJ
126
- */
127
- includeCallbackAuth?: boolean;
128
-
129
- /**
130
- * Callback for streaming status updates during execution
131
- */
132
- onStatusUpdate?: (message: string, responsePhase?: string) => void;
78
+ /**
79
+ * Conversation messages (user/assistant)
80
+ */
81
+ messages: SkipMessage[];
82
+
83
+ /**
84
+ * Conversation ID for tracking
85
+ */
86
+ conversationId: string;
87
+
88
+ /**
89
+ * Data context to provide to Skip
90
+ */
91
+ dataContext?: DataContext;
92
+
93
+ /**
94
+ * Request phase (initial_request, clarify_question_response, etc.)
95
+ */
96
+ requestPhase?: SkipRequestPhase;
97
+
98
+ /**
99
+ * Context user for permissions and metadata
100
+ */
101
+ contextUser: UserInfo;
102
+
103
+ /**
104
+ * Database connection for metadata queries
105
+ */
106
+ dataSource: mssql.ConnectionPool;
107
+
108
+ /**
109
+ * Include entity metadata in request
110
+ */
111
+ includeEntities?: boolean;
112
+
113
+ /**
114
+ * Include saved queries in request
115
+ */
116
+ includeQueries?: boolean;
117
+
118
+ /**
119
+ * Include agent notes in request
120
+ */
121
+ includeNotes?: boolean;
122
+
123
+ /**
124
+ * Include agent requests in request
125
+ */
126
+ includeRequests?: boolean;
127
+
128
+ /**
129
+ * Force refresh of entity metadata cache
130
+ */
131
+ forceEntityRefresh?: boolean;
132
+
133
+ /**
134
+ * Include callback API key and access token for Skip to call back to MJ
135
+ */
136
+ includeCallbackAuth?: boolean;
137
+
138
+ /**
139
+ * Callback for streaming status updates during execution
140
+ */
141
+ onStatusUpdate?: (message: string, responsePhase?: string) => void;
133
142
  }
134
143
 
135
144
  /**
136
145
  * Result from a Skip API call
137
146
  */
138
147
  export interface SkipCallResult {
139
- /**
140
- * Whether the call was successful
141
- */
142
- success: boolean;
143
-
144
- /**
145
- * The final Skip API response
146
- */
147
- response?: SkipAPIResponse;
148
-
149
- /**
150
- * Response phase (analysis_complete, clarifying_question, status_update)
151
- */
152
- responsePhase?: string;
153
-
154
- /**
155
- * Error message if failed
156
- */
157
- error?: string;
158
-
159
- /**
160
- * All streaming responses received (including intermediate status updates)
161
- */
162
- allResponses?: any[];
148
+ /**
149
+ * Whether the call was successful
150
+ */
151
+ success: boolean;
152
+
153
+ /**
154
+ * The final Skip API response
155
+ */
156
+ response?: SkipAPIResponse;
157
+
158
+ /**
159
+ * Response phase (analysis_complete, clarifying_question, status_update)
160
+ */
161
+ responsePhase?: string;
162
+
163
+ /**
164
+ * Error message if failed
165
+ */
166
+ error?: string;
167
+
168
+ /**
169
+ * All streaming responses received (including intermediate status updates)
170
+ */
171
+ allResponses?: any[];
163
172
  }
164
173
 
165
174
  /**
@@ -167,760 +176,777 @@ export interface SkipCallResult {
167
176
  * Provides a clean interface for calling the Skip SaaS API
168
177
  */
169
178
  export class SkipSDK {
170
- private config: SkipSDKConfig;
171
-
172
- // Static cache for Skip entities (shared across all instances)
173
- private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<SkipEntityInfo[]> | null>(null);
174
- private static __lastRefreshTime: number = 0;
175
-
176
- constructor(config?: SkipSDKConfig) {
177
- // Use provided config or fall back to MJ server config
178
- this.config = {
179
- apiUrl: config?.apiUrl || configInfo.askSkip?.chatURL,
180
- apiKey: config?.apiKey || configInfo.askSkip?.apiKey,
181
- organizationId: config?.organizationId || configInfo.askSkip?.orgID,
182
- organizationInfo: config?.organizationInfo || configInfo.askSkip?.organizationInfo
183
- };
184
- }
185
-
186
- /**
187
- * Call the Skip chat API
188
- */
189
- async chat(options: SkipCallOptions): Promise<SkipCallResult> {
190
- LogStatus(`[SkipSDK] Sending request to Skip API: ${this.config.apiUrl}`);
191
-
192
- try {
193
- // Build the Skip API request
194
- const skipRequest = await this.buildSkipRequest(options);
195
-
196
- // Call Skip API with streaming support
197
- const responses = await sendPostRequest(
198
- this.config.apiUrl,
199
- skipRequest,
200
- true, // useCompression
201
- this.buildHeaders(),
202
- (streamMessage: any) => {
203
- // Handle streaming status updates
204
- if (streamMessage.type === 'status_update' && options.onStatusUpdate) {
205
- const statusContent = streamMessage.value?.messages?.[0]?.content;
206
- const responsePhase = streamMessage.value?.responsePhase;
207
- if (statusContent) {
208
- options.onStatusUpdate(statusContent, responsePhase);
209
- }
210
- }
211
- }
212
- );
213
-
214
- // The last response is the final one
215
- if (responses && responses.length > 0) {
216
- const finalResponse = responses[responses.length - 1].value as SkipAPIResponse;
217
-
218
- return {
219
- success: true,
220
- response: finalResponse,
221
- responsePhase: finalResponse.responsePhase,
222
- allResponses: responses
223
- };
224
- } else {
225
- return {
226
- success: false,
227
- error: 'No response received from Skip API'
228
- };
179
+ private config: SkipSDKConfig;
180
+
181
+ // Static cache for Skip entities (shared across all instances)
182
+ private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<
183
+ SkipEntityInfo[]
184
+ > | null>(null);
185
+ private static __lastRefreshTime: number = 0;
186
+
187
+ constructor(config?: SkipSDKConfig) {
188
+ // Use provided config or fall back to MJ server config
189
+ this.config = {
190
+ apiUrl: config?.apiUrl || configInfo.askSkip?.chatURL,
191
+ apiKey: config?.apiKey || configInfo.askSkip?.apiKey,
192
+ organizationId: config?.organizationId || configInfo.askSkip?.orgID,
193
+ organizationInfo: config?.organizationInfo || configInfo.askSkip?.organizationInfo,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Call the Skip chat API
199
+ */
200
+ async chat(options: SkipCallOptions): Promise<SkipCallResult> {
201
+ LogStatus(`[SkipSDK] Sending request to Skip API: ${this.config.apiUrl}`);
202
+
203
+ try {
204
+ // Build the Skip API request
205
+ const skipRequest = await this.buildSkipRequest(options);
206
+
207
+ // Call Skip API with streaming support
208
+ const responses = await sendPostRequest(
209
+ this.config.apiUrl,
210
+ skipRequest,
211
+ true, // useCompression
212
+ this.buildHeaders(),
213
+ (streamMessage: any) => {
214
+ // Handle streaming status updates
215
+ if (streamMessage.type === 'status_update' && options.onStatusUpdate) {
216
+ const statusContent = streamMessage.value?.messages?.[0]?.content;
217
+ const responsePhase = streamMessage.value?.responsePhase;
218
+ if (statusContent) {
219
+ options.onStatusUpdate(statusContent, responsePhase);
229
220
  }
230
-
231
- } catch (error) {
232
- LogError(`[SkipSDK] Error calling Skip API: ${error}`);
233
- return {
234
- success: false,
235
- error: String(error)
236
- };
221
+ }
237
222
  }
238
- }
223
+ );
239
224
 
240
- /**
241
- * Build the Skip API request object
242
- */
243
- private async buildSkipRequest(options: SkipCallOptions): Promise<SkipAPIRequest> {
244
- const {
245
- messages,
246
- conversationId,
247
- dataContext,
248
- requestPhase = 'initial_request',
249
- contextUser,
250
- dataSource,
251
- includeEntities = true,
252
- includeQueries = true,
253
- includeNotes = true,
254
- includeRequests = false,
255
- forceEntityRefresh = false,
256
- includeCallbackAuth = true
257
- } = options;
258
-
259
- // Build base request with metadata
260
- const baseRequest = await this.buildBaseRequest(
261
- contextUser,
262
- dataSource,
263
- includeEntities,
264
- includeQueries,
265
- includeNotes,
266
- includeRequests,
267
- forceEntityRefresh,
268
- includeCallbackAuth,
269
- { conversationId, requestPhase }
270
- );
271
-
272
- // Build artifacts for this conversation
273
- const artifacts = await this.buildArtifacts(contextUser, dataSource, conversationId);
274
-
275
- // Process messages: filter delegation messages and enrich with metadata
276
- const processedMessages = this.processMessages(messages);
277
-
278
- // Construct the full Skip API request
279
- const request: SkipAPIRequest = {
280
- messages: processedMessages,
281
- conversationID: conversationId,
282
- dataContext: dataContext ? CopyScalarsAndArrays(dataContext) as DataContext : undefined,
283
- requestPhase,
284
- artifacts,
285
- entities: baseRequest.entities || [],
286
- queries: baseRequest.queries || [],
287
- notes: baseRequest.notes,
288
- noteTypes: baseRequest.noteTypes,
289
- userEmail: baseRequest.userEmail,
290
- organizationID: baseRequest.organizationID,
291
- organizationInfo: baseRequest.organizationInfo,
292
- apiKeys: baseRequest.apiKeys,
293
- callingServerURL: baseRequest.callingServerURL,
294
- callingServerAPIKey: baseRequest.callingServerAPIKey,
295
- callingServerAccessToken: baseRequest.callingServerAccessToken
296
- };
297
-
298
- return request;
299
- }
300
-
301
- /**
302
- * Build base request with metadata, API keys, and callback auth
303
- */
304
- private async buildBaseRequest(
305
- contextUser: UserInfo,
306
- dataSource: mssql.ConnectionPool,
307
- includeEntities: boolean,
308
- includeQueries: boolean,
309
- includeNotes: boolean,
310
- includeRequests: boolean,
311
- forceEntityRefresh: boolean,
312
- includeCallbackAuth: boolean,
313
- additionalTokenInfo: any = {}
314
- ): Promise<Partial<SkipAPIRequest>> {
315
- const entities = includeEntities ? await this.buildEntities(dataSource, forceEntityRefresh) : [];
316
- const queries = includeQueries ? this.buildQueries() : [];
317
- const { notes, noteTypes } = includeNotes ? await this.buildAgentNotes(contextUser) : { notes: [], noteTypes: [] };
318
- // Note: requests would be built here if includeRequests is true
319
-
320
- // Setup access token for Skip callbacks if needed
321
- let accessToken: GetDataAccessToken | undefined;
322
- if (includeCallbackAuth) {
323
- const tokenInfo = {
324
- type: 'skip_api_request',
325
- userEmail: contextUser.Email,
326
- userName: contextUser.Name,
327
- userID: contextUser.ID,
328
- ...additionalTokenInfo
329
- };
330
-
331
- accessToken = registerAccessToken(
332
- undefined,
333
- 1000 * 60 * 10, // 10 minutes
334
- tokenInfo
335
- );
336
- }
225
+ // The last response is the final one
226
+ if (responses && responses.length > 0) {
227
+ const finalResponse = responses[responses.length - 1].value as SkipAPIResponse;
337
228
 
338
229
  return {
339
- entities,
340
- queries,
341
- notes,
342
- noteTypes,
343
- userEmail: contextUser.Email,
344
- organizationID: this.config.organizationId,
345
- organizationInfo: this.config.organizationInfo,
346
- apiKeys: this.buildAPIKeys(),
347
- callingServerURL: accessToken ? (publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}`) : undefined,
348
- callingServerAPIKey: accessToken ? callbackAPIKey : undefined,
349
- callingServerAccessToken: accessToken ? accessToken.Token : undefined
230
+ success: true,
231
+ response: finalResponse,
232
+ responsePhase: finalResponse.responsePhase,
233
+ allResponses: responses,
350
234
  };
235
+ } else {
236
+ return {
237
+ success: false,
238
+ error: 'No response received from Skip API',
239
+ };
240
+ }
241
+ } catch (error) {
242
+ LogError(`[SkipSDK] Error calling Skip API: ${error}`);
243
+ return {
244
+ success: false,
245
+ error: String(error),
246
+ };
351
247
  }
352
-
353
- /**
354
- * Build entity metadata for Skip
355
- * Copied from AskSkipResolver.BuildSkipEntities - uses cached metadata with refresh logic
356
- */
357
- private async buildEntities(dataSource: mssql.ConnectionPool, forceRefresh: boolean, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
358
- try {
359
- const now = Date.now();
360
- const cacheExpired = (now - SkipSDK.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
361
-
362
- // If force refresh is requested OR cache expired OR cache is empty, refresh
363
- if (forceRefresh || cacheExpired || SkipSDK.__skipEntitiesCache$.value === null) {
364
- LogStatus(`[SkipSDK] Refreshing Skip entities cache (force: ${forceRefresh}, expired: ${cacheExpired})`);
365
- const newData = this.refreshSkipEntities(dataSource);
366
- SkipSDK.__skipEntitiesCache$.next(newData);
367
- }
368
-
369
- return SkipSDK.__skipEntitiesCache$.pipe(take(1)).toPromise();
370
- }
371
- catch (e) {
372
- LogError(`[SkipSDK] buildEntities error: ${e}`);
373
- return [];
374
- }
375
- }
376
-
377
- /**
378
- * Build saved queries for Skip
379
- */
380
- private buildQueries(status: "Pending" | "In-Review" | "Approved" | "Rejected" | "Obsolete" = 'Approved'): SkipQueryInfo[] {
381
- const md = new Metadata();
382
- const approvedQueries = md.Queries.filter((q) => q.Status === status);
383
-
384
- return approvedQueries.map((q) => ({
385
- id: q.ID,
386
- name: q.Name,
387
- description: q.Description,
388
- category: q.Category,
389
- categoryPath: this.buildQueryCategoryPath(md, q.CategoryID),
390
- sql: q.SQL,
391
- originalSQL: q.OriginalSQL,
392
- feedback: q.Feedback,
393
- status: q.Status,
394
- qualityRank: q.QualityRank,
395
- createdAt: q.__mj_CreatedAt,
396
- updatedAt: q.__mj_UpdatedAt,
397
- categoryID: q.CategoryID,
398
- embeddingVector: q.EmbeddingVector,
399
- embeddingModelID: q.EmbeddingModelID,
400
- embeddingModelName: q.EmbeddingModel,
401
- fields: q.Fields.map((f) => ({
402
- id: f.ID,
403
- queryID: f.QueryID,
404
- sequence: f.Sequence,
405
- name: f.Name,
406
- description: f.Description,
407
- sqlBaseType: f.SQLBaseType,
408
- sqlFullType: f.SQLFullType,
409
- sourceEntityID: f.SourceEntityID,
410
- sourceEntity: f.SourceEntity,
411
- sourceFieldName: f.SourceFieldName,
412
- isComputed: f.IsComputed,
413
- computationDescription: f.ComputationDescription,
414
- isSummary: f.IsSummary,
415
- summaryDescription: f.SummaryDescription,
416
- createdAt: f.__mj_CreatedAt,
417
- updatedAt: f.__mj_UpdatedAt
418
- })),
419
- params: q.Parameters.map((p) => ({
420
- id: p.ID,
421
- queryID: p.QueryID,
422
- name: p.Name,
423
- description: p.Description,
424
- type: p.Type,
425
- isRequired: p.IsRequired,
426
- // LinkedParameterName and LinkedParameterType may not exist on QueryParameterInfo
427
- defaultValue: p.DefaultValue,
428
- createdAt: p.__mj_CreatedAt,
429
- updatedAt: p.__mj_UpdatedAt
430
- }))
431
- }));
432
- }
433
-
434
- /**
435
- * Recursively build category path for a query
436
- */
437
- private buildQueryCategoryPath(md: Metadata, categoryID: string): string {
438
- const cat = md.QueryCategories.find((c) => c.ID === categoryID);
439
- if (!cat) return '';
440
- if (!cat.ParentID) return cat.Name;
441
- const parentPath = this.buildQueryCategoryPath(md, cat.ParentID);
442
- return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
248
+ }
249
+
250
+ /**
251
+ * Build the Skip API request object
252
+ */
253
+ private async buildSkipRequest(options: SkipCallOptions): Promise<SkipAPIRequest> {
254
+ const {
255
+ messages,
256
+ conversationId,
257
+ dataContext,
258
+ requestPhase = 'initial_request',
259
+ contextUser,
260
+ dataSource,
261
+ includeEntities = true,
262
+ includeQueries = true,
263
+ includeNotes = true,
264
+ includeRequests = false,
265
+ forceEntityRefresh = false,
266
+ includeCallbackAuth = true,
267
+ } = options;
268
+
269
+ // Build base request with metadata
270
+ const baseRequest = await this.buildBaseRequest(
271
+ contextUser,
272
+ dataSource,
273
+ includeEntities,
274
+ includeQueries,
275
+ includeNotes,
276
+ includeRequests,
277
+ forceEntityRefresh,
278
+ includeCallbackAuth,
279
+ { conversationId, requestPhase }
280
+ );
281
+
282
+ // Build artifacts for this conversation
283
+ const artifacts = await this.buildArtifacts(contextUser, dataSource, conversationId);
284
+
285
+ // Process messages: filter delegation messages and enrich with metadata
286
+ const processedMessages = this.processMessages(messages);
287
+
288
+ // Construct the full Skip API request
289
+ const request: SkipAPIRequest = {
290
+ messages: processedMessages,
291
+ conversationID: conversationId,
292
+ dataContext: dataContext ? (CopyScalarsAndArrays(dataContext) as DataContext) : undefined,
293
+ requestPhase,
294
+ artifacts,
295
+ entities: baseRequest.entities || [],
296
+ queries: baseRequest.queries || [],
297
+ notes: baseRequest.notes,
298
+ noteTypes: baseRequest.noteTypes,
299
+ userEmail: baseRequest.userEmail,
300
+ organizationID: baseRequest.organizationID,
301
+ organizationInfo: baseRequest.organizationInfo,
302
+ apiKeys: baseRequest.apiKeys,
303
+ callingServerURL: baseRequest.callingServerURL,
304
+ callingServerAPIKey: baseRequest.callingServerAPIKey,
305
+ callingServerAccessToken: baseRequest.callingServerAccessToken,
306
+ };
307
+
308
+ return request;
309
+ }
310
+
311
+ /**
312
+ * Build base request with metadata, API keys, and callback auth
313
+ */
314
+ private async buildBaseRequest(
315
+ contextUser: UserInfo,
316
+ dataSource: mssql.ConnectionPool,
317
+ includeEntities: boolean,
318
+ includeQueries: boolean,
319
+ includeNotes: boolean,
320
+ includeRequests: boolean,
321
+ forceEntityRefresh: boolean,
322
+ includeCallbackAuth: boolean,
323
+ additionalTokenInfo: any = {}
324
+ ): Promise<Partial<SkipAPIRequest>> {
325
+ const entities = includeEntities ? await this.buildEntities(dataSource, forceEntityRefresh) : [];
326
+ const queries = includeQueries ? this.buildQueries() : [];
327
+ const { notes, noteTypes } = includeNotes ? await this.buildAgentNotes(contextUser) : { notes: [], noteTypes: [] };
328
+ // Note: requests would be built here if includeRequests is true
329
+
330
+ // Setup access token for Skip callbacks if needed
331
+ let accessToken: GetDataAccessToken | undefined;
332
+ if (includeCallbackAuth) {
333
+ const tokenInfo = {
334
+ type: 'skip_api_request',
335
+ userEmail: contextUser.Email,
336
+ userName: contextUser.Name,
337
+ userID: contextUser.ID,
338
+ ...additionalTokenInfo,
339
+ };
340
+
341
+ accessToken = registerAccessToken(
342
+ undefined,
343
+ 1000 * 60 * 10, // 10 minutes
344
+ tokenInfo
345
+ );
443
346
  }
444
347
 
445
- /**
446
- * Build agent notes for Skip
447
- */
448
- private async buildAgentNotes(contextUser: UserInfo): Promise<{ notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[] }> {
449
- // TODO: Implement agent notes building
450
- // This would query AIAgentNote entities and convert to SkipAPIAgentNote format
451
- // For now, returning empty arrays
452
- return { notes: [], noteTypes: [] };
348
+ return {
349
+ entities,
350
+ queries,
351
+ notes,
352
+ noteTypes,
353
+ userEmail: contextUser.Email,
354
+ organizationID: this.config.organizationId,
355
+ organizationInfo: this.config.organizationInfo,
356
+ apiKeys: this.buildAPIKeys(),
357
+ callingServerURL: accessToken ? publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}` : undefined,
358
+ callingServerAPIKey: accessToken ? callbackAPIKey : undefined,
359
+ callingServerAccessToken: accessToken ? accessToken.Token : undefined,
360
+ };
361
+ }
362
+
363
+ /**
364
+ * Build entity metadata for Skip
365
+ * Copied from AskSkipResolver.BuildSkipEntities - uses cached metadata with refresh logic
366
+ */
367
+ private async buildEntities(
368
+ dataSource: mssql.ConnectionPool,
369
+ forceRefresh: boolean,
370
+ refreshIntervalMinutes: number = 15
371
+ ): Promise<SkipEntityInfo[]> {
372
+ try {
373
+ const now = Date.now();
374
+ const cacheExpired = now - SkipSDK.__lastRefreshTime > refreshIntervalMinutes * 60 * 1000;
375
+
376
+ // If force refresh is requested OR cache expired OR cache is empty, refresh
377
+ if (forceRefresh || cacheExpired || SkipSDK.__skipEntitiesCache$.value === null) {
378
+ LogStatus(`[SkipSDK] Refreshing Skip entities cache (force: ${forceRefresh}, expired: ${cacheExpired})`);
379
+ const newData = this.refreshSkipEntities(dataSource);
380
+ SkipSDK.__skipEntitiesCache$.next(newData);
381
+ }
382
+
383
+ return SkipSDK.__skipEntitiesCache$.pipe(take(1)).toPromise();
384
+ } catch (e) {
385
+ LogError(`[SkipSDK] buildEntities error: ${e}`);
386
+ return [];
453
387
  }
454
-
455
- /**
456
- * Build artifacts for a conversation using optimized query
457
- * Uses GetConversationArtifactsForAgent query which joins through ConversationDetailArtifact
458
- * to get artifacts that were outputs from Skip agent's conversation details
459
- */
460
- private async buildArtifacts(contextUser: UserInfo, dataSource: mssql.ConnectionPool, conversationId: string): Promise<SkipAPIArtifact[]> {
461
- try {
462
- const rq = new RunQuery();
463
-
464
- // Ensure AIEngine is configured and get Skip agent ID
465
- await AIEngine.Instance.Config(false, contextUser);
466
- const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
467
- const skipAgentId = skipAgent?.ID;
468
-
469
- if (!skipAgentId) {
470
- LogError('[SkipSDK] Skip agent not found in AIEngine');
471
- }
472
-
473
- // Use optimized query that replaces 4 RunView calls with 1 query
474
- // This query includes Configuration field needed for component spec extraction
475
- // Filter by Skip agent ID to only get artifacts created by Skip (not delegation agents)
476
- const result = await rq.RunQuery({
477
- QueryName: 'GetConversationArtifactsForAgent',
478
- CategoryPath: 'MJ/Conversations',
479
- Parameters: {
480
- ConversationID: conversationId,
481
- AgentID: skipAgentId // Filter to only artifacts created by Skip agent
482
- }
483
- }, contextUser);
484
-
485
- if (!result.Success || !result.Results || result.Results.length === 0) {
486
- return [];
487
- }
488
-
489
- // Query returns flat result set: one row per artifact version
490
- // Group by ArtifactID to build SkipAPIArtifact objects with their versions
491
- const artifactMap = new Map<string, {
492
- artifact: any,
493
- artifactType: SkipAPIArtifactType,
494
- versions: SkipAPIArtifactVersion[]
495
- }>();
496
-
497
- // Process each row (represents one version)
498
- for (const row of result.Results) {
499
- const artifactId = row.ArtifactID;
500
-
501
- // Initialize artifact entry if not exists
502
- if (!artifactMap.has(artifactId)) {
503
- // Map database sharingScope values to SkipAPIArtifact expected values
504
- let sharingScope: 'None' | 'SpecificUsers' | 'Everyone' | 'Public' = 'None';
505
- const dbSharingScope = (row.SharingScope || '').toLowerCase();
506
- if (dbSharingScope === 'always' || dbSharingScope === 'everyone') {
507
- sharingScope = 'Everyone';
508
- } else if (dbSharingScope === 'public') {
509
- sharingScope = 'Public';
510
- } else if (dbSharingScope === 'specific users' || dbSharingScope === 'specificusers') {
511
- sharingScope = 'SpecificUsers';
512
- }
513
-
514
- artifactMap.set(artifactId, {
515
- artifact: {
516
- id: artifactId,
517
- conversationId: conversationId,
518
- name: row.ArtifactName,
519
- description: row.ArtifactDescription || '',
520
- sharingScope: sharingScope,
521
- comments: row.ArtifactComments || '',
522
- createdAt: new Date(row.ArtifactCreatedAt),
523
- updatedAt: new Date(row.ArtifactUpdatedAt)
524
- },
525
- artifactType: {
526
- id: row.ArtifactTypeID,
527
- name: row.ArtifactTypeName,
528
- description: row.ArtifactTypeDescription,
529
- contentType: row.ArtifactTypeContentType,
530
- enabled: true,
531
- createdAt: new Date(row.ArtifactTypeCreatedAt),
532
- updatedAt: new Date(row.ArtifactTypeUpdatedAt)
533
- },
534
- versions: []
535
- });
536
- }
537
-
538
- // Add this version to the artifact
539
- const entry = artifactMap.get(artifactId)!;
540
- entry.versions.push({
541
- id: row.VersionID,
542
- artifactId: artifactId,
543
- conversationDetailID: row.ConversationDetailID, // Direct from join table!
544
- version: row.Version,
545
- configuration: row.Configuration || '',
546
- content: row.Content || '',
547
- comments: row.VersionComments || '',
548
- createdAt: new Date(row.VersionCreatedAt),
549
- updatedAt: new Date(row.VersionUpdatedAt)
550
- });
551
- }
552
-
553
- // Convert map to SkipAPIArtifact array
554
- const artifacts: SkipAPIArtifact[] = Array.from(artifactMap.values()).map(entry => ({
555
- ...entry.artifact,
556
- artifactType: entry.artifactType,
557
- versions: entry.versions
558
- }));
559
-
560
- return artifacts;
561
- } catch (error) {
562
- LogError(`Failed to build artifacts for conversation ${conversationId}: ${error}`);
563
- return [];
388
+ }
389
+
390
+ /**
391
+ * Build saved queries for Skip
392
+ */
393
+ private buildQueries(status: 'Pending' | 'In-Review' | 'Approved' | 'Rejected' | 'Obsolete' = 'Approved'): SkipQueryInfo[] {
394
+ const md = new Metadata();
395
+ const approvedQueries = md.Queries.filter((q) => q.Status === status);
396
+
397
+ return approvedQueries.map((q) => ({
398
+ id: q.ID,
399
+ name: q.Name,
400
+ description: q.Description,
401
+ category: q.Category,
402
+ categoryPath: this.buildQueryCategoryPath(md, q.CategoryID),
403
+ sql: q.SQL,
404
+ originalSQL: q.OriginalSQL,
405
+ feedback: q.Feedback,
406
+ status: q.Status,
407
+ qualityRank: q.QualityRank,
408
+ createdAt: q.__mj_CreatedAt,
409
+ updatedAt: q.__mj_UpdatedAt,
410
+ categoryID: q.CategoryID,
411
+ embeddingVector: q.EmbeddingVector,
412
+ embeddingModelID: q.EmbeddingModelID,
413
+ embeddingModelName: q.EmbeddingModel,
414
+ fields: q.Fields.map((f) => ({
415
+ id: f.ID,
416
+ queryID: f.QueryID,
417
+ sequence: f.Sequence,
418
+ name: f.Name,
419
+ description: f.Description,
420
+ sqlBaseType: f.SQLBaseType,
421
+ sqlFullType: f.SQLFullType,
422
+ sourceEntityID: f.SourceEntityID,
423
+ sourceEntity: f.SourceEntity,
424
+ sourceFieldName: f.SourceFieldName,
425
+ isComputed: f.IsComputed,
426
+ computationDescription: f.ComputationDescription,
427
+ isSummary: f.IsSummary,
428
+ summaryDescription: f.SummaryDescription,
429
+ createdAt: f.__mj_CreatedAt,
430
+ updatedAt: f.__mj_UpdatedAt,
431
+ })),
432
+ params: q.Parameters.map((p) => ({
433
+ id: p.ID,
434
+ queryID: p.QueryID,
435
+ name: p.Name,
436
+ description: p.Description,
437
+ type: p.Type,
438
+ isRequired: p.IsRequired,
439
+ // LinkedParameterName and LinkedParameterType may not exist on QueryParameterInfo
440
+ defaultValue: p.DefaultValue,
441
+ createdAt: p.__mj_CreatedAt,
442
+ updatedAt: p.__mj_UpdatedAt,
443
+ })),
444
+ }));
445
+ }
446
+
447
+ /**
448
+ * Recursively build category path for a query
449
+ */
450
+ private buildQueryCategoryPath(md: Metadata, categoryID: string): string {
451
+ const cat = md.QueryCategories.find((c) => c.ID === categoryID);
452
+ if (!cat) return '';
453
+ if (!cat.ParentID) return cat.Name;
454
+ const parentPath = this.buildQueryCategoryPath(md, cat.ParentID);
455
+ return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
456
+ }
457
+
458
+ /**
459
+ * Build agent notes for Skip
460
+ */
461
+ private async buildAgentNotes(contextUser: UserInfo): Promise<{ notes: SkipAPIAgentNote[]; noteTypes: SkipAPIAgentNoteType[] }> {
462
+ // TODO: Implement agent notes building
463
+ // This would query AIAgentNote entities and convert to SkipAPIAgentNote format
464
+ // For now, returning empty arrays
465
+ return { notes: [], noteTypes: [] };
466
+ }
467
+
468
+ /**
469
+ * Build artifacts for a conversation using optimized query
470
+ * Uses GetConversationArtifactsForAgent query which joins through ConversationDetailArtifact
471
+ * to get artifacts that were outputs from Skip agent's conversation details
472
+ */
473
+ private async buildArtifacts(
474
+ contextUser: UserInfo,
475
+ dataSource: mssql.ConnectionPool,
476
+ conversationId: string
477
+ ): Promise<SkipAPIArtifact[]> {
478
+ try {
479
+ const rq = new RunQuery();
480
+
481
+ // Ensure AIEngine is configured and get Skip agent ID
482
+ await AIEngine.Instance.Config(false, contextUser);
483
+ const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
484
+ const skipAgentId = skipAgent?.ID;
485
+
486
+ if (!skipAgentId) {
487
+ LogError('[SkipSDK] Skip agent not found in AIEngine');
488
+ }
489
+
490
+ // Use optimized query that replaces 4 RunView calls with 1 query
491
+ // This query includes Configuration field needed for component spec extraction
492
+ // Filter by Skip agent ID to only get artifacts created by Skip (not delegation agents)
493
+ const result = await rq.RunQuery(
494
+ {
495
+ QueryName: 'GetConversationArtifactsForAgent',
496
+ CategoryPath: 'MJ/Conversations',
497
+ Parameters: {
498
+ ConversationID: conversationId,
499
+ AgentID: skipAgentId, // Filter to only artifacts created by Skip agent
500
+ },
501
+ },
502
+ contextUser
503
+ );
504
+
505
+ if (!result.Success || !result.Results || result.Results.length === 0) {
506
+ return [];
507
+ }
508
+
509
+ // Query returns flat result set: one row per artifact version
510
+ // Group by ArtifactID to build SkipAPIArtifact objects with their versions
511
+ const artifactMap = new Map<
512
+ string,
513
+ {
514
+ artifact: any;
515
+ artifactType: SkipAPIArtifactType;
516
+ versions: SkipAPIArtifactVersion[];
564
517
  }
565
- }
566
-
567
- /**
568
- * Build API keys for AI services
569
- */
570
- private buildAPIKeys(): SkipAPIRequestAPIKey[] {
571
- return [
572
- {
573
- vendorDriverName: 'OpenAILLM',
574
- apiKey: GetAIAPIKey('OpenAILLM')
575
- },
576
- {
577
- vendorDriverName: 'AnthropicLLM',
578
- apiKey: GetAIAPIKey('AnthropicLLM')
579
- },
580
- {
581
- vendorDriverName: 'GeminiLLM',
582
- apiKey: GetAIAPIKey('GeminiLLM')
583
- },
584
- {
585
- vendorDriverName: 'GroqLLM',
586
- apiKey: GetAIAPIKey('GroqLLM')
518
+ >();
519
+
520
+ // Process each row (represents one version)
521
+ for (const row of result.Results) {
522
+ const artifactId = row.ArtifactID;
523
+
524
+ // Initialize artifact entry if not exists
525
+ if (!artifactMap.has(artifactId)) {
526
+ // Map database sharingScope values to SkipAPIArtifact expected values
527
+ let sharingScope: 'None' | 'SpecificUsers' | 'Everyone' | 'Public' = 'None';
528
+ const dbSharingScope = (row.SharingScope || '').toLowerCase();
529
+ if (dbSharingScope === 'always' || dbSharingScope === 'everyone') {
530
+ sharingScope = 'Everyone';
531
+ } else if (dbSharingScope === 'public') {
532
+ sharingScope = 'Public';
533
+ } else if (dbSharingScope === 'specific users' || dbSharingScope === 'specificusers') {
534
+ sharingScope = 'SpecificUsers';
535
+ }
536
+
537
+ artifactMap.set(artifactId, {
538
+ artifact: {
539
+ id: artifactId,
540
+ conversationId: conversationId,
541
+ name: row.ArtifactName,
542
+ description: row.ArtifactDescription || '',
543
+ sharingScope: sharingScope,
544
+ comments: row.ArtifactComments || '',
545
+ createdAt: new Date(row.ArtifactCreatedAt),
546
+ updatedAt: new Date(row.ArtifactUpdatedAt),
587
547
  },
588
- {
589
- vendorDriverName: 'MistralLLM',
590
- apiKey: GetAIAPIKey('MistralLLM')
548
+ artifactType: {
549
+ id: row.ArtifactTypeID,
550
+ name: row.ArtifactTypeName,
551
+ description: row.ArtifactTypeDescription,
552
+ contentType: row.ArtifactTypeContentType,
553
+ enabled: true,
554
+ createdAt: new Date(row.ArtifactTypeCreatedAt),
555
+ updatedAt: new Date(row.ArtifactTypeUpdatedAt),
591
556
  },
592
- {
593
- vendorDriverName: 'CerebrasLLM',
594
- apiKey: GetAIAPIKey('CerebrasLLM')
595
- }
596
- ];
597
- }
557
+ versions: [],
558
+ });
559
+ }
598
560
 
599
- /**
600
- * Build HTTP headers for Skip API requests
601
- */
602
- private buildHeaders(): Record<string, string> {
603
- return {
604
- 'x-api-key': this.config.apiKey || '',
605
- 'Content-Type': 'application/json'
606
- };
561
+ // Add this version to the artifact
562
+ const entry = artifactMap.get(artifactId)!;
563
+ entry.versions.push({
564
+ id: row.VersionID,
565
+ artifactId: artifactId,
566
+ conversationDetailID: row.ConversationDetailID, // Direct from join table!
567
+ version: row.Version,
568
+ configuration: row.Configuration || '',
569
+ content: row.Content || '',
570
+ comments: row.VersionComments || '',
571
+ createdAt: new Date(row.VersionCreatedAt),
572
+ updatedAt: new Date(row.VersionUpdatedAt),
573
+ });
574
+ }
575
+
576
+ // Convert map to SkipAPIArtifact array
577
+ const artifacts: SkipAPIArtifact[] = Array.from(artifactMap.values()).map((entry) => ({
578
+ ...entry.artifact,
579
+ artifactType: entry.artifactType,
580
+ versions: entry.versions,
581
+ }));
582
+
583
+ return artifacts;
584
+ } catch (error) {
585
+ LogError(`Failed to build artifacts for conversation ${conversationId}: ${error}`);
586
+ return [];
607
587
  }
608
-
609
- /**
610
- * Refreshes the Skip entities cache
611
- * Rebuilds the entity information that is provided to Skip
612
- * Copied from AskSkipResolver.refreshSkipEntities
613
- */
614
- private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
615
- try {
616
- const md = new Metadata();
617
- const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
618
- .map((e) => e.trim().toLowerCase());
619
-
620
- // Get the list of entities
621
- const entities = md.Entities.filter((e) => {
622
- if (!configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
623
- skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
624
- const sd = e.ScopeDefault?.trim();
625
- if (sd && sd.length > 0) {
626
- const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
627
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai') || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase());
628
- }
629
- else {
630
- return true; // no scope, so include it
631
- }
632
- }
633
- return false;
634
- });
635
-
636
- // Now we have our list of entities, pack em up
637
- const result = await Promise.all(entities.map((e) => this.packSingleSkipEntityInfo(e, dataSource)));
638
-
639
- SkipSDK.__lastRefreshTime = Date.now(); // Update last refresh time
640
- return result;
641
- }
642
- catch (e) {
643
- LogError(`[SkipSDK] refreshSkipEntities error: ${e}`);
644
- return [];
588
+ }
589
+
590
+ /**
591
+ * Build API keys for AI services
592
+ */
593
+ private buildAPIKeys(): SkipAPIRequestAPIKey[] {
594
+ return [
595
+ {
596
+ vendorDriverName: 'OpenAILLM',
597
+ apiKey: GetAIAPIKey('OpenAILLM'),
598
+ },
599
+ {
600
+ vendorDriverName: 'AnthropicLLM',
601
+ apiKey: GetAIAPIKey('AnthropicLLM'),
602
+ },
603
+ {
604
+ vendorDriverName: 'GeminiLLM',
605
+ apiKey: GetAIAPIKey('GeminiLLM'),
606
+ },
607
+ {
608
+ vendorDriverName: 'GroqLLM',
609
+ apiKey: GetAIAPIKey('GroqLLM'),
610
+ },
611
+ {
612
+ vendorDriverName: 'MistralLLM',
613
+ apiKey: GetAIAPIKey('MistralLLM'),
614
+ },
615
+ {
616
+ vendorDriverName: 'CerebrasLLM',
617
+ apiKey: GetAIAPIKey('CerebrasLLM'),
618
+ },
619
+ ];
620
+ }
621
+
622
+ /**
623
+ * Build HTTP headers for Skip API requests
624
+ */
625
+ private buildHeaders(): Record<string, string> {
626
+ return {
627
+ 'x-api-key': this.config.apiKey || '',
628
+ 'Content-Type': 'application/json',
629
+ };
630
+ }
631
+
632
+ /**
633
+ * Refreshes the Skip entities cache
634
+ * Rebuilds the entity information that is provided to Skip
635
+ * Copied from AskSkipResolver.refreshSkipEntities
636
+ */
637
+ private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
638
+ try {
639
+ const md = new Metadata();
640
+ const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? []).map((e) =>
641
+ e.trim().toLowerCase()
642
+ );
643
+
644
+ // Get the list of entities
645
+ const entities = md.Entities.filter((e) => {
646
+ if (
647
+ !configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
648
+ skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
649
+ ) {
650
+ const sd = e.ScopeDefault?.trim();
651
+ if (sd && sd.length > 0) {
652
+ const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
653
+ return (
654
+ !scopes ||
655
+ scopes.length === 0 ||
656
+ scopes.includes('all') ||
657
+ scopes.includes('ai') ||
658
+ skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
659
+ );
660
+ } else {
661
+ return true; // no scope, so include it
662
+ }
645
663
  }
646
- }
664
+ return false;
665
+ });
647
666
 
648
- /**
649
- * Packs information about a single entity for Skip
650
- * Includes fields, relationships, and sample data
651
- * Copied from AskSkipResolver.PackSingleSkipEntityInfo
652
- */
653
- private async packSingleSkipEntityInfo(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo> {
654
- try {
655
- const ret: SkipEntityInfo = {
656
- id: e.ID,
657
- name: e.Name,
658
- schemaName: e.SchemaName,
659
- baseView: e.BaseView,
660
- description: e.Description,
661
-
662
- fields: await Promise.all(e.Fields.filter(f => {
663
- // we want to check the scopes for the field level and make sure it is either All or AI or has both
664
- const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
665
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
666
- }).map(f => {
667
- return this.packSingleSkipEntityField(f, dataSource);
668
- })),
669
-
670
- relatedEntities: e.RelatedEntities.map((r) => {
671
- return this.packSingleSkipEntityRelationship(r);
672
- }),
667
+ // Now we have our list of entities, pack em up
668
+ const result = await Promise.all(entities.map((e) => this.packSingleSkipEntityInfo(e, dataSource)));
673
669
 
674
- rowsPacked: e.RowsToPackWithSchema,
675
- rowsSampleMethod: e.RowsToPackSampleMethod,
676
- rows: await this.packEntityRows(e, dataSource)
677
- };
678
- return ret;
679
- }
680
- catch (e) {
681
- LogError(`[SkipSDK] packSingleSkipEntityInfo error: ${e}`);
682
- return null;
683
- }
670
+ SkipSDK.__lastRefreshTime = Date.now(); // Update last refresh time
671
+ return result;
672
+ } catch (e) {
673
+ LogError(`[SkipSDK] refreshSkipEntities error: ${e}`);
674
+ return [];
684
675
  }
685
-
686
- /**
687
- * Packs information about a single entity relationship
688
- * These relationships help Skip understand the data model
689
- * Copied from AskSkipResolver.PackSingleSkipEntityRelationship
690
- */
691
- private packSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
692
- try {
693
- return {
694
- entityID: r.EntityID,
695
- relatedEntityID: r.RelatedEntityID,
696
- type: r.Type,
697
- entityKeyField: r.EntityKeyField,
698
- relatedEntityJoinField: r.RelatedEntityJoinField,
699
- joinView: r.JoinView,
700
- joinEntityJoinField: r.JoinEntityJoinField,
701
- joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
702
- entity: r.Entity,
703
- entityBaseView: r.EntityBaseView,
704
- relatedEntity: r.RelatedEntity,
705
- relatedEntityBaseView: r.RelatedEntityBaseView,
706
- };
707
- }
708
- catch (e) {
709
- LogError(`[SkipSDK] packSingleSkipEntityRelationship error: ${e}`);
710
- return null;
711
- }
676
+ }
677
+
678
+ /**
679
+ * Packs information about a single entity for Skip
680
+ * Includes fields, relationships, and sample data
681
+ * Copied from AskSkipResolver.PackSingleSkipEntityInfo
682
+ */
683
+ private async packSingleSkipEntityInfo(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo> {
684
+ try {
685
+ const ret: SkipEntityInfo = {
686
+ id: e.ID,
687
+ name: e.Name,
688
+ schemaName: e.SchemaName,
689
+ baseView: e.BaseView,
690
+ description: e.Description,
691
+
692
+ fields: await Promise.all(
693
+ e.Fields.filter((f) => {
694
+ // we want to check the scopes for the field level and make sure it is either All or AI or has both
695
+ const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
696
+ return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
697
+ }).map((f) => {
698
+ return this.packSingleSkipEntityField(f, dataSource);
699
+ })
700
+ ),
701
+
702
+ relatedEntities: e.RelatedEntities.map((r) => {
703
+ return this.packSingleSkipEntityRelationship(r);
704
+ }),
705
+
706
+ rowsPacked: e.RowsToPackWithSchema,
707
+ rowsSampleMethod: e.RowsToPackSampleMethod,
708
+ rows: await this.packEntityRows(e, dataSource),
709
+ };
710
+ return ret;
711
+ } catch (e) {
712
+ LogError(`[SkipSDK] packSingleSkipEntityInfo error: ${e}`);
713
+ return null;
712
714
  }
713
-
714
- /**
715
- * Packs information about a single entity field
716
- * Includes metadata and possible values
717
- * Copied from AskSkipResolver.PackSingleSkipEntityField
718
- */
719
- private async packSingleSkipEntityField(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldInfo> {
720
- try {
721
- return {
722
- entityID: f.EntityID,
723
- sequence: f.Sequence,
724
- name: f.Name,
725
- displayName: f.DisplayName,
726
- category: f.Category,
727
- type: f.Type,
728
- description: f.Description,
729
- isPrimaryKey: f.IsPrimaryKey,
730
- allowsNull: f.AllowsNull,
731
- isUnique: f.IsUnique,
732
- length: f.Length,
733
- precision: f.Precision,
734
- scale: f.Scale,
735
- sqlFullType: f.SQLFullType,
736
- defaultValue: f.DefaultValue,
737
- autoIncrement: f.AutoIncrement,
738
- valueListType: f.ValueListType,
739
- extendedType: f.ExtendedType,
740
- defaultInView: f.DefaultInView,
741
- defaultColumnWidth: f.DefaultColumnWidth,
742
- isVirtual: f.IsVirtual,
743
- isNameField: f.IsNameField,
744
- relatedEntityID: f.RelatedEntityID,
745
- relatedEntityFieldName: f.RelatedEntityFieldName,
746
- relatedEntity: f.RelatedEntity,
747
- relatedEntitySchemaName: f.RelatedEntitySchemaName,
748
- relatedEntityBaseView: f.RelatedEntityBaseView,
749
- possibleValues: await this.packFieldPossibleValues(f, dataSource),
750
- };
751
- }
752
- catch (e) {
753
- LogError(`[SkipSDK] packSingleSkipEntityField error: ${e}`);
754
- return null;
755
- }
715
+ }
716
+
717
+ /**
718
+ * Packs information about a single entity relationship
719
+ * These relationships help Skip understand the data model
720
+ * Copied from AskSkipResolver.PackSingleSkipEntityRelationship
721
+ */
722
+ private packSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
723
+ try {
724
+ return {
725
+ entityID: r.EntityID,
726
+ relatedEntityID: r.RelatedEntityID,
727
+ type: r.Type,
728
+ entityKeyField: r.EntityKeyField,
729
+ relatedEntityJoinField: r.RelatedEntityJoinField,
730
+ joinView: r.JoinView,
731
+ joinEntityJoinField: r.JoinEntityJoinField,
732
+ joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
733
+ entity: r.Entity,
734
+ entityBaseView: r.EntityBaseView,
735
+ relatedEntity: r.RelatedEntity,
736
+ relatedEntityBaseView: r.RelatedEntityBaseView,
737
+ };
738
+ } catch (e) {
739
+ LogError(`[SkipSDK] packSingleSkipEntityRelationship error: ${e}`);
740
+ return null;
756
741
  }
757
-
758
- /**
759
- * Packs entity rows (sample data)
760
- * Copied from AskSkipResolver.PackEntityRows
761
- */
762
- private async packEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
763
- try {
764
- if (e.RowsToPackWithSchema === 'None')
765
- return [];
766
-
767
- // only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
768
- const fields = e.Fields.filter((f) => {
769
- const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
770
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
771
- }).map(f => `[${f.Name}]`).join(',');
772
-
773
- // now run the query based on the row packing method
774
- let sql: string = '';
775
- switch (e.RowsToPackWithSchema) {
776
- case 'All':
777
- sql = `SELECT ${fields} FROM ${e.SchemaName}.${e.BaseView}`;
778
- break;
779
- case 'Sample':
780
- switch (e.RowsToPackSampleMethod) {
781
- case 'random':
782
- sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}] ORDER BY newid()`;
783
- break;
784
- case 'top n':
785
- const orderBy = e.RowsToPackSampleOrder ? ` ORDER BY [${e.RowsToPackSampleOrder}]` : '';
786
- sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}]${orderBy}`;
787
- break;
788
- case 'bottom n':
789
- const firstPrimaryKey = e.FirstPrimaryKey.Name;
790
- const innerOrderBy = e.RowsToPackSampleOrder ? `[${e.RowsToPackSampleOrder}]` : `[${firstPrimaryKey}] DESC`;
791
- sql = `SELECT * FROM (
742
+ }
743
+
744
+ /**
745
+ * Packs information about a single entity field
746
+ * Includes metadata and possible values
747
+ * Copied from AskSkipResolver.PackSingleSkipEntityField
748
+ */
749
+ private async packSingleSkipEntityField(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldInfo> {
750
+ try {
751
+ return {
752
+ entityID: f.EntityID,
753
+ sequence: f.Sequence,
754
+ name: f.Name,
755
+ displayName: f.DisplayName,
756
+ category: f.Category,
757
+ type: f.Type,
758
+ description: f.Description,
759
+ isPrimaryKey: f.IsPrimaryKey,
760
+ allowsNull: f.AllowsNull,
761
+ isUnique: f.IsUnique,
762
+ length: f.Length,
763
+ precision: f.Precision,
764
+ scale: f.Scale,
765
+ sqlFullType: f.SQLFullType,
766
+ defaultValue: f.DefaultValue,
767
+ autoIncrement: f.AutoIncrement,
768
+ valueListType: f.ValueListType,
769
+ extendedType: f.ExtendedType,
770
+ defaultInView: f.DefaultInView,
771
+ defaultColumnWidth: f.DefaultColumnWidth,
772
+ isVirtual: f.IsVirtual,
773
+ isNameField: f.IsNameField,
774
+ relatedEntityID: f.RelatedEntityID,
775
+ relatedEntityFieldName: f.RelatedEntityFieldName,
776
+ relatedEntity: f.RelatedEntity,
777
+ relatedEntitySchemaName: f.RelatedEntitySchemaName,
778
+ relatedEntityBaseView: f.RelatedEntityBaseView,
779
+ possibleValues: await this.packFieldPossibleValues(f, dataSource),
780
+ };
781
+ } catch (e) {
782
+ LogError(`[SkipSDK] packSingleSkipEntityField error: ${e}`);
783
+ return null;
784
+ }
785
+ }
786
+
787
+ /**
788
+ * Packs entity rows (sample data)
789
+ * Copied from AskSkipResolver.PackEntityRows
790
+ */
791
+ private async packEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
792
+ try {
793
+ if (e.RowsToPackWithSchema === 'None') return [];
794
+
795
+ // only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
796
+ const fields = e.Fields.filter((f) => {
797
+ const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
798
+ return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
799
+ })
800
+ .map((f) => `[${f.Name}]`)
801
+ .join(',');
802
+
803
+ // now run the query based on the row packing method
804
+ let sql: string = '';
805
+ switch (e.RowsToPackWithSchema) {
806
+ case 'All':
807
+ sql = `SELECT ${fields} FROM ${e.SchemaName}.${e.BaseView}`;
808
+ break;
809
+ case 'Sample':
810
+ switch (e.RowsToPackSampleMethod) {
811
+ case 'random':
812
+ sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}] ORDER BY newid()`;
813
+ break;
814
+ case 'top n':
815
+ const orderBy = e.RowsToPackSampleOrder ? ` ORDER BY [${e.RowsToPackSampleOrder}]` : '';
816
+ sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}]${orderBy}`;
817
+ break;
818
+ case 'bottom n':
819
+ const firstPrimaryKey = e.FirstPrimaryKey.Name;
820
+ const innerOrderBy = e.RowsToPackSampleOrder ? `[${e.RowsToPackSampleOrder}]` : `[${firstPrimaryKey}] DESC`;
821
+ sql = `SELECT * FROM (
792
822
  SELECT TOP ${e.RowsToPackSampleCount} ${fields}
793
823
  FROM [${e.SchemaName}].[${e.BaseView}]
794
824
  ORDER BY ${innerOrderBy}
795
825
  ) sub
796
826
  ORDER BY [${firstPrimaryKey}] ASC;`;
797
- break;
798
- }
799
- }
800
- const request = new mssql.Request(dataSource);
801
- const result = await request.query(sql);
802
- if (!result || !result.recordset) {
803
- return [];
804
- }
805
- else {
806
- return result.recordset;
807
- }
808
- }
809
- catch (e) {
810
- LogError(`[SkipSDK] packEntityRows error: ${e}`);
811
- return [];
812
- }
827
+ break;
828
+ }
829
+ }
830
+ const request = new mssql.Request(dataSource);
831
+ const result = await request.query(sql);
832
+ if (!result || !result.recordset) {
833
+ return [];
834
+ } else {
835
+ return result.recordset;
836
+ }
837
+ } catch (e) {
838
+ LogError(`[SkipSDK] packEntityRows error: ${e}`);
839
+ return [];
813
840
  }
814
-
815
- /**
816
- * Packs possible values for an entity field
817
- * These values help Skip understand the domain and valid values for fields
818
- * Copied from AskSkipResolver.PackFieldPossibleValues
819
- */
820
- private async packFieldPossibleValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
821
- try {
822
- if (f.ValuesToPackWithSchema === 'None') {
823
- return []; // don't pack anything
824
- }
825
- else if (f.ValuesToPackWithSchema === 'All') {
826
- // wants ALL of the distinct values
827
- return await this.getFieldDistinctValues(f, dataSource);
828
- }
829
- else if (f.ValuesToPackWithSchema === 'Auto') {
830
- // default setting - pack based on the ValueListType
831
- if (f.ValueListTypeEnum === 'List') {
832
- // simple list of values in the Entity Field Values table
833
- return f.EntityFieldValues.map((v) => {
834
- return { value: v.Value, displayValue: v.Value };
835
- });
836
- }
837
- else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
838
- // could be a user provided value, OR the values in the list of possible values.
839
- // get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
840
- const values = await this.getFieldDistinctValues(f, dataSource);
841
- if (!values || values.length === 0) {
842
- // no result, just return the EntityFieldValues
843
- return f.EntityFieldValues.map((v) => {
844
- return { value: v.Value, displayValue: v.Value };
845
- });
846
- }
847
- else {
848
- return [...new Set([...f.EntityFieldValues.map((v) => {
849
- return { value: v.Value, displayValue: v.Value };
850
- }), ...values])];
851
- }
852
- }
853
- }
854
- return []; // if we get here, nothing to pack
855
- }
856
- catch (e) {
857
- LogError(`[SkipSDK] packFieldPossibleValues error: ${e}`);
858
- return [];
859
- }
860
- }
861
-
862
- /**
863
- * Gets distinct values for a field from the database
864
- * Used to provide Skip with information about the possible values
865
- * Copied from AskSkipResolver.GetFieldDistinctValues
866
- */
867
- private async getFieldDistinctValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
868
- try {
869
- const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
870
- const request = new mssql.Request(dataSource);
871
- const result = await request.query(sql);
872
- if (!result || !result.recordset) {
873
- return [];
874
- }
875
- else {
876
- return result.recordset.map((r) => {
877
- return {
878
- value: r[f.Name],
879
- displayValue: r[f.Name]
880
- };
881
- });
882
- }
883
- }
884
- catch (e) {
885
- LogError(`[SkipSDK] getFieldDistinctValues error: ${e}`);
886
- return [];
841
+ }
842
+
843
+ /**
844
+ * Packs possible values for an entity field
845
+ * These values help Skip understand the domain and valid values for fields
846
+ * Copied from AskSkipResolver.PackFieldPossibleValues
847
+ */
848
+ private async packFieldPossibleValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
849
+ try {
850
+ if (f.ValuesToPackWithSchema === 'None') {
851
+ return []; // don't pack anything
852
+ } else if (f.ValuesToPackWithSchema === 'All') {
853
+ // wants ALL of the distinct values
854
+ return await this.getFieldDistinctValues(f, dataSource);
855
+ } else if (f.ValuesToPackWithSchema === 'Auto') {
856
+ // default setting - pack based on the ValueListType
857
+ if (f.ValueListTypeEnum === 'List') {
858
+ // simple list of values in the Entity Field Values table
859
+ return f.EntityFieldValues.map((v) => {
860
+ return { value: v.Value, displayValue: v.Value };
861
+ });
862
+ } else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
863
+ // could be a user provided value, OR the values in the list of possible values.
864
+ // get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
865
+ const values = await this.getFieldDistinctValues(f, dataSource);
866
+ if (!values || values.length === 0) {
867
+ // no result, just return the EntityFieldValues
868
+ return f.EntityFieldValues.map((v) => {
869
+ return { value: v.Value, displayValue: v.Value };
870
+ });
871
+ } else {
872
+ return [
873
+ ...new Set([
874
+ ...f.EntityFieldValues.map((v) => {
875
+ return { value: v.Value, displayValue: v.Value };
876
+ }),
877
+ ...values,
878
+ ]),
879
+ ];
880
+ }
887
881
  }
882
+ }
883
+ return []; // if we get here, nothing to pack
884
+ } catch (e) {
885
+ LogError(`[SkipSDK] packFieldPossibleValues error: ${e}`);
886
+ return [];
888
887
  }
889
-
890
- /**
891
- * Process messages: filter delegation messages and add metadata fields
892
- * Messages coming in should already have conversationDetailID if they exist in the database
893
- */
894
- private processMessages(messages: SkipMessage[]): SkipMessage[] {
895
- // Filter out delegation messages (administrative messages that shouldn't go to Skip)
896
- const filteredMessages = messages.filter(msg => !this.isDelegationMessage(msg.content));
897
-
898
- // Enrich messages with default metadata if not already present
899
- return filteredMessages.map(msg => ({
900
- ...msg,
901
- // Add default metadata fields if not already present
902
- // Messages from DB already have conversationDetailID, temp messages get temp-X
903
- hiddenToUser: msg.hiddenToUser ?? false,
904
- userRating: msg.userRating ?? null,
905
- userFeedback: msg.userFeedback ?? null,
906
- reflectionInsights: msg.reflectionInsights ?? null,
907
- summaryOfEarlierConveration: msg.summaryOfEarlierConveration ?? null
908
- }));
909
- }
910
-
911
- /**
912
- * Check if a message is a delegation message that should be filtered out
913
- * Uses flexible pattern matching to detect variations of delegation messages
914
- */
915
- private isDelegationMessage(content: string): boolean {
916
- if (!content) return false;
917
-
918
- const lowerContent = content.toLowerCase();
919
-
920
- // Check for both "delegating" or "delegate" AND "skip" in any order
921
- const hasDelegatingOrDelegate = lowerContent.includes('delegating') || lowerContent.includes('delegate');
922
- const hasSkip = lowerContent.includes('skip');
923
-
924
- return hasDelegatingOrDelegate && hasSkip;
888
+ }
889
+
890
+ /**
891
+ * Gets distinct values for a field from the database
892
+ * Used to provide Skip with information about the possible values
893
+ * Copied from AskSkipResolver.GetFieldDistinctValues
894
+ */
895
+ private async getFieldDistinctValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
896
+ try {
897
+ const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
898
+ const request = new mssql.Request(dataSource);
899
+ const result = await request.query(sql);
900
+ if (!result || !result.recordset) {
901
+ return [];
902
+ } else {
903
+ return result.recordset.map((r) => {
904
+ return {
905
+ value: r[f.Name],
906
+ displayValue: r[f.Name],
907
+ };
908
+ });
909
+ }
910
+ } catch (e) {
911
+ LogError(`[SkipSDK] getFieldDistinctValues error: ${e}`);
912
+ return [];
925
913
  }
914
+ }
915
+
916
+ /**
917
+ * Process messages: filter delegation messages and add metadata fields
918
+ * Messages coming in should already have conversationDetailID if they exist in the database
919
+ */
920
+ private processMessages(messages: SkipMessage[]): SkipMessage[] {
921
+ // Filter out delegation messages (administrative messages that shouldn't go to Skip)
922
+ const filteredMessages = messages.filter((msg) => !this.isDelegationMessage(msg.content));
923
+
924
+ // Enrich messages with default metadata if not already present
925
+ return filteredMessages.map((msg) => ({
926
+ ...msg,
927
+ // Add default metadata fields if not already present
928
+ // Messages from DB already have conversationDetailID, temp messages get temp-X
929
+ hiddenToUser: msg.hiddenToUser ?? false,
930
+ userRating: msg.userRating ?? null,
931
+ userFeedback: msg.userFeedback ?? null,
932
+ reflectionInsights: msg.reflectionInsights ?? null,
933
+ summaryOfEarlierConveration: msg.summaryOfEarlierConveration ?? null,
934
+ }));
935
+ }
936
+
937
+ /**
938
+ * Check if a message is a delegation message that should be filtered out
939
+ * Uses flexible pattern matching to detect variations of delegation messages
940
+ */
941
+ private isDelegationMessage(content: string): boolean {
942
+ if (!content) return false;
943
+
944
+ const lowerContent = content.toLowerCase();
945
+
946
+ // Check for both "delegating" or "delegate" AND "skip" in any order
947
+ const hasDelegatingOrDelegate = lowerContent.includes('delegating') || lowerContent.includes('delegate');
948
+ const hasSkip = lowerContent.includes('skip');
949
+
950
+ return hasDelegatingOrDelegate && hasSkip;
951
+ }
926
952
  }