package relayconvert import ( "testing" "github.com/QuantumNous/new-api/relaykit/dto" "github.com/QuantumNous/new-api/relaykit/relayconvert/convmeta" sharedgemini "github.com/QuantumNous/new-api/relaykit/relayconvert/internal/shared/gemini" kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil" "github.com/QuantumNous/new-api/relaykit/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRequestConverterRegistryListsSupportedTextConverters(t *testing.T) { tests := []struct { converter string from types.RelayFormat to types.RelayFormat quality RequestConverterQuality stepConverters []string advancedCustom bool }{ {converter: ConverterClaudeMessagesToOpenAIChat, from: types.RelayFormatClaude, to: types.RelayFormatOpenAI, quality: RequestConverterQualityFair, advancedCustom: true}, {converter: ConverterGeminiContentToOpenAIChat, from: types.RelayFormatGemini, to: types.RelayFormatOpenAI, quality: RequestConverterQualityFair, advancedCustom: true}, {converter: ConverterOpenAIChatToClaudeMessages, from: types.RelayFormatOpenAI, to: types.RelayFormatClaude, quality: RequestConverterQualityFair, advancedCustom: true}, {converter: ConverterOpenAIChatToGeminiContent, from: types.RelayFormatOpenAI, to: types.RelayFormatGemini, quality: RequestConverterQualityFair, advancedCustom: true}, {converter: ConverterOpenAIChatToOpenAIResponses, from: types.RelayFormatOpenAI, to: types.RelayFormatOpenAIResponses, quality: RequestConverterQualityGood, advancedCustom: true}, {converter: ConverterOpenAIResponsesToOpenAIChat, from: types.RelayFormatOpenAIResponses, to: types.RelayFormatOpenAI, quality: RequestConverterQualityGood, advancedCustom: true}, { converter: requestConverterClaudeToGemini, from: types.RelayFormatClaude, to: types.RelayFormatGemini, quality: RequestConverterQualityDiscouraged, stepConverters: []string{ ConverterClaudeMessagesToOpenAIChat, ConverterOpenAIChatToGeminiContent, }, }, { converter: requestConverterClaudeToResponses, from: types.RelayFormatClaude, to: types.RelayFormatOpenAIResponses, quality: RequestConverterQualityFair, stepConverters: []string{ ConverterClaudeMessagesToOpenAIChat, ConverterOpenAIChatToOpenAIResponses, }, }, { converter: requestConverterGeminiToClaude, from: types.RelayFormatGemini, to: types.RelayFormatClaude, quality: RequestConverterQualityDiscouraged, stepConverters: []string{ ConverterGeminiContentToOpenAIChat, ConverterOpenAIChatToClaudeMessages, }, }, { converter: requestConverterGeminiToResponses, from: types.RelayFormatGemini, to: types.RelayFormatOpenAIResponses, quality: RequestConverterQualityFair, stepConverters: []string{ ConverterGeminiContentToOpenAIChat, ConverterOpenAIChatToOpenAIResponses, }, }, { converter: requestConverterResponsesToClaude, from: types.RelayFormatOpenAIResponses, to: types.RelayFormatClaude, quality: RequestConverterQualityFair, }, { converter: ConverterOpenAIResponsesToGemini, from: types.RelayFormatOpenAIResponses, to: types.RelayFormatGemini, quality: RequestConverterQualityFair, advancedCustom: true, }, } require.Len(t, requestConverters, len(tests)) for _, tt := range tests { t.Run(tt.converter, func(t *testing.T) { spec, ok := LookupRequestConverter(tt.converter) require.True(t, ok) assert.Equal(t, tt.converter, spec.ID) assert.Equal(t, tt.from, spec.From) assert.Equal(t, tt.to, spec.To) assert.Equal(t, tt.quality, spec.Quality) assert.Equal(t, tt.stepConverters, spec.StepConverters) if len(tt.stepConverters) == 0 { assert.NotNil(t, spec.Convert) } else { assert.Nil(t, spec.Convert) } assert.Equal(t, tt.advancedCustom, dto.IsAdvancedCustomConverterAllowed(tt.converter)) }) } } func TestConvertRequestToTargetRecordsConversionChain(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAI}, } req := &dto.GeneralOpenAIRequest{ Model: "gpt-test", Messages: []dto.Message{ {Role: "user", Content: "hello"}, }, } result, err := ConvertRequest(nil, info, types.RelayFormatOpenAIResponses, req) require.NoError(t, err) require.IsType(t, &dto.OpenAIResponsesRequest{}, result.Value) assert.Equal(t, types.RelayFormatOpenAI, result.From) assert.Equal(t, types.RelayFormat(types.RelayFormatOpenAIResponses), result.To) assert.Equal(t, ConverterOpenAIChatToOpenAIResponses, result.Converter) assert.Equal(t, RequestConverterQualityGood, result.Quality) assert.Equal(t, []RequestStep{ { Converter: ConverterOpenAIChatToOpenAIResponses, From: types.RelayFormatOpenAI, To: types.RelayFormatOpenAIResponses, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, info.ConversionChain) } func TestConvertRequestPlansMultiHopPath(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatClaude}, } req := &dto.ClaudeRequest{ Model: "claude-test", Messages: []dto.ClaudeMessage{ {Role: "user", Content: "hello"}, }, } result, err := ConvertRequest(nil, info, types.RelayFormatOpenAIResponses, req) require.NoError(t, err) require.IsType(t, &dto.OpenAIResponsesRequest{}, result.Value) assert.Equal(t, types.RelayFormat(types.RelayFormatClaude), result.From) assert.Equal(t, types.RelayFormat(types.RelayFormatOpenAIResponses), result.To) assert.Equal(t, requestConverterClaudeToResponses, result.Converter) assert.Equal(t, RequestConverterQualityFair, result.Quality) assert.Equal(t, []RequestStep{ { Converter: ConverterClaudeMessagesToOpenAIChat, From: types.RelayFormatClaude, To: types.RelayFormatOpenAI, }, { Converter: ConverterOpenAIChatToOpenAIResponses, From: types.RelayFormatOpenAI, To: types.RelayFormatOpenAIResponses, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatClaude, types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, info.ConversionChain) } func TestConvertRequestViaExecutesExplicitPath(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAI}, } req := &dto.GeneralOpenAIRequest{ Model: "gpt-test", Messages: []dto.Message{ {Role: "user", Content: "hello"}, }, } result, err := ConvertRequestVia(nil, info, req, types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses) require.NoError(t, err) require.IsType(t, &dto.OpenAIResponsesRequest{}, result.Value) assert.Equal(t, []RequestStep{ { Converter: ConverterOpenAIChatToOpenAIResponses, From: types.RelayFormatOpenAI, To: types.RelayFormatOpenAIResponses, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, info.ConversionChain) } func TestConvertRequestResponsesToGeminiAppliesResponsesPreprocess(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAIResponses}, ChannelMetaAttached: true, UpstreamModelName: "gemini-test", } req := &dto.OpenAIResponsesRequest{ Model: "gemini-test", Input: mustRawMessage(t, []map[string]any{ { "role": "user", "content": "next turn", }, { "type": "custom_tool_call", "call_id": "call_custom", "name": "apply_patch", "input": "patch body", }, { "type": "custom_tool_call_output", "call_id": "call_custom", "output": "ok", }, { "type": "function_call_output", "call_id": "call_custom", "output": "legacy custom output", }, }), Tools: mustRawMessage(t, []map[string]any{ {"type": "custom", "name": "apply_patch"}, }), } result, err := ConvertRequest(nil, info, types.RelayFormatGemini, req) require.NoError(t, err) geminiReq, ok := result.Value.(*dto.GeminiChatRequest) require.True(t, ok) assert.Empty(t, geminiReq.GetTools()) require.Len(t, geminiReq.Contents, 1) assert.Equal(t, "user", geminiReq.Contents[0].Role) require.Len(t, geminiReq.Contents[0].Parts, 1) assert.Equal(t, "next turn", geminiReq.Contents[0].Parts[0].Text) assert.Equal(t, ConverterOpenAIResponsesToGemini, result.Converter) assert.Equal(t, RequestConverterQualityFair, result.Quality) assert.Equal(t, []RequestStep{ { Converter: ConverterOpenAIResponsesToGemini, From: types.RelayFormatOpenAIResponses, To: types.RelayFormatGemini, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAIResponses, types.RelayFormatGemini}, info.ConversionChain) } func TestConvertRequestResponsesToGeminiUsesDirectConverter(t *testing.T) { info := &convmeta.Values{ Options: &convmeta.Options{Gemini: convmeta.GeminiOptions{FunctionCallThoughtSignatureEnabled: true}}, ConversionChain: []types.RelayFormat{types.RelayFormatOpenAIResponses}, ChannelMetaAttached: true, UpstreamModelName: "gemini-test", } maxOutputTokens := uint(256) req := &dto.OpenAIResponsesRequest{ Model: "gemini-test", Instructions: mustRawMessage(t, "system rules"), MaxOutputTokens: &maxOutputTokens, Input: mustRawMessage(t, []map[string]any{ { "role": "assistant", "content": []map[string]any{ {"type": "output_text", "text": "I will call."}, }, }, { "type": "function_call", "call_id": "call_1", "name": "lookup", "arguments": map[string]any{"q": "x"}, }, { "type": "function_call_output", "call_id": "call_1", "output": map[string]any{"ok": true}, }, }), Tools: mustRawMessage(t, []map[string]any{ { "type": "function", "name": "lookup", "description": "Lookup data", "parameters": map[string]any{ "type": "object", "additionalProperties": false, "propertyNames": map[string]any{"pattern": "^[a-z]+$"}, "properties": map[string]any{ "q": map[string]any{ "type": "string", "exclusiveMinimum": 0, }, "filters": map[string]any{ "type": "array", "items": map[string]any{ "type": "object", "additionalProperties": true, "properties": map[string]any{ "name": map[string]any{"type": "string"}, }, }, }, }, }, }, }), Text: mustRawMessage(t, map[string]any{ "format": map[string]any{ "type": "json_schema", "name": "answer", "schema": map[string]any{"type": "object"}, }, }), } result, err := ConvertRequest(nil, info, types.RelayFormatGemini, req) require.NoError(t, err) geminiReq, ok := result.Value.(*dto.GeminiChatRequest) require.True(t, ok) assert.Equal(t, ConverterOpenAIResponsesToGemini, result.Converter) assert.Equal(t, []RequestStep{ { Converter: ConverterOpenAIResponsesToGemini, From: types.RelayFormatOpenAIResponses, To: types.RelayFormatGemini, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAIResponses, types.RelayFormatGemini}, info.ConversionChain) require.NotNil(t, geminiReq.SystemInstructions) require.Len(t, geminiReq.SystemInstructions.Parts, 1) assert.Equal(t, "system rules", geminiReq.SystemInstructions.Parts[0].Text) assert.Equal(t, "application/json", geminiReq.GenerationConfig.ResponseMimeType) assert.Equal(t, maxOutputTokens, *geminiReq.GenerationConfig.MaxOutputTokens) tools := geminiReq.GetTools() require.Len(t, tools, 1) functions, err := kitutil.Any2Type[[]dto.FunctionRequest](tools[0].FunctionDeclarations) require.NoError(t, err) require.Len(t, functions, 1) assert.Equal(t, "lookup", functions[0].Name) params, ok := functions[0].Parameters.(map[string]any) require.True(t, ok) assert.Equal(t, "OBJECT", params["type"]) assert.NotContains(t, params, "additionalProperties") assert.NotContains(t, params, "propertyNames") properties, ok := params["properties"].(map[string]any) require.True(t, ok) queryParam, ok := properties["q"].(map[string]any) require.True(t, ok) assert.Equal(t, "STRING", queryParam["type"]) assert.NotContains(t, queryParam, "exclusiveMinimum") filterParam, ok := properties["filters"].(map[string]any) require.True(t, ok) filterItems, ok := filterParam["items"].(map[string]any) require.True(t, ok) assert.NotContains(t, filterItems, "additionalProperties") require.Len(t, geminiReq.Contents, 2) assert.Equal(t, "model", geminiReq.Contents[0].Role) require.Len(t, geminiReq.Contents[0].Parts, 2) functionCall := geminiReq.Contents[0].Parts[0].FunctionCall require.NotNil(t, functionCall) assert.Equal(t, "lookup", functionCall.FunctionName) assert.Equal(t, map[string]any{"q": "x"}, functionCall.Arguments) var thoughtSignature string require.NoError(t, kitutil.Unmarshal(geminiReq.Contents[0].Parts[0].ThoughtSignature, &thoughtSignature)) assert.Equal(t, sharedgemini.ThoughtSignatureBypassValue, thoughtSignature) assert.Equal(t, "I will call.", geminiReq.Contents[0].Parts[1].Text) assert.Equal(t, "user", geminiReq.Contents[1].Role) require.Len(t, geminiReq.Contents[1].Parts, 1) functionResponse := geminiReq.Contents[1].Parts[0].FunctionResponse require.NotNil(t, functionResponse) assert.Equal(t, "lookup", functionResponse.Name) assert.Equal(t, true, functionResponse.Response["ok"]) assert.Empty(t, geminiReq.Contents[1].Parts[0].ThoughtSignature) } func TestConvertRequestResponsesToGeminiSkipsThoughtSignatureWhenDisabled(t *testing.T) { info := &convmeta.Values{ Options: &convmeta.Options{Gemini: convmeta.GeminiOptions{FunctionCallThoughtSignatureEnabled: false}}, ConversionChain: []types.RelayFormat{types.RelayFormatOpenAIResponses}, ChannelMetaAttached: true, UpstreamModelName: "gemini-test", } req := &dto.OpenAIResponsesRequest{ Model: "gemini-test", Input: mustRawMessage(t, []map[string]any{ { "type": "function_call", "call_id": "call_1", "name": "lookup", "arguments": map[string]any{"q": "x"}, }, }), Tools: mustRawMessage(t, []map[string]any{ {"type": "function", "name": "lookup", "parameters": map[string]any{"type": "object"}}, }), } result, err := ConvertRequest(nil, info, types.RelayFormatGemini, req) require.NoError(t, err) geminiReq, ok := result.Value.(*dto.GeminiChatRequest) require.True(t, ok) require.Len(t, geminiReq.Contents, 1) require.Len(t, geminiReq.Contents[0].Parts, 1) require.NotNil(t, geminiReq.Contents[0].Parts[0].FunctionCall) assert.Empty(t, geminiReq.Contents[0].Parts[0].ThoughtSignature) } func TestConvertRequestOpenAIChatToGeminiAddsThoughtSignatureForAdvancedCustom(t *testing.T) { assistantMessage := dto.Message{Role: "assistant", Content: ""} assistantMessage.SetToolCalls([]dto.ToolCallRequest{ { ID: "call_1", Type: "function", Function: dto.FunctionRequest{ Name: "lookup", Arguments: `{"q":"x"}`, }, }, }) info := &convmeta.Values{ Options: &convmeta.Options{Gemini: convmeta.GeminiOptions{FunctionCallThoughtSignatureEnabled: true}}, ConversionChain: []types.RelayFormat{types.RelayFormatOpenAI}, ChannelMetaAttached: true, ChannelType: 58, // advanced-custom in the host UpstreamModelName: "gemini-test", } req := &dto.GeneralOpenAIRequest{ Model: "gemini-test", Messages: []dto.Message{ {Role: "user", Content: "hi"}, assistantMessage, {Role: "tool", ToolCallId: "call_1", Content: `{"ok":true}`}, }, Tools: []dto.ToolCallRequest{ { Type: "function", Function: dto.FunctionRequest{ Name: "lookup", Parameters: map[string]any{"type": "object"}, }, }, }, } result, err := ConvertRequest(nil, info, types.RelayFormatGemini, req) require.NoError(t, err) geminiReq, ok := result.Value.(*dto.GeminiChatRequest) require.True(t, ok) require.Len(t, geminiReq.Contents, 3) assert.Equal(t, "model", geminiReq.Contents[1].Role) require.Len(t, geminiReq.Contents[1].Parts, 1) require.NotNil(t, geminiReq.Contents[1].Parts[0].FunctionCall) var thoughtSignature string require.NoError(t, kitutil.Unmarshal(geminiReq.Contents[1].Parts[0].ThoughtSignature, &thoughtSignature)) assert.Equal(t, sharedgemini.ThoughtSignatureBypassValue, thoughtSignature) } func TestConvertRequestResponsesToClaudeUsesDirectConverter(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAIResponses}, } stream := true parallelToolCalls := false maxOutputTokens := uint(512) req := &dto.OpenAIResponsesRequest{ Model: "claude-test", Instructions: mustRawMessage(t, "system rules"), Stream: &stream, MaxOutputTokens: &maxOutputTokens, ParallelToolCalls: mustRawMessage(t, parallelToolCalls), Reasoning: &dto.Reasoning{Effort: "medium"}, Input: mustRawMessage(t, []map[string]any{ { "role": "user", "content": "question", }, { "role": "assistant", "content": []map[string]any{ {"type": "output_text", "text": "I will call."}, }, }, { "type": "function_call", "call_id": "call_1", "name": "lookup", "arguments": map[string]any{"q": "x"}, }, { "type": "function_call_output", "call_id": "call_1", "output": map[string]any{"ok": true}, }, }), Tools: mustRawMessage(t, []map[string]any{ { "type": "function", "name": "lookup", "description": "Lookup data", "parameters": map[string]any{ "type": "object", "properties": map[string]any{ "q": map[string]any{"type": "string"}, }, }, }, }), } result, err := ConvertRequest(nil, info, types.RelayFormatClaude, req) require.NoError(t, err) claudeReq, ok := result.Value.(*dto.ClaudeRequest) require.True(t, ok) assert.Equal(t, requestConverterResponsesToClaude, result.Converter) assert.Equal(t, []RequestStep{ { Converter: requestConverterResponsesToClaude, From: types.RelayFormatOpenAIResponses, To: types.RelayFormatClaude, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAIResponses, types.RelayFormatClaude}, info.ConversionChain) system, err := kitutil.Any2Type[[]dto.ClaudeMediaMessage](claudeReq.System) require.NoError(t, err) require.Len(t, system, 1) assert.Equal(t, "system rules", system[0].GetText()) require.NotNil(t, claudeReq.Stream) assert.True(t, *claudeReq.Stream) assert.Equal(t, maxOutputTokens, *claudeReq.MaxTokens) require.NotNil(t, claudeReq.Thinking) assert.Equal(t, "enabled", claudeReq.Thinking.Type) assert.Equal(t, 2048, claudeReq.Thinking.GetBudgetTokens()) tools, err := kitutil.Any2Type[[]*dto.Tool](claudeReq.Tools) require.NoError(t, err) require.Len(t, tools, 1) assert.Equal(t, "lookup", tools[0].Name) require.Len(t, claudeReq.Messages, 3) assert.Equal(t, "user", claudeReq.Messages[0].Role) userParts, err := claudeReq.Messages[0].ParseContent() require.NoError(t, err) require.Len(t, userParts, 1) assert.Equal(t, "question", userParts[0].GetText()) assert.Equal(t, "assistant", claudeReq.Messages[1].Role) assistantParts, err := claudeReq.Messages[1].ParseContent() require.NoError(t, err) require.Len(t, assistantParts, 2) assert.Equal(t, "I will call.", assistantParts[0].GetText()) assert.Equal(t, "tool_use", assistantParts[1].Type) assert.Equal(t, "call_1", assistantParts[1].Id) assert.Equal(t, "lookup", assistantParts[1].Name) assert.Equal(t, map[string]any{"q": "x"}, assistantParts[1].Input) assert.Equal(t, "user", claudeReq.Messages[2].Role) toolResultParts, err := claudeReq.Messages[2].ParseContent() require.NoError(t, err) require.Len(t, toolResultParts, 1) assert.Equal(t, "tool_result", toolResultParts[0].Type) assert.Equal(t, "call_1", toolResultParts[0].ToolUseId) assert.Equal(t, map[string]any{"ok": true}, toolResultParts[0].Content) } func TestConvertRequestViaResponsesToGeminiStillUsesDirectSteps(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAIResponses}, ChannelMetaAttached: true, UpstreamModelName: "gemini-test", } req := &dto.OpenAIResponsesRequest{ Model: "gemini-test", Input: mustRawMessage(t, []map[string]any{ { "role": "user", "content": "hello", }, }), } result, err := ConvertRequestVia(nil, info, req, types.RelayFormatOpenAI, types.RelayFormatGemini) require.NoError(t, err) require.IsType(t, &dto.GeminiChatRequest{}, result.Value) assert.Equal(t, ConverterOpenAIResponsesToOpenAIChat+","+ConverterOpenAIChatToGeminiContent, result.Converter) assert.Equal(t, []RequestStep{ { Converter: ConverterOpenAIResponsesToOpenAIChat, From: types.RelayFormatOpenAIResponses, To: types.RelayFormatOpenAI, }, { Converter: ConverterOpenAIChatToGeminiContent, From: types.RelayFormatOpenAI, To: types.RelayFormatGemini, }, }, result.Steps) } func TestConvertRequestByIDDeduplicatesConversionChain(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, } req := &dto.GeneralOpenAIRequest{ Model: "gpt-test", Messages: []dto.Message{ {Role: "user", Content: "hello"}, }, } result, err := ConvertRequestByID(nil, info, ConverterOpenAIChatToOpenAIResponses, req) require.NoError(t, err) require.IsType(t, &dto.OpenAIResponsesRequest{}, result.Value) require.Len(t, result.Steps, 1) assert.Equal(t, []types.RelayFormat{types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, info.ConversionChain) } func TestConvertRequestByIDExecutesMultiHopConverter(t *testing.T) { info := &convmeta.Values{ ConversionChain: []types.RelayFormat{types.RelayFormatClaude}, } req := &dto.ClaudeRequest{ Model: "claude-test", Messages: []dto.ClaudeMessage{ {Role: "user", Content: "hello"}, }, } result, err := ConvertRequestByID(nil, info, requestConverterClaudeToResponses, req) require.NoError(t, err) require.IsType(t, &dto.OpenAIResponsesRequest{}, result.Value) assert.Equal(t, requestConverterClaudeToResponses, result.Converter) assert.Equal(t, RequestConverterQualityFair, result.Quality) assert.Equal(t, []RequestStep{ { Converter: ConverterClaudeMessagesToOpenAIChat, From: types.RelayFormatClaude, To: types.RelayFormatOpenAI, }, { Converter: ConverterOpenAIChatToOpenAIResponses, From: types.RelayFormatOpenAI, To: types.RelayFormatOpenAIResponses, }, }, result.Steps) assert.Equal(t, []types.RelayFormat{types.RelayFormatClaude, types.RelayFormatOpenAI, types.RelayFormatOpenAIResponses}, info.ConversionChain) } func TestConvertRequestRejectsUnsupportedConverterAndNilRequest(t *testing.T) { _, err := ConvertRequestByID(nil, &convmeta.Values{}, "missing_converter", &dto.GeneralOpenAIRequest{Model: "gpt-test"}) require.Error(t, err) assert.Contains(t, err.Error(), "not registered") _, err = ConvertRequest(nil, &convmeta.Values{}, types.RelayFormatOpenAIResponses, (*dto.GeneralOpenAIRequest)(nil)) require.Error(t, err) assert.Contains(t, err.Error(), "request is nil") } func TestConvertRequestByIDRejectsWrongSourceFormat(t *testing.T) { _, err := ConvertRequestByID( nil, &convmeta.Values{}, ConverterOpenAIChatToOpenAIResponses, &dto.ClaudeRequest{Model: "claude-test"}, ) require.Error(t, err) assert.Contains(t, err.Error(), "expects openai request") } func TestConvertRequestRejectsUnregisteredExplicitPath(t *testing.T) { _, err := ConvertRequest( nil, &convmeta.Values{}, types.RelayFormatEmbedding, &dto.ClaudeRequest{Model: "claude-test"}, ) require.Error(t, err) assert.Contains(t, err.Error(), "from claude to embedding is not registered") } func mustRawMessage(t *testing.T, value any) []byte { t.Helper() raw, err := kitutil.Marshal(value) require.NoError(t, err) return raw }