@memberjunction/server 5.27.1 → 5.29.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/config.d.ts +151 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -1
- package/dist/generated/generated.d.ts +959 -5
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +4639 -280
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +14 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +37 -3
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RestoreContextInput.d.ts +27 -0
- package/dist/generic/RestoreContextInput.d.ts.map +1 -0
- package/dist/generic/RestoreContextInput.js +39 -0
- package/dist/generic/RestoreContextInput.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/resolvers/FeedbackResolver.d.ts +150 -0
- package/dist/resolvers/FeedbackResolver.d.ts.map +1 -0
- package/dist/resolvers/FeedbackResolver.js +876 -0
- package/dist/resolvers/FeedbackResolver.js.map +1 -0
- package/dist/resolvers/FileResolver.d.ts +27 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +32 -3
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +18 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
- package/dist/resolvers/IntegrationDiscoveryResolver.js +247 -22
- package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +77 -0
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +300 -1
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +87 -32
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/package.json +68 -66
- package/src/config.ts +19 -0
- package/src/generated/generated.ts +3430 -281
- package/src/generic/ResolverBase.ts +41 -4
- package/src/generic/RestoreContextInput.ts +32 -0
- package/src/index.ts +22 -5
- package/src/resolvers/FeedbackResolver.ts +940 -0
- package/src/resolvers/FileResolver.ts +33 -4
- package/src/resolvers/IntegrationDiscoveryResolver.ts +224 -20
- package/src/resolvers/MCPResolver.ts +297 -1
- package/src/resolvers/RunAIAgentResolver.ts +89 -32
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* including tool synchronization with progress streaming and OAuth management.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Resolver, Mutation, Query, Subscription, Arg, Ctx, Root, Field, ObjectType, InputType, PubSub, registerEnumType } from 'type-graphql';
|
|
8
|
+
import { Resolver, Mutation, Query, Subscription, Arg, Ctx, Root, Field, Int, ObjectType, InputType, PubSub, registerEnumType } from 'type-graphql';
|
|
9
9
|
import { PubSubEngine } from 'type-graphql';
|
|
10
10
|
import { LogError, LogStatus, UserInfo, Metadata, RunView } from '@memberjunction/core';
|
|
11
11
|
import {
|
|
@@ -455,6 +455,96 @@ export class MCPOAuthEventNotification {
|
|
|
455
455
|
RequiresReauthorization?: boolean;
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
/**
|
|
459
|
+
* Summary of an MCP Server Tool record — only the fields needed for
|
|
460
|
+
* paginated card/list display. Heavy fields (InputSchema, OutputSchema,
|
|
461
|
+
* Annotations) are intentionally excluded to keep responses small.
|
|
462
|
+
*/
|
|
463
|
+
@ObjectType()
|
|
464
|
+
export class MCPToolSummary {
|
|
465
|
+
@Field()
|
|
466
|
+
ID: string;
|
|
467
|
+
|
|
468
|
+
@Field()
|
|
469
|
+
MCPServerID: string;
|
|
470
|
+
|
|
471
|
+
@Field()
|
|
472
|
+
ToolName: string;
|
|
473
|
+
|
|
474
|
+
@Field({ nullable: true })
|
|
475
|
+
ToolTitle?: string;
|
|
476
|
+
|
|
477
|
+
@Field({ nullable: true })
|
|
478
|
+
ToolDescription?: string;
|
|
479
|
+
|
|
480
|
+
@Field()
|
|
481
|
+
Status: string;
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* MCP Server display name (from the vwMCPServerTools view — column name is "MCPServer").
|
|
485
|
+
*/
|
|
486
|
+
@Field({ nullable: true })
|
|
487
|
+
ServerName?: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Paginated result for MCPToolSummary listings.
|
|
492
|
+
*/
|
|
493
|
+
@ObjectType()
|
|
494
|
+
export class MCPToolPageResult {
|
|
495
|
+
@Field(() => [MCPToolSummary])
|
|
496
|
+
items: MCPToolSummary[];
|
|
497
|
+
|
|
498
|
+
@Field(() => Int)
|
|
499
|
+
totalCount: number;
|
|
500
|
+
|
|
501
|
+
@Field()
|
|
502
|
+
hasMore: boolean;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Count-per-server aggregate.
|
|
507
|
+
*/
|
|
508
|
+
@ObjectType()
|
|
509
|
+
export class MCPToolServerCount {
|
|
510
|
+
@Field()
|
|
511
|
+
serverID: string;
|
|
512
|
+
|
|
513
|
+
@Field()
|
|
514
|
+
serverName: string;
|
|
515
|
+
|
|
516
|
+
@Field(() => Int)
|
|
517
|
+
count: number;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Count-per-category aggregate. Category is derived from the ToolName
|
|
522
|
+
* prefix (substring before first underscore).
|
|
523
|
+
*/
|
|
524
|
+
@ObjectType()
|
|
525
|
+
export class MCPToolCategoryCount {
|
|
526
|
+
@Field()
|
|
527
|
+
category: string;
|
|
528
|
+
|
|
529
|
+
@Field(() => Int)
|
|
530
|
+
count: number;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Top-level counts payload for MCP tool group/badge displays.
|
|
535
|
+
*/
|
|
536
|
+
@ObjectType()
|
|
537
|
+
export class MCPToolCountsResult {
|
|
538
|
+
@Field(() => Int)
|
|
539
|
+
totalCount: number;
|
|
540
|
+
|
|
541
|
+
@Field(() => [MCPToolServerCount])
|
|
542
|
+
countByServer: MCPToolServerCount[];
|
|
543
|
+
|
|
544
|
+
@Field(() => [MCPToolCategoryCount])
|
|
545
|
+
countByCategory: MCPToolCategoryCount[];
|
|
546
|
+
}
|
|
547
|
+
|
|
458
548
|
/**
|
|
459
549
|
* MCP Resolver for GraphQL operations
|
|
460
550
|
*/
|
|
@@ -1325,6 +1415,212 @@ export class MCPResolver extends ResolverBase {
|
|
|
1325
1415
|
};
|
|
1326
1416
|
}
|
|
1327
1417
|
|
|
1418
|
+
/**
|
|
1419
|
+
* Returns a paginated list of MCP tool summaries filtered by optional
|
|
1420
|
+
* server, category, and free-text search. Uses narrow Fields + simple
|
|
1421
|
+
* ResultType to keep payload sizes manageable at thousands of tools.
|
|
1422
|
+
*
|
|
1423
|
+
* @param skip Number of rows to skip (for pagination)
|
|
1424
|
+
* @param take Page size
|
|
1425
|
+
* @param searchText Free-text filter against ToolName, ToolTitle, ToolDescription
|
|
1426
|
+
* @param serverID Optional MCPServerID filter
|
|
1427
|
+
* @param category Optional category filter (ToolName prefix before first underscore)
|
|
1428
|
+
* @param ctx GraphQL context
|
|
1429
|
+
*/
|
|
1430
|
+
@Query(() => MCPToolPageResult)
|
|
1431
|
+
async GetMCPToolsPage(
|
|
1432
|
+
@Arg('skip', () => Int) skip: number,
|
|
1433
|
+
@Arg('take', () => Int) take: number,
|
|
1434
|
+
@Arg('searchText', { nullable: true }) searchText: string | null,
|
|
1435
|
+
@Arg('serverID', { nullable: true }) serverID: string | null,
|
|
1436
|
+
@Arg('category', { nullable: true }) category: string | null,
|
|
1437
|
+
@Ctx() ctx: AppContext
|
|
1438
|
+
): Promise<MCPToolPageResult> {
|
|
1439
|
+
const user = ctx.userPayload.userRecord;
|
|
1440
|
+
if (!user) {
|
|
1441
|
+
return { items: [], totalCount: 0, hasMore: false };
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
try {
|
|
1445
|
+
const extraFilter = this.buildMCPToolsFilter(searchText, serverID, category);
|
|
1446
|
+
const rv = new RunView();
|
|
1447
|
+
|
|
1448
|
+
const result = await rv.RunView<{
|
|
1449
|
+
ID: string;
|
|
1450
|
+
MCPServerID: string;
|
|
1451
|
+
ToolName: string;
|
|
1452
|
+
ToolTitle: string | null;
|
|
1453
|
+
ToolDescription: string | null;
|
|
1454
|
+
Status: string;
|
|
1455
|
+
MCPServer: string | null;
|
|
1456
|
+
}>({
|
|
1457
|
+
EntityName: 'MJ: MCP Server Tools',
|
|
1458
|
+
ExtraFilter: extraFilter,
|
|
1459
|
+
OrderBy: 'ToolName ASC',
|
|
1460
|
+
Fields: ['ID', 'MCPServerID', 'ToolName', 'ToolTitle', 'ToolDescription', 'Status', 'MCPServer'],
|
|
1461
|
+
StartRow: skip,
|
|
1462
|
+
MaxRows: take,
|
|
1463
|
+
IgnoreMaxRows: false,
|
|
1464
|
+
ResultType: 'simple'
|
|
1465
|
+
}, user);
|
|
1466
|
+
|
|
1467
|
+
if (!result.Success) {
|
|
1468
|
+
LogError(`MCPResolver: GetMCPToolsPage failed: ${result.ErrorMessage}`);
|
|
1469
|
+
return { items: [], totalCount: 0, hasMore: false };
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const rows = result.Results || [];
|
|
1473
|
+
// RunView's TotalRowCount can be capped when MaxRows is applied. Do an explicit
|
|
1474
|
+
// count query (ignoring MaxRows) so the client sees the true filtered match total.
|
|
1475
|
+
let totalCount = result.TotalRowCount ?? rows.length;
|
|
1476
|
+
try {
|
|
1477
|
+
const countResult = await rv.RunView<{ ID: string }>({
|
|
1478
|
+
EntityName: 'MJ: MCP Server Tools',
|
|
1479
|
+
ExtraFilter: extraFilter,
|
|
1480
|
+
Fields: ['ID'],
|
|
1481
|
+
IgnoreMaxRows: true,
|
|
1482
|
+
ResultType: 'simple'
|
|
1483
|
+
}, user);
|
|
1484
|
+
if (countResult.Success) {
|
|
1485
|
+
totalCount = countResult.Results.length;
|
|
1486
|
+
}
|
|
1487
|
+
} catch {
|
|
1488
|
+
// fall back to the initial totalCount on any error
|
|
1489
|
+
}
|
|
1490
|
+
const items: MCPToolSummary[] = rows.map(r => ({
|
|
1491
|
+
ID: r.ID,
|
|
1492
|
+
MCPServerID: r.MCPServerID,
|
|
1493
|
+
ToolName: r.ToolName,
|
|
1494
|
+
ToolTitle: r.ToolTitle ?? undefined,
|
|
1495
|
+
ToolDescription: r.ToolDescription ?? undefined,
|
|
1496
|
+
Status: r.Status,
|
|
1497
|
+
ServerName: r.MCPServer ?? undefined
|
|
1498
|
+
}));
|
|
1499
|
+
|
|
1500
|
+
return {
|
|
1501
|
+
items,
|
|
1502
|
+
totalCount,
|
|
1503
|
+
hasMore: skip + items.length < totalCount
|
|
1504
|
+
};
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1507
|
+
LogError(`MCPResolver: GetMCPToolsPage exception: ${errorMsg}`);
|
|
1508
|
+
return { items: [], totalCount: 0, hasMore: false };
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/**
|
|
1513
|
+
* Returns aggregate counts for MCP tools — total, per-server, and per-category
|
|
1514
|
+
* (category = ToolName prefix before the first underscore). Used to render
|
|
1515
|
+
* group headers with badges like "Zapier (1,247 tools)".
|
|
1516
|
+
*
|
|
1517
|
+
* @param serverID Optional MCPServerID filter (counts scoped to that server)
|
|
1518
|
+
* @param searchText Optional free-text filter (same semantics as GetMCPToolsPage)
|
|
1519
|
+
* @param ctx GraphQL context
|
|
1520
|
+
*/
|
|
1521
|
+
@Query(() => MCPToolCountsResult)
|
|
1522
|
+
async GetMCPToolCounts(
|
|
1523
|
+
@Arg('serverID', { nullable: true }) serverID: string | null,
|
|
1524
|
+
@Arg('searchText', { nullable: true }) searchText: string | null,
|
|
1525
|
+
@Ctx() ctx: AppContext
|
|
1526
|
+
): Promise<MCPToolCountsResult> {
|
|
1527
|
+
const user = ctx.userPayload.userRecord;
|
|
1528
|
+
if (!user) {
|
|
1529
|
+
return { totalCount: 0, countByServer: [], countByCategory: [] };
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
try {
|
|
1533
|
+
// Build filter WITHOUT the category constraint — counts need the full
|
|
1534
|
+
// matching set so we can bucket by server and by category ourselves.
|
|
1535
|
+
const extraFilter = this.buildMCPToolsFilter(searchText, serverID, null);
|
|
1536
|
+
const rv = new RunView();
|
|
1537
|
+
|
|
1538
|
+
// Fetch minimal fields for ALL matching rows. IgnoreMaxRows so we
|
|
1539
|
+
// don't silently truncate at entity caps when counting.
|
|
1540
|
+
const result = await rv.RunView<{
|
|
1541
|
+
MCPServerID: string;
|
|
1542
|
+
ToolName: string;
|
|
1543
|
+
MCPServer: string | null;
|
|
1544
|
+
}>({
|
|
1545
|
+
EntityName: 'MJ: MCP Server Tools',
|
|
1546
|
+
ExtraFilter: extraFilter,
|
|
1547
|
+
Fields: ['MCPServerID', 'ToolName', 'MCPServer'],
|
|
1548
|
+
IgnoreMaxRows: true,
|
|
1549
|
+
ResultType: 'simple'
|
|
1550
|
+
}, user);
|
|
1551
|
+
|
|
1552
|
+
if (!result.Success) {
|
|
1553
|
+
LogError(`MCPResolver: GetMCPToolCounts failed: ${result.ErrorMessage}`);
|
|
1554
|
+
return { totalCount: 0, countByServer: [], countByCategory: [] };
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
const rows = result.Results || [];
|
|
1558
|
+
const totalCount = rows.length;
|
|
1559
|
+
|
|
1560
|
+
// Bucket by server
|
|
1561
|
+
const serverMap = new Map<string, { serverID: string; serverName: string; count: number }>();
|
|
1562
|
+
// Bucket by category (ToolName prefix before first underscore)
|
|
1563
|
+
const categoryMap = new Map<string, number>();
|
|
1564
|
+
|
|
1565
|
+
for (const r of rows) {
|
|
1566
|
+
const sid = r.MCPServerID;
|
|
1567
|
+
const sname = r.MCPServer ?? '';
|
|
1568
|
+
const existing = serverMap.get(sid);
|
|
1569
|
+
if (existing) {
|
|
1570
|
+
existing.count++;
|
|
1571
|
+
} else {
|
|
1572
|
+
serverMap.set(sid, { serverID: sid, serverName: sname, count: 1 });
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const underscore = r.ToolName.indexOf('_');
|
|
1576
|
+
const cat = underscore > 0 ? r.ToolName.substring(0, underscore) : r.ToolName;
|
|
1577
|
+
categoryMap.set(cat, (categoryMap.get(cat) ?? 0) + 1);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
const countByServer = Array.from(serverMap.values()).sort((a, b) => b.count - a.count);
|
|
1581
|
+
const countByCategory = Array.from(categoryMap.entries())
|
|
1582
|
+
.map(([category, count]) => ({ category, count }))
|
|
1583
|
+
.sort((a, b) => b.count - a.count);
|
|
1584
|
+
|
|
1585
|
+
return { totalCount, countByServer, countByCategory };
|
|
1586
|
+
} catch (error) {
|
|
1587
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1588
|
+
LogError(`MCPResolver: GetMCPToolCounts exception: ${errorMsg}`);
|
|
1589
|
+
return { totalCount: 0, countByServer: [], countByCategory: [] };
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/**
|
|
1594
|
+
* Builds the ExtraFilter string for MCP Server Tools queries. All user
|
|
1595
|
+
* inputs are single-quote-escaped to prevent SQL injection.
|
|
1596
|
+
*/
|
|
1597
|
+
private buildMCPToolsFilter(
|
|
1598
|
+
searchText: string | null | undefined,
|
|
1599
|
+
serverID: string | null | undefined,
|
|
1600
|
+
category: string | null | undefined
|
|
1601
|
+
): string {
|
|
1602
|
+
const clauses: string[] = [];
|
|
1603
|
+
|
|
1604
|
+
if (serverID && serverID.trim().length > 0) {
|
|
1605
|
+
const safe = serverID.replace(/'/g, "''");
|
|
1606
|
+
clauses.push(`MCPServerID='${safe}'`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
if (category && category.trim().length > 0) {
|
|
1610
|
+
const safe = category.replace(/'/g, "''");
|
|
1611
|
+
clauses.push(`ToolName LIKE '${safe}\\_%' ESCAPE '\\'`);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
if (searchText && searchText.trim().length > 0) {
|
|
1615
|
+
const safe = searchText.replace(/'/g, "''");
|
|
1616
|
+
clauses.push(
|
|
1617
|
+
`(ToolName LIKE '%${safe}%' OR ToolTitle LIKE '%${safe}%' OR ToolDescription LIKE '%${safe}%')`
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
return clauses.join(' AND ');
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1328
1624
|
/**
|
|
1329
1625
|
* Gets the public URL for OAuth callbacks
|
|
1330
1626
|
*/
|
|
@@ -2,7 +2,7 @@ import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, Field, PubSub, PubSubE
|
|
|
2
2
|
import { AppContext, UserPayload } from '../types.js';
|
|
3
3
|
import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo, IMetadataProvider } from '@memberjunction/core';
|
|
4
4
|
import { MJConversationDetailEntity, MJConversationDetailAttachmentEntity, MJConversationDetailArtifactEntity, MJArtifactVersionEntity, MJAIAgentRequestEntity } from '@memberjunction/core-entities';
|
|
5
|
-
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
5
|
+
import { AgentRunner, ArtifactToolManager } 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
8
|
import { ChatMessage, ChatMessageContent } from '@memberjunction/ai';
|
|
@@ -1310,43 +1310,100 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
1310
1310
|
);
|
|
1311
1311
|
const attachmentDataResults = await Promise.all(attachmentDataPromises);
|
|
1312
1312
|
|
|
1313
|
-
// Filter out nulls and convert to AttachmentData format
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1313
|
+
// Filter out nulls and convert to AttachmentData format.
|
|
1314
|
+
// IMPORTANT: Skip large document attachments that are handled by artifact tools.
|
|
1315
|
+
// These file types (PDF, Excel, Word) are accessible via ArtifactToolManager
|
|
1316
|
+
// and embedding their multi-MB base64 content in the conversation causes
|
|
1317
|
+
// context overflow on follow-up messages when conversation history is replayed.
|
|
1318
|
+
// Images are kept inline since LLMs handle them natively via multimodal.
|
|
1319
|
+
const ARTIFACT_TOOL_MIME_PREFIXES = [
|
|
1320
|
+
'application/pdf',
|
|
1321
|
+
'application/vnd.openxmlformats-officedocument', // .docx, .xlsx, .pptx
|
|
1322
|
+
'application/vnd.ms-excel', // .xls
|
|
1323
|
+
'application/msword', // .doc
|
|
1324
|
+
];
|
|
1325
|
+
|
|
1326
|
+
const validAttachments: AttachmentData[] = [];
|
|
1327
|
+
|
|
1328
|
+
for (const result of attachmentDataResults) {
|
|
1329
|
+
if (!result) continue;
|
|
1330
|
+
const mime = result.attachment.MimeType || '';
|
|
1331
|
+
const isArtifactToolType = ARTIFACT_TOOL_MIME_PREFIXES.some(prefix => mime.startsWith(prefix));
|
|
1332
|
+
if (isArtifactToolType) {
|
|
1333
|
+
// Skip raw file embedding — the agent accesses the file via
|
|
1334
|
+
// artifact tools (manifest injected into prompt) and/or native
|
|
1335
|
+
// file input (resolved per-driver in AIPromptRunner).
|
|
1336
|
+
// Do NOT add a placeholder attachment: 'Document' maps to 'file_url'
|
|
1337
|
+
// content blocks, and drivers attempt base64 decoding of the text,
|
|
1338
|
+
// causing API errors.
|
|
1339
|
+
} else {
|
|
1340
|
+
validAttachments.push({
|
|
1341
|
+
type: ConversationUtility.GetAttachmentTypeFromMime(result.attachment.MimeType),
|
|
1342
|
+
mimeType: result.attachment.MimeType,
|
|
1343
|
+
fileName: result.attachment.FileName ?? undefined,
|
|
1344
|
+
sizeBytes: result.attachment.FileSizeBytes ?? undefined,
|
|
1345
|
+
width: result.attachment.Width ?? undefined,
|
|
1346
|
+
height: result.attachment.Height ?? undefined,
|
|
1347
|
+
durationSeconds: result.attachment.DurationSeconds ?? undefined,
|
|
1348
|
+
content: result.contentUrl
|
|
1349
|
+
});
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Get input artifacts for this message and convert to AttachmentData.
|
|
1354
|
+
// Like regular attachments above, skip file-backed artifacts whose MIME
|
|
1355
|
+
// types are handled by ArtifactToolManager — embedding their multi-MB
|
|
1356
|
+
// base64 content in every conversation replay causes context overflow.
|
|
1328
1357
|
const inputArtifacts = inputArtifactsByDetailId.get(detail.ID) || [];
|
|
1329
1358
|
for (const artifactVersion of inputArtifacts) {
|
|
1359
|
+
const artifactMime = artifactVersion.MimeType || '';
|
|
1360
|
+
const isArtifactToolHandled = ARTIFACT_TOOL_MIME_PREFIXES.some(
|
|
1361
|
+
prefix => artifactMime.startsWith(prefix)
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1330
1364
|
if (artifactVersion.ContentMode === 'File' && artifactVersion.FileID) {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1365
|
+
if (isArtifactToolHandled) {
|
|
1366
|
+
// Skip raw file embedding — the agent accesses the file via
|
|
1367
|
+
// artifact tools (manifest injected into prompt) and/or native
|
|
1368
|
+
// file input (resolved per-driver in AIPromptRunner).
|
|
1369
|
+
// Do NOT add a placeholder attachment here: 'Document' maps to
|
|
1370
|
+
// 'file_url' content blocks, and drivers attempt base64 decoding
|
|
1371
|
+
// of the placeholder text, causing API errors.
|
|
1372
|
+
} else {
|
|
1373
|
+
// Non-artifact-tool file types: embed normally
|
|
1374
|
+
const fileContent = await this.downloadArtifactFileContent(artifactVersion, contextUser, provider);
|
|
1375
|
+
if (fileContent) {
|
|
1376
|
+
validAttachments.push({
|
|
1377
|
+
type: ConversationUtility.GetAttachmentTypeFromMime(artifactMime),
|
|
1378
|
+
mimeType: artifactMime || 'application/octet-stream',
|
|
1379
|
+
fileName: artifactVersion.FileName || artifactVersion.Name || undefined,
|
|
1380
|
+
sizeBytes: artifactVersion.ContentSizeBytes || undefined,
|
|
1381
|
+
content: fileContent
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
} else if (artifactVersion.Content) {
|
|
1386
|
+
// Text artifact — use BuildInlinePreview for large DataSnapshots
|
|
1387
|
+
// to preserve table structure instead of raw substring truncation.
|
|
1388
|
+
const textContent = artifactVersion.Content;
|
|
1389
|
+
const MAX_INLINE_ARTIFACT_CHARS = 10_000;
|
|
1390
|
+
|
|
1391
|
+
if (textContent.length > MAX_INLINE_ARTIFACT_CHARS) {
|
|
1392
|
+
const preview = ArtifactToolManager.BuildInlinePreview(textContent, 5);
|
|
1393
|
+
validAttachments.push({
|
|
1394
|
+
type: 'Document' as AttachmentData['type'],
|
|
1395
|
+
mimeType: 'text/plain',
|
|
1396
|
+
fileName: undefined,
|
|
1397
|
+
content: `[Artifact: ${artifactVersion.Name || 'Untitled'} — ${textContent.length.toLocaleString()} chars, accessible via artifact tools]\n\nPreview (first 5 rows per table):\n${preview}`
|
|
1398
|
+
});
|
|
1399
|
+
} else {
|
|
1334
1400
|
validAttachments.push({
|
|
1335
|
-
type:
|
|
1336
|
-
mimeType:
|
|
1337
|
-
fileName: artifactVersion.
|
|
1338
|
-
|
|
1339
|
-
content: fileContent
|
|
1401
|
+
type: 'Document' as AttachmentData['type'],
|
|
1402
|
+
mimeType: 'text/plain',
|
|
1403
|
+
fileName: artifactVersion.Name || 'artifact.txt',
|
|
1404
|
+
content: `[Artifact: ${artifactVersion.Name || 'Untitled'}]\n\n${textContent}`
|
|
1340
1405
|
});
|
|
1341
1406
|
}
|
|
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
1407
|
}
|
|
1351
1408
|
}
|
|
1352
1409
|
|