Skip to content

Commit d4f6c72

Browse files
author
leic4u
committed
2 parents f82d3c9 + 5c19d2c commit d4f6c72

6 files changed

Lines changed: 789 additions & 68 deletions

File tree

.gitattributes

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ go.sum merge=union
1616
.github/workflows/upstream-sync.yml merge=ours
1717
.gitattributes merge=ours
1818
README-ccs-fork.md merge=ours
19+
20+
# Image references diverge from upstream (Plus publishes to
21+
# ghcr.io/kaitranntt/cli-proxy-api-plus, upstream publishes to
22+
# eceasy/cli-proxy-api). Keep ours so upstream sync does not silently revert
23+
# the image path back to upstream's.
24+
docker-compose.yml merge=ours

.github/workflows/sync-release-tag.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,26 @@ jobs:
5656
fi
5757
5858
FORK_TAG="${UPSTREAM_TAG}-0"
59+
# GITHUB_TOKEN tag pushes do NOT trigger downstream workflows, so we
60+
# explicitly dispatch release.yaml AND docker-image.yml after tagging.
61+
dispatch_downstream() {
62+
local tag="$1"
63+
gh workflow run release.yaml --repo "${GITHUB_REPOSITORY}" --ref "${GITHUB_REF_NAME}" -f tag="${tag}" || true
64+
gh workflow run docker-image.yml --repo "${GITHUB_REPOSITORY}" --ref "${GITHUB_REF_NAME}" -f tag="${tag}" || true
65+
}
66+
5967
if git ls-remote --exit-code --tags origin "refs/tags/${FORK_TAG}" >/dev/null 2>&1; then
6068
if gh release view "${FORK_TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
6169
echo "[i] ${FORK_TAG} release already exists; nothing to tag."
6270
exit 0
6371
fi
6472
65-
echo "[i] ${FORK_TAG} tag already exists but release is missing; dispatching release workflow."
66-
gh workflow run release.yaml --repo "${GITHUB_REPOSITORY}" --ref "${GITHUB_REF_NAME}" -f tag="${FORK_TAG}"
73+
echo "[i] ${FORK_TAG} tag already exists but release is missing; dispatching downstream workflows."
74+
dispatch_downstream "${FORK_TAG}"
6775
exit 0
6876
fi
6977
7078
echo "[i] Creating ${FORK_TAG} for upstream ${UPSTREAM_TAG}."
7179
git tag -a "$FORK_TAG" -m "CLIProxyAPIPlus ${FORK_TAG} (upstream ${UPSTREAM_TAG})"
7280
git push origin "refs/tags/${FORK_TAG}"
73-
gh workflow run release.yaml --repo "${GITHUB_REPOSITORY}" --ref "${GITHUB_REF_NAME}" -f tag="${FORK_TAG}"
81+
dispatch_downstream "${FORK_TAG}"

.github/workflows/upstream-sync.yml

Lines changed: 185 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,22 @@ name: Upstream Sync
22

33
# Polls for changes from router-for-me/CLIProxyAPI and merges them into this fork.
44
# Plus-only provider dirs are shielded via .gitattributes (merge=ours).
5-
# If the merge is clean AND build passes, push directly to main.
6-
# Otherwise, open a PR for manual review.
5+
#
6+
# Outcomes:
7+
# 1. Clean merge + build + test pass -> fast-forward main directly (no PR/issue noise)
8+
# 2. Anything else (conflicts auto-resolved with theirs, build/test red, etc.)
9+
# -> open or update a SINGLE tracking issue (label: upstream-sync-blocked).
10+
# No new PR is created. The sync branch is pushed for inspection.
11+
# 3. workflow_dispatch with force_pr=true -> always open a PR for manual review.
12+
#
13+
# Invariants enforced by the supersession step:
14+
# - At most ONE open upstream-sync PR exists at any time.
15+
# - At most ONE open upstream-sync-blocked tracking issue exists at any time.
16+
# - Stale upstream-sync/* branches from prior failed runs are pruned.
717

818
on:
919
schedule:
10-
- cron: '17 * * * *' # hourly; GitHub does not emit cross-repo release events
20+
- cron: '17 3 * * *' # daily at 03:17 UTC; upstream releases ~1/day
1121
workflow_dispatch:
1222
inputs:
1323
force_pr:
@@ -24,6 +34,10 @@ concurrency:
2434
group: upstream-sync
2535
cancel-in-progress: false
2636

