Files
kit/internal/models/responses_models.go
T
Ed Zynda 3881d1c28f fix(models): auto-register new OpenAI models for Responses API routing
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
2026-04-24 15:13:38 +03:00

59 lines
2.2 KiB
Go

package models
import (
_ "unsafe" // Required for go:linkname.
)
// responsesModelIDs and responsesReasoningModelIDs are the unexported slices
// in charm.land/fantasy/providers/openai that gate whether a model uses the
// Responses API code path vs Chat Completions. When a brand-new model is
// released (e.g. gpt-5.5) and models.dev is updated via `kit update-models`,
// Kit recognises the model but fantasy's hardcoded list does not. That causes
// a type-mismatch crash: Kit builds *ResponsesProviderOptions (correct for
// the Responses endpoint) but fantasy routes through Chat Completions and
// rejects the type.
//
// RegisterResponsesModels appends model IDs that are missing from fantasy's
// lists so the provider routes through the correct code path. It is called
// once during provider creation after loading the model database.
//go:linkname fantasyResponsesModelIDs charm.land/fantasy/providers/openai.responsesModelIDs
var fantasyResponsesModelIDs []string
//go:linkname fantasyResponsesReasoningModelIDs charm.land/fantasy/providers/openai.responsesReasoningModelIDs
var fantasyResponsesReasoningModelIDs []string
// RegisterResponsesModels ensures every OpenAI model known to our model
// database that should use the Responses API is present in fantasy's
// internal lists. This is a no-op for models already registered.
func RegisterResponsesModels() {
registry := GetGlobalRegistry()
providerInfo := registry.GetProviderInfo("openai")
if providerInfo == nil {
return
}
existing := make(map[string]bool, len(fantasyResponsesModelIDs))
for _, id := range fantasyResponsesModelIDs {
existing[id] = true
}
existingReasoning := make(map[string]bool, len(fantasyResponsesReasoningModelIDs))
for _, id := range fantasyResponsesReasoningModelIDs {
existingReasoning[id] = true
}
for modelID, modelInfo := range providerInfo.Models {
if !isResponsesAPIModel(modelID) {
continue
}
if !existing[modelID] {
fantasyResponsesModelIDs = append(fantasyResponsesModelIDs, modelID)
existing[modelID] = true
}
if modelInfo.Reasoning && !existingReasoning[modelID] {
fantasyResponsesReasoningModelIDs = append(fantasyResponsesReasoningModelIDs, modelID)
existingReasoning[modelID] = true
}
}
}