-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add api-server endpoint automatic tracking and AI PR-generation #366
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
danielboloc
wants to merge
33
commits into
main
Choose a base branch
from
ai-server-cli
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+870
−1
Open
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
3373153
mock: test agent PR draft
danielboloc d37b8f2
chore: trigger test dispatch
danielboloc e0d30da
fix: collapse multi-line python slug extraction to single line
danielboloc cb978b5
ci: test AI trigger
danielboloc 6ad74fc
fix: use versioned model ID and add API error body logging
danielboloc cb1f546
test: use existing model
danielboloc b9d1af1
test: use existing model
danielboloc 7b7f6d5
mock: use gpt-5
danielboloc 850bde4
mock: use gpt-4.1
danielboloc 9b68e70
mock: go back to azureml
danielboloc 04d0b59
refactor: use copilot sessions not models
danielboloc 59bda3b
chore: remove paths filter so any push to ai-server-cli triggers work…
danielboloc 072ff68
mock: copilot run
danielboloc 4dfc1f4
mock: copilot run
danielboloc 6caf0e9
fix: create issue and assign copilot-swe-agent in single gh command
danielboloc 0efb278
refactor: only accept repository dispatch as trigger
danielboloc 85e3e8b
refactor: receive workflow dispatch
danielboloc fd6ed99
refactor: add more assignee agents
danielboloc 5b579a8
ci: improve issue generation from api-server payload
danielboloc e30da02
refactor: use copilot agent
danielboloc 0c5c4ec
refactor: use grapql to assign agent (now fails with gh --assignee)
danielboloc 4bc47f7
debug: add latency between issue generation and assignment
danielboloc 3420c1f
debug: lower issue body
danielboloc eea9384
debug: unassign and assign agent
danielboloc 6cb2f02
refactor: open directly PR
danielboloc 27bfcb7
refactor: make payload and prompt more detailed
danielboloc 36732c3
fix: update issue for failed PR
danielboloc 8eacfeb
test: adding a new endpoint support
danielboloc bd726d8
refactor: remove test trigger
danielboloc faecea0
docs: update version
danielboloc fa39e66
docs: update changelog
danielboloc d15ee97
ci: remove unused triggers
danielboloc 33e00a4
Apply suggestions from code review
danielboloc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,219 @@ | ||
| name: Receive endpoint change and generate CLI PR | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| client_payload: | ||
| description: "Full client_payload JSON (the object under client_payload from the dispatch body)" | ||
| required: true | ||
| default: '{"relevant": true, "source_pr_url": "", "commit": "", "summary": "1 supported endpoint(s) affected: GET /api/v1/analytics/team", "changed_files": ["src/api/analytics/router.ts"], "changed_endpoints": [{"method": "GET", "path": "/api/v1/analytics/team", "change_type": "modified", "auth": ["userApiToken"], "summary": "team analytics", "source": "typescript", "api_server_files": ["src/api/analytics/router.ts"], "id": "analytics-team", "cli_command": "cloudos analytics team", "cli_source_file": "cloudos_cli/analytics/analytics.py", "query_params": ["teamId"]}]}' | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| generate-pr: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ secrets.AGENT_PR_PAT }} | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Resolve payload | ||
| id: payload | ||
| env: | ||
| # Use env vars to safely pass user inputs — avoids single-quote injection | ||
| # when the JSON payload contains quotes. | ||
| CLIENT_PAYLOAD: ${{ inputs.client_payload }} | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | ||
| # workflow_dispatch receives the client_payload object as a JSON string | ||
| # input — write it via env var (safe against single-quote injection). | ||
| printf '%s' "$CLIENT_PAYLOAD" > /tmp/payload.json | ||
| else | ||
| # repository_dispatch: client_payload is already JSON — write via tojson filter | ||
| cat <<'EOJSON' > /tmp/payload.json | ||
| ${{ toJson(github.event.client_payload) }} | ||
| EOJSON | ||
| fi | ||
|
|
||
| - name: Build Copilot PR body | ||
| run: | | ||
| python3 - <<'EOF' | ||
| import json | ||
| import sys | ||
|
|
||
| payload = json.load(open("/tmp/payload.json")) | ||
|
|
||
| if not payload.get("relevant", True): | ||
| print("Payload marked as not relevant — skipping.") | ||
| sys.exit(0) | ||
|
|
||
| endpoints = payload.get("changed_endpoints", []) | ||
| if not endpoints: | ||
| print("No changed endpoints in payload — skipping.") | ||
| sys.exit(0) | ||
|
|
||
| source_pr = payload.get("source_pr_url") or "manual trigger" | ||
| commit = payload.get("commit", "") | ||
| overall = payload.get("summary", "") | ||
| changed_files = payload.get("changed_files", []) | ||
| version = open("cloudos_cli/_version.py").read().strip() | ||
|
|
||
| fence = "```" | ||
|
|
||
| def fmt_list(items): | ||
| return ", ".join(items) if items else "—" | ||
|
|
||
| # Group endpoints by the CLI source file they map to, so the agent can | ||
| # tackle one module at a time. | ||
| by_file = {} | ||
| for ep in endpoints: | ||
| by_file.setdefault(ep.get("cli_source_file") or "(unknown)", []).append(ep) | ||
|
|
||
| lines = [] | ||
| lines.append("## Sync cloudos-cli with a CloudOS API endpoint change\n") | ||
| lines.append(f"**Source PR:** {source_pr} ") | ||
| if commit: | ||
| lines.append(f"**api-server commit:** `{commit}` ") | ||
| lines.append(f"**Summary:** {overall}\n") | ||
|
|
||
| lines.append("### What changed (and where to act)\n") | ||
| lines.append( | ||
| "Each endpoint below was detected as changed in the api-server. " | ||
| "`source` tells you whether the change was found in the OpenAPI " | ||
| "`specification.yaml` (`specification`), in the TypeScript route/handler " | ||
| "code (`typescript`), or both. A `typescript`-only change means the " | ||
| "public contract may have shifted WITHOUT a spec update — read the " | ||
| "linked api-server files carefully before assuming the request/response " | ||
| "shape.\n" | ||
| ) | ||
|
|
||
| for ep in endpoints: | ||
| method = ep.get("method") or "" | ||
| path = ep.get("path") or "" | ||
| change = ep.get("change_type") or "modified" | ||
| header = f"#### `{change}` — {method} {path}".strip() | ||
| lines.append(header) | ||
| if ep.get("new_path"): | ||
| lines.append(f"- **Renamed/new path:** `{ep['new_path']}`") | ||
| if ep.get("id"): | ||
| lines.append(f"- **Endpoint id:** `{ep['id']}` (key in `supported-endpoints.json`)") | ||
| if ep.get("cli_command"): | ||
| lines.append(f"- **CLI command to update:** `{ep['cli_command']}`") | ||
| if ep.get("cli_source_file"): | ||
| lines.append(f"- **CLI source file:** `{ep['cli_source_file']}`") | ||
| if ep.get("query_params"): | ||
| lines.append(f"- **Query params:** {fmt_list(ep['query_params'])}") | ||
| if ep.get("auth"): | ||
| lines.append(f"- **Auth strategies:** {fmt_list(ep['auth'])}") | ||
| lines.append(f"- **Detection source:** {ep.get('source', 'specification')}") | ||
| if ep.get("api_server_files"): | ||
| lines.append( | ||
| "- **api-server files that changed:** " | ||
| + fmt_list([f"`{f}`" for f in ep["api_server_files"]]) | ||
| ) | ||
| if ep.get("summary"): | ||
| lines.append(f"- **Notes:** {ep['summary']}") | ||
| lines.append("") | ||
|
|
||
| if changed_files: | ||
| lines.append("<details><summary>All changed api-server files</summary>\n") | ||
| for f in changed_files: | ||
| lines.append(f"- `{f}`") | ||
| lines.append("\n</details>\n") | ||
|
|
||
| lines.append("### Full machine-readable payload\n") | ||
| lines.append(f"{fence}json\n{json.dumps(endpoints, indent=2)}\n{fence}\n") | ||
|
|
||
| lines.append("### How cloudos-cli is structured\n") | ||
| lines.append( | ||
| "- CLI command groups register in `cloudos_cli/__main__.py`.\n" | ||
| "- Each module under `cloudos_cli/<module>/` has a `cli.py` (Click " | ||
| "commands / options) plus a logic class (e.g. `<module>.py`) that " | ||
| "usually extends `Cloudos` in `cloudos_cli/clos.py`.\n" | ||
| "- All HTTP calls go through the wrappers in " | ||
| "`cloudos_cli/utils/requests.py` and send the `apikey` header.\n" | ||
| "- `supported-endpoints.json` is the source of truth mapping each " | ||
| "endpoint to its `cli_command` and `source_file`.\n" | ||
| ) | ||
|
|
||
| lines.append("### Required steps\n") | ||
| lines.append( | ||
| "1. For every endpoint above, open its `cli_source_file` (fall back to " | ||
| "looking it up by `id` in `supported-endpoints.json` if missing) and " | ||
| "the api-server files listed, to understand the exact contract change.\n" | ||
| "2. Apply the change according to `change_type`:\n" | ||
| " - `added`: add the new command/option and wrapper call.\n" | ||
| " - `modified`: update params, request body, response parsing, and " | ||
| "any affected Click options to match the new contract.\n" | ||
| " - `removed`: deprecate/remove the wrapping command and its tests.\n" | ||
| " - renamed (`new_path` present): update the request path/URL builder.\n" | ||
| "3. Route all HTTP changes through `cloudos_cli/utils/requests.py`, keep " | ||
| "the `apikey` header, and match the style of neighbouring commands.\n" | ||
| "4. Add or update at least 2 tests under `tests/<module>/` using pytest " | ||
| "and `responses` (`@responses.activate`), covering happy path and one " | ||
| "failure/edge case.\n" | ||
| f"5. Bump the patch version in `cloudos_cli/_version.py` (currently " | ||
| f"`{version}`) and add a top `CHANGELOG.md` entry in the existing format.\n" | ||
| "6. Update `supported-endpoints.json` so the mapping reflects the new " | ||
| "path/params/method (and bump its `cli_version` if you change behaviour).\n" | ||
| ) | ||
|
|
||
| body = "\n".join(lines) | ||
| open("/tmp/pr-body.md", "w").write(body) | ||
|
|
||
| # NOTE: PR title generation removed because it is not used by any subsequent step. | ||
| EOF | ||
|
|
||
| - name: Start Copilot cloud agent task (create session) | ||
| env: | ||
| # Agent tasks API requires a USER-to-server token (classic PAT / OAuth / | ||
| # GitHub App user token). Server-to-server (GITHUB_TOKEN / App installation | ||
| # tokens) are NOT supported. | ||
| GH_TOKEN: ${{ secrets.AGENT_PR_PAT }} | ||
| REPO: ${{ github.repository }} | ||
| AGENT_MODEL: claude-sonnet-4.6 | ||
| run: | | ||
| set -euo pipefail | ||
| gh auth status | ||
|
|
||
| # Native cloud-agent session: POST /agents/repos/{owner}/{repo}/tasks. | ||
| # This starts the agent directly instead of assigning an issue, so it | ||
| # doesn't depend on the issue-assignment session bootstrap that was failing. | ||
| # Include "model" only when AGENT_MODEL is set (empty => auto selection). | ||
| jq -n \ | ||
| --rawfile prompt /tmp/pr-body.md \ | ||
| --arg base "main" \ | ||
| --arg model "${AGENT_MODEL:-}" \ | ||
| '{prompt: $prompt, base_ref: $base, create_pull_request: true} | ||
| + (if $model == "" then {} else {model: $model} end)' \ | ||
| | gh api \ | ||
| --method POST \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -H "X-GitHub-Api-Version: 2022-11-28" \ | ||
| "/agents/repos/$REPO/tasks" \ | ||
| --input - | ||
|
|
||
| echo "Cloud agent task created for $REPO (model: ${AGENT_MODEL:-auto})" | ||
|
|
||
| - name: Open fallback issue on failure | ||
| if: failure() | ||
| env: | ||
| GH_TOKEN: ${{ secrets.AGENT_PR_PAT }} | ||
| run: | | ||
| { | ||
| echo "The agent workflow failed." | ||
| echo | ||
| echo "Trigger payload:" | ||
| echo '```json' | ||
| cat /tmp/payload.json | ||
| echo | ||
| echo '```' | ||
| } > /tmp/failure-body.md | ||
|
|
||
| gh issue create \ | ||
| --title "Agent workflow failed: could not create Copilot task PR" \ | ||
| --body-file /tmp/failure-body.md \ | ||
| --label "agent-failed" | ||
|
danielboloc marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| __version__ = '2.91.0' | ||
| __version__ = '2.94.0' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.