Skip to content
Closed
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
8 changes: 6 additions & 2 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ const baseProviderSettingsSchema = z.object({
reasoningEffort: reasoningEffortsSchema.optional(),
modelMaxTokens: z.number().optional(),
modelMaxThinkingTokens: z.number().optional(),

// URL context and grounding (optional for most providers, required for Gemini)
enableUrlContext: z.boolean().optional(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good approach moving these to the base schema as optional fields! This provides flexibility while maintaining backward compatibility. The comment clearly explains the intent.

enableGrounding: z.boolean().optional(),
})

// Several of the providers share common model config properties.
Expand Down Expand Up @@ -174,8 +178,8 @@ const lmStudioSchema = baseProviderSettingsSchema.extend({
const geminiSchema = apiModelIdProviderModelSchema.extend({
geminiApiKey: z.string().optional(),
googleGeminiBaseUrl: z.string().optional(),
enableUrlContext: z.boolean().optional(),
enableGrounding: z.boolean().optional(),
enableUrlContext: z.boolean(),
enableGrounding: z.boolean(),
})

const geminiCliSchema = apiModelIdProviderModelSchema.extend({
Expand Down
111 changes: 108 additions & 3 deletions src/core/config/ProviderSettingsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const providerProfilesSchema = z.object({
openAiHeadersMigrated: z.boolean().optional(),
consecutiveMistakeLimitMigrated: z.boolean().optional(),
todoListEnabledMigrated: z.boolean().optional(),
enableUrlContextMigrated: z.boolean().optional(),
enableGroundingMigrated: z.boolean().optional(),
})
.optional(),
})
Expand All @@ -56,6 +58,8 @@ export class ProviderSettingsManager {
openAiHeadersMigrated: true, // Mark as migrated on fresh installs
consecutiveMistakeLimitMigrated: true, // Mark as migrated on fresh installs
todoListEnabledMigrated: true, // Mark as migrated on fresh installs
enableUrlContextMigrated: true, // Mark as migrated on fresh installs
enableGroundingMigrated: true, // Mark as migrated on fresh installs
},
}

Expand Down Expand Up @@ -123,6 +127,8 @@ export class ProviderSettingsManager {
openAiHeadersMigrated: false,
consecutiveMistakeLimitMigrated: false,
todoListEnabledMigrated: false,
enableUrlContextMigrated: false,
enableGroundingMigrated: false,
} // Initialize with default values
isDirty = true
}
Expand Down Expand Up @@ -157,6 +163,22 @@ export class ProviderSettingsManager {
isDirty = true
}

// Only run migration if either flag is false
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentional? The migration checks and updates are done separately for each flag. If an error occurs between updating the flags, it could leave the migration in an inconsistent state. Consider combining both flag updates into a single operation:

Suggested change
if (
if (
!providerProfiles.migrations.enableUrlContextMigrated ||
!providerProfiles.migrations.enableGroundingMigrated
) {
await this.migrateEnableUrlContextAndGrounding(providerProfiles)
providerProfiles.migrations.enableUrlContextMigrated = true
providerProfiles.migrations.enableGroundingMigrated = true
isDirty = true
}

!providerProfiles.migrations.enableUrlContextMigrated ||
!providerProfiles.migrations.enableGroundingMigrated
) {
await this.migrateEnableUrlContextAndGrounding(providerProfiles)
if (!providerProfiles.migrations.enableUrlContextMigrated) {
providerProfiles.migrations.enableUrlContextMigrated = true
isDirty = true
}
if (!providerProfiles.migrations.enableGroundingMigrated) {
providerProfiles.migrations.enableGroundingMigrated = true
isDirty = true
}
}

if (isDirty) {
await this.store(providerProfiles)
}
Expand Down Expand Up @@ -274,6 +296,38 @@ export class ProviderSettingsManager {
}
}

private async migrateEnableUrlContextAndGrounding(providerProfiles: ProviderProfiles) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for this new migration. This is a critical migration that affects data integrity and should be thoroughly tested. Could we add tests similar to the other migrations (like migrateTodoListEnabled at line 190)?

