@memberjunction/server 5.36.0 → 5.38.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.
- package/README.md +12 -12
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +14 -2
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts +10 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +155 -13
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/generated/generated.d.ts +97 -5
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +553 -13
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +57 -14
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/AdhocQueryResolver.js +42 -28
- package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +5 -4
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.d.ts.map +1 -1
- package/dist/resolvers/QuerySystemUserResolver.js +6 -18
- package/dist/resolvers/QuerySystemUserResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +6 -59
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/package.json +70 -70
- package/src/__tests__/AdhocQueryResolver.bugs.test.ts +269 -0
- package/src/__tests__/resolverBase.rls.test.ts +224 -0
- package/src/agents/skip-agent.ts +15 -2
- package/src/agents/skip-sdk.ts +163 -13
- package/src/generated/generated.ts +388 -14
- package/src/index.ts +62 -17
- package/src/resolvers/AdhocQueryResolver.ts +42 -28
- package/src/resolvers/QueryResolver.ts +7 -6
- package/src/resolvers/QuerySystemUserResolver.ts +11 -25
- package/src/resolvers/RunAIAgentResolver.ts +6 -66
package/src/agents/skip-agent.ts
CHANGED
|
@@ -358,13 +358,25 @@ export class SkipProxyAgent extends BaseAgent {
|
|
|
358
358
|
const skipMessage = response.messages?.filter(msg => msg.role === 'system').pop();
|
|
359
359
|
const analysisText = response.analysis?.trim();
|
|
360
360
|
|
|
361
|
+
// Skip-Brain attaches actionableCommands as an extension field on the
|
|
362
|
+
// analysis_complete response (e.g. `client:capture-data-snapshot` when
|
|
363
|
+
// the Analysis Agent needs the user's current view). Forward to the
|
|
364
|
+
// next step so AgentRunner persists them to ConversationDetail.ActionableCommands
|
|
365
|
+
// — which the chat UI's actionable-commands.component reads to render buttons.
|
|
366
|
+
const responseExt = response as unknown as { actionableCommands?: unknown[] };
|
|
367
|
+
const actionableCommands = Array.isArray(responseExt.actionableCommands) && responseExt.actionableCommands.length > 0
|
|
368
|
+
? (responseExt.actionableCommands as BaseAgentNextStep<ComponentSpec>['actionableCommands'])
|
|
369
|
+
: undefined;
|
|
370
|
+
LogStatus(`[SkipProxyAgent DEBUG] handleAnalysisComplete actionableCommands from response: ${JSON.stringify(responseExt.actionableCommands)}`);
|
|
371
|
+
|
|
361
372
|
if (!hasComponentOptions) {
|
|
362
373
|
if (analysisText || skipMessage?.content) {
|
|
363
374
|
return {
|
|
364
375
|
terminate: true,
|
|
365
376
|
step: 'Success',
|
|
366
377
|
message: analysisText || skipMessage!.content,
|
|
367
|
-
newPayload: undefined
|
|
378
|
+
newPayload: undefined,
|
|
379
|
+
actionableCommands
|
|
368
380
|
};
|
|
369
381
|
}
|
|
370
382
|
|
|
@@ -387,7 +399,8 @@ export class SkipProxyAgent extends BaseAgent {
|
|
|
387
399
|
terminate: true,
|
|
388
400
|
step: 'Success',
|
|
389
401
|
message: skipMessage?.content || response.title || 'Analysis complete',
|
|
390
|
-
newPayload: componentSpec
|
|
402
|
+
newPayload: componentSpec,
|
|
403
|
+
actionableCommands
|
|
391
404
|
};
|
|
392
405
|
}
|
|
393
406
|
|
package/src/agents/skip-sdk.ts
CHANGED
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
SkipAPIArtifactType
|
|
25
25
|
} from '@memberjunction/skip-types';
|
|
26
26
|
import { DataContext } from '@memberjunction/data-context';
|
|
27
|
-
import { IMetadataProvider, UserInfo, LogStatus, LogError, Metadata, RunQuery, EntityInfo, EntityFieldInfo, EntityFieldValueInfo, DatabaseProviderBase } from '@memberjunction/core';
|
|
27
|
+
import { IMetadataProvider, UserInfo, LogStatus, LogError, Metadata, RunQuery, RunView, EntityInfo, EntityFieldInfo, EntityFieldValueInfo, DatabaseProviderBase } from '@memberjunction/core';
|
|
28
|
+
import { MJConversationDetailEntity, QueryEngine } from '@memberjunction/core-entities';
|
|
28
29
|
import { request as httpRequest } from 'http';
|
|
29
30
|
import { request as httpsRequest } from 'https';
|
|
30
31
|
import { gzip as gzipCompress, createGunzip } from 'zlib';
|
|
@@ -482,15 +483,15 @@ export class SkipSDK {
|
|
|
482
483
|
* Build saved queries for Skip
|
|
483
484
|
*/
|
|
484
485
|
private buildQueries(status: "Pending" | "In-Review" | "Approved" | "Rejected" | "Obsolete" = 'Approved'): SkipQueryInfo[] {
|
|
485
|
-
const
|
|
486
|
-
const approvedQueries =
|
|
486
|
+
const qe = QueryEngine.Instance;
|
|
487
|
+
const approvedQueries = qe.Queries.filter((q) => q.Status === status);
|
|
487
488
|
|
|
488
489
|
return approvedQueries.map((q) => ({
|
|
489
490
|
ID: q.ID,
|
|
490
491
|
Name: q.Name,
|
|
491
492
|
Description: q.Description,
|
|
492
493
|
Category: q.Category,
|
|
493
|
-
CategoryPath: this.buildQueryCategoryPath(
|
|
494
|
+
CategoryPath: this.buildQueryCategoryPath(qe, q.CategoryID),
|
|
494
495
|
CategoryID: q.CategoryID,
|
|
495
496
|
SQL: q.SQL,
|
|
496
497
|
Status: q.Status,
|
|
@@ -500,7 +501,7 @@ export class SkipSDK {
|
|
|
500
501
|
EmbeddingModelID: q.EmbeddingModelID,
|
|
501
502
|
EmbeddingModelName: q.EmbeddingModel,
|
|
502
503
|
TechnicalDescription: q.TechnicalDescription,
|
|
503
|
-
Fields: q.
|
|
504
|
+
Fields: qe.GetQueryFields(q.ID).map((f) => ({
|
|
504
505
|
ID: f.ID,
|
|
505
506
|
QueryID: f.QueryID,
|
|
506
507
|
Name: f.Name,
|
|
@@ -516,7 +517,7 @@ export class SkipSDK {
|
|
|
516
517
|
IsSummary: f.IsSummary,
|
|
517
518
|
SummaryDescription: f.SummaryDescription
|
|
518
519
|
})),
|
|
519
|
-
Parameters: q.
|
|
520
|
+
Parameters: qe.GetQueryParameters(q.ID).map((p) => ({
|
|
520
521
|
ID: p.ID,
|
|
521
522
|
QueryID: p.QueryID,
|
|
522
523
|
Name: p.Name,
|
|
@@ -527,12 +528,15 @@ export class SkipSDK {
|
|
|
527
528
|
SampleValue: p.SampleValue,
|
|
528
529
|
ValidationFilters: p.ValidationFilters
|
|
529
530
|
})),
|
|
530
|
-
Entities: q.
|
|
531
|
+
Entities: qe.QueryEntities.filter(e => UUIDsEqual(e.QueryID, q.ID)).map((e) => ({
|
|
531
532
|
ID: e.ID,
|
|
532
533
|
QueryID: e.QueryID,
|
|
533
534
|
EntityID: e.EntityID,
|
|
534
535
|
Entity: e.Entity
|
|
535
536
|
})),
|
|
537
|
+
SQLDialectID: q.SQLDialectID,
|
|
538
|
+
UsesTemplate: q.UsesTemplate,
|
|
539
|
+
IsApproved: q.IsApproved,
|
|
536
540
|
CacheEnabled: q.CacheEnabled,
|
|
537
541
|
CacheMaxSize: q.CacheMaxSize,
|
|
538
542
|
CacheTTLMinutes: q.CacheTTLMinutes,
|
|
@@ -543,11 +547,11 @@ export class SkipSDK {
|
|
|
543
547
|
/**
|
|
544
548
|
* Recursively build category path for a query
|
|
545
549
|
*/
|
|
546
|
-
private buildQueryCategoryPath(
|
|
547
|
-
const cat =
|
|
550
|
+
private buildQueryCategoryPath(qe: QueryEngine, categoryID: string): string {
|
|
551
|
+
const cat = qe.Categories.find((c) => UUIDsEqual(c.ID, categoryID));
|
|
548
552
|
if (!cat) return '';
|
|
549
553
|
if (!cat.ParentID) return cat.Name;
|
|
550
|
-
const parentPath = this.buildQueryCategoryPath(
|
|
554
|
+
const parentPath = this.buildQueryCategoryPath(qe, cat.ParentID);
|
|
551
555
|
return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
|
|
552
556
|
}
|
|
553
557
|
|
|
@@ -559,11 +563,11 @@ export class SkipSDK {
|
|
|
559
563
|
* across all queries, not just approved ones.
|
|
560
564
|
*/
|
|
561
565
|
private buildQueryCatalog(): SkipQueryCatalogEntry[] {
|
|
562
|
-
const
|
|
566
|
+
const qe = QueryEngine.Instance;
|
|
563
567
|
|
|
564
|
-
return
|
|
568
|
+
return qe.Queries.map((q) => ({
|
|
565
569
|
Name: q.Name,
|
|
566
|
-
CategoryPath: this.buildQueryCategoryPath(
|
|
570
|
+
CategoryPath: this.buildQueryCategoryPath(qe, q.CategoryID)
|
|
567
571
|
}));
|
|
568
572
|
}
|
|
569
573
|
|
|
@@ -682,6 +686,16 @@ export class SkipSDK {
|
|
|
682
686
|
versions: entry.versions
|
|
683
687
|
}));
|
|
684
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
|
+
|
|
685
699
|
return artifacts;
|
|
686
700
|
} catch (error) {
|
|
687
701
|
LogError(`Failed to build artifacts for conversation ${conversationId}: ${error}`);
|
|
@@ -689,6 +703,142 @@ export class SkipSDK {
|
|
|
689
703
|
}
|
|
690
704
|
}
|
|
691
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
|
+
|
|
692
842
|
/**
|
|
693
843
|
* Build API keys for AI services
|
|
694
844
|
*/
|