Commit graph

27 commits

Author SHA1 Message Date
Garfie
92192498ca feat(ui): render custom endpoint iconURL in drawer and conversation list
Render `EndpointConfig.iconURL` in drawer + conversation list rows with
per-conversation override via `Conversation.iconURL`. Extract shared
`EndpointIcon` composable to `:core:ui`. Eager `/api/config` fetch via
`EndpointConfigFetchSessionTask` so cold-start has icons. Clear config
cache on logout to prevent cross-user leak.

Closes #68.
2026-04-29 20:19:11 -06:00
Garfie
f0d605a932 chore(data): replace normalizeEndpoint shim with Migration(3, 4) (#69)
Pre-#67 builds wrote Conversation.endpoint as the Kotlin enum `.name`
(e.g. "OPENAI"). #67 added a read-side shim in ConversationMapper that
converted those legacy rows back to the wire-format SerialName on read.
Replace the shim with a one-time data migration so the conversion runs
once per user on db upgrade and the read path is plain.

Bump database to v4 and register MIGRATION_3_4 on both Android and iOS
builders. The migration runs nine UPDATE statements per affected column
(conversations.endpoint, conversations.endpointType, presets.endpoint).
The presets table is migrated defensively — PresetDao currently has no
production callers, so the column is empty in the field, but symmetry
is enforced if a future change starts persisting presets.

Schema v4.json is structurally identical to v3.json; identityHash is
unchanged (b4117797915eafc13b5bd956677f83bf). Only the version field
differs.

Pre-#67 ChatPayloadBuilder coerced any unknown endpoint name to
EModelEndpoint.AGENTS, so a legacy "AGENTS" row may have originated
from any custom endpoint. The migration rewrites "AGENTS" -> "agents",
preserving the same lossy outcome the read-side shim already produced.
A dedicated test pins this contract so a future "smart" migration can't
silently change cache/fresh consistency.

Verified via :core:data:connectedDebugAndroidTest (5 tests, all green:
existing v1->v3 helper test, v1->v4 Room API test, v3->v4 endpoint
normalization, v3->v4 lossy AGENTS, v3->v4 preset normalization).
2026-04-28 22:03:36 -06:00
Garfie
2b44937368 fix(chat): wire format for custom and user_provided endpoints (#60)
ChatPayloadBuilder caught EModelEndpoint.valueOf for custom endpoint
names (OpenRouter, Deepseek, etc.) and silently sent endpoint=agents,
producing 403 "Forbidden: Insufficient permissions" on the server.

Type endpoint identity as String end-to-end, add EndpointClassifier to
derive endpointType / key / modelDisplayLabel from /api/config, and
propagate the dispatch fields to all five chat-send sites in
ChatViewModel (doSendMessage, editUserMessage, editAiMessage,
regenerateMessageNow, continueGenerationNow). A normalization shim in
ConversationMapper converts legacy enum-name rows in the Room cache
back to wire format on read.

Verified end-to-end against a docker LibreChat v0.8.5 instance with
OpenRouter user_provided keys; built-in .env Anthropic regression also
passes.
2026-04-28 20:21:28 -06:00
Garfie Chiu
4454324b6e
Merge pull request #65 from garfiec/fix/repo-cache-mutex
fix(data): mutex guard repository caches against suspension races
2026-04-27 22:06:31 -06:00
Garfie
ac125bb24c fix(data): mutex guard repository caches against suspension races
AgentRepositoryImpl.cachedAgents and PresetRepositoryImpl.cachedPresets
are read-checked, fetched across a suspension point, then written. With
@Volatile alone, two coroutines can both observe null, both issue the
network request, and both write the cache — a redundant API call per
cold-cache race. Wrap read-check-write in a Mutex so at most one fetch
runs per cold cache; invalidateCache() becomes suspend to match.
2026-04-27 18:32:07 -06:00
Garfie
e2e3423dcb fix(data): mutex guard clearTokens vs refreshAccessToken race
Wraps `CommonTokenDataStore.clearTokens()` in the existing `refreshMutex`,
serializing logout against any in-flight `refreshAccessToken()` call
(which already holds the same mutex for its full HTTP duration). Without
this, a refresh completing successfully after `clearTokens()` would
silently resurrect the just-killed session by writing a fresh token to
disk. A private `clearTokensLocked()` helper holds the inner clear logic
and is called from inside `refreshAccessToken()` to avoid self-deadlock
on the non-reentrant mutex.
2026-04-27 18:32:06 -06:00
Garfie
abde86ca4e chore(sync): sync to upstream LibreChat v0.8.5
Targets upstream tag v0.8.5 (commit 9ccc8d9be). Bumps the supported
backend version constant, advances the upstream submodule pin, and
adds wire-protocol parity for new server features.

Wire protocol
- Add ContentType.SUMMARY enum value and flat sibling fields on
  MessageContentPart (content as JsonElement to absorb array/string/
  legacy-text variants per BaseClient.getSummaryText).
- Add Anthropic effort.xhigh dropdown value, ThinkingDisplay (Opus 4.7
  reasoning visibility), and Message.contextMeta pass-through.
- Add allowAccountDeletion to StartupConfig (defaults to true to
  preserve older-server behavior).
- Drop unused instanceProjectId from StartupConfig and from
  ConfigRepositoryImpl.isValidLibreChatConfig.

New endpoints / features
- Favorites: UserFavorite DTO + FavoritesApi + FavoritesRepository,
  Settings -> Favorites sub-screen (list/unpin), pin icons in chat
  model selector. Repository owns the canonical list as a StateFlow
  guarded by Mutex.withLock so chat-side toggles and settings-side
  bulk-unpin can't drift apart and concurrent writes can't lose each
  other.
- Prompt-group usage telemetry (POST /api/prompts/groups/:id/use,
  fire-and-forget on prompt insertion).
- Summarize SSE events (on_summarize_complete -> StreamEvent.ContextSummary)
  and a collapsible "Summarized earlier messages" card rendered from
  the persisted SUMMARY content part. Forward-compat else-arm preserved
  on both LangGraph and legacy SSE mappers.

Backward compatibility
- BackendVersion.isCompatibleOrNewer helper added; documented fail-open
  contract for callers that have already null-checked
  detectedBackendVersion.
- New VERSION_GATES.md catalog at repo root tracks every code path that
  branches on server version. Initial entries:
  - isCollaborative agent toggle (hidden on v0.8.5+; full-ACL UX deferred)
  - xhigh effort dropdown value (filtered out on <v0.8.5 to avoid
    server-side enum rejection)

Defensive hardening
- ChatApi.startChat asserts Content-Type: application/json before
  decoding ChatStartResponse and catches NoTransformationFoundException
  as a backstop, translating both to a friendly ApiException. Observed
  during testing on v0.8.5-rc1 with summarization enabled, where the
  agents chat-start endpoint returned 200 OK with no Content-Type and
  Ktor leaked a NoTransformationFoundException stack trace into the
  chat surface. Belt-and-suspenders against future backend regressions
  or unsupported-server pointing.

Documentation
- DISCOVERY.md updated for the v0.8.5 surface: /api/endpoints now
  requires JWT, /api/config pre-auth/post-auth split, allowAccountDeletion,
  instanceProjectId removal, favorites endpoints (with all three XOR
  variants: agentId | model+endpoint | spec), prompt-usage endpoint,
  /api/admin/** marked out-of-scope. Plus a new MessageContentPart +
  SUMMARY wire-shape section.

iOS
- Swift SharedFrameworkTest.swift updated for the new
  StreamEvent.ContextSummary variant and Message.contextMeta
  constructor argument.

Out of scope (by design)
- /api/admin/** route family - web-only feature.
- Full ACL migration for agent sharing - replaced by version-gating
  the legacy isCollaborative toggle.
- Spec-pinning UI on mobile - UserFavorite.spec round-trips for
  fidelity with web-authored pins, but mobile does not yet surface
  a spec picker.
2026-04-26 23:48:14 -06:00
Garfie
6fde5c55cb fix(permissions): gate mobile surfaces on server role permissions (#49) 2026-04-24 21:21:05 -05:00
Garfie
9159bcf62e fix(cache): route Android temp files into named subdirs for logout sweep
Five Android producers wrote temp files directly into cacheDir root,
bypassing CommonSessionCacheCleaner which only sweeps entries listed in
CACHE_SUBDIRECTORIES. Moved each producer to its own named subdir and
registered the new names so logout actually clears them.
2026-04-14 21:30:31 -05:00
Garfie Chiu
4814f8a5b5
Merge pull request #40 from garfiec/fix/camera-photos-sweep
fix(data): sweep camera_photos cache dir on logout
2026-04-14 19:57:21 -05:00
Garfie Chiu
7433f75187
Merge pull request #39 from garfiec/fix/volatile-agent-preset-caches
fix(data): mark agent/preset caches @Volatile
2026-04-14 19:56:01 -05:00
Garfie
6f2b397b9a fix(data): mark in-memory repository caches @Volatile for cross-thread visibility 2026-04-14 19:46:59 -05:00
Garfie
5b94495197 fix(data): sweep camera_photos cache dir on logout
The Android camera-capture flow in ChatInput writes temp photos to
cacheDir/camera_photos/ but this subdir was missing from
CACHE_SUBDIRECTORIES, so captured photos leaked across user sessions.
Add it to the sweep list so CommonSessionCacheCleaner clears it on
logout.
2026-04-14 18:11:48 -05:00
Garfie
4a6c1f9a29 fix(ios): stream SSE via NWConnection to bypass NSURLSession text/* buffering 2026-04-14 07:21:48 -05:00
Garfie
0483581f23 feat(favorites): move bookmarks to server-backed tags with offline cache 2026-04-13 19:40:14 -05:00
Garfie
63d87c843b cleanup: burn down detekt baselines and rename Compose Resources package 2026-04-11 12:35:19 -05:00
Garfie
b4872e4de0 feat: add detekt-koin, .editorconfig, GitHub Actions CI for android + ios 2026-04-09 23:48:41 -05:00
Garfie
e8bda6ca05 refactor: replace fully-qualified names with proper imports across codebase 2026-04-08 19:15:49 -05:00
Garfie
00055c515e refactor: extract supporting types to own files and organize into sub-packages 2026-04-07 21:49:15 -05:00
Garfie
21825a4943 fix: create Application Support directory before opening Room database on iOS 2026-04-05 15:42:34 -05:00
Garfie
00056b634b chore: rename project from LibreChat Android to LibreChat Mobile 2026-04-03 16:56:36 -05:00
Garfie
e5c5b549db chore: rename package com.librechat to com.garfiec.librechat 2026-04-03 16:12:37 -05:00
Garfie
770603e466 KMP iOS migration
Complete Kotlin Multiplatform migration with iOS parity:
- All 13 modules converted to KMP (commonMain + androidMain + iosMain)
- 100% feature parity: auth, chat, SSE streaming, conversations,
  settings, agents, files, voice input, TTS, OAuth, file picker,
  clipboard paste
- Platform consolidation: DataStore path, SessionCacheCleaner,
  bubble layouts, ChatInput core, MIME types, mention parsing,
  TextField styling, FilterChip components extracted to commonMain
- iOS crash reporting via Kotlin/Native exception hook
- YAML parsing via custom pure-Kotlin parser (zero dependencies)
- Dependency health: Timber removed, kaml replaced, FuzzyWuzzy removed,
  security-crypto stabilized
- CONTRIBUTING.md
- Gradle configuration cache enabled
2026-04-02 19:54:58 -05:00
Garfie
1868bab690 Add linting and static code analysis 2026-03-28 12:49:37 -05:00
Garfie
e4effcf40f Migrate DI from Hilt to Koin 4.1 2026-03-28 09:05:14 -05:00
Garfie
d30988f8b4 Sync upstream to v0.8.4
Breaking changes:
- 2FA enable changed from GET to POST with OTP verification body
- Backup code regeneration now requires OTP verification
- Account deletion requires OTP when 2FA is enabled
- New shared OtpVerificationDialog component in core/ui
- Fix OTP digit box layout (weight-based instead of fixed size)

Additive changes:
- Add effort (AnthropicEffort) and thinkingLevel (Google) fields to
  Conversation, Preset, and ChatRequest models
- Add tool_options to Agent model and create/update requests
- Add artifacts field to EphemeralAgent
- Add remoteAgents to InterfaceConfig
- Add programmatic_tools and deferred_tools agent capabilities
- Add thinkingLevel parameter to EndpointParameterRegistry

SSE hardening:
- Implement mapSyncEvent() for resume state-snapshot protocol
- Preserve partial stream content on disconnect (don't wipe visible text)
- Persist conversation to Room on stream creation (not just on Final)
- Auto-reconnect SSE stream when network recovers via ConnectivityObserver
- Network-aware retries in SseClient (wait for connectivity, don't burn attempts)
- Add StreamEvent.Retrying for retry progress UI feedback
- Lazy connectivity observation (only when network error occurs)
- Clear draft/streaming state on SSE stream expiry
- Add isNetworkError flag to StreamEvent.Error for typed detection

Code quality:
- Remove [Comparison] debug Timber logs (perf overhead on every SSE event)
- Replace Log.d with Timber.d across feature modules
- Extract hardcoded OTP dialog strings to strings.xml for localization
- Parameterize OtpVerificationDialog labels for localizability
- Add auto-submit guard with reset on failure for OTP input
- Replace fake QR code generator with copyable otpauth:// URI
- Replace fragile OTP string matching with HTTP status code checks
- Consolidate identical request classes into OtpVerificationRequest
- Share isHttpStatus helper across settings module
- Remove duplicate verifyTempToken method (identical to verifyTwoFactor)
- Remove duplicate 2FA dialog composables from AccountSettingsScreen
- Extract shared resume stream logic to reduce duplication
- Guard no-op retryInfo state updates
- Move ApiException to core:common for cross-module access
- Document SseEventMapper single-stream threading constraint

Bump SUPPORTED_BACKEND_VERSION to 0.8.4, advance upstream submodule.
2026-03-26 17:33:41 -05:00
Garfie
f182b2b82a Initial release 2026-03-09 22:00:00 -05:00