@memberjunction/server 2.103.0 → 2.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/agents/skip-agent.d.ts +29 -0
  2. package/dist/agents/skip-agent.d.ts.map +1 -0
  3. package/dist/agents/skip-agent.js +143 -0
  4. package/dist/agents/skip-agent.js.map +1 -0
  5. package/dist/agents/skip-sdk.d.ts +47 -0
  6. package/dist/agents/skip-sdk.d.ts.map +1 -0
  7. package/dist/agents/skip-sdk.js +270 -0
  8. package/dist/agents/skip-sdk.js.map +1 -0
  9. package/dist/config.d.ts +9 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +1 -0
  12. package/dist/config.js.map +1 -1
  13. package/dist/generated/generated.d.ts +76 -16
  14. package/dist/generated/generated.d.ts.map +1 -1
  15. package/dist/generated/generated.js +527 -120
  16. package/dist/generated/generated.js.map +1 -1
  17. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  18. package/dist/resolvers/AskSkipResolver.js +24 -9
  19. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  20. package/dist/resolvers/ComponentRegistryResolver.d.ts +19 -0
  21. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  22. package/dist/resolvers/ComponentRegistryResolver.js +140 -2
  23. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  24. package/dist/resolvers/RunAIAgentResolver.d.ts +3 -3
  25. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  26. package/dist/resolvers/RunAIAgentResolver.js +16 -13
  27. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  28. package/package.json +38 -38
  29. package/src/agents/skip-agent.ts +285 -0
  30. package/src/agents/skip-sdk.ts +543 -0
  31. package/src/config.ts +3 -2
  32. package/src/generated/generated.ts +351 -93
  33. package/src/resolvers/AskSkipResolver.ts +32 -10
  34. package/src/resolvers/ComponentRegistryResolver.ts +133 -4
  35. package/src/resolvers/RunAIAgentResolver.ts +16 -10
