Skip to content

feat: add LLM profiles API layer and React Query hooks#387

Merged
malhotra5 merged 3 commits into
mainfrom
feat/llm-profiles-api-hooks
May 12, 2026
Merged

feat: add LLM profiles API layer and React Query hooks#387
malhotra5 merged 3 commits into
mainfrom
feat/llm-profiles-api-hooks

Conversation

@malhotra5
Copy link
Copy Markdown
Member

Summary

This PR adds the foundational data layer for the LLM profiles feature. This is Part 1 (PR A) of the split plan for #210, focusing purely on the API and hooks layer with no UI changes.

Changes

API Layer

  • ProfilesService (src/api/profiles-service/profiles-service.api.ts): Thin wrapper around SDK's ProfilesClient with methods for:
    • listProfiles() - List all profiles
    • getProfile(name, exposeSecrets?) - Get profile details
    • saveProfile(name, request) - Create/update profile
    • deleteProfile(name) - Delete profile
    • renameProfile(name, newName) - Rename profile
    • activateProfile(name) - Activate a profile
  • Re-exports SDK types (ProfileInfo, ProfileListResponse, etc.) for consumer convenience

React Query Hooks

Hook Purpose
useLlmProfiles Query hook for listing all profiles
useSaveLlmProfile Mutation hook for creating/updating profiles
useDeleteLlmProfile Mutation hook for deleting profiles
useRenameLlmProfile Mutation hook for renaming profiles
useActivateLlmProfile Mutation hook for activating a profile

All mutation hooks:

  • Properly invalidate both profile list and settings caches on success
  • Disable global toasts (consumers handle errors via try-catch)
  • Include backend identity in query keys to prevent cache pollution when switching backends

Utilities

  • deriveProfileNameFromModel(model): Derives a clean profile name from model strings
    • Example: 'openai/gpt-4''gpt-4'
    • Sanitizes invalid characters, ensures alphanumeric start, truncates to 64 chars
  • PROFILE_NAME_PATTERN: Validation regex for profile names

Query Keys

  • Added LLM_PROFILES_QUERY_KEYS constant to centralized query keys

Tests

47 tests covering all new functionality:

Test File Tests
profiles-service.test.ts 10
use-llm-profiles.test.tsx 6
use-save-llm-profile.test.tsx 4
use-delete-llm-profile.test.tsx 3
use-rename-llm-profile.test.tsx 6
use-activate-llm-profile.test.tsx 2
derive-profile-name.test.ts 16

PR Split Plan

This is part of breaking down the large #210 into smaller, reviewable PRs:

PR Focus Status
A (this) API + Utils + Hooks + Tests 🔄 In Review
B UI Components + i18n Pending
C Route Integration Pending

Verification

