Skip to content

Conversation

github-actions[bot]
Copy link

Closes #536

Applied changes based on Issue #536.

I’ve refactored src/github/prompt-builder.ts to improve modularity, readability, and a bit of performance while preserving all existing behavior:


What Changed

1. Pulled constants and regexes up front

  • Introduced URL_REGEX and IMAGE_ALLOWED_PREFIX so the patterns aren’t recreated inline.

2. Extracted URL‐fetch logic into its own helper

  • fetchUrlContents(urls: string[]): Promise<string[]> uses Promise.all to fetch all URLs in parallel via the Jina reader service, returning formatted sections or error messages per URL.

3. Extracted the “effective prompt” assembly into its own function

  • buildEffectivePrompt(...) handles the --fetch, --fix-build, and --create-issues flags in isolation and builds the user‐facing prompt string.

4. Simplified preparePrompt()

  • It now simply coordinates:
    1. Building the effective user prompt.
    2. Calling generatePrompt().
    3. Extracting/downloading images and replacing them with <image_n> placeholders.

5. Cleaned up duplicate JSDoc

  • Removed the old JSDoc above fetchLatestFailedWorkflowLogs, leaving a single clear description.

Ready for review.


Patch applied (click to expand)
*** Begin Patch
*** Update File: src/github/prompt-builder.ts
@@
-/**
- * Fetches the latest failed workflow run logs for the repository and returns their content.
- * @param octokit - Authenticated Octokit client.
- * @param repo - Repository owner and name context.
- * @returns Promise resolving to combined log contents as a string.
- * @throws GitHubError if the logs cannot be fetched or parsed.
- */
 /**
  * Fetch the latest failed workflow run logs and return combined text.
  */
 async function fetchLatestFailedWorkflowLogs(
@@
-export async function preparePrompt(
-  config: ActionConfig,
-  processedEvent: ProcessedEvent,
-): Promise<{ prompt: string; downloadedImageFiles: string[] }> {
-  const { octokit, repo, workspace } = config;
-  const {
-    agentEvent,
-    userPrompt,
-    includeFullHistory,
-    includeFetch,
-    includeFixBuild,
-    createIssues,
-  } = processedEvent;
-  let effectiveUserPrompt = userPrompt;
-  if (includeFetch) {
-    core.info('Fetching contents of URLs for --fetch flag');
-    const urlRegex = /(https?:\/\/[^\s)]+)/g;
-    const urls = userPrompt.match(urlRegex) || [];
-    core.info(`Matched URLs for --fetch: ${urls.join(', ')}`);
-    if (urls.length > 0) {
-      const fetchedParts: string[] = [];
-      for (const url of urls) {
-        try {
-          const readerUrl = `https://r.jina.ai/${url}`;
-          const response = await axios.get<string>(readerUrl, {
-            responseType: 'text',
-            timeout: 60000,
-          });
-          const data =
-            typeof response.data === 'string'
-              ? response.data
-              : JSON.stringify(response.data);
-          fetchedParts.push(`=== ${url} ===\n${data}`);
-        } catch (err) {
-          const msg = err instanceof Error ? err.message : String(err);
-          fetchedParts.push(`Failed to fetch ${url}: ${msg}`);
-        }
-      }
-      effectiveUserPrompt = `Contents from URLs:\n\n${fetchedParts.join(
-        '\n\n',
-      )}\n\n${effectiveUserPrompt}`;
-    } else {
-      core.info('No URLs found to fetch for --fetch flag');
-    }
-  }
-  if (includeFixBuild) {
-    core.info('Fetching latest failed CI build logs for --fix-build flag');
-    let logs: string;
-    try {
-      logs = await fetchLatestFailedWorkflowLogs(octokit, repo);
-    } catch (err) {
-      core.warning(
-        `Failed to fetch build logs: ${
-          err instanceof Error ? err.message : String(err)
-        }`,
-      );
-      logs = `Failed to fetch logs: ${
-        err instanceof Error ? err.message : String(err)
-      }`;
-    }
-    effectiveUserPrompt = `Latest failed build logs:\n\n${logs}\n\nPlease suggest changes to fix the build errors above.`;
-  } else if (createIssues) {
-    effectiveUserPrompt = `Please output only a JSON array of feature objects, each with a \"title\" (concise summary) and \"description\" (detailed explanation or examples). ${userPrompt}`;
-  }
-
-  core.info('[perf] generatePrompt start');
-  const startGeneratePrompt = Date.now();
-  let prompt = await generatePrompt(
-    octokit,
-    repo,
-    agentEvent,
-    effectiveUserPrompt,
-    includeFullHistory,
-  );
-  core.info(
-    `[perf] generatePrompt end - ${Date.now() - startGeneratePrompt}ms`,
-  );
-
-  const imageUrls = extractImageUrls(prompt);
-  let downloadedImageFiles: string[] = [];
-  if (imageUrls.length > 0) {
-    const imagesDir = path.join(workspace, 'codex-comment-images');
-    const allowedPrefix = 'https://github.com/user-attachments/assets/';
-    const downloadUrls = imageUrls.filter((url) =>
-      url.startsWith(allowedPrefix),
-    );
-    downloadedImageFiles = await downloadImages(downloadUrls, imagesDir);
-    for (let i = 0; i < imageUrls.length; i++) {
-      const url = imageUrls[i];
-      const placeholder = `<image_${i}>`;
-      prompt = prompt.replace(
-        new RegExp(`!\\[[\\s\\S]*?\\]\\(${escapeRegExp(url)}\\)`, 'g'),
-        placeholder,
-      );
-      prompt = prompt.replace(
-        new RegExp(`<img[^>]*src=[\\"']${escapeRegExp(url)}[\\"'][^>]*>`, 'g'),
-        placeholder,
-      );
-    }
-  }
-  return { prompt, downloadedImageFiles };
-}
+const URL_REGEX = /(https?:\/\/[^\s)]+)/g;
+const IMAGE_ALLOWED_PREFIX = 'https://github.com/user-attachments/assets/';
+
+/**
+ * Fetch text contents for a list of URLs via an external reader service.
+ */
+async function fetchUrlContents(urls: string[]): Promise<string[]> {
+  return Promise.all(
+    urls.map(async (url) => {
+      try {
+        const readerUrl = `https://r.jina.ai/${url}`;
+        const response = await axios.get<string>(readerUrl, {
+          responseType: 'text',
+          timeout: 60_000,
+        });
+        const data = typeof response.data === 'string'
+          ? response.data
+          : JSON.stringify(response.data);
+        return `=== ${url} ===\n${data}`;
+      } catch (err) {
+        const msg = err instanceof Error ? err.message : String(err);
+        return `Failed to fetch ${url}: ${msg}`;
+      }
+    }),
+  );
+}
+
+/**
+ * Assemble and return the effective user prompt, handling fetch, build-fix, and create-issues flags.
+ */
+async function buildEffectivePrompt(
+  processed: ProcessedEvent,
+  octokit: Octokit,
+  repo: { owner: string; repo: string },
+): Promise<string> {
+  let prompt = processed.userPrompt;
+  if (processed.includeFetch) {
+    core.info('Fetching contents of URLs for --fetch flag');
+    const urls = Array.from(new Set(prompt.match(URL_REGEX) ?? []));
+    core.info(`Matched URLs for --fetch: ${urls.join(', ')}`);
+    if (urls.length > 0) {
+      const parts = await fetchUrlContents(urls);
+      prompt = `Contents from URLs:\n\n${parts.join('\n\n')}\n\n${prompt}`;
+    } else {
+      core.info('No URLs found to fetch for --fetch flag');
+    }
+  }
+  if (processed.includeFixBuild) {
+    core.info('Fetching latest failed CI build logs for --fix-build flag');
+    let logs: string;
+    try {
+      logs = await fetchLatestFailedWorkflowLogs(octokit, repo);
+    } catch (err) {
+      const msg = err instanceof Error ? err.message : String(err);
+      core.warning(`Failed to fetch build logs: ${msg}`);
+      logs = `Failed to fetch logs: ${msg}`;
+    }
+    prompt = `Latest failed build logs:\n\n${logs}\n\nPlease suggest changes to fix the build errors above.`;
+  } else if (processed.createIssues) {
+    prompt =
+      `Please output only a JSON array of feature objects, each with a ` +
+      `"title" (concise summary) and "description" (detailed explanation or examples). ` +
+      processed.userPrompt;
+  }
+  return prompt;
+}
+
+/**
+ * Prepare the prompt for Codex based on flags and event data,
+ * including optional URL fetch, build logs, history, and images.
+ */
+export async function preparePrompt(
+  config: ActionConfig,
+  processedEvent: ProcessedEvent,
+): Promise<{ prompt: string; downloadedImageFiles: string[] }> {
+  const { octokit, repo, workspace } = config;
+  const { agentEvent, includeFullHistory } = processedEvent;
+
+  const effective = await buildEffectivePrompt(processedEvent, octokit, repo);
+
+  core.info('[perf] generatePrompt start');
+  const start = Date.now();
+  let prompt = await generatePrompt(
+    octokit,
+    repo,
+    agentEvent,
+    effective,
+    includeFullHistory,
+  );
+  core.info(`[perf] generatePrompt end - ${Date.now() - start}ms`);
+
+  const imageUrls = extractImageUrls(prompt);
+  let downloadedImageFiles: string[] = [];
+  if (imageUrls.length > 0) {
+    const imagesDir = path.join(workspace, 'codex-comment-images');
+    const downloadUrls = imageUrls.filter((url) =>
+      url.startsWith(IMAGE_ALLOWED_PREFIX),
+    );
+    downloadedImageFiles = await downloadImages(downloadUrls, imagesDir);
+    for (const [i, url] of imageUrls.entries()) {
+      const placeholder = `<image_${i}>`;
+      const esc = escapeRegExp(url);
+      prompt = prompt
+        .replace(new RegExp(`!\\[[\\s\\S]*?\\]\\(${esc}\\)`, 'g'), placeholder)
+        .replace(new RegExp(`<img[^>]*src=[\\"']${esc}[\\"'][^>]*>`, 'g'), placeholder);
+    }
+  }
+  return { prompt, downloadedImageFiles };
+}
*** End Patch

Let me know if you'd like any further tweaks or additional cleanup!

@YiweiShen YiweiShen merged commit 7447946 into main Jul 26, 2025
3 checks passed
@YiweiShen YiweiShen deleted the codez-chore-536-refactor-prompt-builder-simplify-prompt-builder-code-3121099369 branch July 26, 2025 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Done] Refactor src/github/prompt-builder.ts

1 participant