try {
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
// Only apply migration to gemini provider settings
if (apiConfig.apiProvider === "gemini") {
// Type assertion to access gemini-specific properties
const geminiConfig = apiConfig as any
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of any type assertions here bypasses TypeScript's type checking. Could we consider using proper type guards or creating a more specific type for Gemini configurations? This would improve type safety and make the code more maintainable.

if (geminiConfig.enableUrlContext === undefined) {
geminiConfig.enableUrlContext = true
}
if (geminiConfig.enableGrounding === undefined) {
geminiConfig.enableGrounding = true
}
} else {
// For non-Gemini providers, remove these fields if they exist (cleanup old data)
const configAny = apiConfig as any
if (configAny.enableUrlContext !== undefined) {
delete configAny.enableUrlContext
}
if (configAny.enableGrounding !== undefined) {
delete configAny.enableGrounding
}
}
}
} catch (error) {
console.error(
`[MigrateEnableUrlContextAndGrounding] Failed to migrate enableUrlContext and enableGrounding settings:`,
error,
)
}
}

/**
* List all available configs with metadata.
*/
Expand Down Expand Up @@ -308,7 +362,28 @@ export class ProviderSettingsManager {

// Filter out settings from other providers.
const filteredConfig = discriminatedProviderSettingsWithIdSchema.parse(config)
providerProfiles.apiConfigs[name] = { ...filteredConfig, id }

// Handle gemini-specific fields - only ensure they exist for Gemini configs
if (filteredConfig.apiProvider === "gemini") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic for ensuring Gemini fields have defaults is duplicated in export (lines 536-547) and in the load method (lines 599-610). Could we extract this into a helper function to reduce duplication? Something like:

Suggested change
if (filteredConfig.apiProvider === "gemini") {
private ensureGeminiDefaults(config: any): void {
if (config.apiProvider === "gemini") {
config.enableUrlContext = config.enableUrlContext ?? true
config.enableGrounding = config.enableGrounding ?? true
} else {
delete config.enableUrlContext
delete config.enableGrounding
}
}

// For Gemini, ensure these fields are always present with defaults if not provided
const geminiConfig = { ...filteredConfig } as any
geminiConfig.enableUrlContext = (config as any).enableUrlContext ?? true
geminiConfig.enableGrounding = (config as any).enableGrounding ?? true
providerProfiles.apiConfigs[name] = {
...geminiConfig,
id,
}
} else {
// For non-Gemini providers, ensure we don't include gemini-specific fields
// The discriminated schema should filter these out, but let's be explicit
const cleanConfig = { ...filteredConfig } as any
delete cleanConfig.enableUrlContext
delete cleanConfig.enableGrounding
providerProfiles.apiConfigs[name] = {
...cleanConfig,
id,
}
}
await this.store(providerProfiles)
return id
})
Expand Down Expand Up @@ -455,7 +530,21 @@ export class ProviderSettingsManager {
const configs = profiles.apiConfigs
for (const name in configs) {
// Avoid leaking properties from other providers.
configs[name] = discriminatedProviderSettingsWithIdSchema.parse(configs[name])
const parsedConfig = discriminatedProviderSettingsWithIdSchema.parse(configs[name])

// Handle gemini-specific fields - only ensure they exist for Gemini configs
if (parsedConfig.apiProvider === "gemini") {
// For Gemini, ensure these fields are always present with defaults if not provided
const geminiConfig = { ...parsedConfig } as any
geminiConfig.enableUrlContext = (configs[name] as any).enableUrlContext ?? true
geminiConfig.enableGrounding = (configs[name] as any).enableGrounding ?? true
configs[name] = geminiConfig
continue
}
// For non-Gemini providers, the discriminated schema will automatically filter out
// fields that don't belong to their schema, so we don't need to manually remove them

configs[name] = parsedConfig
}
return profiles
})
Expand Down Expand Up @@ -502,7 +591,23 @@ export class ProviderSettingsManager {
const apiConfigs = Object.entries(providerProfiles.apiConfigs).reduce(
(acc, [key, apiConfig]) => {
const result = providerSettingsWithIdSchema.safeParse(apiConfig)
return result.success ? { ...acc, [key]: result.data } : acc
if (!result.success) {
return acc
}

// Clean up enableUrlContext and enableGrounding fields for non-Gemini configs
const cleanedConfig = { ...result.data } as any
if (cleanedConfig.apiProvider !== "gemini") {
// For non-Gemini providers, remove these fields if they exist (cleanup old data)
if (cleanedConfig.enableUrlContext !== undefined) {
delete cleanedConfig.enableUrlContext
}
if (cleanedConfig.enableGrounding !== undefined) {
delete cleanedConfig.enableGrounding
}
}

return { ...acc, [key]: cleanedConfig }
},
{} as Record<string, ProviderSettingsWithId>,
)
Expand Down
Loading