mirror of
https://github.com/mark3labs/kit.git
synced 2026-06-13 19:20:06 +00:00
3881d1c28f
Fantasy's hardcoded responsesModelIDs list gates whether a model uses the Responses API or Chat Completions code path. When a new model (e.g. gpt-5.5) is added via `kit update-models` but fantasy hasn't been updated yet, the type mismatch between *ResponsesProviderOptions and *ProviderOptions causes a crash. - Add isResponsesAPIModel()/isResponsesReasoningModel() helpers that supplement fantasy's checks with prefix-based heuristics for modern OpenAI model families (gpt-4.1+, gpt-5+, o-series, codex, chatgpt) - Add RegisterResponsesModels() using go:linkname to append missing model IDs from our database into fantasy's internal slices at init time and after ReloadGlobalRegistry() - Replace all direct openai.IsResponsesModel/IsResponsesReasoningModel calls in providers.go with the new helpers - Merge embedded + cached model databases instead of cache-only fallback - Bump fantasy v0.19.0 -> v0.20.0 to match existing import usage - Document the technique and model-family update process in AGENTS.md
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package models
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"charm.land/fantasy/providers/openai"
|
|
)
|
|
|
|
func TestIsResponsesAPIModel(t *testing.T) {
|
|
tests := []struct {
|
|
modelID string
|
|
expected bool
|
|
}{
|
|
// Already in fantasy's list — always true
|
|
{"gpt-5", true},
|
|
{"gpt-4.1", true},
|
|
{"o3", true},
|
|
{"o4-mini", true},
|
|
{"codex-mini-latest", true},
|
|
|
|
// NOT in fantasy's list but matches our heuristic
|
|
{"gpt-5.5", true},
|
|
{"gpt-5.6-turbo", true},
|
|
{"gpt-4.1-ultra", true},
|
|
{"o3-jumbo", true},
|
|
{"o4-mega", true},
|
|
|
|
// Should NOT match
|
|
{"gpt-3.5-turbo", true}, // actually IS in fantasy's responses list (legacy Chat Completions compat)
|
|
{"llama-3", false},
|
|
{"claude-opus-4-6", false},
|
|
{"gemini-2.5-pro", false},
|
|
{"random-model", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.modelID, func(t *testing.T) {
|
|
got := isResponsesAPIModel(tt.modelID)
|
|
if got != tt.expected {
|
|
t.Errorf("isResponsesAPIModel(%q) = %v, want %v", tt.modelID, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsResponsesReasoningModel(t *testing.T) {
|
|
tests := []struct {
|
|
modelID string
|
|
expected bool
|
|
}{
|
|
// In fantasy's reasoning list
|
|
{"gpt-5", true},
|
|
{"o3", true},
|
|
{"o4-mini", true},
|
|
|
|
// NOT in fantasy's list but matches reasoning heuristic (gpt-5 prefix)
|
|
{"gpt-5.5", true},
|
|
{"gpt-5.6-turbo", true},
|
|
|
|
// Responses API but NOT reasoning
|
|
{"gpt-4.1", false},
|
|
{"gpt-4.1-mini", false},
|
|
|
|
// Not OpenAI at all
|
|
{"claude-opus-4-6", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.modelID, func(t *testing.T) {
|
|
got := isResponsesReasoningModel(tt.modelID)
|
|
if got != tt.expected {
|
|
t.Errorf("isResponsesReasoningModel(%q) = %v, want %v", tt.modelID, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegisterResponsesModels(t *testing.T) {
|
|
// After RegisterResponsesModels() (called in init()),
|
|
// any model matching our heuristic that's in the model database
|
|
// should be queryable via openai.IsResponsesModel.
|
|
|
|
// Models in the embedded database that are also in fantasy's list
|
|
// should remain accessible.
|
|
if !openai.IsResponsesModel("gpt-5") {
|
|
t.Error("gpt-5 should be a responses model after registration")
|
|
}
|
|
|
|
// The registration should not break existing models.
|
|
if openai.IsResponsesModel("random-nonexistent-model") {
|
|
t.Error("random model should NOT be a responses model")
|
|
}
|
|
}
|
|
|
|
func TestBuildOpenAIProviderOptions_NewModel(t *testing.T) {
|
|
// A model like gpt-5.5 that isn't in fantasy's hardcoded list
|
|
// but matches our heuristic should get ResponsesProviderOptions.
|
|
config := &ProviderConfig{
|
|
ModelString: "openai/gpt-5.5",
|
|
}
|
|
opts := buildOpenAIProviderOptions(config, "gpt-5.5")
|
|
if opts == nil {
|
|
t.Fatal("buildOpenAIProviderOptions should return non-nil for gpt-5.5")
|
|
}
|
|
v, ok := opts[openai.Name]
|
|
if !ok {
|
|
t.Fatal("should have openai key in provider options")
|
|
}
|
|
if _, ok := v.(*openai.ResponsesProviderOptions); !ok {
|
|
t.Errorf("expected *ResponsesProviderOptions, got %T", v)
|
|
}
|
|
}
|
|
|
|
func TestBuildOpenAIProviderOptions_NonResponsesModel(t *testing.T) {
|
|
// A model that doesn't match any heuristic should get nil.
|
|
config := &ProviderConfig{
|
|
ModelString: "openai/some-old-model",
|
|
}
|
|
opts := buildOpenAIProviderOptions(config, "some-old-model")
|
|
if opts != nil {
|
|
t.Errorf("buildOpenAIProviderOptions should return nil for unknown model, got %v", opts)
|
|
}
|
|
}
|