2026-02-01 16:47:50 +09:00
|
|
|
import type { CategoryConfig, CategoriesConfig } from "../../config/schema"
|
|
|
|
|
import { DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS } from "./constants"
|
2026-02-08 18:03:15 +09:00
|
|
|
import { resolveModel } from "../../shared/model-resolver"
|
2026-02-01 16:47:50 +09:00
|
|
|
import { isModelAvailable } from "../../shared/model-availability"
|
2026-03-23 20:39:47 +09:00
|
|
|
import { normalizeModel } from "../../shared/model-normalization"
|
2026-02-01 16:47:50 +09:00
|
|
|
import { CATEGORY_MODEL_REQUIREMENTS } from "../../shared/model-requirements"
|
2026-02-08 18:03:15 +09:00
|
|
|
import { log } from "../../shared/logger"
|
2026-02-01 16:47:50 +09:00
|
|
|
|
|
|
|
|
export interface ResolveCategoryConfigOptions {
|
|
|
|
|
userCategories?: CategoriesConfig
|
|
|
|
|
inheritedModel?: string
|
|
|
|
|
systemDefaultModel?: string
|
|
|
|
|
availableModels?: Set<string>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ResolveCategoryConfigResult {
|
|
|
|
|
config: CategoryConfig
|
|
|
|
|
promptAppend: string
|
|
|
|
|
model: string | undefined
|
2026-03-23 20:39:47 +09:00
|
|
|
isUserConfiguredModel: boolean
|
2026-02-01 16:47:50 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve the configuration for a given category name.
|
|
|
|
|
* Merges default and user configurations, handles model resolution.
|
|
|
|
|
*/
|
|
|
|
|
export function resolveCategoryConfig(
|
|
|
|
|
categoryName: string,
|
|
|
|
|
options: ResolveCategoryConfigOptions
|
|
|
|
|
): ResolveCategoryConfigResult | null {
|
2026-02-16 22:12:21 +09:00
|
|
|
const { userCategories, inheritedModel: _inheritedModel, systemDefaultModel, availableModels } = options
|
2026-02-01 16:47:50 +09:00
|
|
|
|
2026-02-01 19:26:57 +09:00
|
|
|
const defaultConfig = DEFAULT_CATEGORIES[categoryName]
|
|
|
|
|
const userConfig = userCategories?.[categoryName]
|
|
|
|
|
const hasExplicitUserConfig = userConfig !== undefined
|
|
|
|
|
|
2026-02-11 13:52:06 +09:00
|
|
|
if (userConfig?.disable) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-01 16:47:50 +09:00
|
|
|
const categoryReq = CATEGORY_MODEL_REQUIREMENTS[categoryName]
|
2026-02-01 19:26:57 +09:00
|
|
|
if (categoryReq?.requiresModel && availableModels && !hasExplicitUserConfig) {
|
2026-02-01 16:47:50 +09:00
|
|
|
if (!isModelAvailable(categoryReq.requiresModel, availableModels)) {
|
|
|
|
|
log(`[resolveCategoryConfig] Category ${categoryName} requires ${categoryReq.requiresModel} but not available`)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const defaultPromptAppend = CATEGORY_PROMPT_APPENDS[categoryName] ?? ""
|
|
|
|
|
|
|
|
|
|
if (!defaultConfig && !userConfig) {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Model priority for categories: user override > category default > system default
|
|
|
|
|
// Categories have explicit models - no inheritance from parent session
|
|
|
|
|
const model = resolveModel({
|
|
|
|
|
userModel: userConfig?.model,
|
|
|
|
|
inheritedModel: defaultConfig?.model, // Category's built-in model takes precedence over system default
|
|
|
|
|
systemDefault: systemDefaultModel,
|
|
|
|
|
})
|
2026-03-23 20:39:47 +09:00
|
|
|
const isUserConfiguredModel = normalizeModel(userConfig?.model) !== undefined
|
2026-02-01 16:47:50 +09:00
|
|
|
const config: CategoryConfig = {
|
|
|
|
|
...defaultConfig,
|
|
|
|
|
...userConfig,
|
|
|
|
|
model,
|
|
|
|
|
variant: userConfig?.variant ?? defaultConfig?.variant,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let promptAppend = defaultPromptAppend
|
|
|
|
|
if (userConfig?.prompt_append) {
|
|
|
|
|
promptAppend = defaultPromptAppend
|
|
|
|
|
? defaultPromptAppend + "\n\n" + userConfig.prompt_append
|
|
|
|
|
: userConfig.prompt_append
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 20:39:47 +09:00
|
|
|
return { config, promptAppend, model, isUserConfiguredModel }
|
2026-02-01 16:47:50 +09:00
|
|
|
}
|