@memberjunction/server 2.106.0 → 2.108.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/dist/agents/skip-sdk.d.ts +11 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +262 -3
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -2
- package/dist/context.js.map +1 -1
- package/dist/generated/generated.d.ts +355 -20
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2220 -154
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +0 -2
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +1 -0
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts +21 -0
- package/dist/services/ScheduledJobsService.d.ts.map +1 -0
- package/dist/services/ScheduledJobsService.js +105 -0
- package/dist/services/ScheduledJobsService.js.map +1 -0
- package/package.json +34 -30
- package/src/agents/skip-sdk.ts +351 -8
- package/src/config.ts +10 -0
- package/src/context.ts +3 -2
- package/src/generated/generated.ts +1419 -119
- package/src/index.ts +50 -0
- package/src/resolvers/AskSkipResolver.ts +5 -3
- package/src/resolvers/RunAIAgentResolver.ts +1 -0
- package/src/services/ScheduledJobsService.ts +164 -0
package/src/agents/skip-sdk.ts
CHANGED
|
@@ -16,12 +16,15 @@ import {
|
|
|
16
16
|
SkipAPIRequestAPIKey,
|
|
17
17
|
SkipQueryInfo,
|
|
18
18
|
SkipEntityInfo,
|
|
19
|
+
SkipEntityFieldInfo,
|
|
20
|
+
SkipEntityFieldValueInfo,
|
|
21
|
+
SkipEntityRelationshipInfo,
|
|
19
22
|
SkipAPIAgentNote,
|
|
20
23
|
SkipAPIAgentNoteType,
|
|
21
24
|
SkipAPIArtifact
|
|
22
25
|
} from '@memberjunction/skip-types';
|
|
23
26
|
import { DataContext } from '@memberjunction/data-context';
|
|
24
|
-
import { UserInfo, LogStatus, LogError, Metadata, RunView } from '@memberjunction/core';
|
|
27
|
+
import { UserInfo, LogStatus, LogError, Metadata, RunView, EntityInfo, EntityFieldInfo, EntityRelationshipInfo } from '@memberjunction/core';
|
|
25
28
|
import { sendPostRequest } from '../util.js';
|
|
26
29
|
import { configInfo, baseUrl, publicUrl, graphqlPort, graphqlRootPath, apiKey as callbackAPIKey } from '../config.js';
|
|
27
30
|
import { GetAIAPIKey } from '@memberjunction/ai';
|
|
@@ -29,6 +32,8 @@ import { CopyScalarsAndArrays } from '@memberjunction/global';
|
|
|
29
32
|
import mssql from 'mssql';
|
|
30
33
|
import { registerAccessToken, GetDataAccessToken } from '../resolvers/GetDataResolver.js';
|
|
31
34
|
import { ConversationArtifactEntity, ConversationArtifactVersionEntity, ArtifactTypeEntity } from '@memberjunction/core-entities';
|
|
35
|
+
import { BehaviorSubject } from 'rxjs';
|
|
36
|
+
import { take } from 'rxjs/operators';
|
|
32
37
|
|
|
33
38
|
/**
|
|
34
39
|
* Configuration options for Skip SDK
|
|
@@ -162,6 +167,10 @@ export interface SkipCallResult {
|
|
|
162
167
|
export class SkipSDK {
|
|
163
168
|
private config: SkipSDKConfig;
|
|
164
169
|
|
|
170
|
+
// Static cache for Skip entities (shared across all instances)
|
|
171
|
+
private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<SkipEntityInfo[]> | null>(null);
|
|
172
|
+
private static __lastRefreshTime: number = 0;
|
|
173
|
+
|
|
165
174
|
constructor(config?: SkipSDKConfig) {
|
|
166
175
|
// Use provided config or fall back to MJ server config
|
|
167
176
|
this.config = {
|
|
@@ -261,9 +270,12 @@ export class SkipSDK {
|
|
|
261
270
|
// Build artifacts for this conversation
|
|
262
271
|
const artifacts = await this.buildArtifacts(contextUser, dataSource, conversationId);
|
|
263
272
|
|
|
273
|
+
// Process messages: filter delegation messages and enrich with metadata
|
|
274
|
+
const processedMessages = this.processMessages(messages);
|
|
275
|
+
|
|
264
276
|
// Construct the full Skip API request
|
|
265
277
|
const request: SkipAPIRequest = {
|
|
266
|
-
messages,
|
|
278
|
+
messages: processedMessages,
|
|
267
279
|
conversationID: conversationId,
|
|
268
280
|
dataContext: dataContext ? CopyScalarsAndArrays(dataContext) as DataContext : undefined,
|
|
269
281
|
requestPhase,
|
|
@@ -338,13 +350,26 @@ export class SkipSDK {
|
|
|
338
350
|
|
|
339
351
|
/**
|
|
340
352
|
* Build entity metadata for Skip
|
|
341
|
-
*
|
|
353
|
+
* Copied from AskSkipResolver.BuildSkipEntities - uses cached metadata with refresh logic
|
|
342
354
|
*/
|
|
343
|
-
private async buildEntities(dataSource: mssql.ConnectionPool, forceRefresh: boolean): Promise<SkipEntityInfo[]> {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
355
|
+
private async buildEntities(dataSource: mssql.ConnectionPool, forceRefresh: boolean, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
|
|
356
|
+
try {
|
|
357
|
+
const now = Date.now();
|
|
358
|
+
const cacheExpired = (now - SkipSDK.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
|
|
359
|
+
|
|
360
|
+
// If force refresh is requested OR cache expired OR cache is empty, refresh
|
|
361
|
+
if (forceRefresh || cacheExpired || SkipSDK.__skipEntitiesCache$.value === null) {
|
|
362
|
+
LogStatus(`[SkipSDK] Refreshing Skip entities cache (force: ${forceRefresh}, expired: ${cacheExpired})`);
|
|
363
|
+
const newData = this.refreshSkipEntities(dataSource);
|
|
364
|
+
SkipSDK.__skipEntitiesCache$.next(newData);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return SkipSDK.__skipEntitiesCache$.pipe(take(1)).toPromise();
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
LogError(`[SkipSDK] buildEntities error: ${e}`);
|
|
371
|
+
return [];
|
|
372
|
+
}
|
|
348
373
|
}
|
|
349
374
|
|
|
350
375
|
/**
|
|
@@ -538,4 +563,322 @@ export class SkipSDK {
|
|
|
538
563
|
'Content-Type': 'application/json'
|
|
539
564
|
};
|
|
540
565
|
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Refreshes the Skip entities cache
|
|
569
|
+
* Rebuilds the entity information that is provided to Skip
|
|
570
|
+
* Copied from AskSkipResolver.refreshSkipEntities
|
|
571
|
+
*/
|
|
572
|
+
private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
|
|
573
|
+
try {
|
|
574
|
+
const md = new Metadata();
|
|
575
|
+
const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
|
|
576
|
+
.map((e) => e.trim().toLowerCase());
|
|
577
|
+
|
|
578
|
+
// Get the list of entities
|
|
579
|
+
const entities = md.Entities.filter((e) => {
|
|
580
|
+
if (!configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
|
|
581
|
+
skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
|
|
582
|
+
const sd = e.ScopeDefault?.trim();
|
|
583
|
+
if (sd && sd.length > 0) {
|
|
584
|
+
const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
|
|
585
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai') || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase());
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
return true; // no scope, so include it
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return false;
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Now we have our list of entities, pack em up
|
|
595
|
+
const result = await Promise.all(entities.map((e) => this.packSingleSkipEntityInfo(e, dataSource)));
|
|
596
|
+
|
|
597
|
+
SkipSDK.__lastRefreshTime = Date.now(); // Update last refresh time
|
|
598
|
+
return result;
|
|
599
|
+
}
|
|
600
|
+
catch (e) {
|
|
601
|
+
LogError(`[SkipSDK] refreshSkipEntities error: ${e}`);
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Packs information about a single entity for Skip
|
|
608
|
+
* Includes fields, relationships, and sample data
|
|
609
|
+
* Copied from AskSkipResolver.PackSingleSkipEntityInfo
|
|
610
|
+
*/
|
|
611
|
+
private async packSingleSkipEntityInfo(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo> {
|
|
612
|
+
try {
|
|
613
|
+
const ret: SkipEntityInfo = {
|
|
614
|
+
id: e.ID,
|
|
615
|
+
name: e.Name,
|
|
616
|
+
schemaName: e.SchemaName,
|
|
617
|
+
baseView: e.BaseView,
|
|
618
|
+
description: e.Description,
|
|
619
|
+
|
|
620
|
+
fields: await Promise.all(e.Fields.filter(f => {
|
|
621
|
+
// we want to check the scopes for the field level and make sure it is either All or AI or has both
|
|
622
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
623
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
624
|
+
}).map(f => {
|
|
625
|
+
return this.packSingleSkipEntityField(f, dataSource);
|
|
626
|
+
})),
|
|
627
|
+
|
|
628
|
+
relatedEntities: e.RelatedEntities.map((r) => {
|
|
629
|
+
return this.packSingleSkipEntityRelationship(r);
|
|
630
|
+
}),
|
|
631
|
+
|
|
632
|
+
rowsPacked: e.RowsToPackWithSchema,
|
|
633
|
+
rowsSampleMethod: e.RowsToPackSampleMethod,
|
|
634
|
+
rows: await this.packEntityRows(e, dataSource)
|
|
635
|
+
};
|
|
636
|
+
return ret;
|
|
637
|
+
}
|
|
638
|
+
catch (e) {
|
|
639
|
+
LogError(`[SkipSDK] packSingleSkipEntityInfo error: ${e}`);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Packs information about a single entity relationship
|
|
646
|
+
* These relationships help Skip understand the data model
|
|
647
|
+
* Copied from AskSkipResolver.PackSingleSkipEntityRelationship
|
|
648
|
+
*/
|
|
649
|
+
private packSingleSkipEntityRelationship(r: EntityRelationshipInfo): SkipEntityRelationshipInfo {
|
|
650
|
+
try {
|
|
651
|
+
return {
|
|
652
|
+
entityID: r.EntityID,
|
|
653
|
+
relatedEntityID: r.RelatedEntityID,
|
|
654
|
+
type: r.Type,
|
|
655
|
+
entityKeyField: r.EntityKeyField,
|
|
656
|
+
relatedEntityJoinField: r.RelatedEntityJoinField,
|
|
657
|
+
joinView: r.JoinView,
|
|
658
|
+
joinEntityJoinField: r.JoinEntityJoinField,
|
|
659
|
+
joinEntityInverseJoinField: r.JoinEntityInverseJoinField,
|
|
660
|
+
entity: r.Entity,
|
|
661
|
+
entityBaseView: r.EntityBaseView,
|
|
662
|
+
relatedEntity: r.RelatedEntity,
|
|
663
|
+
relatedEntityBaseView: r.RelatedEntityBaseView,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
catch (e) {
|
|
667
|
+
LogError(`[SkipSDK] packSingleSkipEntityRelationship error: ${e}`);
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Packs information about a single entity field
|
|
674
|
+
* Includes metadata and possible values
|
|
675
|
+
* Copied from AskSkipResolver.PackSingleSkipEntityField
|
|
676
|
+
*/
|
|
677
|
+
private async packSingleSkipEntityField(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldInfo> {
|
|
678
|
+
try {
|
|
679
|
+
return {
|
|
680
|
+
entityID: f.EntityID,
|
|
681
|
+
sequence: f.Sequence,
|
|
682
|
+
name: f.Name,
|
|
683
|
+
displayName: f.DisplayName,
|
|
684
|
+
category: f.Category,
|
|
685
|
+
type: f.Type,
|
|
686
|
+
description: f.Description,
|
|
687
|
+
isPrimaryKey: f.IsPrimaryKey,
|
|
688
|
+
allowsNull: f.AllowsNull,
|
|
689
|
+
isUnique: f.IsUnique,
|
|
690
|
+
length: f.Length,
|
|
691
|
+
precision: f.Precision,
|
|
692
|
+
scale: f.Scale,
|
|
693
|
+
sqlFullType: f.SQLFullType,
|
|
694
|
+
defaultValue: f.DefaultValue,
|
|
695
|
+
autoIncrement: f.AutoIncrement,
|
|
696
|
+
valueListType: f.ValueListType,
|
|
697
|
+
extendedType: f.ExtendedType,
|
|
698
|
+
defaultInView: f.DefaultInView,
|
|
699
|
+
defaultColumnWidth: f.DefaultColumnWidth,
|
|
700
|
+
isVirtual: f.IsVirtual,
|
|
701
|
+
isNameField: f.IsNameField,
|
|
702
|
+
relatedEntityID: f.RelatedEntityID,
|
|
703
|
+
relatedEntityFieldName: f.RelatedEntityFieldName,
|
|
704
|
+
relatedEntity: f.RelatedEntity,
|
|
705
|
+
relatedEntitySchemaName: f.RelatedEntitySchemaName,
|
|
706
|
+
relatedEntityBaseView: f.RelatedEntityBaseView,
|
|
707
|
+
possibleValues: await this.packFieldPossibleValues(f, dataSource),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
catch (e) {
|
|
711
|
+
LogError(`[SkipSDK] packSingleSkipEntityField error: ${e}`);
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Packs entity rows (sample data)
|
|
718
|
+
* Copied from AskSkipResolver.PackEntityRows
|
|
719
|
+
*/
|
|
720
|
+
private async packEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
|
|
721
|
+
try {
|
|
722
|
+
if (e.RowsToPackWithSchema === 'None')
|
|
723
|
+
return [];
|
|
724
|
+
|
|
725
|
+
// only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
|
|
726
|
+
const fields = e.Fields.filter((f) => {
|
|
727
|
+
const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
|
|
728
|
+
return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
|
|
729
|
+
}).map(f => `[${f.Name}]`).join(',');
|
|
730
|
+
|
|
731
|
+
// now run the query based on the row packing method
|
|
732
|
+
let sql: string = '';
|
|
733
|
+
switch (e.RowsToPackWithSchema) {
|
|
734
|
+
case 'All':
|
|
735
|
+
sql = `SELECT ${fields} FROM ${e.SchemaName}.${e.BaseView}`;
|
|
736
|
+
break;
|
|
737
|
+
case 'Sample':
|
|
738
|
+
switch (e.RowsToPackSampleMethod) {
|
|
739
|
+
case 'random':
|
|
740
|
+
sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}] ORDER BY newid()`;
|
|
741
|
+
break;
|
|
742
|
+
case 'top n':
|
|
743
|
+
const orderBy = e.RowsToPackSampleOrder ? ` ORDER BY [${e.RowsToPackSampleOrder}]` : '';
|
|
744
|
+
sql = `SELECT TOP ${e.RowsToPackSampleCount} ${fields} FROM [${e.SchemaName}].[${e.BaseView}]${orderBy}`;
|
|
745
|
+
break;
|
|
746
|
+
case 'bottom n':
|
|
747
|
+
const firstPrimaryKey = e.FirstPrimaryKey.Name;
|
|
748
|
+
const innerOrderBy = e.RowsToPackSampleOrder ? `[${e.RowsToPackSampleOrder}]` : `[${firstPrimaryKey}] DESC`;
|
|
749
|
+
sql = `SELECT * FROM (
|
|
750
|
+
SELECT TOP ${e.RowsToPackSampleCount} ${fields}
|
|
751
|
+
FROM [${e.SchemaName}].[${e.BaseView}]
|
|
752
|
+
ORDER BY ${innerOrderBy}
|
|
753
|
+
) sub
|
|
754
|
+
ORDER BY [${firstPrimaryKey}] ASC;`;
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const request = new mssql.Request(dataSource);
|
|
759
|
+
const result = await request.query(sql);
|
|
760
|
+
if (!result || !result.recordset) {
|
|
761
|
+
return [];
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
return result.recordset;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
catch (e) {
|
|
768
|
+
LogError(`[SkipSDK] packEntityRows error: ${e}`);
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Packs possible values for an entity field
|
|
775
|
+
* These values help Skip understand the domain and valid values for fields
|
|
776
|
+
* Copied from AskSkipResolver.PackFieldPossibleValues
|
|
777
|
+
*/
|
|
778
|
+
private async packFieldPossibleValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
|
|
779
|
+
try {
|
|
780
|
+
if (f.ValuesToPackWithSchema === 'None') {
|
|
781
|
+
return []; // don't pack anything
|
|
782
|
+
}
|
|
783
|
+
else if (f.ValuesToPackWithSchema === 'All') {
|
|
784
|
+
// wants ALL of the distinct values
|
|
785
|
+
return await this.getFieldDistinctValues(f, dataSource);
|
|
786
|
+
}
|
|
787
|
+
else if (f.ValuesToPackWithSchema === 'Auto') {
|
|
788
|
+
// default setting - pack based on the ValueListType
|
|
789
|
+
if (f.ValueListTypeEnum === 'List') {
|
|
790
|
+
// simple list of values in the Entity Field Values table
|
|
791
|
+
return f.EntityFieldValues.map((v) => {
|
|
792
|
+
return { value: v.Value, displayValue: v.Value };
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
|
|
796
|
+
// could be a user provided value, OR the values in the list of possible values.
|
|
797
|
+
// get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
|
|
798
|
+
const values = await this.getFieldDistinctValues(f, dataSource);
|
|
799
|
+
if (!values || values.length === 0) {
|
|
800
|
+
// no result, just return the EntityFieldValues
|
|
801
|
+
return f.EntityFieldValues.map((v) => {
|
|
802
|
+
return { value: v.Value, displayValue: v.Value };
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
return [...new Set([...f.EntityFieldValues.map((v) => {
|
|
807
|
+
return { value: v.Value, displayValue: v.Value };
|
|
808
|
+
}), ...values])];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return []; // if we get here, nothing to pack
|
|
813
|
+
}
|
|
814
|
+
catch (e) {
|
|
815
|
+
LogError(`[SkipSDK] packFieldPossibleValues error: ${e}`);
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Gets distinct values for a field from the database
|
|
822
|
+
* Used to provide Skip with information about the possible values
|
|
823
|
+
* Copied from AskSkipResolver.GetFieldDistinctValues
|
|
824
|
+
*/
|
|
825
|
+
private async getFieldDistinctValues(f: EntityFieldInfo, dataSource: mssql.ConnectionPool): Promise<SkipEntityFieldValueInfo[]> {
|
|
826
|
+
try {
|
|
827
|
+
const sql = `SELECT DISTINCT ${f.Name} FROM ${f.SchemaName}.${f.BaseView}`;
|
|
828
|
+
const request = new mssql.Request(dataSource);
|
|
829
|
+
const result = await request.query(sql);
|
|
830
|
+
if (!result || !result.recordset) {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
return result.recordset.map((r) => {
|
|
835
|
+
return {
|
|
836
|
+
value: r[f.Name],
|
|
837
|
+
displayValue: r[f.Name]
|
|
838
|
+
};
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
catch (e) {
|
|
843
|
+
LogError(`[SkipSDK] getFieldDistinctValues error: ${e}`);
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Process messages: filter delegation messages and add metadata fields
|
|
850
|
+
* Messages coming in should already have conversationDetailID if they exist in the database
|
|
851
|
+
*/
|
|
852
|
+
private processMessages(messages: SkipMessage[]): SkipMessage[] {
|
|
853
|
+
// Filter out delegation messages (administrative messages that shouldn't go to Skip)
|
|
854
|
+
const filteredMessages = messages.filter(msg => !this.isDelegationMessage(msg.content));
|
|
855
|
+
|
|
856
|
+
// Enrich messages with default metadata if not already present
|
|
857
|
+
return filteredMessages.map(msg => ({
|
|
858
|
+
...msg,
|
|
859
|
+
// Add default metadata fields if not already present
|
|
860
|
+
// Messages from DB already have conversationDetailID, temp messages get temp-X
|
|
861
|
+
hiddenToUser: msg.hiddenToUser ?? false,
|
|
862
|
+
userRating: msg.userRating ?? null,
|
|
863
|
+
userFeedback: msg.userFeedback ?? null,
|
|
864
|
+
reflectionInsights: msg.reflectionInsights ?? null,
|
|
865
|
+
summaryOfEarlierConveration: msg.summaryOfEarlierConveration ?? null
|
|
866
|
+
}));
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Check if a message is a delegation message that should be filtered out
|
|
871
|
+
* Uses flexible pattern matching to detect variations of delegation messages
|
|
872
|
+
*/
|
|
873
|
+
private isDelegationMessage(content: string): boolean {
|
|
874
|
+
if (!content) return false;
|
|
875
|
+
|
|
876
|
+
const lowerContent = content.toLowerCase();
|
|
877
|
+
|
|
878
|
+
// Check for both "delegating" or "delegate" AND "skip" in any order
|
|
879
|
+
const hasDelegatingOrDelegate = lowerContent.includes('delegating') || lowerContent.includes('delegate');
|
|
880
|
+
const hasSkip = lowerContent.includes('skip');
|
|
881
|
+
|
|
882
|
+
return hasDelegatingOrDelegate && hasSkip;
|
|
883
|
+
}
|
|
541
884
|
}
|
package/src/config.ts
CHANGED
|
@@ -130,6 +130,14 @@ const componentRegistrySchema = z.object({
|
|
|
130
130
|
headers: z.record(z.string()).optional(),
|
|
131
131
|
}).passthrough(); // Allow additional fields
|
|
132
132
|
|
|
133
|
+
const scheduledJobsSchema = z.object({
|
|
134
|
+
enabled: z.boolean().optional().default(false),
|
|
135
|
+
systemUserEmail: z.string().optional().default('system@memberjunction.org'),
|
|
136
|
+
maxConcurrentJobs: z.number().optional().default(5),
|
|
137
|
+
defaultLockTimeout: z.number().optional().default(600000), // 10 minutes in ms
|
|
138
|
+
staleLockCleanupInterval: z.number().optional().default(300000), // 5 minutes in ms
|
|
139
|
+
});
|
|
140
|
+
|
|
133
141
|
const configInfoSchema = z.object({
|
|
134
142
|
userHandling: userHandlingInfoSchema,
|
|
135
143
|
databaseSettings: databaseSettingsInfoSchema,
|
|
@@ -139,6 +147,7 @@ const configInfoSchema = z.object({
|
|
|
139
147
|
sqlLogging: sqlLoggingSchema.optional(),
|
|
140
148
|
authProviders: z.array(authProviderSchema).optional(),
|
|
141
149
|
componentRegistries: z.array(componentRegistrySchema).optional(),
|
|
150
|
+
scheduledJobs: scheduledJobsSchema.optional().default({}),
|
|
142
151
|
|
|
143
152
|
apiKey: z.string().optional(),
|
|
144
153
|
baseUrl: z.string().default('http://localhost'),
|
|
@@ -180,6 +189,7 @@ export type SqlLoggingOptions = z.infer<typeof sqlLoggingOptionsSchema>;
|
|
|
180
189
|
export type SqlLoggingInfo = z.infer<typeof sqlLoggingSchema>;
|
|
181
190
|
export type AuthProviderConfig = z.infer<typeof authProviderSchema>;
|
|
182
191
|
export type ComponentRegistryConfig = z.infer<typeof componentRegistrySchema>;
|
|
192
|
+
export type ScheduledJobsConfig = z.infer<typeof scheduledJobsSchema>;
|
|
183
193
|
export type ConfigInfo = z.infer<typeof configInfoSchema>;
|
|
184
194
|
|
|
185
195
|
export const configInfo: ConfigInfo = loadConfig();
|
package/src/context.ts
CHANGED
|
@@ -145,9 +145,10 @@ export const contextFunction =
|
|
|
145
145
|
const apiKey = String(req.headers['x-mj-api-key']);
|
|
146
146
|
|
|
147
147
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
-
const
|
|
148
|
+
const reqAny = req as any;
|
|
149
|
+
const operationName: string | undefined = reqAny.body?.operationName;
|
|
149
150
|
if (operationName !== 'IntrospectionQuery') {
|
|
150
|
-
console.log({ operationName });
|
|
151
|
+
console.log({ operationName, variables: reqAny.body?.variables || undefined });
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
const userPayload = await getUserPayload(
|