@memberjunction/server 5.44.0 → 5.45.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 (254) hide show
  1. package/dist/agentSessions/ReturningVisitorRecap.d.ts +39 -0
  2. package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
  3. package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
  4. package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
  5. package/dist/agentSessions/SessionJanitor.d.ts +13 -0
  6. package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
  7. package/dist/agentSessions/SessionJanitor.js +65 -7
  8. package/dist/agentSessions/SessionJanitor.js.map +1 -1
  9. package/dist/agentSessions/SessionManager.d.ts.map +1 -1
  10. package/dist/agentSessions/SessionManager.js +37 -0
  11. package/dist/agentSessions/SessionManager.js.map +1 -1
  12. package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
  13. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
  14. package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
  15. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
  16. package/dist/auth/magicLink/index.d.ts +1 -1
  17. package/dist/auth/magicLink/index.d.ts.map +1 -1
  18. package/dist/auth/magicLink/index.js +1 -1
  19. package/dist/auth/magicLink/index.js.map +1 -1
  20. package/dist/auth/magicLink/types.d.ts +26 -0
  21. package/dist/auth/magicLink/types.d.ts.map +1 -1
  22. package/dist/config.d.ts +1883 -144
  23. package/dist/config.d.ts.map +1 -1
  24. package/dist/config.js +160 -36
  25. package/dist/config.js.map +1 -1
  26. package/dist/context.d.ts.map +1 -1
  27. package/dist/context.js +45 -2
  28. package/dist/context.js.map +1 -1
  29. package/dist/generated/generated.d.ts +522 -0
  30. package/dist/generated/generated.d.ts.map +1 -1
  31. package/dist/generated/generated.js +2860 -0
  32. package/dist/generated/generated.js.map +1 -1
  33. package/dist/generic/ResolverBase.d.ts +13 -0
  34. package/dist/generic/ResolverBase.d.ts.map +1 -1
  35. package/dist/generic/ResolverBase.js +22 -0
  36. package/dist/generic/ResolverBase.js.map +1 -1
  37. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  38. package/dist/generic/RunViewResolver.js +4 -0
  39. package/dist/generic/RunViewResolver.js.map +1 -1
  40. package/dist/index.d.ts +0 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +101 -3
  43. package/dist/index.js.map +1 -1
  44. package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
  45. package/dist/integration/CustomColumnPromoter.js +6 -0
  46. package/dist/integration/CustomColumnPromoter.js.map +1 -1
  47. package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
  48. package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
  49. package/dist/realtimeWidget/WidgetRouter.js +182 -0
  50. package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
  51. package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
  52. package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
  53. package/dist/realtimeWidget/WidgetSessionService.js +477 -0
  54. package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
  55. package/dist/realtimeWidget/host-identity.d.ts +40 -0
  56. package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
  57. package/dist/realtimeWidget/host-identity.js +58 -0
  58. package/dist/realtimeWidget/host-identity.js.map +1 -0
  59. package/dist/realtimeWidget/index.d.ts +10 -0
  60. package/dist/realtimeWidget/index.d.ts.map +1 -0
  61. package/dist/realtimeWidget/index.js +9 -0
  62. package/dist/realtimeWidget/index.js.map +1 -0
  63. package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
  64. package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
  65. package/dist/realtimeWidget/visitorIdentity.js +202 -0
  66. package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
  67. package/dist/realtimeWidget/widgetCore.d.ts +106 -0
  68. package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
  69. package/dist/realtimeWidget/widgetCore.js +173 -0
  70. package/dist/realtimeWidget/widgetCore.js.map +1 -0
  71. package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
  72. package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
  73. package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
  74. package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
  75. package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
  76. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  77. package/dist/resolvers/ComponentRegistryResolver.js +31 -8
  78. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  79. package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
  80. package/dist/resolvers/EntityPermissionResolver.js +1 -2
  81. package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
  82. package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
  83. package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
  84. package/dist/resolvers/QuerySystemUserResolver.js +15 -13
  85. package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
  86. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
  87. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  88. package/dist/resolvers/RealtimeClientSessionResolver.js +47 -3
  89. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  90. package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
  91. package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
  92. package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
  93. package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
  94. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  95. package/dist/resolvers/RunAIAgentResolver.js +14 -3
  96. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  97. package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
  98. package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
  99. package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
  100. package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
  101. package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
  102. package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
  103. package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
  104. package/dist/resolvers/TelephonyResolver.d.ts +26 -0
  105. package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
  106. package/dist/resolvers/TelephonyResolver.js +86 -0
  107. package/dist/resolvers/TelephonyResolver.js.map +1 -0
  108. package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
  109. package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
  110. package/dist/resolvers/TestQuerySQLResolver.js +2 -3
  111. package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
  112. package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
  113. package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
  114. package/dist/resolvers/VonageTelephonyResolver.js +86 -0
  115. package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
  116. package/dist/rest/MediaStreamHandler.js +4 -1
  117. package/dist/rest/MediaStreamHandler.js.map +1 -1
  118. package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
  119. package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
  120. package/dist/telephony/RingCentralTelephonyService.js +262 -0
  121. package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
  122. package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
  123. package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
  124. package/dist/telephony/TeamsMeetingsRouter.js +96 -0
  125. package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
  126. package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
  127. package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
  128. package/dist/telephony/TeamsMeetingsService.js +196 -0
  129. package/dist/telephony/TeamsMeetingsService.js.map +1 -0
  130. package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
  131. package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
  132. package/dist/telephony/TwilioTelephonyRouter.js +143 -0
  133. package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
  134. package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
  135. package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
  136. package/dist/telephony/TwilioTelephonyService.js +193 -0
  137. package/dist/telephony/TwilioTelephonyService.js.map +1 -0
  138. package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
  139. package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
  140. package/dist/telephony/VonageTelephonyRouter.js +201 -0
  141. package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
  142. package/dist/telephony/VonageTelephonyService.d.ts +90 -0
  143. package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
  144. package/dist/telephony/VonageTelephonyService.js +191 -0
  145. package/dist/telephony/VonageTelephonyService.js.map +1 -0
  146. package/dist/telephony/calendar-scheduler.d.ts +67 -0
  147. package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
  148. package/dist/telephony/calendar-scheduler.js +133 -0
  149. package/dist/telephony/calendar-scheduler.js.map +1 -0
  150. package/dist/telephony/index.d.ts +29 -0
  151. package/dist/telephony/index.d.ts.map +1 -0
  152. package/dist/telephony/index.js +38 -0
  153. package/dist/telephony/index.js.map +1 -0
  154. package/dist/telephony/media-upgrade-router.d.ts +33 -0
  155. package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
  156. package/dist/telephony/media-upgrade-router.js +56 -0
  157. package/dist/telephony/media-upgrade-router.js.map +1 -0
  158. package/dist/telephony/ringcentral-runtime.d.ts +14 -0
  159. package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
  160. package/dist/telephony/ringcentral-runtime.js +18 -0
  161. package/dist/telephony/ringcentral-runtime.js.map +1 -0
  162. package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
  163. package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
  164. package/dist/telephony/teams-meetings-runtime.js +20 -0
  165. package/dist/telephony/teams-meetings-runtime.js.map +1 -0
  166. package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
  167. package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
  168. package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
  169. package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
  170. package/dist/telephony/telephony-runtime.d.ts +14 -0
  171. package/dist/telephony/telephony-runtime.d.ts.map +1 -0
  172. package/dist/telephony/telephony-runtime.js +18 -0
  173. package/dist/telephony/telephony-runtime.js.map +1 -0
  174. package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
  175. package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
  176. package/dist/telephony/twilioMediaRegistry.js +106 -0
  177. package/dist/telephony/twilioMediaRegistry.js.map +1 -0
  178. package/dist/telephony/vonage-runtime.d.ts +14 -0
  179. package/dist/telephony/vonage-runtime.d.ts.map +1 -0
  180. package/dist/telephony/vonage-runtime.js +18 -0
  181. package/dist/telephony/vonage-runtime.js.map +1 -0
  182. package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
  183. package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
  184. package/dist/telephony/vonageMediaRegistry.js +158 -0
  185. package/dist/telephony/vonageMediaRegistry.js.map +1 -0
  186. package/package.json +88 -83
  187. package/src/__tests__/search-knowledge-tags.test.ts +9 -9
  188. package/src/__tests__/teams-meetings-service.test.ts +65 -0
  189. package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
  190. package/src/__tests__/twilio-telephony-service.test.ts +23 -0
  191. package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
  192. package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
  193. package/src/__tests__/widget.test.ts +204 -0
  194. package/src/__tests__/widgetGuestElevation.test.ts +57 -0
  195. package/src/__tests__/widgetHostIdentity.test.ts +117 -0
  196. package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
  197. package/src/agentSessions/SessionJanitor.ts +66 -6
  198. package/src/agentSessions/SessionManager.ts +38 -0
  199. package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
  200. package/src/auth/magicLink/index.ts +1 -0
  201. package/src/auth/magicLink/types.ts +26 -0
  202. package/src/config.ts +172 -38
  203. package/src/context.ts +50 -3
  204. package/src/generated/generated.ts +1991 -1
  205. package/src/generic/ResolverBase.ts +28 -0
  206. package/src/generic/RunViewResolver.ts +4 -0
  207. package/src/index.ts +109 -3
  208. package/src/integration/CustomColumnPromoter.ts +6 -0
  209. package/src/realtimeWidget/WidgetRouter.ts +204 -0
  210. package/src/realtimeWidget/WidgetSessionService.ts +714 -0
  211. package/src/realtimeWidget/host-identity.ts +84 -0
  212. package/src/realtimeWidget/index.ts +15 -0
  213. package/src/realtimeWidget/visitorIdentity.ts +258 -0
  214. package/src/realtimeWidget/widgetCore.ts +231 -0
  215. package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
  216. package/src/resolvers/ComponentRegistryResolver.ts +36 -10
  217. package/src/resolvers/EntityPermissionResolver.ts +1 -2
  218. package/src/resolvers/QuerySystemUserResolver.ts +14 -10
  219. package/src/resolvers/RealtimeClientSessionResolver.ts +59 -2
  220. package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
  221. package/src/resolvers/RunAIAgentResolver.ts +16 -4
  222. package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
  223. package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
  224. package/src/resolvers/TelephonyResolver.ts +63 -0
  225. package/src/resolvers/TestQuerySQLResolver.ts +2 -3
  226. package/src/resolvers/VonageTelephonyResolver.ts +63 -0
  227. package/src/rest/MediaStreamHandler.ts +4 -1
  228. package/src/telephony/RingCentralTelephonyService.ts +342 -0
  229. package/src/telephony/TeamsMeetingsRouter.ts +124 -0
  230. package/src/telephony/TeamsMeetingsService.ts +268 -0
  231. package/src/telephony/TwilioTelephonyRouter.ts +184 -0
  232. package/src/telephony/TwilioTelephonyService.ts +261 -0
  233. package/src/telephony/VonageTelephonyRouter.ts +239 -0
  234. package/src/telephony/VonageTelephonyService.ts +259 -0
  235. package/src/telephony/calendar-scheduler.ts +176 -0
  236. package/src/telephony/index.ts +43 -0
  237. package/src/telephony/media-upgrade-router.ts +62 -0
  238. package/src/telephony/ringcentral-runtime.ts +22 -0
  239. package/src/telephony/teams-meetings-runtime.ts +24 -0
  240. package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
  241. package/src/telephony/telephony-runtime.ts +22 -0
  242. package/src/telephony/twilioMediaRegistry.ts +137 -0
  243. package/src/telephony/vonage-runtime.ts +22 -0
  244. package/src/telephony/vonageMediaRegistry.ts +200 -0
  245. package/dist/agents/skip-agent.d.ts +0 -111
  246. package/dist/agents/skip-agent.d.ts.map +0 -1
  247. package/dist/agents/skip-agent.js +0 -1487
  248. package/dist/agents/skip-agent.js.map +0 -1
  249. package/dist/agents/skip-sdk.d.ts +0 -261
  250. package/dist/agents/skip-sdk.d.ts.map +0 -1
  251. package/dist/agents/skip-sdk.js +0 -909
  252. package/dist/agents/skip-sdk.js.map +0 -1
  253. package/src/agents/skip-agent.ts +0 -1594
  254. package/src/agents/skip-sdk.ts +0 -1210
