|
1 | 1 | import type { |
| 2 | + ActionabilityJudgmentArtefact, |
2 | 3 | AvailableSuggestedReviewer, |
3 | 4 | AvailableSuggestedReviewersResponse, |
| 5 | + PriorityJudgmentArtefact, |
4 | 6 | SandboxEnvironment, |
5 | 7 | SandboxEnvironmentInput, |
| 8 | + SignalFindingArtefact, |
6 | 9 | SignalProcessingStateResponse, |
7 | 10 | SignalReport, |
8 | 11 | SignalReportArtefact, |
@@ -76,19 +79,138 @@ function optionalString(value: unknown): string | null { |
76 | 79 | return typeof value === "string" ? value : null; |
77 | 80 | } |
78 | 81 |
|
79 | | -type AnyArtefact = SignalReportArtefact | SuggestedReviewersArtefact; |
| 82 | +type AnyArtefact = |
| 83 | + | SignalReportArtefact |
| 84 | + | PriorityJudgmentArtefact |
| 85 | + | ActionabilityJudgmentArtefact |
| 86 | + | SignalFindingArtefact |
| 87 | + | SuggestedReviewersArtefact; |
| 88 | + |
| 89 | +const PRIORITY_VALUES = new Set(["P0", "P1", "P2", "P3", "P4"]); |
| 90 | + |
| 91 | +function normalizePriorityJudgmentArtefact( |
| 92 | + value: Record<string, unknown>, |
| 93 | +): PriorityJudgmentArtefact | null { |
| 94 | + const id = optionalString(value.id); |
| 95 | + if (!id) return null; |
| 96 | + |
| 97 | + const contentValue = isObjectRecord(value.content) ? value.content : null; |
| 98 | + if (!contentValue) return null; |
| 99 | + |
| 100 | + const priority = optionalString(contentValue.priority); |
| 101 | + if (!priority || !PRIORITY_VALUES.has(priority)) return null; |
| 102 | + |
| 103 | + return { |
| 104 | + id, |
| 105 | + type: "priority_judgment", |
| 106 | + created_at: optionalString(value.created_at) ?? new Date(0).toISOString(), |
| 107 | + content: { |
| 108 | + explanation: optionalString(contentValue.explanation) ?? "", |
| 109 | + priority: priority as PriorityJudgmentArtefact["content"]["priority"], |
| 110 | + }, |
| 111 | + }; |
| 112 | +} |
| 113 | + |
| 114 | +const ACTIONABILITY_VALUES = new Set([ |
| 115 | + "immediately_actionable", |
| 116 | + "requires_human_input", |
| 117 | + "not_actionable", |
| 118 | +]); |
| 119 | + |
| 120 | +function normalizeActionabilityJudgmentArtefact( |
| 121 | + value: Record<string, unknown>, |
| 122 | +): ActionabilityJudgmentArtefact | null { |
| 123 | + const id = optionalString(value.id); |
| 124 | + if (!id) return null; |
| 125 | + |
| 126 | + const contentValue = isObjectRecord(value.content) ? value.content : null; |
| 127 | + if (!contentValue) return null; |
| 128 | + |
| 129 | + // Support both agentic ("actionability") and legacy ("choice") field names |
| 130 | + const actionability = |
| 131 | + optionalString(contentValue.actionability) ?? |
| 132 | + optionalString(contentValue.choice); |
| 133 | + if (!actionability || !ACTIONABILITY_VALUES.has(actionability)) return null; |
| 134 | + |
| 135 | + return { |
| 136 | + id, |
| 137 | + type: "actionability_judgment", |
| 138 | + created_at: optionalString(value.created_at) ?? new Date(0).toISOString(), |
| 139 | + content: { |
| 140 | + explanation: optionalString(contentValue.explanation) ?? "", |
| 141 | + actionability: |
| 142 | + actionability as ActionabilityJudgmentArtefact["content"]["actionability"], |
| 143 | + already_addressed: |
| 144 | + typeof contentValue.already_addressed === "boolean" |
| 145 | + ? contentValue.already_addressed |
| 146 | + : false, |
| 147 | + }, |
| 148 | + }; |
| 149 | +} |
| 150 | + |
| 151 | +function normalizeSignalFindingArtefact( |
| 152 | + value: Record<string, unknown>, |
| 153 | +): SignalFindingArtefact | null { |
| 154 | + const id = optionalString(value.id); |
| 155 | + if (!id) return null; |
| 156 | + |
| 157 | + const contentValue = isObjectRecord(value.content) ? value.content : null; |
| 158 | + if (!contentValue) return null; |
| 159 | + |
| 160 | + const signalId = optionalString(contentValue.signal_id); |
| 161 | + if (!signalId) return null; |
| 162 | + |
| 163 | + return { |
| 164 | + id, |
| 165 | + type: "signal_finding", |
| 166 | + created_at: optionalString(value.created_at) ?? new Date(0).toISOString(), |
| 167 | + content: { |
| 168 | + signal_id: signalId, |
| 169 | + relevant_code_paths: Array.isArray(contentValue.relevant_code_paths) |
| 170 | + ? contentValue.relevant_code_paths.filter( |
| 171 | + (p: unknown): p is string => typeof p === "string", |
| 172 | + ) |
| 173 | + : [], |
| 174 | + relevant_commit_hashes: isObjectRecord( |
| 175 | + contentValue.relevant_commit_hashes, |
| 176 | + ) |
| 177 | + ? Object.fromEntries( |
| 178 | + Object.entries(contentValue.relevant_commit_hashes).filter( |
| 179 | + (e): e is [string, string] => typeof e[1] === "string", |
| 180 | + ), |
| 181 | + ) |
| 182 | + : {}, |
| 183 | + data_queried: optionalString(contentValue.data_queried) ?? "", |
| 184 | + verified: |
| 185 | + typeof contentValue.verified === "boolean" |
| 186 | + ? contentValue.verified |
| 187 | + : false, |
| 188 | + }, |
| 189 | + }; |
| 190 | +} |
80 | 191 |
|
81 | 192 | function normalizeSignalReportArtefact(value: unknown): AnyArtefact | null { |
82 | 193 | if (!isObjectRecord(value)) { |
83 | 194 | return null; |
84 | 195 | } |
85 | 196 |
|
| 197 | + const dispatchType = optionalString(value.type); |
| 198 | + if (dispatchType === "signal_finding") { |
| 199 | + return normalizeSignalFindingArtefact(value); |
| 200 | + } |
| 201 | + if (dispatchType === "actionability_judgment") { |
| 202 | + return normalizeActionabilityJudgmentArtefact(value); |
| 203 | + } |
| 204 | + if (dispatchType === "priority_judgment") { |
| 205 | + return normalizePriorityJudgmentArtefact(value); |
| 206 | + } |
| 207 | + |
86 | 208 | const id = optionalString(value.id); |
87 | 209 | if (!id) { |
88 | 210 | return null; |
89 | 211 | } |
90 | 212 |
|
91 | | - const type = optionalString(value.type) ?? "unknown"; |
| 213 | + const type = dispatchType ?? "unknown"; |
92 | 214 | const created_at = |
93 | 215 | optionalString(value.created_at) ?? new Date(0).toISOString(); |
94 | 216 |
|
|
0 commit comments