🗝️ feat: Cache Stable OpenAI Prompt Prefixes Across Conversations #16257
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Frontend Unit Tests | |
| on: | |
| pull_request: | |
| paths: | |
| - 'e2e/client-build.test.mjs' | |
| - 'client/**' | |
| - 'packages/client/**' | |
| - 'packages/data-provider/**' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - '.github/workflows/frontend-review.yml' | |
| - '!**.md' | |
| # Post-merge safety net and full-run baseline for gated selection (same rationale as | |
| # backend-review.yml stage 1): every dev merge touching frontend paths runs the full suite. | |
| push: | |
| branches: | |
| - dev | |
| paths: | |
| - 'e2e/client-build.test.mjs' | |
| - 'client/**' | |
| - 'packages/client/**' | |
| - 'packages/data-provider/**' | |
| - 'package.json' | |
| - 'package-lock.json' | |
| - '.github/workflows/frontend-review.yml' | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| concurrency: | |
| # PR pushes supersede each other (per-PR canceling group). Push events get a PER-COMMIT group: | |
| # dev-push runs are the post-merge safety net and the full-run baseline, and with a shared | |
| # canceling group closely spaced merges cancel each other's runs — observed live on 2026-08-23, | |
| # when three consecutive dev merges cancelled the runs that would have caught #15142's red | |
| # (Codex P2 on #15145). | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=${{ secrets.NODE_MAX_OLD_SPACE_SIZE || 6144 }}' | |
| jobs: | |
| client-build-regression: | |
| name: Client build recovery regression | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| cache: npm | |
| - run: npm ci | |
| - run: google-chrome --version | |
| - run: npm run test:client-build | |
| env: | |
| PLAYWRIGHT_CHANNEL: chrome | |
| # Stage 1.5 of codegraph gating (stage 1 = backend jest, #15132; stage 2 = matrix lanes, | |
| # #15136). Same mechanism, same record: across 604 finalized shadow receipts the frontend | |
| # selection has zero structural misses (its one raw MISSED was a chronic flake), over 500 | |
| # narrowed decisions. Selected (safe mode) on synchronize only; full on PR open/reopen, on | |
| # every dev push, and on any doubt. A workspace skips ONLY on an explicit NONE. Kill switch: | |
| # repo variable CODEGRAPH_GATING=off. Neither frontend workspace defines | |
| # testPathIgnorePatterns, so --runTestsByPath needs no exclude mirroring here. | |
| codegraph_select: | |
| name: Codegraph select | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'synchronize' && | |
| vars.CODEGRAPH_GATING != 'off' | |
| outputs: | |
| decided: ${{ steps.sel.outputs.decided }} | |
| client_run: ${{ steps.sel.outputs.client_run }} | |
| client_files: ${{ steps.sel.outputs.client_files }} | |
| clientpkg_run: ${{ steps.sel.outputs.clientpkg_run }} | |
| clientpkg_files: ${{ steps.sel.outputs.clientpkg_files }} | |
| steps: | |
| - name: Select tests, fail open on any doubt | |
| id: sel | |
| env: | |
| URL: ${{ secrets.CODEGRAPH_URL }} | |
| TOKEN: ${{ secrets.CODEGRAPH_TOKEN }} | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| CHANGED: ${{ github.event.pull_request.changed_files }} | |
| run: | | |
| set +e | |
| note() { echo "$1" >> "$GITHUB_STEP_SUMMARY"; } | |
| note "### Codegraph select — GATING (frontend jest)" | |
| if [ -z "$URL" ] || [ -z "$TOKEN" ]; then note "_no codegraph config; running FULL_"; exit 0; fi | |
| if ! gh api "repos/$REPO/pulls/$PR/files" --paginate \ | |
| --jq '.[] | {path: .filename, status, patch}' > files.ndjson; then | |
| note "_could not fetch changed files; running FULL_"; exit 0 | |
| fi | |
| jq -s . files.ndjson > files.json | |
| N=$(jq 'length' files.json) | |
| if [ "$N" -eq 0 ] || { [ -n "$CHANGED" ] && [ "$N" -ne "$CHANGED" ]; }; then | |
| note "_changed-file list incomplete ($N of ${CHANGED:-?}); running FULL_"; exit 0 | |
| fi | |
| jq -c --arg b "$BASE_SHA" --arg h "$HEAD_SHA" \ | |
| '{files: ., mode: "safe", lockBaseSha: $b, lockHeadSha: $h}' files.json > body.json | |
| RESP=$(curl -sS --fail-with-body -m 45 -H "Authorization: Bearer $TOKEN" \ | |
| -H 'content-type: application/json' --data-binary @body.json "$URL/v1/select"); RC=$? | |
| if [ "$RC" -ne 0 ] || [ -z "$RESP" ] || ! echo "$RESP" | jq -e '.selected.client.mode' >/dev/null 2>&1; then | |
| note "_codegraph unavailable (curl exit $RC: ${RESP:0:120}); running FULL_" | |
| exit 0 | |
| fi | |
| emit() { | |
| key="$1"; ws="$2" | |
| mode=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].mode') | |
| files="" | |
| if [ "$mode" = "FILES" ]; then | |
| # Only an explicit NONE may skip. FILES with a missing/empty list is a malformed | |
| # decision (service/schema skew) and must run FULL (Codex P1 on #15145). | |
| raw_n=$(echo "$RESP" | jq -r --arg w "$ws" '.selected[$w].files // [] | length') | |
| # Every selected path must live under the workspace: a wrong-prefixed path would | |
| # survive ltrimstr, match nothing in the workspace cwd, and --passWithNoTests would | |
| # turn "ran nothing" into green — a silent fail-closed (Codex P1 on #15145). | |
| misplaced=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" '[.selected[$w].files // [] | .[] | select(startswith($p) | not)] | length') | |
| if [ "$raw_n" = "0" ] || [ "$misplaced" != "0" ]; then | |
| mode="FULL" | |
| note "| $ws | malformed FILES decision ($raw_n files, $misplaced outside $ws/); running FULL |" | |
| else | |
| files=$(echo "$RESP" | jq -r --arg w "$ws" --arg p "$ws/" \ | |
| '.selected[$w].files // [] | map(select(test(" ") | not)) | map(ltrimstr($p)) | join(" ")') | |
| spaced=$(echo "$RESP" | jq -r --arg w "$ws" '[.selected[$w].files // [] | .[] | select(test(" "))] | length') | |
| if [ "$spaced" != "0" ]; then mode="FULL"; files=""; fi | |
| fi | |
| fi | |
| if [ "$mode" = "NONE" ]; then | |
| echo "${key}_run=false" >> "$GITHUB_OUTPUT" | |
| note "| $ws | skip (no reachable tests) |" | |
| elif [ "$mode" = "FILES" ]; then | |
| n=$(echo "$files" | wc -w | tr -d ' ') | |
| echo "${key}_run=true" >> "$GITHUB_OUTPUT" | |
| echo "${key}_files=$files" >> "$GITHUB_OUTPUT" | |
| note "| $ws | $n selected files |" | |
| else | |
| echo "${key}_run=true" >> "$GITHUB_OUTPUT" | |
| note "| $ws | FULL |" | |
| fi | |
| } | |
| note "| workspace | decision |" | |
| note "|---|---|" | |
| emit client client | |
| emit clientpkg packages/client | |
| echo "decided=true" >> "$GITHUB_OUTPUT" | |
| note "" | |
| note "kill switch: repo variable \`CODEGRAPH_GATING=off\`; full runs remain on PR open and on every dev push" | |
| exit 0 | |
| build: | |
| name: Build packages | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| client/node_modules | |
| packages/client/node_modules | |
| packages/data-provider/node_modules | |
| key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Restore data-provider build cache | |
| id: cache-data-provider | |
| uses: actions/cache@v5 | |
| with: | |
| path: packages/data-provider/dist | |
| key: build-data-provider-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} | |
| - name: Build data-provider | |
| if: steps.cache-data-provider.outputs.cache-hit != 'true' | |
| run: npm run build:data-provider | |
| - name: Restore client-package build cache | |
| id: cache-client-package | |
| uses: actions/cache@v5 | |
| with: | |
| path: packages/client/dist | |
| key: build-client-package-${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json', 'packages/client/src/**', 'packages/client/tsconfig*.json', 'packages/client/tsdown.config.mjs', 'packages/client/package.json', 'packages/data-provider/src/**', 'packages/data-provider/tsconfig*.json', 'packages/data-provider/tsdown.config.mjs', 'packages/data-provider/package.json') }} | |
| - name: Build client-package | |
| if: steps.cache-client-package.outputs.cache-hit != 'true' | |
| run: npm run build:client-package | |
| - name: Upload data-provider build | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| retention-days: 2 | |
| - name: Upload client-package build | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: build-client-package | |
| path: packages/client/dist | |
| retention-days: 2 | |
| typecheck: | |
| name: TypeScript type checks (client) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| client/node_modules | |
| packages/client/node_modules | |
| packages/data-provider/node_modules | |
| key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download client-package build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-client-package | |
| path: packages/client/dist | |
| - name: Type check client | |
| run: npm run typecheck | |
| working-directory: client | |
| test-packages-client: | |
| name: 'Tests: @librechat/client' | |
| needs: [build, codegraph_select] | |
| if: >- | |
| !cancelled() && needs.build.result == 'success' && | |
| needs.codegraph_select.outputs.clientpkg_run != 'false' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| client/node_modules | |
| packages/client/node_modules | |
| packages/data-provider/node_modules | |
| key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Run unit tests | |
| env: | |
| SELECTED: ${{ needs.codegraph_select.outputs.clientpkg_files }} | |
| run: | | |
| # A selected path can be stale in exactly two ways at this checkout (Codex P2, #15145 r6): | |
| # deleted on the branch — dropped, which matches full CI (the file runs nowhere) — or | |
| # renamed, where the NEW path is a changed test file and is selected independently. If | |
| # NOTHING selected exists, the selection is stale wholesale and the suite runs FULL; | |
| # --passWithNoTests must never turn "ran nothing" into green. | |
| if [ -n "$SELECTED" ]; then | |
| KEEP="" | |
| for f in $SELECTED; do | |
| if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi | |
| done | |
| KEEP="${KEEP# }" | |
| if [ -z "$KEEP" ]; then | |
| echo "no selected test file exists at HEAD (stale selection); running FULL" | |
| npm run test:ci | |
| else | |
| echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" | |
| npm run test:ci -- --passWithNoTests --runTestsByPath $KEEP | |
| fi | |
| else | |
| npm run test:ci | |
| fi | |
| working-directory: packages/client | |
| test-ubuntu: | |
| name: 'Tests: Ubuntu (shard ${{ matrix.shard }}/2)' | |
| needs: [build, codegraph_select] | |
| if: >- | |
| !cancelled() && needs.build.result == 'success' && | |
| needs.codegraph_select.outputs.client_run != 'false' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2] | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| client/node_modules | |
| packages/client/node_modules | |
| packages/data-provider/node_modules | |
| key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download client-package build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-client-package | |
| path: packages/client/dist | |
| - name: Run unit tests (shard ${{ matrix.shard }}/2) | |
| env: | |
| SELECTED: ${{ needs.codegraph_select.outputs.client_files }} | |
| run: | | |
| if [ -n "$SELECTED" ]; then | |
| KEEP="" | |
| for f in $SELECTED; do | |
| if [ -f "$f" ]; then KEEP="$KEEP $f"; else echo "dropping selected path absent at HEAD (deleted or renamed): $f"; fi | |
| done | |
| KEEP="${KEEP# }" | |
| if [ -z "$KEEP" ]; then | |
| echo "no selected test file exists at HEAD (stale selection); running FULL" | |
| npm run test:ci -- --shard=${{ matrix.shard }}/2 | |
| else | |
| echo "codegraph: $(echo $KEEP | wc -w) selected test files (safe mode)" | |
| npm run test:ci -- --shard=${{ matrix.shard }}/2 --passWithNoTests --runTestsByPath $KEEP | |
| fi | |
| else | |
| npm run test:ci -- --shard=${{ matrix.shard }}/2 | |
| fi | |
| working-directory: client | |
| build-verify: | |
| name: Vite build verification | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Use Node.js 24.16.0 | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: '24.16.0' | |
| - name: Restore node_modules cache | |
| id: cache-node-modules | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| node_modules | |
| client/node_modules | |
| packages/client/node_modules | |
| packages/data-provider/node_modules | |
| key: node-modules-frontend-${{ runner.os }}-24.16.0-${{ hashFiles('package-lock.json') }} | |
| - name: Install dependencies | |
| if: steps.cache-node-modules.outputs.cache-hit != 'true' | |
| run: npm ci | |
| - name: Download data-provider build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-data-provider | |
| path: packages/data-provider/dist | |
| - name: Download client-package build | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: build-client-package | |
| path: packages/client/dist | |
| - name: Build client | |
| run: cd client && npm run build:ci |