(input: ConfigStepInput)
| 191 | } |
| 192 | |
| 193 | export async function migrateConfigStep(input: ConfigStepInput): Promise<ConfigStepResult> { |
| 194 | let oldText: string; |
| 195 | try { |
| 196 | oldText = await readFile(sourceConfigToml(input.sourceHome), 'utf-8'); |
| 197 | } catch { |
| 198 | return emptyResult(); |
| 199 | } |
| 200 | |
| 201 | let parsedRaw: unknown; |
| 202 | try { |
| 203 | parsedRaw = parseToml(oldText); |
| 204 | } catch { |
| 205 | // Malformed legacy config.toml: skip config migration rather than aborting |
| 206 | // the whole run. sessions/MCP/history still migrate. |
| 207 | return emptyResult(); |
| 208 | } |
| 209 | const parsed: Record<string, unknown> = isRecord(parsedRaw) ? parsedRaw : {}; |
| 210 | |
| 211 | // Decide how the target config.toml is handled: a missing or pristine-stub |
| 212 | // target is overwritten; a parseable user config is merged into; an |
| 213 | // unparseable target falls back to a side file (it cannot be merged). |
| 214 | const configPath = targetConfigFile(input.targetHome); |
| 215 | let targetText: string | undefined; |
| 216 | try { |
| 217 | targetText = await readFile(configPath, 'utf-8'); |
| 218 | } catch { |
| 219 | targetText = undefined; |
| 220 | } |
| 221 | let targetMode: 'overwrite' | 'merge' | 'sibling'; |
| 222 | let targetParsed: Record<string, unknown> = {}; |
| 223 | if (targetText === undefined || targetText === DEFAULT_CONFIG_FILE_TEXT) { |
| 224 | targetMode = 'overwrite'; |
| 225 | } else { |
| 226 | try { |
| 227 | const tp: unknown = parseToml(targetText); |
| 228 | targetParsed = isRecord(tp) ? tp : {}; |
| 229 | targetMode = 'merge'; |
| 230 | } catch { |
| 231 | targetMode = 'sibling'; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // Provider names available to migrated models: those kept by this run, plus |
| 236 | // any already present in the target config being merged into. |
| 237 | const availableProviderNames = new Set<string>( |
| 238 | isRecord(targetParsed['providers']) ? Object.keys(targetParsed['providers']) : [], |
| 239 | ); |
| 240 | |
| 241 | // Model alias names already present in the target config being merged into — |
| 242 | // a migrated `default_model` may legitimately point at one of these. |
| 243 | const availableModelNames = new Set<string>( |
| 244 | isRecord(targetParsed['models']) ? Object.keys(targetParsed['models']) : [], |
| 245 | ); |
| 246 | |
| 247 | // 1) Providers — keep only those kimi-code's schema accepts. |
| 248 | const droppedProviders: string[] = []; |
| 249 | const keptProviders: Record<string, Record<string, unknown>> = {}; |
| 250 | if (isRecord(parsed['providers'])) { |
no test coverage detected