Skip to content

Commit 13ceea5

Browse files
authored
Merge pull request #56 from zaewc/develop
develop to master
2 parents 1181498 + f04e570 commit 13ceea5

114 files changed

Lines changed: 10543 additions & 5200 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets).
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md).

.changeset/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "master",
9+
"updateInternalDependencies": "patch",
10+
"ignore": []
11+
}

.github/actions/gemini/action.yml

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: 'Run Gemini with fallback'
2+
description: 'Run gemini-cli against a prompt file, with CLI model fallback and optional direct REST API fallback for plan-style read-only tasks.'
3+
4+
inputs:
5+
api_key:
6+
description: 'Gemini API key (secret).'
7+
required: true
8+
prompt_file:
9+
description: 'Path to a file containing the prompt to send.'
10+
required: true
11+
output_file:
12+
description: 'Path to write Gemini stdout to.'
13+
required: true
14+
models:
15+
description: 'Comma-separated fallback chain (CLI --model values).'
16+
required: true
17+
yolo:
18+
description: 'If "true", pass --yolo so tool calls (file edits) auto-accept. Set "false" for analysis-only callers that should also fail-safe via prompt instruction.'
19+
default: 'true'
20+
allow_rest_fallback:
21+
description: 'If "true", also try a direct Gemini REST API call when every CLI attempt fails. Useful for plan/eval phases that only need text output; pointless for phases that must edit files.'
22+
default: 'false'
23+
reset_between_attempts:
24+
description: 'If "true", run `git reset --hard` + `git clean -fd -e .ai -e .harness` between CLI fallback attempts. Required for agentic (file-editing) callers so a partially-modified tree from a failed attempt does not leak into the next model''s context.'
25+
default: 'false'
26+
27+
outputs:
28+
used_model:
29+
description: 'The model that produced output, or empty if all attempts failed.'
30+
value: ${{ steps.run.outputs.used_model }}
31+
used_rest:
32+
description: 'true if the REST fallback produced the output, false if CLI did.'
33+
value: ${{ steps.run.outputs.used_rest }}
34+
35+
runs:
36+
using: composite
37+
steps:
38+
- name: Install gemini-cli
39+
shell: bash
40+
run: |
41+
if ! command -v gemini >/dev/null 2>&1; then
42+
npm install -g @google/gemini-cli
43+
fi
44+
echo "::group::gemini version"
45+
gemini --version || true
46+
echo "::endgroup::"
47+
48+
- name: Run with fallback
49+
id: run
50+
shell: bash
51+
env:
52+
GEMINI_API_KEY: ${{ inputs.api_key }}
53+
PROMPT_FILE: ${{ inputs.prompt_file }}
54+
OUTPUT_FILE: ${{ inputs.output_file }}
55+
MODELS_CSV: ${{ inputs.models }}
56+
YOLO_FLAG: ${{ inputs.yolo == 'true' && '--yolo' || '' }}
57+
ALLOW_REST: ${{ inputs.allow_rest_fallback }}
58+
RESET_BETWEEN: ${{ inputs.reset_between_attempts }}
59+
GEMINI_CLI_TRUST_WORKSPACE: 'true'
60+
run: |
61+
set -e
62+
mkdir -p "$(dirname "$OUTPUT_FILE")" .ai
63+
64+
rc=1
65+
used_model=""
66+
used_rest="false"
67+
68+
# Snapshot the working tree state. Used to reset between fallback
69+
# attempts when the caller is editing files (otherwise a quota-killed
70+
# attempt leaves half-written files that confuse the next model).
71+
initial_head=""
72+
if [ "$RESET_BETWEEN" = "true" ]; then
73+
initial_head=$(git rev-parse HEAD 2>/dev/null || echo "")
74+
echo "::notice::reset_between_attempts=true (initial HEAD=$initial_head)"
75+
fi
76+
77+
attempt=0
78+
IFS=',' read -ra MODELS <<< "$MODELS_CSV"
79+
for model in "${MODELS[@]}"; do
80+
model="$(echo "$model" | xargs)"
81+
[ -z "$model" ] && continue
82+
83+
# Before any retry (attempt > 0), restore the working tree to its
84+
# pre-Gemini state so the next model starts from a clean slate.
85+
if [ "$attempt" -gt 0 ] && [ -n "$initial_head" ]; then
86+
echo "::notice::resetting working tree before retry"
87+
git reset --hard "$initial_head" 2>/dev/null || true
88+
git clean -fd -e .ai -e .harness 2>/dev/null || true
89+
fi
90+
attempt=$((attempt + 1))
91+
92+
echo "::group::CLI attempt: $model"
93+
set +e
94+
gemini --model "$model" $YOLO_FLAG --prompt "$(cat "$PROMPT_FILE")" \
95+
> "$OUTPUT_FILE" 2> .ai/gemini.err
96+
rc=$?
97+
set -e
98+
echo "exit: $rc"
99+
tail -n 20 .ai/gemini.err 2>/dev/null || true
100+
echo "::endgroup::"
101+
102+
if [ $rc -eq 0 ]; then
103+
used_model="$model"
104+
echo "CLI succeeded with $model"
105+
break
106+
fi
107+
if grep -qE 'TerminalQuotaError|Quota exceeded|"code": ?429|status: ?429' .ai/gemini.err 2>/dev/null; then
108+
echo "::notice::$model hit quota; trying next"
109+
continue
110+
fi
111+
echo "::warning::$model failed with non-quota error; stop CLI loop"
112+
break
113+
done
114+
115+
if [ $rc -ne 0 ] && [ "$ALLOW_REST" = "true" ]; then
116+
echo "::notice::falling back to direct Gemini REST API"
117+
for model in "${MODELS[@]}"; do
118+
model="$(echo "$model" | xargs)"
119+
[ -z "$model" ] && continue
120+
echo "::group::REST attempt: $model"
121+
body=$(jq -n --rawfile p "$PROMPT_FILE" '{contents:[{parts:[{text:$p}]}]}')
122+
http_code=$(curl -sS -o .ai/rest.json -w '%{http_code}' \
123+
"https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent" \
124+
-H "x-goog-api-key: $GEMINI_API_KEY" \
125+
-H "Content-Type: application/json" \
126+
-d "$body" || echo "000")
127+
echo "HTTP $http_code"
128+
if [ "$http_code" = "200" ]; then
129+
text=$(jq -r '.candidates[0].content.parts[0].text // empty' .ai/rest.json)
130+
if [ -n "$text" ]; then
131+
printf '%s' "$text" > "$OUTPUT_FILE"
132+
rc=0
133+
used_model="$model"
134+
used_rest="true"
135+
echo "REST succeeded with $model"
136+
echo "::endgroup::"
137+
break
138+
fi
139+
fi
140+
jq -r '.error.message // .' .ai/rest.json 2>/dev/null | head -3 || true
141+
echo "::endgroup::"
142+
done
143+
fi
144+
145+
echo "used_model=$used_model" >> "$GITHUB_OUTPUT"
146+
echo "used_rest=$used_rest" >> "$GITHUB_OUTPUT"
147+
148+
if [ $rc -ne 0 ]; then
149+
echo "::error::all Gemini attempts (CLI${ALLOW_REST:+ + REST}) failed"
150+
echo "::group::stdout tail"
151+
tail -n 60 "$OUTPUT_FILE" 2>/dev/null || echo "(empty)"
152+
echo "::endgroup::"
153+
exit $rc
154+
fi

0 commit comments

Comments
 (0)