37+
env:
38+
TRACKING_ISSUE_LABEL: upstream-sync-blocked
39+
PR_LABEL: upstream-sync
40+
2741
jobs:
2842
sync:
2943
runs-on: ubuntu-latest
@@ -39,6 +53,43 @@ jobs:
3953
git config user.name "ccs-upstream-sync[bot]"
4054
git config user.email "ccs-upstream-sync@users.noreply.github.com"
4155
56+
- name: Supersede prior sync PRs and stale branches
57+
env:
58+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59+
run: |
60+
set +e
61+
# Close every open upstream-sync PR and delete its head branch.
62+
# Invariant: 0 open upstream-sync PRs before we start a new run.
63+
OPEN_PRS=$(gh pr list \
64+
--repo "${{ github.repository }}" \
65+
--state open \
66+
--search 'in:title upstream-sync' \
67+
--json number,headRefName \
68+
--jq '.[] | "\(.number) \(.headRefName)"' || true)
69+
if [ -n "${OPEN_PRS}" ]; then
70+
echo "${OPEN_PRS}" | while read -r NUM BRANCH; do
71+
[ -z "${NUM}" ] && continue
72+
echo "[i] Superseding PR #${NUM} (branch ${BRANCH})"
73+
gh pr close "${NUM}" --repo "${{ github.repository }}" \
74+
--comment "Superseded by upcoming upstream-sync run." || true
75+
if [ -n "${BRANCH}" ]; then
76+
git push origin --delete "${BRANCH}" 2>/dev/null || true
77+
fi
78+
done
79+
fi
80+
81+
# Belt-and-braces: delete any orphan upstream-sync/* branches that no
82+
# longer have an associated open PR (e.g. PRs closed manually).
83+
git ls-remote --heads origin 'upstream-sync/*' \
84+
| awk '{print $2}' \
85+
| sed 's|refs/heads/||' \
86+
| while read -r BR; do
87+
[ -z "${BR}" ] && continue
88+
echo "[i] Pruning orphan branch ${BR}"
89+
git push origin --delete "${BR}" 2>/dev/null || true
90+
done
91+
set -e
92+
4293
- name: Add upstream remote
4394
run: git remote add upstream https://github.com/router-for-me/CLIProxyAPI.git
4495

