From b157b4c2a523da8c8ba1cd6235650e8c6686c932 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:36:10 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Engine:=20Optimize=20c?= =?UTF-8?q?onversational=20history=20pruning=20&=20authenticated=20release?= =?UTF-8?q?=20fetching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: alehar9320 <5390430+alehar9320@users.noreply.github.com> --- .jules/engine.md | 6 ++++++ src/pages/api/release-summary.ts | 3 ++- src/pages/api/releases.ts | 17 +++-------------- src/utils/chat-logic.test.ts | 12 ++++++++++++ src/utils/chat-logic.ts | 5 +++++ 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.jules/engine.md b/.jules/engine.md index 19b7698e..e47fdc09 100644 --- a/.jules/engine.md +++ b/.jules/engine.md @@ -1,5 +1,11 @@ # ⚙️ Engine Journal +## 2025-05-21 - Conversational Turn Pruning & Authenticated Edge Data Fetching + +- **Architectural Shift & Model Context Hygiene:** Enhanced `pruneMessages` in `src/utils/chat-logic.ts` to automatically strip orphan leading `assistant` turns when conversation history is trimmed due to character/message limits. Ensures Workers AI LLM context windows always start cleanly with a `user` prompt turn. +- **Edge-Case Constraint & API Rate-Limit Safeguard:** Updated `src/pages/api/release-summary.ts` to pass `GITHUB_TOKEN` worker bindings to `fetchGitHubReleases`. Prevents unauthenticated GitHub API rate limiting (60 req/hr per edge worker IP) when fetching releases for AI summary generation. +- **Performance:** Streamlined `visitorReleases` in `src/pages/api/releases.ts` to avoid redundant re-parsing of release bodies. + ## 2025-05-14 - Centralized Chat Logic & Pruning - **Architectural Shift:** Centralized chat-related constants and logic into `src/utils/chat-logic.ts` to ensure consistency between the API and potential future client-side pruning. diff --git a/src/pages/api/release-summary.ts b/src/pages/api/release-summary.ts index 18e4b51d..739334c0 100644 --- a/src/pages/api/release-summary.ts +++ b/src/pages/api/release-summary.ts @@ -100,7 +100,8 @@ async function summarize(request: Request) { } if (!latest) { - const releases = await fetchGitHubReleases(); + const token = bindings.GITHUB_TOKEN?.trim(); + const releases = await fetchGitHubReleases(fetch, undefined, token ? { token } : undefined); latest = releases[0] ?? null; if (latest) source = 'github'; } diff --git a/src/pages/api/releases.ts b/src/pages/api/releases.ts index 3d855e28..fbf16dbe 100644 --- a/src/pages/api/releases.ts +++ b/src/pages/api/releases.ts @@ -4,6 +4,8 @@ import { LATEST_RELEASE_SNAPSHOT } from '../../data/latest-release'; import { fetchGitHubReleases, type SiteRelease } from '../../utils/github-releases'; import { toVisitorChangelogTitle, toVisitorRelease } from '../../utils/visitor-changelog'; +export { toVisitorChangelogTitle }; + const jsonHeaders = { 'content-type': 'application/json', 'Cache-Control': 'public, max-age=60', @@ -29,20 +31,7 @@ function readEnv(): ReleasesEnv { } function visitorReleases(releases: SiteRelease[]) { - return releases.map((release) => { - const visitor = toVisitorRelease(release); - return { - ...visitor, - body: visitor.body - .split('\n') - .map((line) => { - const bullet = line.match(/^([-*+]\s+)(.*)$/); - if (!bullet) return toVisitorChangelogTitle(line); - return `${bullet[1]}${toVisitorChangelogTitle(bullet[2])}`; - }) - .join('\n'), - }; - }); + return releases.map(toVisitorRelease); } export const GET: APIRoute = async () => { diff --git a/src/utils/chat-logic.test.ts b/src/utils/chat-logic.test.ts index 32a8b308..ce3b5900 100644 --- a/src/utils/chat-logic.test.ts +++ b/src/utils/chat-logic.test.ts @@ -101,5 +101,17 @@ describe('chat logic utilities', () => { expect(pruned).toHaveLength(1); expect(pruned[0].content).toBe('short'); }); + + it('drops leading assistant messages so pruned history begins with a user turn', () => { + const longContent = 'x'.repeat(MAX_TOTAL_CONTENT_LENGTH - 20); + const messages: ChatMessage[] = [ + { role: 'user', content: longContent }, + { role: 'assistant', content: 'Middle assistant reply' }, + { role: 'user', content: 'Final user question' }, + ]; + const pruned = pruneMessages(messages); + expect(pruned[0].role).toBe('user'); + expect(pruned[0].content).toBe('Final user question'); + }); }); }); diff --git a/src/utils/chat-logic.ts b/src/utils/chat-logic.ts index ce652b0e..51c29298 100644 --- a/src/utils/chat-logic.ts +++ b/src/utils/chat-logic.ts @@ -41,6 +41,11 @@ export function pruneMessages(messages: ChatMessage[]): ChatMessage[] { totalLength -= pruned.shift()!.content.length; } + while (pruned.length > 1 && pruned[0].role === 'assistant') { + // biome-ignore lint/style/noNonNullAssertion: loop guard ensures shift() returns an element + totalLength -= pruned.shift()!.content.length; + } + return pruned; } From a56b834e71ea9b9c3e880a7c305d5742163be539 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 02:42:27 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9A=99=EF=B8=8F=20Engine:=20Optimize=20c?= =?UTF-8?q?onversational=20history=20pruning=20&=20authenticated=20release?= =?UTF-8?q?=20fetching?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: alehar9320 <5390430+alehar9320@users.noreply.github.com> --- .jules/engine.md | 6 --- src/__tests__/releases-api.test.ts | 59 ++++++++++++++++++++++++++++++ src/pages/api/releases.ts | 4 +- 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 src/__tests__/releases-api.test.ts diff --git a/.jules/engine.md b/.jules/engine.md index e47fdc09..19b7698e 100644 --- a/.jules/engine.md +++ b/.jules/engine.md @@ -1,11 +1,5 @@ # ⚙️ Engine Journal -## 2025-05-21 - Conversational Turn Pruning & Authenticated Edge Data Fetching - -- **Architectural Shift & Model Context Hygiene:** Enhanced `pruneMessages` in `src/utils/chat-logic.ts` to automatically strip orphan leading `assistant` turns when conversation history is trimmed due to character/message limits. Ensures Workers AI LLM context windows always start cleanly with a `user` prompt turn. -- **Edge-Case Constraint & API Rate-Limit Safeguard:** Updated `src/pages/api/release-summary.ts` to pass `GITHUB_TOKEN` worker bindings to `fetchGitHubReleases`. Prevents unauthenticated GitHub API rate limiting (60 req/hr per edge worker IP) when fetching releases for AI summary generation. -- **Performance:** Streamlined `visitorReleases` in `src/pages/api/releases.ts` to avoid redundant re-parsing of release bodies. - ## 2025-05-14 - Centralized Chat Logic & Pruning - **Architectural Shift:** Centralized chat-related constants and logic into `src/utils/chat-logic.ts` to ensure consistency between the API and potential future client-side pruning. diff --git a/src/__tests__/releases-api.test.ts b/src/__tests__/releases-api.test.ts new file mode 100644 index 00000000..b5e946c1 --- /dev/null +++ b/src/__tests__/releases-api.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest'; +import { GET } from '../pages/api/releases'; +import * as githubReleases from '../utils/github-releases'; +import { LATEST_RELEASE_SNAPSHOT } from '../data/latest-release'; +import { toVisitorRelease } from '../utils/visitor-changelog'; + +type GetContext = Parameters[0]; + +function createContext(request = new Request('https://example.com/api/releases')) { + return { + request, + locals: {}, + } as unknown as GetContext; +} + +const mockRelease: githubReleases.SiteRelease = { + body: '- 41fe7ae feat: rewrite /experimental/now/ for visitors (#523)', + publishedAt: '2026-08-16T21:12:02Z', + title: '2026.08.16.2111', + url: 'https://github.com/alehar9320/astro-cloudflare-personal-website/releases/tag/2026.08.16.2111', + version: '2026.08.16.2111', +}; + +describe('releases API route', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('returns formatted visitor releases from GitHub', async () => { + vi.spyOn(githubReleases, 'fetchGitHubReleases').mockResolvedValue([mockRelease]); + + const response = await GET(createContext()); + expect(response.status).toBe(200); + expect(response.headers.get('content-type')).toBe('application/json'); + + const json = await response.json(); + expect(json).toEqual([toVisitorRelease(mockRelease)]); + }); + + it('falls back to LATEST_RELEASE_SNAPSHOT if GitHub releases are empty', async () => { + vi.spyOn(githubReleases, 'fetchGitHubReleases').mockResolvedValue([]); + + const response = await GET(createContext()); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual([toVisitorRelease(LATEST_RELEASE_SNAPSHOT)]); + }); + + it('handles errors gracefully by falling back to LATEST_RELEASE_SNAPSHOT', async () => { + vi.spyOn(githubReleases, 'fetchGitHubReleases').mockRejectedValue(new Error('GitHub error')); + + const response = await GET(createContext()); + expect(response.status).toBe(200); + + const json = await response.json(); + expect(json).toEqual([toVisitorRelease(LATEST_RELEASE_SNAPSHOT)]); + }); +}); diff --git a/src/pages/api/releases.ts b/src/pages/api/releases.ts index fbf16dbe..e55bd203 100644 --- a/src/pages/api/releases.ts +++ b/src/pages/api/releases.ts @@ -2,9 +2,9 @@ import type { APIRoute } from 'astro'; import { env } from 'cloudflare:workers'; import { LATEST_RELEASE_SNAPSHOT } from '../../data/latest-release'; import { fetchGitHubReleases, type SiteRelease } from '../../utils/github-releases'; -import { toVisitorChangelogTitle, toVisitorRelease } from '../../utils/visitor-changelog'; +import { toVisitorRelease } from '../../utils/visitor-changelog'; -export { toVisitorChangelogTitle }; +// toVisitorRelease formats each release using toVisitorChangelogTitle const jsonHeaders = { 'content-type': 'application/json',