@eventcatalog/core 3.40.1 → 3.41.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 (139) hide show
  1. package/bin/eventcatalog.config.d.ts +1 -0
  2. package/dist/__mocks__/astro-content.d.cts +1 -1
  3. package/dist/__mocks__/astro-content.d.ts +1 -1
  4. package/dist/analytics/analytics.cjs +1 -1
  5. package/dist/analytics/analytics.js +2 -2
  6. package/dist/analytics/count-resources.cjs +1 -0
  7. package/dist/analytics/count-resources.js +1 -1
  8. package/dist/analytics/log-build.cjs +3 -2
  9. package/dist/analytics/log-build.js +4 -4
  10. package/dist/catalog-to-astro-content-directory.cjs +1 -0
  11. package/dist/catalog-to-astro-content-directory.js +2 -2
  12. package/dist/{chunk-4UVFXLPI.js → chunk-3DVHEVHQ.js} +1 -0
  13. package/dist/{chunk-K3ZVEX2Y.js → chunk-3H2RT3CM.js} +1 -1
  14. package/dist/{chunk-BRMLU4PR.js → chunk-3R6TKNHG.js} +1 -1
  15. package/dist/{chunk-OIVICT4V.js → chunk-DKFIEB24.js} +1 -1
  16. package/dist/{chunk-55D645EH.js → chunk-IR4IAKWS.js} +1 -0
  17. package/dist/{chunk-YDXB3BD2.js → chunk-O6KT4DPL.js} +1 -1
  18. package/dist/{chunk-D6IBLY3O.js → chunk-QMORF42U.js} +1 -0
  19. package/dist/{chunk-HNG4KOYQ.js → chunk-QVJGIQYP.js} +1 -1
  20. package/dist/{chunk-4OEF5W6Y.js → chunk-TWFS6THS.js} +1 -1
  21. package/dist/{chunk-7UR72UMK.js → chunk-ZN3JKTWB.js} +5 -5
  22. package/dist/constants.cjs +1 -1
  23. package/dist/constants.js +1 -1
  24. package/dist/eventcatalog.cjs +5 -2
  25. package/dist/eventcatalog.config.d.cts +27 -11
  26. package/dist/eventcatalog.config.d.ts +27 -11
  27. package/dist/eventcatalog.js +14 -14
  28. package/dist/generate.cjs +1 -1
  29. package/dist/generate.js +3 -3
  30. package/dist/map-catalog-to-astro.cjs +1 -0
  31. package/dist/map-catalog-to-astro.js +1 -1
  32. package/dist/search-indexer.cjs +1 -0
  33. package/dist/search-indexer.js +1 -1
  34. package/dist/utils/cli-logger.cjs +1 -1
  35. package/dist/utils/cli-logger.js +2 -2
  36. package/dist/watcher.cjs +1 -0
  37. package/dist/watcher.js +2 -2
  38. package/eventcatalog/public/agents/anthropic-dark.svg +1 -0
  39. package/eventcatalog/public/agents/anthropic-light.svg +1 -0
  40. package/eventcatalog/public/agents/openai-dark.svg +1 -0
  41. package/eventcatalog/public/agents/openai-light.svg +1 -0
  42. package/eventcatalog/public/agents/openai.svg +1 -0
  43. package/eventcatalog/public/icons/agent/anthropic-dark.svg +1 -0
  44. package/eventcatalog/public/icons/agent/anthropic-light.svg +1 -0
  45. package/eventcatalog/public/icons/agent/anthropic.svg +1 -0
  46. package/eventcatalog/public/icons/agent/gemini.svg +1 -0
  47. package/eventcatalog/public/icons/agent/openai-dark.svg +1 -0
  48. package/eventcatalog/public/icons/agent/openai-light.svg +1 -0
  49. package/eventcatalog/public/icons/agent/openai.svg +1 -0
  50. package/eventcatalog/public/icons/agents/anthropic-dark.svg +1 -0
  51. package/eventcatalog/public/icons/agents/anthropic-light.svg +1 -0
  52. package/eventcatalog/public/icons/agents/anthropic.svg +1 -0
  53. package/eventcatalog/public/icons/agents/gemini.svg +1 -0
  54. package/eventcatalog/public/icons/agents/openai-dark.svg +1 -0
  55. package/eventcatalog/public/icons/agents/openai-light.svg +1 -0
  56. package/eventcatalog/public/icons/agents/openai.svg +1 -0
  57. package/eventcatalog/public/icons/protocols/mcp-dark.svg +1 -0
  58. package/eventcatalog/public/icons/protocols/mcp-light.svg +1 -0
  59. package/eventcatalog/public/icons/protocols/mcp.svg +1 -0
  60. package/eventcatalog/public/icons/tools/datadog.svg +1 -0
  61. package/eventcatalog/public/icons/tools/github.svg +1 -0
  62. package/eventcatalog/public/icons/tools/hubspot.svg +1 -0
  63. package/eventcatalog/public/icons/tools/slack.svg +1 -0
  64. package/eventcatalog/public/icons/tools/snowflake.svg +1 -0
  65. package/eventcatalog/public/icons/tools/zendesk.svg +1 -0
  66. package/eventcatalog/src/components/Badge.astro +41 -2
  67. package/eventcatalog/src/components/Grids/MessageGrid.tsx +16 -11
  68. package/eventcatalog/src/components/MDX/AgentTools/AgentTools.astro +132 -0
  69. package/eventcatalog/src/components/MDX/NodeGraph/NodeGraph.astro +2 -0
  70. package/eventcatalog/src/components/MDX/ResourceRef/ResourceRef.astro +8 -3
  71. package/eventcatalog/src/components/MDX/components.tsx +2 -0
  72. package/eventcatalog/src/components/Search/SearchModal.tsx +3 -0
  73. package/eventcatalog/src/components/Search/search-utils.ts +2 -0
  74. package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +3 -0
  75. package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +3 -0
  76. package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +165 -0
  77. package/eventcatalog/src/components/Tables/Discover/columns.tsx +132 -7
  78. package/eventcatalog/src/components/Tables/Table.tsx +2 -0
  79. package/eventcatalog/src/components/Tables/columns/TeamsTableColumns.tsx +17 -0
  80. package/eventcatalog/src/components/Tables/columns/UserTableColumns.tsx +17 -0
  81. package/eventcatalog/src/content.config.ts +83 -25
  82. package/eventcatalog/src/enterprise/collections/resource-docs-utils.ts +7 -4
  83. package/eventcatalog/src/enterprise/mcp/mcp-server.ts +9 -2
  84. package/eventcatalog/src/enterprise/tools/catalog-tools.ts +62 -28
  85. package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +13 -1
  86. package/eventcatalog/src/pages/_index.astro +1 -3
  87. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +5 -5
  88. package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +5 -2
  89. package/eventcatalog/src/pages/directory/[type]/index.astro +2 -0
  90. package/eventcatalog/src/pages/discover/[type]/_index.data.ts +5 -0
  91. package/eventcatalog/src/pages/discover/[type]/index.astro +69 -23
  92. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/[docType]/[docId].mdx.ts +1 -0
  93. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/_index.data.ts +1 -0
  94. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/changelog/_index.data.ts +1 -1
  95. package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +47 -2
  96. package/eventcatalog/src/pages/docs/[type]/[id]/[version].md.ts +2 -0
  97. package/eventcatalog/src/pages/docs/[type]/[id]/[version].mdx.ts +2 -0
  98. package/eventcatalog/src/pages/docs/[type]/[id]/_index.data.ts +1 -0
  99. package/eventcatalog/src/pages/docs/llm/llms-full.txt.ts +3 -0
  100. package/eventcatalog/src/pages/docs/llm/llms.txt.ts +3 -0
  101. package/eventcatalog/src/pages/docs/teams/[id]/index.astro +24 -1
  102. package/eventcatalog/src/pages/docs/users/[id]/index.astro +24 -1
  103. package/eventcatalog/src/pages/visualiser/[type]/[id]/[version]/_index.data.ts +2 -1
  104. package/eventcatalog/src/stores/sidebar-store/builders/agent.ts +141 -0
  105. package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +9 -0
  106. package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +19 -0
  107. package/eventcatalog/src/stores/sidebar-store/builders/message.ts +8 -2
  108. package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +10 -7
  109. package/eventcatalog/src/stores/sidebar-store/state.ts +121 -2
  110. package/eventcatalog/src/types/index.ts +3 -1
  111. package/eventcatalog/src/utils/collection-colors.ts +5 -0
  112. package/eventcatalog/src/utils/collections/agents.ts +163 -0
  113. package/eventcatalog/src/utils/collections/commands.ts +3 -2
  114. package/eventcatalog/src/utils/collections/domains.ts +94 -15
  115. package/eventcatalog/src/utils/collections/events.ts +3 -2
  116. package/eventcatalog/src/utils/collections/flows.ts +20 -3
  117. package/eventcatalog/src/utils/collections/icons.ts +3 -1
  118. package/eventcatalog/src/utils/collections/messages.ts +35 -5
  119. package/eventcatalog/src/utils/collections/queries.ts +3 -2
  120. package/eventcatalog/src/utils/collections/schemas.ts +4 -4
  121. package/eventcatalog/src/utils/collections/services.ts +1 -1
  122. package/eventcatalog/src/utils/collections/teams.ts +5 -1
  123. package/eventcatalog/src/utils/collections/types.ts +1 -0
  124. package/eventcatalog/src/utils/collections/users.ts +5 -1
  125. package/eventcatalog/src/utils/collections/util.ts +2 -0
  126. package/eventcatalog/src/utils/eventcatalog-config/catalog.ts +1 -0
  127. package/eventcatalog/src/utils/llms.ts +1 -1
  128. package/eventcatalog/src/utils/node-graphs/agents-node-graph.ts +4 -0
  129. package/eventcatalog/src/utils/node-graphs/container-node-graph.ts +2 -2
  130. package/eventcatalog/src/utils/node-graphs/domains-node-graph.ts +42 -9
  131. package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +20 -0
  132. package/eventcatalog/src/utils/node-graphs/export-node-graph.ts +4 -0
  133. package/eventcatalog/src/utils/node-graphs/flows-node-graph.ts +22 -1
  134. package/eventcatalog/src/utils/node-graphs/message-node-graph.ts +175 -98
  135. package/eventcatalog/src/utils/node-graphs/services-node-graph.ts +117 -23
  136. package/eventcatalog/src/utils/node-graphs/utils/utils.ts +30 -0
  137. package/eventcatalog/src/utils/page-loaders/page-data-loader.ts +2 -0
  138. package/eventcatalog/src/utils/resource-reference-colors.ts +1 -0
  139. package/package.json +11 -4