@@ -65,6 +116,25 @@ jobs:
65116
git log --oneline "${UPSTREAM_MERGE_BASE}..upstream/main" | head -20
66117
fi
67118
119+
- name: Close stale tracking issue when fork is back in sync
120+
if: steps.check.outputs.has_changes == 'false'
121+
env:
122+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
123+
run: |
124+
set +e
125+
OPEN_ISSUES=$(gh issue list \
126+
--repo "${{ github.repository }}" \
127+
--state open \
128+
--label "${TRACKING_ISSUE_LABEL}" \
129+
--json number \
130+
--jq '.[].number' || true)
131+
for n in ${OPEN_ISSUES}; do
132+
echo "[i] Closing stale tracking issue #${n} (fork is in sync)."
133+
gh issue close "${n}" --repo "${{ github.repository }}" \
134+
--comment "Fork is now in sync with upstream. Auto-closing." || true
135+
done
136+
set -e
137+
68138
- name: Create sync branch
69139
if: steps.check.outputs.has_changes == 'true'
70140
id: branch
@@ -95,7 +165,7 @@ jobs:
95165
echo "[!] Conflicts detected; auto-resolving with upstream side:"
96166
echo "$UNMERGED"
97167
98-
# Save conflict list for PR body
168+
# Save conflict list for issue body
99169
{
100170
echo 'CONFLICT_FILES<<EOF'
101171
echo "$UNMERGED"
@@ -142,13 +212,20 @@ jobs:
142212
continue-on-error: true
143213
run: |
144214
set +e
145-
go build ./...
146-
BUILD_EXIT=$?
215+
BUILD_LOG=$(mktemp)
216+
go build ./... 2>&1 | tee "${BUILD_LOG}"
217+
BUILD_EXIT=${PIPESTATUS[0]}
147218
if [ $BUILD_EXIT -ne 0 ]; then
148219
echo "passed=false" >> $GITHUB_OUTPUT
149220
else
150221
echo "passed=true" >> $GITHUB_OUTPUT
151222
fi
223+
# Capture last 60 lines for issue body
224+
{
225+
echo 'BUILD_TAIL<<EOF'
226+
tail -n 60 "${BUILD_LOG}"
227+
echo 'EOF'
228+
} >> $GITHUB_ENV
152229
set -e
153230
154231
- name: Test gate
@@ -157,20 +234,27 @@ jobs:
157234
continue-on-error: true
158235
run: |
159236
set +e
160-
go test ./... -count=1 -timeout 10m
161-
TEST_EXIT=$?
237+
TEST_LOG=$(mktemp)
238+
go test ./... -count=1 -timeout 10m 2>&1 | tee "${TEST_LOG}"
239+
TEST_EXIT=${PIPESTATUS[0]}
162240
if [ $TEST_EXIT -ne 0 ]; then
163241
echo "passed=false" >> $GITHUB_OUTPUT
164242
else
165243
echo "passed=true" >> $GITHUB_OUTPUT
166244
fi
245+
{
246+
echo 'TEST_TAIL<<EOF'
247+
tail -n 60 "${TEST_LOG}"
248+
echo 'EOF'
249+
} >> $GITHUB_ENV
167250
set -e
168251
169252
- name: Push sync branch
170253
if: steps.check.outputs.has_changes == 'true'
171254
run: git push origin "${{ steps.branch.outputs.name }}"
172255

173-
- name: Fast-forward main (clean merge + build + test pass, no conflicts, no force-pr)
256+
- name: Fast-forward main (clean merge + build + test pass)
257+
id: ffwd
174258
if: |
175259
steps.check.outputs.has_changes == 'true' &&
176260
steps.merge.outputs.conflicts == 'false' &&
@@ -183,51 +267,112 @@ jobs:
183267
git push origin main
184268
git push origin --delete "${{ steps.branch.outputs.name }}"
185269
echo "[OK] Synced ${{ steps.check.outputs.merge_base }}..${{ steps.check.outputs.upstream_head }} directly to main."
270+
echo "did_ff=true" >> $GITHUB_OUTPUT
271+
272+
- name: Close stale tracking issue (fast-forward succeeded)
273+
if: steps.ffwd.outputs.did_ff == 'true'
274+
env:
275+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
276+
run: |
277+
set +e
278+
OPEN_ISSUES=$(gh issue list \
279+
--repo "${{ github.repository }}" \
280+
--state open \
281+
--label "${TRACKING_ISSUE_LABEL}" \
282+
--json number \
283+
--jq '.[].number' || true)
284+
for n in ${OPEN_ISSUES}; do
285+
gh issue close "${n}" --repo "${{ github.repository }}" \
286+
--comment "Resolved by automated fast-forward to upstream ${{ steps.check.outputs.upstream_head }}." || true
287+
done
288+
set -e
186289
187-
- name: Open PR (conflicts OR gate failure OR force_pr)
290+
- name: Open PR (force_pr only — manual escape hatch)
188291
if: |
189292
steps.check.outputs.has_changes == 'true' &&
190-
(steps.merge.outputs.conflicts == 'true' ||
191-
steps.build.outputs.passed == 'false' ||
192-
steps.test.outputs.passed == 'false' ||
193-
github.event.inputs.force_pr == 'true')
293+
github.event.inputs.force_pr == 'true'
194294
env:
195295
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
196296
run: |
197-
STATUS=""
198-
[ "${{ steps.merge.outputs.conflicts }}" = "true" ] && STATUS="${STATUS}- [!] Auto-resolved conflicts in: ${CONFLICT_FILES:-see diff}\n"
199-
[ "${{ steps.build.outputs.passed }}" = "false" ] && STATUS="${STATUS}- [!] \`go build\` FAILED\n"
200-
[ "${{ steps.test.outputs.passed }}" = "false" ] && STATUS="${STATUS}- [!] \`go test\` FAILED\n"
201-
[ -z "${STATUS}" ] && STATUS="- [OK] Clean merge + gates green (force_pr requested)\n"
202297
TITLE="chore(upstream-sync): $(date -u +%Y-%m-%d) pull from router-for-me/CLIProxyAPI"
203-
BODY=$(printf "## Upstream sync: \`router-for-me/CLIProxyAPI\` \`main\`\n\nCommits being synced: \`${{ steps.check.outputs.merge_base }}..${{ steps.check.outputs.upstream_head }}\`\n\n### Gate status\n%b\n### What to do\n1. Review the diff — especially \`cmd/server/main.go\`, \`go.mod\`, \`go.sum\`.\n2. Plus-only provider dirs are protected by \`.gitattributes merge=ours\`.\n3. Re-run failing gates locally: \`go build ./... && go test ./...\`.\n4. Merge when green.\n" "${STATUS}")
298+
BODY=$(printf "## Upstream sync (force_pr): \`router-for-me/CLIProxyAPI\` -> \`main\`\n\nCommits being synced: \`${{ steps.check.outputs.merge_base }}..${{ steps.check.outputs.upstream_head }}\`\n\nManually requested via workflow_dispatch.\n")
204299
set +e
205300
PR_URL=$(gh pr create \
206301
--base main \
207302
--head "${{ steps.branch.outputs.name }}" \
208303
--title "${TITLE}" \
209304
--body "${BODY}" 2>&1)
210305
PR_EXIT=$?
211-
if [ $PR_EXIT -ne 0 ]; then
212-
echo "[!] gh pr create exited $PR_EXIT — retrying via REST API."
213-
echo "$PR_URL"
214-
PR_URL=$(gh api "repos/${{ github.repository }}/pulls" \
215-
-X POST \
216-
-f base=main \
217-
-f head="${{ steps.branch.outputs.name }}" \
218-
-f title="${TITLE}" \
219-
-f body="${BODY}" \
220-
--jq '.html_url' 2>&1)
221-
PR_EXIT=$?
222-
fi
223306
set -e
224-
echo "$PR_URL"
225-
if [ $PR_EXIT -ne 0 ]; then
226-
echo "[!] PR creation exited $PR_EXIT — check log above."
227-
exit $PR_EXIT
307+
echo "${PR_URL}"
308+
if [ $PR_EXIT -ne 0 ]; then exit $PR_EXIT; fi
309+
PR_NUM=$(echo "${PR_URL}" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+$' || true)
310+
if [ -n "${PR_NUM}" ]; then
311+
gh pr edit "${PR_NUM}" --add-label "${PR_LABEL}" 2>&1 || true
228312
fi
229-
# Add label after create; tolerate label add failures (don't fail the job)
230-
PR_NUM=$(echo "$PR_URL" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+$' || true)
231-
if [ -n "$PR_NUM" ]; then
232-
gh pr edit "$PR_NUM" --add-label "upstream-sync" 2>&1 || echo "[i] Label add skipped (label may not exist yet)."
313+
314+
- name: Update tracking issue on gate failure or unresolved conflicts
315+
if: |
316+
steps.check.outputs.has_changes == 'true' &&
317+
github.event.inputs.force_pr != 'true' &&
318+
(steps.merge.outputs.conflicts == 'true' ||
319+
steps.build.outputs.passed == 'false' ||
320+
steps.test.outputs.passed == 'false')
321+
env:
322+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
323+
run: |
324+
set +e
325+
STATUS=""
326+
[ "${{ steps.merge.outputs.conflicts }}" = "true" ] && STATUS="${STATUS}- :warning: Auto-resolved conflicts in: \`${CONFLICT_FILES:-(see diff)}\`\n"
327+
[ "${{ steps.build.outputs.passed }}" = "false" ] && STATUS="${STATUS}- :x: \`go build ./...\` FAILED\n"
328+
[ "${{ steps.test.outputs.passed }}" = "false" ] && STATUS="${STATUS}- :x: \`go test ./...\` FAILED\n"
329+
330+
BRANCH="${{ steps.branch.outputs.name }}"
331+
UPSTREAM="${{ steps.check.outputs.upstream_head }}"
332+
UPSTREAM_TAG="${{ steps.check.outputs.upstream_tag }}"
333+
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
334+
BRANCH_URL="${{ github.server_url }}/${{ github.repository }}/tree/${BRANCH}"
335+
336+
# Ensure the label exists (idempotent).
337+
gh label create "${TRACKING_ISSUE_LABEL}" \
338+
--color "B60205" \
339+
--description "Automated upstream sync is blocked; needs maintainer attention." \
340+
--repo "${{ github.repository }}" 2>/dev/null || true
341+
342+
# Find existing open tracking issue (single-issue invariant).
343+
ISSUE_NUM=$(gh issue list \
344+
--repo "${{ github.repository }}" \
345+
--state open \
346+
--label "${TRACKING_ISSUE_LABEL}" \
347+
--json number \
348+
--jq '.[0].number' || true)
349+
350+
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
351+
COMMENT_BODY=$(printf '**New blocked sync attempt** (%s)\n\n- Upstream HEAD: `%s`\n- Upstream tag: `%s`\n- Sync branch: [%s](%s)\n- Workflow run: %s\n\n### Gate status\n%b\n<details><summary>Build tail (last 60 lines)</summary>\n\n```\n%s\n```\n\n</details>\n\n<details><summary>Test tail (last 60 lines)</summary>\n\n```\n%s\n```\n\n</details>\n' \
352+
"${NOW}" \
353+
"${UPSTREAM}" \
354+
"${UPSTREAM_TAG:-none}" \
355+
"${BRANCH}" "${BRANCH_URL}" \
356+
"${RUN_URL}" \
357+
"${STATUS}" \
358+
"${BUILD_TAIL:-<no build log captured>}" \
359+
"${TEST_TAIL:-<no test log captured>}")
360+
361+
if [ -n "${ISSUE_NUM}" ]; then
362+
echo "[i] Updating existing tracking issue #${ISSUE_NUM}"
363+
gh issue comment "${ISSUE_NUM}" \
364+
--repo "${{ github.repository }}" \
365+
--body "${COMMENT_BODY}" || exit 1
366+
else
367+
echo "[i] Creating new tracking issue"
368+
ISSUE_TITLE="upstream-sync blocked: needs maintainer attention"
369+
INTRO=$(printf 'The automated upstream sync from `router-for-me/CLIProxyAPI` cannot fast-forward `main` cleanly. This issue tracks the blocked state. **It will be auto-closed** once a sync run reaches main without intervention.\n\nEach subsequent failed run will append a comment with the latest failing commit and gate output. **No new PRs or issues are spawned** — this is the single source of truth.\n\n### What to do\n1. Pull the latest sync branch listed in the most recent comment.\n2. Reproduce locally: `go build ./... && go test ./...`.\n3. Patch Plus-only files (`.gitattributes merge=ours` paths) to track upstream API changes.\n4. Push the fix to `main`. The next scheduled run will close this issue.\n\nIf the fork has truly diverged beyond simple repair, consider reducing the `merge=ours` surface area in `.gitattributes`.\n\n---\n')
370+
ISSUE_BODY="${INTRO}${COMMENT_BODY}"
371+
gh issue create \
372+
--repo "${{ github.repository }}" \
373+
--title "${ISSUE_TITLE}" \
374+
--label "${TRACKING_ISSUE_LABEL}" \
375+
--assignee "${{ github.repository_owner }}" \
376+
--body "${ISSUE_BODY}" || exit 1
233377
fi
378+
set -e

internal/registry/models/models.json

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,29 +1293,6 @@
12931293
]
12941294
}
12951295
},
1296-
{
1297-
"id": "gpt-5.5",
1298-
"object": "model",
1299-
"created": 1776902400,
1300-
"owned_by": "openai",
1301-
"type": "openai",
1302-
"display_name": "GPT 5.5",
1303-
"version": "gpt-5.5",
1304-
"description": "Frontier model for complex coding, research, and real-world work.",
1305-
"context_length": 272000,
1306-
"max_completion_tokens": 128000,
1307-
"supported_parameters": [
1308-
"tools"
1309-
],
1310-
"thinking": {
1311-
"levels": [
1312-
"low",
1313-
"medium",
1314-
"high",
1315-
"xhigh"
1316-
]
1317-
}
1318-
},
13191296
{
13201297
"id": "codex-auto-review",
13211298
"object": "model",

0 commit comments

Comments
 (0)