Skip to content

feat(serve): remove LLM endpoints over the management API - #241

Merged
ArtemisMucaj merged 1 commit into
mainfrom
feat/llm-endpoint-delete
Aug 24, 2026
Merged

ArtemisMucaj merged 1 commit into
mainfrom
feat/llm-endpoint-delete

Conversation

@ArtemisMucaj

Copy link
Copy Markdown
Owner

Adds DELETE /api/llm/endpoints/{name}, so an endpoint added by mistake or
pointing at a decommissioned provider can be removed without hand-editing the
config.

Why the referential cleanup is the substance

set_usage already refuses to create a binding naming an unregistered
endpoint, with the rationale in its own comment:

Refuse an endpoint that isn't registered: the resolver treats a dangling name
as "unset" and silently falls back, which reads as the setting being ignored.

A delete that only removed the map entry would reintroduce that exact state from
the other direction, so this clears every reference:

  • openai.active promotes another registered endpoint when it named the
    removed one — null if it was the last.
  • Per-usage bindings naming it are dropped, so the usage inherits the active
    endpoint again.

The whole binding is dropped rather than just its endpoint field: a binding
that kept its pinned model would silently apply that model to whichever
endpoint it inherits next.

Worth being concrete about the bug this fixes, since it is visible rather than
theoretical. Before this change, deleting an endpoint left usage_json
reporting:

{"id":"explain_code","endpoint":"alpha","model":"m-alpha","inherited":false}

inherited: false means "a deliberate per-usage choice" — pointing at an
endpoint that no longer exists. A settings UI can't distinguish that from a
valid pin, and Hoplon's picker re-inserts an unrecognised current choice (so a
hand-pinned model still displays), which resurrects the deleted endpoint as a
selectable option.

Tests

Four integration tests in management_server_tests.rs, using the existing
ephemeral-port harness:

  • round-trips add → delete and returns the refreshed list
  • promotes a survivor when the active endpoint is removed; active is null
    after the last one goes
  • clears a usage bound to the removed endpoint (asserts inherited flips back
    to true and the pinned model goes with it)
  • unknown name is 404, matching PUT /api/llm/usages/{id}

I checked the usages test actually catches the bug by reverting the retain and
confirming it fails with the dangling endpoint: "alpha", inherited: false
above, rather than passing either way.

cargo test is green (487 tests), cargo clippy --all-targets clean, cargo fmt applied.

Note on the OpenAPI description

Added the new route to docs/management-api.openapi.json. Worth flagging that
the rest of the LLM surface — /api/llm/endpoints, /api/llm/active,
/api/llm/usages, /api/llm/models, /api/llm/target — is still undocumented
there; only the Copilot login was. That is pre-existing and left alone rather
than expanded into here, but it is probably worth its own pass.

Closes #240
Unblocks ArtemisMucaj/hoplon#42

`/api/llm/endpoints/{name}` served GET and PUT but no DELETE, so an
endpoint added by mistake or pointing at a decommissioned provider stayed
in the config permanently — the only way out was hand-editing the file.

Removing one has to clear every reference to it, not just the entry.
`set_usage` already refuses to *create* a binding naming an unregistered
endpoint, on the grounds that the resolver treats a dangling name as
unset and silently falls back, "which reads as the setting being
ignored". A delete that left references behind would reintroduce exactly
that state from the other direction, so:

- `openai.active` promotes another registered endpoint when it named the
  removed one (none if it was the last), and
- any usage bound to it is dropped, so the usage inherits the active
  endpoint again. The whole binding goes, not just its `endpoint` field:
  a binding that kept its pinned `model` would apply that model to
  whichever endpoint it inherits next.

Unknown names are 404, matching what `PUT /api/llm/usages/{id}` already
reports for the same condition.

Also documents the route in the OpenAPI description. The other LLM routes
are still undocumented there — a pre-existing gap left alone here.

Requested by ArtemisMucaj/hoplon#42, which wants the remove button the
Memory pane already has.

Closes #240
@ArtemisMucaj
ArtemisMucaj merged commit 92cfb38 into main Aug 24, 2026
1 check passed
@ArtemisMucaj
ArtemisMucaj deleted the feat/llm-endpoint-delete branch August 24, 2026 13:24
ArtemisMucaj added a commit to ArtemisMucaj/hoplon that referenced this pull request Aug 24, 2026
The comments landed with #43 described the behaviour of a codesearch that
does not serve DELETE at all, so they asserted the opposite of what the
route now does: "the server keeps naming the removed endpoint" was true
of a hand-patched build used to test the client, and is false of the
implementation in ArtemisMucaj/codesearch#241, which drops the bindings
that named it.

`loadUsages()` after a delete is still required — bindings change either
way and are not derived from `endpoints` — but the reason is that they
fall back to inheriting the active endpoint, not that they dangle.

Also documents on the client method that a delete has effects beyond the
endpoint list, so the next caller does not assume re-reading it is
enough, and points both references at the PR that adds the route.

No behaviour change.

Co-authored-by: Artemis MUCAJ <artemis.mucaj@netatmo.com>
ArtemisMucaj added a commit to ArtemisMucaj/hoplon that referenced this pull request Aug 24, 2026
v2.4.0 served /api/llm/endpoints/{name} as PUT-only, so the remove button added
in #43 answered 405 on every click and the pane could only explain why. v2.5.0
adds the DELETE route (ArtemisMucaj/codesearch#241), which is what makes that
button work.

The skills tag moves with it, as that script requires — the skill text is
byte-identical between the two tags, but the pin is what keeps a skill and the
binary it documents from one release.

No app code changes: deleteLlmEndpoint and LlmView.remove(_:) were written
against this contract and already re-read the usage bindings.

Verified against the release binary: deleting an endpoint that was both active
and bound to a usage returns 200, promotes the survivor, and drops the binding
back to inherited:true; an unknown name is 404. The built app bundles 2.5.0.

Closes #42
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.

Management API has no DELETE for LLM endpoints (only GET + PUT)

1 participant