From bcec178bbebd8c17d5485d47d9aa60ffdbbe15dd Mon Sep 17 00:00:00 2001 From: Seonfx Date: Fri, 16 Jan 2026 08:37:53 -0400 Subject: [PATCH] fix: add JSON fallback for spec generation with custom API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes spec generation failure when using custom API endpoints (e.g., GLM proxy) that don't support structured output. The AI returns JSON instead of XML, but the fallback parser only looked for XML tags. Changes: - escapeXml: Handle undefined/null values gracefully (converts to empty string) - generate-spec: Add JSON extraction fallback when XML tags aren't found - Reuses existing extractJson() utility (already used for Cursor models) - Converts extracted JSON to XML using specToXml() Closes #510 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/server/src/lib/app-spec-format.ts | 8 +++-- .../src/routes/app-spec/generate-spec.ts | 33 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/server/src/lib/app-spec-format.ts b/apps/server/src/lib/app-spec-format.ts index 2894bbc4..0941fa4c 100644 --- a/apps/server/src/lib/app-spec-format.ts +++ b/apps/server/src/lib/app-spec-format.ts @@ -11,9 +11,13 @@ export { specOutputSchema } from '@automaker/types'; /** * Escape special XML characters + * Handles undefined/null values by converting them to empty strings */ -function escapeXml(str: string): string { - return str +function escapeXml(str: string | undefined | null): string { + if (str === undefined || str === null) { + return ''; + } + return String(str) .replace(/&/g, '&') .replace(//g, '>') diff --git a/apps/server/src/routes/app-spec/generate-spec.ts b/apps/server/src/routes/app-spec/generate-spec.ts index d79ffc5f..f519aca5 100644 --- a/apps/server/src/routes/app-spec/generate-spec.ts +++ b/apps/server/src/routes/app-spec/generate-spec.ts @@ -201,19 +201,26 @@ Your entire response should be valid JSON starting with { and ending with }. No xmlContent = responseText.substring(xmlStart, xmlEnd + ''.length); logger.info(`Extracted XML content: ${xmlContent.length} chars (from position ${xmlStart})`); } else { - // No valid XML structure found in the response text - // This happens when structured output was expected but not received, and the agent - // output conversational text instead of XML (e.g., "The project directory appears to be empty...") - // We should NOT save this conversational text as it's not a valid spec - logger.error('❌ Response does not contain valid XML structure'); - logger.error( - 'This typically happens when structured output failed and the agent produced conversational text instead of XML' - ); - throw new Error( - 'Failed to generate spec: No valid XML structure found in response. ' + - 'The response contained conversational text but no tags. ' + - 'Please try again.' - ); + // No XML found, try JSON extraction + logger.warn('⚠️ No XML tags found, attempting JSON extraction...'); + const extractedJson = extractJson(responseText, { logger }); + + if (extractedJson) { + logger.info('✅ Successfully extracted JSON from response text'); + xmlContent = specToXml(extractedJson); + logger.info(`✅ Converted extracted JSON to XML: ${xmlContent.length} chars`); + } else { + // Neither XML nor valid JSON found + logger.error('❌ Response does not contain valid XML or JSON structure'); + logger.error( + 'This typically happens when structured output failed and the agent produced conversational text instead of structured output' + ); + throw new Error( + 'Failed to generate spec: No valid XML or JSON structure found in response. ' + + 'The response contained conversational text but no tags or valid JSON. ' + + 'Please try again.' + ); + } } }