Skip to content
Merged
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
30 changes: 24 additions & 6 deletions ai-release-notes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

Generate polished, AI-powered release notes from GitHub's auto-generated changelog and optionally create a draft GitHub release.

The action compares the current tag with the previous one, fetches the raw changelog via the GitHub API, rewrites it using the [GitHub Models API](https://docs.github.com/en/github-models), and creates a draft release with the result.
The action compares the current tag with the previous one, fetches the raw changelog via the GitHub API, rewrites it using an OpenAI-compatible chat completions API, and creates a draft release with the result.

> **Note:** This action previously used the GitHub Models API, which was [fully retired on July 30, 2026](https://github.blog/changelog/2026-07-30-github-models-is-now-retired/). It now calls a configurable OpenAI-compatible endpoint instead and requires an `api-key`. Without an `api-key`, the AI rewrite is skipped and the raw GitHub changelog is used — the release is still created.

## Prerequisites

The calling workflow must:

1. **Check out the repository** with full history (`fetch-depth: 0`) so previous tags can be detected.
2. **Grant permissions** for `contents: write` (to create releases) and `models: read` (to call the AI API).
2. **Grant permissions** for `contents: write` (to create releases).
3. **Provide an API key** for an OpenAI-compatible provider via the `api-key` input (typically an org/repo secret). Without it, the release falls back to the raw GitHub changelog.

## Usage

Expand All @@ -25,7 +28,6 @@ on:

permissions:
contents: write
models: read

jobs:
release-notes:
Expand All @@ -38,6 +40,20 @@ jobs:
- uses: shopware/github-actions/ai-release-notes@main
with:
product-name: "My Product"
api-key: ${{ secrets.OPENAI_API_KEY }}
```

### Using a Different Provider

Any OpenAI-compatible chat completions endpoint works — for example the Anthropic API:

```yaml
- uses: shopware/github-actions/ai-release-notes@main
with:
product-name: "My Product"
api-endpoint: "https://api.anthropic.com/v1/chat/completions"
api-key: ${{ secrets.ANTHROPIC_API_KEY }}
model: "claude-sonnet-5"
```

### Customised Example
Expand Down Expand Up @@ -87,20 +103,22 @@ Use the action as a pure notes generator — for example to post to Slack or app
| `collapse-types` | no | `"dependency,translation,CI"` | Change types collapsed under the last section |
| `additional-rules` | no | `""` | Extra rules appended to the default prompt |
| `custom-prompt` | no | `""` | Completely override the system prompt (ignores all formatting inputs) |
| `model` | no | `"gpt-4o"` | GitHub Models API model name |
| `api-endpoint` | no | `"https://api.openai.com/v1/chat/completions"` | OpenAI-compatible chat completions endpoint URL |
| `api-key` | no | `""` | API key for the AI provider — when empty, the AI rewrite is skipped and the raw changelog is used |
| `model` | no | `"gpt-4o"` | Model name understood by the configured endpoint |
| `temperature` | no | `"0.4"` | AI temperature (`0.0` = deterministic, `1.0` = creative) |
| `tag-pattern` | no | `"^v"` | Regex pattern to match tags when detecting the previous release |
| `release-name` | no | `"Release {tag}"` | Release name template — use `{tag}` as placeholder for the tag name |
| `draft` | no | `"true"` | Create the release as a draft |
| `prerelease` | no | `"false"` | Mark the release as a prerelease |
| `create-release` | no | `"true"` | Whether to create a GitHub release (`"false"` = only generate notes) |
| `github-token` | no | `${{ github.token }}` | GitHub token (needs `models:read` + `contents:write`) |
| `github-token` | no | `${{ github.token }}` | GitHub token (needs `contents:write`) |

## Outputs

| Output | Description |
|---|---|
| `release-notes` | The AI-generated release notes (Markdown) |
| `release-notes` | The AI-generated release notes (Markdown) — or the raw changelog when the AI rewrite was skipped |
| `raw-notes` | The raw GitHub-generated changelog before AI rewrite |
| `release-url` | URL of the created release (empty if `create-release` is `"false"`) |
| `previous-tag` | The detected previous git tag |
Expand Down
34 changes: 26 additions & 8 deletions ai-release-notes/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@ inputs:
description: "Completely override the default system prompt (ignores all formatting inputs)"
required: false
default: ""
api-endpoint:
description: "OpenAI-compatible chat completions endpoint URL"
required: false
default: "https://api.openai.com/v1/chat/completions"
api-key:
description: "API key for the AI provider. When empty, the AI rewrite is skipped and the raw GitHub changelog is used as release notes."
required: false
default: ""
model:
description: "GitHub Models API model name"
description: "Model name understood by the configured endpoint"
required: false
default: "gpt-4o"
temperature:
Expand Down Expand Up @@ -70,13 +78,13 @@ inputs:
required: false
default: "true"
github-token:
description: "GitHub token with models:read and contents:write permissions"
description: "GitHub token with contents:write permission"
required: false
default: "${{ github.token }}"

outputs:
release-notes:
description: "The AI-generated release notes (Markdown)"
description: "The AI-generated release notes (Markdown) — or the raw changelog when the AI rewrite was skipped"
value: "${{ steps.ai-notes.outputs.notes }}"
raw-notes:
description: "The raw GitHub-generated changelog before AI rewrite"
Expand Down Expand Up @@ -140,7 +148,8 @@ runs:
id: ai-notes
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
GITHUB_TOKEN: "${{ inputs.github-token }}"
INPUT_API_ENDPOINT: "${{ inputs.api-endpoint }}"
INPUT_API_KEY: "${{ inputs.api-key }}"
RAW_NOTES: "${{ steps.raw-notes.outputs.body }}"
INPUT_PRODUCT_NAME: "${{ inputs.product-name }}"
INPUT_PRODUCT_DESCRIPTION: "${{ inputs.product-description }}"
Expand All @@ -158,6 +167,13 @@ runs:
const currentTag = process.env.GITHUB_REF_NAME;
const rawNotes = process.env.RAW_NOTES;
const customPrompt = process.env.INPUT_CUSTOM_PROMPT;
const apiKey = process.env.INPUT_API_KEY;

if (!apiKey) {
core.warning('No api-key configured — skipping AI rewrite and using the raw GitHub changelog as release notes.');
core.setOutput('notes', rawNotes);
return;
}

let systemPrompt;

Expand Down Expand Up @@ -216,11 +232,11 @@ runs:
systemPrompt = lines.join('\n');
}

const response = await fetch('https://models.inference.ai.azure.com/chat/completions', {
const response = await fetch(process.env.INPUT_API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.GITHUB_TOKEN}`,
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: process.env.INPUT_MODEL,
Expand All @@ -234,14 +250,16 @@ runs:

if (!response.ok) {
const errorBody = await response.text();
core.setFailed(`GitHub Models API error (${response.status}): ${errorBody}`);
core.warning(`AI API error (${response.status}): ${errorBody} — falling back to the raw GitHub changelog.`);
core.setOutput('notes', rawNotes);
return;
}

const result = await response.json();

if (!result.choices?.length) {
core.setFailed(`Unexpected API response: ${JSON.stringify(result)}`);
core.warning(`Unexpected AI API response: ${JSON.stringify(result)} — falling back to the raw GitHub changelog.`);
core.setOutput('notes', rawNotes);
return;
}

Expand Down
Loading