Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,16 @@ func TestFilterModelOptionsAllowlistUsesDefaultAndProtocolPaths(t *testing.T) {
func TestFilterModelOptionsAllowsGeminiInteractionResponseFormatArray(t *testing.T) {
filtered := filterModelOptions(map[string]interface{}{
"response_format": []interface{}{
map[string]interface{}{"type": "text"},
map[string]interface{}{
"type": "text",
"mime_type": "application/json",
"schema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"answer": map[string]interface{}{"type": "string"},
},
},
},
map[string]interface{}{"type": "image", "image_size": "1K", "delivery": "b64_json"},
},
}, llm.AdapterGeminiInteractions, modelOptionPolicyConfig{
Expand All @@ -85,6 +94,11 @@ func TestFilterModelOptionsAllowsGeminiInteractionResponseFormatArray(t *testing
if !ok || len(formats) != 2 {
t.Fatalf("expected Gemini Interactions response_format array to pass, got %#v", filtered)
}
textFormat := formats[0].(map[string]interface{})
schema, ok := textFormat["schema"].(map[string]interface{})
if !ok || schema["type"] != "object" {
t.Fatalf("expected whitelisted text schema to pass, got %#v", textFormat)
}
imageFormat := formats[1].(map[string]interface{})
if imageFormat["image_size"] != "1K" {
t.Fatalf("expected whitelisted image_size to pass, got %#v", imageFormat)
Expand Down Expand Up @@ -976,7 +990,7 @@ func TestFilterModelOptionsGeminiInteractionsAllowsVideoParams(t *testing.T) {
"response_format": map[string]interface{}{
"aspect_ratio": "16:9",
"image_size": "1K",
"mime_type": "image/png",
"mime_type": "image/jpeg",
"delivery": "b64_json",
},
"generation_config": map[string]interface{}{
Expand All @@ -997,7 +1011,7 @@ func TestFilterModelOptionsGeminiInteractionsAllowsVideoParams(t *testing.T) {
})

responseFormat, ok := filtered["response_format"].(map[string]interface{})
if !ok || responseFormat["aspect_ratio"] != "16:9" || responseFormat["image_size"] != "1K" || responseFormat["mime_type"] != "image/png" {
if !ok || responseFormat["aspect_ratio"] != "16:9" || responseFormat["image_size"] != "1K" || responseFormat["mime_type"] != "image/jpeg" {
t.Fatalf("expected Gemini response_format aspect ratio to pass, got %#v", filtered)
}
if _, ok := responseFormat["delivery"]; ok {
Expand Down Expand Up @@ -1133,7 +1147,7 @@ func TestFilterModelOptionsSelectsGeminiToolsForResolvedRouteProtocol(t *testing
}
}

func TestFilterModelOptionsGeminiInteractionsAllowsCamelCaseVideoConfig(t *testing.T) {
func TestFilterModelOptionsGeminiInteractionsRejectsLegacyCamelCaseConfig(t *testing.T) {
filtered := filterModelOptions(map[string]interface{}{
"generationConfig": map[string]interface{}{
"videoConfig": map[string]interface{}{
Expand All @@ -1146,13 +1160,8 @@ func TestFilterModelOptionsGeminiInteractionsAllowsCamelCaseVideoConfig(t *testi
DeniedPathsJSON: config.DefaultModelOptionDeniedPathsJSON(),
})

generationConfig, ok := filtered["generationConfig"].(map[string]interface{})
if !ok {
t.Fatalf("expected camelCase Gemini generationConfig to pass, got %#v", filtered)
}
videoConfig, ok := generationConfig["videoConfig"].(map[string]interface{})
if !ok || videoConfig["task"] != "text_to_video" {
t.Fatalf("expected camelCase Gemini video task to pass, got %#v", filtered)
if len(filtered) != 0 {
t.Fatalf("expected legacy camelCase Interactions options to be rejected, got %#v", filtered)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -755,12 +755,6 @@ func withGeminiInteractionResponseType(options map[string]interface{}, responseT
format[key] = value
}
}
if raw, ok := next["responseFormat"].(map[string]interface{}); ok {
for key, value := range raw {
format[key] = value
}
delete(next, "responseFormat")
}
format["type"] = strings.TrimSpace(responseType)
next["response_format"] = format
return next
Expand Down
19 changes: 19 additions & 0 deletions backend/internal/application/settings/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,29 @@ func isLegacyDefaultModelOptionAllowedPaths(value string) bool {
previousCombinedDefault["gemini_interactions"],
"generation_config.thinking_summaries",
)
legacyInteractionsDefault := cloneStringSliceMap(latestDefault)
legacyInteractionsDefault["gemini_interactions"] = append(
removeStringValue(legacyInteractionsDefault["gemini_interactions"], "response_format.schema"),
"responseFormat.type",
"responseFormat.aspectRatio",
"responseFormat.imageSize",
"responseFormat.mimeType",
"generationConfig.videoConfig.task",
)
legacyInteractionsWithoutSummaries := cloneStringSliceMap(legacyInteractionsDefault)
legacyInteractionsWithoutSummaries["gemini_interactions"] = removeStringValue(
legacyInteractionsWithoutSummaries["gemini_interactions"],
"generation_config.thinking_summaries",
)
legacyCombinedDefault := cloneStringSliceMap(legacyInteractionsWithoutSummaries)
legacyCombinedDefault["gemini_generate_content"] = previousGenerateContentDefault["gemini_generate_content"]
previousDefaults := []map[string][]string{
previousGenerateContentDefault,
previousInteractionsDefault,
previousCombinedDefault,
legacyInteractionsDefault,
legacyInteractionsWithoutSummaries,
legacyCombinedDefault,
}
for _, previousDefault := range previousDefaults {
if sameStringSliceMap(current, previousDefault) {
Expand Down
33 changes: 33 additions & 0 deletions backend/internal/application/settings/service_seed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,39 @@ func TestSeedAddsGeminiThinkingSummariesToPreviousDefaultModelOptionAllowedPaths
}
}

func TestSeedReplacesLegacyGeminiInteractionsOptionPaths(t *testing.T) {
previousDefault := map[string][]string{}
if err := json.Unmarshal([]byte(config.DefaultModelOptionAllowedPathsJSON()), &previousDefault); err != nil {
t.Fatalf("decode current model option defaults: %v", err)
}
previousDefault["gemini_interactions"] = append(
removeStringValue(previousDefault["gemini_interactions"], "response_format.schema"),
"responseFormat.type",
"responseFormat.aspectRatio",
"responseFormat.imageSize",
"responseFormat.mimeType",
"generationConfig.videoConfig.task",
)
previousJSON, err := json.Marshal(previousDefault)
if err != nil {
t.Fatalf("encode previous model option defaults: %v", err)
}
repo := newSettingsSeedRepo(domainsettings.SystemSetting{
Namespace: "chat",
Key: "model_option_allowed_paths",
Value: string(previousJSON),
ValueType: "json",
})
service := NewService(repo, "")

if err := service.Seed(context.Background(), config.Config{}); err != nil {
t.Fatalf("seed settings: %v", err)
}
if got := repo.items["chat:model_option_allowed_paths"].Value; got != config.DefaultModelOptionAllowedPathsJSON() {
t.Fatalf("expected legacy Gemini Interactions paths to migrate, got %q", got)
}
}

func TestSeedAddsGeminiGenerateContentThinkingPathsToPreviousDefaultModelOptionAllowedPaths(t *testing.T) {
previousDefault := map[string][]string{}
if err := json.Unmarshal([]byte(config.DefaultModelOptionAllowedPathsJSON()), &previousDefault); err != nil {
Expand Down
6 changes: 1 addition & 5 deletions backend/internal/infra/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,7 @@ func DefaultModelOptionAllowedPathsJSON() string {
"response_format.aspect_ratio",
"response_format.image_size",
"response_format.mime_type",
"responseFormat.type",
"responseFormat.aspectRatio",
"responseFormat.imageSize",
"responseFormat.mimeType",
"generationConfig.videoConfig.task",
"response_format.schema",
"generation_config.video_config.task"
],
"xai_responses": [
Expand Down
Loading
Loading