Add agent workflows & stuff
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { ConvexError } from 'convex/values';
|
||||
import OpenAI from 'openai';
|
||||
|
||||
type ReviewRisk = 'low' | 'medium' | 'high';
|
||||
type ReviewAction = 'sync' | 'open_review_pr' | 'manual_review' | 'do_not_sync';
|
||||
|
||||
export type AiCompatibilityReview = {
|
||||
summary: string;
|
||||
risk: ReviewRisk;
|
||||
compatible: boolean;
|
||||
requiresHumanReview: boolean;
|
||||
recommendedAction: ReviewAction;
|
||||
potentialConflicts: string[];
|
||||
importantFiles: string[];
|
||||
reasoningSummary: string;
|
||||
};
|
||||
|
||||
export type ReviewInput = {
|
||||
spoonName: string;
|
||||
upstreamFullName: string;
|
||||
forkFullName: string;
|
||||
status: string;
|
||||
upstreamAheadBy: number;
|
||||
forkAheadBy: number;
|
||||
upstreamCommits: {
|
||||
sha: string;
|
||||
message: string;
|
||||
authorName?: string;
|
||||
committedAt?: number;
|
||||
}[];
|
||||
forkCommits: {
|
||||
sha: string;
|
||||
message: string;
|
||||
authorName?: string;
|
||||
committedAt?: number;
|
||||
}[];
|
||||
importantFilePatterns?: string[];
|
||||
ignoredFilePatterns?: string[];
|
||||
};
|
||||
|
||||
export type ReasoningEffort =
|
||||
| 'none'
|
||||
| 'minimal'
|
||||
| 'low'
|
||||
| 'medium'
|
||||
| 'high'
|
||||
| 'xhigh';
|
||||
|
||||
export type OpenAiReviewSettings = {
|
||||
apiKey: string;
|
||||
model: string;
|
||||
reasoningEffort: ReasoningEffort;
|
||||
};
|
||||
|
||||
const reviewSchema = {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
summary: { type: 'string' },
|
||||
risk: { type: 'string', enum: ['low', 'medium', 'high'] },
|
||||
compatible: { type: 'boolean' },
|
||||
requiresHumanReview: { type: 'boolean' },
|
||||
recommendedAction: {
|
||||
type: 'string',
|
||||
enum: ['sync', 'open_review_pr', 'manual_review', 'do_not_sync'],
|
||||
},
|
||||
potentialConflicts: { type: 'array', items: { type: 'string' } },
|
||||
importantFiles: { type: 'array', items: { type: 'string' } },
|
||||
reasoningSummary: { type: 'string' },
|
||||
},
|
||||
required: [
|
||||
'summary',
|
||||
'risk',
|
||||
'compatible',
|
||||
'requiresHumanReview',
|
||||
'recommendedAction',
|
||||
'potentialConflicts',
|
||||
'importantFiles',
|
||||
'reasoningSummary',
|
||||
],
|
||||
} as const;
|
||||
|
||||
const isReviewRisk = (value: unknown): value is ReviewRisk =>
|
||||
value === 'low' || value === 'medium' || value === 'high';
|
||||
|
||||
const isReviewAction = (value: unknown): value is ReviewAction =>
|
||||
value === 'sync' ||
|
||||
value === 'open_review_pr' ||
|
||||
value === 'manual_review' ||
|
||||
value === 'do_not_sync';
|
||||
|
||||
const validateReview = (value: unknown): AiCompatibilityReview => {
|
||||
if (!value || typeof value !== 'object') {
|
||||
throw new ConvexError('OpenAI returned an invalid review payload.');
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
if (
|
||||
typeof record.summary !== 'string' ||
|
||||
!isReviewRisk(record.risk) ||
|
||||
typeof record.compatible !== 'boolean' ||
|
||||
typeof record.requiresHumanReview !== 'boolean' ||
|
||||
!isReviewAction(record.recommendedAction) ||
|
||||
!Array.isArray(record.potentialConflicts) ||
|
||||
!Array.isArray(record.importantFiles) ||
|
||||
typeof record.reasoningSummary !== 'string'
|
||||
) {
|
||||
throw new ConvexError('OpenAI review did not match the expected schema.');
|
||||
}
|
||||
return {
|
||||
summary: record.summary,
|
||||
risk: record.risk,
|
||||
compatible: record.compatible,
|
||||
requiresHumanReview: record.requiresHumanReview,
|
||||
recommendedAction: record.recommendedAction,
|
||||
potentialConflicts: record.potentialConflicts.filter(
|
||||
(item): item is string => typeof item === 'string',
|
||||
),
|
||||
importantFiles: record.importantFiles.filter(
|
||||
(item): item is string => typeof item === 'string',
|
||||
),
|
||||
reasoningSummary: record.reasoningSummary,
|
||||
};
|
||||
};
|
||||
|
||||
export const reviewUpstreamCompatibility = async (
|
||||
input: ReviewInput,
|
||||
settings: OpenAiReviewSettings,
|
||||
): Promise<AiCompatibilityReview> => {
|
||||
const response = await new OpenAI({
|
||||
apiKey: settings.apiKey,
|
||||
}).responses.create({
|
||||
model: settings.model,
|
||||
store: false,
|
||||
reasoning: {
|
||||
effort: settings.reasoningEffort,
|
||||
},
|
||||
input: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are reviewing whether upstream changes can be safely brought into a maintained fork. You do not execute code. You do not claim tests passed. Treat fork-only commits as user customizations that must be preserved. If changed files overlap with fork-only changes, increase risk. If patch context is incomplete, say so and require human review. Prefer conservative recommendations. Return only the required structured output.',
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: JSON.stringify(input, null, 2),
|
||||
},
|
||||
],
|
||||
text: {
|
||||
format: {
|
||||
type: 'json_schema',
|
||||
name: 'spoon_upstream_compatibility_review',
|
||||
strict: true,
|
||||
schema: reviewSchema,
|
||||
},
|
||||
},
|
||||
});
|
||||
const raw = response.output_text;
|
||||
if (!raw) throw new ConvexError('OpenAI returned an empty review.');
|
||||
return validateReview(JSON.parse(raw));
|
||||
};
|
||||
Reference in New Issue
Block a user