package openai import ( "encoding/json" "fmt" "path" "path/filepath" "reflect" "sort" "strings" "iop/packages/go/config" ) const ( modeDirect = config.ModeDirect modeLight = config.ModeLight ) const ( reasonDirectNoReservedControls = "direct_no_reserved_controls" reasonLightExactPrepare = "light_exact_prepare" reasonLightExactPair = "light_exact_pair" reasonMalformedPartialPair = "malformed_partial_pair" reasonMalformedMixedCalls = "malformed_mixed_calls" reasonMalformedDuplicateCalls = "malformed_duplicate_calls" reasonMalformedWrongPath = "malformed_wrong_path" reasonMalformedControlRole = "malformed_control_role" reasonMalformedConflictingPath = "malformed_conflicting_path" reasonModeDisabled = "mode_disabled" reasonUnhealthyRoute = "unhealthy_route" ) type reservedPaths struct { RequestID string JobDir string // e.g. ".iop/job/" PlanPath string // e.g. ".iop/job//plan.md" ReviewPath string // e.g. ".iop/job//review.md" } func newReservedPaths(requestID string) reservedPaths { cleanID := strings.TrimSpace(requestID) jobDir := ".iop/job/" + cleanID return reservedPaths{ RequestID: cleanID, JobDir: jobDir, PlanPath: jobDir + "/plan.md", ReviewPath: jobDir + "/review.md", } } type normalizedToolCall struct { ID string `json:"id"` ProviderCallID string `json:"provider_call_id,omitempty"` Name string `json:"name"` Arguments map[string]any `json:"arguments,omitempty"` RawArgs string `json:"raw_args,omitempty"` Path string `json:"path,omitempty"` } type normalizedStageOutput struct { ResponseID string `json:"response_id,omitempty"` Created int64 `json:"created,omitempty"` Content string `json:"content,omitempty"` Reasoning string `json:"reasoning,omitempty"` ReasoningSignature string `json:"reasoning_signature,omitempty"` ToolCalls []normalizedToolCall `json:"tool_calls,omitempty"` TerminalReason string `json:"terminal_reason,omitempty"` Usage json.RawMessage `json:"usage,omitempty"` OpenAIUsage *openAIUsage `json:"-"` } // hotPathSelectorGate is immutable evidence from the single provider-pool // admission that produced output. Classification never substitutes a caller // flag or re-resolves mutable catalog state for these facts. type hotPathSelectorGate struct { PresetID string SelectorModel string ModelGroupKey string ProviderID string RunID string NodeID string ExecutionPath string ProfileDriver string ProfileCapabilities []string Healthy bool CapabilitySatisfied bool } type hotPathDecision struct { Mode string `json:"mode"` Reason string `json:"reason"` PrepareCall *normalizedToolCall `json:"prepare_call,omitempty"` PairCalls []normalizedToolCall `json:"pair_calls,omitempty"` GeneralCalls []normalizedToolCall `json:"general_calls,omitempty"` } func classifyHotPathOutput(preset config.ExecutionPreset, issuedPaths reservedPaths, output normalizedStageOutput, gate hotPathSelectorGate) (hotPathDecision, error) { if !gate.Healthy || !gate.CapabilitySatisfied || gate.PresetID != preset.ID || gate.SelectorModel != preset.Selector.Model || strings.TrimSpace(gate.ModelGroupKey) == "" || strings.TrimSpace(gate.ProviderID) == "" || strings.TrimSpace(gate.RunID) == "" || strings.TrimSpace(gate.NodeID) == "" || strings.TrimSpace(gate.ExecutionPath) == "" || strings.TrimSpace(gate.ProfileDriver) == "" { return hotPathDecision{Reason: reasonUnhealthyRoute}, fmt.Errorf("route capability or health gate check failed (%s)", reasonUnhealthyRoute) } var prepareCalls []normalizedToolCall var planCalls []normalizedToolCall var reviewCalls []normalizedToolCall var wrongPathCalls []normalizedToolCall var generalCalls []normalizedToolCall for _, tc := range output.ToolCalls { control, err := classifyReservedControlCall(preset, issuedPaths, tc) if err != nil { return hotPathDecision{Reason: control.reason}, err } switch control.kind { case "": generalCalls = append(generalCalls, tc) case "prepare": prepareCalls = append(prepareCalls, tc) case "plan": planCalls = append(planCalls, tc) case "review": reviewCalls = append(reviewCalls, tc) default: wrongPathCalls = append(wrongPathCalls, tc) } } if len(wrongPathCalls) > 0 { return hotPathDecision{Reason: reasonMalformedWrongPath}, fmt.Errorf("malformed output: tool call targets wrong or invalid reserved path (%s)", reasonMalformedWrongPath) } reservedCount := len(prepareCalls) + len(planCalls) + len(reviewCalls) // Mode Direct Candidate if reservedCount == 0 { if !isModeAllowed(preset, modeDirect) { return hotPathDecision{Reason: reasonModeDisabled}, fmt.Errorf("mode %q is disabled for preset %q (%s)", modeDirect, preset.ID, reasonModeDisabled) } return hotPathDecision{ Mode: modeDirect, Reason: reasonDirectNoReservedControls, GeneralCalls: generalCalls, }, nil } // Mode Light Candidate if !isModeAllowed(preset, modeLight) { return hotPathDecision{Reason: reasonModeDisabled}, fmt.Errorf("mode %q is disabled for preset %q (%s)", modeLight, preset.ID, reasonModeDisabled) } if len(generalCalls) > 0 { return hotPathDecision{Reason: reasonMalformedMixedCalls}, fmt.Errorf("malformed output: mixed reserved controls and general tool calls (%s)", reasonMalformedMixedCalls) } if len(prepareCalls) > 1 || len(planCalls) > 1 || len(reviewCalls) > 1 { return hotPathDecision{Reason: reasonMalformedDuplicateCalls}, fmt.Errorf("malformed output: duplicate reserved control calls (%s)", reasonMalformedDuplicateCalls) } // Exact Prepare if len(prepareCalls) == 1 && len(planCalls) == 0 && len(reviewCalls) == 0 { prep := prepareCalls[0] return hotPathDecision{ Mode: modeLight, Reason: reasonLightExactPrepare, PrepareCall: &prep, }, nil } // Exact Pair if len(prepareCalls) == 0 && len(planCalls) == 1 && len(reviewCalls) == 1 { return hotPathDecision{ Mode: modeLight, Reason: reasonLightExactPair, PairCalls: []normalizedToolCall{planCalls[0], reviewCalls[0]}, }, nil } return hotPathDecision{Reason: reasonMalformedPartialPair}, fmt.Errorf("malformed output: partial reserved control pair (%s)", reasonMalformedPartialPair) } type reservedControlClassification struct { kind string reason string } func classifyReservedControlCall(preset config.ExecutionPreset, issued reservedPaths, tc normalizedToolCall) (reservedControlClassification, error) { sources := reservedPathSourcesFromToolCall(tc) paths := reservedPathsFromToolCall(tc) if len(sources) == 0 { return reservedControlClassification{}, nil } if len(sources) != 1 || len(paths) != 1 { return reservedControlClassification{reason: reasonMalformedConflictingPath}, fmt.Errorf("malformed output: conflicting reserved path sources (%s)", reasonMalformedConflictingPath) } observed := paths[0] type roleMatch struct { role string path string } var matches []roleMatch for _, alternative := range preset.WorkspaceTools { for _, role := range []string{"prepare", "write"} { op, ok := alternative.Operations[role] if !ok || strings.TrimSpace(op.ToolName) != strings.TrimSpace(tc.Name) { continue } mappedPath, ok := mappedControlPath(tc, op) if !ok { continue } matches = append(matches, roleMatch{role: role, path: mappedPath}) } } if len(matches) == 0 { return reservedControlClassification{reason: reasonMalformedControlRole}, fmt.Errorf("malformed output: reserved path used by a non-canonical control role (%s)", reasonMalformedControlRole) } cleanJobDir := cleanRelativePath(issued.JobDir) cleanPlan := cleanRelativePath(issued.PlanPath) cleanReview := cleanRelativePath(issued.ReviewPath) for _, match := range matches { if match.path != observed { continue } switch { case match.role == "prepare" && observed == cleanJobDir: return reservedControlClassification{kind: "prepare"}, nil case match.role == "write" && observed == cleanPlan: return reservedControlClassification{kind: "plan"}, nil case match.role == "write" && observed == cleanReview: return reservedControlClassification{kind: "review"}, nil } } if observed != cleanJobDir && observed != cleanPlan && observed != cleanReview { return reservedControlClassification{reason: reasonMalformedWrongPath}, fmt.Errorf("malformed output: tool call targets wrong or invalid reserved path (%s)", reasonMalformedWrongPath) } for _, match := range matches { if match.path != observed { return reservedControlClassification{reason: reasonMalformedWrongPath}, fmt.Errorf("malformed output: mapped control path must equal the complete issued path (%s)", reasonMalformedWrongPath) } } return reservedControlClassification{reason: reasonMalformedControlRole}, fmt.Errorf("malformed output: canonical control role does not match reserved path (%s)", reasonMalformedControlRole) } func mappedControlPath(tc normalizedToolCall, op config.ExecutionWorkspaceOperation) (string, bool) { mapped, ok := op.ArgumentMap["path"].(string) if !ok || strings.TrimSpace(mapped) == "" { return "", false } value, ok := lookupMappedArgument(tc.Arguments, mapped) if !ok && tc.RawArgs != "" { var args map[string]any decoder := json.NewDecoder(strings.NewReader(tc.RawArgs)) decoder.UseNumber() if decoder.Decode(&args) == nil { value, ok = lookupMappedArgument(args, mapped) } } if !ok { return "", false } text, ok := value.(string) if !ok { return "", false } mappedPath := cleanRelativePath(text) if mappedPath == "" || mappedPath == "." { return "", false } return mappedPath, true } func lookupMappedArgument(arguments map[string]any, mapped string) (any, bool) { if arguments == nil { return nil, false } parts := strings.Split(mapped, ".") var current any = arguments for _, part := range parts { object, ok := current.(map[string]any) if !ok { return nil, false } current, ok = object[part] if !ok { return nil, false } } return current, true } func reservedPathsFromToolCall(tc normalizedToolCall) []string { set := make(map[string]struct{}) for _, item := range reservedPathSourcesFromToolCall(tc) { set[item] = struct{}{} } paths := make([]string, 0, len(set)) for item := range set { paths = append(paths, item) } sort.Strings(paths) return paths } // reservedPathSourcesFromToolCall preserves each independently supplied // reserved-path occurrence. RawArgs normally serializes Arguments for normalized // provider calls, so an equivalent decoded copy is not counted twice. A raw // argument that differs from the decoded argument is still an independent source // and must be rejected if it contains a reserved path. func reservedPathSourcesFromToolCall(tc normalizedToolCall) []string { var paths []string add := func(value string) { paths = append(paths, reservedPathsFromString(value)...) } add(tc.Path) if tc.Arguments != nil { collectReservedStrings(tc.Arguments, add) if tc.RawArgs == "" { return paths } var decoded map[string]any decoder := json.NewDecoder(strings.NewReader(tc.RawArgs)) decoder.UseNumber() if decoder.Decode(&decoded) == nil && decoded != nil { if !reflect.DeepEqual(decoded, tc.Arguments) { collectReservedStrings(decoded, add) } return paths } add(tc.RawArgs) return paths } if tc.RawArgs == "" { return paths } var decoded any decoder := json.NewDecoder(strings.NewReader(tc.RawArgs)) decoder.UseNumber() if decoder.Decode(&decoded) == nil { collectReservedStrings(decoded, add) } else { add(tc.RawArgs) } return paths } func collectReservedStrings(value any, add func(string)) { switch typed := value.(type) { case string: add(typed) case map[string]any: for _, item := range typed { collectReservedStrings(item, add) } case []any: for _, item := range typed { collectReservedStrings(item, add) } } } func reservedPathsFromString(value string) []string { normalized := strings.ReplaceAll(value, `\/`, "/") normalized = filepath.ToSlash(normalized) var paths []string for search := normalized; ; { idx := strings.Index(search, ".iop/job") if idx < 0 { break } candidate := search[idx:] end := len(candidate) for i, ch := range candidate { if ch == ' ' || ch == '\t' || ch == '\n' || ch == '"' || ch == '\'' || ch == '`' || ch == ';' || ch == ',' || ch == '}' || ch == ']' || ch == ')' { end = i break } } paths = append(paths, cleanRelativePath(candidate[:end])) advance := idx + len(".iop/job") if advance >= len(search) { break } search = search[advance:] } return paths } func isModeAllowed(preset config.ExecutionPreset, mode string) bool { for _, m := range preset.AllowedModes { if m == mode { return true } } return false } func extractPathFromToolCall(tc normalizedToolCall) string { paths := reservedPathsFromToolCall(tc) if len(paths) == 1 { return paths[0] } return "" } func extractIopJobPath(s string) string { idx := strings.Index(s, ".iop/job/") if idx < 0 { return "" } sub := s[idx:] for i, ch := range sub { if ch == ' ' || ch == '\t' || ch == '\n' || ch == '"' || ch == '\'' || ch == '`' || ch == ';' { return sub[:i] } } return sub } func cleanRelativePath(p string) string { p = strings.TrimSpace(p) p = filepath.ToSlash(p) p = path.Clean(p) p = strings.TrimPrefix(p, "./") p = strings.TrimSuffix(p, "/") return p }