feat: operator-defined model aliases

Spec §5 (v0.2): Config.ModelAliases map[string]string, alias-first
resolution precedence ahead of provider-prefix splitting, alias-OR-target
authorization, lazy target resolution with a 404 naming both ids,
/v1/models listing, admin overview alias table (JSON + dashboard), and
construction-time validation (shape, prefix-shadow, explicit-model
collision, chain, empty target).

routes_unified.go/routes_media.go needed no resolution-call-site changes
- both already pass the client's raw requested id through to
registry.resolve, so aliases inherit the __alias echo and multipart
rewrite machinery for free. cache.go needed no changes either - cacheKey
already hashes requestedModel, so two aliases resolving to the same
target already produce distinct cache entries.

Adds registry_test.go validation table + precedence/authz/lazy-404/
listing coverage, a dedicated cache-key split test, chat/embeddings/
images/audio-transcriptions end-to-end tests, an extended admin overview
test, and an integration test covering the openai-type vs anthropic-type
echo asymmetry under real Traefik+Yaegi.
This commit is contained in:
2026-08-20 20:56:01 +01:00
parent f49c51b341
commit 2090b31ae2
12 changed files with 1221 additions and 15 deletions
+28
View File
@@ -151,6 +151,34 @@ func TestCacheKey_ChatVsEmbeddings_DistinctKeys(t *testing.T) {
}
}
// TestCacheKey_TwoOperatorAliases_SameTarget_DistinctKeys is task 6's
// (spec §5, v0.2) dedicated cache-key regression, phrased in terms of
// operator-defined model aliases (modelAliases) rather than
// TestCacheKey_BareVsPrefixedAlias_DistinctKeys' bare-vs-prefixed pair
// above. Two DIFFERENT alias ids ("aliased/coding", "aliased/fast")
// configured to resolve to the identical provider+upstreamModel target
// must still hash to distinct cache entries: cacheKey already hashes
// requestedModel as real key material (this file's own doc comment,
// TestCacheKey_BareVsPrefixedAlias_DistinctKeys), and
// modelRegistry.resolveAliasTarget (registry.go) leaves that string
// completely untouched regardless of which alias resolved to which
// target — the two features compose with NO cache.go changes required.
func TestCacheKey_TwoOperatorAliases_SameTarget_DistinctKeys(t *testing.T) {
req := map[string]any{"messages": []any{"hi"}}
codingAlias := cacheKey("anthropic", "claude-sonnet-4-5", "aliased/coding", cacheEndpointChat, req)
fastAlias := cacheKey("anthropic", "claude-sonnet-4-5", "aliased/fast", cacheEndpointChat, req)
if codingAlias == fastAlias {
t.Error("cacheKey identical for two different operator-defined aliases resolving to the identical provider+upstreamModel target")
}
// Same alias twice must still hash identically — a genuine repeat
// request under the same alias is a genuine cache hit.
if got, want := cacheKey("anthropic", "claude-sonnet-4-5", "aliased/coding", cacheEndpointChat, req), codingAlias; got != want {
t.Errorf("cacheKey differs for two requests using the identical alias: %q vs %q", got, want)
}
}
// --- validateCacheConfig: defaults and validation ---
func TestValidateCacheConfig_DefaultsAppliedWhenZero(t *testing.T) {