remove SSE-based implementation for editor agent
This commit is contained in:
@@ -5,34 +5,17 @@ export const EDITOR_AGENT_TITLE_MAX_CHARS = 20;
|
||||
export const EDITOR_AGENT_DEFAULT_CONVERSATION_TITLE = '新对话';
|
||||
export const EDITOR_AGENT_MESSAGES_DOCUMENT_VERSION = 1;
|
||||
|
||||
export type EditorAgentStage =
|
||||
| 'idle'
|
||||
| 'thinking'
|
||||
| 'responding'
|
||||
| 'generating'
|
||||
export type EditorAgentMessageRole = 'user' | 'assistant' | 'system';
|
||||
|
||||
export type EditorAgentToolCallStatus =
|
||||
| 'pending_confirmation'
|
||||
| 'executing'
|
||||
| 'completed'
|
||||
| 'cancelled'
|
||||
| 'failed';
|
||||
|
||||
export type EditorAgentMessageRole = 'user' | 'assistant';
|
||||
|
||||
export type EditorAgentMessageKind = 'chat' | 'stage' | 'error';
|
||||
|
||||
export type EditorAgentMessageStatus =
|
||||
| 'streaming'
|
||||
| 'generating'
|
||||
| 'completed'
|
||||
| 'failed'
|
||||
| 'stopped';
|
||||
|
||||
export type EditorAgentAttachmentSource = 'canvas_resource' | 'library_asset';
|
||||
|
||||
export type EditorAgentToolName =
|
||||
| 'generate_image'
|
||||
| 'edit_image'
|
||||
| 'generate_character'
|
||||
| 'generate_icon_spritesheet'
|
||||
| 'generate_ui_design';
|
||||
|
||||
export interface EditorAgentAttachmentRef {
|
||||
source: EditorAgentAttachmentSource;
|
||||
referenceId: string;
|
||||
@@ -45,49 +28,45 @@ export interface EditorAgentAttachmentRef {
|
||||
}
|
||||
|
||||
export interface EditorAgentGeneratedImage {
|
||||
resourceId: string | null;
|
||||
resourceId?: string | null;
|
||||
objectKey?: string | null;
|
||||
assetObjectId?: string | null;
|
||||
imageSrc: string;
|
||||
thumbnailSrc: string | null;
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
thumbnailSrc?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
}
|
||||
|
||||
export type EditorAgentGenerationStatus = 'generating' | 'completed' | 'failed';
|
||||
|
||||
export interface EditorAgentGenerationRecord {
|
||||
toolCallId: string;
|
||||
toolName: EditorAgentToolName;
|
||||
summary?: string | null;
|
||||
taskId: string | null;
|
||||
status: EditorAgentGenerationStatus;
|
||||
model: string | null;
|
||||
export interface EditorAgentToolCall {
|
||||
toolName: string;
|
||||
summary: string;
|
||||
status: EditorAgentToolCallStatus;
|
||||
args: unknown;
|
||||
images: EditorAgentGeneratedImage[];
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface EditorAgentMessage {
|
||||
id: string;
|
||||
id: number;
|
||||
role: EditorAgentMessageRole;
|
||||
kind: EditorAgentMessageKind;
|
||||
text: string;
|
||||
attachments: EditorAgentAttachmentRef[];
|
||||
generations: EditorAgentGenerationRecord[];
|
||||
status: EditorAgentMessageStatus;
|
||||
toolCall: EditorAgentToolCall | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface EditorAgentConversationSummary {
|
||||
conversationId: string;
|
||||
projectId: string;
|
||||
title: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface EditorAgentConversationDetail
|
||||
extends EditorAgentConversationSummary {
|
||||
export interface EditorAgentConversationDetail {
|
||||
conversationId: string;
|
||||
projectId: string;
|
||||
title: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
messages: EditorAgentMessage[];
|
||||
}
|
||||
|
||||
@@ -109,63 +88,12 @@ export interface EditorAgentConversationResponse {
|
||||
conversation: EditorAgentConversationDetail;
|
||||
}
|
||||
|
||||
export interface StreamEditorAgentMessageRequest {
|
||||
clientMessageId: string;
|
||||
export interface EditorAgentMessageRequest {
|
||||
text: string;
|
||||
attachments?: EditorAgentAttachmentRef[];
|
||||
}
|
||||
|
||||
export interface EditorAgentStageEvent {
|
||||
conversationId: string;
|
||||
stage: EditorAgentStage;
|
||||
export interface EditorAgentMessageResponse {
|
||||
deltaMessages: EditorAgentMessage[];
|
||||
errorMessage: string | null;
|
||||
}
|
||||
|
||||
export interface EditorAgentMessageDeltaEvent {
|
||||
conversationId: string;
|
||||
messageId: string;
|
||||
role: EditorAgentMessageRole;
|
||||
kind: EditorAgentMessageKind;
|
||||
textDelta: string;
|
||||
}
|
||||
|
||||
export interface EditorAgentToolEvent {
|
||||
conversationId: string;
|
||||
messageId: string;
|
||||
toolCallId: string;
|
||||
toolName: EditorAgentToolName;
|
||||
summary?: string | null;
|
||||
taskId?: string | null;
|
||||
model?: string | null;
|
||||
status?: EditorAgentGenerationStatus | null;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export interface EditorAgentGenerationResultEvent {
|
||||
conversationId: string;
|
||||
messageId: string;
|
||||
toolCallId: string;
|
||||
toolName: EditorAgentToolName;
|
||||
model: string | null;
|
||||
images: EditorAgentGeneratedImage[];
|
||||
}
|
||||
|
||||
export interface EditorAgentErrorEvent {
|
||||
conversationId: string | null;
|
||||
code: string;
|
||||
message: string;
|
||||
recoverable: boolean;
|
||||
}
|
||||
|
||||
export interface EditorAgentDoneEvent {
|
||||
conversationId: string;
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
export type EditorAgentSseEvent =
|
||||
| { event: 'stage'; data: EditorAgentStageEvent }
|
||||
| { event: 'message_delta'; data: EditorAgentMessageDeltaEvent }
|
||||
| { event: 'tool_started'; data: EditorAgentToolEvent }
|
||||
| { event: 'tool_completed'; data: EditorAgentToolEvent }
|
||||
| { event: 'generation_result'; data: EditorAgentGenerationResultEvent }
|
||||
| { event: 'error'; data: EditorAgentErrorEvent }
|
||||
| { event: 'done'; data: EditorAgentDoneEvent };
|
||||
|
||||
@@ -1,41 +1,24 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
cancelEditorAgentToolCall,
|
||||
confirmEditorAgentToolCall,
|
||||
createEditorAgentConversation,
|
||||
deleteEditorAgentConversation,
|
||||
getEditorAgentConversation,
|
||||
listEditorAgentConversations,
|
||||
streamEditorAgentMessage,
|
||||
sendEditorAgentMessage,
|
||||
} from './editorAgentClient';
|
||||
|
||||
const requestJsonMock = vi.hoisted(() => vi.fn());
|
||||
const fetchWithApiAuthMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('../apiClient', () => ({
|
||||
requestJson: requestJsonMock,
|
||||
fetchWithApiAuth: fetchWithApiAuthMock,
|
||||
}));
|
||||
|
||||
function createSseResponse(payload: string) {
|
||||
const encoder = new TextEncoder();
|
||||
return new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(payload));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'text/event-stream; charset=utf-8' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
describe('editorAgentClient', () => {
|
||||
afterEach(() => {
|
||||
requestJsonMock.mockReset();
|
||||
fetchWithApiAuthMock.mockReset();
|
||||
});
|
||||
|
||||
it('uses the planned editor agent conversation CRUD routes', async () => {
|
||||
@@ -117,53 +100,96 @@ describe('editorAgentClient', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('posts editor agent message streams and emits typed SSE events', async () => {
|
||||
fetchWithApiAuthMock.mockResolvedValueOnce(
|
||||
createSseResponse(
|
||||
[
|
||||
'event: stage',
|
||||
'data: {"conversationId":"conversation-1","stage":"thinking"}',
|
||||
'',
|
||||
'event: message_delta',
|
||||
'data: {"conversationId":"conversation-1","messageId":"assistant-1","role":"assistant","kind":"chat","textDelta":"我先看一下"}',
|
||||
'',
|
||||
'event: done',
|
||||
'data: {"conversationId":"conversation-1","title":"生成角色"}',
|
||||
'',
|
||||
'',
|
||||
].join('\n'),
|
||||
),
|
||||
);
|
||||
const events: string[] = [];
|
||||
it('sends an editor agent message and returns delta messages', async () => {
|
||||
const responseBody = {
|
||||
deltaMessages: [
|
||||
{
|
||||
id: 1,
|
||||
role: 'assistant',
|
||||
text: '我来处理',
|
||||
attachments: [],
|
||||
toolCall: null,
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
errorMessage: null,
|
||||
};
|
||||
requestJsonMock.mockResolvedValueOnce(responseBody);
|
||||
|
||||
await streamEditorAgentMessage(
|
||||
'conversation-1',
|
||||
{
|
||||
clientMessageId: 'client-message-1',
|
||||
text: '帮我把角色改成像素风',
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
onEvent: (event) => events.push(event.event),
|
||||
},
|
||||
);
|
||||
const result = await sendEditorAgentMessage('conversation-1', {
|
||||
text: '帮我把角色改成像素风',
|
||||
attachments: [],
|
||||
});
|
||||
|
||||
expect(events).toEqual(['stage', 'message_delta', 'done']);
|
||||
expect(fetchWithApiAuthMock).toHaveBeenCalledWith(
|
||||
'/api/editor/agent-conversations/conversation-1/messages/stream',
|
||||
expect(result).toEqual(responseBody);
|
||||
expect(requestJsonMock).toHaveBeenCalledWith(
|
||||
'/api/editor/agent-conversations/conversation-1/messages',
|
||||
expect.objectContaining({
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
clientMessageId: 'client-message-1',
|
||||
text: '帮我把角色改成像素风',
|
||||
attachments: [],
|
||||
}),
|
||||
}),
|
||||
'发送画布 Agent 消息失败',
|
||||
expect.objectContaining({
|
||||
timeoutMs: 1_200_000,
|
||||
authImpact: 'local',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('confirms and cancels a pending tool call by numeric message id', async () => {
|
||||
const completedMessage = {
|
||||
id: 7,
|
||||
role: 'system',
|
||||
text: 'internal completed tool output',
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'completed',
|
||||
args: {},
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
createdAt: '2026-07-10T00:00:00.000Z',
|
||||
};
|
||||
const cancelledMessage = {
|
||||
...completedMessage,
|
||||
toolCall: {
|
||||
...completedMessage.toolCall,
|
||||
status: 'cancelled',
|
||||
},
|
||||
};
|
||||
requestJsonMock
|
||||
.mockResolvedValueOnce(completedMessage)
|
||||
.mockResolvedValueOnce(cancelledMessage);
|
||||
|
||||
await expect(confirmEditorAgentToolCall('conversation/1', 7)).resolves.toBe(
|
||||
completedMessage,
|
||||
);
|
||||
await expect(cancelEditorAgentToolCall('conversation/1', 7)).resolves.toBe(
|
||||
cancelledMessage,
|
||||
);
|
||||
|
||||
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'/api/editor/agent-conversations/conversation%2F1/messages/7/confirm',
|
||||
{ method: 'POST' },
|
||||
'确认画布 Agent 操作失败',
|
||||
{
|
||||
timeoutMs: 1_200_000,
|
||||
authImpact: 'local',
|
||||
},
|
||||
);
|
||||
expect(requestJsonMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/api/editor/agent-conversations/conversation%2F1/messages/7/cancel',
|
||||
{ method: 'POST' },
|
||||
'取消画布 Agent 操作失败',
|
||||
{ authImpact: 'local' },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,23 +4,18 @@ import type {
|
||||
EditorAgentConversationListResponse,
|
||||
EditorAgentConversationResponse,
|
||||
EditorAgentConversationSummary,
|
||||
EditorAgentSseEvent,
|
||||
StreamEditorAgentMessageRequest,
|
||||
EditorAgentMessage,
|
||||
EditorAgentMessageRequest,
|
||||
EditorAgentMessageResponse,
|
||||
} from '../../../packages/shared/src/contracts/editorAgent';
|
||||
import {
|
||||
appendApiErrorRequestId,
|
||||
parseApiErrorMessage,
|
||||
} from '../../../packages/shared/src/http';
|
||||
import { fetchWithApiAuth, requestJson } from '../apiClient';
|
||||
import { readEditorAgentSseEvents } from './editorAgentSse';
|
||||
import { requestJson } from '../apiClient';
|
||||
|
||||
const EDITOR_PROJECT_AGENT_CONVERSATION_API_BASE = '/api/editor/projects';
|
||||
const EDITOR_AGENT_CONVERSATION_API_BASE = '/api/editor/agent-conversations';
|
||||
const EDITOR_AGENT_STREAM_TIMEOUT_MS = 1_200_000;
|
||||
const EDITOR_AGENT_MESSAGE_TIMEOUT_MS = 1_200_000;
|
||||
|
||||
export type StreamEditorAgentMessageOptions = {
|
||||
export type SendEditorAgentMessageOptions = {
|
||||
signal?: AbortSignal;
|
||||
onEvent?: (event: EditorAgentSseEvent) => void;
|
||||
};
|
||||
|
||||
type DeleteEditorAgentConversationResponse = {
|
||||
@@ -48,6 +43,16 @@ function agentConversationPath(conversationId: string) {
|
||||
)}`;
|
||||
}
|
||||
|
||||
function agentToolCallActionPath(
|
||||
conversationId: string,
|
||||
messageId: number,
|
||||
action: 'confirm' | 'cancel',
|
||||
) {
|
||||
return `${agentConversationPath(conversationId)}/messages/${encodeURIComponent(
|
||||
String(messageId),
|
||||
)}/${action}`;
|
||||
}
|
||||
|
||||
export async function listEditorAgentConversations(
|
||||
projectId: string,
|
||||
): Promise<EditorAgentConversationSummary[]> {
|
||||
@@ -97,36 +102,48 @@ export async function deleteEditorAgentConversation(
|
||||
return response.conversation;
|
||||
}
|
||||
|
||||
export async function streamEditorAgentMessage(
|
||||
export async function sendEditorAgentMessage(
|
||||
conversationId: string,
|
||||
payload: StreamEditorAgentMessageRequest,
|
||||
options: StreamEditorAgentMessageOptions = {},
|
||||
) {
|
||||
const response = await fetchWithApiAuth(
|
||||
`${agentConversationPath(conversationId)}/messages/stream`,
|
||||
payload: EditorAgentMessageRequest,
|
||||
options: SendEditorAgentMessageOptions = {},
|
||||
): Promise<EditorAgentMessageResponse> {
|
||||
return requestJson<EditorAgentMessageResponse>(
|
||||
`${agentConversationPath(conversationId)}/messages`,
|
||||
{
|
||||
...jsonRequest('POST', payload as unknown as Record<string, unknown>),
|
||||
signal: options.signal,
|
||||
},
|
||||
'发送画布 Agent 消息失败',
|
||||
{
|
||||
timeoutMs: EDITOR_AGENT_STREAM_TIMEOUT_MS,
|
||||
timeoutMs: EDITOR_AGENT_MESSAGE_TIMEOUT_MS,
|
||||
authImpact: 'local',
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const responseText = await response.text();
|
||||
throw new Error(
|
||||
appendApiErrorRequestId(
|
||||
parseApiErrorMessage(responseText, '发送画布 Agent 消息失败'),
|
||||
response.headers.get('x-request-id'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error('streaming response body is unavailable');
|
||||
}
|
||||
|
||||
await readEditorAgentSseEvents(response, (event) => options.onEvent?.(event));
|
||||
}
|
||||
|
||||
export async function confirmEditorAgentToolCall(
|
||||
conversationId: string,
|
||||
messageId: number,
|
||||
): Promise<EditorAgentMessage> {
|
||||
return requestJson<EditorAgentMessage>(
|
||||
agentToolCallActionPath(conversationId, messageId, 'confirm'),
|
||||
{ method: 'POST' },
|
||||
'确认画布 Agent 操作失败',
|
||||
{
|
||||
timeoutMs: EDITOR_AGENT_MESSAGE_TIMEOUT_MS,
|
||||
authImpact: 'local',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function cancelEditorAgentToolCall(
|
||||
conversationId: string,
|
||||
messageId: number,
|
||||
): Promise<EditorAgentMessage> {
|
||||
return requestJson<EditorAgentMessage>(
|
||||
agentToolCallActionPath(conversationId, messageId, 'cancel'),
|
||||
{ method: 'POST' },
|
||||
'取消画布 Agent 操作失败',
|
||||
{ authImpact: 'local' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
|
||||
import { readEditorAgentSseEvents } from './editorAgentSse';
|
||||
|
||||
function createSseResponse(chunks: string[]) {
|
||||
const encoder = new TextEncoder();
|
||||
return new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
for (const chunk of chunks) {
|
||||
controller.enqueue(encoder.encode(chunk));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/event-stream; charset=utf-8',
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test('readEditorAgentSseEvents parses stage, generation result and done events', async () => {
|
||||
const onEvent = vi.fn();
|
||||
await readEditorAgentSseEvents(
|
||||
createSseResponse([
|
||||
'event: stage\r\ndata: {"conversationId":"conversation-1","stage":"generating"}\r\n\r\n',
|
||||
'event: generation_result\r\ndata: {"conversationId":"conversation-1","messageId":"assistant-1","toolCallId":"tool-1","toolName":"generate_image","model":"gpt-image-2","images":[{"resourceId":"resource-1","imageSrc":"/generated/1.png","thumbnailSrc":null,"width":512,"height":512}]}\r\n\r\n',
|
||||
'event: done\r\ndata: {"conversationId":"conversation-1","title":"像素角色"}\r\n\r\n',
|
||||
]),
|
||||
onEvent,
|
||||
);
|
||||
|
||||
expect(onEvent).toHaveBeenNthCalledWith(1, {
|
||||
event: 'stage',
|
||||
data: {
|
||||
conversationId: 'conversation-1',
|
||||
stage: 'generating',
|
||||
},
|
||||
});
|
||||
expect(onEvent).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
event: 'generation_result',
|
||||
data: expect.objectContaining({
|
||||
toolCallId: 'tool-1',
|
||||
images: [
|
||||
{
|
||||
resourceId: 'resource-1',
|
||||
imageSrc: '/generated/1.png',
|
||||
thumbnailSrc: null,
|
||||
width: 512,
|
||||
height: 512,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(onEvent).toHaveBeenNthCalledWith(3, {
|
||||
event: 'done',
|
||||
data: {
|
||||
conversationId: 'conversation-1',
|
||||
title: '像素角色',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('readEditorAgentSseEvents accepts fallback event name from message payload', async () => {
|
||||
const onEvent = vi.fn();
|
||||
|
||||
await readEditorAgentSseEvents(
|
||||
createSseResponse([
|
||||
'data: {"event":"error","data":{"conversationId":"conversation-1","code":"INSUFFICIENT_BALANCE","message":"泥点不足","recoverable":true}}\n\n',
|
||||
]),
|
||||
onEvent,
|
||||
);
|
||||
|
||||
expect(onEvent).toHaveBeenCalledWith({
|
||||
event: 'error',
|
||||
data: {
|
||||
conversationId: 'conversation-1',
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
message: '泥点不足',
|
||||
recoverable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
import type { EditorAgentSseEvent } from '../../../packages/shared/src/contracts/editorAgent';
|
||||
import { readSseJsonStream } from '../sseStream';
|
||||
|
||||
const EDITOR_AGENT_SSE_EVENT_NAMES = new Set<EditorAgentSseEvent['event']>([
|
||||
'stage',
|
||||
'message_delta',
|
||||
'tool_started',
|
||||
'tool_completed',
|
||||
'generation_result',
|
||||
'error',
|
||||
'done',
|
||||
]);
|
||||
|
||||
function isEditorAgentSseEventName(
|
||||
value: unknown,
|
||||
): value is EditorAgentSseEvent['event'] {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
EDITOR_AGENT_SSE_EVENT_NAMES.has(value as EditorAgentSseEvent['event'])
|
||||
);
|
||||
}
|
||||
|
||||
function readEventNameFromPayload(parsed: Record<string, unknown>) {
|
||||
return isEditorAgentSseEventName(parsed.event) ? parsed.event : null;
|
||||
}
|
||||
|
||||
function readEventDataFromPayload(parsed: Record<string, unknown>) {
|
||||
const data = parsed.data;
|
||||
return typeof data === 'object' && data !== null
|
||||
? (data as Record<string, unknown>)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function normalizeEditorAgentSseEvent(
|
||||
eventName: string,
|
||||
parsed: Record<string, unknown>,
|
||||
): EditorAgentSseEvent | null {
|
||||
const payloadEventName = readEventNameFromPayload(parsed);
|
||||
if (payloadEventName) {
|
||||
const payloadData = readEventDataFromPayload(parsed);
|
||||
if (!payloadData) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
event: payloadEventName,
|
||||
data: payloadData,
|
||||
} as unknown as EditorAgentSseEvent;
|
||||
}
|
||||
|
||||
if (!isEditorAgentSseEventName(eventName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
event: eventName,
|
||||
data: parsed,
|
||||
} as unknown as EditorAgentSseEvent;
|
||||
}
|
||||
|
||||
export async function readEditorAgentSseEvents(
|
||||
response: Response,
|
||||
onEvent: (event: EditorAgentSseEvent) => void,
|
||||
) {
|
||||
await readSseJsonStream(response, ({ eventName, parsed }) => {
|
||||
const event = normalizeEditorAgentSseEvent(eventName, parsed);
|
||||
if (event) {
|
||||
onEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user