@@ -1,1210 +0,0 @@
1
- /**
2
- * Skip TypeScript SDK
3
- *
4
- * A general-purpose SDK for calling the Skip SaaS API.
5
- * This module provides a clean, reusable interface for integrating with Skip
6
- * that can eventually be extracted into a standalone npm package.
7
- */
8
-
9
- import {
10
- SkipAPIRequest,
11
- SkipAPIResponse,
12
- SkipMessage,
13
- SkipAPIAnalysisCompleteResponse,
14
- SkipAPIClarifyingQuestionResponse,
15
- SkipRequestPhase,
16
- SkipResponsePhase,
17
- SkipAPIRequestAPIKey,
18
- SkipQueryInfo,
19
- SkipQueryCatalogEntry,
20
- SkipAPIAgentNote,
21
- SkipAPIAgentNoteType,
22
- SkipAPIArtifact,
23
- SkipAPIArtifactVersion,
24
- SkipAPIArtifactType
25
- } from '@memberjunction/skip-types';
26
- import { DataContext } from '@memberjunction/data-context';
27
- import { IMetadataProvider, UserInfo, LogStatus, LogError, Metadata, RunQuery, RunView, EntityInfo, EntityFieldInfo, EntityFieldValueInfo, DatabaseProviderBase } from '@memberjunction/core';
28
- import { MJConversationDetailEntity, QueryEngine } from '@memberjunction/core-entities';
29
- import { request as httpRequest } from 'http';
30
- import { request as httpsRequest } from 'https';
31
- import { gzip as gzipCompress, createGunzip } from 'zlib';
32
- import { configInfo, baseUrl, publicUrl, graphqlPort, graphqlRootPath, apiKey as callbackAPIKey } from '../config.js';
33
- import { getDbType } from '../index.js';
34
- import { GetAIAPIKey } from '@memberjunction/ai';
35
- import { AIEngine } from '@memberjunction/aiengine';
36
- import { CopyScalarsAndArrays, UUIDsEqual } from '@memberjunction/global';
37
- import mssql from 'mssql';
38
- import { registerAccessToken, GetDataAccessToken } from '../resolvers/GetDataResolver.js';
39
- import { BehaviorSubject } from 'rxjs';
40
- import { take } from 'rxjs/operators';
41
-
42
- /**
43
- * Configuration options for Skip SDK
44
- */
45
- export interface SkipSDKConfig {
46
- /**
47
- * Skip API base URL (e.g., 'https://skip.memberjunction.com')
48
- */
49
- apiUrl?: string;
50
-
51
- /**
52
- * Skip API key for authentication
53
- */
54
- apiKey?: string;
55
-
56
- /**
57
- * Organization ID
58
- */
59
- organizationId?: string;
60
-
61
- /**
62
- * Optional organization context information
63
- */
64
- organizationInfo?: string;
65
-
66
- /**
67
- * Optional metadata provider this SDK instance binds to. When set, every metadata
68
- * lookup (entities, queries, schema) and direct SQL call is routed through this
69
- * provider — multi-tenant servers should pass the per-request provider here.
70
- * When omitted, the SDK falls back to the global `Metadata.Provider` (single-server
71
- * mode, legacy callers).
72
- */
73
- provider?: IMetadataProvider;
74
- }
75
-
76
- /**
77
- * Options for making a Skip API call
78
- */
79
- export interface SkipCallOptions {
80
- /**
81
- * Conversation messages (user/assistant)
82
- */
83
- messages: SkipMessage[];
84
-
85
- /**
86
- * Conversation ID for tracking
87
- */
88
- conversationId: string;
89
-
90
- /**
91
- * Data context to provide to Skip
92
- */
93
- dataContext?: DataContext;
94
-
95
- /**
96
- * Request phase (initial_request, clarify_question_response, etc.)
97
- */
98
- requestPhase?: SkipRequestPhase;
99
-
100
- /**
101
- * Context user for permissions and metadata
102
- */
103
- contextUser: UserInfo;
104
-
105
- /**
106
- * Database connection for metadata queries
107
- */
108
- dataSource: mssql.ConnectionPool;
109
-
110
- /**
111
- * Include entity metadata in request
112
- */
113
- includeEntities?: boolean;
114
-
115
- /**
116
- * Include saved queries in request
117
- */
118
- includeQueries?: boolean;
119
-
120
- /**
121
- * Include agent notes in request
122
- */
123
- includeNotes?: boolean;
124
-
125
- /**
126
- * Include agent requests in request
127
- */
128
- includeRequests?: boolean;
129
-
130
- /**
131
- * Force refresh of entity metadata cache
132
- */
133
- forceEntityRefresh?: boolean;
134
-
135
- /**
136
- * Include callback API key and access token for Skip to call back to MJ
137
- */
138
- includeCallbackAuth?: boolean;
139
-
140
- /**
141
- * Callback for streaming status updates during execution
142
- */
143
- onStatusUpdate?: (message: string, responsePhase?: string) => void;
144
-
145
- /**
146
- * Optional payload data from a previous response (e.g., PRD in progress).
147
- * This enables incremental artifact building where structured data accumulates
148
- * throughout the conversation. When Skip returns a response with a payload,
149
- * the client should pass that payload back in the next request.
150
- */
151
- payload?: Record<string, any>;
152
-
153
- /**
154
- * Optional reference ID from the calling system. When the MJ API proxies a request
155
- * to Skip via SkipProxyAgent, this contains the MJ-side Agent Run ID for cross-system
156
- * correlation and debugging.
157
- */
158
- externalReferenceID?: string;
159
- }
160
-
161
- /**
162
- * Result from a Skip API call
163
- */
164
- export interface SkipCallResult {
165
- /**
166
- * Whether the call was successful
167
- */
168
- success: boolean;
169
-
170
- /**
171
- * The final Skip API response
172
- */
173
- response?: SkipAPIResponse;
174
-
175
- /**
176
- * Response phase (analysis_complete, clarifying_question, status_update)
177
- */
178
- responsePhase?: string;
179
-
180
- /**
181
- * Error message if failed
182
- */
183
- error?: string;
184
-
185
- /**
186
- * All streaming responses received (including intermediate status updates)
187
- */
188
- allResponses?: any[];
189
- }
190
-
191
- /**
192
- * Shape of a single SSE event received from the Skip API stream.
193
- * Skip sends two formats:
194
- * - Wrapped: `{type: 'status_update'|'streaming'|'complete', value: SkipAPIResponse}`
195
- * - Flat (queue): `{responsePhase: 'queued'|'error', message: '...', error: '...'}`
196
- */
197
- interface SkipStreamMessage {
198
- type?: string;
199
- value?: SkipAPIResponse;
200
- responsePhase?: string;
201
- message?: string;
202
- error?: string;
203
- }
204
-
205
- /**
206
- * Skip TypeScript SDK
207
- * Provides a clean interface for calling the Skip SaaS API
208
- */
209
- export class SkipSDK {
210
- private config: SkipSDKConfig;
211
-
212
- /**
213
- * The metadata provider this SDK instance is bound to. Set via the constructor
214
- * config or the `Provider` setter. Falls back to the global `Metadata.Provider`
215
- * when not set — multi-tenant servers should always supply an explicit provider
216
- * so each request reaches its own database connection.
217
- */
218
- public get Provider(): IMetadataProvider {
219
- return this.config.provider ?? (new Metadata() as unknown as IMetadataProvider);
220
- }
221
- public set Provider(value: IMetadataProvider | null) {
222
- this.config.provider = value ?? undefined;
223
- }
224
-
225
- // Static cache for Skip entities (shared across all instances)
226
- private static __skipEntitiesCache$: BehaviorSubject<Promise<EntityInfo[]> | null> = new BehaviorSubject<Promise<EntityInfo[]> | null>(null);
227
- private static __lastRefreshTime: number = 0;
228
-
229
- constructor(config?: SkipSDKConfig) {
230
- // Use provided config or fall back to MJ server config
231
- this.config = {
232
- apiUrl: config?.apiUrl || configInfo.askSkip?.chatURL,
233
- apiKey: config?.apiKey || configInfo.askSkip?.apiKey,
234
- organizationId: config?.organizationId || configInfo.askSkip?.orgID,
235
- organizationInfo: config?.organizationInfo || configInfo.askSkip?.organizationInfo
236
- };
237
- }
238
-
239
- /**
240
- * Call the Skip chat API
241
- */
242
- async chat(options: SkipCallOptions): Promise<SkipCallResult> {
243
- LogStatus(`[SkipSDK] Sending request to Skip API: ${this.config.apiUrl}`);
244
-
245
- try {
246
- // Build the Skip API request
247
- const skipRequest = await this.buildSkipRequest(options);
248
-
249
- // Call Skip API with SSE streaming support
250
- const responses = await this.sendSSERequest(
251
- this.config.apiUrl,
252
- skipRequest,
253
- this.buildHeaders(),
254
- (streamMessage: SkipStreamMessage) => {
255
- // Handle streaming status updates
256
- // Queue messages come as flat objects: {responsePhase: 'queued'|'error', message: '...', error: '...'}
257
- // Skip API messages come wrapped: {type: 'status_update', value: {responsePhase: '...', messages: [...]}}
258
- if (streamMessage.type === 'status_update' && options.onStatusUpdate) {
259
- const statusContent = streamMessage.value?.messages?.[0]?.content;
260
- const responsePhase = streamMessage.value?.responsePhase;
261
- if (statusContent) {
262
- options.onStatusUpdate(statusContent, responsePhase);
263
- }
264
- } else if (streamMessage.responsePhase === SkipResponsePhase.queued && options.onStatusUpdate) {
265
- // Handle queue progress messages
266
- const statusContent = streamMessage.message;
267
- const responsePhase = streamMessage.responsePhase;
268
- if (statusContent) {
269
- options.onStatusUpdate(statusContent, responsePhase);
270
- }
271
- } else if (streamMessage.responsePhase === 'error') {
272
- // Queue error messages - log but don't throw (final response will handle error)
273
- // Note: 'error' is not in SkipResponsePhase enum - it's a queue-specific error state
274
- LogError(`[SkipSDK] Queue error: ${streamMessage.error || 'Unknown error'}`);
275
- if (options.onStatusUpdate) {
276
- options.onStatusUpdate(`Error: ${streamMessage.error || 'Request failed'}`, 'error');
277
- }
278
- }
279
- }
280
- );
281
-
282
- // The last response is the final one
283
- if (responses && responses.length > 0) {
284
- const finalResponse = responses[responses.length - 1].value as SkipAPIResponse;
285
-
286
- // Check if Skip itself reported an error (success: false in the response body)
287
- if (finalResponse.success === false) {
288
- const skipError = finalResponse.error || 'Skip API returned an error response';
289
- LogError(`[SkipSDK] Skip API error: ${skipError}`);
290
- return {
291
- success: false,
292
- response: finalResponse,
293
- responsePhase: finalResponse.responsePhase,
294
- error: skipError,
295
- allResponses: responses
296
- };
297
- }
298
-
299
- return {
300
- success: true,
301
- response: finalResponse,
302
- responsePhase: finalResponse.responsePhase,
303
- allResponses: responses
304
- };
305
- } else {
306
- return {
307
- success: false,
308
- error: 'No response received from Skip API'
309
- };
310
- }
311
-
312
- } catch (error) {
313
- LogError(`[SkipSDK] Error calling Skip API: ${error}`);
314
-
315
- // Provide user-friendly error messages for common failures
316
- const rawError = error instanceof Error ? error.message : String(error);
317
- let userFriendlyError = rawError;
318
- const errorStr = rawError.toLowerCase();
319
-
320
- if (errorStr.includes('stream error') || errorStr.includes('aborted') || errorStr.includes('econnreset')) {
321
- userFriendlyError = 'The Skip analysis service became unavailable during processing. Please try again.';
322
- } else if (errorStr.includes('econnrefused') || errorStr.includes('enotfound')) {
323
- userFriendlyError = 'Unable to connect to the Skip analysis service. The service may be temporarily unavailable.';
324
- } else if (errorStr.includes('timeout')) {
325
- userFriendlyError = 'The Skip analysis service took too long to respond. Please try again.';
326
- }
327
-
328
- return {
329
- success: false,
330
- error: userFriendlyError
331
- };
332
- }
333
- }
334
-
335
- /**
336
- * Build the Skip API request object
337
- */
338
- private async buildSkipRequest(options: SkipCallOptions): Promise<SkipAPIRequest> {
339
- const {
340
- messages,
341
- conversationId,
342
- dataContext,
343
- requestPhase = 'initial_request',
344
- contextUser,
345
- dataSource,
346
- includeEntities = true,
347
- includeQueries = true,
348
- includeNotes = true,
349
- includeRequests = false,
350
- forceEntityRefresh = false,
351
- includeCallbackAuth = true,
352
- payload,
353
- externalReferenceID
354
- } = options;
355
-
356
- // Build base request with metadata
357
- const baseRequest = await this.buildBaseRequest(
358
- contextUser,
359
- dataSource,
360
- includeEntities,
361
- includeQueries,
362
- includeNotes,
363
- includeRequests,
364
- forceEntityRefresh,
365
- includeCallbackAuth,
366
- { conversationId, requestPhase }
367
- );
368
-
369
- // Build artifacts for this conversation
370
- const artifacts = await this.buildArtifacts(contextUser, dataSource, conversationId);
371
-
372
- // Process messages: filter delegation messages and enrich with metadata
373
- const processedMessages = this.processMessages(messages);
374
-
375
- // Construct the full Skip API request
376
- const request: SkipAPIRequest = {
377
- messages: processedMessages,
378
- conversationID: conversationId,
379
- dataContext: dataContext ? CopyScalarsAndArrays(dataContext) as DataContext : undefined,
380
- requestPhase,
381
- artifacts,
382
- payload, // Pass through payload for incremental artifact building (e.g., PRD in progress)
383
- entities: baseRequest.entities || [],
384
- queries: baseRequest.queries || [],
385
- queryCatalog: baseRequest.queryCatalog,
386
- notes: baseRequest.notes,
387
- noteTypes: baseRequest.noteTypes,
388
- userEmail: baseRequest.userEmail,
389
- organizationID: baseRequest.organizationID,
390
- organizationInfo: baseRequest.organizationInfo,
391
- apiKeys: baseRequest.apiKeys,
392
- callingServerURL: baseRequest.callingServerURL,
393
- callingServerAPIKey: baseRequest.callingServerAPIKey,
394
- callingServerAccessToken: baseRequest.callingServerAccessToken,
395
- externalReferenceID,
396
- databasePlatform: getDbType()
397
- };
398
-
399
- return request;
400
- }
401
-
402
- /**
403
- * Build base request with metadata, API keys, and callback auth
404
- */
405
- private async buildBaseRequest(
406
- contextUser: UserInfo,
407
- dataSource: mssql.ConnectionPool,
408
- includeEntities: boolean,
409
- includeQueries: boolean,
410
- includeNotes: boolean,
411
- includeRequests: boolean,
412
- forceEntityRefresh: boolean,
413
- includeCallbackAuth: boolean,
414
- additionalTokenInfo: any = {}
415
- ): Promise<Partial<SkipAPIRequest>> {
416
- const entities = includeEntities ? await this.buildEntities(forceEntityRefresh) : [];
417
- const queries = includeQueries ? this.buildQueries() : [];
418
- // Always build the lightweight query catalog for collision detection,
419
- // regardless of whether full queries are included
420
- const queryCatalog = this.buildQueryCatalog();
421
- const { notes, noteTypes } = includeNotes ? await this.buildAgentNotes(contextUser) : { notes: [], noteTypes: [] };
422
- // Note: requests would be built here if includeRequests is true
423
-
424
- // Setup access token for Skip callbacks if needed
425
- let accessToken: GetDataAccessToken | undefined;
426
- if (includeCallbackAuth) {
427
- const tokenInfo = {
428
- type: 'skip_api_request',
429
- userEmail: contextUser.Email,
430
- userName: contextUser.Name,
431
- userID: contextUser.ID,
432
- ...additionalTokenInfo
433
- };
434
-
435
- accessToken = registerAccessToken(
436
- undefined,
437
- 1000 * 60 * 10, // 10 minutes
438
- tokenInfo
439
- );
440
- }
441
-
442
- return {
443
- entities,
444
- queries,
445
- queryCatalog,
446
- notes,
447
- noteTypes,
448
- userEmail: contextUser.Email,
449
- organizationID: this.config.organizationId,
450
- organizationInfo: this.config.organizationInfo,
451
- apiKeys: this.buildAPIKeys(),
452
- callingServerURL: accessToken ? (publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}`) : undefined,
453
- callingServerAPIKey: accessToken ? callbackAPIKey : undefined,
454
- callingServerAccessToken: accessToken ? accessToken.Token : undefined
455
- };
456
- }
457
-
458
- /**
459
- * Build entity metadata for Skip
460
- * Copied from AskSkipResolver.BuildSkipEntities - uses cached metadata with refresh logic
461
- */
462
- private async buildEntities(forceRefresh: boolean, refreshIntervalMinutes: number = 15): Promise<EntityInfo[]> {
463
- try {
464
- const now = Date.now();
465
- const cacheExpired = (now - SkipSDK.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
466
-
467
- // If force refresh is requested OR cache expired OR cache is empty, refresh
468
- if (forceRefresh || cacheExpired || SkipSDK.__skipEntitiesCache$.value === null) {
469
- LogStatus(`[SkipSDK] Refreshing Skip entities cache (force: ${forceRefresh}, expired: ${cacheExpired})`);
470
- const newData = this.refreshSkipEntities();
471
- SkipSDK.__skipEntitiesCache$.next(newData);
472
- }
473
-
474
- return SkipSDK.__skipEntitiesCache$.pipe(take(1)).toPromise();
475
- }
476
- catch (e) {
477
- LogError(`[SkipSDK] buildEntities error: ${e}`);
478
- return [];
479
- }
480
- }
481
-
482
- /**
483
- * Build saved queries for Skip
484
- */
485
- private buildQueries(status: "Pending" | "In-Review" | "Approved" | "Rejected" | "Obsolete" = 'Approved'): SkipQueryInfo[] {
486
- const qe = QueryEngine.Instance;
487
- const approvedQueries = qe.Queries.filter((q) => q.Status === status);
488
-
489
- return approvedQueries.map((q) => ({
490
- ID: q.ID,
491
- Name: q.Name,
492
- Description: q.Description,
493
- Category: q.Category,
494
- CategoryPath: this.buildQueryCategoryPath(qe, q.CategoryID),
495
- CategoryID: q.CategoryID,
496
- SQL: q.SQL,
497
- Status: q.Status,
498
- QualityRank: q.QualityRank,
499
- Reusable: q.Reusable,
500
- EmbeddingVector: q.EmbeddingVector,
501
- EmbeddingModelID: q.EmbeddingModelID,
502
- EmbeddingModelName: q.EmbeddingModel,
503
- TechnicalDescription: q.TechnicalDescription,
504
- Fields: qe.GetQueryFields(q.ID).map((f) => ({
505
- ID: f.ID,
506
- QueryID: f.QueryID,
507
- Name: f.Name,
508
- Description: f.Description,
509
- Sequence: f.Sequence,
510
- SQLBaseType: f.SQLBaseType,
511
- SQLFullType: f.SQLFullType,
512
- SourceEntityID: f.SourceEntityID,
513
- SourceEntity: f.SourceEntity,
514
- SourceFieldName: f.SourceFieldName,
515
- IsComputed: f.IsComputed,
516
- ComputationDescription: f.ComputationDescription,
517
- IsSummary: f.IsSummary,
518
- SummaryDescription: f.SummaryDescription
519
- })),
520
- Parameters: qe.GetQueryParameters(q.ID).map((p) => ({
521
- ID: p.ID,
522
- QueryID: p.QueryID,
523
- Name: p.Name,
524
- Description: p.Description,
525
- Type: p.Type,
526
- IsRequired: p.IsRequired,
527
- DefaultValue: p.DefaultValue,
528
- SampleValue: p.SampleValue,
529
- ValidationFilters: p.ValidationFilters
530
- })),
531
- Entities: qe.QueryEntities.filter(e => UUIDsEqual(e.QueryID, q.ID)).map((e) => ({
532
- ID: e.ID,
533
- QueryID: e.QueryID,
534
- EntityID: e.EntityID,
535
- Entity: e.Entity
536
- })),
537
- SQLDialectID: q.SQLDialectID,
538
- UsesTemplate: q.UsesTemplate,
539
- IsApproved: q.IsApproved,
540
- CacheEnabled: q.CacheEnabled,
541
- CacheMaxSize: q.CacheMaxSize,
542
- CacheTTLMinutes: q.CacheTTLMinutes,
543
- CacheValidationSQL: q.CacheValidationSQL
544
- }));
545
- }
546
-
547
- /**
548
- * Recursively build category path for a query
549
- */
550
- private buildQueryCategoryPath(qe: QueryEngine, categoryID: string): string {
551
- const cat = qe.Categories.find((c) => UUIDsEqual(c.ID, categoryID));
552
- if (!cat) return '';
553
- if (!cat.ParentID) return cat.Name;
554
- const parentPath = this.buildQueryCategoryPath(qe, cat.ParentID);
555
- return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
556
- }
557
-
558
- /**
559
- * Build a lightweight catalog of ALL query names and category paths (regardless of status).
560
- * Always called regardless of includeQueries, so collision detection
561
- * has accurate data even when full query metadata is not transmitted.
562
- * Includes all statuses because the database enforces name+category uniqueness
563
- * across all queries, not just approved ones.
564
- */
565
- private buildQueryCatalog(): SkipQueryCatalogEntry[] {
566
- const qe = QueryEngine.Instance;
567
-
568
- return qe.Queries.map((q) => ({
569
- Name: q.Name,
570
- CategoryPath: this.buildQueryCategoryPath(qe, q.CategoryID)
571
- }));
572
- }
573
-
574
- /**
575
- * Build agent notes for Skip
576
- */
577
- private async buildAgentNotes(contextUser: UserInfo): Promise<{ notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[] }> {
578
- // TODO: Implement agent notes building
579
- // This would query AIAgentNote entities and convert to SkipAPIAgentNote format
580
- // For now, returning empty arrays
581
- return { notes: [], noteTypes: [] };
582
- }
583
-
584
- /**
585
- * Build artifacts for a conversation using optimized query
586
- * Uses GetConversationArtifactsForAgent query which joins through ConversationDetailArtifact
587
- * to get artifacts that were outputs from Skip agent's conversation details
588
- */
589
- private async buildArtifacts(contextUser: UserInfo, dataSource: mssql.ConnectionPool, conversationId: string): Promise<SkipAPIArtifact[]> {
590
- try {
591
- const rq = new RunQuery();
592
-
593
- // Ensure AIEngine is configured and get Skip agent ID
594
- await AIEngine.Instance.Config(false, contextUser);
595
- const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
596
- const skipAgentId = skipAgent?.ID;
597
-
598
- if (!skipAgentId) {
599
- LogError('[SkipSDK] Skip agent not found in AIEngine');
600
- }
601
-
602
- // Use optimized query that replaces 4 RunView calls with 1 query
603
- // This query includes Configuration field needed for component spec extraction
604
- // Filter by Skip agent ID to only get artifacts created by Skip (not delegation agents)
605
- const result = await rq.RunQuery({
606
- QueryName: 'GetConversationArtifactsForAgent',
607
- CategoryPath: 'MJ/Conversations',
608
- Parameters: {
609
- ConversationID: conversationId,
610
- AgentID: skipAgentId // Filter to only artifacts created by Skip agent
611
- }
612
- }, contextUser);
613
-
614
- if (!result.Success || !result.Results || result.Results.length === 0) {
615
- return [];
616
- }
617
-
618
- // Query returns flat result set: one row per artifact version
619
- // Group by ArtifactID to build SkipAPIArtifact objects with their versions
620
- const artifactMap = new Map<string, {
621
- artifact: any,
622
- artifactType: SkipAPIArtifactType,
623
- versions: SkipAPIArtifactVersion[]
624
- }>();
625
-
626
- // Process each row (represents one version)
627
- for (const row of result.Results) {
628
- const artifactId = row.ArtifactID;
629
-
630
- // Initialize artifact entry if not exists
631
- if (!artifactMap.has(artifactId)) {
632
- // Map database sharingScope values to SkipAPIArtifact expected values
633
- let sharingScope: 'None' | 'SpecificUsers' | 'Everyone' | 'Public' = 'None';
634
- const dbSharingScope = (row.SharingScope || '').toLowerCase();
635
- if (dbSharingScope === 'always' || dbSharingScope === 'everyone') {
636
- sharingScope = 'Everyone';
637
- } else if (dbSharingScope === 'public') {
638
- sharingScope = 'Public';
639
- } else if (dbSharingScope === 'specific users' || dbSharingScope === 'specificusers') {
640
- sharingScope = 'SpecificUsers';
641
- }
642
-
643
- artifactMap.set(artifactId, {
644
- artifact: {
645
- id: artifactId,
646
- conversationId: conversationId,
647
- name: row.ArtifactName,
648
- description: row.ArtifactDescription || '',
649
- sharingScope: sharingScope,
650
- comments: row.ArtifactComments || '',
651
- createdAt: new Date(row.ArtifactCreatedAt),
652
- updatedAt: new Date(row.ArtifactUpdatedAt)
653
- },
654
- artifactType: {
655
- id: row.ArtifactTypeID,
656
- name: row.ArtifactTypeName,
657
- description: row.ArtifactTypeDescription,
658
- contentType: row.ArtifactTypeContentType,
659
- enabled: true,
660
- createdAt: new Date(row.ArtifactTypeCreatedAt),
661
- updatedAt: new Date(row.ArtifactTypeUpdatedAt)
662
- },
663
- versions: []
664
- });
665
- }
666
-
667
- // Add this version to the artifact
668
- const entry = artifactMap.get(artifactId)!;
669
- entry.versions.push({
670
- id: row.VersionID,
671
- artifactId: artifactId,
672
- conversationDetailID: row.ConversationDetailID, // Direct from join table!
673
- version: row.Version,
674
- configuration: row.Configuration || '',
675
- content: row.Content || '',
676
- comments: row.VersionComments || '',
677
- createdAt: new Date(row.VersionCreatedAt),
678
- updatedAt: new Date(row.VersionUpdatedAt)
679
- });
680
- }
681
-
682
- // Convert map to SkipAPIArtifact array
683
- const artifacts: SkipAPIArtifact[] = Array.from(artifactMap.values()).map(entry => ({
684
- ...entry.artifact,
685
- artifactType: entry.artifactType,
686
- versions: entry.versions
687
- }));
688
-
689
- // Also include INPUT artifacts (e.g., user-captured Data Snapshots
690
- // attached to messages via the Analyze button or
691
- // client:capture-data-snapshot actionable command). The query above
692
- // only returns Direction='Output' artifacts produced BY Skip — but
693
- // Skip also needs to see artifacts the user gave it as input.
694
- const inputArtifacts = await this.buildInputArtifacts(contextUser, conversationId, artifactMap);
695
- if (inputArtifacts.length > 0) {
696
- artifacts.push(...inputArtifacts);
697
- }
698
-
699
- return artifacts;
700
- } catch (error) {
701
- LogError(`Failed to build artifacts for conversation ${conversationId}: ${error}`);
702
- return [];
703
- }
704
- }
705
-
706
- /**
707
- * Fetch INPUT artifacts attached to user messages in this conversation
708
- * (Direction='Input' on ConversationDetailArtifact). Skip should see these
709
- * so it can use captured Data Snapshots, user-uploaded files, etc., in
710
- * its analysis.
711
- *
712
- * Deduplicates against `alreadyLoaded` (the Output artifacts) so the same
713
- * artifact ID doesn't appear twice if it was both produced and re-attached.
714
- */
715
- private async buildInputArtifacts(
716
- contextUser: UserInfo,
717
- conversationId: string,
718
- alreadyLoaded: Map<string, { artifact: any; artifactType: SkipAPIArtifactType; versions: SkipAPIArtifactVersion[] }>
719
- ): Promise<SkipAPIArtifact[]> {
720
- try {
721
- const rv = new RunView();
722
- // Pull conversation detail IDs in this conversation
723
- const detailsResult = await rv.RunView<MJConversationDetailEntity>({
724
- EntityName: 'MJ: Conversation Details',
725
- ExtraFilter: `ConversationID='${conversationId}'`,
726
- Fields: ['ID'],
727
- ResultType: 'simple',
728
- }, contextUser);
729
- const detailIds = detailsResult.Success && detailsResult.Results
730
- ? (detailsResult.Results as { ID: string }[]).map(r => r.ID)
731
- : [];
732
- if (detailIds.length === 0) return [];
733
-
734
- // Junction rows where Direction='Input' for those details
735
- const junctionResult = await rv.RunView({
736
- EntityName: 'MJ: Conversation Detail Artifacts',
737
- ExtraFilter: `ConversationDetailID IN ('${detailIds.join("','")}') AND Direction='Input'`,
738
- Fields: ['ArtifactVersionID', 'ConversationDetailID'],
739
- ResultType: 'simple',
740
- }, contextUser);
741
- const junctions = junctionResult.Success && junctionResult.Results
742
- ? (junctionResult.Results as { ArtifactVersionID: string; ConversationDetailID: string }[])
743
- : [];
744
- if (junctions.length === 0) return [];
745
-
746
- // Load each ArtifactVersion + its parent Artifact + ArtifactType
747
- const versionIds = [...new Set(junctions.map(j => j.ArtifactVersionID))];
748
- const versionsResult = await rv.RunView({
749
- EntityName: 'MJ: Artifact Versions',
750
- ExtraFilter: `ID IN ('${versionIds.join("','")}')`,
751
- ResultType: 'simple',
752
- }, contextUser);
753
- const versions = versionsResult.Success && versionsResult.Results
754
- ? (versionsResult.Results as Record<string, any>[])
755
- : [];
756
- if (versions.length === 0) return [];
757
-
758
- const artifactIds = [...new Set(versions.map(v => v.ArtifactID as string))];
759
- const artifactsResult = await rv.RunView({
760
- EntityName: 'MJ: Artifacts',
761
- ExtraFilter: `ID IN ('${artifactIds.join("','")}')`,
762
- ResultType: 'simple',
763
- }, contextUser);
764
- const artifactRows = artifactsResult.Success && artifactsResult.Results
765
- ? (artifactsResult.Results as Record<string, any>[])
766
- : [];
767
-
768
- const typeIds = [...new Set(artifactRows.map(a => a.TypeID as string))];
769
- const typesResult = await rv.RunView({
770
- EntityName: 'MJ: Artifact Types',
771
- ExtraFilter: `ID IN ('${typeIds.join("','")}')`,
772
- ResultType: 'simple',
773
- }, contextUser);
774
- const typeRows = typesResult.Success && typesResult.Results
775
- ? (typesResult.Results as Record<string, any>[])
776
- : [];
777
- const typeMap = new Map<string, Record<string, any>>(typeRows.map(t => [t.ID as string, t]));
778
-
779
- // Build a junction lookup: artifactVersionId -> conversationDetailId
780
- const versionToDetail = new Map(junctions.map(j => [j.ArtifactVersionID, j.ConversationDetailID]));
781
-
782
- // Build SkipAPIArtifact entries
783
- const inputArtifactMap = new Map<string, { artifact: any; artifactType: SkipAPIArtifactType; versions: SkipAPIArtifactVersion[] }>();
784
- for (const v of versions) {
785
- const aRow = artifactRows.find(a => UUIDsEqual(a.ID, v.ArtifactID));
786
- if (!aRow) continue;
787
- if (alreadyLoaded.has(aRow.ID as string)) continue; // dedup
788
-
789
- const typeRow = typeMap.get(aRow.TypeID as string);
790
- if (!typeRow) continue;
791
-
792
- let entry = inputArtifactMap.get(aRow.ID as string);
793
- if (!entry) {
794
- entry = {
795
- artifact: {
796
- id: aRow.ID,
797
- conversationId,
798
- name: aRow.Name,
799
- description: aRow.Description || '',
800
- sharingScope: 'None',
801
- comments: aRow.Comments || '',
802
- createdAt: new Date(aRow.__mj_CreatedAt),
803
- updatedAt: new Date(aRow.__mj_UpdatedAt),
804
- },
805
- artifactType: {
806
- id: typeRow.ID,
807
- name: typeRow.Name,
808
- description: typeRow.Description,
809
- contentType: typeRow.ContentType,
810
- enabled: true,
811
- createdAt: new Date(typeRow.__mj_CreatedAt),
812
- updatedAt: new Date(typeRow.__mj_UpdatedAt),
813
- },
814
- versions: [],
815
- };
816
- inputArtifactMap.set(aRow.ID as string, entry);
817
- }
818
- entry.versions.push({
819
- id: v.ID,
820
- artifactId: v.ArtifactID,
821
- conversationDetailID: versionToDetail.get(v.ID) ?? '',
822
- version: v.VersionNumber,
823
- configuration: v.Configuration || '',
824
- content: v.Content || '',
825
- comments: v.Comments || '',
826
- createdAt: new Date(v.__mj_CreatedAt),
827
- updatedAt: new Date(v.__mj_UpdatedAt),
828
- });
829
- }
830
-
831
- return Array.from(inputArtifactMap.values()).map(entry => ({
832
- ...entry.artifact,
833
- artifactType: entry.artifactType,
834
- versions: entry.versions,
835
- }));
836
- } catch (error) {
837
- LogError(`Failed to load input artifacts for conversation ${conversationId}: ${error}`);
838
- return [];
839
- }
840
- }
841
-
842
- /**
843
- * Build API keys for AI services
844
- */
845
- private buildAPIKeys(): SkipAPIRequestAPIKey[] {
846
- return [
847
- {
848
- vendorDriverName: 'OpenAILLM',
849
- apiKey: GetAIAPIKey('OpenAILLM')
850
- },
851
- {
852
- vendorDriverName: 'AnthropicLLM',
853
- apiKey: GetAIAPIKey('AnthropicLLM')
854
- },
855
- {
856
- vendorDriverName: 'GeminiLLM',
857
- apiKey: GetAIAPIKey('GeminiLLM')
858
- },
859
- {
860
- vendorDriverName: 'GroqLLM',
861
- apiKey: GetAIAPIKey('GroqLLM')
862
- },
863
- {
864
- vendorDriverName: 'MistralLLM',
865
- apiKey: GetAIAPIKey('MistralLLM')
866
- },
867
- {
868
- vendorDriverName: 'CerebrasLLM',
869
- apiKey: GetAIAPIKey('CerebrasLLM')
870
- }
871
- ];
872
- }
873
-
874
- /**
875
- * Build HTTP headers for Skip API requests
876
- */
877
- private buildHeaders(): Record<string, string> {
878
- return {
879
- 'x-api-key': this.config.apiKey || '',
880
- 'Content-Type': 'application/json'
881
- };
882
- }
883
-
884
- /**
885
- * Send an SSE-aware POST request to the Skip API.
886
- * Replaces sendPostRequest for SSE format: parses `data: {json}\n\n` events
887
- * instead of NDJSON `{json}\n` lines. This is required because Azure Container
888
- * Apps' Envoy proxy buffers NDJSON responses but streams SSE responses.
889
- */
890
- private async sendSSERequest(
891
- url: string,
892
- payload: SkipAPIRequest,
893
- headers: Record<string, string>,
894
- streamCallback?: (event: SkipStreamMessage) => void
895
- ): Promise<SkipStreamMessage[]> {
896
- // Gzip the request body
897
- const compressed = await new Promise<Buffer>((resolve, reject) => {
898
- gzipCompress(JSON.stringify(payload), (err, result) => {
899
- if (err) reject(err);
900
- else resolve(result);
901
- });
902
- });
903
-
904
- return new Promise((resolve, reject) => {
905
- try {
906
- const parsedUrl = new URL(url);
907
- const options = {
908
- hostname: parsedUrl.hostname,
909
- port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
910
- path: parsedUrl.pathname,
911
- method: 'POST' as const,
912
- headers: {
913
- 'Content-Type': 'application/json',
914
- 'Content-Encoding': 'gzip',
915
- ...headers
916
- }
917
- };
918
-
919
- const requestFn = parsedUrl.protocol === 'https:' ? httpsRequest : httpRequest;
920
- const events: SkipStreamMessage[] = [];
921
- let buffer = '';
922
- let streamEnded = false;
923
-
924
- const parseSSELine = (line: string): void => {
925
- if (line.trim() === '') return; // Skip empty lines (SSE event delimiters)
926
- if (!line.startsWith('data: ')) return; // Skip non-data SSE fields
927
- const jsonStr = line.slice(6);
928
- if (!jsonStr.trim()) return;
929
-
930
- try {
931
- const event = JSON.parse(jsonStr) as SkipStreamMessage;
932
- events.push(event);
933
- streamCallback?.(event);
934
- } catch (e) {
935
- LogError(`[SkipSDK] SSE parse error: ${e}`);
936
- }
937
- };
938
-
939
- const handleStreamEnd = (): void => {
940
- if (streamEnded) return;
941
- streamEnded = true;
942
- // Try to parse any remaining data in buffer
943
- if (buffer.trim()) {
944
- parseSSELine(buffer);
945
- }
946
- resolve(events);
947
- };
948
-
949
- const req = requestFn(options, (res) => {
950
- // Check for non-2xx HTTP status codes before attempting SSE parsing.
951
- // The Skip API returns JSON error bodies for auth/validation failures (401, 403, etc.)
952
- // which won't contain SSE `data:` lines, resulting in an empty events array
953
- // and the misleading "No response received from Skip API" error.
954
- if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
955
- let errorBody = '';
956
- res.on('data', (chunk: Buffer) => { errorBody += chunk.toString(); });
957
- res.on('end', () => {
958
- let errorMessage = `Skip API returned HTTP ${res.statusCode}`;
959
- try {
960
- const parsed = JSON.parse(errorBody);
961
- if (parsed.message) {
962
- errorMessage = parsed.message;
963
- } else if (parsed.error) {
964
- errorMessage = parsed.error;
965
- }
966
- } catch {
967
- // Non-JSON body — use raw text if available
968
- if (errorBody.trim()) {
969
- errorMessage += `: ${errorBody.trim().substring(0, 200)}`;
970
- }
971
- }
972
- LogError(`[SkipSDK] HTTP ${res.statusCode} from ${url}: ${errorMessage}`);
973
- reject(new Error(errorMessage));
974
- });
975
- return;
976
- }
977
-
978
- const gunzip = createGunzip();
979
- const stream = res.headers['content-encoding'] === 'gzip' ? res.pipe(gunzip) : res;
980
-
981
- stream.on('data', (chunk: Buffer) => {
982
- buffer += chunk.toString();
983
- let boundary: number;
984
- while ((boundary = buffer.indexOf('\n')) !== -1) {
985
- const line = buffer.substring(0, boundary);
986
- buffer = buffer.substring(boundary + 1);
987
- parseSSELine(line);
988
- }
989
- });
990
-
991
- stream.on('end', handleStreamEnd);
992
-
993
- stream.on('close', () => {
994
- if (!streamEnded) {
995
- LogError(`[SkipSDK] SSE stream closed prematurely for ${url}`);
996
- handleStreamEnd();
997
- }
998
- });
999
-
1000
- stream.on('error', (e: Error) => {
1001
- if (!streamEnded) {
1002
- LogError(`[SkipSDK] SSE stream error for ${url}: ${e.message}`);
1003
- reject(new Error(`SSE stream error: ${e.message}`));
1004
- }
1005
- });
1006
- });
1007
-
1008
- req.on('error', (e: Error) => {
1009
- LogError(`[SkipSDK] SSE request error for ${url}: ${e.message}`);
1010
- reject(new Error(`HTTP request failed to ${url}: ${e.message}`));
1011
- });
1012
-
1013
- req.write(compressed);
1014
- req.end();
1015
- } catch (e) {
1016
- LogError(`[SkipSDK] sendSSERequest error: ${e}`);
1017
- reject(e);
1018
- }
1019
- });
1020
- }
1021
-
1022
- /**
1023
- * Refreshes the Skip entities cache
1024
- * Rebuilds the entity information that is provided to Skip
1025
- * Refreshes the entity metadata cache. Filters entities by schema/scope config,
1026
- * filters fields by AI scope, and enriches field values from the database.
1027
- * Returns EntityInfo objects directly — no intermediate Skip-specific types needed.
1028
- */
1029
- private async refreshSkipEntities(): Promise<EntityInfo[]> {
1030
- try {
1031
- const md = this.Provider;
1032
-
1033
- // Diagnostic logging
1034
- LogStatus(`[SkipSDK.refreshSkipEntities] Total entities in metadata: ${md.Entities.length}`);
1035
- LogStatus(`[SkipSDK.refreshSkipEntities] Config excludeSchemas: ${JSON.stringify(configInfo.askSkip?.entitiesToSend?.excludeSchemas)}`);
1036
- LogStatus(`[SkipSDK.refreshSkipEntities] Config includeEntitiesFromExcludedSchemas: ${JSON.stringify(configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas)}`);
1037
-
1038
- const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
1039
- .map((e) => e.trim().toLowerCase());
1040
-
1041
- // Get the list of entities
1042
- const entities = md.Entities.filter((e) => {
1043
- if (!(configInfo.askSkip?.entitiesToSend?.excludeSchemas ?? []).includes(e.SchemaName) ||
1044
- skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
1045
- const sd = e.ScopeDefault?.trim();
1046
- if (sd && sd.length > 0) {
1047
- const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
1048
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai') || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase());
1049
- }
1050
- else {
1051
- return true; // no scope, so include it
1052
- }
1053
- }
1054
- return false;
1055
- });
1056
-
1057
- LogStatus(`[SkipSDK.refreshSkipEntities] Filtered entities count: ${entities.length}`);
1058
- if (entities.length === 0) {
1059
- LogError(`[SkipSDK.refreshSkipEntities] WARNING: No entities passed filtering! This will result in empty Skip entities list.`);
1060
- }
1061
-
1062
- // Build enriched EntityInfo objects with filtered fields and packed values
1063
- const result = await Promise.all(entities.map((e) => this.buildEntityForSkip(e)));
1064
-
1065
- LogStatus(`[SkipSDK.refreshSkipEntities] Successfully packed ${result.length} entities for Skip`);
1066
-
1067
- SkipSDK.__lastRefreshTime = Date.now();
1068
- return result;
1069
- }
1070
- catch (e) {
1071
- LogError(`[SkipSDK] refreshSkipEntities error: ${e}`);
1072
- return [];
1073
- }
1074
- }
1075
-
1076
- /**
1077
- * Builds an EntityInfo object for Skip, filtering fields by AI scope and
1078
- * enriching field values from the database. Returns a new EntityInfo
1079
- * constructed from a plain object so it serializes cleanly via toJSON().
1080
- */
1081
- private async buildEntityForSkip(e: EntityInfo): Promise<EntityInfo> {
1082
- try {
1083
- // Filter fields by scope (only include fields visible to AI)
1084
- const filteredFields = e.Fields.filter(f => {
1085
- const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
1086
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
1087
- });
1088
-
1089
- // Enrich each field with packed possible values
1090
- const enrichedFields = await Promise.all(filteredFields.map(f => this.enrichFieldValues(f)));
1091
-
1092
- // Clone the entity via toJSON, then swap in filtered+enriched fields and Skip-specific
1093
- // Active-only organic keys. Any future EntityInfo properties flow through automatically.
1094
- return new EntityInfo({
1095
- ...e.toJSON(),
1096
- Fields: enrichedFields,
1097
- OrganicKeys: e.OrganicKeys.filter(ok => ok.Status === 'Active'),
1098
- });
1099
- }
1100
- catch (err) {
1101
- LogError(`[SkipSDK] buildEntityForSkip error: ${err}`);
1102
- return null;
1103
- }
1104
- }
1105
-
1106
- /**
1107
- * Enriches a field's EntityFieldValues with possible values from the database.
1108
- * Returns a plain object that can be used to construct an EntityFieldInfo.
1109
- */
1110
- private async enrichFieldValues(f: EntityFieldInfo): Promise<Record<string, unknown>> {
1111
- return {
1112
- ...f.toJSON(),
1113
- EntityFieldValues: await this.packFieldValues(f),
1114
- };
1115
- }
1116
-
1117
- /**
1118
- * Packs possible values for an entity field based on the ValuesToPackWithSchema setting.
1119
- * Returns EntityFieldValueInfo-compatible objects.
1120
- */
1121
- private async packFieldValues(f: EntityFieldInfo): Promise<EntityFieldValueInfo[]> {
1122
- try {
1123
- if (f.ValuesToPackWithSchema === 'None') {
1124
- return [];
1125
- }
1126
- else if (f.ValuesToPackWithSchema === 'All') {
1127
- return await this.getFieldDistinctValues(f);
1128
- }
1129
- else if (f.ValuesToPackWithSchema === 'Auto') {
1130
- if (f.ValueListTypeEnum === 'List') {
1131
- return f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
1132
- }
1133
- else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
1134
- const values = await this.getFieldDistinctValues(f);
1135
- if (!values || values.length === 0) {
1136
- return f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
1137
- }
1138
- else {
1139
- const fromEntityFieldValues = f.EntityFieldValues.map((v) => new EntityFieldValueInfo({ Value: v.Value, Code: v.Value }));
1140
- return [...new Set([...fromEntityFieldValues, ...values])];
1141
- }
1142
- }
1143
- }
1144
- return [];
1145
- }
1146
- catch (e) {
1147
- LogError(`[SkipSDK] packFieldValues error: ${e}`);
1148
- return [];
1149
- }
1150
- }
1151
-
1152
- /**
1153
- * Gets distinct values for a field from the database.
1154
- * Returns EntityFieldValueInfo objects.
1155
- */
1156
- private async getFieldDistinctValues(f: EntityFieldInfo): Promise<EntityFieldValueInfo[]> {
1157
- try {
1158
- // Use this SDK instance's bound provider so multi-tenant servers route the SQL
1159
- // through the right connection. ExecuteSQL works on both SQL Server and PostgreSQL.
1160
- const provider = this.Provider as unknown as DatabaseProviderBase;
1161
- const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
1162
- const rows = await provider.ExecuteSQL<Record<string, unknown>>(sql);
1163
- if (!rows || rows.length === 0) {
1164
- return [];
1165
- }
1166
- return rows.map((r) => new EntityFieldValueInfo({ Value: r[f.Name] as string, Code: r[f.Name] as string }));
1167
- }
1168
- catch (e) {
1169
- LogError(`[SkipSDK] getFieldDistinctValues error: ${e}`);
1170
- return [];
1171
- }
1172
- }
1173
-
1174
- /**
1175
- * Process messages: filter delegation messages and add metadata fields
1176
- * Messages coming in should already have conversationDetailID if they exist in the database
1177
- */
1178
- private processMessages(messages: SkipMessage[]): SkipMessage[] {
1179
- // Filter out delegation messages (administrative messages that shouldn't go to Skip)
1180
- const filteredMessages = messages.filter(msg => !this.isDelegationMessage(msg.content));
1181
-
1182
- // Enrich messages with default metadata if not already present
1183
- return filteredMessages.map(msg => ({
1184
- ...msg,
1185
- // Add default metadata fields if not already present
1186
- // Messages from DB already have conversationDetailID, temp messages get temp-X
1187
- hiddenToUser: msg.hiddenToUser ?? false,
1188
- userRating: msg.userRating ?? null,
1189
- userFeedback: msg.userFeedback ?? null,
1190
- reflectionInsights: msg.reflectionInsights ?? null,
1191
- summaryOfEarlierConveration: msg.summaryOfEarlierConveration ?? null
1192
- }));
1193
- }
1194
-
1195
- /**
1196
- * Check if a message is a delegation message that should be filtered out
1197
- * Uses flexible pattern matching to detect variations of delegation messages
1198
- */
1199
- private isDelegationMessage(content: string): boolean {
1200
- if (!content) return false;
1201
-
1202
- const lowerContent = content.toLowerCase();
1203
-
1204
- // Check for both "delegating" or "delegate" AND "skip" in any order
1205
- const hasDelegatingOrDelegate = lowerContent.includes('delegating') || lowerContent.includes('delegate');
1206
- const hasSkip = lowerContent.includes('skip');
1207
-
1208
- return hasDelegatingOrDelegate && hasSkip;
1209
- }
1210
- }