fix(sdk): surface RemoteConversation.fork() title from ConversationInfo #8209
Workflow file for this run
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
| --- | |
| name: REST API breakage checks | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| agent-server-rest-api: | |
| name: REST API (OpenAPI) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 | |
| with: | |
| enable-cache: true | |
| - name: Install workspace deps (dev) | |
| run: uv sync --frozen --group dev | |
| - name: Install oasdiff | |
| # Pin the version so install.sh skips its GitHub API "latest release" | |
| # lookup, which intermittently hits the 60/hr unauthenticated rate | |
| # limit on shared runners and fails the whole check. Bump as needed. | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/oasdiff/oasdiff/main/install.sh | version=1.19.1 sh -s -- -b /usr/local/bin | |
| oasdiff --version | |
| - name: Run agent server REST API breakage check | |
| id: api_breakage | |
| # Let this step fail so CI is visibly red on breakage. | |
| # Later reporting steps still run because they use if: always(). | |
| env: | |
| AGENT_SERVER_REST_API_BASE_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before | |
| }} | |
| AGENT_SERVER_REST_TYPE_WIDENING_REPORT_PATH: rest-api-type-widening-report.json | |
| run: | | |
| uv run --with packaging python .github/scripts/check_agent_server_rest_api_breakage.py 2>&1 | tee api-breakage.log | |
| exit_code=${PIPESTATUS[0]} | |
| echo "exit_code=${exit_code}" >> "$GITHUB_OUTPUT" | |
| exit "${exit_code}" | |
| - name: Write REST API breakage summary | |
| if: ${{ always() }} | |
| env: | |
| EXIT_CODE: ${{ steps.api_breakage.outputs.exit_code }} | |
| IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }} | |
| LOG_PATH: api-breakage.log | |
| REPORT_PATH: rest-api-type-widening-report.json | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| python3 <<'PY' >> "$GITHUB_STEP_SUMMARY" | |
| import json | |
| import os | |
| from pathlib import Path | |
| exit_code = int(os.environ.get('EXIT_CODE', '0') or '0') | |
| is_fork = os.environ.get('IS_FORK', 'false') == 'true' | |
| run_url = os.environ['RUN_URL'] | |
| status = '✅ **PASSED**' if exit_code == 0 else '❌ **FAILED**' | |
| try: | |
| report = json.loads(Path(os.environ['REPORT_PATH']).read_text()) | |
| except Exception: | |
| report = {} | |
| type_widenings_since_base = report.get( | |
| 'additive_response_property_type_widenings_since_base', | |
| [], | |
| ) | |
| print(f'## REST API breakage checks (OpenAPI) — {status}') | |
| print() | |
| print(f"**Result:** {status}") | |
| if exit_code != 0: | |
| print() | |
| print('> ⚠️ Breaking REST API changes or policy violations detected.') | |
| print() | |
| if type_widenings_since_base: | |
| print('### Additive response property type widenings accepted') | |
| print() | |
| print( | |
| 'These REST response fields now accept an additional type. ' | |
| 'The old type remains valid, and this PR was auto-marked ' | |
| 'with the `release-note-required` label:' | |
| ) | |
| print() | |
| for change in type_widenings_since_base: | |
| print( | |
| '- `{property_path}` adds `{added_types}` in response ' | |
| '`{response_status}`'.format(**change) | |
| ) | |
| print() | |
| if is_fork: | |
| print( | |
| '_Fork PR detected: sticky PR comment was skipped because ' | |
| 'the GitHub token is read-only for `pull_request` workflows ' | |
| 'from forks._' | |
| ) | |
| print() | |
| if exit_code != 0: | |
| try: | |
| log = Path(os.environ['LOG_PATH']).read_text() | |
| except Exception as exc: | |
| log = f'Unable to read log file: {exc}' | |
| excerpt = log[:1000].replace('```', '``\\`') | |
| print('<details><summary>Log excerpt (first 1000 characters)</summary>') | |
| print() | |
| print('```text') | |
| print(excerpt) | |
| print('```') | |
| print() | |
| print('</details>') | |
| print() | |
| print(f'[Action log]({run_url})') | |
| PY | |
| - name: Post REST API breakage report to PR | |
| if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| uses: actions/github-script@v9 | |
| env: | |
| EXIT_CODE: ${{ steps.api_breakage.outputs.exit_code }} | |
| LOG_PATH: api-breakage.log | |
| REPORT_PATH: rest-api-type-widening-report.json | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const marker = '<!-- agent-server-rest-api-breakage-report -->'; | |
| const exitCode = Number(process.env.EXIT_CODE || '0'); | |
| const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | |
| const status = exitCode === 0 ? '✅ **PASSED**' : '❌ **FAILED**'; | |
| let typeWideningsSinceBase = []; | |
| try { | |
| const report = JSON.parse(fs.readFileSync(process.env.REPORT_PATH, 'utf8')); | |
| typeWideningsSinceBase = report.additive_response_property_type_widenings_since_base || []; | |
| } catch (_error) { | |
| typeWideningsSinceBase = []; | |
| } | |
| let body = `${marker}\n## REST API breakage checks (OpenAPI) — ${status}\n\n**Result:** ${status}\n`; | |
| if (exitCode !== 0) { | |
| body += `\n> ⚠️ Breaking REST API changes or policy violations detected.\n`; | |
| let log = ''; | |
| try { | |
| log = fs.readFileSync(process.env.LOG_PATH, 'utf8'); | |
| } catch (e) { | |
| log = `Unable to read log file: ${e}`; | |
| } | |
| const excerpt = log.slice(0, 1000).replace(/```/g, '``\\`'); | |
| body += `\n<details><summary>Log excerpt (first 1000 characters)</summary>\n\n\`\`\`text\n${excerpt}\n\`\`\`\n\n</details>\n`; | |
| } | |
| if (typeWideningsSinceBase.length > 0) { | |
| body += '\n### Additive response property type widenings accepted\n\n'; | |
| body += 'These REST response fields now accept an additional type. The old type remains valid, and this PR was auto-marked with the `release-note-required` label:\n\n'; | |
| for (const change of typeWideningsSinceBase) { | |
| body += `- \`${change.property_path}\` adds \`${change.added_types}\` in response \`${change.response_status}\`\n`; | |
| } | |
| } | |
| body += `\n[Action log](${runUrl})\n`; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner, | |
| repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const existing = comments.find((c) => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: existing.id, | |
| body, | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number, | |
| body, | |
| }); | |
| } | |
| - name: Apply release-note-required label for REST type widenings | |
| if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| uses: actions/github-script@v9 | |
| env: | |
| REPORT_PATH: rest-api-type-widening-report.json | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| let typeWideningsSinceBase = []; | |
| try { | |
| const report = JSON.parse(fs.readFileSync(process.env.REPORT_PATH, 'utf8')); | |
| typeWideningsSinceBase = report.additive_response_property_type_widenings_since_base || []; | |
| } catch (_error) { | |
| typeWideningsSinceBase = []; | |
| } | |
| if (typeWideningsSinceBase.length === 0) { | |
| return; | |
| } | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const label = 'release-note-required'; | |
| const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({ | |
| owner, | |
| repo, | |
| issue_number, | |
| per_page: 100, | |
| }); | |
| const hasLabel = currentLabels.some((item) => item.name === label); | |
| if (!hasLabel) { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number, | |
| labels: [label], | |
| }); | |
| } | |
| - name: Generate REST API contract PR summary | |
| if: ${{ always() && github.event_name == 'pull_request' }} | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| touch rest-api-contract-summary.md | |
| uv run --with packaging python .github/scripts/generate_agent_server_rest_api_contract_summary.py \ | |
| --base-ref "$BASE_REF" \ | |
| --output rest-api-contract-summary.md || true | |
| - name: Update REST API contract summary in PR description | |
| if: ${{ always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| REPOSITORY: ${{ github.repository }} | |
| run: | | |
| gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --jq .body > pr-body.md | |
| python .github/scripts/update_pr_body_with_rest_api_summary.py \ | |
| --body-file pr-body.md \ | |
| --summary-file rest-api-contract-summary.md \ | |
| --output updated-pr-body.md | |
| if cmp -s pr-body.md updated-pr-body.md; then | |
| echo "REST API contract summary in PR description is already current." | |
| else | |
| jq -n --rawfile body updated-pr-body.md '{body: $body}' \ | |
| | gh api --method PATCH "repos/${REPOSITORY}/pulls/${PR_NUMBER}" --input - >/dev/null | |
| echo "Updated REST API contract summary in PR description." | |
| fi |