Commit ·
448b355
1
Parent(s): 2891d39
update
Browse files
backend/src/agents/orchestrator.ts
CHANGED
|
@@ -125,18 +125,30 @@ export class Orchestrator {
|
|
| 125 |
|
| 126 |
// Step 4: Quality Check
|
| 127 |
onProgress("quality", "running", "Running quality validation...");
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
let finalReadme = readme;
|
| 132 |
const score = validation.score || 0;
|
| 133 |
if (score < 80) {
|
| 134 |
onProgress("quality", "running", `Score ${score}/100 — enhancing...`);
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
}
|
| 141 |
|
| 142 |
onProgress("quality", "complete", `Quality Score: ${score}/100`);
|
|
@@ -252,8 +264,13 @@ export class Orchestrator {
|
|
| 252 |
|
| 253 |
if (onProgress) onProgress("quality", "running", "Re-validating improved README...");
|
| 254 |
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
if (onProgress) onProgress("quality", "complete", `New Score: ${validation.score}/100`);
|
| 259 |
|
|
|
|
| 125 |
|
| 126 |
// Step 4: Quality Check
|
| 127 |
onProgress("quality", "running", "Running quality validation...");
|
| 128 |
+
let validation: any = { score: 70, categories: {}, suggestions: ["Quality scoring was unavailable — default score applied."] };
|
| 129 |
+
try {
|
| 130 |
+
const validationResult = await this.archestra.callTool("validate_readme", { readme });
|
| 131 |
+
const validationText = extractToolText(validationResult);
|
| 132 |
+
validation = safeJsonParse(validationText);
|
| 133 |
+
} catch (scoreError: any) {
|
| 134 |
+
console.warn("Quality scoring failed (non-fatal):", scoreError.message);
|
| 135 |
+
onProgress("quality", "running", "Scoring unavailable — proceeding with generated README...");
|
| 136 |
+
}
|
| 137 |
|
| 138 |
let finalReadme = readme;
|
| 139 |
const score = validation.score || 0;
|
| 140 |
if (score < 80) {
|
| 141 |
onProgress("quality", "running", `Score ${score}/100 — enhancing...`);
|
| 142 |
+
try {
|
| 143 |
+
const enhancedResult = await this.archestra.callTool("enhance_readme", {
|
| 144 |
+
readme,
|
| 145 |
+
suggestions: (validation.suggestions || []).join(", ")
|
| 146 |
+
});
|
| 147 |
+
finalReadme = extractToolText(enhancedResult);
|
| 148 |
+
} catch (enhanceError: any) {
|
| 149 |
+
console.warn("Enhancement failed (non-fatal):", enhanceError.message);
|
| 150 |
+
// Keep the original readme
|
| 151 |
+
}
|
| 152 |
}
|
| 153 |
|
| 154 |
onProgress("quality", "complete", `Quality Score: ${score}/100`);
|
|
|
|
| 264 |
|
| 265 |
if (onProgress) onProgress("quality", "running", "Re-validating improved README...");
|
| 266 |
|
| 267 |
+
let validation: any = { score: 75, categories: {}, suggestions: ["Re-scoring was unavailable — default score applied."] };
|
| 268 |
+
try {
|
| 269 |
+
const validationResult = await this.archestra.callTool("validate_readme", { readme: enhanced });
|
| 270 |
+
validation = safeJsonParse(extractToolText(validationResult));
|
| 271 |
+
} catch (scoreError: any) {
|
| 272 |
+
console.warn("Re-validation scoring failed (non-fatal):", scoreError.message);
|
| 273 |
+
}
|
| 274 |
|
| 275 |
if (onProgress) onProgress("quality", "complete", `New Score: ${validation.score}/100`);
|
| 276 |
|
backend/src/utils/json.ts
CHANGED
|
@@ -1,43 +1,127 @@
|
|
| 1 |
export function safeJsonParse(text: string): any {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
try {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
// Attempt to extract JSON from markdown code blocks
|
| 7 |
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/) || text.match(/```\s*([\s\S]*?)\s*```/);
|
| 8 |
if (jsonMatch && jsonMatch[1]) {
|
| 9 |
try {
|
| 10 |
return JSON.parse(jsonMatch[1].trim());
|
| 11 |
-
} catch
|
| 12 |
-
throw new Error(`Failed to parse extracted JSON: ${innerError}`);
|
| 13 |
-
}
|
| 14 |
}
|
| 15 |
-
|
| 16 |
-
//
|
| 17 |
-
const firstBrace =
|
| 18 |
-
const lastBrace =
|
| 19 |
-
const firstBracket =
|
| 20 |
-
const lastBracket =
|
| 21 |
|
| 22 |
let start = -1;
|
| 23 |
let end = -1;
|
| 24 |
-
|
| 25 |
if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
|
| 26 |
-
|
| 27 |
-
|
| 28 |
} else if (firstBracket !== -1) {
|
| 29 |
-
|
| 30 |
-
|
| 31 |
}
|
| 32 |
|
| 33 |
if (start !== -1 && end !== -1 && end > start) {
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
-
throw
|
| 42 |
}
|
| 43 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
export function safeJsonParse(text: string): any {
|
| 2 |
+
// Pre-clean: strip markdown code fences the LLM may have wrapped around JSON
|
| 3 |
+
let cleaned = text.trim();
|
| 4 |
+
const fenceMatch = cleaned.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);
|
| 5 |
+
if (fenceMatch) cleaned = fenceMatch[1].trim();
|
| 6 |
+
|
| 7 |
try {
|
| 8 |
+
return JSON.parse(cleaned);
|
| 9 |
+
} catch (firstError) {
|
| 10 |
+
// ── Strategy 1: extract JSON from code blocks inside larger text ──
|
|
|
|
| 11 |
const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/) || text.match(/```\s*([\s\S]*?)\s*```/);
|
| 12 |
if (jsonMatch && jsonMatch[1]) {
|
| 13 |
try {
|
| 14 |
return JSON.parse(jsonMatch[1].trim());
|
| 15 |
+
} catch { /* fall through */ }
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
+
|
| 18 |
+
// ── Strategy 2: find the outermost { … } or [ … ] ──
|
| 19 |
+
const firstBrace = cleaned.indexOf('{');
|
| 20 |
+
const lastBrace = cleaned.lastIndexOf('}');
|
| 21 |
+
const firstBracket = cleaned.indexOf('[');
|
| 22 |
+
const lastBracket = cleaned.lastIndexOf(']');
|
| 23 |
|
| 24 |
let start = -1;
|
| 25 |
let end = -1;
|
|
|
|
| 26 |
if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
|
| 27 |
+
start = firstBrace;
|
| 28 |
+
end = lastBrace;
|
| 29 |
} else if (firstBracket !== -1) {
|
| 30 |
+
start = firstBracket;
|
| 31 |
+
end = lastBracket;
|
| 32 |
}
|
| 33 |
|
| 34 |
if (start !== -1 && end !== -1 && end > start) {
|
| 35 |
+
try {
|
| 36 |
+
return JSON.parse(cleaned.substring(start, end + 1));
|
| 37 |
+
} catch { /* fall through */ }
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// ── Strategy 3: repair truncated JSON (e.g. unterminated strings) ──
|
| 41 |
+
// This happens when Gemini hits maxOutputTokens mid-response
|
| 42 |
+
if (start !== -1) {
|
| 43 |
+
let partial = cleaned.substring(start, end !== -1 && end > start ? end + 1 : undefined);
|
| 44 |
+
try {
|
| 45 |
+
return repairAndParseJSON(partial);
|
| 46 |
+
} catch { /* fall through */ }
|
| 47 |
}
|
| 48 |
|
| 49 |
+
throw firstError;
|
| 50 |
}
|
| 51 |
}
|
| 52 |
+
|
| 53 |
+
/**
|
| 54 |
+
* Attempt to repair truncated JSON that was cut off mid-generation.
|
| 55 |
+
* Handles: unterminated strings, missing closing braces/brackets, trailing commas.
|
| 56 |
+
*/
|
| 57 |
+
function repairAndParseJSON(raw: string): any {
|
| 58 |
+
let s = raw;
|
| 59 |
+
|
| 60 |
+
// Remove trailing incomplete key-value pairs (e.g. `, "key": "unterminated...`)
|
| 61 |
+
// Step 1: close any unterminated strings by counting quotes
|
| 62 |
+
let inString = false;
|
| 63 |
+
let escaped = false;
|
| 64 |
+
let lastGoodIndex = 0;
|
| 65 |
+
|
| 66 |
+
for (let i = 0; i < s.length; i++) {
|
| 67 |
+
const ch = s[i];
|
| 68 |
+
if (escaped) { escaped = false; continue; }
|
| 69 |
+
if (ch === '\\') { escaped = true; continue; }
|
| 70 |
+
if (ch === '"') {
|
| 71 |
+
inString = !inString;
|
| 72 |
+
if (!inString) lastGoodIndex = i; // end of a complete string
|
| 73 |
+
}
|
| 74 |
+
if (!inString) lastGoodIndex = i;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// If we ended inside a string, truncate to last good position and close the string
|
| 78 |
+
if (inString) {
|
| 79 |
+
s = s.substring(0, lastGoodIndex + 1);
|
| 80 |
+
// If the last good char was the opening quote of a value, remove the dangling key
|
| 81 |
+
// Otherwise close the string we were inside
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// Remove trailing commas before we add closing braces
|
| 85 |
+
s = s.replace(/,\s*$/, '');
|
| 86 |
+
|
| 87 |
+
// Count open vs close braces and brackets
|
| 88 |
+
let braces = 0;
|
| 89 |
+
let brackets = 0;
|
| 90 |
+
inString = false;
|
| 91 |
+
escaped = false;
|
| 92 |
+
for (let i = 0; i < s.length; i++) {
|
| 93 |
+
const ch = s[i];
|
| 94 |
+
if (escaped) { escaped = false; continue; }
|
| 95 |
+
if (ch === '\\') { escaped = true; continue; }
|
| 96 |
+
if (ch === '"') { inString = !inString; continue; }
|
| 97 |
+
if (inString) continue;
|
| 98 |
+
if (ch === '{') braces++;
|
| 99 |
+
else if (ch === '}') braces--;
|
| 100 |
+
else if (ch === '[') brackets++;
|
| 101 |
+
else if (ch === ']') brackets--;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
// Remove trailing incomplete entries (trailing commas, partial keys)
|
| 105 |
+
s = s.replace(/,\s*"[^"]*"?\s*:?\s*"?[^"]*$/, '');
|
| 106 |
+
s = s.replace(/,\s*$/, '');
|
| 107 |
+
|
| 108 |
+
// Re-count after cleanup
|
| 109 |
+
braces = 0; brackets = 0; inString = false; escaped = false;
|
| 110 |
+
for (let i = 0; i < s.length; i++) {
|
| 111 |
+
const ch = s[i];
|
| 112 |
+
if (escaped) { escaped = false; continue; }
|
| 113 |
+
if (ch === '\\') { escaped = true; continue; }
|
| 114 |
+
if (ch === '"') { inString = !inString; continue; }
|
| 115 |
+
if (inString) continue;
|
| 116 |
+
if (ch === '{') braces++;
|
| 117 |
+
else if (ch === '}') braces--;
|
| 118 |
+
else if (ch === '[') brackets++;
|
| 119 |
+
else if (ch === ']') brackets--;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
// Close any remaining open brackets/braces
|
| 123 |
+
while (brackets > 0) { s += ']'; brackets--; }
|
| 124 |
+
while (braces > 0) { s += '}'; braces--; }
|
| 125 |
+
|
| 126 |
+
return JSON.parse(s);
|
| 127 |
+
}
|
mcp-servers/doc-generator/src/index.mts
CHANGED
|
@@ -17,7 +17,7 @@ const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
-
async function callGemini(messages: any[], maxTokens: number =
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
// Convert OpenAI-style messages to Gemini format
|
|
@@ -124,7 +124,7 @@ function createServer() {
|
|
| 124 |
// Style-specific system prompts and token limits
|
| 125 |
const stylePrompts: Record<string, { prompt: string; tokens: number }> = {
|
| 126 |
minimal: {
|
| 127 |
-
tokens:
|
| 128 |
prompt: `You are a concise technical writer. Generate a MINIMAL README.md — short, clean, and actionable.
|
| 129 |
|
| 130 |
Include ONLY these sections:
|
|
@@ -141,7 +141,7 @@ Rules:
|
|
| 141 |
- Maximum ~150 lines total`
|
| 142 |
},
|
| 143 |
standard: {
|
| 144 |
-
tokens:
|
| 145 |
prompt: `You are a world-class technical writer who creates beautiful, comprehensive README.md files. You write READMEs that developers LOVE — clear, professional, and visually appealing.
|
| 146 |
|
| 147 |
Include these sections:
|
|
@@ -164,7 +164,7 @@ Rules:
|
|
| 164 |
- Be thorough but concise`
|
| 165 |
},
|
| 166 |
detailed: {
|
| 167 |
-
tokens:
|
| 168 |
prompt: `You are a world-class technical writer who creates beautiful, comprehensive README.md files for open-source projects. You write READMEs that developers LOVE — clear, professional, and visually appealing.
|
| 169 |
|
| 170 |
Your README MUST include ALL of the following sections (skip only if truly irrelevant):
|
|
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
+
async function callGemini(messages: any[], maxTokens: number = 8192) {
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
// Convert OpenAI-style messages to Gemini format
|
|
|
|
| 124 |
// Style-specific system prompts and token limits
|
| 125 |
const stylePrompts: Record<string, { prompt: string; tokens: number }> = {
|
| 126 |
minimal: {
|
| 127 |
+
tokens: 4096,
|
| 128 |
prompt: `You are a concise technical writer. Generate a MINIMAL README.md — short, clean, and actionable.
|
| 129 |
|
| 130 |
Include ONLY these sections:
|
|
|
|
| 141 |
- Maximum ~150 lines total`
|
| 142 |
},
|
| 143 |
standard: {
|
| 144 |
+
tokens: 8192,
|
| 145 |
prompt: `You are a world-class technical writer who creates beautiful, comprehensive README.md files. You write READMEs that developers LOVE — clear, professional, and visually appealing.
|
| 146 |
|
| 147 |
Include these sections:
|
|
|
|
| 164 |
- Be thorough but concise`
|
| 165 |
},
|
| 166 |
detailed: {
|
| 167 |
+
tokens: 16384,
|
| 168 |
prompt: `You are a world-class technical writer who creates beautiful, comprehensive README.md files for open-source projects. You write READMEs that developers LOVE — clear, professional, and visually appealing.
|
| 169 |
|
| 170 |
Your README MUST include ALL of the following sections (skip only if truly irrelevant):
|
mcp-servers/readme-improver/src/index.mts
CHANGED
|
@@ -17,7 +17,7 @@ const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
-
async function callGemini(messages: any[], maxTokens =
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
const systemInstruction = messages.find((m: any) => m.role === 'system')?.content || '';
|
|
@@ -139,7 +139,7 @@ ${readme.substring(0, 15000)}`;
|
|
| 139 |
const improved = await callGemini([
|
| 140 |
{ role: "system", content: "You are an expert technical writer who improves open-source documentation. Return ONLY the improved README in raw Markdown. Never wrap in code blocks." },
|
| 141 |
{ role: "user", content: prompt }
|
| 142 |
-
],
|
| 143 |
return {
|
| 144 |
content: [{ type: "text", text: improved }],
|
| 145 |
};
|
|
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
+
async function callGemini(messages: any[], maxTokens = 8192): Promise<string> {
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
const systemInstruction = messages.find((m: any) => m.role === 'system')?.content || '';
|
|
|
|
| 139 |
const improved = await callGemini([
|
| 140 |
{ role: "system", content: "You are an expert technical writer who improves open-source documentation. Return ONLY the improved README in raw Markdown. Never wrap in code blocks." },
|
| 141 |
{ role: "user", content: prompt }
|
| 142 |
+
], 8192);
|
| 143 |
return {
|
| 144 |
content: [{ type: "text", text: improved }],
|
| 145 |
};
|
mcp-servers/readme-scorer/src/index.mts
CHANGED
|
@@ -17,7 +17,7 @@ const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
-
async function callGemini(messages: any[], maxTokens =
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
const systemInstruction = messages.find((m: any) => m.role === 'system')?.content || '';
|
|
@@ -34,16 +34,20 @@ async function callGemini(messages: any[], maxTokens = 1000): Promise<string> {
|
|
| 34 |
try {
|
| 35 |
const controller = new AbortController();
|
| 36 |
const timeout = setTimeout(() => controller.abort(), 90_000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
const response = await fetch(GEMINI_URL, {
|
| 38 |
method: "POST",
|
| 39 |
headers: { "Content-Type": "application/json" },
|
| 40 |
body: JSON.stringify({
|
| 41 |
...(systemInstruction ? { systemInstruction: { parts: [{ text: systemInstruction }] } } : {}),
|
| 42 |
contents,
|
| 43 |
-
generationConfig
|
| 44 |
-
temperature: 0.3,
|
| 45 |
-
maxOutputTokens: maxTokens,
|
| 46 |
-
},
|
| 47 |
}),
|
| 48 |
signal: controller.signal,
|
| 49 |
});
|
|
@@ -143,7 +147,7 @@ ${readme.substring(0, 12000)}`;
|
|
| 143 |
const validation = await callGemini([
|
| 144 |
{ role: "system", content: "You are a documentation QA specialist who scores README files. Return ONLY valid JSON, no markdown wrapping, no backticks." },
|
| 145 |
{ role: "user", content: prompt }
|
| 146 |
-
]);
|
| 147 |
return {
|
| 148 |
content: [{ type: "text", text: validation }],
|
| 149 |
};
|
|
|
|
| 17 |
const GEMINI_MODEL = "gemini-2.5-flash";
|
| 18 |
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`;
|
| 19 |
|
| 20 |
+
async function callGemini(messages: any[], maxTokens = 2048, responseFormat?: string): Promise<string> {
|
| 21 |
if (!GEMINI_API_KEY) throw new Error("GEMINI_API_KEY not set");
|
| 22 |
|
| 23 |
const systemInstruction = messages.find((m: any) => m.role === 'system')?.content || '';
|
|
|
|
| 34 |
try {
|
| 35 |
const controller = new AbortController();
|
| 36 |
const timeout = setTimeout(() => controller.abort(), 90_000);
|
| 37 |
+
const generationConfig: any = {
|
| 38 |
+
temperature: 0.3,
|
| 39 |
+
maxOutputTokens: maxTokens,
|
| 40 |
+
};
|
| 41 |
+
if (responseFormat) {
|
| 42 |
+
generationConfig.responseMimeType = responseFormat;
|
| 43 |
+
}
|
| 44 |
const response = await fetch(GEMINI_URL, {
|
| 45 |
method: "POST",
|
| 46 |
headers: { "Content-Type": "application/json" },
|
| 47 |
body: JSON.stringify({
|
| 48 |
...(systemInstruction ? { systemInstruction: { parts: [{ text: systemInstruction }] } } : {}),
|
| 49 |
contents,
|
| 50 |
+
generationConfig,
|
|
|
|
|
|
|
|
|
|
| 51 |
}),
|
| 52 |
signal: controller.signal,
|
| 53 |
});
|
|
|
|
| 147 |
const validation = await callGemini([
|
| 148 |
{ role: "system", content: "You are a documentation QA specialist who scores README files. Return ONLY valid JSON, no markdown wrapping, no backticks." },
|
| 149 |
{ role: "user", content: prompt }
|
| 150 |
+
], 2048, "application/json");
|
| 151 |
return {
|
| 152 |
content: [{ type: "text", text: validation }],
|
| 153 |
};
|