wavetermdev/waveterm · error
expected OpenAIChatMessage, got %T
Error message
expected OpenAIChatMessage, got %T
What it means
While converting chat.NativeMessages into OpenAI inputs, RunOpenAIChatStep type-asserts every native message to *OpenAIChatMessage and returns "expected OpenAIChatMessage, got %T" at openai-backend.go:510 if any message has a different concrete type. The OpenAI backend assumes its chats contain only OpenAI-native messages; a foreign message type indicates the chat history was polluted by another backend or a bug.
Source
Thrown at pkg/aiusechat/openai/openai-backend.go:510
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(chatOpts.Config.TimeoutMs)*time.Millisecond)
defer cancel()
}
// Validate continuation if provided
if cont != nil {
if !uctypes.AreModelsCompatible(chat.APIType, chatOpts.Config.Model, cont.Model) {
return nil, nil, nil, fmt.Errorf("cannot continue with a different model, model:%q, cont-model:%q", chatOpts.Config.Model, cont.Model)
}
}
// Convert GenAIMessages to input objects (OpenAIMessage or OpenAIFunctionCallInput)
var inputs []any
for _, genMsg := range chat.NativeMessages {
// Cast to OpenAIChatMessage
chatMsg, ok := genMsg.(*OpenAIChatMessage)
if !ok {
return nil, nil, nil, fmt.Errorf("expected OpenAIChatMessage, got %T", genMsg)
}
// Convert to appropriate input type based on what's populated
if chatMsg.Message != nil {
// Clean message to remove preview URLs
cleanedMsg := chatMsg.Message.cleanAndCopy()
inputs = append(inputs, *cleanedMsg)
} else if chatMsg.FunctionCall != nil {
cleanedFunctionCall := chatMsg.FunctionCall.clean()
inputs = append(inputs, *cleanedFunctionCall)
} else if chatMsg.FunctionCallOutput != nil {
inputs = append(inputs, *chatMsg.FunctionCallOutput)
}
}
req, err := buildOpenAIHTTPRequest(ctx, inputs, chatOpts, cont)
if err != nil {
return nil, nil, nil, errView on GitHub (pinned to a4447c1563)
Solutions
- Start a new chat for the new backend instead of reusing a chat id across API types
- Inspect chat.NativeMessages (%T of each) to find the offending message type and remove it from the store
- Ensure all PostMessage calls for this chat use *OpenAIChatMessage native payloads
- If you control the loop, skip (continue) non-matching messages instead of failing the whole step — as UpdateToolUseData does
Example fix
// before
chatMsg, ok := genMsg.(*OpenAIChatMessage)
if !ok {
return nil, nil, nil, fmt.Errorf("expected OpenAIChatMessage, got %T", genMsg)
}
// after
chatMsg, ok := genMsg.(*OpenAIChatMessage)
if !ok {
log.Printf("skipping non-openai message %T in chat %s", genMsg, chatOpts.ChatId)
continue
} Defensive patterns
Strategy: type-guard
Validate before calling
chat := chatstore.DefaultChatStore.Get(chatOpts.ChatId)
if chat != nil {
for _, m := range chat.NativeMessages {
if _, ok := m.(*OpenAIChatMessage); !ok {
return fmt.Errorf("chat %s contains foreign message type %T", chatOpts.ChatId, m)
}
}
} Type guard
func allMessagesAreOpenAI(chatId string) bool {
chat := chatstore.DefaultChatStore.Get(chatId)
if chat == nil {
return false
}
for _, m := range chat.NativeMessages {
if _, ok := m.(*OpenAIChatMessage); !ok {
return false
}
}
return true
} Try / catch
if _, _, _, err := RunOpenAIChatStep(ctx, sse, chatOpts, nil); err != nil {
if strings.Contains(err.Error(), "expected OpenAIChatMessage") {
// chat history polluted by another backend; start a new chat
return nil
}
return err
} Prevention
- Never reuse a ChatId across different provider backends
- Only post *OpenAIChatMessage natives to OpenAI-managed chats
- Audit chatstore writes in custom code for native type correctness
- Sanitize or skip foreign messages before stepping instead of failing
When it happens
Trigger: A chat whose NativeMessages slice contains a non-*OpenAIChatMessage entry — e.g. messages inserted by a different backend (azure/other provider) under the same chat id, or test/mock message types appended to the store.
Common situations: Switching provider backends while keeping the same ChatId so another backend wrote its message type into the same chat; custom code posting messages directly to chatstore with the wrong native type; mixed-provider chat reuse after a config change.
Related errors
- chat not found: %s
- expected StoredChatMessage, got %T
- message %d: expected *anthropicChatMessage, got %T
- %s
- function call with callId %s not found in chat %s
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/f8c8b429f49c8a3a.
Report an issue: GitHub.