mcp
This commit is contained in:
@@ -40,6 +40,7 @@ describe('AssistantService', () => {
|
||||
listLists: jest.fn(),
|
||||
addItem: jest.fn(),
|
||||
};
|
||||
listsService.listLists.mockResolvedValue([]);
|
||||
service = new AssistantService(
|
||||
chatLogsRepository 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 () => {
|
||||
mockMistralResponse({
|
||||
choices: [
|
||||
|
||||
@@ -106,6 +106,13 @@ export class AssistantService {
|
||||
): Promise<AssistantChatResponse> {
|
||||
const messages = this.normalizeMessages(request.messages);
|
||||
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);
|
||||
|
||||
if (localResponse) {
|
||||
@@ -123,18 +130,24 @@ export class AssistantService {
|
||||
}
|
||||
|
||||
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 =
|
||||
this.extractAssistantContent(response) ??
|
||||
this.createActionContent(actions);
|
||||
const latestUserMessage = this.latestUserMessage(messages);
|
||||
|
||||
actions.forEach((action) => {
|
||||
this.listRealtimeService.publishSnapshot(userId, action.list);
|
||||
});
|
||||
|
||||
if (
|
||||
this.isListCreationRequest(latestUserMessage?.content ?? '') &&
|
||||
isCreationRequest &&
|
||||
!actions.some((action) => action.type === 'list.created')
|
||||
) {
|
||||
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[]> {
|
||||
const logs = await this.chatLogsRepository.find({
|
||||
where: { userId },
|
||||
@@ -578,7 +625,10 @@ export class AssistantService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private extractActions(responsePayload: unknown): AssistantAction[] {
|
||||
private extractActions(
|
||||
responsePayload: unknown,
|
||||
options: { assumeCreatedListForUnknownToolResult?: boolean } = {},
|
||||
): AssistantAction[] {
|
||||
if (!responsePayload || typeof responsePayload !== 'object') {
|
||||
return [];
|
||||
}
|
||||
@@ -634,12 +684,20 @@ export class AssistantService {
|
||||
continue;
|
||||
}
|
||||
|
||||
actions.push({
|
||||
type: 'list.item_added',
|
||||
listId: list.id,
|
||||
itemTitle: this.lastItemTitle(list),
|
||||
list,
|
||||
});
|
||||
if (options.assumeCreatedListForUnknownToolResult) {
|
||||
actions.push({
|
||||
type: 'list.created',
|
||||
listId: list.id,
|
||||
list,
|
||||
});
|
||||
} else {
|
||||
actions.push({
|
||||
type: 'list.item_added',
|
||||
listId: list.id,
|
||||
itemTitle: this.lastItemTitle(list),
|
||||
list,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user