feat: handling platform event mcp notification for acp (#10038)
This commit is contained in:
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { Session } from '../../api';
|
||||
import { AppEvents } from '../../constants/events';
|
||||
import { ChatState } from '../../types/chatState';
|
||||
import { maybeHandlePlatformEvent } from '../../utils/platform_events';
|
||||
import { handleAcpSessionNotification } from '../chatNotifications';
|
||||
import type { AcpChatSessionSnapshot } from '../chatSessionStore';
|
||||
import { acpChatSessionActions, acpChatSessionStore } from '../chatSessionStore';
|
||||
@@ -21,6 +22,10 @@ vi.mock('../chatSessionStore', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../utils/platform_events', () => ({
|
||||
maybeHandlePlatformEvent: vi.fn(),
|
||||
}));
|
||||
|
||||
const SESSION_ID = 'session-1';
|
||||
|
||||
function sessionInfoUpdate(title: string): SessionNotification {
|
||||
@@ -33,6 +38,27 @@ function sessionInfoUpdate(title: string): SessionNotification {
|
||||
};
|
||||
}
|
||||
|
||||
function platformEventToolUpdate(status: 'in_progress' | 'completed'): SessionNotification {
|
||||
return {
|
||||
sessionId: SESSION_ID,
|
||||
update: {
|
||||
sessionUpdate: 'tool_call_update',
|
||||
toolCallId: 'tool-1',
|
||||
status,
|
||||
_meta: {
|
||||
toolNotification: {
|
||||
type: 'platform_event',
|
||||
params: {
|
||||
extension: 'apps',
|
||||
event_type: 'app_created',
|
||||
app_name: 'platform-event-repro',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sessionWithName(name: string): Session {
|
||||
return {
|
||||
id: SESSION_ID,
|
||||
@@ -138,4 +164,26 @@ describe('handleAcpSessionNotification', () => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('forwards live ACP platform events to the desktop platform event handler', async () => {
|
||||
await handleAcpSessionNotification(platformEventToolUpdate('in_progress'));
|
||||
|
||||
expect(maybeHandlePlatformEvent).toHaveBeenCalledWith(
|
||||
{
|
||||
method: 'platform_event',
|
||||
params: {
|
||||
extension: 'apps',
|
||||
event_type: 'app_created',
|
||||
app_name: 'platform-event-repro',
|
||||
},
|
||||
},
|
||||
SESSION_ID
|
||||
);
|
||||
});
|
||||
|
||||
it('does not forward completed platform event metadata as a live desktop event', async () => {
|
||||
await handleAcpSessionNotification(platformEventToolUpdate('completed'));
|
||||
|
||||
expect(maybeHandlePlatformEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,14 +4,18 @@ import type { AcpChatStateChange } from './shared';
|
||||
import { isRecord } from './shared';
|
||||
|
||||
type ToolNotification =
|
||||
| {
|
||||
type: 'message';
|
||||
params: LoggingMessageNotificationParams;
|
||||
}
|
||||
| {
|
||||
type: 'progress';
|
||||
params: ProgressNotificationParams;
|
||||
};
|
||||
| {
|
||||
type: 'message';
|
||||
params: LoggingMessageNotificationParams;
|
||||
}
|
||||
| {
|
||||
type: 'progress';
|
||||
params: ProgressNotificationParams;
|
||||
}
|
||||
| {
|
||||
type: 'platform_event';
|
||||
params: PlatformEventParams;
|
||||
};
|
||||
|
||||
type LoggingMessageNotificationParams = {
|
||||
level: string;
|
||||
@@ -26,20 +30,31 @@ type ProgressNotificationParams = {
|
||||
message?: string;
|
||||
};
|
||||
|
||||
type PlatformEventParams = Record<string, unknown>;
|
||||
|
||||
export function toolNotificationChange(
|
||||
update: ToolCallUpdate
|
||||
): Extract<AcpChatStateChange, { type: 'notification' }> | undefined {
|
||||
const toolNotification = parseToolNotification(update._meta);
|
||||
if (!toolNotification) {
|
||||
const notification = toolNotificationEvent(update);
|
||||
if (!notification) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'notification',
|
||||
notification: toNotificationEvent(update.toolCallId, toolNotification),
|
||||
notification,
|
||||
};
|
||||
}
|
||||
|
||||
export function toolNotificationEvent(update: ToolCallUpdate): NotificationEvent | undefined {
|
||||
const toolNotification = parseToolNotification(update._meta);
|
||||
if (!toolNotification) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return toNotificationEvent(update.toolCallId, toolNotification);
|
||||
}
|
||||
|
||||
function parseToolNotification(meta: unknown): ToolNotification | undefined {
|
||||
if (!isRecord(meta)) {
|
||||
return undefined;
|
||||
@@ -60,6 +75,11 @@ function parseToolNotification(meta: unknown): ToolNotification | undefined {
|
||||
return params ? { type: 'progress', params } : undefined;
|
||||
}
|
||||
|
||||
if (toolNotification.type === 'platform_event') {
|
||||
const params = parsePlatformEventParams(toolNotification.params);
|
||||
return params ? { type: 'platform_event', params } : undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -92,6 +112,10 @@ function parseProgressParams(value: unknown): ProgressNotificationParams | undef
|
||||
};
|
||||
}
|
||||
|
||||
function parsePlatformEventParams(value: unknown): PlatformEventParams | undefined {
|
||||
return isRecord(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function toNotificationEvent(
|
||||
toolCallId: string,
|
||||
toolNotification: ToolNotification
|
||||
@@ -100,11 +124,19 @@ function toNotificationEvent(
|
||||
type: 'Notification',
|
||||
request_id: toolCallId,
|
||||
message: {
|
||||
method:
|
||||
toolNotification.type === 'message'
|
||||
? 'notifications/message'
|
||||
: 'notifications/progress',
|
||||
method: notificationMethod(toolNotification),
|
||||
params: toolNotification.params,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function notificationMethod(toolNotification: ToolNotification): string {
|
||||
switch (toolNotification.type) {
|
||||
case 'message':
|
||||
return 'notifications/message';
|
||||
case 'progress':
|
||||
return 'notifications/progress';
|
||||
case 'platform_event':
|
||||
return 'platform_event';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { GooseSessionNotification_unstable } from '@aaif/goose-sdk';
|
||||
import type { SessionNotification } from '@agentclientprotocol/sdk';
|
||||
import { USE_ACP_CHAT } from '../acpChatFeatureFlag';
|
||||
import { AppEvents } from '../constants/events';
|
||||
import { maybeHandlePlatformEvent } from '../utils/platform_events';
|
||||
import { toolNotificationEvent } from './adapter/toolNotifications';
|
||||
import { acpChatSessionActions, acpChatSessionStore } from './chatSessionStore';
|
||||
|
||||
export function handleAcpSessionNotification(notification: SessionNotification): Promise<void> {
|
||||
@@ -14,6 +16,7 @@ export function handleAcpSessionNotification(notification: SessionNotification):
|
||||
? notification.update.title
|
||||
: undefined;
|
||||
acpChatSessionActions.applyAcpSessionNotification(notification);
|
||||
maybeHandleLivePlatformEvent(notification);
|
||||
|
||||
if (updatedName && updatedName !== sessionNameBeforeNotification) {
|
||||
window.dispatchEvent(
|
||||
@@ -26,6 +29,22 @@ export function handleAcpSessionNotification(notification: SessionNotification):
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function maybeHandleLivePlatformEvent(notification: SessionNotification): void {
|
||||
const update = notification.update;
|
||||
if (
|
||||
update.sessionUpdate !== 'tool_call_update' ||
|
||||
update.status === 'completed' ||
|
||||
update.status === 'failed'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = toolNotificationEvent(update);
|
||||
if (event?.message.method === 'platform_event') {
|
||||
maybeHandlePlatformEvent(event.message, notification.sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleAcpGooseSessionNotification(
|
||||
notification: GooseSessionNotification_unstable
|
||||
): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user