Files
CaIon 7c044d7c5c feat(relay): explicit @ model modifiers and canonical billing identity
Model-name post-processing is rebuilt around an explicit trailing
@key:value modifier syntax (thinking/effort/temperature/topp) that
overrides request fields, survives model mapping, and records
conversion diagnostics on the consume log.

- Legacy naked aliases (-thinking, -nothinking, -thinking-<budget>,
  effort tails) now parse only for positively matched families
  (gpt-*/o-series, claude-*, gemini-*, incl. vendor/ namespaces);
  names like qwen-max stay opaque. EffortTailModelIDs remains the
  escape hatch for real in-family IDs such as gpt-5.1-codex-max.
- Billing identity resolves once in ModelPriceHelper via a ladder:
  configured request name first (legacy wildcard entries intact), then
  canonical billing names rebuilt from parsed intent
  (base@effort:E@thinking:S, then base@thinking:S; order, duplicates,
  and budget values are irrelevant; temperature/topp never priced),
  then base. Routing and token limits fall back through
  RoutingMatchModelName; pricing lookups stay wildcard-only.
- Pass-through stays byte-identical: modifiers and aliases are neither
  parsed nor validated there and forward verbatim for the upstream
  (or a chained gateway) to interpret.
- Unknown modifier keys and invalid known-key values are rejected with
  400; models whose real names contain @tag:value are exempted via the
  thinking-suffix blacklist, which now supports re:-prefixed Go regex
  entries.
- Claude reasoning render coerces unsupported combinations (disable,
  adaptive, budgets) with warning diagnostics instead of erroring;
  native-protocol requests without host syntax pass through untouched.

BREAKING(openrouter): drop the host-invented "-thinking" model-name
alias (added in 4f6d16e36) that trimmed any *-thinking model on
OpenRouter channels and injected reasoning.enabled. It matched too
broadly and mangled real model IDs such as kimi-k2-thinking.
Migration: use some-model@thinking:on, or keep the old public name via
a channel model mapping {"some-model-thinking": "some-model@thinking:on"}.
Claude/Gemini family aliases (incl. anthropic/claude-*-thinking) keep
working via the family whitelist.
2026-09-04 20:45:52 +08:00

45 lines
1.5 KiB
Go

package model_setting
import (
"bytes"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestShouldPreserveThinkingSuffixExactAndRegex(t *testing.T) {
settings := GetGlobalSettings()
original := append([]string(nil), settings.ThinkingModelBlacklist...)
t.Cleanup(func() { settings.ThinkingModelBlacklist = original })
assert.True(t, ShouldPreserveThinkingSuffix("kimi-k2-thinking"))
assert.True(t, ShouldPreserveThinkingSuffix("moonshotai/kimi-k2-thinking"))
assert.False(t, ShouldPreserveThinkingSuffix("m@sha256:abc"))
settings.ThinkingModelBlacklist = []string{
"kimi-k2-thinking",
"re:[",
"re:",
"re:.*@sha256:.*",
}
var logged bytes.Buffer
previous := gin.DefaultErrorWriter
gin.DefaultErrorWriter = &logged
t.Cleanup(func() { gin.DefaultErrorWriter = previous })
assert.True(t, ShouldPreserveThinkingSuffix("kimi-k2-thinking"))
assert.True(t, ShouldPreserveThinkingSuffix("m@sha256:abc"))
assert.False(t, ShouldPreserveThinkingSuffix("m@sha256"))
assert.False(t, ShouldPreserveThinkingSuffix("qwen3-max@thinking:on"))
require.Contains(t, logged.String(), `invalid thinking_model_blacklist regex "re:["`)
require.Contains(t, logged.String(), `invalid thinking_model_blacklist regex "re:"`)
settings.ThinkingModelBlacklist = []string{"re:^beta@"}
assert.False(t, ShouldPreserveThinkingSuffix("m@sha256:abc"))
assert.True(t, ShouldPreserveThinkingSuffix("beta@sha256:abc"))
assert.False(t, ShouldPreserveThinkingSuffix("alpha@sha256:abc"))
}