@eventcatalog/core 3.0.0-beta.23 → 3.0.0-beta.25
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/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-F6UWJOIP.js → chunk-3SWCGDD7.js} +1 -1
- package/dist/{chunk-C3DPW2RT.js → chunk-DFHXF3VF.js} +1 -1
- package/dist/{chunk-U5LM336G.js → chunk-NAW6EPCS.js} +1 -1
- package/dist/{chunk-AJIDWPJI.js → chunk-OJA6CNVO.js} +1 -1
- package/dist/{chunk-XPNI6MQE.js → chunk-RA6AYXGJ.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.js +5 -5
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/ChatPanel/ChatPanel.tsx +289 -116
- package/eventcatalog/src/components/ChatPanel/ChatPanelButton.tsx +3 -3
- package/eventcatalog/src/components/Search/SearchDataLoader.astro +25 -0
- package/eventcatalog/src/components/SideNav/NestedSideBar/SearchBar.tsx +1 -1
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +1 -1
- package/eventcatalog/src/components/SideNav/SideNav.astro +0 -15
- package/eventcatalog/src/enterprise/ai/chat-api.ts +242 -144
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +126 -121
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +5 -2
- package/eventcatalog/src/pages/schemas/explorer/index.astro +2 -2
- package/eventcatalog/src/stores/{sidebar-store.ts → sidebar-store/index.ts} +1 -1
- package/eventcatalog/src/utils/collections/schemas.ts +14 -0
- package/package.json +2 -1
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/container.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/domain.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/flow.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/message.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/service.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar → stores/sidebar-store}/builders/shared.ts +0 -0
- /package/eventcatalog/src/{components/SideNav/NestedSideBar/sidebar-builder.ts → stores/sidebar-store/state.ts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { BookOpen } from 'lucide-react';
|
|
3
3
|
import ChatPanel from './ChatPanel';
|
|
4
4
|
|
|
5
5
|
const ChatPanelButton = () => {
|
|
@@ -12,8 +12,8 @@ const ChatPanelButton = () => {
|
|
|
12
12
|
className="flex items-center gap-1.5 px-4 py-1.5 rounded-md bg-white hover:bg-gray-50 ring-1 ring-inset ring-gray-300 shadow-sm transition-colors text-sm ml-[-1px]"
|
|
13
13
|
aria-label="Open AI Assistant"
|
|
14
14
|
>
|
|
15
|
-
<
|
|
16
|
-
<span className="font-light text-gray-600">Ask
|
|
15
|
+
<BookOpen size={14} className="text-purple-500" />
|
|
16
|
+
<span className="font-light text-gray-600">Ask</span>
|
|
17
17
|
</button>
|
|
18
18
|
|
|
19
19
|
<ChatPanel isOpen={isOpen} onClose={() => setIsOpen(false)} />
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* SearchDataLoader.astro
|
|
4
|
+
*
|
|
5
|
+
* This component loads the sidebar/search data independently of the sidebar UI.
|
|
6
|
+
* It ensures the search functionality works on all pages, even when the nested
|
|
7
|
+
* sidebar is not rendered (e.g., /discover pages).
|
|
8
|
+
*/
|
|
9
|
+
import { getNestedSideBarData } from '@stores/sidebar-store/state';
|
|
10
|
+
|
|
11
|
+
const props = await getNestedSideBarData();
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<script is:inline define:vars={{ props }}>
|
|
15
|
+
window.sidebarData = props;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { setSidebarData } from '@stores/sidebar-store';
|
|
20
|
+
|
|
21
|
+
const data = (window as any).sidebarData;
|
|
22
|
+
if (data) {
|
|
23
|
+
setSidebarData(data);
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
ListOrdered,
|
|
18
18
|
ArrowLeftRight,
|
|
19
19
|
} from 'lucide-react';
|
|
20
|
-
import type { NavNode } from '
|
|
20
|
+
import type { NavNode } from '@stores/sidebar-store/state';
|
|
21
21
|
|
|
22
22
|
const cn = (...classes: (string | false | undefined)[]) => classes.filter(Boolean).join(' ');
|
|
23
23
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import * as LucideIcons from 'lucide-react';
|
|
5
5
|
import { ChevronRight, ChevronLeft, ChevronDown, Home, Star, FileQuestion } from 'lucide-react';
|
|
6
|
-
import type {
|
|
6
|
+
import type { NavNode, ChildRef } from '@stores/sidebar-store/state';
|
|
7
7
|
import SearchBar from './SearchBar';
|
|
8
8
|
import { saveState, loadState, saveCollapsedSections, loadCollapsedSections } from './storage';
|
|
9
9
|
import { useStore } from '@nanostores/react';
|
|
@@ -2,26 +2,11 @@
|
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
|
|
4
4
|
interface Props extends Omit<HTMLAttributes<'div'>, 'children'> {}
|
|
5
|
-
import { getNestedSideBarData } from './NestedSideBar/sidebar-builder';
|
|
6
5
|
import NestedSideBar from './NestedSideBar';
|
|
7
6
|
import { ClientRouter } from 'astro:transitions';
|
|
8
|
-
|
|
9
|
-
const props = await getNestedSideBarData();
|
|
10
7
|
---
|
|
11
8
|
|
|
12
9
|
<div {...Astro.props}>
|
|
13
10
|
<ClientRouter />
|
|
14
11
|
<NestedSideBar client:only="react" />
|
|
15
12
|
</div>
|
|
16
|
-
|
|
17
|
-
<script is:inline define:vars={{ props }}>
|
|
18
|
-
window.sidebarData = props;
|
|
19
|
-
</script>
|
|
20
|
-
|
|
21
|
-
<script>
|
|
22
|
-
import { setSidebarData } from '@stores/sidebar-store';
|
|
23
|
-
const data = (window as any).sidebarData;
|
|
24
|
-
if (data) {
|
|
25
|
-
setSidebarData(data);
|
|
26
|
-
}
|
|
27
|
-
</script>
|
|
@@ -21,16 +21,43 @@ export const defaultConfiguration = {
|
|
|
21
21
|
let hasChatConfiguration = false;
|
|
22
22
|
let model: LanguageModel;
|
|
23
23
|
let modelConfiguration: any;
|
|
24
|
+
let extendedTools: any;
|
|
24
25
|
|
|
25
26
|
try {
|
|
26
27
|
const providerConfiguration = await import(/* @vite-ignore */ join(catalogDirectory, 'eventcatalog.chat.js'));
|
|
27
28
|
model = await providerConfiguration.default();
|
|
28
29
|
modelConfiguration = providerConfiguration.configuration || defaultConfiguration;
|
|
29
30
|
hasChatConfiguration = true;
|
|
31
|
+
extendedTools = providerConfiguration.tools || {};
|
|
30
32
|
} catch (error) {
|
|
33
|
+
console.error('[Chat] Error loading chat configuration', error);
|
|
31
34
|
hasChatConfiguration = false;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
// Built-in tools metadata for client visibility
|
|
38
|
+
const builtInToolsMetadata = [
|
|
39
|
+
{
|
|
40
|
+
name: 'getResources',
|
|
41
|
+
description: 'Get events, services, commands, queries, flows, domains, channels, entities from EventCatalog',
|
|
42
|
+
},
|
|
43
|
+
{ name: 'getResource', description: 'Get a specific resource by its id and version' },
|
|
44
|
+
{ name: 'getProducersAndConsumersFromSchema', description: 'Get the producers and consumers for a schema' },
|
|
45
|
+
{ name: 'getMessagesProducedOrConsumedByResource', description: 'Get messages produced or consumed by a resource' },
|
|
46
|
+
{ name: 'getProducerAndConsumerForMessage', description: 'Get the producers and consumers for a message' },
|
|
47
|
+
{ name: 'getConsumersOfMessage', description: 'Get the consumers for a message' },
|
|
48
|
+
{ name: 'getSchemaForResource', description: 'Get the schema or specifications (OpenAPI, AsyncAPI, GraphQL) for a resource' },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Get extended tools metadata from user configuration
|
|
52
|
+
const getExtendedToolsMetadata = () => {
|
|
53
|
+
if (!extendedTools || typeof extendedTools !== 'object') return [];
|
|
54
|
+
return Object.entries(extendedTools).map(([name, toolConfig]: [string, any]) => ({
|
|
55
|
+
name,
|
|
56
|
+
description: toolConfig?.description || 'Custom tool',
|
|
57
|
+
isCustom: true,
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
|
|
34
61
|
const getBaseSystemPrompt = (
|
|
35
62
|
referrer: string
|
|
36
63
|
) => `You are an expert in software architecture and domain-driven design, specializing in the open source tool EventCatalog.
|
|
@@ -39,39 +66,47 @@ You assist developers, architects, and business stakeholders who need informatio
|
|
|
39
66
|
|
|
40
67
|
There are many different resource types in EventCatalog, including:
|
|
41
68
|
- Events (collection name 'events') (asynchronous messages that notify about something that has happened)
|
|
69
|
+
- example docs url: /docs/events/MyEvent/1.0.0
|
|
42
70
|
- Commands (collection name 'commands') (requests to perform an action)
|
|
71
|
+
- example docs url: /docs/commands/MyCommand/1.0.0
|
|
43
72
|
- Queries (collection name 'queries') (requests for information)
|
|
73
|
+
- example docs url: /docs/queries/MyQuery/1.0.0
|
|
44
74
|
- Services (collection name 'services') (bounded contexts or applications that produce/consume events)
|
|
75
|
+
- example docs url: /docs/services/MyService/1.0.0
|
|
45
76
|
- Domains (collection name 'domains') (business capabilities or functional areas)
|
|
77
|
+
- example docs url: /docs/domains/MyDomain/1.0.0
|
|
46
78
|
- Flows (collection name 'flows') (state machines)
|
|
79
|
+
- example docs url: /docs/flows/MyFlow/1.0.0
|
|
47
80
|
- Channels (collection name 'channels') (communication channels)
|
|
81
|
+
- example docs url: /docs/channels/MyChannel/1.0.0
|
|
48
82
|
- Entities (collection name 'entities') (data objects)
|
|
83
|
+
- example docs url: /docs/entities/MyEntity/1.0.0
|
|
49
84
|
- Containers (collection name 'containers') (at the moment these are data stores (databases))
|
|
85
|
+
- example docs url: /docs/containers/MyContainer/1.0.0
|
|
50
86
|
|
|
51
87
|
The user will ask you some questions about the software architecture catalog, you should use the tools provided to you to get the information they need.
|
|
52
88
|
|
|
53
|
-
At point the referer url will be the URL of the page the user is on, you should use this to help you answer the question,
|
|
54
|
-
You may be able to get the resource from the URL
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
At point the referer url (${referrer}) will be the URL of the page the user is on, you should use this to help you answer the question,
|
|
90
|
+
You may be able to get the resource from the URL
|
|
91
|
+
- Example if the url is like /docs|visualiser|architecture/{collection}/{id}/{version}
|
|
92
|
+
- (e.g /docs/events/MyEvent/1.0.0) in this case the id is MyEvent and the version is 1.0.0 and collection is events.
|
|
93
|
+
- (e.g /visualiser/domains/MyDomain/1.0.0) in this case the id is MyDomain and the version is 1.0.0 and collection is domains.
|
|
94
|
+
- (e.g /architecture/services/MyService/1.0.0) in this case the id is MyService and the version is 1.0.0 and collection is services.
|
|
59
95
|
|
|
60
|
-
|
|
61
|
-
- /architecture/domains/E-Commerce/1.0.0 -> id: E-Commerce, version: 1.0.0, collection: domains
|
|
62
|
-
- /visualiser/domains/E-Commerce/1.0.0
|
|
96
|
+
The referer URL is: ${referrer}
|
|
63
97
|
|
|
64
98
|
Sometimes the user will be on the specification page (openapi, asyncapi, graphql) for a resource too
|
|
65
99
|
- /docs/services/OrdersService/0.0.3/asyncapi/order-service-asyncapi -> id: OrdersService, version: 0.0.3, collection: services, schema (specification): asyncapi
|
|
66
100
|
- /docs/services/OrdersService/0.0.3/spec/openapi-v2 -> id: OrdersService, version: 0.0.3, collection: services, schema (specification): openapi
|
|
67
101
|
|
|
68
102
|
Your primary goal is to help users understand their software architecture through accurate documentation interpretation.
|
|
103
|
+
|
|
69
104
|
Use the tools provided to get the context you need to an answer the question.
|
|
70
105
|
|
|
71
106
|
When responding:
|
|
72
|
-
1.
|
|
73
|
-
2
|
|
74
|
-
3
|
|
107
|
+
1. Explain connections between resources when relevant.
|
|
108
|
+
2. Use appropriate technical terminology.
|
|
109
|
+
3. Use clear formatting with headings and bullet points when helpful.
|
|
75
110
|
4. State clearly when information is missing rather than making assumptions.
|
|
76
111
|
5. Don't provide code examples unless specifically requested.
|
|
77
112
|
6. When you refer to a resource in EventCatalog, try and create a link to the resource in the response
|
|
@@ -90,9 +125,24 @@ interface Message {
|
|
|
90
125
|
}
|
|
91
126
|
|
|
92
127
|
export const GET = async ({ request }: APIContext<{ question: string; messages: Message[]; additionalContext?: string }>) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
128
|
+
if (!isEventCatalogScaleEnabled()) {
|
|
129
|
+
return new Response(JSON.stringify({ error: 'Chat is not enabled' }), {
|
|
130
|
+
status: 403,
|
|
131
|
+
headers: { 'Content-Type': 'application/json' },
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!hasChatConfiguration) {
|
|
136
|
+
return new Response(JSON.stringify({ error: 'No chat configuration found' }), {
|
|
137
|
+
status: 404,
|
|
138
|
+
headers: { 'Content-Type': 'application/json' },
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Return available tools metadata
|
|
143
|
+
const tools = [...builtInToolsMetadata, ...getExtendedToolsMetadata()];
|
|
144
|
+
return new Response(JSON.stringify({ tools }), {
|
|
145
|
+
status: 200,
|
|
96
146
|
headers: { 'Content-Type': 'application/json' },
|
|
97
147
|
});
|
|
98
148
|
};
|
|
@@ -117,146 +167,194 @@ export const POST = async ({ request }: APIContext<{ question: string; messages:
|
|
|
117
167
|
// Get the URL of the request
|
|
118
168
|
const referrer = request.headers.get('referer');
|
|
119
169
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
getResource: tool({
|
|
151
|
-
description: 'Use this tool to get a specific resource from EventCatalog by its id and version',
|
|
152
|
-
inputSchema: z.object({
|
|
153
|
-
collection: z
|
|
154
|
-
.enum(['events', 'services', 'commands', 'queries', 'flows', 'domains', 'channels', 'entities'])
|
|
155
|
-
.describe('The collection to get the events from'),
|
|
156
|
-
id: z.string().describe('The id of the resource to get'),
|
|
157
|
-
version: z.string().describe('The version of the resource to get'),
|
|
170
|
+
try {
|
|
171
|
+
const result = await streamText({
|
|
172
|
+
model,
|
|
173
|
+
system: getBaseSystemPrompt(referrer ?? ''),
|
|
174
|
+
messages: convertToModelMessages(messages) as ModelMessage[],
|
|
175
|
+
temperature: modelConfiguration?.temperature ?? 0.7,
|
|
176
|
+
stopWhen: stepCountIs(5),
|
|
177
|
+
// maxTokens: 4000, // Increased to handle large tool results
|
|
178
|
+
onError: (error) => {
|
|
179
|
+
console.error('[Chat] On error', error);
|
|
180
|
+
},
|
|
181
|
+
// maxOutputTokens: 40000,
|
|
182
|
+
// tools: tools,
|
|
183
|
+
tools: {
|
|
184
|
+
getResources: tool({
|
|
185
|
+
description:
|
|
186
|
+
'Use this tool to get events, services, commands, queries, flows, domains, channels, entities from EventCatalog',
|
|
187
|
+
inputSchema: z.object({
|
|
188
|
+
collection: z
|
|
189
|
+
.enum(['events', 'services', 'commands', 'queries', 'flows', 'domains', 'channels', 'entities', 'containers'])
|
|
190
|
+
.describe('The collection to get the events from'),
|
|
191
|
+
}),
|
|
192
|
+
execute: async ({ collection }) => {
|
|
193
|
+
const resources = await getCollection(collection as any);
|
|
194
|
+
return resources.map((resource: any) => ({
|
|
195
|
+
id: resource.data.id,
|
|
196
|
+
version: resource.data.version,
|
|
197
|
+
name: resource.data.name,
|
|
198
|
+
}));
|
|
199
|
+
},
|
|
158
200
|
}),
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.describe('The collection of the resource to get the messages produced or consumed for')
|
|
173
|
-
.default('services'),
|
|
201
|
+
getResource: tool({
|
|
202
|
+
description: 'Use this tool to get a specific resource from EventCatalog by its id and version',
|
|
203
|
+
inputSchema: z.object({
|
|
204
|
+
collection: z
|
|
205
|
+
.enum(['events', 'services', 'commands', 'queries', 'flows', 'domains', 'channels', 'entities', 'containers'])
|
|
206
|
+
.describe('The collection to get the events from'),
|
|
207
|
+
id: z.string().describe('The id of the resource to get'),
|
|
208
|
+
version: z.string().describe('The version of the resource to get'),
|
|
209
|
+
}),
|
|
210
|
+
execute: async ({ collection, id, version }) => {
|
|
211
|
+
const resource = await getEntry(collection as any, `${id}-${version}`);
|
|
212
|
+
return resource;
|
|
213
|
+
},
|
|
174
214
|
}),
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
215
|
+
getProducersAndConsumersFromSchema: tool({
|
|
216
|
+
description: 'Use this tool to get the producers and consumers for a schema by its id and version',
|
|
217
|
+
inputSchema: z.object({
|
|
218
|
+
collection: z
|
|
219
|
+
.enum(['events', 'commands', 'queries'])
|
|
220
|
+
.describe('The collection to get the producers and consumers from'),
|
|
221
|
+
id: z.string().describe('The id of the message to get the producers and consumers for'),
|
|
222
|
+
version: z.string().describe('The version of the message to get the producers and consumers for'),
|
|
223
|
+
}),
|
|
224
|
+
execute: async ({ collection, id, version }) => {
|
|
225
|
+
const resource = await getEntry(collection as any, `${id}-${version}`);
|
|
226
|
+
const producers = resource.data.producers || [];
|
|
227
|
+
const consumers = resource.data.consumers || [];
|
|
178
228
|
return {
|
|
179
|
-
|
|
229
|
+
producers,
|
|
230
|
+
consumers,
|
|
180
231
|
};
|
|
181
|
-
}
|
|
182
|
-
return resource;
|
|
183
|
-
},
|
|
184
|
-
}),
|
|
185
|
-
getProducerAndConsumerForMessage: tool({
|
|
186
|
-
description: 'Use this tool to get the producers and consumers for a message by its id and version',
|
|
187
|
-
inputSchema: z.object({
|
|
188
|
-
messageId: z.string().describe('The id of the message to get the producers and consumers for'),
|
|
189
|
-
messageVersion: z.string().describe('The version of the message to get the producers and consumers for'),
|
|
190
|
-
messageCollection: z
|
|
191
|
-
.enum(['events', 'commands', 'queries'])
|
|
192
|
-
.describe('The collection of the message to get the producers and consumers for')
|
|
193
|
-
.default('events'),
|
|
232
|
+
},
|
|
194
233
|
}),
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
234
|
+
getMessagesProducedOrConsumedByResource: tool({
|
|
235
|
+
description:
|
|
236
|
+
'Use this tool to get the messages produced or consumed by a resource by its id and version. Look at the `sends` and `receives` properties to get the messages produced or consumed by the resource',
|
|
237
|
+
inputSchema: z.object({
|
|
238
|
+
resourceId: z.string().describe('The id of the resource to get the messages produced or consumed for'),
|
|
239
|
+
resourceVersion: z.string().describe('The version of the resource to get the messages produced or consumed for'),
|
|
240
|
+
resourceCollection: z
|
|
241
|
+
.enum(['services', 'events', 'commands', 'queries', 'flows', 'domains', 'channels', 'entities'])
|
|
242
|
+
.describe('The collection of the resource to get the messages produced or consumed for')
|
|
243
|
+
.default('services'),
|
|
244
|
+
}),
|
|
245
|
+
execute: async ({ resourceId, resourceVersion, resourceCollection }) => {
|
|
246
|
+
const resource = await getEntry(resourceCollection as any, `${resourceId}-${resourceVersion}`);
|
|
247
|
+
if (!resource) {
|
|
248
|
+
return {
|
|
249
|
+
error: `Resource not found with id ${resourceId} and version ${resourceVersion} and collection ${resourceCollection}`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return resource;
|
|
253
|
+
},
|
|
211
254
|
}),
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
.default('services'),
|
|
255
|
+
getProducerAndConsumerForMessage: tool({
|
|
256
|
+
description: 'Use this tool to get the producers and consumers for a message by its id and version',
|
|
257
|
+
inputSchema: z.object({
|
|
258
|
+
messageId: z.string().describe('The id of the message to get the producers and consumers for'),
|
|
259
|
+
messageVersion: z.string().describe('The version of the message to get the producers and consumers for'),
|
|
260
|
+
messageCollection: z
|
|
261
|
+
.enum(['events', 'commands', 'queries'])
|
|
262
|
+
.describe('The collection of the message to get the producers and consumers for')
|
|
263
|
+
.default('events'),
|
|
264
|
+
}),
|
|
265
|
+
execute: async ({ messageId, messageVersion, messageCollection }) => {
|
|
266
|
+
const services = await getCollection('services');
|
|
267
|
+
const message = await getEntry(messageCollection as any, `${messageId}-${messageVersion}`);
|
|
268
|
+
const consumers = await getProducersOfMessage(services, message as any);
|
|
269
|
+
return consumers;
|
|
270
|
+
},
|
|
229
271
|
}),
|
|
230
|
-
|
|
231
|
-
|
|
272
|
+
getConsumersOfMessage: tool({
|
|
273
|
+
description: 'Use this tool to get the consumers for a message by its id and version',
|
|
274
|
+
inputSchema: z.object({
|
|
275
|
+
messageId: z.string().describe('The id of the message to get the consumers for'),
|
|
276
|
+
messageVersion: z.string().describe('The version of the message to get the consumers for'),
|
|
277
|
+
messageCollection: z
|
|
278
|
+
.enum(['events', 'commands', 'queries'])
|
|
279
|
+
.describe('The collection of the message to get the consumers for')
|
|
280
|
+
.default('events'),
|
|
281
|
+
}),
|
|
282
|
+
execute: async ({ messageId, messageVersion, messageCollection }) => {
|
|
283
|
+
const services = await getCollection('services');
|
|
284
|
+
const message = await getEntry(messageCollection as any, `${messageId}-${messageVersion}`);
|
|
285
|
+
const consumers = await getConsumersOfMessage(services, message as any);
|
|
286
|
+
return consumers;
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
getSchemaForResource: tool({
|
|
290
|
+
description:
|
|
291
|
+
'Use this tool to get the schema or specifications (openapi or asyncapi or graphql) for a resource by its id and version, you will use code blocks to render the schema to the user too',
|
|
292
|
+
inputSchema: z.object({
|
|
293
|
+
resourceId: z.string().describe('The id of the resource to get the schema for'),
|
|
294
|
+
resourceVersion: z.string().describe('The version of the resource to get the schema for'),
|
|
295
|
+
resourceCollection: z
|
|
296
|
+
.enum(['services', 'events', 'commands', 'queries', 'flows', 'domains', 'channels', 'entities'])
|
|
297
|
+
.describe('The collection of the resource to get the schema for')
|
|
298
|
+
.default('services'),
|
|
299
|
+
}),
|
|
300
|
+
execute: async ({ resourceId, resourceVersion, resourceCollection }) => {
|
|
301
|
+
const resource = await getEntry(resourceCollection as any, `${resourceId}-${resourceVersion}`);
|
|
232
302
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
303
|
+
if (!resource) {
|
|
304
|
+
return {
|
|
305
|
+
error: `Resource not found with id ${resourceId} and version ${resourceVersion} and collection ${resourceCollection}`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
238
308
|
|
|
239
|
-
|
|
309
|
+
const schema = await getSchemasFromResource(resource);
|
|
240
310
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
311
|
+
// If we have any schemas back then read them and return them
|
|
312
|
+
if (schema.length > 0) {
|
|
313
|
+
return schema.map((schemaItem) => ({
|
|
314
|
+
url: schemaItem.url,
|
|
315
|
+
format: schemaItem.format,
|
|
316
|
+
code: fs.readFileSync(schemaItem.url, 'utf-8'),
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
249
319
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
320
|
+
return [];
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
...extendedTools,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
return result.toUIMessageStreamResponse({
|
|
327
|
+
headers: {
|
|
328
|
+
'Content-Type': 'text/event-stream',
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
} catch (err: any) {
|
|
332
|
+
console.error('[Chat] Error during streaming:', err);
|
|
333
|
+
|
|
334
|
+
// Extract a user-friendly error message
|
|
335
|
+
let errorMessage = 'An unexpected error occurred while processing your request.';
|
|
336
|
+
|
|
337
|
+
if (err?.message) {
|
|
338
|
+
// Check for common error patterns and provide friendlier messages
|
|
339
|
+
if (err.message.includes('API key') || err.message.includes('authentication') || err.message.includes('401')) {
|
|
340
|
+
errorMessage = 'Authentication error: Please check your API key configuration.';
|
|
341
|
+
} else if (err.message.includes('rate limit') || err.message.includes('429')) {
|
|
342
|
+
errorMessage = 'Rate limit exceeded. Please wait a moment and try again.';
|
|
343
|
+
} else if (err.message.includes('timeout') || err.message.includes('ETIMEDOUT')) {
|
|
344
|
+
errorMessage = 'Request timed out. Please try again.';
|
|
345
|
+
} else if (err.message.includes('model') || err.message.includes('not found')) {
|
|
346
|
+
errorMessage = 'Model configuration error: ' + err.message;
|
|
347
|
+
} else {
|
|
348
|
+
// Use the original message if it's not too technical
|
|
349
|
+
errorMessage = err.message.length < 200 ? err.message : 'An error occurred while processing your request.';
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return new Response(JSON.stringify({ error: errorMessage }), {
|
|
354
|
+
status: 500,
|
|
355
|
+
headers: { 'Content-Type': 'application/json' },
|
|
356
|
+
});
|
|
357
|
+
}
|
|
260
358
|
};
|
|
261
359
|
|
|
262
360
|
export const prerender = false;
|