@@ -1,4 +1,4 @@
1
- import { Arg, Ctx, Field, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
1
+ import { Arg, Ctx, Field, InputType, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
2
2
  import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo, EntitySaveOptions, EntityDeleteOptions, IMetadataProvider } from '@memberjunction/core';
3
3
  import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
4
4
  import { BehaviorSubject } from 'rxjs';
@@ -66,6 +66,18 @@ import { AIEngine } from '@memberjunction/aiengine';
66
66
  import { deleteAccessToken, GetDataAccessToken, registerAccessToken, tokenExists } from './GetDataResolver.js';
67
67
  import e from 'express';
68
68
 
69
+ /**
70
+ * Skip API Endpoints Configuration
71
+ * Defines all available endpoints for the Skip API
72
+ */
73
+ const SKIP_API_ENDPOINTS = {
74
+ CHAT: '/chat',
75
+ LEARNING: '/learning',
76
+ FEEDBACK_COMPONENT: '/feedback/component',
77
+ REGISTRY: '/registry',
78
+ // Add more endpoints as needed
79
+ } as const;
80
+
69
81
  /**
70
82
  * Store for active conversation streams
71
83
  * Maps conversationID to the last status message received
@@ -368,7 +380,9 @@ function initializeSkipLearningCycleScheduler() {
368
380
  }
369
381
 
370
382
  // Check if we have a valid endpoint when cycles are enabled
371
- if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
383
+ const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
384
+ (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
385
+ if (!hasLearningEndpoint) {
372
386
  LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no Learning Cycle API endpoint is configured');
373
387
  return;
374
388
  }
@@ -577,7 +591,9 @@ export class AskSkipResolver {
577
591
  }
578
592
 
579
593
  // Check if we have a valid endpoint when cycles are enabled
580
- if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
594
+ const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
595
+ (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
596
+ if (!hasLearningEndpoint) {
581
597
  return {
582
598
  success: false,
583
599
  error: 'Learning cycle API endpoint is not configured',
@@ -729,9 +745,10 @@ export class AskSkipResolver {
729
745
  userPayload: UserPayload
730
746
  ): Promise<SkipAPILearningCycleResponse> {
731
747
  const skipConfigInfo = configInfo.askSkip;
732
- LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${skipConfigInfo.learningCycleURL}`);
748
+ const learningURL = skipConfigInfo.url ? `${skipConfigInfo.url}${SKIP_API_ENDPOINTS.LEARNING}` : skipConfigInfo.learningCycleURL;
749
+ LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${learningURL}`);
733
750
 
734
- const response = await sendPostRequest(skipConfigInfo.learningCycleURL, input, true, this.buildSkipPostHeaders());
751
+ const response = await sendPostRequest(learningURL, input, true, this.buildSkipPostHeaders());
735
752
 
736
753
  if (response && response.length > 0) {
737
754
  // the last object in the response array is the final response from the Skip API
@@ -789,10 +806,11 @@ export class AskSkipResolver {
789
806
  userPayload: UserPayload = null
790
807
  ): Promise<AskSkipResultType> {
791
808
  const skipConfigInfo = configInfo.askSkip;
792
- LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
809
+ const chatURL = skipConfigInfo.url ? `${skipConfigInfo.url}${SKIP_API_ENDPOINTS.CHAT}` : skipConfigInfo.chatURL;
810
+ LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${chatURL}`);
793
811
 
794
812
  try {
795
- const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, this.buildSkipPostHeaders());
813
+ const response = await sendPostRequest(chatURL, input, true, this.buildSkipPostHeaders());
796
814
 
797
815
  if (response && response.length > 0) {
798
816
  // the last object in the response array is the final response from the Skip API
@@ -2428,7 +2446,8 @@ cycle.`);
2428
2446
  startTime: Date
2429
2447
  ): Promise<AskSkipResultType> {
2430
2448
  const skipConfigInfo = configInfo.askSkip;
2431
- LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${skipConfigInfo.chatURL}`);
2449
+ const chatURL = skipConfigInfo.url ? `${skipConfigInfo.url}${SKIP_API_ENDPOINTS.CHAT}` : skipConfigInfo.chatURL;
2450
+ LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${chatURL}`);
2432
2451
 
2433
2452
  if (conversationDetailCount > 10) {
2434
2453
  // Set status of conversation to Available since we still want to allow the user to ask questions
@@ -2459,7 +2478,7 @@ cycle.`);
2459
2478
  let response;
2460
2479
  try {
2461
2480
  response = await sendPostRequest(
2462
- skipConfigInfo.chatURL,
2481
+ chatURL,
2463
2482
  input,
2464
2483
  true,
2465
2484
  this.buildSkipPostHeaders(),
@@ -3259,7 +3278,9 @@ cycle.`);
3259
3278
  }
3260
3279
 
3261
3280
  // Check if we have a valid endpoint when cycles are enabled
3262
- if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
3281
+ const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
3282
+ (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
3283
+ if (!hasLearningEndpoint) {
3263
3284
  return {
3264
3285
  Success: false,
3265
3286
  Message: 'Learning cycle API endpoint is not configured'
@@ -3394,6 +3415,7 @@ cycle.`);
3394
3415
  };
3395
3416
  }
3396
3417
  }
3418
+
3397
3419
  }
3398
3420
 
3399
3421
  export default AskSkipResolver;
@@ -1,15 +1,17 @@
1
- import { Arg, Ctx, Field, InputType, ObjectType, Query, Resolver } from 'type-graphql';
1
+ import { Arg, Ctx, Field, InputType, ObjectType, Query, Mutation, Resolver } from 'type-graphql';
2
2
  import { UserInfo, Metadata, LogError, LogStatus } from '@memberjunction/core';
3
3
  import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
4
  import { ComponentEntity, ComponentRegistryEntity, ComponentMetadataEngine } from '@memberjunction/core-entities';
5
5
  import { ComponentSpec } from '@memberjunction/interactive-component-types';
6
- import {
6
+ import {
7
7
  ComponentRegistryClient,
8
8
  ComponentResponse,
9
9
  ComponentSearchResult,
10
10
  DependencyTree,
11
11
  RegistryError,
12
- RegistryErrorCode
12
+ RegistryErrorCode,
13
+ ComponentFeedbackParams as SDKComponentFeedbackParams,
14
+ ComponentFeedbackResponse as SDKComponentFeedbackResponse
13
15
  } from '@memberjunction/component-registry-client-sdk';
14
16
  import { AppContext } from '../types.js';
15
17
  import { configInfo } from '../config.js';
@@ -96,6 +98,61 @@ class ComponentDependencyTreeType {
96
98
  dependencies?: ComponentDependencyTreeType[];
97
99
  }
98
100
 
101
+ /**
102
+ * Input type for submitting component feedback
103
+ * Registry-agnostic feedback collection for any component from any registry
104
+ */
105
+ @InputType()
106
+ class ComponentFeedbackInput {
107
+ @Field()
108
+ componentName: string;
109
+
110
+ @Field()
111
+ componentNamespace: string;
112
+
113
+ @Field({ nullable: true })
114
+ componentVersion?: string;
115
+
116
+ @Field({ nullable: true })
117
+ registryName?: string;
118
+
119
+ @Field()
120
+ rating: number;
121
+
122
+ @Field({ nullable: true })
123
+ feedbackType?: string;
124
+
125
+ @Field({ nullable: true })
126
+ comments?: string;
127
+
128
+ @Field({ nullable: true })
129
+ conversationID?: string;
130
+
131
+ @Field({ nullable: true })
132
+ conversationDetailID?: string;
133
+
134
+ @Field({ nullable: true })
135
+ reportID?: string;
136
+
137
+ @Field({ nullable: true })
138
+ dashboardID?: string;
139
+ }
140
+
141
+ /**
142
+ * Response type for component feedback submission
143
+ */
144
+ @ObjectType()
145
+ class ComponentFeedbackResponse {
146
+ @Field()
147
+ success: boolean;
148
+
149
+ @Field({ nullable: true })
150
+ feedbackID?: string;
151
+
152
+ @Field({ nullable: true })
153
+ error?: string;
154
+ }
155
+
99
156
  /**
100
157
  * Resolver for Component Registry operations
101
158
  *
@@ -153,7 +210,8 @@ export class ComponentRegistryExtendedResolver {
153
210
  namespace,
154
211
  name,
155
212
  version: version || 'latest',
156
- hash: hash
213
+ hash: hash,
214
+ userEmail: user.Email
157
215
  });
158
216
 
159
217
  // If not modified (304), return response with notModified flag
@@ -532,4 +590,75 @@ export class ComponentRegistryExtendedResolver {
532
590
  limit: result.limit
533
591
  };
534
592
  }
593
+
594
+ /**
595
+ * Send feedback for a component from any registry
596
+ * This is a registry-agnostic mutation that allows feedback collection
597
+ * for components from any source registry (Skip, MJ Central, etc.)
598
+ */
599
+ @Mutation(() => ComponentFeedbackResponse)
600
+ async SendComponentFeedback(
601
+ @Arg('feedback') feedback: ComponentFeedbackInput,
602
+ @Ctx() { userPayload }: AppContext
603
+ ): Promise<ComponentFeedbackResponse> {
604
+ try {
605
+ // Get user from cache
606
+ const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email?.trim().toLowerCase());
607
+ if (!user) {
608
+ return {
609
+ success: false,
610
+ error: `User ${userPayload.email} not found in UserCache`
611
+ };
612
+ }
613
+
614
+ // Registry name is required for feedback submission
615
+ if (!feedback.registryName) {
616
+ return {
617
+ success: false,
618
+ error: 'Registry name is required for feedback submission'
619
+ };
620
+ }
621
+
622
+ // Get registry configuration
623
+ const registry = await this.getRegistryByName(feedback.registryName, user);
624
+ if (!registry) {
625
+ return {
626
+ success: false,
627
+ error: `Registry not found: ${feedback.registryName}`
628
+ };
629
+ }
630
+
631
+ // Check user permissions
632
+ await this.checkUserAccess(user, registry.ID);
633
+
634
+ // Create client using the same pattern as GetRegistryComponent
635
+ // This respects REGISTRY_URI_OVERRIDE_* and REGISTRY_API_KEY_* environment variables
636
+ const registryClient = this.createClientForRegistry(registry);
637
+
638
+ const sdkParams: SDKComponentFeedbackParams = {
639
+ componentName: feedback.componentName,
640
+ componentNamespace: feedback.componentNamespace,
641
+ componentVersion: feedback.componentVersion,
642
+ registryName: feedback.registryName,
643
+ rating: feedback.rating,
644
+ feedbackType: feedback.feedbackType,
645
+ comments: feedback.comments,
646
+ conversationID: feedback.conversationID,
647
+ conversationDetailID: feedback.conversationDetailID,
648
+ reportID: feedback.reportID,
649
+ dashboardID: feedback.dashboardID,
650
+ userEmail: user.Email // Pass the authenticated user's email to the registry
651
+ };
652
+
653
+ const result = await registryClient.submitFeedback(sdkParams);
654
+
655
+ return result;
656
+ } catch (error) {
657
+ LogError(error);
658
+ return {
659
+ success: false,
660
+ error: error instanceof Error ? error.message : 'Unknown error'
661
+ };
662
+ }
663
+ }
535
664
  }
