| 32 | } |
| 33 | |
| 34 | function makeHarness(initial: KimiConfig): { |
| 35 | harness: FakeHarness; |
| 36 | current: () => KimiConfig; |
| 37 | setConfigCalls: Array<Partial<KimiConfig>>; |
| 38 | removeCalls: string[]; |
| 39 | } { |
| 40 | // `persisted` simulates the on-disk config; the real RPC's `removeProvider` |
| 41 | // reads from / writes to disk on every call (see |
| 42 | // `packages/agent-core/src/rpc/core-impl.ts removeKimiProvider`). Tests must |
| 43 | // model this: anything the handler builds up in its in-memory `config` |
| 44 | // object disappears unless it is flushed via `setConfig` BEFORE the next |
| 45 | // `removeProvider`. |
| 46 | let persisted: KimiConfig = structuredClone(initial); |
| 47 | const setConfigCalls: Array<Partial<KimiConfig>> = []; |
| 48 | const removeCalls: string[] = []; |
| 49 | const harness: FakeHarness = { |
| 50 | ensureConfigFile: async () => {}, |
| 51 | getConfig: async () => structuredClone(persisted), |
| 52 | setConfig: async (patch) => { |
| 53 | setConfigCalls.push(structuredClone(patch)); |
| 54 | // Mirror the real `setKimiConfig`: deep-merge with undefined keys |
| 55 | // skipped (see `agent-core/src/config/merge.ts deepMerge`). This is |
| 56 | // load-bearing for tests that assert `setConfig({defaultModel: |
| 57 | // undefined})` does NOT wipe a key from disk — only `removeProvider` |
| 58 | // can. |
| 59 | const next: Record<string, unknown> = { ...persisted }; |
| 60 | for (const [key, value] of Object.entries(patch)) { |
| 61 | if (value === undefined) continue; |
| 62 | next[key] = value; |
| 63 | } |
| 64 | persisted = next as KimiConfig; |
| 65 | return structuredClone(persisted); |
| 66 | }, |
| 67 | removeProvider: async (providerId) => { |
| 68 | removeCalls.push(providerId); |
| 69 | const nextProviders = { ...persisted.providers }; |
| 70 | delete nextProviders[providerId]; |
| 71 | const nextModels = { ...persisted.models }; |
| 72 | let removedDefault = false; |
| 73 | for (const [alias, model] of Object.entries(nextModels)) { |
| 74 | if (model.provider === providerId) { |
| 75 | delete nextModels[alias]; |
| 76 | if (persisted.defaultModel === alias) removedDefault = true; |
| 77 | } |
| 78 | } |
| 79 | persisted = { ...persisted, providers: nextProviders, models: nextModels }; |
| 80 | if (removedDefault) persisted = { ...persisted, defaultModel: undefined }; |
| 81 | return structuredClone(persisted); |
| 82 | }, |
| 83 | }; |
| 84 | return { |
| 85 | harness, |
| 86 | current: () => persisted, |
| 87 | setConfigCalls, |
| 88 | removeCalls, |
| 89 | }; |
| 90 | } |
| 91 | |