Repository navigation
189 lines (180 loc) · 9.91 KB
/
Copy pathpullfrog.yml
File metadata and controls
189 lines (180 loc) · 9.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# PULLFROG ACTION — DO NOT EDIT EXCEPT WHERE INDICATED
name: Pullfrog
run-name: ${{ inputs.name || github.workflow }}
on:
workflow_dispatch:
inputs:
prompt:
type: string
description: Agent prompt
name:
type: string
description: Run name
# Every run is dispatched against the default branch, so a single shared group
# would make two reviews cancel each other. The group is keyed on the run name
# the dispatcher passes — one group per pull request under review — so a fresh
# review of the SAME pull request supersedes a stale one, while reviews of
# different pull requests never collide. Without a run name the key falls back
# to the run id, which is unique, so the group degrades to "cancel nothing".
concurrency:
group: ${{ github.workflow }}-${{ inputs.name || github.run_id }}
cancel-in-progress: true
# Read-only by default: no job may write to the repository unless it says so.
permissions:
contents: read
jobs:
pullfrog:
name: Pullfrog agent
runs-on: ubuntu-latest
timeout-minutes: 45 # Two bounded attempts plus checkout and cleanup.
env:
# Whether a second subscription token is configured. Only the presence is
# hoisted to job level, not the token: a step-level `if:` cannot read the
# `secrets` context at all — GitHub refuses to parse the whole workflow
# with `Unrecognized named-value: 'secrets'` — while it does read `env`.
# The provider keys deliberately stay on the agent steps, so a token is
# never in the environment of the checkout or of the final `run:`.
HAS_OAUTH_FALLBACK: ${{ secrets.PULLFROG_OAUTH_FALLBACK != '' }}
permissions:
# The action mints a short-lived OIDC token to prove this run's identity
# to Pullfrog's own token service, which is how a Router or subscription
# plan authenticates without a long-lived key stored in this repository.
# It grants nothing on the repository itself.
id-token: write # OIDC identity for Pullfrog's token service only.
# Read the diff under review. Deliberately NOT `write`: the agent is
# review-only (see `push: disabled` below) and has no commit path.
contents: read
steps:
- name: Checkout code
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 1
# actions/checkout defaults to persist-credentials: true, which
# leaves the job's GITHUB_TOKEN in .git/config. That hands the agent
# a working push credential inside the very checkout it operates on,
# which contradicts `push: disabled` below — the token is a write
# path the input cannot close. Pinakes' own CI policy check refuses
# any checkout that persists credentials, and it was right to.
# Nothing here needs it: the agent reaches GitHub through the App's
# installation token, and these repositories are public, so an
# unauthenticated fetch works.
persist-credentials: false
- name: Run agent
id: agent
# Held back rather than fatal: a failure here is the cue for the
# fallback step below. The final step restores the failure when no
# attempt succeeded, so a genuinely broken review still reports red.
continue-on-error: true
# Pinned to a commit SHA rather than the documented `@v0`, which is a
# tag that MOVES — it has already advanced through ninety v0.1.x
# releases. This is the one action here that runs an agent with access
# to the repository, so a mutable ref is the last place to accept one.
# Pullfrog's versioning page supports pinning and notes nothing is
# lost: the action fetches its steps from npm either way, so the agent
# still tracks patch releases without this file changing. Bump the SHA
# to update.
uses: pullfrog/pullfrog@0d318bef8c7cf7ae3f193ef32b2bc74e1d94b4d1 # v0.1.90
with:
prompt: ${{ inputs.prompt }}
timeout: 20m
# REVIEW ONLY. `push` defaults to `enabled`, which lets the agent
# push branches and open pull requests of its own. Every commit and
# pull request in this repository is authored by its maintainer, so
# the agent gets no write path: it reads the diff and comments. The
# input is declared in the action's action.yml and is not mentioned
# in the getting-started guide.
push: disabled
# Scrubs *_TOKEN / *_KEY / *_SECRET / *_PASSWORD / *_CREDENTIAL from
# the environment the agent's shell sees. Already the default for a
# public repository; set explicitly so that making this repository
# private some day cannot silently widen it — private repos default
# to `enabled`.
shell: restricted
env:
# add at least one provider API key
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_GENERATIVE_AI_API_KEY:
${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
META_MODEL_API_KEY: ${{ secrets.META_MODEL_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
# for Amazon Bedrock (https://docs.pullfrog.com/bedrock)
# AWS_BEARER_TOKEN_BEDROCK: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS_REGION: us-east-1
# BEDROCK_MODEL_ID: <bedrock-model-id>
# for Google Vertex AI (https://docs.pullfrog.com/vertex)
# VERTEX_SERVICE_ACCOUNT_JSON: >-
# ${{ secrets.VERTEX_SERVICE_ACCOUNT_JSON }}
# GOOGLE_CLOUD_PROJECT: my-project
# VERTEX_LOCATION: global
# VERTEX_MODEL_ID: <vertex-model-id>
# for Azure OpenAI (https://docs.pullfrog.com/azure)
# AZURE_RESOURCE_NAME is the <name> in https://<name>.openai.azure.com
# AZURE_DEPLOYMENT is your deployment's name, not the model it serves
# AZURE_RESOURCE_NAME: my-resource
# AZURE_API_KEY: ${{ secrets.AZURE_API_KEY }}
# AZURE_DEPLOYMENT: <deployment-name>
# both limits are required — set them to the real limits of that model
# AZURE_CONTEXT: "400000"
# AZURE_MAX_OUTPUT: "128000"
# for any OpenAI-compatible endpoint — LiteLLM, Cloudflare AI Gateway,
# self-hosted vLLM (https://docs.pullfrog.com/openai-compatible)
# OPENAI_COMPATIBLE_BASE_URL: https://litellm.example.com/v1
# OPENAI_COMPATIBLE_API_KEY: ${{ secrets.OPENAI_COMPATIBLE_API_KEY }}
# OPENAI_COMPATIBLE_MODEL: <model-id>
# both limits are required — set them to the real limits of that model
# OPENAI_COMPATIBLE_CONTEXT: "128000"
# OPENAI_COMPATIBLE_MAX_OUTPUT: "16384"
# The action exposes result, not a structured failure code. One retry is
# therefore allowed after ANY failure, not only a 429. Each attempt is
# capped at 20m: deterministic errors can spend the fallback quota, and
# a late failure after posting can repeat a review. This bounded trade-off
# is deliberate; a failed review must never turn into a green no-op.
# The workflow OAuth token leads account OAuth tokens of the same kind.
# Verified in runtime 0.1.97: stable subscription sorting keeps workflow
# entries first. This does not claim API keys outrank subscriptions.
# https://github.com/pullfrog/pullfrog/blob/f0684f2c9286f321085978685f6cbe91d51d4233/utils/credentialPool.ts#L175-L187
# A per-account rate limit is not fixed by a second expression, only by a
# second attempt: the first credential has to be tried and seen to fail.
# Preserve the primary step's provider configuration for non-Claude
# models and replace only its OAuth token. Keys stay scoped to the two
# agent steps, never checkout or final failure reporting.
- name: Run agent with the fallback subscription token
id: agent_fallback
if: steps.agent.outcome == 'failure' && env.HAS_OAUTH_FALLBACK == 'true'
uses: pullfrog/pullfrog@0d318bef8c7cf7ae3f193ef32b2bc74e1d94b4d1 # v0.1.90
with:
prompt: ${{ inputs.prompt }}
timeout: 20m
push: disabled
shell: restricted
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.PULLFROG_OAUTH_FALLBACK }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
GOOGLE_GENERATIVE_AI_API_KEY:
${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
META_MODEL_API_KEY: ${{ secrets.META_MODEL_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
- name: Fail when no attempt succeeded
if: steps.agent.outcome == 'failure' && steps.agent_fallback.outcome != 'success'
run: |
echo "::error::The agent failed with the primary credential, and the fallback failed too or is not configured."
exit 1