Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions codeflash/api/aiservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,53 @@ def get_optimization_review(
console.rule()
return ""

def generate_workflow_steps(
self,
repo_files: dict[str, str],
directory_structure: dict[str, Any],
codeflash_config: dict[str, Any] | None = None,
) -> str | None:
"""Generate GitHub Actions workflow steps based on repository analysis.

:param repo_files: Dictionary mapping file paths to their contents
:param directory_structure: 2-level nested directory structure
:param codeflash_config: Optional codeflash configuration
:return: YAML string for workflow steps section, or None on error
"""
payload = {
"repo_files": repo_files,
"directory_structure": directory_structure,
"codeflash_config": codeflash_config,
}

logger.debug(
f"[aiservice.py:generate_workflow_steps] Sending request to AI service with {len(repo_files)} files, "
f"{len(directory_structure)} top-level directories"
)

try:
response = self.make_ai_service_request("/workflow-gen", payload=payload, timeout=60)
except requests.exceptions.RequestException as e:
logger.warning(f"[aiservice.py:generate_workflow_steps] Request exception: {e}")
return None

if response.status_code == 200:
response_data = response.json()
workflow_steps = cast("str", response_data.get("workflow_steps"))
logger.debug(
f"[aiservice.py:generate_workflow_steps] Successfully received workflow steps "
f"({len(workflow_steps) if workflow_steps else 0} chars)"
)
return workflow_steps
logger.warning(f"[aiservice.py:generate_workflow_steps] Failed with status {response.status_code}")
try:
error_response = response.json()
error = cast("str", error_response.get("error", "Unknown error"))
logger.debug(f"[aiservice.py:generate_workflow_steps] Error: {error}")
except Exception:
logger.debug("[aiservice.py:generate_workflow_steps] Could not parse error response")
return None


class LocalAiServiceClient(AiServiceClient):
"""Client for interacting with the local AI service."""
Expand Down
38 changes: 37 additions & 1 deletion codeflash/api/cfapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,17 @@ def make_cfapi_request(
*,
api_key: str | None = None,
suppress_errors: bool = False,
params: dict[str, Any] | None = None,
) -> Response:
"""Make an HTTP request using the specified method, URL, headers, and JSON payload.

:param endpoint: The endpoint URL to send the request to.
:param method: The HTTP method to use ('GET', 'POST', etc.).
:param payload: Optional JSON payload to include in the POST request body.
:param extra_headers: Optional extra headers to include in the request.
:param api_key: Optional API key to use for authentication.
:param suppress_errors: If True, suppress error logging for HTTP errors.
:param params: Optional query parameters for GET requests.
:return: The response object from the API.
"""
url = f"{get_cfapi_base_urls().cfapi_base_url}/cfapi{endpoint}"
Expand All @@ -75,7 +79,7 @@ def make_cfapi_request(
cfapi_headers["Content-Type"] = "application/json"
response = requests.post(url, data=json_payload, headers=cfapi_headers, timeout=60)
else:
response = requests.get(url, headers=cfapi_headers, timeout=60)
response = requests.get(url, headers=cfapi_headers, params=params, timeout=60)
response.raise_for_status()
return response # noqa: TRY300
except requests.exceptions.HTTPError:
Expand Down Expand Up @@ -239,6 +243,38 @@ def create_pr(
return make_cfapi_request(endpoint="/create-pr", method="POST", payload=payload)


def check_workflow_file_exists(owner: str, repo: str, base_branch: str) -> Response:
"""Check if the GitHub Actions workflow file exists on the repository.

:param owner: Repository owner (username or organization)
:param repo: Repository name
:param base_branch: Base branch to check (e.g., "main", "master")
:return: Response object with exists (bool) and content (str | None) fields
"""
params = {"owner": owner, "repo": repo, "baseBranch": base_branch}
return make_cfapi_request(endpoint="/check-workflow-file-exists", method="GET", params=params)


def setup_github_actions(
owner: str, repo: str, base_branch: str, workflow_content: str, api_key: str | None = None
) -> Response:
"""Set up GitHub Actions workflow by creating a PR with the workflow file and optionally setting up the repository secret.

:param owner: Repository owner (username or organization)
:param repo: Repository name
:param base_branch: Base branch to create PR against (e.g., "main", "master")
:param workflow_content: Content of the GitHub Actions workflow file (YAML)
:param api_key: API key to store as repository secret (if provided, will attempt to set up secret automatically)
:return: Response object with pr_url, pr_number, secret_setup_success, and secret_setup_error on success
"""
payload = {"owner": owner, "repo": repo, "baseBranch": base_branch, "workflowContent": workflow_content}
# Include apiKey in payload if provided - this will be encrypted and stored as a repository secret
if api_key:
payload["apiKey"] = api_key

return make_cfapi_request(endpoint="/setup-github-actions", method="POST", payload=payload, api_key=api_key)


def create_staging(
original_code: dict[Path, str],
new_code: dict[Path, str],
Expand Down
Loading