feat(serve): remove LLM endpoints over the management API - #241
Merged
Merged
Conversation
`/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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
DELETE /api/llm/endpoints/{name}, so an endpoint added by mistake orpointing at a decommissioned provider can be removed without hand-editing the
config.
Why the referential cleanup is the substance
set_usagealready refuses to create a binding naming an unregisteredendpoint, with the rationale in its own comment:
A delete that only removed the map entry would reintroduce that exact state from
the other direction, so this clears every reference:
openai.activepromotes another registered endpoint when it named theremoved one —
nullif it was the last.endpoint again.
The whole binding is dropped rather than just its
endpointfield: a bindingthat kept its pinned
modelwould silently apply that model to whicheverendpoint 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_jsonreporting:
{"id":"explain_code","endpoint":"alpha","model":"m-alpha","inherited":false}inherited: falsemeans "a deliberate per-usage choice" — pointing at anendpoint 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 existingephemeral-port harness:
activeis nullafter the last one goes
inheritedflips backto
trueand the pinned model goes with it)PUT /api/llm/usages/{id}I checked the usages test actually catches the bug by reverting the
retainandconfirming it fails with the dangling
endpoint: "alpha", inherited: falseabove, rather than passing either way.
cargo testis green (487 tests),cargo clippy --all-targetsclean,cargo fmtapplied.Note on the OpenAPI description
Added the new route to
docs/management-api.openapi.json. Worth flagging thatthe rest of the LLM surface —
/api/llm/endpoints,/api/llm/active,/api/llm/usages,/api/llm/models,/api/llm/target— is still undocumentedthere; 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