mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-21 09:28:00 +00:00
feat: opencode zen adapter + first-run config generation, fix stats/stream bugs
- adapters/opencode.lua: opencode.ai zen free pool adapter — sends the opencode client User-Agent (zen fingerprints clients by UA; non-official clients hit FreeUsageLimitError); pairs with api_key: public - config: no config file ships in the repo; first run generates a default config at the -config path with a random admin key, loopback listen and a keyless zen source (config.EnsureDefault); remove config.example.yaml - lua: seed bundled adapters from the embedded FS instead of a hardcoded name list - ui: widen model kind select (chat was clipped to 'cha') - phase 5 bugfixes: stats ms/s bucket mixing, cleanScopes nil, ctx.Err guards, direct-path ModelAvailable, empty stream body failure, bestImageModel rewrite, transform failure recording, Core.mu, timer, effective model for tool-calls
This commit is contained in:
@ -67,6 +67,40 @@ func (f *fakeProvider) Image(ctx context.Context, req *types.ImageGenRequest) (*
|
||||
return nil, errors.New("no image")
|
||||
}
|
||||
|
||||
// TestDirectSkipsCooledCandidate: direct paths share the AUTO-chain rule that
|
||||
// cooldown is the only hard skip — a cooling candidate must never be hit, and
|
||||
// a fully cooling set must fail without touching upstream.
|
||||
func TestDirectSkipsCooledCandidate(t *testing.T) {
|
||||
hot := fakeProv("hot", "m")
|
||||
cold := fakeProv("cold", "m")
|
||||
cold.available.Store(false)
|
||||
s := New(2)
|
||||
req := &types.ChatRequest{
|
||||
Model: "m",
|
||||
Messages: []types.ChatMessage{{Role: "user", Content: types.StringContent("x")}},
|
||||
}
|
||||
resp, src, _, err := s.Chat(context.Background(), []Provider{cold, hot}, req)
|
||||
if err != nil || src != "hot" {
|
||||
t.Fatalf("want hot to serve, got src=%q err=%v", src, err)
|
||||
}
|
||||
if resp == nil || resp.Content != "hot" {
|
||||
t.Fatalf("bad response: %#v", resp)
|
||||
}
|
||||
if cold.chatHits.Load() != 0 {
|
||||
t.Fatalf("cooled candidate must not be hit, got %d hits", cold.chatHits.Load())
|
||||
}
|
||||
// all candidates cooled: direct path reports it instead of hitting upstream
|
||||
hot.available.Store(false)
|
||||
hits := hot.chatHits.Load()
|
||||
_, _, _, err = s.Chat(context.Background(), []Provider{cold, hot}, req)
|
||||
if err == nil || !strings.Contains(err.Error(), "cooling down") {
|
||||
t.Fatalf("want cooling-down error, got %v", err)
|
||||
}
|
||||
if hot.chatHits.Load() != hits || cold.chatHits.Load() != 0 {
|
||||
t.Fatalf("cooled candidates must not be hit, got hot=%d cold=%d", hot.chatHits.Load(), cold.chatHits.Load())
|
||||
}
|
||||
}
|
||||
|
||||
// lookup resolves (model, source) -> provider for chain builders in tests.
|
||||
type lookup func(m, s string) Provider
|
||||
|
||||
|
||||
Reference in New Issue
Block a user