@@ -22,7 +22,7 @@ export class AIAgentRunResult {
22
22
  executionTimeMs?: number;
23
23
 
24
24
  @Field()
25
- payload: string; // JSON serialized ExecuteAgentResult with scalars only
25
+ result: string; // JSON serialized ExecuteAgentResult with scalars only
26
26
  }
27
27
 
28
28
  @ObjectType()
@@ -315,7 +315,8 @@ export class RunAIAgentResolver extends ResolverBase {
315
315
  templateData?: string,
316
316
  lastRunId?: string,
317
317
  autoPopulateLastRunPayload?: boolean,
318
- configurationId?: string
318
+ configurationId?: string,
319
+ conversationDetailId?: string
319
320
  ): Promise<AIAgentRunResult> {
320
321
  const startTime = Date.now();
321
322
 
@@ -361,7 +362,8 @@ export class RunAIAgentResolver extends ResolverBase {
361
362
  lastRunId: lastRunId,
362
363
  autoPopulateLastRunPayload: autoPopulateLastRunPayload,
363
364
  configurationId: configurationId,
364
- data: parsedData
365
+ data: parsedData,
366
+ conversationDetailId: conversationDetailId,
365
367
  });
366
368
 
367
369
  // Update agent run ref once available
@@ -376,7 +378,7 @@ export class RunAIAgentResolver extends ResolverBase {
376
378
 
377
379
  // Create sanitized payload for JSON serialization
378
380
  const sanitizedResult = this.sanitizeAgentResult(result);
379
- const payload = JSON.stringify(sanitizedResult);
381
+ const returnResult = JSON.stringify(sanitizedResult);
380
382
 
381
383
  // Log completion
382
384
  if (result.success) {
@@ -389,7 +391,7 @@ export class RunAIAgentResolver extends ResolverBase {
389
391
  success: result.success,
390
392
  errorMessage: result.agentRun?.ErrorMessage || undefined,
391
393
  executionTimeMs: executionTime,
392
- payload
394
+ result: returnResult
393
395
  };
394
396
 
395
397
  } catch (error) {
@@ -407,7 +409,7 @@ export class RunAIAgentResolver extends ResolverBase {
407
409
  success: false,
408
410
  errorMessage: errorResult.errorMessage,
409
411
  executionTimeMs: executionTime,
410
- payload: JSON.stringify(errorResult)
412
+ result: JSON.stringify(errorResult)
411
413
  };
412
414
  }
413
415
  }