@@ -18,6 +18,7 @@ import {
18
18
  ArrowLeftRight,
19
19
  Package,
20
20
  Box,
21
+ Bot,
21
22
  } from 'lucide-react';
22
23
  import type { NavNode } from '@stores/sidebar-store/state';
23
24
  import { getBadgeClasses } from './utils';
@@ -83,6 +84,7 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
83
84
 
84
85
  const filterTypes = [
85
86
  { key: 'channel', label: 'Channels', badge: 'Channel', icon: ArrowLeftRight },
87
+ { key: 'agent', label: 'Agents', badge: 'Agent', icon: Bot },
86
88
  { key: 'command', label: 'Commands', badge: 'Command', icon: MessageSquare },
87
89
  { key: 'container', label: 'Data Stores', badge: 'Container', icon: Database },
88
90
  { key: 'data-product', label: 'Data Products', badge: 'Data Product', icon: Package },
@@ -114,6 +116,7 @@ export default function SearchBar({ nodes, onSelectResult, onSearchChange }: Pro
114
116
  const results: SearchResult[] = [];
115
117
 
116
118
  const badgeToFilterKey: Record<string, string> = {
119
+ Agent: 'agent',
117
120
  Domain: 'domain',
118
121
  Service: 'service',
119
122
  Event: 'event',
@@ -218,6 +218,9 @@ export default function NestedSideBar() {
218
218
  { pattern: /^\/docs\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
219
219
  { pattern: /^\/visualiser\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
220
220
  { pattern: /^\/architecture\/domains\/([^/]+)\/([^/]+)/, type: 'domain' },
221
+ // Agents
222
+ { pattern: /^\/docs\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
223
+ { pattern: /^\/visualiser\/agents\/([^/]+)\/([^/]+)/, type: 'agent' },
221
224
  // Services
222
225
  { pattern: /^\/docs\/services\/([^/]+)\/([^/]+)/, type: 'service' },
223
226
  { pattern: /^\/architecture\/services\/([^/]+)\/([^/]+)/, type: 'service' },
@@ -14,10 +14,12 @@ import { UserIcon } from '@heroicons/react/24/outline';
14
14
  import { useEffect, useMemo, useState } from 'react';
15
15
  import type { TableConfiguration } from '@types';
16
16
  import { isSameVersion } from '@utils/collections/version-compare';
17
+ import { resolveIconUrl } from '@utils/icon';
17
18
  import { FilterDropdown, CheckboxItem } from './FilterComponents';
18
19
  import { getDiscoverColumns } from './columns';
19
20
 
20
21
  export type CollectionType =
22
+ | 'agents'
21
23
  | 'events'
22
24
  | 'commands'
23
25
  | 'queries'
@@ -37,6 +39,8 @@ export interface DiscoverTableData {
37
39
  hasRepository?: boolean;
38
40
  isDeprecated?: boolean;
39
41
  hasDataDependencies?: boolean;
42
+ hasTools?: boolean;
43
+ hasModel?: boolean;
40
44
  hasInputs?: boolean;
41
45
  hasOutputs?: boolean;
42
46
  data: {
@@ -58,6 +62,9 @@ export interface DiscoverTableData {
58
62
  consumers?: Array<any>;
59
63
  receives?: Array<any>;
60
64
  sends?: Array<any>;
65
+ tools?: Array<any>;
66
+ model?: any;
67
+ agents?: Array<any>;
61
68
  services?: Array<any>;
62
69
  servicesThatWriteToContainer?: Array<any>;
63
70
  servicesThatReadFromContainer?: Array<any>;
@@ -80,6 +87,8 @@ export interface DiscoverTableProps<T extends DiscoverTableData> {
80
87
  owners?: Array<{ id: string; name: string; type?: 'user' | 'team' }>;
81
88
  producers?: Array<{ id: string; name: string }>;
82
89
  consumers?: Array<{ id: string; name: string }>;
90
+ agentProviders?: Array<{ id: string; name: string }>;
91
+ agentModels?: Array<{ id: string; name: string }>;
83
92
  propertyOptions?: PropertyOption[];
84
93
  tableConfiguration?: TableConfiguration;
85
94
  showDomainsFilter?: boolean;
@@ -88,6 +97,44 @@ export interface DiscoverTableProps<T extends DiscoverTableData> {
88
97
  showConsumersFilter?: boolean;
89
98
  }
90
99
 
100
+ const getAgentModelId = (model: any) => [model?.name, model?.version].filter(Boolean).join(':');
101
+
102
+ const getAgentProviderIconUrls = (provider?: string) => {
103
+ if (!provider) return null;
104
+ const providerIconName = provider
105
+ .trim()
106
+ .toLowerCase()
107
+ .replace(/\s+/g, '-')
108
+ .replace(/[^a-z0-9-]/g, '');
109
+
110
+ const hasThemedIcon = ['openai', 'anthropic'].includes(providerIconName);
111
+
112
+ return hasThemedIcon
113
+ ? {
114
+ light: resolveIconUrl(`/icons/agent/${providerIconName}-light.svg`),
115
+ dark: resolveIconUrl(`/icons/agent/${providerIconName}-dark.svg`),
116
+ }
117
+ : {
118
+ default: resolveIconUrl(`/icons/agent/${providerIconName}.svg`),
119
+ };
120
+ };
121
+
122
+ const AgentProviderIcon = ({ provider, className }: { provider: string; className: string }) => {
123
+ const providerIconUrls = getAgentProviderIconUrls(provider);
124
+ if (!providerIconUrls) return null;
125
+
126
+ if ('light' in providerIconUrls && 'dark' in providerIconUrls) {
127
+ return (
128
+ <>
129
+ <img src={providerIconUrls.light} alt="" className={`${className} dark:hidden`} loading="lazy" />
130
+ <img src={providerIconUrls.dark} alt="" className={`hidden ${className} dark:inline-block`} loading="lazy" />
131
+ </>
132
+ );
133
+ }
134
+
135
+ return <img src={providerIconUrls.default} alt="" className={className} loading="lazy" />;
136
+ };
137
+
91
138
  export function DiscoverTable<T extends DiscoverTableData>({
92
139
  data: initialData,
93
140
  collectionType,
@@ -97,6 +144,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
97
144
  owners = [],
98
145
  producers = [],
99
146
  consumers = [],
147
+ agentProviders = [],
148
+ agentModels = [],
100
149
  propertyOptions = [],
101
150
  tableConfiguration,
102
151
  showDomainsFilter = true,
@@ -129,6 +178,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
129
178
  const [selectedOwners, setSelectedOwners] = useState<string[]>([]);
130
179
  const [selectedProducers, setSelectedProducers] = useState<string[]>([]);
131
180
  const [selectedConsumers, setSelectedConsumers] = useState<string[]>([]);
181
+ const [selectedAgentProviders, setSelectedAgentProviders] = useState<string[]>([]);
182
+ const [selectedAgentModels, setSelectedAgentModels] = useState<string[]>([]);
132
183
  const [selectedBadges, setSelectedBadges] = useState<string[]>([]);
133
184
  const [selectedProperties, setSelectedProperties] = useState<string[]>([]);
134
185
 
@@ -210,6 +261,22 @@ export function DiscoverTable<T extends DiscoverTableData>({
210
261
  }
211
262
  }
212
263
 
264
+ // Agent provider filter
265
+ if (selectedAgentProviders.length > 0) {
266
+ const provider = row.data.model?.provider;
267
+ if (!provider || !selectedAgentProviders.includes(provider)) {
268
+ return false;
269
+ }
270
+ }
271
+
272
+ // Agent model filter
273
+ if (selectedAgentModels.length > 0) {
274
+ const modelId = getAgentModelId(row.data.model);
275
+ if (!modelId || !selectedAgentModels.includes(modelId)) {
276
+ return false;
277
+ }
278
+ }
279
+
213
280
  // Badge filter
214
281
  if (selectedBadges.length > 0) {
215
282
  const itemBadges = row.data?.badges || [];
@@ -227,6 +294,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
227
294
  if (prop === 'hasOwners' && !row.hasOwners) return false;
228
295
  if (prop === 'hasRepository' && !row.hasRepository) return false;
229
296
  if (prop === 'hasDataDependencies' && !row.hasDataDependencies) return false;
297
+ if (prop === 'hasModel' && !row.hasModel) return false;
298
+ if (prop === 'hasTools' && !row.hasTools) return false;
230
299
  if (prop === 'isDeprecated' && !row.isDeprecated) return false;
231
300
 
232
301
  // Message-specific checks
@@ -292,6 +361,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
292
361
  selectedOwners,
293
362
  selectedProducers,
294
363
  selectedConsumers,
364
+ selectedAgentProviders,
365
+ selectedAgentModels,
295
366
  selectedBadges,
296
367
  selectedProperties,
297
368
  tableFilter,
@@ -379,6 +450,24 @@ export function DiscoverTable<T extends DiscoverTableData>({
379
450
  return counts;
380
451
  }, [initialData]);
381
452
 
453
+ const agentProviderCounts = useMemo(() => {
454
+ const counts: Record<string, number> = {};
455
+ initialData.forEach((item) => {
456
+ const provider = item.data.model?.provider;
457
+ if (provider) counts[provider] = (counts[provider] || 0) + 1;
458
+ });
459
+ return counts;
460
+ }, [initialData]);
461
+
462
+ const agentModelCounts = useMemo(() => {
463
+ const counts: Record<string, number> = {};
464
+ initialData.forEach((item) => {
465
+ const modelId = getAgentModelId(item.data.model);
466
+ if (modelId) counts[modelId] = (counts[modelId] || 0) + 1;
467
+ });
468
+ return counts;
469
+ }, [initialData]);
470
+
382
471
  const toggleDomain = (domainId: string) => {
383
472
  setSelectedDomains((prev) => (prev.includes(domainId) ? prev.filter((id) => id !== domainId) : [...prev, domainId]));
384
473
  };
@@ -395,6 +484,16 @@ export function DiscoverTable<T extends DiscoverTableData>({
395
484
  setSelectedConsumers((prev) => (prev.includes(consumerId) ? prev.filter((id) => id !== consumerId) : [...prev, consumerId]));
396
485
  };
397
486
 
487
+ const toggleAgentProvider = (providerId: string) => {
488
+ setSelectedAgentProviders((prev) =>
489
+ prev.includes(providerId) ? prev.filter((id) => id !== providerId) : [...prev, providerId]
490
+ );
491
+ };
492
+
493
+ const toggleAgentModel = (modelId: string) => {
494
+ setSelectedAgentModels((prev) => (prev.includes(modelId) ? prev.filter((id) => id !== modelId) : [...prev, modelId]));
495
+ };
496
+
398
497
  const toggleBadge = (badgeContent: string) => {
399
498
  setSelectedBadges((prev) => (prev.includes(badgeContent) ? prev.filter((b) => b !== badgeContent) : [...prev, badgeContent]));
400
499
  };
@@ -408,6 +507,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
408
507
  setSelectedOwners([]);
409
508
  setSelectedProducers([]);
410
509
  setSelectedConsumers([]);
510
+ setSelectedAgentProviders([]);
511
+ setSelectedAgentModels([]);
411
512
  setSelectedBadges([]);
412
513
  setSelectedProperties([]);
413
514
  setShowOnlyLatest(true);
@@ -421,6 +522,8 @@ export function DiscoverTable<T extends DiscoverTableData>({
421
522
  selectedOwners.length +
422
523
  selectedProducers.length +
423
524
  selectedConsumers.length +
525
+ selectedAgentProviders.length +
526
+ selectedAgentModels.length +
424
527
  selectedBadges.length +
425
528
  selectedProperties.length +
426
529
  (!showOnlyLatest ? 1 : 0) +
@@ -438,9 +541,16 @@ export function DiscoverTable<T extends DiscoverTableData>({
438
541
  // Get selected consumer names for display
439
542
  const selectedConsumerNames = selectedConsumers.map((id) => consumers.find((c) => c.id === id)?.name || id);
440
543
 
544
+ const selectedAgentProviderNames = selectedAgentProviders.map(
545
+ (id) => agentProviders.find((provider) => provider.id === id)?.name || id
546
+ );
547
+ const selectedAgentModelNames = selectedAgentModels.map((id) => agentModels.find((model) => model.id === id)?.name || id);
548
+
441
549
  // Filter producers/consumers to only show those with count > 0
442
550
  const filteredProducers = producers.filter((p) => (producerCounts[p.id] || 0) > 0);
443
551
  const filteredConsumers = consumers.filter((c) => (consumerCounts[c.id] || 0) > 0);
552
+ const filteredAgentProviders = agentProviders.filter((provider) => (agentProviderCounts[provider.id] || 0) > 0);
553
+ const filteredAgentModels = agentModels.filter((model) => (agentModelCounts[model.id] || 0) > 0);
444
554
 
445
555
  // Split owners into users and teams for the filter dropdown
446
556
  const ownerUsers = owners.filter((o) => o.type !== 'team');
@@ -523,6 +633,61 @@ export function DiscoverTable<T extends DiscoverTableData>({
523
633
 
524
634
  {/* Catalog Filters Section */}
525
635
  <div className="space-y-3">
636
+ {collectionType === 'agents' && (filteredAgentProviders.length > 0 || filteredAgentModels.length > 0) && (
637
+ <>
638
+ {filteredAgentProviders.length > 0 && (
639
+ <div>
640
+ <label className="block text-xs font-medium text-[rgb(var(--ec-page-text)/0.8)] mb-1.5">Provider</label>
641
+ <FilterDropdown
642
+ label="Select providers..."
643
+ selectedItems={selectedAgentProviderNames}
644
+ onClear={() => setSelectedAgentProviders([])}
645
+ onRemoveItem={(name) => {
646
+ const provider = filteredAgentProviders.find((p) => p.name === name);
647
+ if (provider) toggleAgentProvider(provider.id);
648
+ }}
649
+ >
650
+ {filteredAgentProviders.map((provider) => (
651
+ <CheckboxItem
652
+ key={provider.id}
653
+ label={provider.name}
654
+ checked={selectedAgentProviders.includes(provider.id)}
655
+ onChange={() => toggleAgentProvider(provider.id)}
656
+ count={agentProviderCounts[provider.id] || 0}
657
+ icon={<AgentProviderIcon provider={provider.name} className="h-3 w-3 rounded-sm object-contain" />}
658
+ />
659
+ ))}
660
+ </FilterDropdown>
661
+ </div>
662
+ )}
663
+
664
+ {filteredAgentModels.length > 0 && (
665
+ <div>
666
+ <label className="block text-xs font-medium text-[rgb(var(--ec-page-text)/0.8)] mb-1.5">Model</label>
667
+ <FilterDropdown
668
+ label="Select models..."
669
+ selectedItems={selectedAgentModelNames}
670
+ onClear={() => setSelectedAgentModels([])}
671
+ onRemoveItem={(name) => {
672
+ const model = filteredAgentModels.find((m) => m.name === name);
673
+ if (model) toggleAgentModel(model.id);
674
+ }}
675
+ >
676
+ {filteredAgentModels.map((model) => (
677
+ <CheckboxItem
678
+ key={model.id}
679
+ label={model.name}
680
+ checked={selectedAgentModels.includes(model.id)}
681
+ onChange={() => toggleAgentModel(model.id)}
682
+ count={agentModelCounts[model.id] || 0}
683
+ />
684
+ ))}
685
+ </FilterDropdown>
686
+ </div>
687
+ )}
688
+ </>
689
+ )}
690
+
526
691
  {/* Domains Filter */}
527
692
  {showDomainsFilter && domains.length > 0 && (
528
693
  <div>
@@ -20,6 +20,42 @@ import type { TableConfiguration } from '@types';
20
20
 
21
21
  const columnHelper = createColumnHelper<DiscoverTableData>();
22
22
 
23
+ const getAgentProviderIconUrls = (provider?: string) => {
24
+ if (!provider) return null;
25
+ const providerIconName = provider
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/\s+/g, '-')
29
+ .replace(/[^a-z0-9-]/g, '');
30
+
31
+ const hasThemedIcon = ['openai', 'anthropic'].includes(providerIconName);
32
+
33
+ return hasThemedIcon
34
+ ? {
35
+ light: resolveIconUrl(`/icons/agent/${providerIconName}-light.svg`),
36
+ dark: resolveIconUrl(`/icons/agent/${providerIconName}-dark.svg`),
37
+ }
38
+ : {
39
+ default: resolveIconUrl(`/icons/agent/${providerIconName}.svg`),
40
+ };
41
+ };
42
+
43
+ const AgentProviderIcon = ({ provider, className }: { provider: string; className: string }) => {
44
+ const providerIconUrls = getAgentProviderIconUrls(provider);
45
+ if (!providerIconUrls) return null;
46
+
47
+ if ('light' in providerIconUrls && 'dark' in providerIconUrls) {
48
+ return (
49
+ <>
50
+ <img src={providerIconUrls.light} alt="" className={`${className} dark:hidden`} loading="lazy" />
51
+ <img src={providerIconUrls.dark} alt="" className={`hidden ${className} dark:inline-block`} loading="lazy" />
52
+ </>
53
+ );
54
+ }
55
+
56
+ return <img src={providerIconUrls.default} alt="" className={className} loading="lazy" />;
57
+ };
58
+
23
59
  // Badge cell component (proper React component to use hooks)
24
60
  const BadgesCell = ({
25
61
  badges,
@@ -107,6 +143,7 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
107
143
  const favorites = useStore(favoritesStore);
108
144
  const href = buildUrl(`/docs/${item.collection}/${item.data.id}/${item.data.version}`);
109
145
  const visualiserHref = buildUrl(`/visualiser/${item.collection}/${item.data.id}/${item.data.version}`);
146
+ const hasVisualiser = true;
110
147
  const nodeKey = `${item.collection}-${item.data.id}-${item.data.version}`;
111
148
  const badgeLabel =
112
149
  collectionType === 'external-systems'
@@ -169,13 +206,15 @@ const RowActionsMenu = ({ item, collectionType }: { item: DiscoverTableData; col
169
206
  <DocumentTextIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
170
207
  View documentation
171
208
  </a>
172
- <a
173
- href={visualiserHref}
174
- className="flex items-center gap-2.5 px-3 py-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
175
- >
176
- <MapIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
177
- View in visualiser
178
- </a>
209
+ {hasVisualiser && (
210
+ <a
211
+ href={visualiserHref}
212
+ className="flex items-center gap-2.5 px-3 py-2.5 text-xs font-medium text-[rgb(var(--ec-page-text))] transition-colors hover:bg-[rgb(var(--ec-content-hover))] hover:text-[rgb(var(--ec-accent))]"
213
+ >
214
+ <MapIcon className="h-3.5 w-3.5 text-[rgb(var(--ec-page-text-muted))]" />
215
+ View in visualiser
216
+ </a>
217
+ )}
179
218
  <button
180
219
  type="button"
181
220
  onClick={handleToggleFavorite}
@@ -277,6 +316,82 @@ const CollectionListCell = ({
277
316
  );
278
317
  };
279
318
 
319
+ // ============================================================================
320
+ // AGENT COLUMNS
321
+ // ============================================================================
322
+ export const getAgentColumns = (tableConfiguration: TableConfiguration) => [
323
+ columnHelper.accessor('data.name', {
324
+ id: 'name',
325
+ header: () => <span>{tableConfiguration?.columns?.name?.label || 'Agent'}</span>,
326
+ cell: (info) => <ResourceNameCell item={info.row.original} />,
327
+ meta: {
328
+ filterVariant: 'name',
329
+ },
330
+ }),
331
+ createSummaryColumn(tableConfiguration),
332
+ columnHelper.accessor((row) => row.data.model?.provider, {
333
+ id: 'provider',
334
+ header: () => <span>{tableConfiguration?.columns?.provider?.label || 'Provider'}</span>,
335
+ cell: (info) => {
336
+ const provider = info.getValue() as string | undefined;
337
+ if (!provider) return <span className="text-xs text-[rgb(var(--ec-icon-color))]">-</span>;
338
+ return (
339
+ <span className="inline-flex items-center gap-2" title={provider} aria-label={provider}>
340
+ <AgentProviderIcon provider={provider} className="h-3 w-3 flex-shrink-0 rounded-sm object-contain" />
341
+ <span className="text-[0.8rem] font-medium text-[rgb(var(--ec-page-text))]">{provider}</span>
342
+ </span>
343
+ );
344
+ },
345
+ meta: {
346
+ showFilter: false,
347
+ },
348
+ }),
349
+ columnHelper.accessor('data.model', {
350
+ id: 'model',
351
+ header: () => <span>{tableConfiguration?.columns?.model?.label || 'Model'}</span>,
352
+ cell: (info) => {
353
+ const model = info.getValue() as any;
354
+ if (!model?.name) return <span className="text-xs text-[rgb(var(--ec-icon-color))]">-</span>;
355
+ return (
356
+ <div className="flex flex-col gap-0.5 text-[0.8rem]">
357
+ <span className="font-medium text-[rgb(var(--ec-page-text))]">{model.name}</span>
358
+ </div>
359
+ );
360
+ },
361
+ meta: {
362
+ showFilter: false,
363
+ },
364
+ }),
365
+ columnHelper.accessor('data.receives', {
366
+ id: 'receives',
367
+ header: () => (
368
+ <span className="flex items-center gap-1">
369
+ <ArrowDownIcon className="w-3.5 h-3.5" />
370
+ Receives
371
+ </span>
372
+ ),
373
+ cell: (info) => <CollectionListCell items={info.getValue()} />,
374
+ meta: {
375
+ showFilter: false,
376
+ },
377
+ }),
378
+ columnHelper.accessor('data.sends', {
379
+ id: 'sends',
380
+ header: () => (
381
+ <span className="flex items-center gap-1">
382
+ <ArrowUpIcon className="w-3.5 h-3.5" />
383
+ Sends
384
+ </span>
385
+ ),
386
+ cell: (info) => <CollectionListCell items={info.getValue()} />,
387
+ meta: {
388
+ showFilter: false,
389
+ },
390
+ }),
391
+ createBadgesColumn(tableConfiguration),
392
+ createActionsColumn('agents', tableConfiguration),
393
+ ];
394
+
280
395
  // ============================================================================
281
396
  // EVENT COLUMNS
282
397
  // ============================================================================
@@ -440,6 +555,14 @@ export const getDomainColumns = (tableConfiguration: TableConfiguration) => [
440
555
  showFilter: false,
441
556
  },
442
557
  }),
558
+ columnHelper.accessor('data.agents', {
559
+ id: 'agents',
560
+ header: () => <span>Agents</span>,
561
+ cell: (info) => <CollectionListCell items={info.getValue()} />,
562
+ meta: {
563
+ showFilter: false,
564
+ },
565
+ }),
443
566
  createBadgesColumn(tableConfiguration),
444
567
  createActionsColumn('domains', tableConfiguration),
445
568
  ];
@@ -552,6 +675,8 @@ export const getDataProductColumns = (tableConfiguration: TableConfiguration) =>
552
675
  // ============================================================================
553
676
  export const getDiscoverColumns = (collectionType: CollectionType, tableConfiguration: TableConfiguration) => {
554
677
  switch (collectionType) {
678
+ case 'agents':
679
+ return getAgentColumns(tableConfiguration);
555
680
  case 'events':
556
681
  return getEventColumns(tableConfiguration);
557
682
  case 'commands':
@@ -31,6 +31,7 @@ declare module '@tanstack/react-table' {
31
31
  | 'ownedCommands'
32
32
  | 'ownedQueries'
33
33
  | 'ownedEvents'
34
+ | 'ownedAgents'
34
35
  | 'ownedServices'
35
36
  | 'associatedTeams'
36
37
  | 'servicesThatWriteToContainer'
@@ -134,6 +135,7 @@ export type TData<T extends TCollectionTypes> = {
134
135
  role?: string;
135
136
  ownedCommands: any;
136
137
  ownedEvents: any;
138
+ ownedAgents: any;
137
139
  ownedServices: any;
138
140
  associatedTeams: any;
139
141
  ownedQueries: any;
@@ -121,4 +121,21 @@ export const columns = (tableConfiguration: TableConfiguration) => [
121
121
  footer: (info) => info.column.id,
122
122
  filterFn: filterCollectionByName('ownedServices'),
123
123
  }),
124
+
125
+ columnHelper.accessor('data.ownedAgents', {
126
+ id: 'ownedAgents',
127
+ header: () => <span>{tableConfiguration.columns?.ownedAgents?.label || 'Owned agents'}</span>,
128
+ meta: {
129
+ filterVariant: 'collection',
130
+ collectionFilterKey: 'ownedAgents',
131
+ },
132
+ cell: (info) => (
133
+ <CollectionListCell
134
+ items={info.getValue() as Array<{ collection: string; data: { id: string; name: string; version?: string } }> | undefined}
135
+ emptyText="No agents"
136
+ />
137
+ ),
138
+ footer: (info) => info.column.id,
139
+ filterFn: filterCollectionByName('ownedAgents'),
140
+ }),
124
141
  ];
@@ -141,6 +141,23 @@ export const columns = (tableConfiguration: TableConfiguration) => [
141
141
  filterFn: filterCollectionByName('ownedServices'),
142
142
  }),
143
143
 
144
+ columnHelper.accessor('data.ownedAgents', {
145
+ id: 'ownedAgents',
146
+ header: () => <span>{tableConfiguration.columns?.ownedAgents?.label || 'Owned agents'}</span>,
147
+ meta: {
148
+ filterVariant: 'collection',
149
+ collectionFilterKey: 'ownedAgents',
150
+ },
151
+ cell: (info) => (
152
+ <CollectionListCell
153
+ items={info.getValue() as Array<{ collection: string; data: { id: string; name: string; version?: string } }> | undefined}
154
+ emptyText="No agents"
155
+ />
156
+ ),
157
+ footer: (info) => info.column.id,
158
+ filterFn: filterCollectionByName('ownedAgents'),
159
+ }),
160
+
144
161
  columnHelper.accessor('data.associatedTeams', {
145
162
  id: 'associatedTeams',
146
163
  header: () => <span>{tableConfiguration.columns?.associatedTeams?.label || 'Teams'}</span>,