mcp
This commit is contained in:
@@ -40,6 +40,7 @@ describe('AssistantService', () => {
|
|||||||
listLists: jest.fn(),
|
listLists: jest.fn(),
|
||||||
addItem: jest.fn(),
|
addItem: jest.fn(),
|
||||||
};
|
};
|
||||||
|
listsService.listLists.mockResolvedValue([]);
|
||||||
service = new AssistantService(
|
service = new AssistantService(
|
||||||
chatLogsRepository as never,
|
chatLogsRepository as never,
|
||||||
listRealtimeService as never,
|
listRealtimeService as never,
|
||||||
@@ -570,6 +571,107 @@ describe('AssistantService', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('treats unknown list tool results as created lists for creation requests', async () => {
|
||||||
|
const createdList = {
|
||||||
|
id: 'list-1',
|
||||||
|
ownerId: 'user-1',
|
||||||
|
accessRole: 'owner',
|
||||||
|
name: 'Einkauf',
|
||||||
|
kind: 'shopping',
|
||||||
|
items: [],
|
||||||
|
collaborators: [],
|
||||||
|
createdAt: '2026-06-12T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
const providerResponse = {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'tool',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: JSON.stringify({ list: createdList }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Erledigt.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
mockMistralResponse(providerResponse);
|
||||||
|
|
||||||
|
const result = await service.chat('user-1', {
|
||||||
|
messages: [{ role: 'user', content: 'Erstelle eine Einkaufsliste' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.actions).toEqual([
|
||||||
|
{
|
||||||
|
type: 'list.created',
|
||||||
|
listId: 'list-1',
|
||||||
|
list: createdList,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(result.message.content).toBe('Erledigt.');
|
||||||
|
expect(listRealtimeService.publishSnapshot).toHaveBeenCalledWith(
|
||||||
|
'user-1',
|
||||||
|
createdList,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects lists created through the connector when the provider omits tool details', async () => {
|
||||||
|
const createdList = {
|
||||||
|
id: 'list-1',
|
||||||
|
ownerId: 'user-1',
|
||||||
|
accessRole: 'owner',
|
||||||
|
name: 'Einkauf',
|
||||||
|
kind: 'shopping',
|
||||||
|
items: [],
|
||||||
|
collaborators: [],
|
||||||
|
createdAt: '2026-06-12T00:00:00.000Z',
|
||||||
|
updatedAt: '2026-06-12T00:00:00.000Z',
|
||||||
|
};
|
||||||
|
listsService.listLists
|
||||||
|
.mockResolvedValueOnce([])
|
||||||
|
.mockResolvedValueOnce([createdList]);
|
||||||
|
mockMistralResponse({
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
content: 'Ich habe die Liste angelegt.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.chat('user-1', {
|
||||||
|
messages: [{ role: 'user', content: 'Erstelle eine Einkaufsliste' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
message: {
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Ich habe die Liste angelegt.',
|
||||||
|
},
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
type: 'list.created',
|
||||||
|
listId: 'list-1',
|
||||||
|
list: createdList,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(listRealtimeService.publishSnapshot).toHaveBeenCalledWith(
|
||||||
|
'user-1',
|
||||||
|
createdList,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns a clear message when list creation did not use the connector', async () => {
|
it('returns a clear message when list creation did not use the connector', async () => {
|
||||||
mockMistralResponse({
|
mockMistralResponse({
|
||||||
choices: [
|
choices: [
|
||||||
|
|||||||
@@ -106,6 +106,13 @@ export class AssistantService {
|
|||||||
): Promise<AssistantChatResponse> {
|
): Promise<AssistantChatResponse> {
|
||||||
const messages = this.normalizeMessages(request.messages);
|
const messages = this.normalizeMessages(request.messages);
|
||||||
const context = this.normalizeContext(request.context);
|
const context = this.normalizeContext(request.context);
|
||||||
|
const latestUserMessage = this.latestUserMessage(messages);
|
||||||
|
const isCreationRequest = this.isListCreationRequest(
|
||||||
|
latestUserMessage?.content ?? '',
|
||||||
|
);
|
||||||
|
const listIdsBeforeMistral = isCreationRequest
|
||||||
|
? await this.listIdsForUser(userId)
|
||||||
|
: null;
|
||||||
const localResponse = await this.tryHandleLocalListQuery(userId, messages);
|
const localResponse = await this.tryHandleLocalListQuery(userId, messages);
|
||||||
|
|
||||||
if (localResponse) {
|
if (localResponse) {
|
||||||
@@ -123,18 +130,24 @@ export class AssistantService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await this.callMistralAgent(userId, messages, context);
|
const response = await this.callMistralAgent(userId, messages, context);
|
||||||
const actions = this.extractActions(response);
|
let actions = this.extractActions(response, {
|
||||||
|
assumeCreatedListForUnknownToolResult: isCreationRequest,
|
||||||
|
});
|
||||||
|
actions = await this.addDetectedCreatedLists(
|
||||||
|
userId,
|
||||||
|
actions,
|
||||||
|
listIdsBeforeMistral,
|
||||||
|
);
|
||||||
const content =
|
const content =
|
||||||
this.extractAssistantContent(response) ??
|
this.extractAssistantContent(response) ??
|
||||||
this.createActionContent(actions);
|
this.createActionContent(actions);
|
||||||
const latestUserMessage = this.latestUserMessage(messages);
|
|
||||||
|
|
||||||
actions.forEach((action) => {
|
actions.forEach((action) => {
|
||||||
this.listRealtimeService.publishSnapshot(userId, action.list);
|
this.listRealtimeService.publishSnapshot(userId, action.list);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.isListCreationRequest(latestUserMessage?.content ?? '') &&
|
isCreationRequest &&
|
||||||
!actions.some((action) => action.type === 'list.created')
|
!actions.some((action) => action.type === 'list.created')
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
@@ -175,6 +188,40 @@ export class AssistantService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async listIdsForUser(userId: string): Promise<Set<string>> {
|
||||||
|
const lists = await this.listsService.listLists(userId);
|
||||||
|
return new Set(lists.map((list) => list.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addDetectedCreatedLists(
|
||||||
|
userId: string,
|
||||||
|
actions: AssistantAction[],
|
||||||
|
listIdsBeforeMistral: Set<string> | null,
|
||||||
|
): Promise<AssistantAction[]> {
|
||||||
|
if (!listIdsBeforeMistral) {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listsAfterMistral = await this.listsService.listLists(userId);
|
||||||
|
const existingCreatedIds = new Set(
|
||||||
|
actions
|
||||||
|
.filter((action) => action.type === 'list.created')
|
||||||
|
.map((action) => action.listId),
|
||||||
|
);
|
||||||
|
const detectedActions = listsAfterMistral
|
||||||
|
.filter((list) => !listIdsBeforeMistral.has(list.id))
|
||||||
|
.filter((list) => !existingCreatedIds.has(list.id))
|
||||||
|
.map(
|
||||||
|
(list): AssistantAction => ({
|
||||||
|
type: 'list.created',
|
||||||
|
listId: list.id,
|
||||||
|
list,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.uniqueActions([...actions, ...detectedActions]);
|
||||||
|
}
|
||||||
|
|
||||||
async listChatLogs(userId: string): Promise<AssistantChatLog[]> {
|
async listChatLogs(userId: string): Promise<AssistantChatLog[]> {
|
||||||
const logs = await this.chatLogsRepository.find({
|
const logs = await this.chatLogsRepository.find({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
@@ -578,7 +625,10 @@ export class AssistantService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractActions(responsePayload: unknown): AssistantAction[] {
|
private extractActions(
|
||||||
|
responsePayload: unknown,
|
||||||
|
options: { assumeCreatedListForUnknownToolResult?: boolean } = {},
|
||||||
|
): AssistantAction[] {
|
||||||
if (!responsePayload || typeof responsePayload !== 'object') {
|
if (!responsePayload || typeof responsePayload !== 'object') {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -634,12 +684,20 @@ export class AssistantService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
actions.push({
|
if (options.assumeCreatedListForUnknownToolResult) {
|
||||||
type: 'list.item_added',
|
actions.push({
|
||||||
listId: list.id,
|
type: 'list.created',
|
||||||
itemTitle: this.lastItemTitle(list),
|
listId: list.id,
|
||||||
list,
|
list,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
actions.push({
|
||||||
|
type: 'list.item_added',
|
||||||
|
listId: list.id,
|
||||||
|
itemTitle: this.lastItemTitle(list),
|
||||||
|
list,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user