@@ -465,7 +467,8 @@ export class RunAIAgentResolver extends ResolverBase {
465
467
  @Arg('templateData', { nullable: true }) templateData?: string,
466
468
  @Arg('lastRunId', { nullable: true }) lastRunId?: string,
467
469
  @Arg('autoPopulateLastRunPayload', { nullable: true }) autoPopulateLastRunPayload?: boolean,
468
- @Arg('configurationId', { nullable: true }) configurationId?: string
470
+ @Arg('configurationId', { nullable: true }) configurationId?: string,
471
+ @Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string
469
472
  ): Promise<AIAgentRunResult> {
470
473
  const p = GetReadWriteProvider(providers);
471
474
  return this.executeAIAgent(
@@ -479,7 +482,8 @@ export class RunAIAgentResolver extends ResolverBase {
479
482
  templateData,
480
483
  lastRunId,
481
484
  autoPopulateLastRunPayload,
482
- configurationId
485
+ configurationId,
486
+ conversationDetailId
483
487
  );
484
488
  }
485
489
 
@@ -499,7 +503,8 @@ export class RunAIAgentResolver extends ResolverBase {
499
503
  @Arg('templateData', { nullable: true }) templateData?: string,
500
504
  @Arg('lastRunId', { nullable: true }) lastRunId?: string,
501
505
  @Arg('autoPopulateLastRunPayload', { nullable: true }) autoPopulateLastRunPayload?: boolean,
502
- @Arg('configurationId', { nullable: true }) configurationId?: string
506
+ @Arg('configurationId', { nullable: true }) configurationId?: string,
507
+ @Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string
503
508
  ): Promise<AIAgentRunResult> {
504
509
  const p = GetReadWriteProvider(providers);
505
510
  return this.executeAIAgent(
@@ -513,7 +518,8 @@ export class RunAIAgentResolver extends ResolverBase {
513
518
  templateData,
514
519
  lastRunId,
515
520
  autoPopulateLastRunPayload,
516
- configurationId
521
+ configurationId,
522
+ conversationDetailId
517
523
  );
518
524
  }
519
525