This commit restructures the file search settings. The previously individual settings for file search method, regex search backend, match search backend, and ripgrep path have been grouped into a new filesSearchSettings object.
427 lines
12 KiB
TypeScript
427 lines
12 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
import { DEFAULT_MODELS } from '../constants';
|
|
import {
|
|
MAX_DELAY,
|
|
MAX_MAX_CHAR_LIMIT,
|
|
MIN_DELAY,
|
|
MIN_MAX_CHAR_LIMIT,
|
|
fewShotExampleSchema,
|
|
modelOptionsSchema
|
|
} from '../settings/versions/shared';
|
|
import { DEFAULT_SETTINGS } from "../settings/versions/v1/v1";
|
|
import { ApiProvider } from '../types/llm/model';
|
|
import { isRegexValid, isValidIgnorePattern } from '../utils/auto-complete';
|
|
|
|
export const SETTINGS_SCHEMA_VERSION = 0.4
|
|
|
|
const InfioProviderSchema = z.object({
|
|
name: z.literal('Infio'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Infio',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const OpenRouterProviderSchema = z.object({
|
|
name: z.literal('OpenRouter'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'OpenRouter',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const SiliconFlowProviderSchema = z.object({
|
|
name: z.literal('SiliconFlow'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'SiliconFlow',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const AlibabaQwenProviderSchema = z.object({
|
|
name: z.literal('AlibabaQwen'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'AlibabaQwen',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const AnthropicProviderSchema = z.object({
|
|
name: z.literal('Anthropic'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().optional(),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Anthropic',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const DeepSeekProviderSchema = z.object({
|
|
name: z.literal('DeepSeek'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'DeepSeek',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const GoogleProviderSchema = z.object({
|
|
name: z.literal('Google'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Google',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const OpenAIProviderSchema = z.object({
|
|
name: z.literal('OpenAI'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().optional(),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'OpenAI',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const OpenAICompatibleProviderSchema = z.object({
|
|
name: z.literal('OpenAICompatible'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().optional(),
|
|
useCustomUrl: z.boolean().catch(true)
|
|
}).catch({
|
|
name: 'OpenAICompatible',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: true
|
|
})
|
|
|
|
const OllamaProviderSchema = z.object({
|
|
name: z.literal('Ollama'),
|
|
apiKey: z.string().catch('ollama'),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Ollama',
|
|
apiKey: 'ollama',
|
|
baseUrl: '',
|
|
useCustomUrl: true
|
|
})
|
|
|
|
const GroqProviderSchema = z.object({
|
|
name: z.literal('Groq'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Groq',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const GrokProviderSchema = z.object({
|
|
name: z.literal('Grok'),
|
|
apiKey: z.string().catch(''),
|
|
baseUrl: z.string().catch(''),
|
|
useCustomUrl: z.boolean().catch(false)
|
|
}).catch({
|
|
name: 'Grok',
|
|
apiKey: '',
|
|
baseUrl: '',
|
|
useCustomUrl: false
|
|
})
|
|
|
|
const ollamaModelSchema = z.object({
|
|
baseUrl: z.string().catch(''),
|
|
model: z.string().catch(''),
|
|
})
|
|
|
|
const openAICompatibleModelSchema = z.object({
|
|
baseUrl: z.string().catch(''),
|
|
apiKey: z.string().catch(''),
|
|
model: z.string().catch(''),
|
|
})
|
|
|
|
const ragOptionsSchema = z.object({
|
|
chunkSize: z.number().catch(1000),
|
|
thresholdTokens: z.number().catch(8192),
|
|
minSimilarity: z.number().catch(0.0),
|
|
limit: z.number().catch(10),
|
|
excludePatterns: z.array(z.string()).catch([]),
|
|
includePatterns: z.array(z.string()).catch([]),
|
|
})
|
|
|
|
export const triggerSchema = z.object({
|
|
type: z.enum(['string', 'regex']),
|
|
value: z.string().min(1, { message: "Trigger value must be at least 1 character long" })
|
|
}).strict().superRefine((trigger, ctx) => {
|
|
if (trigger.type === "regex") {
|
|
if (!trigger.value.endsWith("$")) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: "Regex triggers must end with a $.",
|
|
path: ["value"],
|
|
});
|
|
}
|
|
if (!isRegexValid(trigger.value)) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: `Invalid regex: "${trigger.value}"`,
|
|
path: ["value"],
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const FilesSearchSettingsSchema = z.object({
|
|
method: z.enum(['match', 'regex', 'semantic', 'auto']).catch('auto'),
|
|
regexBackend: z.enum(['coreplugin', 'ripgrep']).catch('ripgrep'),
|
|
matchBackend: z.enum(['omnisearch', 'coreplugin']).catch('coreplugin'),
|
|
ripgrepPath: z.string().catch(''),
|
|
}).catch({
|
|
method: 'auto',
|
|
regexBackend: 'ripgrep',
|
|
matchBackend: 'coreplugin',
|
|
ripgrepPath: '',
|
|
});
|
|
|
|
export const InfioSettingsSchema = z.object({
|
|
// Version
|
|
version: z.literal(SETTINGS_SCHEMA_VERSION).catch(SETTINGS_SCHEMA_VERSION),
|
|
|
|
// Provider
|
|
defaultProvider: z.nativeEnum(ApiProvider).catch(ApiProvider.OpenRouter),
|
|
infioProvider: InfioProviderSchema,
|
|
openrouterProvider: OpenRouterProviderSchema,
|
|
siliconflowProvider: SiliconFlowProviderSchema,
|
|
alibabaQwenProvider: AlibabaQwenProviderSchema,
|
|
anthropicProvider: AnthropicProviderSchema,
|
|
deepseekProvider: DeepSeekProviderSchema,
|
|
openaiProvider: OpenAIProviderSchema,
|
|
googleProvider: GoogleProviderSchema,
|
|
ollamaProvider: OllamaProviderSchema,
|
|
groqProvider: GroqProviderSchema,
|
|
grokProvider: GrokProviderSchema,
|
|
openaicompatibleProvider: OpenAICompatibleProviderSchema,
|
|
|
|
// MCP Servers
|
|
mcpEnabled: z.boolean().catch(false),
|
|
|
|
// Chat Model start list
|
|
collectedChatModels: z.array(z.object({
|
|
provider: z.nativeEnum(ApiProvider),
|
|
modelId: z.string(),
|
|
})).catch([]),
|
|
|
|
// Chat Model
|
|
chatModelProvider: z.nativeEnum(ApiProvider).catch(ApiProvider.OpenRouter),
|
|
chatModelId: z.string().catch(''),
|
|
|
|
// Apply Model
|
|
applyModelProvider: z.nativeEnum(ApiProvider).catch(ApiProvider.OpenRouter),
|
|
applyModelId: z.string().catch(''),
|
|
|
|
// Embedding Model
|
|
embeddingModelProvider: z.nativeEnum(ApiProvider).catch(ApiProvider.Google),
|
|
embeddingModelId: z.string().catch(''),
|
|
|
|
// fuzzyMatchThreshold
|
|
fuzzyMatchThreshold: z.number().catch(0.85),
|
|
|
|
// experimentalDiffStrategy
|
|
experimentalDiffStrategy: z.boolean().catch(false),
|
|
|
|
// multiSearchReplaceDiffStrategy
|
|
multiSearchReplaceDiffStrategy: z.boolean().catch(true),
|
|
|
|
// Mode
|
|
mode: z.string().catch('ask'),
|
|
defaultMention: z.enum(['none', 'current-file', 'vault']).catch('none'),
|
|
|
|
// web search
|
|
serperApiKey: z.string().catch(''),
|
|
serperSearchEngine: z.enum(['google', 'duckduckgo', 'bing']).catch('google'),
|
|
jinaApiKey: z.string().catch(''),
|
|
|
|
// Files Search
|
|
filesSearchSettings: FilesSearchSettingsSchema,
|
|
|
|
/// [compatible]
|
|
// activeModels [compatible]
|
|
activeModels: z.array(
|
|
z.object({
|
|
name: z.string(),
|
|
provider: z.string(),
|
|
enabled: z.boolean(),
|
|
isEmbeddingModel: z.boolean(),
|
|
isBuiltIn: z.boolean(),
|
|
apiKey: z.string().optional(),
|
|
baseUrl: z.string().optional(),
|
|
dimension: z.number().optional(),
|
|
})
|
|
).catch(DEFAULT_MODELS),
|
|
// API Keys [compatible]
|
|
infioApiKey: z.string().catch(''),
|
|
openAIApiKey: z.string().catch(''),
|
|
anthropicApiKey: z.string().catch(''),
|
|
geminiApiKey: z.string().catch(''),
|
|
groqApiKey: z.string().catch(''),
|
|
deepseekApiKey: z.string().catch(''),
|
|
ollamaEmbeddingModel: ollamaModelSchema.catch({
|
|
baseUrl: '',
|
|
model: '',
|
|
}),
|
|
ollamaChatModel: ollamaModelSchema.catch({
|
|
baseUrl: '',
|
|
model: '',
|
|
}),
|
|
openAICompatibleChatModel: openAICompatibleModelSchema.catch({
|
|
baseUrl: '',
|
|
apiKey: '',
|
|
model: '',
|
|
}),
|
|
ollamaApplyModel: ollamaModelSchema.catch({
|
|
baseUrl: '',
|
|
model: '',
|
|
}),
|
|
openAICompatibleApplyModel: openAICompatibleModelSchema.catch({
|
|
baseUrl: '',
|
|
apiKey: '',
|
|
model: '',
|
|
}),
|
|
|
|
// System Prompt
|
|
systemPrompt: z.string().catch(''),
|
|
|
|
// RAG Options
|
|
ragOptions: ragOptionsSchema.catch({
|
|
chunkSize: 1000,
|
|
thresholdTokens: 8192,
|
|
minSimilarity: 0.0,
|
|
limit: 10,
|
|
excludePatterns: [],
|
|
includePatterns: [],
|
|
}),
|
|
|
|
// autocomplete options
|
|
autocompleteEnabled: z.boolean(),
|
|
advancedMode: z.boolean(),
|
|
|
|
// [compatible]
|
|
apiProvider: z.enum(['azure', 'openai', "ollama"]),
|
|
azureOAIApiSettings: z.string().catch(''),
|
|
openAIApiSettings: z.string().catch(''),
|
|
ollamaApiSettings: z.string().catch(''),
|
|
|
|
triggers: z.array(triggerSchema),
|
|
delay: z.number().int().min(MIN_DELAY, { message: "Delay must be between 0ms and 2000ms" }).max(MAX_DELAY, { message: "Delay must be between 0ms and 2000ms" }),
|
|
modelOptions: modelOptionsSchema,
|
|
systemMessage: z.string().min(3, { message: "System message must be at least 3 characters long" }),
|
|
fewShotExamples: z.array(fewShotExampleSchema),
|
|
userMessageTemplate: z.string().min(3, { message: "User message template must be at least 3 characters long" }),
|
|
chainOfThoughRemovalRegex: z.string().refine((regex) => isRegexValid(regex), { message: "Invalid regex" }),
|
|
dontIncludeDataviews: z.boolean(),
|
|
maxPrefixCharLimit: z.number().int().min(MIN_MAX_CHAR_LIMIT, { message: `Max prefix char limit must be at least ${MIN_MAX_CHAR_LIMIT}` }).max(MAX_MAX_CHAR_LIMIT, { message: `Max prefix char limit must be at most ${MAX_MAX_CHAR_LIMIT}` }),
|
|
maxSuffixCharLimit: z.number().int().min(MIN_MAX_CHAR_LIMIT, { message: `Max prefix char limit must be at least ${MIN_MAX_CHAR_LIMIT}` }).max(MAX_MAX_CHAR_LIMIT, { message: `Max prefix char limit must be at most ${MAX_MAX_CHAR_LIMIT}` }),
|
|
removeDuplicateMathBlockIndicator: z.boolean(),
|
|
removeDuplicateCodeBlockIndicator: z.boolean(),
|
|
ignoredFilePatterns: z.string().refine((value) => value
|
|
.split("\n")
|
|
.filter(s => s.trim().length > 0)
|
|
.filter(s => !isValidIgnorePattern(s)).length === 0,
|
|
{ message: "Invalid ignore pattern" }
|
|
),
|
|
ignoredTags: z.string().refine((value) => value
|
|
.split("\n")
|
|
.filter(s => s.includes(" ")).length === 0, { message: "Tags cannot contain spaces" }
|
|
).refine((value) => value
|
|
.split("\n")
|
|
.filter(s => s.includes("#")).length === 0, { message: "Enter tags without the # symbol" }
|
|
).refine((value) => value
|
|
.split("\n")
|
|
.filter(s => s.includes(",")).length === 0, { message: "Enter each tag on a new line without commas" }
|
|
),
|
|
cacheSuggestions: z.boolean(),
|
|
debugMode: z.boolean(),
|
|
})
|
|
|
|
export type InfioSettings = z.infer<typeof InfioSettingsSchema>
|
|
export type FilesSearchSettings = z.infer<typeof FilesSearchSettingsSchema>
|
|
|
|
type Migration = {
|
|
fromVersion: number
|
|
toVersion: number
|
|
migrate: (data: Record<string, unknown>) => Record<string, unknown>
|
|
}
|
|
|
|
const MIGRATIONS: Migration[] = [
|
|
{
|
|
fromVersion: 0.1,
|
|
toVersion: 0.4,
|
|
migrate: (data) => {
|
|
const newData = { ...data }
|
|
newData.version = SETTINGS_SCHEMA_VERSION
|
|
return newData
|
|
},
|
|
},
|
|
]
|
|
|
|
function migrateSettings(
|
|
data: Record<string, unknown>,
|
|
): Record<string, unknown> {
|
|
let currentData = { ...data }
|
|
const currentVersion = (currentData.version as number) ?? 0
|
|
|
|
for (const migration of MIGRATIONS) {
|
|
if (
|
|
currentVersion >= migration.fromVersion &&
|
|
currentVersion < migration.toVersion &&
|
|
migration.toVersion <= SETTINGS_SCHEMA_VERSION
|
|
) {
|
|
console.log(
|
|
`Migrating settings from ${migration.fromVersion} to ${migration.toVersion}`,
|
|
)
|
|
currentData = migration.migrate(currentData)
|
|
}
|
|
}
|
|
|
|
return currentData
|
|
}
|
|
|
|
export function parseInfioSettings(data: unknown): InfioSettings {
|
|
try {
|
|
const migratedData = migrateSettings(data as Record<string, unknown>)
|
|
return InfioSettingsSchema.parse(migratedData)
|
|
} catch (error) {
|
|
return InfioSettingsSchema.parse({ ...DEFAULT_SETTINGS })
|
|
}
|
|
}
|