chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Please read this first
|
||||
|
||||
- **Have you read the docs?**[Agents SDK docs](https://openai.github.io/openai-agents-python/)
|
||||
- **Have you searched for related issues?** Others may have faced similar issues.
|
||||
|
||||
### Describe the bug
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### Debug information
|
||||
- Agents SDK version: (e.g. `v0.0.3`)
|
||||
- Python version (e.g. Python 3.14)
|
||||
|
||||
### Repro steps
|
||||
|
||||
Ideally provide a minimal python script that can be run to reproduce the bug.
|
||||
|
||||
|
||||
### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Please read this first
|
||||
|
||||
- **Have you read the docs?**[Agents SDK docs](https://openai.github.io/openai-agents-python/)
|
||||
- **Have you searched for related issues?** Others may have had similar requests
|
||||
|
||||
### Describe the feature
|
||||
What is the feature you're requesting? How would it work? Please provide examples and details if possible.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Custom model providers
|
||||
about: Questions or bugs about using non-OpenAI models
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Please read this first
|
||||
|
||||
- **Have you read the custom model provider docs, including the 'Common issues' section?** [Model provider docs](https://openai.github.io/openai-agents-python/models/#using-other-llm-providers)
|
||||
- **Have you searched for related issues?** Others may have faced similar issues.
|
||||
|
||||
### Describe the question
|
||||
A clear and concise description of what the question or bug is.
|
||||
|
||||
### Debug information
|
||||
- Agents SDK version: (e.g. `v0.0.3`)
|
||||
- Python version (e.g. Python 3.14)
|
||||
|
||||
### Repro steps
|
||||
Ideally provide a minimal python script that can be run to reproduce the issue.
|
||||
|
||||
### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Question
|
||||
about: Questions about the SDK
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Please read this first
|
||||
|
||||
- **Have you read the docs?**[Agents SDK docs](https://openai.github.io/openai-agents-python/)
|
||||
- **Have you searched for related issues?** Others may have had similar requests
|
||||
|
||||
### Question
|
||||
Describe your question. Provide details if available.
|
||||
@@ -0,0 +1,19 @@
|
||||
### Summary
|
||||
|
||||
<!-- Please give a short summary of the change and the problem this solves. -->
|
||||
|
||||
### Test plan
|
||||
|
||||
<!-- Please explain how this was tested -->
|
||||
<!-- If verification could not complete because of local environment setup, include the failing command, the missing dependency, and why it is unrelated to this PR. Leave the pass checkbox below unchecked until all verification steps pass. -->
|
||||
|
||||
### Issue number
|
||||
|
||||
<!-- For example: "Closes #1234" -->
|
||||
|
||||
### Checks
|
||||
|
||||
- [ ] I've added new tests, if relevant
|
||||
- [ ] I've run `.agents/skills/code-change-verification/scripts/run.sh`
|
||||
- [ ] I've confirmed all verification steps pass
|
||||
- [ ] If using Codex, I've run `/review` before submitting this PR
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- "dependencies"
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-code}"
|
||||
base_sha="${2:-${BASE_SHA:-}}"
|
||||
head_sha="${3:-${HEAD_SHA:-}}"
|
||||
|
||||
if [ -z "${GITHUB_OUTPUT:-}" ]; then
|
||||
echo "GITHUB_OUTPUT is not set." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$head_sha" ]; then
|
||||
head_sha="$(git rev-parse HEAD 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$base_sha" ]; then
|
||||
if ! git rev-parse --verify origin/main >/dev/null 2>&1; then
|
||||
git fetch --no-tags --depth=1 origin main || true
|
||||
fi
|
||||
if git rev-parse --verify origin/main >/dev/null 2>&1 && [ -n "$head_sha" ]; then
|
||||
base_sha="$(git merge-base origin/main "$head_sha" 2>/dev/null || true)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$base_sha" ] || [ -z "$head_sha" ]; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! git cat-file -e "$base_sha" 2>/dev/null; then
|
||||
git fetch --no-tags --depth=1 origin "$base_sha" || true
|
||||
fi
|
||||
|
||||
if ! git cat-file -e "$base_sha" 2>/dev/null; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
changed_files=$(git diff --name-only "$base_sha" "$head_sha" || true)
|
||||
|
||||
case "$mode" in
|
||||
code)
|
||||
pattern='^(src/|tests/|examples/|pyproject.toml$|uv.lock$|Makefile$)'
|
||||
;;
|
||||
docs)
|
||||
pattern='^(docs/|mkdocs.yml$)'
|
||||
;;
|
||||
*)
|
||||
pattern="$mode"
|
||||
;;
|
||||
esac
|
||||
|
||||
if echo "$changed_files" | grep -Eq "$pattern"; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repeat_count="${1:-5}"
|
||||
|
||||
asyncio_progress_args=(
|
||||
tests/test_asyncio_progress.py
|
||||
)
|
||||
|
||||
run_step_execution_args=(
|
||||
tests/test_run_step_execution.py
|
||||
-k
|
||||
"cancel or post_invoke"
|
||||
)
|
||||
|
||||
for run in $(seq 1 "$repeat_count"); do
|
||||
echo "Async teardown stability run ${run}/${repeat_count}"
|
||||
uv run pytest -q "${asyncio_progress_args[@]}"
|
||||
uv run pytest -q "${run_step_execution_args[@]}"
|
||||
done
|
||||
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib import error, request
|
||||
|
||||
|
||||
def warn(message: str) -> None:
|
||||
print(message, file=sys.stderr)
|
||||
|
||||
|
||||
def parse_version(value: str | None) -> tuple[int, int, int] | None:
|
||||
if not value:
|
||||
return None
|
||||
match = re.match(r"^v?(\d+)\.(\d+)(?:\.(\d+))?", value)
|
||||
if not match:
|
||||
return None
|
||||
major = int(match.group(1))
|
||||
minor = int(match.group(2))
|
||||
patch = int(match.group(3) or 0)
|
||||
return major, minor, patch
|
||||
|
||||
|
||||
def latest_tag_version(exclude_version: tuple[int, int, int] | None) -> tuple[int, int, int] | None:
|
||||
try:
|
||||
output = subprocess.check_output(["git", "tag", "--list", "v*"], text=True)
|
||||
except Exception as exc:
|
||||
warn(f"Milestone assignment skipped (failed to list tags: {exc}).")
|
||||
return None
|
||||
versions: list[tuple[int, int, int]] = []
|
||||
for tag in output.splitlines():
|
||||
parsed = parse_version(tag)
|
||||
if not parsed:
|
||||
continue
|
||||
if exclude_version and parsed == exclude_version:
|
||||
continue
|
||||
versions.append(parsed)
|
||||
if not versions:
|
||||
return None
|
||||
return max(versions)
|
||||
|
||||
|
||||
def classify_bump(
|
||||
target: tuple[int, int, int] | None,
|
||||
previous: tuple[int, int, int] | None,
|
||||
) -> str | None:
|
||||
if not target or not previous:
|
||||
return None
|
||||
if target < previous:
|
||||
warn("Milestone assignment skipped (release version is behind latest tag).")
|
||||
return None
|
||||
if target[0] != previous[0]:
|
||||
return "major"
|
||||
if target[1] != previous[1]:
|
||||
return "minor"
|
||||
return "patch"
|
||||
|
||||
|
||||
def parse_milestone_title(title: str | None) -> tuple[int, int] | None:
|
||||
if not title:
|
||||
return None
|
||||
match = re.match(r"^(\d+)\.(\d+)\.x$", title)
|
||||
if not match:
|
||||
return None
|
||||
return int(match.group(1)), int(match.group(2))
|
||||
|
||||
|
||||
def fetch_open_milestones(owner: str, repo: str, token: str) -> list[dict]:
|
||||
url = f"https://api.github.com/repos/{owner}/{repo}/milestones?state=open&per_page=100"
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"Authorization": f"Bearer {token}",
|
||||
}
|
||||
req = request.Request(url, headers=headers)
|
||||
try:
|
||||
with request.urlopen(req) as response:
|
||||
return json.load(response)
|
||||
except error.HTTPError as exc:
|
||||
warn(f"Milestone assignment skipped (failed to list milestones: {exc.code}).")
|
||||
except Exception as exc:
|
||||
warn(f"Milestone assignment skipped (failed to list milestones: {exc}).")
|
||||
return []
|
||||
|
||||
|
||||
def select_milestone(milestones: list[dict], required_bump: str) -> str | None:
|
||||
parsed: list[dict] = []
|
||||
for milestone in milestones:
|
||||
parsed_title = parse_milestone_title(milestone.get("title"))
|
||||
if not parsed_title:
|
||||
continue
|
||||
parsed.append(
|
||||
{
|
||||
"milestone": milestone,
|
||||
"major": parsed_title[0],
|
||||
"minor": parsed_title[1],
|
||||
}
|
||||
)
|
||||
|
||||
parsed.sort(key=lambda entry: (entry["major"], entry["minor"]))
|
||||
if not parsed:
|
||||
warn("Milestone assignment skipped (no open milestones matching X.Y.x).")
|
||||
return None
|
||||
|
||||
majors = sorted({entry["major"] for entry in parsed})
|
||||
current_major = majors[0]
|
||||
next_major = majors[1] if len(majors) > 1 else None
|
||||
|
||||
current_major_entries = [entry for entry in parsed if entry["major"] == current_major]
|
||||
patch_target = current_major_entries[0]
|
||||
minor_target = current_major_entries[1] if len(current_major_entries) > 1 else patch_target
|
||||
|
||||
major_target = None
|
||||
if next_major is not None:
|
||||
next_major_entries = [entry for entry in parsed if entry["major"] == next_major]
|
||||
if next_major_entries:
|
||||
major_target = next_major_entries[0]
|
||||
|
||||
target_entry = None
|
||||
if required_bump == "major":
|
||||
target_entry = major_target
|
||||
elif required_bump == "minor":
|
||||
target_entry = minor_target
|
||||
else:
|
||||
target_entry = patch_target
|
||||
|
||||
if not target_entry:
|
||||
warn("Milestone assignment skipped (not enough open milestones for selection).")
|
||||
return None
|
||||
|
||||
return target_entry["milestone"].get("title")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--version", help="Release version (e.g., 0.6.6).")
|
||||
parser.add_argument(
|
||||
"--required-bump",
|
||||
choices=("major", "minor", "patch"),
|
||||
help="Override bump type (major/minor/patch).",
|
||||
)
|
||||
parser.add_argument("--repo", help="GitHub repository (owner/repo).")
|
||||
parser.add_argument("--token", help="GitHub token.")
|
||||
args = parser.parse_args()
|
||||
|
||||
required_bump = args.required_bump
|
||||
if not required_bump:
|
||||
target_version = parse_version(args.version)
|
||||
if not target_version:
|
||||
warn("Milestone assignment skipped (missing or invalid release version).")
|
||||
return 0
|
||||
previous_version = latest_tag_version(target_version)
|
||||
required_bump = classify_bump(target_version, previous_version)
|
||||
if not required_bump:
|
||||
warn("Milestone assignment skipped (unable to determine required bump).")
|
||||
return 0
|
||||
|
||||
token = args.token or os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
||||
if not token:
|
||||
warn("Milestone assignment skipped (missing GitHub token).")
|
||||
return 0
|
||||
|
||||
repo = args.repo or os.environ.get("GITHUB_REPOSITORY")
|
||||
if not repo or "/" not in repo:
|
||||
warn("Milestone assignment skipped (missing repository info).")
|
||||
return 0
|
||||
owner, name = repo.split("/", 1)
|
||||
|
||||
milestones = fetch_open_milestones(owner, name, token)
|
||||
if not milestones:
|
||||
return 0
|
||||
|
||||
milestone_title = select_milestone(milestones, required_bump)
|
||||
if milestone_title:
|
||||
print(milestone_title)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Deploy docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "mkdocs.yml"
|
||||
|
||||
permissions:
|
||||
contents: write # This allows pushing to gh-pages
|
||||
|
||||
jobs:
|
||||
deploy_docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Determine docs-only push
|
||||
id: docs-only
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" != "push" ]; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
set -euo pipefail
|
||||
before="${{ github.event.before }}"
|
||||
sha="${{ github.sha }}"
|
||||
changed_files=$(git diff --name-only "$before" "$sha" || true)
|
||||
non_docs=$(echo "$changed_files" | grep -vE '^(docs/|mkdocs.yml$)' || true)
|
||||
if [ -n "$non_docs" ]; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
- name: Setup uv
|
||||
if: steps.docs-only.outputs.skip != 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Install dependencies
|
||||
if: steps.docs-only.outputs.skip != 'true'
|
||||
run: make sync
|
||||
- name: Deploy docs
|
||||
if: steps.docs-only.outputs.skip != 'true'
|
||||
run: make deploy-docs
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899
|
||||
with:
|
||||
days-before-issue-stale: 7
|
||||
days-before-issue-close: 3
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "skip-stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 7 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 3 days since being marked as stale."
|
||||
any-of-issue-labels: 'question,needs-more-info'
|
||||
days-before-pr-stale: 10
|
||||
days-before-pr-close: 7
|
||||
stale-pr-label: "stale"
|
||||
exempt-pr-labels: "skip-stale"
|
||||
stale-pr-message: "This PR is stale because it has been open for 10 days with no activity."
|
||||
close-pr-message: "This PR was closed because it has been inactive for 7 days since being marked as stale."
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,35 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/openai-agents
|
||||
permissions:
|
||||
id-token: write # Important for trusted publishing to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OPENAI_API_KEY: fake-for-tests
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Install dependencies
|
||||
run: make sync
|
||||
- name: Build package
|
||||
run: uv build
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b
|
||||
@@ -0,0 +1,143 @@
|
||||
name: Create release PR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to release (e.g., 0.6.6)"
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release-pr:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Fetch tags
|
||||
run: git fetch origin --tags --prune
|
||||
- name: Ensure release branch does not exist
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
branch="release/v${RELEASE_VERSION}"
|
||||
if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then
|
||||
echo "Branch $branch already exists on origin." >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Update version
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
version = os.environ["RELEASE_VERSION"]
|
||||
if version.startswith("v"):
|
||||
print("Version must not start with 'v' (use x.y.z...).", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if ".." in version:
|
||||
print("Version contains consecutive dots (use x.y.z...).", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not re.match(r"^\d+\.\d+(\.\d+)*([a-zA-Z0-9\.-]+)?$", version):
|
||||
print(
|
||||
"Version must be semver-like (e.g., 0.6.6, 0.6.6-rc1, 0.6.6.dev1).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
path = pathlib.Path("pyproject.toml")
|
||||
text = path.read_text()
|
||||
updated, count = re.subn(
|
||||
r'(?m)^version\s*=\s*"[^\"]+"',
|
||||
f'version = "{version}"',
|
||||
text,
|
||||
)
|
||||
if count != 1:
|
||||
print("Expected to update exactly one version line.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if updated == text:
|
||||
print("Version already set; no changes made.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
path.write_text(updated)
|
||||
PY
|
||||
- name: Sync dependencies
|
||||
run: make sync
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
- name: Create release branch and commit
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
branch="release/v${RELEASE_VERSION}"
|
||||
git checkout -b "$branch"
|
||||
git add pyproject.toml uv.lock
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit." >&2
|
||||
exit 1
|
||||
fi
|
||||
git commit -m "Bump version to ${RELEASE_VERSION}"
|
||||
git push --set-upstream origin "$branch"
|
||||
- name: Build PR body
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
printf 'Release PR for %s.\n\nThe release readiness report will be prepared manually.\n' "$RELEASE_VERSION" > pr-body.md
|
||||
- name: Create or update PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
head_branch="release/v${RELEASE_VERSION}"
|
||||
milestone_name="$(python .github/scripts/select-release-milestone.py --version "$RELEASE_VERSION")"
|
||||
pr_number="$(gh pr list --head "$head_branch" --base "main" --json number --jq '.[0].number // empty')"
|
||||
if [ -z "$pr_number" ]; then
|
||||
create_args=(
|
||||
--title "Release ${RELEASE_VERSION}"
|
||||
--body-file pr-body.md
|
||||
--base "main"
|
||||
--head "$head_branch"
|
||||
--label "project"
|
||||
)
|
||||
if [ -n "$milestone_name" ]; then
|
||||
create_args+=(--milestone "$milestone_name")
|
||||
fi
|
||||
if ! gh pr create "${create_args[@]}"; then
|
||||
echo "PR create with label/milestone failed; retrying without them." >&2
|
||||
gh pr create \
|
||||
--title "Release ${RELEASE_VERSION}" \
|
||||
--body-file pr-body.md \
|
||||
--base "main" \
|
||||
--head "$head_branch"
|
||||
fi
|
||||
else
|
||||
edit_args=(
|
||||
--title "Release ${RELEASE_VERSION}"
|
||||
--body-file pr-body.md
|
||||
--add-label "project"
|
||||
)
|
||||
if [ -n "$milestone_name" ]; then
|
||||
edit_args+=(--milestone "$milestone_name")
|
||||
fi
|
||||
if ! gh pr edit "$pr_number" "${edit_args[@]}"; then
|
||||
echo "PR edit with label/milestone failed; retrying without them." >&2
|
||||
gh pr edit "$pr_number" --title "Release ${RELEASE_VERSION}" --body-file pr-body.md
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Tag release on merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
tag-release:
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
startsWith(github.event.pull_request.head.ref, 'release/v')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate merge commit
|
||||
env:
|
||||
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
run: |
|
||||
if [ -z "$MERGE_SHA" ]; then
|
||||
echo "merge_commit_sha is empty; refusing to tag to avoid tagging the wrong commit." >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Checkout merge commit
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.merge_commit_sha }}
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
with:
|
||||
python-version: "3.11"
|
||||
- name: Configure git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
- name: Fetch tags
|
||||
run: git fetch origin --tags --prune
|
||||
- name: Resolve version
|
||||
id: version
|
||||
env:
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import tomllib
|
||||
|
||||
path = pathlib.Path("pyproject.toml")
|
||||
data = tomllib.loads(path.read_text())
|
||||
version = data.get("project", {}).get("version")
|
||||
if not version:
|
||||
print("Missing project.version in pyproject.toml.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
head_ref = os.environ.get("HEAD_REF", "")
|
||||
if head_ref.startswith("release/v"):
|
||||
expected = head_ref[len("release/v") :]
|
||||
if expected != version:
|
||||
print(
|
||||
f"Version mismatch: branch {expected} vs pyproject {version}.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
output_path = pathlib.Path(os.environ["GITHUB_OUTPUT"])
|
||||
output_path.write_text(f"version={version}\n")
|
||||
PY
|
||||
- name: Create tag
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
if git tag -l "v${VERSION}" | grep -q "v${VERSION}"; then
|
||||
echo "Tag v${VERSION} already exists; skipping."
|
||||
exit 0
|
||||
fi
|
||||
git tag -a "v${VERSION}" -m "Release v${VERSION}"
|
||||
git push origin "v${VERSION}"
|
||||
@@ -0,0 +1,162 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
# All PRs, including stacked PRs
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
UV_FROZEN: "1"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Detect code changes
|
||||
id: changes
|
||||
run: ./.github/scripts/detect-changes.sh code "${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}"
|
||||
- name: Setup uv
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make sync
|
||||
- name: Verify formatting
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make format-check
|
||||
- name: Run lint
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make lint
|
||||
- name: Skip lint
|
||||
if: steps.changes.outputs.run != 'true'
|
||||
run: echo "Skipping lint for non-code changes."
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Detect code changes
|
||||
id: changes
|
||||
run: ./.github/scripts/detect-changes.sh code "${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}"
|
||||
- name: Setup uv
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make sync
|
||||
- name: Run typecheck
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make typecheck
|
||||
- name: Skip typecheck
|
||||
if: steps.changes.outputs.run != 'true'
|
||||
run: echo "Skipping typecheck for non-code changes."
|
||||
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "3.14"
|
||||
env:
|
||||
OPENAI_API_KEY: fake-for-tests
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Detect code changes
|
||||
id: changes
|
||||
run: ./.github/scripts/detect-changes.sh code "${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}"
|
||||
- name: Setup uv
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make sync
|
||||
- name: Run tests with coverage
|
||||
if: steps.changes.outputs.run == 'true' && matrix.python-version == '3.12'
|
||||
run: make coverage
|
||||
- name: Run tests
|
||||
if: steps.changes.outputs.run == 'true' && matrix.python-version != '3.12'
|
||||
run: make tests
|
||||
- name: Run async teardown stability tests
|
||||
if: steps.changes.outputs.run == 'true' && (matrix.python-version == '3.10' || matrix.python-version == '3.14')
|
||||
run: make tests-asyncio-stability
|
||||
- name: Skip tests
|
||||
if: steps.changes.outputs.run != 'true'
|
||||
run: echo "Skipping tests for non-code changes."
|
||||
|
||||
tests-windows:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
OPENAI_API_KEY: fake-for-tests
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Detect code changes
|
||||
id: changes
|
||||
shell: bash
|
||||
run: ./.github/scripts/detect-changes.sh code "${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}"
|
||||
- name: Setup uv
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
python-version: "3.13"
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: uv sync --all-extras --all-packages --group dev
|
||||
- name: Run tests
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: uv run pytest
|
||||
- name: Skip tests
|
||||
if: steps.changes.outputs.run != 'true'
|
||||
run: echo "Skipping tests for non-code changes."
|
||||
|
||||
build-docs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OPENAI_API_KEY: fake-for-tests
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- name: Detect docs changes
|
||||
id: changes
|
||||
run: ./.github/scripts/detect-changes.sh docs "${{ github.event.pull_request.base.sha || github.event.before }}" "${{ github.sha }}"
|
||||
- name: Setup uv
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # setup-uv v8.1.0; uv 0.11.14
|
||||
with:
|
||||
version: "0.11.14"
|
||||
enable-cache: true
|
||||
- name: Install dependencies
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make sync
|
||||
- name: Build docs
|
||||
if: steps.changes.outputs.run == 'true'
|
||||
run: make build-docs
|
||||
- name: Skip docs build
|
||||
if: steps.changes.outputs.run != 'true'
|
||||
run: echo "Skipping docs build for non-docs changes."
|
||||
Reference in New Issue
Block a user