File size: 3,679 Bytes
abcf568
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import type { StudioProcessorStreamEvent } from '../../domain/types'
import { throwIfStudioRunCancelled } from '../../runtime/execution/run-cancellation'
import { determineStudioAgentLoopAction } from './loop-policy'
import { appendStudioAssistantConversationTurn, emitStudioAssistantText } from './message-assembly'
import { createStudioLoopFinishStepEvent, logStudioLoopStepFinished } from './observability'
import {
  buildStudioLoopStepRequest,
  createStudioLoopRuntime,
  persistStudioProviderSnapshot,
  requestStudioLoopStep
} from './request-builder'
import { StudioLoopCheckpointManager } from './state'
import { executeStudioToolCallsForStep } from './tool-dispatch'
import type { StudioOpenAIToolLoopInput } from './types'

export async function* createStudioOpenAIToolLoop(
  input: StudioOpenAIToolLoopInput
): AsyncGenerator<StudioProcessorStreamEvent> {
  const runtime = await createStudioLoopRuntime(input)
  const checkpoints = new StudioLoopCheckpointManager(input)

  for (let step = 0; step < runtime.maxSteps; step += 1) {
    throwIfStudioRunCancelled(input.abortSignal)
    const stepStartedAt = Date.now()

    if (step > 0) {
      runtime.currentAssistantMessage = await input.createAssistantMessage()
      yield {
        type: 'assistant-message-start',
        message: runtime.currentAssistantMessage
      }
    }

    const autonomy = await checkpoints.beginStep()
    throwIfStudioRunCancelled(input.abortSignal)
    const request = buildStudioLoopStepRequest(runtime)
    const result = await requestStudioLoopStep({
      loopInput: input,
      runtime,
      request,
      step,
      stepStartedAt
    })

    await persistStudioProviderSnapshot(input, runtime.currentAssistantMessage, result.message)
    yield* emitStudioAssistantText(result.assistantText)

    const nextAction = determineStudioAgentLoopAction({
      finishReason: result.completion.choices[0]?.finish_reason ?? null,
      toolCallCount: result.toolCalls.length,
      step,
      maxSteps: runtime.maxSteps
    })

    if (nextAction.type === 'finish') {
      await checkpoints.markSuccess()
      yield createStudioLoopFinishStepEvent(result.completion)
      return
    }

    if (nextAction.type === 'abort') {
      yield* emitStudioAssistantText(nextAction.message)
      await checkpoints.markFailure(nextAction.message)
      yield createStudioLoopFinishStepEvent(result.completion)
      return
    }

    appendStudioAssistantConversationTurn(runtime, result)

    const toolIterator = executeStudioToolCallsForStep(input, runtime, result, autonomy)
    let toolExecution: IteratorResult<StudioProcessorStreamEvent, { failureMessage: string | null }>
    while (true) {
      toolExecution = await toolIterator.next()
      if (toolExecution.done) {
        break
      }
      yield toolExecution.value
    }

    if (toolExecution.value.failureMessage) {
      const failedAutonomy = await checkpoints.markFailure(toolExecution.value.failureMessage)
      if (failedAutonomy.consecutiveFailures >= failedAutonomy.maxConsecutiveFailures) {
        const stopMessage = `Stopped after ${failedAutonomy.consecutiveFailures} consecutive failures: ${toolExecution.value.failureMessage}`
        yield* emitStudioAssistantText(stopMessage)
        await checkpoints.markStopped(stopMessage)
        yield createStudioLoopFinishStepEvent(result.completion)
        return
      }
    } else {
      await checkpoints.markSuccess()
    }

    logStudioLoopStepFinished({
      loopInput: input,
      step,
      failed: Boolean(toolExecution.value.failureMessage),
      stepStartedAt
    })

    yield createStudioLoopFinishStepEvent(result.completion)
  }
}