fix(server/providers): harden and correct session history normalization/pagination

Address correctness and safety issues in provider session adapters while
preserving existing normalized message shapes.

Claude sessions:
- Ensure user text content parts generate unique normalized message ids.
- Replace duplicate `${baseId}_text` ids with index-suffixed ids to avoid
  collisions when one user message contains multiple text segments.

Cursor sessions:
- Add session id sanitization before constructing SQLite paths to prevent
  path traversal via crafted session ids.
- Enforce containment by resolving the computed DB path and asserting it stays
  under ~/.cursor/chats/<cwdId>.
- Refactor blob parsing to a two-pass flow: first build blobMap and collect
  JSON blobs, then parse binary parent refs against the fully populated map.
- Fix pagination semantics so limit=0 returns an empty page instead of full
  history, with consistent total/hasMore/offset/limit metadata.

Gemini sessions:
- Honor FetchHistoryOptions pagination by reading limit/offset and slicing
  normalized history accordingly.
- Return consistent hasMore/offset/limit metadata for paged responses.

Validation:
- eslint passed for touched files.
- server TypeScript check passed (tsc --noEmit -p server/tsconfig.json).
This commit is contained in:
Haileyesus
2026-04-21 13:24:52 +03:00
parent d297dd2271
commit 1d885076e9
3 changed files with 81 additions and 33 deletions

View File

@@ -113,8 +113,10 @@ export class GeminiSessionsProvider implements IProviderSessions {
*/
async fetchHistory(
sessionId: string,
_options: FetchHistoryOptions = {},
options: FetchHistoryOptions = {},
): Promise<FetchHistoryResult> {
const { limit = null, offset = 0 } = options;
let rawMessages: AnyRecord[];
try {
rawMessages = sessionManager.getSessionMessages(sessionId) as AnyRecord[];
@@ -208,12 +210,18 @@ export class GeminiSessionsProvider implements IProviderSessions {
}
}
const start = Math.max(0, offset);
const pageLimit = limit === null ? null : Math.max(0, limit);
const messages = pageLimit === null
? normalized.slice(start)
: normalized.slice(start, start + pageLimit);
return {
messages: normalized,
messages,
total: normalized.length,
hasMore: false,
offset: 0,
limit: null,
hasMore: pageLimit === null ? false : start + pageLimit < normalized.length,
offset: start,
limit: pageLimit,
};
}
}