Skip to content

fix(desktop): delete legacy custom providers - #69258

Open
SacrEllfarch wants to merge 3 commits into
NousResearch:mainfrom
SacrEllfarch:fix/delete-legacy-custom-providers
Open

fix(desktop): delete legacy custom providers#69258
SacrEllfarch wants to merge 3 commits into
NousResearch:mainfrom
SacrEllfarch:fix/delete-legacy-custom-providers

Conversation

@SacrEllfarch

@SacrEllfarch SacrEllfarch commented Jul 22, 2026

Copy link
Copy Markdown

Summary

  • surface valid legacy custom_providers: entries in Desktop Custom Endpoints settings
  • render legacy entries as read-only Legacy config rows with a Delete action
  • delete the exact legacy list entry through a source-qualified, path-safe management ID
  • clear active model routing credentials when the deleted endpoint is in use
  • deduplicate equivalent modern and legacy rows for Desktop display without mutating the other schema

Problem

The Desktop model picker reads the compatibility custom_providers: schema, but the Custom Endpoints management API only listed and deleted the newer providers: schema. A provider could therefore remain selectable while being invisible and undeletable in Settings.

Scope boundary

This PR owns the Desktop/API path only. The source-qualified DELETE contract handles legacy custom_providers rows, while the existing modern DELETE path remains scoped to providers entries. Listing may deduplicate equivalent rows for the Desktop view, but deleting a modern row does not delete a legacy row. CLI removal remains separate in #14360 and this PR does not close #5525.

Baseline on 9ecacd6bf with a synthetic Legacy Proxy config:

management_endpoint_count = 0
picker_compat_provider_count = 1
delete_status = 404

After this change:

management_endpoint_count_before = 1
picker_compat_provider_count_before = 1
delete_status = 200
management_endpoint_count_after = 0
picker_compat_provider_count_after = 0

Legacy management IDs include the config-list position and a non-secret identity fingerprint. The DELETE contract includes the source schema, avoiding collisions with modern provider keys, colliding legacy runtime slugs, and names containing /. URL matching normalizes only scheme, hostname, default ports, and trailing slashes; case-sensitive paths, queries, and model IDs remain distinct. URL credentials and fragments are excluded from management identity.

Compatibility with #71141

This branch is rebased on the merged #71141 credential/model-catalogue work. It preserves .env-backed key_env storage and model catalogue persistence, reports both key_env and legacy api_key_env references without resolving their values, and removes the derived modern endpoint env entry when a modern endpoint is deleted. Deleting a legacy custom_providers row deliberately does not remove an arbitrary user-owned environment variable.

When an active endpoint is removed, the model mirror cleanup covers inline and environment-backed credential fields plus protocol fields (api_mode and canonical transport) so stale routing state is not left behind.

Testing

  • final head 3cf53fd46 is rebased on upstream/main 98105f31f
  • final-head custom-endpoint backend selection: 7 passed
  • final-head Desktop settings/helper tests: 22 passed
  • temporary combined integration with fix(desktop): add custom endpoint API mode #69824: focused backend 25 passed, Desktop settings/helper 28 passed, full tests/hermes_cli/test_web_server.py 125 passed
  • combined full Desktop Vitest: 432 passed, 1 skipped test files; 4038 passed, 2 skipped tests
  • Desktop typecheck, changed-file Prettier, production build, and git diff --check: passed
  • ESLint: 0 errors (74 existing upstream warnings)
  • the final upstream commit after combined verification changes only tools/file_operations.py and its tests; the PR-relevant source/test tree is byte-identical
  • signed commit verified after the final rebase

This complements the CLI/config work in #14360 by covering the Desktop REST and GUI path only. Related settings design discussion: #68591.

Copilot AI review requested due to automatic review settings July 22, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes Desktop Custom Endpoints management so legacy custom_providers: entries (still consumed by the Desktop model picker via compatibility logic) are surfaced in the settings UI and can be deleted safely, including cleanup of active model routing credentials when the deleted endpoint is in use.

