fix re enter problem
This commit is contained in:
@@ -172,34 +172,3 @@ fn build_tools_system_prompt(base_prompt: &str, tool_specs: &[ToolPromptSpec]) -
|
||||
|
||||
prompt
|
||||
}
|
||||
|
||||
//
|
||||
// async fn run_chat_agent(config: LlmConfig) -> Result<(), LlmError> {
|
||||
// let client = LlmClient::new(config)?;
|
||||
// let agent = LlmChatAgentBuilder::new()
|
||||
// .with_client(client)
|
||||
// .system_prompt("You are a helpful assistant with an echo tool.")
|
||||
// .build();
|
||||
//
|
||||
// let names = agent
|
||||
// .tools()
|
||||
// .iter()
|
||||
// .map(|t| t.tool_name().to_string())
|
||||
// .collect();
|
||||
// let tool_hook = ToolValidationHook::new(names.clone());
|
||||
//
|
||||
// let output = agent
|
||||
// .prompt(LlmMessage::user(
|
||||
// "Use the echo tool to echo 'Hello from the JSON harness!', then tell me what it said.",
|
||||
// ))
|
||||
// .max_turns(5)
|
||||
// .add_hook(tool_hook)
|
||||
// .await
|
||||
// .expect("agent should succeed");
|
||||
//
|
||||
// println!("--- Final Agent Response ---");
|
||||
// println!("{}", output.text);
|
||||
// println!("-----------------------------");
|
||||
//
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
@@ -481,15 +481,15 @@ pub async fn confirm_editor_agent_tool_call(
|
||||
.with_details(json!({ "message": "message not found" })));
|
||||
}
|
||||
|
||||
let msg = &document.messages[message_id];
|
||||
let msg = &mut document.messages[message_id];
|
||||
|
||||
// Validate role and tool_call
|
||||
// A persisted executing state prevents a repeated confirmation from starting a second job.
|
||||
if msg.role != EditorAgentMessageRole::System {
|
||||
return Err(editor_agent_bad_request("message is not a system message"));
|
||||
}
|
||||
let tc = msg
|
||||
.tool_call
|
||||
.as_ref()
|
||||
.as_mut()
|
||||
.ok_or_else(|| editor_agent_bad_request("message has no tool call"))?;
|
||||
if tc.status != EditorAgentToolCallStatus::PendingConfirmation {
|
||||
return Err(editor_agent_bad_request(
|
||||
@@ -497,10 +497,13 @@ pub async fn confirm_editor_agent_tool_call(
|
||||
));
|
||||
}
|
||||
|
||||
let tool_name = tc.tool_name.clone();
|
||||
let tool_args = tc.args.clone();
|
||||
|
||||
// TODO dont use match
|
||||
match tc.tool_name.as_str() {
|
||||
match tool_name.as_str() {
|
||||
EditImageTool::NAME => {
|
||||
let args: EditImageToolArgs = serde_json::from_value(tc.args.clone())
|
||||
let args: EditImageToolArgs = serde_json::from_value(tool_args)
|
||||
.map_err(|e| editor_agent_bad_request(format!("invalid tool call args: {e}")))?;
|
||||
|
||||
// Get project
|
||||
@@ -533,6 +536,13 @@ pub async fn confirm_editor_agent_tool_call(
|
||||
let edit_tool = EditImageTool {
|
||||
context: tool_context,
|
||||
};
|
||||
|
||||
// mark it quickly
|
||||
if let Some(tool_call) = &mut document.messages[message_id].tool_call {
|
||||
tool_call.status = EditorAgentToolCallStatus::Executing;
|
||||
}
|
||||
write_messages_document(&state, &conversation, &document).await?;
|
||||
|
||||
let result = edit_tool
|
||||
.execute(
|
||||
&state,
|
||||
@@ -546,7 +556,21 @@ pub async fn confirm_editor_agent_tool_call(
|
||||
None, // source_resource_id
|
||||
Some(canvas_completion),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
let msg = &mut document.messages[message_id];
|
||||
msg.text = format!("[tool_call:{tool_name}] output: {error}");
|
||||
if let Some(tool_call) = &mut msg.tool_call {
|
||||
tool_call.status = EditorAgentToolCallStatus::Failed;
|
||||
tool_call.error = Some(error.to_string());
|
||||
}
|
||||
write_messages_document(&state, &conversation, &document).await?;
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Build generated images
|
||||
let generated_images = vec![EditorAgentGeneratedImage {
|
||||
@@ -579,7 +603,7 @@ pub async fn confirm_editor_agent_tool_call(
|
||||
}
|
||||
_ => Err(editor_agent_bad_request(format!(
|
||||
"unsupported tool: {}",
|
||||
tc.tool_name
|
||||
tool_name
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
+72
-1
@@ -3,7 +3,10 @@
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { EditorAgentMessageResponse } from '../../../../packages/shared/src/contracts/editorAgent.ts';
|
||||
import type {
|
||||
EditorAgentMessage,
|
||||
EditorAgentMessageResponse,
|
||||
} from '../../../../packages/shared/src/contracts/editorAgent.ts';
|
||||
import {
|
||||
type EditorAgentConversationClient,
|
||||
useEditorAgentConversation,
|
||||
@@ -305,6 +308,74 @@ describe('useEditorAgentConversation', () => {
|
||||
expect(onCanvasRefreshRequested).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('marks a confirmation as executing before the request finishes', async () => {
|
||||
const client = createClient();
|
||||
const pendingMessage: EditorAgentMessage = {
|
||||
id: 0,
|
||||
role: 'system',
|
||||
text: '需要生成一张图',
|
||||
attachments: [],
|
||||
toolCall: {
|
||||
toolName: 'edit-image',
|
||||
summary: '',
|
||||
status: 'pending_confirmation',
|
||||
args: { object_image_id: 'source-image', prompt: '换成像素风' },
|
||||
images: [],
|
||||
error: null,
|
||||
},
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
};
|
||||
const completedMessage: EditorAgentMessage = {
|
||||
...pendingMessage,
|
||||
toolCall: {
|
||||
...pendingMessage.toolCall!,
|
||||
status: 'completed',
|
||||
images: [],
|
||||
},
|
||||
};
|
||||
let resolveConfirmation: ((message: EditorAgentMessage) => void) | undefined;
|
||||
vi.mocked(client.getConversation).mockResolvedValue({
|
||||
conversationId: 'conversation-1',
|
||||
projectId: 'project-1',
|
||||
title: '角色参考',
|
||||
messages: [pendingMessage],
|
||||
createdAt: '2026-07-03T00:00:00.000Z',
|
||||
updatedAt: '2026-07-03T00:00:00.000Z',
|
||||
});
|
||||
vi.mocked(client.confirmToolCall).mockImplementation(
|
||||
() =>
|
||||
new Promise<EditorAgentMessage>((resolve) => {
|
||||
resolveConfirmation = resolve;
|
||||
}),
|
||||
);
|
||||
const { result } = renderHook(() =>
|
||||
useEditorAgentConversation({ projectId: 'project-1', client }),
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.messages[0]?.toolCall?.status).toBe(
|
||||
'pending_confirmation',
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
void result.current.confirmToolCall(0);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.messages[0]?.toolCall?.status).toBe('executing');
|
||||
});
|
||||
await result.current.confirmToolCall(0);
|
||||
expect(client.confirmToolCall).toHaveBeenCalledTimes(1);
|
||||
|
||||
await act(async () => {
|
||||
resolveConfirmation?.(completedMessage);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.messages[0]?.toolCall?.status).toBe('completed');
|
||||
});
|
||||
});
|
||||
|
||||
it('cancels a pending tool call and keeps the replacement in the same position', async () => {
|
||||
const client = createClient();
|
||||
const pendingMessage = {
|
||||
|
||||
@@ -409,6 +409,23 @@ export function useEditorAgentConversation({
|
||||
setToolCallAction(nextAction);
|
||||
setErrorMessage(null);
|
||||
|
||||
if (action === 'confirm') {
|
||||
setMessages((currentMessages) =>
|
||||
currentMessages.map((message) =>
|
||||
message.id === messageId &&
|
||||
message.toolCall?.status === 'pending_confirmation'
|
||||
? {
|
||||
...message,
|
||||
toolCall: {
|
||||
...message.toolCall,
|
||||
status: 'executing',
|
||||
},
|
||||
}
|
||||
: message,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const updatedMessage = await (action === 'confirm'
|
||||
? client.confirmToolCall(conversationId, messageId)
|
||||
@@ -436,6 +453,8 @@ export function useEditorAgentConversation({
|
||||
? '确认画布 Agent 操作失败'
|
||||
: '取消画布 Agent 操作失败',
|
||||
);
|
||||
// The server may have accepted the action even when its response was lost.
|
||||
void loadConversation(conversationId).catch(() => undefined);
|
||||
}
|
||||
return null;
|
||||
} finally {
|
||||
@@ -445,7 +464,7 @@ export function useEditorAgentConversation({
|
||||
}
|
||||
}
|
||||
},
|
||||
[client, requestCanvasRefreshForMessages],
|
||||
[client, loadConversation, requestCanvasRefreshForMessages],
|
||||
);
|
||||
|
||||
const confirmToolCall = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user