Skip to content

Commit 9a619ad

Browse files
committed
fix: prevent duplicate function_call items in session history after resuming from interruptions
1 parent 152c8d1 commit 9a619ad

File tree

1 file changed

+45
-16
lines changed
  • packages/agents-core/src

1 file changed

+45
-16
lines changed

packages/agents-core/src/run.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -732,24 +732,35 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
732732

733733
state._originalInput = turnResult.originalInput;
734734
state._generatedItems = turnResult.generatedItems;
735-
if (turnResult.nextStep.type === 'next_step_run_again') {
736-
state._currentTurnPersistedItemCount = 0;
737-
}
738-
state._currentStep = turnResult.nextStep;
735+
// Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
736+
// The counter will be reset when _currentTurn is incremented (starting a new turn)
739737

740738
if (turnResult.nextStep.type === 'next_step_interruption') {
741739
// we are still in an interruption, so we need to avoid an infinite loop
740+
state._currentStep = turnResult.nextStep;
742741
return new RunResult<TContext, TAgent>(state);
743742
}
744743

745-
continue;
744+
// If continuing from interruption with next_step_run_again, set step to undefined
745+
// so the loop treats it as a new step without incrementing the turn.
746+
// The counter has already been adjusted by resolveInterruptedTurn's rewind logic.
747+
if (turnResult.nextStep.type === 'next_step_run_again') {
748+
state._currentStep = undefined;
749+
continue;
750+
}
751+
752+
state._currentStep = turnResult.nextStep;
746753
}
747754

748755
if (state._currentStep.type === 'next_step_run_again') {
749756
const artifacts = await prepareAgentArtifacts(state);
750757

751-
state._currentTurn++;
752-
state._currentTurnPersistedItemCount = 0;
758+
// Only increment turn and reset counter if we're starting a new turn,
759+
// not if we're continuing from an interruption (which would have _lastTurnResponse set)
760+
if (!state._lastTurnResponse) {
761+
state._currentTurn++;
762+
state._currentTurnPersistedItemCount = 0;
763+
}
753764

754765
if (state._currentTurn > state._maxTurns) {
755766
state._currentAgentSpan?.setError({
@@ -770,7 +781,8 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
770781
let parallelGuardrailPromise:
771782
| Promise<InputGuardrailResult[]>
772783
| undefined;
773-
if (state._currentTurn === 1) {
784+
// Only run input guardrails on the first turn of a new run, not when continuing from an interruption
785+
if (state._currentTurn === 1 && !state._lastTurnResponse) {
774786
const guardrails = this.#splitInputGuardrails(state);
775787
if (guardrails.blocking.length > 0) {
776788
await this.#runInputGuardrails(state, guardrails.blocking);
@@ -1021,22 +1033,35 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
10211033

10221034
result.state._originalInput = turnResult.originalInput;
10231035
result.state._generatedItems = turnResult.generatedItems;
1024-
if (turnResult.nextStep.type === 'next_step_run_again') {
1025-
result.state._currentTurnPersistedItemCount = 0;
1026-
}
1027-
result.state._currentStep = turnResult.nextStep;
1036+
// Don't reset counter here - resolveInterruptedTurn already adjusted it via rewind logic
1037+
// The counter will be reset when _currentTurn is incremented (starting a new turn)
1038+
10281039
if (turnResult.nextStep.type === 'next_step_interruption') {
10291040
// we are still in an interruption, so we need to avoid an infinite loop
1041+
result.state._currentStep = turnResult.nextStep;
10301042
return;
10311043
}
1032-
continue;
1044+
1045+
// If continuing from interruption with next_step_run_again, set step to undefined
1046+
// so the loop treats it as a new step without incrementing the turn.
1047+
// The counter has already been adjusted by resolveInterruptedTurn's rewind logic.
1048+
if (turnResult.nextStep.type === 'next_step_run_again') {
1049+
result.state._currentStep = undefined;
1050+
continue;
1051+
}
1052+
1053+
result.state._currentStep = turnResult.nextStep;
10331054
}
10341055

10351056
if (result.state._currentStep.type === 'next_step_run_again') {
10361057
const artifacts = await prepareAgentArtifacts(result.state);
10371058

1038-
result.state._currentTurn++;
1039-
result.state._currentTurnPersistedItemCount = 0;
1059+
// Only increment turn and reset counter if we're starting a new turn,
1060+
// not if we're continuing from an interruption (which would have _lastTurnResponse set)
1061+
if (!result.state._lastTurnResponse) {
1062+
result.state._currentTurn++;
1063+
result.state._currentTurnPersistedItemCount = 0;
1064+
}
10401065

10411066
if (result.state._currentTurn > result.state._maxTurns) {
10421067
result.state._currentAgentSpan?.setError({
@@ -1057,7 +1082,11 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
10571082
let parallelGuardrailPromise:
10581083
| Promise<InputGuardrailResult[]>
10591084
| undefined;
1060-
if (result.state._currentTurn === 1) {
1085+
// Only run input guardrails on the first turn of a new run, not when continuing from an interruption
1086+
if (
1087+
result.state._currentTurn === 1 &&
1088+
!result.state._lastTurnResponse
1089+
) {
10611090
const guardrails = this.#splitInputGuardrails(result.state);
10621091
if (guardrails.blocking.length > 0) {
10631092
await this.#runInputGuardrails(result.state, guardrails.blocking);

0 commit comments

Comments
 (0)