Changes:

  • Extend the Desktop custom endpoints REST surface to list legacy custom_providers entries, mark them read-only in the UI, and support source-qualified deletion.
  • Add path-safe, source-qualified identifiers for legacy list entries and deduplicate equivalent modern/legacy rows to avoid “invisible but selectable” endpoints.
  • Add Python + Vitest coverage for legacy listing, deletion, credential scrubbing, and collision/ambiguity handling.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/hermes_cli/test_web_server.py Adds coverage for listing/dedup/deleting legacy custom_providers endpoints and credential scrubbing behavior.
hermes_cli/web_server.py Implements legacy row projection + identity/dedup logic and extends DELETE to support both schemas with source-qualified deletion.
apps/desktop/src/hermes.ts Updates the Desktop REST helper to use a query-based DELETE route for legacy endpoints.
apps/desktop/src/hermes.test.ts Adds coverage that the correct DELETE route is chosen based on endpoint source.
apps/desktop/src/app/settings/custom-endpoints-settings.tsx Renders legacy endpoints as read-only “Legacy config” rows with a delete action and refreshes after deletion.
apps/desktop/src/app/settings/custom-endpoints-settings.test.tsx Adds UI tests ensuring legacy rows are delete-only and modern/direct-config rows retain existing behaviors.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hermes_cli/web_server.py Outdated
Comment thread hermes_cli/web_server.py
@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch from 55d7af3 to d48e177 Compare July 22, 2026 11:09
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/desktop Electron desktop app (apps/desktop/*) comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades needs-decision Awaiting maintainer decision before any implementation labels Jul 22, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #14360: both address deletion across the legacy and modern custom-provider schemas. This PR adds the Desktop/API visibility and source-qualified legacy-management path, while #14360 currently has a separate CLI reachability gap; please choose or consolidate the approach.

@SacrEllfarch

SacrEllfarch commented Jul 22, 2026

Copy link
Copy Markdown
Author

Thanks for connecting these two PRs and for pointing out the overlap. I took a closer look at the current head of #14360 and the latest main branch.

I think the clearest path for now is to keep the PRs separate, since they cover different user-facing entry points and currently do not overlap at the file level. #69258 is intentionally focused on Desktop/API visibility and deletion, while #14360 is focused on the CLI flow reported in #5525. This PR does not intend to close that issue.

To make that boundary explicit, I have narrowed #69258 so the modern providers deletion path no longer removes matching legacy entries. The Desktop API still deduplicates equivalent rows for display, and the source-qualified route handles deletion of an individual legacy custom_providers entry without taking over the modern or CLI deletion paths.

From what I can see, #14360 may still need a small follow-up for the current CLI path: the removal action is gated on the legacy custom_providers list, so a providers:-only configuration cannot currently reach the updated helper. Its regression test also calls the helper directly and uses the former menu setup rather than exercising the current curses_radiolist entry path.

My preference would therefore be to keep #69258 scoped to Desktop/API and let #14360 complete the CLI entry-point coverage independently. If maintainers would prefer one shared deletion primitive, I would be happy to coordinate a follow-up config-layer extraction with a common, source-safe identity contract.

@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch from d48e177 to 40e3b17 Compare July 23, 2026 02:24
@eiritsu

eiritsu commented Jul 23, 2026

Copy link
Copy Markdown

Re: PR #68591 coordination

Hi @SacrEllfarch, I noticed our PRs have some overlap in the Custom Endpoints area. I wanted to reach out to discuss how we can coordinate.

PR #68591 (my PR)

My PR takes a different approach:

  • Deletes (standalone page)
  • Adds a compact at the bottom of the API Keys page
  • Removes single-endpoint routes, uses only plural routes (aligned with upstream main)
  • Focus: simplify the UI, one card instead of a full page

PR #69258 (your PR)

Your PR:

  • Modifies to support legacy custom_providers:
  • Adds read-only rows for legacy entries with Delete action
  • Adds tests for legacy management
  • Focus: legacy provider visibility and deletion

Conflict

Proposed coordination

Would you be open to merging the approaches?

Option A: Use #68591's UI (compact card) + #69258's legacy support

Option B: Keep #69258's UI (full page) + #68591's route cleanup

  • Keep with your legacy support
  • But align with upstream main (plural routes only)

Option C: Merge #69258 first, then I'll rebase #68591

What do you think? Happy to collaborate on a combined solution.


Related: PR #68591, PR #69258, Issue #14360, Issue #5525

@eiritsu

eiritsu commented Jul 23, 2026

Copy link
Copy Markdown

补充说明(上一条评论反引号被解析了):

PR #68591 方案

  • 删除 custom-endpoints-settings.tsx 独立页面
  • 在 API Keys 页面底部添加 CustomEndpointKeyCard 卡片
  • 删除单数路由,只用复数路由(与上游 main 一致)
  • 简化 UI,一个卡片替代整个页面

PR #69258 方案

  • 修改 custom-endpoints-settings.tsx 支持 legacy custom_providers
  • 添加只读行显示 legacy 条目,支持删除
  • 添加测试
  • 关注 legacy 提供商的可见性和删除

冲突点

建议方案

期待你的反馈!

@SacrEllfarch

SacrEllfarch commented Jul 23, 2026

Copy link
Copy Markdown
Author

感谢你主动联系并详细整理这些协调方案。#68591 将 Custom Endpoint 入口整合到 API Keys 页面的思路确实更紧凑,也有助于提升入口的可发现性。

综合目前两个 PR 的范围和现有功能,我更倾向于 Option B:保留当前完整的 Custom Endpoints 页面,同时确保接口继续统一使用复数 /api/providers/custom-endpoints 路由。

我选择保留完整页面,主要基于以下几点:

  1. 当前页面不仅用于填写 API Key 和 Base URL,还承担多个端点的列表展示、新增、编辑、模型发现、连通性测试、激活切换和删除。#69258 是在这个既有工作流上补齐 legacy custom_providers 的可见性和删除能力,并没有引入新的页面结构。
  2. #69258 将 legacy 条目显示为只读,是为了避免 Desktop 在不了解 legacy 配置全部字段的情况下重写或丢失用户手工配置;用户仍可以准确删除对应条目。
  3. #69824 正在同一个页面上补充 Auto / Chat / Responses / Messages API compatibility mode。如果现在移除完整页面,这部分能力还需要重新设计和整合。
  4. 保留页面不会阻止后续采用更紧凑的入口。未来仍然可以把入口放到 API Keys 页面,同时继续使用现有的多端点管理页面或复数 REST API。

关于路由,我重新检查了 #68591 当前 head。第二个提交已经撤回了单数 /api/providers/custom-endpoint API,现在两个 PR 都使用现有的复数 /api/providers/custom-endpoints 接口。因此,#69258 并不依赖将被删除的单数路由,双方在这一点上实际上已经基本对齐。

#69258 对现有 modern endpoint 路径保持兼容:

  • 列表和保存继续使用 /api/providers/custom-endpoints
  • modern endpoint 继续使用现有的 DELETE /api/providers/custom-endpoints/{id}
  • legacy 条目由于没有稳定且路径安全的 provider ID,使用 source-qualified 的 DELETE /api/providers/custom-endpoints?endpoint_id=...&source=custom_providers

这个额外的 legacy DELETE 形式是为了区分 modern provider key、重名 legacy 条目以及名称中包含 / 的配置,不会改变现有 modern endpoint 的行为。

因此我建议按 Option B 协调:#69258 继续保持当前聚焦的 legacy 管理修复,双方只在确有差异的客户端 helper 或复数路由细节上进行统一,而不在这个修复 PR 中同时完成页面重构。

如果后续继续推进紧凑卡片方案,我建议在 rebase 后明确保留或重新承载以下能力:多个端点、legacy 条目管理、端点测试与切换,以及 #69824 增加的 API mode。这样界面方向可以独立讨论,也不会让当前的缺陷修复承担额外的产品决策。

再次感谢协调,也欢迎维护者对最终的界面方向和合并顺序给出意见。

eiritsu added a commit to eiritsu/hermes-agent that referenced this pull request Jul 24, 2026
Adds a compact inline card under the LocalEndpointRow in the API Keys page,
giving users a quick-edit affordance for their active custom endpoint's
base_url and api_key without leaving the page.

- Calls plural /api/providers/custom-endpoints (aligned with upstream main)
- Falls back to the existing CustomEndpointsSettings full page for advanced
  flows (new/edit multiple, API compatibility mode, legacy entries)
- Sits on top of NousResearch#69258 which adds legacy custom_providers management to
  the full page
@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch from 40e3b17 to 686c5fe Compare July 28, 2026 03:24

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for covering the Desktop/API path separately from the CLI work; current main does leave legacy custom_providers runtime-reachable but absent from this management API (hermes_cli/web_server.py:7672-7712, hermes_cli/runtime_provider.py:748-780).

Problems

  • hermes_cli/web_server.py:7717 returns the raw legacy base_url. Although _canonical_endpoint_url() excludes URL userinfo from the management-ID fingerprint (hermes_cli/web_server.py:7637-7662), it does not sanitize the returned row. A https://user:secret@host/v1 legacy URL is then rendered directly by apps/desktop/src/app/settings/custom-endpoints-settings.tsx:79.

Suggested changes

  • Return a display-safe legacy URL with credentials and fragments removed, without changing the raw value used for matching/deletion, and cover that response/rendering invariant with a credential-bearing URL fixture.

This is an automated hermes-sweeper review.

Comment thread hermes_cli/web_server.py Outdated
"id": _legacy_custom_endpoint_id(index, name, base_url, model),
"_config_index": index,
"name": name,
"base_url": base_url,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This returns the raw legacy URL. _canonical_endpoint_url() strips userinfo only for the management ID, so a configured https://user:secret@host/v1 is exposed by this API and rendered in Desktop. Please return a display-safe URL without userinfo/fragments and add a response-level regression test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for catching this. Fixed in 3ef0b4ff0590aa5f4d162d8413089de0d82d3990. Legacy rows now expose a display-safe base_url with URL userinfo and fragments removed, while the raw configured URL is retained internally for identity, matching, active-model cleanup, and deletion. Added a response-level regression test covering a credential-bearing URL and verified that the stored configuration remains unchanged. The focused web-server tests pass.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 30, 2026
@alt-glitch alt-glitch removed the sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data label Jul 30, 2026
@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch 2 times, most recently from 3cf53fd to 34393fc Compare August 3, 2026 08:13
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Seven PRs address or reference this three-issue complex: #5549, #14360, and #63287 implement dual-schema CLI removal; #7658 adds provider editing; #57583 sorts model choices; #62160 hardens null legacy URLs; and #69258 adds a distinct Desktop/API legacy-management path. The CLI removal candidates address the schema mismatch behind #5525, while #69258 makes legacy entries visible and individually deletable in Desktop but does not repair the reported CLI entry point.

Related pull requests

Duplicates

#5549, #14360, and #63287 substantially overlap on dual-schema CLI provider removal; #63287 was explicitly closed in favor of #14360, while #5549 remains a closed reference implementation. #69258 is related but not a duplicate because it owns the Desktop/API path.

Suggested consolidation

Keep #69258 open with a salvage path: retain its source-qualified Desktop/API legacy listing and deletion design, but satisfy the contributor keep_open review by returning a display-safe URL without userinfo or fragments while preserving the raw value for matching/deletion, and add response-level regression coverage. Keep #14360, #7658, #57583, and #62160 as separate issue-specific work; leave #5549 closed as a reference and #63287 closed as superseded by #14360.

Cross-PR triage: Reviewed 7 pull requests and 3 issues in this complex. Each diff was read against this issue; Assessment working set: 90 kB of PR diffs, 17 kB of issue/PR text, 10 kB of discussion (15 comments), 12 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch from 34393fc to 3ef0b4f Compare August 4, 2026 07:57
@SacrEllfarch
SacrEllfarch force-pushed the fix/delete-legacy-custom-providers branch from 3ef0b4f to c3e45b2 Compare August 12, 2026 10:44
@SacrEllfarch

Copy link
Copy Markdown
Author

Rebased this branch onto current main and resolved the Desktop/API conflicts. The legacy DELETE route now participates in the newer profile-scoped config flow, including a regression that verifies deletion only mutates the requested profile. The display-safe URL behavior from 3ef0b4ff remains intact: Desktop receives URLs without userinfo/fragments while raw configured URLs stay internal for identity, matching, active-model cleanup, and deletion.\n\nFresh local verification on the rebased head: focused backend tests 162 passed, 1 skipped; Desktop Vitest 25 passed; Desktop typecheck and targeted ESLint passed. All rewritten commits retain valid SSH signatures.

@SacrEllfarch
SacrEllfarch requested a review from teknium1 August 13, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]:Remove a saved custom provider does not work.

6 participants