|
|
@@ -1966,4 +1966,153 @@ describe("Queued message processing after condense", () => {
|
|
|
expect(spyB).toHaveBeenCalledWith("B message", undefined)
|
|
|
expect(taskB.messageQueueService.isEmpty()).toBe(true)
|
|
|
})
|
|
|
+
|
|
|
+ describe("completeSubtask native protocol handling", () => {
|
|
|
+ let mockProvider: any
|
|
|
+ let mockApiConfig: any
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks()
|
|
|
+
|
|
|
+ if (!TelemetryService.hasInstance()) {
|
|
|
+ TelemetryService.createInstance([])
|
|
|
+ }
|
|
|
+
|
|
|
+ mockApiConfig = {
|
|
|
+ apiProvider: "anthropic",
|
|
|
+ apiKey: "test-key",
|
|
|
+ }
|
|
|
+
|
|
|
+ mockProvider = {
|
|
|
+ context: {
|
|
|
+ globalStorageUri: { fsPath: "/test/storage" },
|
|
|
+ },
|
|
|
+ getState: vi.fn().mockResolvedValue({
|
|
|
+ apiConfiguration: mockApiConfig,
|
|
|
+ }),
|
|
|
+ say: vi.fn(),
|
|
|
+ postStateToWebview: vi.fn().mockResolvedValue(undefined),
|
|
|
+ postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
|
|
+ updateTaskHistory: vi.fn().mockResolvedValue(undefined),
|
|
|
+ log: vi.fn(),
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should push tool_result to userMessageContent for native protocol with pending tool call ID", async () => {
|
|
|
+ // Create a task with a model that supports native tools
|
|
|
+ const task = new Task({
|
|
|
+ provider: mockProvider,
|
|
|
+ apiConfiguration: {
|
|
|
+ ...mockApiConfig,
|
|
|
+ apiProvider: "anthropic",
|
|
|
+ toolProtocol: "native", // Explicitly set native protocol
|
|
|
+ },
|
|
|
+ task: "parent task",
|
|
|
+ startTask: false,
|
|
|
+ })
|
|
|
+
|
|
|
+ // Mock the API to return a native protocol model
|
|
|
+ vi.spyOn(task.api, "getModel").mockReturnValue({
|
|
|
+ id: "claude-3-5-sonnet-20241022",
|
|
|
+ info: {
|
|
|
+ contextWindow: 200000,
|
|
|
+ maxTokens: 8192,
|
|
|
+ supportsPromptCache: true,
|
|
|
+ supportsNativeTools: true,
|
|
|
+ defaultToolProtocol: "native",
|
|
|
+ } as ModelInfo,
|
|
|
+ })
|
|
|
+
|
|
|
+ // For native protocol, NewTaskTool does NOT push tool_result immediately.
|
|
|
+ // It only sets the pending tool call ID. The actual tool_result is pushed by completeSubtask.
|
|
|
+ task.pendingNewTaskToolCallId = "test-tool-call-id"
|
|
|
+
|
|
|
+ // Call completeSubtask
|
|
|
+ await task.completeSubtask("Subtask completed successfully")
|
|
|
+
|
|
|
+ // For native protocol, should push the actual tool_result with the subtask's result
|
|
|
+ expect(task.userMessageContent).toHaveLength(1)
|
|
|
+ expect(task.userMessageContent[0]).toEqual({
|
|
|
+ type: "tool_result",
|
|
|
+ tool_use_id: "test-tool-call-id",
|
|
|
+ content: "[new_task completed] Result: Subtask completed successfully",
|
|
|
+ })
|
|
|
+
|
|
|
+ // Should NOT have added a user message to apiConversationHistory
|
|
|
+ expect(task.apiConversationHistory).toHaveLength(0)
|
|
|
+
|
|
|
+ // pending tool call ID should be cleared
|
|
|
+ expect(task.pendingNewTaskToolCallId).toBeUndefined()
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should add user message to apiConversationHistory for XML protocol", async () => {
|
|
|
+ // Create a task with a model that doesn't support native tools
|
|
|
+ const task = new Task({
|
|
|
+ provider: mockProvider,
|
|
|
+ apiConfiguration: {
|
|
|
+ ...mockApiConfig,
|
|
|
+ apiProvider: "anthropic",
|
|
|
+ },
|
|
|
+ task: "parent task",
|
|
|
+ startTask: false,
|
|
|
+ })
|
|
|
+
|
|
|
+ // Mock the API to return an XML protocol model (no native tool support)
|
|
|
+ vi.spyOn(task.api, "getModel").mockReturnValue({
|
|
|
+ id: "claude-2",
|
|
|
+ info: {
|
|
|
+ contextWindow: 100000,
|
|
|
+ maxTokens: 4096,
|
|
|
+ supportsPromptCache: false,
|
|
|
+ supportsNativeTools: false,
|
|
|
+ } as ModelInfo,
|
|
|
+ })
|
|
|
+
|
|
|
+ // Call completeSubtask
|
|
|
+ await task.completeSubtask("Subtask completed successfully")
|
|
|
+
|
|
|
+ // For XML protocol, should add to apiConversationHistory
|
|
|
+ expect(task.apiConversationHistory).toHaveLength(1)
|
|
|
+ expect(task.apiConversationHistory[0]).toEqual(
|
|
|
+ expect.objectContaining({
|
|
|
+ role: "user",
|
|
|
+ content: [{ type: "text", text: "[new_task completed] Result: Subtask completed successfully" }],
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ // Should NOT have added to userMessageContent
|
|
|
+ expect(task.userMessageContent).toHaveLength(0)
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should set isPaused to false after completeSubtask", async () => {
|
|
|
+ const task = new Task({
|
|
|
+ provider: mockProvider,
|
|
|
+ apiConfiguration: mockApiConfig,
|
|
|
+ task: "parent task",
|
|
|
+ startTask: false,
|
|
|
+ })
|
|
|
+
|
|
|
+ // Mock the API to return an XML protocol model
|
|
|
+ vi.spyOn(task.api, "getModel").mockReturnValue({
|
|
|
+ id: "claude-2",
|
|
|
+ info: {
|
|
|
+ contextWindow: 100000,
|
|
|
+ maxTokens: 4096,
|
|
|
+ supportsPromptCache: false,
|
|
|
+ supportsNativeTools: false,
|
|
|
+ } as ModelInfo,
|
|
|
+ })
|
|
|
+
|
|
|
+ // Set isPaused to true (simulating waiting for subtask)
|
|
|
+ task.isPaused = true
|
|
|
+ task.childTaskId = "child-task-id"
|
|
|
+
|
|
|
+ // Call completeSubtask
|
|
|
+ await task.completeSubtask("Subtask completed")
|
|
|
+
|
|
|
+ // Should reset paused state
|
|
|
+ expect(task.isPaused).toBe(false)
|
|
|
+ expect(task.childTaskId).toBeUndefined()
|
|
|
+ })
|
|
|
+ })
|
|
|
})
|