mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-14 13:53:02 -07:00
fix(core): throw explicit error on dropped tool responses (#26668)
This commit is contained in:
@@ -2227,6 +2227,69 @@ describe('LocalAgentExecutor', () => {
|
|||||||
// Agent should terminate with ABORTED status
|
// Agent should terminate with ABORTED status
|
||||||
expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
|
expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw a critical error when a tool response is dropped by the scheduler', async () => {
|
||||||
|
const definition = createTestDefinition([LS_TOOL_NAME]);
|
||||||
|
const executor = await LocalAgentExecutor.create(
|
||||||
|
definition,
|
||||||
|
mockConfig,
|
||||||
|
onActivity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Turn 1: Model calls two tools
|
||||||
|
mockModelResponse([
|
||||||
|
{ name: LS_TOOL_NAME, args: { path: 'dir1' }, id: 'call1' },
|
||||||
|
{ name: LS_TOOL_NAME, args: { path: 'dir2' }, id: 'call2' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Simulate scheduler returning only ONE result for TWO calls (dropped response)
|
||||||
|
mockScheduleAgentTools.mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
status: 'success',
|
||||||
|
request: { callId: 'call1', name: LS_TOOL_NAME },
|
||||||
|
response: {
|
||||||
|
responseParts: [
|
||||||
|
{
|
||||||
|
functionResponse: {
|
||||||
|
name: LS_TOOL_NAME,
|
||||||
|
id: 'call1',
|
||||||
|
response: { ok: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
executor.run({ goal: 'Protocol test' }, signal),
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Critical System Failure: Tool execution result was lost/dropped by the scheduler',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a critical error when all scheduler results are missing/dropped', async () => {
|
||||||
|
const definition = createTestDefinition([LS_TOOL_NAME]);
|
||||||
|
const executor = await LocalAgentExecutor.create(
|
||||||
|
definition,
|
||||||
|
mockConfig,
|
||||||
|
onActivity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Turn 1: Model calls one tool
|
||||||
|
mockModelResponse([
|
||||||
|
{ name: LS_TOOL_NAME, args: { path: 'dir1' }, id: 'call1' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Simulate scheduler returning NO results (dropped response)
|
||||||
|
mockScheduleAgentTools.mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
executor.run({ goal: 'Protocol test 2' }, signal),
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Critical System Failure: Tool execution result was lost/dropped by the scheduler',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Model Routing', () => {
|
describe('Model Routing', () => {
|
||||||
@@ -2334,7 +2397,15 @@ describe('LocalAgentExecutor', () => {
|
|||||||
},
|
},
|
||||||
response: {
|
response: {
|
||||||
resultDisplay: 'ls result',
|
resultDisplay: 'ls result',
|
||||||
responseParts: [],
|
responseParts: [
|
||||||
|
{
|
||||||
|
functionResponse: {
|
||||||
|
name: LS_TOOL_NAME,
|
||||||
|
id: 'call1',
|
||||||
|
response: { ok: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1287,25 +1287,29 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconstruct toolResponseParts in the original order
|
// Ensure exactly one response per function call to satisfy the Gemini API protocol.
|
||||||
const toolResponseParts: Part[] = [];
|
const toolResponseParts: Part[] = [];
|
||||||
for (const [index, functionCall] of functionCalls.entries()) {
|
for (const [index, functionCall] of functionCalls.entries()) {
|
||||||
const callId = functionCall.id ?? `${promptId}-${index}`;
|
const callId = functionCall.id ?? `${promptId}-${index}`;
|
||||||
const part = syncResults.get(callId);
|
const part = syncResults.get(callId);
|
||||||
|
|
||||||
if (part) {
|
if (part) {
|
||||||
toolResponseParts.push(part);
|
toolResponseParts.push(part);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If all authorized tool calls failed (and task isn't complete), provide a generic error.
|
const isAborted = signal.aborted;
|
||||||
if (
|
const isTaskComplete =
|
||||||
functionCalls.length > 0 &&
|
functionCall.name === COMPLETE_TASK_TOOL_NAME && taskCompleted;
|
||||||
toolResponseParts.length === 0 &&
|
|
||||||
!taskCompleted
|
// Safely skip missing responses if the run was interrupted or the turn won't be sent back.
|
||||||
) {
|
if (isAborted || isTaskComplete) {
|
||||||
toolResponseParts.push({
|
continue;
|
||||||
text: 'All tool calls failed or were unauthorized. Please analyze the errors and try an alternative approach.',
|
}
|
||||||
});
|
|
||||||
|
throw new Error(
|
||||||
|
`[LocalAgentExecutor] Critical System Failure: Tool execution result was lost/dropped by the scheduler for callId ${callId} (${functionCall.name}). This indicates an internal race condition or scheduler bug.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user