npm run typecheck  # ✅ Pass
npm test -- --run __tests__/api/profiles-service.test.ts \
  __tests__/utils/derive-profile-name.test.ts \
  __tests__/hooks/query/use-llm-profiles.test.tsx \
  __tests__/hooks/mutation/*.test.tsx  # ✅ 47/47 Pass

This PR was created by an AI agent (OpenHands) on behalf of the user.

@malhotra5 can click here to continue refining the PR

This PR adds the foundational data layer for the LLM profiles feature:

## API Layer
- ProfilesService: Thin wrapper around SDK ProfilesClient with methods
  for list, get, save, delete, rename, and activate profile operations
- Re-exports SDK types for consumer convenience

## React Query Hooks
- useLlmProfiles: Query hook for listing all profiles
- useSaveLlmProfile: Mutation hook for creating/updating profiles
- useDeleteLlmProfile: Mutation hook for deleting profiles
- useRenameLlmProfile: Mutation hook for renaming profiles
- useActivateLlmProfile: Mutation hook for activating a profile

All mutation hooks properly invalidate both profile list and settings
caches on success, and disable global toasts (consumers handle errors).

## Utilities
- deriveProfileNameFromModel: Derives a clean profile name from model
  strings (e.g., 'openai/gpt-4' -> 'gpt-4')
- PROFILE_NAME_PATTERN: Validation regex for profile names

## Tests
- 47 tests covering all new functionality
- API service method tests
- Hook behavior tests (success, error handling, cache invalidation)
- Utility function tests

Part 1 of LLM Profiles feature (PR A from split plan).
No UI changes - this is purely a data layer addition.

Co-authored-by: openhands <openhands@all-hands.dev>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-canvas Error Error May 12, 2026 8:00pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

PR Artifacts Cleaned Up

The .pr/ directory has been removed after approval.

Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid foundational work with comprehensive test coverage (47 tests). Found some test quality gaps and consistency issues that should be addressed before merge. The production code looks good overall.

Comment thread src/api/profiles-service/profiles-service.api.ts Outdated
Comment thread __tests__/api/profiles-service.test.ts
Comment thread __tests__/hooks/query/use-llm-profiles.test.tsx
Comment thread __tests__/hooks/query/use-llm-profiles.test.tsx
Comment thread __tests__/utils/derive-profile-name.test.ts Outdated
Comment thread src/hooks/mutation/use-save-llm-profile.ts Outdated
Comment thread __tests__/utils/derive-profile-name.test.ts
Comment thread __tests__/hooks/query/use-llm-profiles.test.tsx
- Remove client.close() calls for consistency with other services
  (SettingsService, SecretsService don't call close())
- Use SETTINGS_QUERY_KEYS.personal() instead of .all for precision
- Add ActiveBackendProvider wrapper in useLlmProfiles tests
- Add test for query key including backend.id and orgId
- Add test for backend-switch cache isolation
- Fix truncation test to actually exercise trailing-dash removal
- Add test for model names that sanitize to empty string

Addresses review feedback from all-hands-bot.

Co-authored-by: openhands <openhands@all-hands.dev>
Copy link
Copy Markdown
Member Author

Addressed all 8 review comments in ae83e9a. Ready for another review.

@malhotra5 malhotra5 requested a review from all-hands-bot May 12, 2026 19:56
Copy link
Copy Markdown
Contributor

@all-hands-bot all-hands-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid foundational work with comprehensive test coverage. All previous review issues have been addressed. The API layer follows existing service patterns, hooks properly manage cache invalidation, and the 47 tests cover real behavior (not just mock assertions).

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟢 LOW

Additive changes only (new files, nothing imports them yet). Well-tested, follows established patterns (thin SDK wrapper, React Query hooks). No breaking changes, security issues, or complexity concerns. Safe to merge as Part 1 of the feature split.

@malhotra5 malhotra5 merged commit 919d705 into main May 12, 2026
5 of 6 checks passed
@malhotra5 malhotra5 deleted the feat/llm-profiles-api-hooks branch May 12, 2026 20:19
rbren added a commit that referenced this pull request May 12, 2026
npm normalizes the `github:OpenHands/typescript-client#sha` shorthand
(and even an explicit `git+https://github.com/...` URL) to
`git+ssh://git@github.com/...` whenever it rewrites package-lock.json
during a plain `npm install`. Vercel's build environment has no GitHub
SSH key, so an ssh-pinned lockfile causes Vercel to fall back to a stale
cached copy of the package whose dist/clients.js predates the addition
of ConversationClient, FileClient, and SharedClient. Rolldown then
fails the build with:

  [MISSING_EXPORT] ConversationClient is not exported by
  node_modules/@openhands/typescript-client/dist/clients.js

PR #382 fixed this once by hand-editing the lockfile, but the very next
local `npm install` (e.g. PR #387 bumping React Query hooks) silently
rewrote the resolved URL back to ssh and the bug returned.

This change makes the Vercel build self-healing:

* package.json now pins the dep as an explicit `git+https://` URL so
  the intent is documented in one place.
* package-lock.json's top-level dep spec matches that URL; the nested
  `node_modules/@openhands/typescript-client` entry already resolved
  to https, so this brings both halves of the lockfile in sync.
* vercel.json sets `installCommand` to `bash scripts/vercel-install.sh`,
  which:
    - rewrites any leftover `git+ssh://git@github.com/` resolved URLs
      back to https (handles future regressions),
    - configures `git config --global url."https://github.com/".insteadOf`
      for both `ssh://git@github.com/` and `git@github.com:` (handles
      anything npm has already normalized in cache),
    - then runs `npm ci` for a strict, lockfile-driven install.

Locally verified:

* `bash scripts/vercel-install.sh` produces a clean install with the
  https-resolved typescript-client.
* `npm run build` and `npm run lint` both succeed after the install.
* Re-running `npm install` rewrites `resolved` back to `git+ssh` as
  expected — the install script normalizes it again on every Vercel
  build, so the lockfile drift no longer breaks deploys.

Refs: #384 (Vercel preview build fails: MISSING_EXPORT for SharedClient
/ ConversationClient / FileClient).

Co-authored-by: openhands <openhands@all-hands.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants