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, err

View on GitHub (pinned to a4447c1563)

Solutions

  1. Start a new chat for the new backend instead of reusing a chat id across API types
  2. Inspect chat.NativeMessages (%T of each) to find the offending message type and remove it from the store
  3. Ensure all PostMessage calls for this chat use *OpenAIChatMessage native payloads
  4. 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

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


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/f8c8b429f49c8a3a. Report an issue: GitHub.