@memberjunction/server 5.23.0 → 5.25.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 +12 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +70 -1
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +954 -0
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +26108 -20749
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ArtifactFileResolver.d.ts +15 -0
- package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -0
- package/dist/resolvers/ArtifactFileResolver.js +74 -0
- package/dist/resolvers/ArtifactFileResolver.js.map +1 -0
- package/dist/resolvers/AutotagPipelineResolver.d.ts +23 -1
- package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
- package/dist/resolvers/AutotagPipelineResolver.js +197 -13
- package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
- package/dist/resolvers/FetchEntityVectorsResolver.js +6 -2
- package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +12 -32
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GeoResolver.d.ts +58 -0
- package/dist/resolvers/GeoResolver.d.ts.map +1 -0
- package/dist/resolvers/GeoResolver.js +302 -0
- package/dist/resolvers/GeoResolver.js.map +1 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts +34 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +183 -48
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.d.ts +23 -41
- package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
- package/dist/resolvers/SearchKnowledgeResolver.js +133 -382
- package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts +19 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +149 -0
- package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -0
- package/package.json +63 -63
- package/src/__tests__/search-knowledge-tags.test.ts +255 -0
- package/src/__tests__/skip-sdk-organic-keys.test.ts +274 -0
- package/src/agents/skip-sdk.ts +83 -2
- package/src/config.ts +11 -0
- package/src/generated/generated.ts +3690 -1
- package/src/generic/RunViewResolver.ts +1 -0
- package/src/index.ts +2 -0
- package/src/resolvers/ArtifactFileResolver.ts +71 -0
- package/src/resolvers/AutotagPipelineResolver.ts +213 -10
- package/src/resolvers/FetchEntityVectorsResolver.ts +6 -2
- package/src/resolvers/FileResolver.ts +12 -41
- package/src/resolvers/GeoResolver.ts +258 -0
- package/src/resolvers/RunAIAgentResolver.ts +229 -76
- package/src/resolvers/SearchKnowledgeResolver.ts +118 -462
- package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +138 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine, Subscription, Root, ResolverFilterData, ID, Int } from 'type-graphql';
|
|
2
2
|
import { AppContext, UserPayload } from '../types.js';
|
|
3
|
-
import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
|
-
import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity, MJAIAgentRequestEntity } from '@memberjunction/core-entities';
|
|
3
|
+
import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
4
|
+
import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity, MJConversationDetailArtifactEntity, MJArtifactVersionEntity, MJAIAgentRequestEntity } from '@memberjunction/core-entities';
|
|
5
5
|
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
6
6
|
import { MJAIAgentEntityExtended, MJAIAgentRunEntityExtended, ExecuteAgentResult, ConversationUtility, AttachmentData } from '@memberjunction/ai-core-plus';
|
|
7
7
|
import { AIEngine } from '@memberjunction/aiengine';
|
|
8
|
-
import { ChatMessage } from '@memberjunction/ai';
|
|
8
|
+
import { ChatMessage, ChatMessageContent } from '@memberjunction/ai';
|
|
9
9
|
import { ResolverBase } from '../generic/ResolverBase.js';
|
|
10
10
|
import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
11
11
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
12
12
|
import { GetReadWriteProvider } from '../util.js';
|
|
13
13
|
import { SafeJSONParse, UUIDsEqual } from '@memberjunction/global';
|
|
14
|
-
import {
|
|
14
|
+
import { GetAttachmentService } from '@memberjunction/aiengine';
|
|
15
15
|
import { NotificationEngine } from '@memberjunction/notifications';
|
|
16
16
|
|
|
17
17
|
@ObjectType()
|
|
@@ -361,7 +361,9 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
361
361
|
createArtifacts: boolean = false,
|
|
362
362
|
createNotification: boolean = false,
|
|
363
363
|
sourceArtifactId?: string,
|
|
364
|
-
sourceArtifactVersionId?: string
|
|
364
|
+
sourceArtifactVersionId?: string,
|
|
365
|
+
/** LATENCY OPT #2: Pre-resolved conversationId avoids redundant DB load in AgentRunner */
|
|
366
|
+
conversationId?: string
|
|
365
367
|
): Promise<AIAgentRunResult> {
|
|
366
368
|
const startTime = Date.now();
|
|
367
369
|
|
|
@@ -418,6 +420,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
418
420
|
}
|
|
419
421
|
}, {
|
|
420
422
|
conversationDetailId: conversationDetailId, // Use existing if provided
|
|
423
|
+
conversationId: conversationId, // LATENCY OPT #2: pre-resolved to skip redundant load in AgentRunner
|
|
421
424
|
userMessage: userMessage, // Provide user message when conversationDetailId not provided
|
|
422
425
|
createArtifacts: createArtifacts || false,
|
|
423
426
|
sourceArtifactId: sourceArtifactId
|
|
@@ -435,37 +438,53 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
435
438
|
|
|
436
439
|
const executionTime = Date.now() - startTime;
|
|
437
440
|
|
|
438
|
-
//
|
|
441
|
+
// LATENCY OPTIMIZATION (Opt #6): These three post-execution operations are independent
|
|
442
|
+
// of each other — none reads the output of another. Previously they ran sequentially,
|
|
443
|
+
// adding their latencies together (~50ms total). Now they run in parallel via Promise.all,
|
|
444
|
+
// so we only pay the cost of the slowest one.
|
|
445
|
+
//
|
|
446
|
+
// 1. syncFeedbackRequestFromConversation — links a prior Chat-step feedback request to
|
|
447
|
+
// the new agent run so the conversation thread stays coherent.
|
|
448
|
+
// 2. sendFeedbackRequestNotification — sends an in-app/email/SMS notification when the
|
|
449
|
+
// agent paused for human input (Chat step).
|
|
450
|
+
// 3. createCompletionNotification — sends an in-app/email/SMS notification that the
|
|
451
|
+
// agent finished and created an artifact.
|
|
452
|
+
const postExecutionOps: Promise<void>[] = [];
|
|
453
|
+
|
|
439
454
|
if (lastRunId && result.agentRun?.ID) {
|
|
440
|
-
|
|
441
|
-
lastRunId,
|
|
442
|
-
result.agentRun.ID,
|
|
443
|
-
userMessage,
|
|
444
|
-
currentUser
|
|
455
|
+
postExecutionOps.push(
|
|
456
|
+
this.syncFeedbackRequestFromConversation(lastRunId, result.agentRun.ID, userMessage, currentUser, p)
|
|
445
457
|
);
|
|
446
458
|
}
|
|
447
459
|
|
|
448
|
-
// Send notification if agent created a feedback request (Chat step)
|
|
449
460
|
if (result.feedbackRequestId) {
|
|
450
|
-
|
|
461
|
+
postExecutionOps.push(
|
|
462
|
+
this.sendFeedbackRequestNotification(result, currentUser, pubSub, userPayload)
|
|
463
|
+
);
|
|
451
464
|
}
|
|
452
465
|
|
|
453
|
-
// Create notification if enabled and artifact was created successfully
|
|
454
466
|
if (createNotification && result.success && artifactInfo && artifactInfo.artifactId && artifactInfo.versionId && artifactInfo.versionNumber) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
467
|
+
postExecutionOps.push(
|
|
468
|
+
this.createCompletionNotification(
|
|
469
|
+
result.agentRun,
|
|
470
|
+
{
|
|
471
|
+
artifactId: artifactInfo.artifactId,
|
|
472
|
+
versionId: artifactInfo.versionId,
|
|
473
|
+
versionNumber: artifactInfo.versionNumber
|
|
474
|
+
},
|
|
475
|
+
conversationResult.conversationId,
|
|
476
|
+
finalConversationDetailId,
|
|
477
|
+
currentUser,
|
|
478
|
+
pubSub,
|
|
479
|
+
userPayload
|
|
480
|
+
)
|
|
466
481
|
);
|
|
467
482
|
}
|
|
468
483
|
|
|
484
|
+
if (postExecutionOps.length > 0) {
|
|
485
|
+
await Promise.all(postExecutionOps);
|
|
486
|
+
}
|
|
487
|
+
|
|
469
488
|
// Create sanitized payload for JSON serialization
|
|
470
489
|
const sanitizedResult = this.sanitizeAgentResult(result);
|
|
471
490
|
const returnResult = JSON.stringify(sanitizedResult);
|
|
@@ -680,34 +699,31 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
680
699
|
* Create a user notification for agent completion with artifact
|
|
681
700
|
* Notification includes navigation link back to the conversation
|
|
682
701
|
*/
|
|
702
|
+
/**
|
|
703
|
+
* LATENCY OPTIMIZATION (Opt #2): Now accepts conversationId directly instead of
|
|
704
|
+
* conversationDetailId. Previously this method loaded a ConversationDetail entity
|
|
705
|
+
* from the DB solely to extract its ConversationID field for building a URL — a
|
|
706
|
+
* redundant ~50ms DB round-trip since the caller already resolved conversationId
|
|
707
|
+
* when loading conversation history.
|
|
708
|
+
*/
|
|
683
709
|
private async createCompletionNotification(
|
|
684
710
|
agentRun: MJAIAgentRunEntityExtended,
|
|
685
711
|
artifactInfo: { artifactId: string; versionId: string; versionNumber: number },
|
|
712
|
+
conversationId: string,
|
|
686
713
|
conversationDetailId: string,
|
|
687
714
|
contextUser: UserInfo,
|
|
688
715
|
pubSub: PubSubEngine,
|
|
689
716
|
userPayload: UserPayload
|
|
690
717
|
): Promise<void> {
|
|
691
718
|
try {
|
|
692
|
-
const md = new Metadata();
|
|
693
|
-
|
|
694
719
|
// Get agent info for notification message
|
|
695
720
|
await AIEngine.Instance.Config(false, contextUser);
|
|
696
721
|
const agent = AIEngine.Instance.Agents.find(a => UUIDsEqual(a.ID, agentRun.AgentID));
|
|
697
722
|
const agentName = agent?.Name || 'Agent';
|
|
698
723
|
|
|
699
|
-
// Load conversation detail to get conversation info
|
|
700
|
-
const detail = await md.GetEntityObject<MJConversationDetailEntity>(
|
|
701
|
-
'MJ: Conversation Details',
|
|
702
|
-
contextUser
|
|
703
|
-
);
|
|
704
|
-
if (!(await detail.Load(conversationDetailId))) {
|
|
705
|
-
throw new Error(`Failed to load conversation detail ${conversationDetailId}`);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
724
|
// Build conversation URL for email/SMS templates
|
|
709
725
|
const baseUrl = process.env.APP_BASE_URL || 'http://localhost:4201';
|
|
710
|
-
const conversationUrl = `${baseUrl}/conversations/${
|
|
726
|
+
const conversationUrl = `${baseUrl}/conversations/${conversationId}?artifact=${artifactInfo.artifactId}`;
|
|
711
727
|
|
|
712
728
|
// Craft message based on versioning
|
|
713
729
|
const message = artifactInfo.versionNumber > 1
|
|
@@ -724,7 +740,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
724
740
|
message: message,
|
|
725
741
|
resourceConfiguration: {
|
|
726
742
|
type: 'conversation',
|
|
727
|
-
conversationId:
|
|
743
|
+
conversationId: conversationId,
|
|
728
744
|
messageId: conversationDetailId,
|
|
729
745
|
artifactId: artifactInfo.artifactId,
|
|
730
746
|
versionId: artifactInfo.versionId,
|
|
@@ -755,7 +771,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
755
771
|
action: 'create',
|
|
756
772
|
title: `${agentName} completed your request`,
|
|
757
773
|
message: message,
|
|
758
|
-
conversationId:
|
|
774
|
+
conversationId: conversationId
|
|
759
775
|
})
|
|
760
776
|
});
|
|
761
777
|
|
|
@@ -781,10 +797,11 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
781
797
|
lastRunId: string,
|
|
782
798
|
newRunId: string,
|
|
783
799
|
userMessage: string | undefined,
|
|
784
|
-
contextUser: UserInfo
|
|
800
|
+
contextUser: UserInfo,
|
|
801
|
+
provider: IMetadataProvider
|
|
785
802
|
): Promise<void> {
|
|
786
803
|
try {
|
|
787
|
-
const rv =
|
|
804
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
788
805
|
const result = await rv.RunView<MJAIAgentRequestEntity>({
|
|
789
806
|
EntityName: 'MJ: AI Agent Requests',
|
|
790
807
|
ExtraFilter: `OriginatingAgentRunID='${lastRunId}' AND Status='Requested'`,
|
|
@@ -918,11 +935,26 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
918
935
|
}
|
|
919
936
|
|
|
920
937
|
try {
|
|
938
|
+
// LATENCY OPTIMIZATION (Opt #2 + #3): Load ConversationDetail once here to extract
|
|
939
|
+
// conversationId, then pass it downstream. Previously this record was loaded multiple
|
|
940
|
+
// times: once in loadConversationHistoryWithAttachments (just to get conversationId),
|
|
941
|
+
// once in AgentRunner (same reason), and once in createCompletionNotification. Now we
|
|
942
|
+
// load it a single time and thread conversationId through the call chain.
|
|
943
|
+
const currentDetail = await p.GetEntityObject<MJConversationDetailEntity>(
|
|
944
|
+
'MJ: Conversation Details',
|
|
945
|
+
currentUser
|
|
946
|
+
);
|
|
947
|
+
if (!await currentDetail.Load(conversationDetailId)) {
|
|
948
|
+
throw new Error(`Conversation detail ${conversationDetailId} not found`);
|
|
949
|
+
}
|
|
950
|
+
const conversationId = currentDetail.ConversationID;
|
|
951
|
+
|
|
921
952
|
// Load conversation history with attachments from DB
|
|
922
953
|
const messages = await this.loadConversationHistoryWithAttachments(
|
|
923
|
-
|
|
954
|
+
conversationId,
|
|
924
955
|
currentUser,
|
|
925
|
-
maxHistoryMessages || 20
|
|
956
|
+
maxHistoryMessages || 20,
|
|
957
|
+
p
|
|
926
958
|
);
|
|
927
959
|
|
|
928
960
|
// Convert to JSON string for the existing executeAIAgent method
|
|
@@ -935,7 +967,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
935
967
|
p, dataSource, agentId, userPayload, messagesJson, sessionId, pubSub,
|
|
936
968
|
data, payload, lastRunId, autoPopulateLastRunPayload, configurationId,
|
|
937
969
|
conversationDetailId, createArtifacts || false, createNotification || false,
|
|
938
|
-
sourceArtifactId, sourceArtifactVersionId
|
|
970
|
+
sourceArtifactId, sourceArtifactVersionId, conversationId
|
|
939
971
|
);
|
|
940
972
|
|
|
941
973
|
LogStatus(`🔥 Fire-and-forget: Agent ${agentId} execution started in background for session ${sessionId}`);
|
|
@@ -965,7 +997,8 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
965
997
|
createArtifacts || false,
|
|
966
998
|
createNotification || false,
|
|
967
999
|
sourceArtifactId,
|
|
968
|
-
sourceArtifactVersionId
|
|
1000
|
+
sourceArtifactVersionId,
|
|
1001
|
+
conversationId // LATENCY OPT #2: pass pre-resolved conversationId
|
|
969
1002
|
);
|
|
970
1003
|
} catch (error) {
|
|
971
1004
|
const errorMessage = (error as Error).message || 'Unknown error loading conversation history';
|
|
@@ -999,8 +1032,8 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
999
1032
|
throw new Error('Unable to determine current user');
|
|
1000
1033
|
}
|
|
1001
1034
|
|
|
1002
|
-
const
|
|
1003
|
-
const request = await
|
|
1035
|
+
const p = GetReadWriteProvider(providers);
|
|
1036
|
+
const request = await p.GetEntityObject<MJAIAgentRequestEntity>(
|
|
1004
1037
|
'MJ: AI Agent Requests',
|
|
1005
1038
|
currentUser
|
|
1006
1039
|
);
|
|
@@ -1064,7 +1097,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1064
1097
|
async ReassignAgentRequest(
|
|
1065
1098
|
@Arg('requestId') requestId: string,
|
|
1066
1099
|
@Arg('newUserID') newUserID: string,
|
|
1067
|
-
@Ctx() { userPayload }: AppContext,
|
|
1100
|
+
@Ctx() { userPayload, providers }: AppContext,
|
|
1068
1101
|
@Arg('note', { nullable: true }) note?: string
|
|
1069
1102
|
): Promise<AIAgentRunResult> {
|
|
1070
1103
|
const startTime = Date.now();
|
|
@@ -1074,8 +1107,8 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1074
1107
|
throw new Error('Unable to determine current user');
|
|
1075
1108
|
}
|
|
1076
1109
|
|
|
1077
|
-
const
|
|
1078
|
-
const request = await
|
|
1110
|
+
const p = GetReadWriteProvider(providers);
|
|
1111
|
+
const request = await p.GetEntityObject<MJAIAgentRequestEntity>(
|
|
1079
1112
|
'MJ: AI Agent Requests',
|
|
1080
1113
|
currentUser
|
|
1081
1114
|
);
|
|
@@ -1178,14 +1211,16 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1178
1211
|
createArtifacts: boolean = false,
|
|
1179
1212
|
createNotification: boolean = false,
|
|
1180
1213
|
sourceArtifactId?: string,
|
|
1181
|
-
sourceArtifactVersionId?: string
|
|
1214
|
+
sourceArtifactVersionId?: string,
|
|
1215
|
+
/** LATENCY OPT #2: Pre-resolved conversationId avoids redundant DB load in AgentRunner */
|
|
1216
|
+
conversationId?: string
|
|
1182
1217
|
): void {
|
|
1183
1218
|
// Execute in background - errors are handled within, not propagated
|
|
1184
1219
|
this.executeAIAgent(
|
|
1185
1220
|
p, dataSource, agentId, userPayload, messagesJson, sessionId, pubSub,
|
|
1186
1221
|
data, payload, undefined, lastRunId, autoPopulateLastRunPayload,
|
|
1187
1222
|
configurationId, conversationDetailId, createArtifacts, createNotification,
|
|
1188
|
-
sourceArtifactId, sourceArtifactVersionId
|
|
1223
|
+
sourceArtifactId, sourceArtifactVersionId, conversationId
|
|
1189
1224
|
).catch((error: unknown) => {
|
|
1190
1225
|
// Background execution failed unexpectedly (executeAIAgent has its own try-catch,
|
|
1191
1226
|
// so this would only fire for truly unexpected errors).
|
|
@@ -1210,34 +1245,40 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1210
1245
|
/**
|
|
1211
1246
|
* Load conversation history with attachments from database.
|
|
1212
1247
|
* Builds ChatMessage[] with multimodal content blocks for attachments.
|
|
1248
|
+
*
|
|
1249
|
+
* LATENCY OPTIMIZATIONS (plans/agent-latency-optimization.md — Opts #3 and #8):
|
|
1250
|
+
*
|
|
1251
|
+
* Opt #3: This method now accepts conversationId directly instead of conversationDetailId.
|
|
1252
|
+
* Previously it loaded a ConversationDetail entity object just to extract its ConversationID
|
|
1253
|
+
* field — a redundant DB round-trip (~40ms) since the caller already has this information.
|
|
1254
|
+
* The caller (RunAIAgentFromConversationDetail) now loads the ConversationDetail once and
|
|
1255
|
+
* passes conversationId down.
|
|
1256
|
+
*
|
|
1257
|
+
* Opt #8: Switched from ResultType 'entity_object' to 'simple' with explicit Fields.
|
|
1258
|
+
* The history query only needs ID, Role, and Message from each ConversationDetail record.
|
|
1259
|
+
* Using 'entity_object' created full BaseEntity instances with getters/setters, dirty tracking,
|
|
1260
|
+
* and validation — none of which are needed for read-only history assembly. The 'simple' result
|
|
1261
|
+
* type returns plain JS objects, reducing per-record overhead (~30ms total savings).
|
|
1213
1262
|
*/
|
|
1214
1263
|
private async loadConversationHistoryWithAttachments(
|
|
1215
|
-
|
|
1264
|
+
conversationId: string,
|
|
1216
1265
|
contextUser: UserInfo,
|
|
1217
|
-
maxMessages: number
|
|
1266
|
+
maxMessages: number,
|
|
1267
|
+
provider: IMetadataProvider
|
|
1218
1268
|
): Promise<ChatMessage[]> {
|
|
1219
|
-
const
|
|
1220
|
-
const
|
|
1221
|
-
const attachmentService = getAttachmentService();
|
|
1222
|
-
|
|
1223
|
-
// Load the current conversation detail to get the conversation ID
|
|
1224
|
-
const currentDetail = await md.GetEntityObject<MJConversationDetailEntity>(
|
|
1225
|
-
'MJ: Conversation Details',
|
|
1226
|
-
contextUser
|
|
1227
|
-
);
|
|
1228
|
-
if (!await currentDetail.Load(conversationDetailId)) {
|
|
1229
|
-
throw new Error(`Conversation detail ${conversationDetailId} not found`);
|
|
1230
|
-
}
|
|
1269
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
1270
|
+
const attachmentService = GetAttachmentService();
|
|
1231
1271
|
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
//
|
|
1235
|
-
const detailsResult = await rv.RunView<
|
|
1272
|
+
// Load recent conversation details (messages) for this conversation.
|
|
1273
|
+
// Only fetch the three fields we actually use — ID for attachment lookups,
|
|
1274
|
+
// Role for message routing, Message for content.
|
|
1275
|
+
const detailsResult = await rv.RunView<{ ID: string; Role: string; Message: string }>({
|
|
1236
1276
|
EntityName: 'MJ: Conversation Details',
|
|
1237
1277
|
ExtraFilter: `ConversationID='${conversationId}'`,
|
|
1238
1278
|
OrderBy: '__mj_CreatedAt DESC',
|
|
1239
1279
|
MaxRows: maxMessages,
|
|
1240
|
-
|
|
1280
|
+
Fields: ['ID', 'Role', 'Message'],
|
|
1281
|
+
ResultType: 'simple'
|
|
1241
1282
|
}, contextUser);
|
|
1242
1283
|
|
|
1243
1284
|
if (!detailsResult.Success || !detailsResult.Results) {
|
|
@@ -1251,9 +1292,12 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1251
1292
|
const messageIds = details.map(d => d.ID);
|
|
1252
1293
|
|
|
1253
1294
|
// Batch load all attachments for these messages
|
|
1254
|
-
const attachmentsByDetailId = await attachmentService.
|
|
1295
|
+
const attachmentsByDetailId = await attachmentService.GetAttachmentsBatch(messageIds, contextUser, provider);
|
|
1255
1296
|
|
|
1256
|
-
//
|
|
1297
|
+
// Batch load input artifacts for these messages
|
|
1298
|
+
const inputArtifactsByDetailId = await this.loadInputArtifactsBatch(messageIds, contextUser, provider);
|
|
1299
|
+
|
|
1300
|
+
// Build ChatMessage array with attachments and input artifacts
|
|
1257
1301
|
const messages: ChatMessage[] = [];
|
|
1258
1302
|
|
|
1259
1303
|
for (const detail of details) {
|
|
@@ -1262,7 +1306,7 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1262
1306
|
|
|
1263
1307
|
// Get attachment data with content URLs (handles both inline and FileID storage)
|
|
1264
1308
|
const attachmentDataPromises = attachments.map(att =>
|
|
1265
|
-
attachmentService.
|
|
1309
|
+
attachmentService.GetAttachmentData(att, contextUser, provider)
|
|
1266
1310
|
);
|
|
1267
1311
|
const attachmentDataResults = await Promise.all(attachmentDataPromises);
|
|
1268
1312
|
|
|
@@ -1280,12 +1324,38 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1280
1324
|
content: result.contentUrl
|
|
1281
1325
|
}));
|
|
1282
1326
|
|
|
1327
|
+
// Get input artifacts for this message and convert to AttachmentData
|
|
1328
|
+
const inputArtifacts = inputArtifactsByDetailId.get(detail.ID) || [];
|
|
1329
|
+
for (const artifactVersion of inputArtifacts) {
|
|
1330
|
+
if (artifactVersion.ContentMode === 'File' && artifactVersion.FileID) {
|
|
1331
|
+
// File-backed artifact — download content and treat like a document attachment
|
|
1332
|
+
const fileContent = await this.downloadArtifactFileContent(artifactVersion, contextUser, provider);
|
|
1333
|
+
if (fileContent) {
|
|
1334
|
+
validAttachments.push({
|
|
1335
|
+
type: ConversationUtility.GetAttachmentTypeFromMime(artifactVersion.MimeType || ''),
|
|
1336
|
+
mimeType: artifactVersion.MimeType || 'application/octet-stream',
|
|
1337
|
+
fileName: artifactVersion.FileName || artifactVersion.Name || undefined,
|
|
1338
|
+
sizeBytes: artifactVersion.ContentSizeBytes || undefined,
|
|
1339
|
+
content: fileContent
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
} else if (artifactVersion.Content) {
|
|
1343
|
+
// Text artifact — include content directly as a text attachment
|
|
1344
|
+
validAttachments.push({
|
|
1345
|
+
type: 'Document' as AttachmentData['type'],
|
|
1346
|
+
mimeType: 'text/plain',
|
|
1347
|
+
fileName: artifactVersion.Name || 'artifact.txt',
|
|
1348
|
+
content: `[Artifact: ${artifactVersion.Name || 'Untitled'}]\n\n${artifactVersion.Content}`
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1283
1353
|
// Build message content (with or without attachments)
|
|
1284
|
-
let content:
|
|
1354
|
+
let content: ChatMessageContent;
|
|
1285
1355
|
|
|
1286
1356
|
if (validAttachments.length > 0) {
|
|
1287
1357
|
// Use ConversationUtility to build multimodal content blocks
|
|
1288
|
-
content = ConversationUtility.BuildChatMessageContent(
|
|
1358
|
+
content = await ConversationUtility.BuildChatMessageContent(
|
|
1289
1359
|
detail.Message || '',
|
|
1290
1360
|
validAttachments
|
|
1291
1361
|
);
|
|
@@ -1313,4 +1383,87 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1313
1383
|
return 'user'; // Default to user
|
|
1314
1384
|
}
|
|
1315
1385
|
|
|
1386
|
+
/**
|
|
1387
|
+
* Batch load input artifact versions for conversation details.
|
|
1388
|
+
* Returns a map of ConversationDetailID -> ArtifactVersion[]
|
|
1389
|
+
*/
|
|
1390
|
+
private async loadInputArtifactsBatch(
|
|
1391
|
+
conversationDetailIds: string[],
|
|
1392
|
+
contextUser: UserInfo,
|
|
1393
|
+
provider: IMetadataProvider
|
|
1394
|
+
): Promise<Map<string, MJArtifactVersionEntity[]>> {
|
|
1395
|
+
const map = new Map<string, MJArtifactVersionEntity[]>();
|
|
1396
|
+
if (conversationDetailIds.length === 0) return map;
|
|
1397
|
+
|
|
1398
|
+
const rv = RunView.FromMetadataProvider(provider);
|
|
1399
|
+
const idList = conversationDetailIds.map(id => `'${id}'`).join(',');
|
|
1400
|
+
|
|
1401
|
+
// Load ConversationDetailArtifact links with Direction='Input'
|
|
1402
|
+
const linksResult = await rv.RunView<MJConversationDetailArtifactEntity>({
|
|
1403
|
+
EntityName: 'MJ: Conversation Detail Artifacts',
|
|
1404
|
+
ExtraFilter: `ConversationDetailID IN (${idList}) AND Direction = 'Input'`,
|
|
1405
|
+
ResultType: 'entity_object'
|
|
1406
|
+
}, contextUser);
|
|
1407
|
+
|
|
1408
|
+
if (!linksResult.Success || !linksResult.Results || linksResult.Results.length === 0) {
|
|
1409
|
+
return map;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// Load the referenced artifact versions
|
|
1413
|
+
const versionIds = linksResult.Results.map(l => `'${l.ArtifactVersionID}'`).join(',');
|
|
1414
|
+
const versionsResult = await rv.RunView<MJArtifactVersionEntity>({
|
|
1415
|
+
EntityName: 'MJ: Artifact Versions',
|
|
1416
|
+
ExtraFilter: `ID IN (${versionIds})`,
|
|
1417
|
+
ResultType: 'entity_object'
|
|
1418
|
+
}, contextUser);
|
|
1419
|
+
|
|
1420
|
+
if (!versionsResult.Success || !versionsResult.Results) return map;
|
|
1421
|
+
|
|
1422
|
+
// Build a lookup of version ID -> version entity
|
|
1423
|
+
const versionMap = new Map<string, MJArtifactVersionEntity>();
|
|
1424
|
+
for (const v of versionsResult.Results) {
|
|
1425
|
+
versionMap.set(v.ID, v);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// Group by conversation detail ID
|
|
1429
|
+
for (const link of linksResult.Results) {
|
|
1430
|
+
const version = versionMap.get(link.ArtifactVersionID);
|
|
1431
|
+
if (version) {
|
|
1432
|
+
const existing = map.get(link.ConversationDetailID) || [];
|
|
1433
|
+
existing.push(version);
|
|
1434
|
+
map.set(link.ConversationDetailID, existing);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
return map;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* Download file content from an artifact version's FileID.
|
|
1443
|
+
* Returns base64 data URL for extraction pipeline compatibility.
|
|
1444
|
+
* Uses the same downloadFileContent path as ConversationAttachmentService
|
|
1445
|
+
* to avoid Box driver path resolution issues.
|
|
1446
|
+
*/
|
|
1447
|
+
private async downloadArtifactFileContent(
|
|
1448
|
+
artifactVersion: MJArtifactVersionEntity,
|
|
1449
|
+
contextUser: UserInfo,
|
|
1450
|
+
provider: IMetadataProvider
|
|
1451
|
+
): Promise<string | null> {
|
|
1452
|
+
if (!artifactVersion.FileID) return null;
|
|
1453
|
+
|
|
1454
|
+
try {
|
|
1455
|
+
// Use the attachment service's downloadFileContent which uses GetObject directly
|
|
1456
|
+
const attachmentService = GetAttachmentService();
|
|
1457
|
+
const buffer = await attachmentService.DownloadFileContent(artifactVersion.FileID, contextUser, provider);
|
|
1458
|
+
if (!buffer) return null;
|
|
1459
|
+
|
|
1460
|
+
const base64 = buffer.toString('base64');
|
|
1461
|
+
const mimeType = artifactVersion.MimeType || 'application/octet-stream';
|
|
1462
|
+
return `data:${mimeType};base64,${base64}`;
|
|
1463
|
+
} catch (err) {
|
|
1464
|
+
LogError(`Failed to download artifact file ${artifactVersion.FileID}: ${err}`);
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1316
1469
|
}
|