feat: 流式展示设计 Agent 对话

需求:设计 Agent 对话与确认生成的回复需要实时展示。

实现:统一通过 Agent Gateway 提交 Turn,接收并去重 assistant delta,以 canonical Workspace 收口,并修复跨项目旧请求回写竞态。
This commit is contained in:
2026-08-02 21:21:50 +08:00
parent 518d7ab4cd
commit 90a249db31
9 changed files with 819 additions and 93 deletions

View File

@@ -147,8 +147,123 @@ describe('Works Square AI design adapter', () => {
))).toBe(true);
});
it('submits a conversation turn through the persistent Agent Gateway Session', async () => {
const fetchMock = vi.fn<typeof fetch>()
.mockResolvedValueOnce(jsonResponse({
session_id: 'session-one',
status: 'active',
}, 201))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-one',
status: 'queued',
error: null,
}, 202))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-one',
status: 'succeeded',
error: null,
}))
.mockResolvedValueOnce(jsonResponse(serverWorkspace));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
clientInstanceId: 'installation-one',
});
await expect(adapter.submitMessage({
workspaceId: 'workspace-one',
clientTurnId: 'turn-two',
expectedTurnRevision: 1,
message: '做一张保护海洋的公益海报',
attachmentAssetIds: ['asset-reference'],
})).resolves.toMatchObject({
workspaceId: 'workspace-one',
turnRevision: 1,
});
expect(fetchMock).toHaveBeenNthCalledWith(
2,
'https://square.example/api/agents/sessions/session-one/commands',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
client_command_id: 'turn-two',
name: 'turn.submit',
input: {
expected_turn_revision: 1,
message: '做一张保护海洋的公益海报',
attachment_asset_ids: ['asset-reference'],
action: null,
},
}),
}),
);
expect(fetchMock).toHaveBeenNthCalledWith(
3,
'https://square.example/api/agents/sessions/session-one/runs/run-one',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(fetchMock).toHaveBeenNthCalledWith(
4,
'https://square.example/api/design/workspaces/workspace-one',
expect.objectContaining({ headers: expect.any(Object) }),
);
});
it('maps an invalid Runtime command to a user input error', async () => {
const fetchMock = vi.fn<typeof fetch>()
.mockResolvedValueOnce(jsonResponse({
session_id: 'session-one',
status: 'active',
}, 201))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-invalid',
status: 'queued',
error: null,
}, 202))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-invalid',
status: 'failed',
error: {
code: 'agent_command_invalid',
message: 'private validation detail',
retryable: false,
},
}));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example',
fetchImpl: fetchMock,
});
await expect(adapter.submitMessage({
workspaceId: 'workspace-one',
clientTurnId: 'turn-invalid',
expectedTurnRevision: 1,
message: 'invalid',
})).rejects.toMatchObject({
status: 422,
code: 'agent_command_invalid',
message: '设计请求内容无效,请检查后重试',
});
});
it('keeps task creation behind structured Quote confirmation', async () => {
const fetchMock = vi.fn<typeof fetch>().mockResolvedValue(jsonResponse(serverWorkspace));
const fetchMock = vi.fn<typeof fetch>()
.mockResolvedValueOnce(jsonResponse({
session_id: 'session-one',
status: 'active',
}, 201))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-confirm',
status: 'queued',
error: null,
}, 202))
.mockResolvedValueOnce(jsonResponse({
run_id: 'run-confirm',
status: 'succeeded',
error: null,
}))
.mockResolvedValueOnce(jsonResponse(serverWorkspace));
const adapter = new WorksSquareDesignWorkspace({
apiBaseUrl: 'https://square.example/',
fetchImpl: fetchMock,
@@ -162,19 +277,33 @@ describe('Works Square AI design adapter', () => {
});
expect(workspace.workspaceId).toBe('workspace-one');
expect(fetchMock).toHaveBeenCalledWith(
'https://square.example/api/design/workspaces/workspace-one/turns',
expect(fetchMock).toHaveBeenNthCalledWith(
2,
'https://square.example/api/agents/sessions/session-one/commands',
expect.objectContaining({
method: 'POST',
body: JSON.stringify({
client_turn_id: 'turn-two',
expected_turn_revision: 1,
message: '确认生成',
attachment_asset_ids: [],
action: { type: 'confirm_generation', quote_id: 'quote-one' },
client_command_id: 'turn-two',
name: 'turn.submit',
input: {
expected_turn_revision: 1,
message: '确认生成',
attachment_asset_ids: [],
action: { type: 'confirm_generation', quote_id: 'quote-one' },
},
}),
}),
);
expect(fetchMock).toHaveBeenNthCalledWith(
3,
'https://square.example/api/agents/sessions/session-one/runs/run-confirm',
expect.objectContaining({ headers: expect.any(Object) }),
);
expect(fetchMock).toHaveBeenNthCalledWith(
4,
'https://square.example/api/design/workspaces/workspace-one',
expect.objectContaining({ headers: expect.any(Object) }),
);
});
it('maps stable generation tasks and private asset relay paths', async () => {
@@ -278,13 +407,32 @@ describe('Works Square AI design adapter', () => {
type: 'design.workspace.updated',
schema_version: 1,
payload: {
workspace: {
workspace_id: 'workspace-one',
view_revision: 2,
},
workspace: serverWorkspace,
generation_tasks: [snapshotTask],
},
};
const assistantDeltaEvent = {
session_id: 'session-one',
sequence: 2,
runtime: 'design',
type: 'design.assistant.delta',
schema_version: 1,
payload: {
workspace_id: 'workspace-one',
client_turn_id: 'turn-two',
turn_revision: 2,
chunk_index: 0,
delta: '方向已经明确',
},
};
const malformedDeltaEvent = {
...assistantDeltaEvent,
sequence: 99,
payload: {
...assistantDeltaEvent.payload,
chunk_index: -1,
},
};
const taskEvent = {
session_id: 'session-one',
sequence: 3,
@@ -336,6 +484,8 @@ describe('Works Square AI design adapter', () => {
{
frames: [
{ type: 'event', event: snapshotEvent },
{ type: 'event', event: malformedDeltaEvent },
{ type: 'event', event: assistantDeltaEvent },
{ type: 'event', event: taskEvent },
],
closeCode: 1000,
@@ -367,12 +517,28 @@ describe('Works Square AI design adapter', () => {
type: 'design.generation_tasks.snapshot',
workspaceId: 'workspace-one',
workspaceViewRevision: 2,
workspace: expect.objectContaining({
workspaceId: 'workspace-one',
title: serverWorkspace.title,
messages: [expect.objectContaining({
text: serverWorkspace.messages[0].text,
})],
}),
generationTasks: [expect.objectContaining({
taskId: 'task-snapshot',
medium: 'image',
status: 'running',
})],
},
{
id: 'session-one:2',
type: 'design.assistant.delta',
workspaceId: 'workspace-one',
clientTurnId: 'turn-two',
turnRevision: 2,
chunkIndex: 0,
delta: '方向已经明确',
},
{
id: 'session-one:3',
type: 'design.generation_task.updated',