@memberjunction/server 5.44.0 → 5.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agentSessions/ReturningVisitorRecap.d.ts +39 -0
- package/dist/agentSessions/ReturningVisitorRecap.d.ts.map +1 -0
- package/dist/agentSessions/ReturningVisitorRecap.js +198 -0
- package/dist/agentSessions/ReturningVisitorRecap.js.map +1 -0
- package/dist/agentSessions/SessionJanitor.d.ts +13 -0
- package/dist/agentSessions/SessionJanitor.d.ts.map +1 -1
- package/dist/agentSessions/SessionJanitor.js +65 -7
- package/dist/agentSessions/SessionJanitor.js.map +1 -1
- package/dist/agentSessions/SessionManager.d.ts.map +1 -1
- package/dist/agentSessions/SessionManager.js +37 -0
- package/dist/agentSessions/SessionManager.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.d.ts +10 -0
- package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkRouter.js +16 -0
- package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.d.ts +7 -0
- package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -1
- package/dist/auth/magicLink/MagicLinkService.js +13 -3
- package/dist/auth/magicLink/MagicLinkService.js.map +1 -1
- package/dist/auth/magicLink/index.d.ts +1 -1
- package/dist/auth/magicLink/index.d.ts.map +1 -1
- package/dist/auth/magicLink/index.js +1 -1
- package/dist/auth/magicLink/index.js.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts +38 -1
- package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -1
- package/dist/auth/magicLink/magicLinkCore.js +42 -19
- package/dist/auth/magicLink/magicLinkCore.js.map +1 -1
- package/dist/auth/magicLink/types.d.ts +26 -0
- package/dist/auth/magicLink/types.d.ts.map +1 -1
- package/dist/config.d.ts +1883 -144
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +160 -36
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +45 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +522 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2860 -0
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +13 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +22 -0
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +4 -0
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -3
- package/dist/index.js.map +1 -1
- package/dist/integration/CustomColumnPromoter.d.ts.map +1 -1
- package/dist/integration/CustomColumnPromoter.js +6 -0
- package/dist/integration/CustomColumnPromoter.js.map +1 -1
- package/dist/realtimeWidget/WidgetRouter.d.ts +26 -0
- package/dist/realtimeWidget/WidgetRouter.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetRouter.js +182 -0
- package/dist/realtimeWidget/WidgetRouter.js.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts +265 -0
- package/dist/realtimeWidget/WidgetSessionService.d.ts.map +1 -0
- package/dist/realtimeWidget/WidgetSessionService.js +477 -0
- package/dist/realtimeWidget/WidgetSessionService.js.map +1 -0
- package/dist/realtimeWidget/host-identity.d.ts +40 -0
- package/dist/realtimeWidget/host-identity.d.ts.map +1 -0
- package/dist/realtimeWidget/host-identity.js +58 -0
- package/dist/realtimeWidget/host-identity.js.map +1 -0
- package/dist/realtimeWidget/index.d.ts +10 -0
- package/dist/realtimeWidget/index.d.ts.map +1 -0
- package/dist/realtimeWidget/index.js +9 -0
- package/dist/realtimeWidget/index.js.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts +76 -0
- package/dist/realtimeWidget/visitorIdentity.d.ts.map +1 -0
- package/dist/realtimeWidget/visitorIdentity.js +202 -0
- package/dist/realtimeWidget/visitorIdentity.js.map +1 -0
- package/dist/realtimeWidget/widgetCore.d.ts +106 -0
- package/dist/realtimeWidget/widgetCore.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetCore.js +173 -0
- package/dist/realtimeWidget/widgetCore.js.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts +51 -0
- package/dist/realtimeWidget/widgetGuestElevation.d.ts.map +1 -0
- package/dist/realtimeWidget/widgetGuestElevation.js +79 -0
- package/dist/realtimeWidget/widgetGuestElevation.js.map +1 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts +7 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +31 -8
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityPermissionResolver.js +1 -2
- package/dist/resolvers/EntityPermissionResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts +9 -6
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +15 -13
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts +10 -0
- package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
- package/dist/resolvers/RealtimeClientSessionResolver.js +47 -3
- package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts +27 -0
- package/dist/resolvers/RingCentralTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js +87 -0
- package/dist/resolvers/RingCentralTelephonyResolver.js.map +1 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts +9 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +20 -4
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +2 -0
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/TeamsMeetingsResolver.d.ts +28 -0
- package/dist/resolvers/TeamsMeetingsResolver.d.ts.map +1 -0
- package/dist/resolvers/TeamsMeetingsResolver.js +91 -0
- package/dist/resolvers/TeamsMeetingsResolver.js.map +1 -0
- package/dist/resolvers/TelephonyResolver.d.ts +26 -0
- package/dist/resolvers/TelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/TelephonyResolver.js +86 -0
- package/dist/resolvers/TelephonyResolver.js.map +1 -0
- package/dist/resolvers/TestQuerySQLResolver.d.ts +1 -1
- package/dist/resolvers/TestQuerySQLResolver.d.ts.map +1 -1
- package/dist/resolvers/TestQuerySQLResolver.js +2 -3
- package/dist/resolvers/TestQuerySQLResolver.js.map +1 -1
- package/dist/resolvers/VonageTelephonyResolver.d.ts +26 -0
- package/dist/resolvers/VonageTelephonyResolver.d.ts.map +1 -0
- package/dist/resolvers/VonageTelephonyResolver.js +86 -0
- package/dist/resolvers/VonageTelephonyResolver.js.map +1 -0
- package/dist/rest/MediaStreamHandler.js +4 -1
- package/dist/rest/MediaStreamHandler.js.map +1 -1
- package/dist/telephony/RingCentralTelephonyService.d.ts +115 -0
- package/dist/telephony/RingCentralTelephonyService.d.ts.map +1 -0
- package/dist/telephony/RingCentralTelephonyService.js +262 -0
- package/dist/telephony/RingCentralTelephonyService.js.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts +40 -0
- package/dist/telephony/TeamsMeetingsRouter.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsRouter.js +96 -0
- package/dist/telephony/TeamsMeetingsRouter.js.map +1 -0
- package/dist/telephony/TeamsMeetingsService.d.ts +113 -0
- package/dist/telephony/TeamsMeetingsService.d.ts.map +1 -0
- package/dist/telephony/TeamsMeetingsService.js +196 -0
- package/dist/telephony/TeamsMeetingsService.js.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts +36 -0
- package/dist/telephony/TwilioTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyRouter.js +143 -0
- package/dist/telephony/TwilioTelephonyRouter.js.map +1 -0
- package/dist/telephony/TwilioTelephonyService.d.ts +95 -0
- package/dist/telephony/TwilioTelephonyService.d.ts.map +1 -0
- package/dist/telephony/TwilioTelephonyService.js +193 -0
- package/dist/telephony/TwilioTelephonyService.js.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts +41 -0
- package/dist/telephony/VonageTelephonyRouter.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyRouter.js +201 -0
- package/dist/telephony/VonageTelephonyRouter.js.map +1 -0
- package/dist/telephony/VonageTelephonyService.d.ts +90 -0
- package/dist/telephony/VonageTelephonyService.d.ts.map +1 -0
- package/dist/telephony/VonageTelephonyService.js +191 -0
- package/dist/telephony/VonageTelephonyService.js.map +1 -0
- package/dist/telephony/calendar-scheduler.d.ts +67 -0
- package/dist/telephony/calendar-scheduler.d.ts.map +1 -0
- package/dist/telephony/calendar-scheduler.js +133 -0
- package/dist/telephony/calendar-scheduler.js.map +1 -0
- package/dist/telephony/index.d.ts +29 -0
- package/dist/telephony/index.d.ts.map +1 -0
- package/dist/telephony/index.js +38 -0
- package/dist/telephony/index.js.map +1 -0
- package/dist/telephony/media-upgrade-router.d.ts +33 -0
- package/dist/telephony/media-upgrade-router.d.ts.map +1 -0
- package/dist/telephony/media-upgrade-router.js +56 -0
- package/dist/telephony/media-upgrade-router.js.map +1 -0
- package/dist/telephony/ringcentral-runtime.d.ts +14 -0
- package/dist/telephony/ringcentral-runtime.d.ts.map +1 -0
- package/dist/telephony/ringcentral-runtime.js +18 -0
- package/dist/telephony/ringcentral-runtime.js.map +1 -0
- package/dist/telephony/teams-meetings-runtime.d.ts +16 -0
- package/dist/telephony/teams-meetings-runtime.d.ts.map +1 -0
- package/dist/telephony/teams-meetings-runtime.js +20 -0
- package/dist/telephony/teams-meetings-runtime.js.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts +69 -0
- package/dist/telephony/teamsAcsMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/teamsAcsMediaRegistry.js +118 -0
- package/dist/telephony/teamsAcsMediaRegistry.js.map +1 -0
- package/dist/telephony/telephony-runtime.d.ts +14 -0
- package/dist/telephony/telephony-runtime.d.ts.map +1 -0
- package/dist/telephony/telephony-runtime.js +18 -0
- package/dist/telephony/telephony-runtime.js.map +1 -0
- package/dist/telephony/twilioMediaRegistry.d.ts +60 -0
- package/dist/telephony/twilioMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/twilioMediaRegistry.js +106 -0
- package/dist/telephony/twilioMediaRegistry.js.map +1 -0
- package/dist/telephony/vonage-runtime.d.ts +14 -0
- package/dist/telephony/vonage-runtime.d.ts.map +1 -0
- package/dist/telephony/vonage-runtime.js +18 -0
- package/dist/telephony/vonage-runtime.js.map +1 -0
- package/dist/telephony/vonageMediaRegistry.d.ts +78 -0
- package/dist/telephony/vonageMediaRegistry.d.ts.map +1 -0
- package/dist/telephony/vonageMediaRegistry.js +158 -0
- package/dist/telephony/vonageMediaRegistry.js.map +1 -0
- package/package.json +88 -83
- package/src/__tests__/RunAIAgentResolver.streaming.test.ts +128 -0
- package/src/__tests__/magicLink.test.ts +53 -3
- package/src/__tests__/search-knowledge-tags.test.ts +9 -9
- package/src/__tests__/teams-meetings-service.test.ts +65 -0
- package/src/__tests__/teamsAcsMediaRegistry.test.ts +82 -0
- package/src/__tests__/twilio-telephony-service.test.ts +23 -0
- package/src/__tests__/twilioMediaRegistry.test.ts +103 -0
- package/src/__tests__/vonageMediaRegistry.test.ts +180 -0
- package/src/__tests__/widget.test.ts +204 -0
- package/src/__tests__/widgetGuestElevation.test.ts +57 -0
- package/src/__tests__/widgetHostIdentity.test.ts +117 -0
- package/src/agentSessions/ReturningVisitorRecap.ts +242 -0
- package/src/agentSessions/SessionJanitor.ts +66 -6
- package/src/agentSessions/SessionManager.ts +38 -0
- package/src/auth/magicLink/MagicLinkRouter.ts +17 -0
- package/src/auth/magicLink/MagicLinkService.ts +13 -3
- package/src/auth/magicLink/index.ts +1 -0
- package/src/auth/magicLink/magicLinkCore.ts +48 -20
- package/src/auth/magicLink/types.ts +26 -0
- package/src/config.ts +172 -38
- package/src/context.ts +50 -3
- package/src/generated/generated.ts +1991 -1
- package/src/generic/ResolverBase.ts +28 -0
- package/src/generic/RunViewResolver.ts +4 -0
- package/src/index.ts +109 -3
- package/src/integration/CustomColumnPromoter.ts +6 -0
- package/src/realtimeWidget/WidgetRouter.ts +204 -0
- package/src/realtimeWidget/WidgetSessionService.ts +714 -0
- package/src/realtimeWidget/host-identity.ts +84 -0
- package/src/realtimeWidget/index.ts +15 -0
- package/src/realtimeWidget/visitorIdentity.ts +258 -0
- package/src/realtimeWidget/widgetCore.ts +231 -0
- package/src/realtimeWidget/widgetGuestElevation.ts +115 -0
- package/src/resolvers/ComponentRegistryResolver.ts +36 -10
- package/src/resolvers/EntityPermissionResolver.ts +1 -2
- package/src/resolvers/QuerySystemUserResolver.ts +14 -10
- package/src/resolvers/RealtimeClientSessionResolver.ts +59 -2
- package/src/resolvers/RingCentralTelephonyResolver.ts +64 -0
- package/src/resolvers/RunAIAgentResolver.ts +32 -8
- package/src/resolvers/SearchKnowledgeResolver.ts +2 -0
- package/src/resolvers/TeamsMeetingsResolver.ts +68 -0
- package/src/resolvers/TelephonyResolver.ts +63 -0
- package/src/resolvers/TestQuerySQLResolver.ts +2 -3
- package/src/resolvers/VonageTelephonyResolver.ts +63 -0
- package/src/rest/MediaStreamHandler.ts +4 -1
- package/src/telephony/RingCentralTelephonyService.ts +342 -0
- package/src/telephony/TeamsMeetingsRouter.ts +124 -0
- package/src/telephony/TeamsMeetingsService.ts +268 -0
- package/src/telephony/TwilioTelephonyRouter.ts +184 -0
- package/src/telephony/TwilioTelephonyService.ts +261 -0
- package/src/telephony/VonageTelephonyRouter.ts +239 -0
- package/src/telephony/VonageTelephonyService.ts +259 -0
- package/src/telephony/calendar-scheduler.ts +176 -0
- package/src/telephony/index.ts +43 -0
- package/src/telephony/media-upgrade-router.ts +62 -0
- package/src/telephony/ringcentral-runtime.ts +22 -0
- package/src/telephony/teams-meetings-runtime.ts +24 -0
- package/src/telephony/teamsAcsMediaRegistry.ts +152 -0
- package/src/telephony/telephony-runtime.ts +22 -0
- package/src/telephony/twilioMediaRegistry.ts +137 -0
- package/src/telephony/vonage-runtime.ts +22 -0
- package/src/telephony/vonageMediaRegistry.ts +200 -0
- package/dist/agents/skip-agent.d.ts +0 -111
- package/dist/agents/skip-agent.d.ts.map +0 -1
- package/dist/agents/skip-agent.js +0 -1487
- package/dist/agents/skip-agent.js.map +0 -1
- package/dist/agents/skip-sdk.d.ts +0 -261
- package/dist/agents/skip-sdk.d.ts.map +0 -1
- package/dist/agents/skip-sdk.js +0 -909
- package/dist/agents/skip-sdk.js.map +0 -1
- package/src/agents/skip-agent.ts +0 -1594
- package/src/agents/skip-sdk.ts +0 -1210
package/src/agents/skip-sdk.ts
DELETED
|
@@ -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
|
-
}
|