iop/apps/edge/internal/openai/hot_path_review.go
toki 495996fee4 feat(openai): 핫패스 에이전트 실행 경로를 확장한다
Anthropic·Chat 게이트와 관찰·종료 제어를 통합하고 관련 계약·검증 산출물을 반영한다.
2026-08-06 00:09:24 +09:00

102 lines
3.3 KiB
Go

package openai
import (
"context"
"fmt"
)
func (s *Server) advanceHotPathReview(
ctx context.Context,
requestID string,
phase hotPathLightPhase,
output normalizedStageOutput,
visible normalizedStageOutput,
outer *hotPathOuterTurn,
protocol string,
) (normalizedStageOutput, bool, error) {
kind, cleanup, err := classifyHotPathReviewOutput(requestID, phase, output)
if err != nil {
return normalizedStageOutput{}, false, err
}
if cleanup {
terminalOutput := output.StageResponseOverlay(visible)
if outer != nil {
terminalOutput = hotPathCompatibilityOutput(outer, terminalOutput, protocol)
}
intent := hotPathTerminalIntent{Output: terminalOutput}
mapped, err := s.lightFlows.beginCleanupWithOuter(ctx, requestID, s.edgeIDValue(), intent, outer, s.requestCoordinator)
if err != nil {
return normalizedStageOutput{}, false, err
}
return mapped, true, nil
}
mapped, err := s.lightFlows.issueTools(ctx, requestID, s.edgeIDValue(), output, visible, kind, outer, s.requestCoordinator)
if err != nil {
return normalizedStageOutput{}, false, err
}
return mapped, true, nil
}
func classifyHotPathReviewOutput(requestID string, phase hotPathLightPhase, output normalizedStageOutput) (hotPathPendingKind, bool, error) {
paths := newReservedPaths(requestID)
switch phase {
case hotPathPhaseReviewActive:
if len(output.ToolCalls) == 0 {
return "", false, fmt.Errorf("review stage completed before writing the issued review artifact")
}
writeCount := 0
reservedCount := 0
for _, call := range output.ToolCalls {
observed := reservedPathsFromToolCall(call)
if len(observed) == 0 {
continue
}
reservedCount++
if len(observed) == 1 && cleanRelativePath(observed[0]) == cleanRelativePath(paths.ReviewPath) {
writeCount++
}
}
if writeCount == 0 && reservedCount == 0 {
return hotPathPendingReviewInspection, false, nil
}
if writeCount == 1 && reservedCount == 1 && len(output.ToolCalls) == 1 {
return hotPathPendingReviewWrite, false, nil
}
return "", false, fmt.Errorf("review write must be one exact review-path tool call")
case hotPathPhaseReviewAwaitRead:
if len(output.ToolCalls) != 1 {
return "", false, fmt.Errorf("review write result must be followed by one exact review read")
}
observed := reservedPathsFromToolCall(output.ToolCalls[0])
if len(observed) != 1 || cleanRelativePath(observed[0]) != cleanRelativePath(paths.ReviewPath) {
return "", false, fmt.Errorf("review write result must be followed by the issued review read")
}
return hotPathPendingReviewRead, false, nil
case hotPathPhaseReviewResolution:
if len(output.ToolCalls) == 0 {
return "", true, nil
}
for _, call := range output.ToolCalls {
if len(reservedPathsFromToolCall(call)) > 0 {
return "", false, fmt.Errorf("review resolution cannot start another reserved review cycle")
}
}
return hotPathPendingReviewRepair, false, nil
case hotPathPhaseReviewRepair:
if len(output.ToolCalls) == 0 {
return "", true, nil
}
for _, call := range output.ToolCalls {
if len(reservedPathsFromToolCall(call)) > 0 {
return "", false, fmt.Errorf("repair cannot start a second review cycle")
}
}
return hotPathPendingReviewRepair, false, nil
default:
return "", false, fmt.Errorf("phase %q is not a review phase", phase)
}
}