Files
KimiCodeSwitch/docs/superpowers/plans/2026-07-09-kimicode-global-settings-panel.md

938 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Kimi Code 全局配置面板与模型默认上下文 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a Kimi Code global settings panel inside ProviderEdit and auto-set `max_context_size` for new models based on a model-ID lookup table.
**Architecture:** Extend the shared `Config` type with typed agent settings, add two pure helper modules (`agent-settings.ts` for `raw_other` IO and `model-defaults.ts` for context lookup), and render the panel in a new React component wired through `ProviderEdit`.
**Tech Stack:** React 18, TypeScript, Tailwind CSS, custom i18n, Tauri v2 backend (no Rust changes).
---
### Task 1: Add agent settings types
**Files:**
- Modify: `src/types/index.ts`
- [ ] **Step 1: Append new types**
Insert at the end of `src/types/index.ts`:
```ts
export interface ThinkingConfig {
enabled?: boolean;
effort?: "low" | "medium" | "high" | "max";
keep?: "all" | false | 0 | "no" | "off" | "none" | null;
}
export interface LoopControlConfig {
max_retries_per_step?: number;
reserved_context_size?: number;
}
export interface BackgroundConfig {
max_running_tasks?: number;
keep_alive_on_exit?: boolean;
}
export interface PermissionRule {
decision?: "allow" | "deny" | "ask";
scope?: string;
pattern?: string;
reason?: string;
}
export interface Hook {
event?: string;
matcher?: string;
command?: string;
timeout?: number;
}
export interface AgentSettings {
thinking?: ThinkingConfig;
loop_control?: LoopControlConfig;
background?: BackgroundConfig;
permission?: { rules?: PermissionRule[] };
hooks?: Hook[];
}
```
- [ ] **Step 2: Commit**
```bash
git add src/types/index.ts
git commit -m "types: add AgentSettings and related Kimi Code config types"
```
---
### Task 2: Create model default context size lookup
**Files:**
- Create: `src/lib/model-defaults.ts`
- [ ] **Step 1: Create the lookup module**
Create `src/lib/model-defaults.ts`:
```ts
export const DEFAULT_MAX_CONTEXT_SIZE = 256000;
interface ModelContextRule {
pattern: RegExp;
max_context_size: number;
}
const MODEL_CONTEXT_RULES: ModelContextRule[] = [
// Kimi
{ pattern: /^kimi-for-coding$/i, max_context_size: 262144 },
{ pattern: /^kimi-k2\.5/i, max_context_size: 256000 },
{ pattern: /^kimi-k2/i, max_context_size: 256000 },
{ pattern: /^kimi-/i, max_context_size: 256000 },
// GLM
{ pattern: /^glm-5\.2/i, max_context_size: 1000000 },
{ pattern: /^glm-5\.1/i, max_context_size: 256000 },
{ pattern: /^glm-5/i, max_context_size: 256000 },
{ pattern: /^glm-4/i, max_context_size: 128000 },
{ pattern: /^glm-/i, max_context_size: 128000 },
// MiniMax
{ pattern: /^MiniMax-M3/i, max_context_size: 1000000 },
{ pattern: /^MiniMax-Text-01/i, max_context_size: 400000 },
{ pattern: /^MiniMax-/i, max_context_size: 256000 },
// Qwen
{ pattern: /^qwen2\.5/i, max_context_size: 128000 },
{ pattern: /^qwen-max/i, max_context_size: 128000 },
{ pattern: /^qwen-plus/i, max_context_size: 128000 },
{ pattern: /^qwen-turbo/i, max_context_size: 128000 },
{ pattern: /^qwen-coder/i, max_context_size: 128000 },
{ pattern: /^qwen-/i, max_context_size: 128000 },
// DeepSeek
{ pattern: /^deepseek-r1/i, max_context_size: 64000 },
{ pattern: /^deepseek-v3/i, max_context_size: 64000 },
{ pattern: /^deepseek-coder/i, max_context_size: 64000 },
{ pattern: /^deepseek-/i, max_context_size: 64000 },
// Hunyuan
{ pattern: /^hunyuan-pro/i, max_context_size: 32000 },
{ pattern: /^hunyuan-standard/i, max_context_size: 32000 },
{ pattern: /^hunyuan-lite/i, max_context_size: 32000 },
{ pattern: /^hunyuan-/i, max_context_size: 32000 },
// Doubao
{ pattern: /^doubao-pro/i, max_context_size: 128000 },
{ pattern: /^doubao-lite/i, max_context_size: 128000 },
{ pattern: /^doubao-vision/i, max_context_size: 128000 },
{ pattern: /^doubao-/i, max_context_size: 128000 },
// ERNIE
{ pattern: /^ernie-4\.0/i, max_context_size: 128000 },
{ pattern: /^ernie-3\.5/i, max_context_size: 128000 },
{ pattern: /^ernie-speed/i, max_context_size: 128000 },
{ pattern: /^ernie-lite/i, max_context_size: 128000 },
{ pattern: /^ernie-/i, max_context_size: 128000 },
// Spark
{ pattern: /^spark-v4/i, max_context_size: 32000 },
{ pattern: /^spark-v3\.5/i, max_context_size: 32000 },
{ pattern: /^spark-pro/i, max_context_size: 32000 },
{ pattern: /^spark-max/i, max_context_size: 32000 },
{ pattern: /^spark-/i, max_context_size: 32000 },
// SenseChat
{ pattern: /^sensechat-/i, max_context_size: 128000 },
// Baichuan
{ pattern: /^baichuan-4/i, max_context_size: 128000 },
{ pattern: /^baichuan-3/i, max_context_size: 128000 },
{ pattern: /^baichuan-/i, max_context_size: 128000 },
// Yi
{ pattern: /^yi-/i, max_context_size: 128000 },
// Claude
{ pattern: /^claude-opus/i, max_context_size: 200000 },
{ pattern: /^claude-sonnet/i, max_context_size: 200000 },
{ pattern: /^claude-haiku/i, max_context_size: 200000 },
{ pattern: /^claude-/i, max_context_size: 200000 },
// OpenAI
{ pattern: /^gpt-4\.1/i, max_context_size: 1047576 },
{ pattern: /^gpt-4o/i, max_context_size: 128000 },
{ pattern: /^gpt-4-turbo/i, max_context_size: 128000 },
{ pattern: /^gpt-4-/i, max_context_size: 128000 },
// Gemini
{ pattern: /^gemini-2\.0-flash/i, max_context_size: 1048576 },
{ pattern: /^gemini-1\.5-pro/i, max_context_size: 2097152 },
{ pattern: /^gemini-1\.5-flash/i, max_context_size: 1048576 },
{ pattern: /^gemini-/i, max_context_size: 1048576 },
];
export function getDefaultMaxContextSize(modelId: string): number {
for (const rule of MODEL_CONTEXT_RULES) {
if (rule.pattern.test(modelId)) {
return rule.max_context_size;
}
}
return DEFAULT_MAX_CONTEXT_SIZE;
}
```
- [ ] **Step 2: Commit**
```bash
git add src/lib/model-defaults.ts
git commit -m "feat: add model default max_context_size lookup table"
```
---
### Task 3: Create agent settings IO helpers
**Files:**
- Create: `src/lib/agent-settings.ts`
- [ ] **Step 1: Create helper module**
Create `src/lib/agent-settings.ts`:
```ts
import type { AgentSettings } from "../types";
const DEFAULT_SETTINGS: AgentSettings = {
thinking: {
enabled: true,
effort: "medium",
keep: "all",
},
loop_control: {
max_retries_per_step: 3,
reserved_context_size: 50000,
},
background: {
keep_alive_on_exit: false,
},
permission: { rules: [] },
hooks: [],
};
function asRecord(value: unknown): Record<string, unknown> {
if (value && typeof value === "object" && !Array.isArray(value)) {
return value as Record<string, unknown>;
}
return {};
}
function getSection<T>(rawOther: unknown, key: string): T | undefined {
const root = asRecord(rawOther);
const section = root[key];
if (section === undefined || section === null) return undefined;
return section as T;
}
export function getAgentSettings(rawOther: unknown): AgentSettings {
return {
thinking: {
...DEFAULT_SETTINGS.thinking,
...getSection<AgentSettings["thinking"]>(rawOther, "thinking"),
},
loop_control: {
...DEFAULT_SETTINGS.loop_control,
...getSection<AgentSettings["loop_control"]>(rawOther, "loop_control"),
},
background: {
...DEFAULT_SETTINGS.background,
...getSection<AgentSettings["background"]>(rawOther, "background"),
},
permission: {
rules: getSection<AgentSettings["permission"]>(rawOther, "permission")?.rules ?? [],
},
hooks: getSection<AgentSettings["hooks"]>(rawOther, "hooks") ?? [],
};
}
export function setAgentSettings(
rawOther: unknown,
patch: Partial<AgentSettings>
): unknown {
const root = { ...asRecord(rawOther) };
const current = getAgentSettings(rawOther);
const next: AgentSettings = {
thinking: { ...current.thinking, ...patch.thinking },
loop_control: { ...current.loop_control, ...patch.loop_control },
background: { ...current.background, ...patch.background },
permission: { rules: patch.permission?.rules ?? current.permission?.rules ?? [] },
hooks: patch.hooks ?? current.hooks ?? [],
};
if (next.thinking) root.thinking = next.thinking;
if (next.loop_control) root.loop_control = next.loop_control;
if (next.background) root.background = next.background;
if (next.permission?.rules && next.permission.rules.length > 0) {
root.permission = next.permission;
} else {
delete root.permission;
}
if (next.hooks && next.hooks.length > 0) {
root.hooks = next.hooks;
} else {
delete root.hooks;
}
return root;
}
```
- [ ] **Step 2: Commit**
```bash
git add src/lib/agent-settings.ts
git commit -m "feat: add agent settings read/write helpers"
```
---
### Task 4: Add i18n keys
**Files:**
- Modify: `src/i18n/zh.ts`
- Modify: `src/i18n/en.ts`
- [ ] **Step 1: Add Chinese keys**
Insert into `src/i18n/zh.ts` in the "Agent / target" section:
```ts
agentSettings: "全局配置",
enableThinking: "启用思考",
thinkingLevel: "思考等级",
thinkingKeep: "保留思考内容",
thinkingLow: "低",
thinkingMedium: "中",
thinkingHigh: "高",
thinkingMax: "最大",
thinkingContextHint: "启用思考会占用更多上下文,请确保模型上下文长度和预留空间足够。",
loopControlSettings: "循环控制",
maxRetriesPerStep: "单步重试次数",
reservedContextSize: "上下文预留大小",
backgroundSettings: "后台任务",
maxRunningTasks: "最大并发数",
keepAliveOnExit: "退出时保持运行",
permissionRules: "权限规则",
permissionDecision: "处置",
permissionPattern: "模式",
permissionAllow: "允许",
permissionDeny: "拒绝",
permissionAsk: "询问",
addRule: "+ 添加规则",
addCommonRules: "添加常用规则",
hooks: "生命周期钩子",
hookEvent: "事件",
hookMatcher: "匹配器",
hookCommand: "命令",
hookTimeout: "超时",
addHook: "+ 添加钩子",
```
- [ ] **Step 2: Add English keys**
Insert matching keys into `src/i18n/en.ts`:
```ts
agentSettings: "Global Settings",
enableThinking: "Enable thinking",
thinkingLevel: "Thinking level",
thinkingKeep: "Keep thinking content",
thinkingLow: "Low",
thinkingMedium: "Medium",
thinkingHigh: "High",
thinkingMax: "Max",
thinkingContextHint: "Thinking uses more context. Ensure the model context length and reserved size are sufficient.",
loopControlSettings: "Loop Control",
maxRetriesPerStep: "Max retries per step",
reservedContextSize: "Reserved context size",
backgroundSettings: "Background Tasks",
maxRunningTasks: "Max running tasks",
keepAliveOnExit: "Keep alive on exit",
permissionRules: "Permission Rules",
permissionDecision: "Decision",
permissionPattern: "Pattern",
permissionAllow: "Allow",
permissionDeny: "Deny",
permissionAsk: "Ask",
addRule: "+ Add rule",
addCommonRules: "Add common rules",
hooks: "Lifecycle Hooks",
hookEvent: "Event",
hookMatcher: "Matcher",
hookCommand: "Command",
hookTimeout: "Timeout",
addHook: "+ Add hook",
```
- [ ] **Step 3: Commit**
```bash
git add src/i18n/zh.ts src/i18n/en.ts
git commit -m "i18n: add agent settings panel translations"
```
---
### Task 5: Build the AgentSettingsPanel component
**Files:**
- Create: `src/components/AgentSettingsPanel.tsx`
- [ ] **Step 1: Create the panel component**
Create `src/components/AgentSettingsPanel.tsx`:
```tsx
import { useTranslation } from "../i18n";
import { getAgentSettings, setAgentSettings } from "../lib/agent-settings";
import type { AgentSettings, PermissionRule, Hook } from "../types";
import type { ReactNode } from "react";
import type { TranslationKey } from "../i18n/zh";
interface AgentSettingsPanelProps {
rawOther: unknown;
onChange: (nextRawOther: unknown) => void;
}
const THINKING_LEVELS = ["low", "medium", "high", "max"] as const;
const THINKING_LABELS: Record<(typeof THINKING_LEVELS)[number], TranslationKey> = {
low: "thinkingLow",
medium: "thinkingMedium",
high: "thinkingHigh",
max: "thinkingMax",
};
const PERMISSION_DECISIONS = ["allow", "deny", "ask"] as const;
const PERMISSION_LABELS: Record<(typeof PERMISSION_DECISIONS)[number], TranslationKey> = {
allow: "permissionAllow",
deny: "permissionDeny",
ask: "permissionAsk",
};
const COMMON_EVENTS = ["PreToolUse", "PostToolUse"] as const;
export function AgentSettingsPanel({ rawOther, onChange }: AgentSettingsPanelProps) {
const { t } = useTranslation();
const settings = getAgentSettings(rawOther);
const update = (patch: Partial<AgentSettings>) => {
onChange(setAgentSettings(rawOther, patch));
};
const updateThinking = (patch: Partial<AgentSettings["thinking"]>) => {
update({ thinking: { ...settings.thinking, ...patch } });
};
const updateLoopControl = (patch: Partial<AgentSettings["loop_control"]>) => {
update({ loop_control: { ...settings.loop_control, ...patch } });
};
const updateBackground = (patch: Partial<AgentSettings["background"]>) => {
update({ background: { ...settings.background, ...patch } });
};
const setRules = (rules: PermissionRule[]) => {
update({ permission: { rules } });
};
const setHooks = (hooks: Hook[]) => {
update({ hooks });
};
const thinkingEnabled = settings.thinking?.enabled ?? true;
return (
<div className="mt-6 space-y-4">
<h3 className="text-gray-400 text-sm font-medium">{t("agentSettings")}</h3>
{/* Thinking */}
<Card title={t("enableThinking")}>
<Checkbox
label={t("enableThinking")}
checked={thinkingEnabled}
onChange={(checked) => updateThinking({ enabled: checked })}
/>
<div className="flex items-center gap-3 flex-wrap">
<span className="text-sm text-gray-400">{t("thinkingLevel")}</span>
<Segmented
options={THINKING_LEVELS.map((lvl) => ({ key: lvl, label: t(THINKING_LABELS[lvl]) }))}
value={settings.thinking?.effort ?? "medium"}
onChange={(effort) => updateThinking({ effort })}
disabled={!thinkingEnabled}
/>
</div>
<Checkbox
label={t("thinkingKeep")}
checked={settings.thinking?.keep === "all"}
disabled={!thinkingEnabled}
onChange={(checked) => updateThinking({ keep: checked ? "all" : false })}
/>
<p className="text-xs text-gray-500">{t("thinkingContextHint")}</p>
</Card>
{/* Loop Control */}
<Card title={t("loopControlSettings")}>
<NumberField
label={t("maxRetriesPerStep")}
value={settings.loop_control?.max_retries_per_step ?? 3}
onChange={(v) => updateLoopControl({ max_retries_per_step: v })}
/>
<NumberField
label={t("reservedContextSize")}
value={settings.loop_control?.reserved_context_size ?? 50000}
onChange={(v) => updateLoopControl({ reserved_context_size: v })}
/>
</Card>
{/* Background */}
<Card title={t("backgroundSettings")}>
<NumberField
label={t("maxRunningTasks")}
value={settings.background?.max_running_tasks}
onChange={(v) => updateBackground({ max_running_tasks: v })}
/>
<Checkbox
label={t("keepAliveOnExit")}
checked={settings.background?.keep_alive_on_exit ?? false}
onChange={(checked) => updateBackground({ keep_alive_on_exit: checked })}
/>
</Card>
{/* Permission Rules */}
<Card title={t("permissionRules")}>
<div className="space-y-2">
{(settings.permission?.rules ?? []).map((rule, idx) => (
<div key={idx} className="flex items-center gap-2">
<select
className="bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={rule.decision ?? "allow"}
onChange={(e) => {
const rules = [...(settings.permission?.rules ?? [])];
rules[idx] = { ...rule, decision: e.target.value as PermissionRule["decision"] };
setRules(rules);
}}
>
{PERMISSION_DECISIONS.map((d) => (
<option key={d} value={d}>{t(PERMISSION_LABELS[d])}</option>
))}
</select>
<input
type="text"
className="flex-1 min-w-0 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={rule.pattern ?? ""}
placeholder={t("permissionPattern")}
onChange={(e) => {
const rules = [...(settings.permission?.rules ?? [])];
rules[idx] = { ...rule, pattern: e.target.value };
setRules(rules);
}}
/>
<button
type="button"
onClick={() => {
const rules = [...(settings.permission?.rules ?? [])];
rules.splice(idx, 1);
setRules(rules);
}}
className="px-2 py-1 text-sm text-red-400 hover:bg-red-900/20 rounded"
>
×
</button>
</div>
))}
</div>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => {
setRules([...(settings.permission?.rules ?? []), { decision: "allow", pattern: "" }]);
}}
className="px-3 py-1.5 text-sm border border-[#2a2a2e] rounded hover:bg-[#252529]"
>
{t("addRule")}
</button>
<button
type="button"
onClick={() => {
setRules([
...(settings.permission?.rules ?? []),
{ decision: "allow", pattern: "Read" },
{ decision: "deny", pattern: "Bash(rm -rf*)" },
]);
}}
className="px-3 py-1.5 text-sm border border-[#2a2a2e] rounded hover:bg-[#252529]"
>
{t("addCommonRules")}
</button>
</div>
</Card>
{/* Hooks */}
<Card title={t("hooks")}>
<div className="space-y-2">
{(settings.hooks ?? []).map((hook, idx) => (
<div key={idx} className="flex items-center gap-2 flex-wrap">
<input
list="hook-events"
type="text"
className="w-32 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={hook.event ?? ""}
placeholder={t("hookEvent")}
onChange={(e) => {
const hooks = [...(settings.hooks ?? [])];
hooks[idx] = { ...hook, event: e.target.value };
setHooks(hooks);
}}
/>
<datalist id="hook-events">
{COMMON_EVENTS.map((e) => <option key={e} value={e} />)}
</datalist>
<input
type="text"
className="w-24 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={hook.matcher ?? ""}
placeholder={t("hookMatcher")}
onChange={(e) => {
const hooks = [...(settings.hooks ?? [])];
hooks[idx] = { ...hook, matcher: e.target.value };
setHooks(hooks);
}}
/>
<input
type="text"
className="flex-1 min-w-0 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={hook.command ?? ""}
placeholder={t("hookCommand")}
onChange={(e) => {
const hooks = [...(settings.hooks ?? [])];
hooks[idx] = { ...hook, command: e.target.value };
setHooks(hooks);
}}
/>
<input
type="number"
className="w-20 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm"
value={hook.timeout ?? ""}
placeholder={t("hookTimeout")}
onChange={(e) => {
const hooks = [...(settings.hooks ?? [])];
hooks[idx] = { ...hook, timeout: e.target.value === "" ? undefined : Number(e.target.value) };
setHooks(hooks);
}}
/>
<button
type="button"
onClick={() => {
const hooks = [...(settings.hooks ?? [])];
hooks.splice(idx, 1);
setHooks(hooks);
}}
className="px-2 py-1 text-sm text-red-400 hover:bg-red-900/20 rounded"
>
×
</button>
</div>
))}
</div>
<button
type="button"
onClick={() => {
setHooks([...(settings.hooks ?? []), { event: "", matcher: "", command: "" }]);
}}
className="px-3 py-1.5 text-sm border border-[#2a2a2e] rounded hover:bg-[#252529]"
>
{t("addHook")}
</button>
</Card>
</div>
);
}
function Card({ title, children }: { title: string; children: ReactNode }) {
return (
<div className="bg-[#16161a] border border-[#2a2a2e] rounded-xl p-4 space-y-3">
<h4 className="text-gray-400 text-sm font-medium">{title}</h4>
{children}
</div>
);
}
function Checkbox({
label,
checked,
disabled,
onChange,
}: {
label: string;
checked: boolean;
disabled?: boolean;
onChange: (checked: boolean) => void;
}) {
return (
<label className={`flex items-center gap-2 text-sm ${disabled ? "text-gray-600" : "text-[#e5e5e7]"}`}>
<input
type="checkbox"
checked={checked}
disabled={disabled}
onChange={(e) => onChange(e.target.checked)}
className="rounded border-[#2a2a2e] bg-[#1f1f23] text-blue-600 focus:ring-blue-500"
/>
{label}
</label>
);
}
function NumberField({
label,
value,
onChange,
}: {
label: string;
value?: number;
onChange: (value: number | undefined) => void;
}) {
return (
<label className="flex items-center gap-3 text-sm text-[#e5e5e7]">
<span className="text-gray-400">{label}</span>
<input
type="number"
value={value ?? ""}
onChange={(e) => {
const raw = e.target.value;
onChange(raw === "" ? undefined : Number(raw));
}}
className="w-32 bg-[#1f1f23] border border-[#2a2a2e] rounded px-2 py-1 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
/>
</label>
);
}
function Segmented({
options,
value,
onChange,
disabled,
}: {
options: { key: string; label: string }[];
value: string;
onChange: (value: string) => void;
disabled?: boolean;
}) {
return (
<div className={`flex items-center rounded overflow-hidden border border-[#2a2a2e] ${disabled ? "opacity-50" : ""}`}>
{options.map((opt) => (
<button
key={opt.key}
type="button"
disabled={disabled}
onClick={() => onChange(opt.key)}
className={`px-3 py-1 text-sm ${
value === opt.key
? "bg-blue-600 text-white"
: "bg-[#1f1f23] text-gray-400 hover:bg-[#252529]"
}`}
>
{opt.label}
</button>
))}
</div>
);
}
```
- [ ] **Step 2: Commit**
```bash
git add src/components/AgentSettingsPanel.tsx
git commit -m "feat: add AgentSettingsPanel component"
```
---
### Task 6: Wire AgentSettingsPanel into ProviderEdit
**Files:**
- Modify: `src/components/ProviderEdit.tsx`
- [ ] **Step 1: Update ProviderEditProps**
Add `rawOther` and `onRawOtherChange` to the props interface:
```tsx
interface ProviderEditProps {
agent: Agent;
provider: Provider;
models: Model[];
defaultModel: string | null;
rawOther: unknown;
onRawOtherChange: (nextRawOther: unknown) => void;
onBack: () => void;
// ... existing callbacks
}
```
- [ ] **Step 2: Destructure new props**
```tsx
export function ProviderEdit({
agent,
provider,
models,
defaultModel,
rawOther,
onRawOtherChange,
onBack,
// ... existing
}: ProviderEditProps) {
```
- [ ] **Step 3: Render the panel in the model mapping tab**
Locate the model mapping tab content (around the model table and "+ 添加模型映射" button) and insert `<AgentSettingsPanel />` at the bottom, conditionally for Kimi Code:
```tsx
{agent === "kimi_code" && (
<AgentSettingsPanel rawOther={rawOther} onChange={onRawOtherChange} />
)}
```
- [ ] **Step 4: Commit**
```bash
git add src/components/ProviderEdit.tsx
git commit -m "feat: render AgentSettingsPanel in ProviderEdit for Kimi Code"
```
---
### Task 7: Wire ProviderEdit to App.tsx
**Files:**
- Modify: `src/App.tsx`
- [ ] **Step 1: Pass rawOther and updater to ProviderEdit**
Find the `<ProviderEdit ... />` call and add:
```tsx
<ProviderEdit
agent={agent}
provider={currentProvider}
models={...}
defaultModel={config.default_model}
rawOther={config.raw_other}
onRawOtherChange={(nextRawOther) =>
updateConfig((cfg) => ({ ...cfg, raw_other: nextRawOther }))
}
// ... existing props
/>
```
- [ ] **Step 2: Commit**
```bash
git add src/App.tsx
git commit -m "chore: pass rawOther to ProviderEdit"
```
---
### Task 8: Apply smart max_context_size defaults when adding models
**Files:**
- Modify: `src/App.tsx`
- [ ] **Step 1: Import helper in App.tsx**
In `src/App.tsx`, import:
```tsx
import { getDefaultMaxContextSize } from "./lib/model-defaults";
```
- [ ] **Step 2: Use lookup in onModelAdd**
Find the `onModelAdd` prop passed to `<ProviderEdit />` and update the default `max_context_size`:
```tsx
onModelAdd={() => {
const providerModels = Object.values(config.models).filter(
(m) => m.provider === currentProvider.name
);
const alias = `${currentProvider.name}-${providerModels.length + 1}`;
updateConfig((cfg) => ({
...cfg,
models: {
...cfg.models,
[alias]: {
alias,
provider: currentProvider.name,
model: "",
max_context_size: getDefaultMaxContextSize(alias),
display_name: null,
role: null,
supports_1m: false,
capabilities: [],
},
},
}));
}}
```
- [ ] **Step 3: Use lookup in handleApplyProviderJson**
Update the model creation loop in `handleApplyProviderJson`:
```tsx
updatedModels[alias] = {
...m,
alias,
provider: provider.name,
max_context_size: m.max_context_size ?? getDefaultMaxContextSize(m.model),
};
```
- [ ] **Step 4: Commit**
```bash
git add src/App.tsx
git commit -m "feat: apply smart default max_context_size when adding models"
```
---
### Task 9: Verify with build and manual checks
- [ ] **Step 1: Type check**
```bash
npm run build
```
Expected: `tsc` and Vite build succeed.
- [ ] **Step 2: Run the app**
```bash
npm run tauri-dev
```
- [ ] **Step 3: Manual verification**
1. Kimi Code tab → edit a provider → "模型映射" tab → scroll to bottom.
2. Confirm "全局配置" panel shows 5 cards.
3. Toggle "启用思考" and verify effort/keep enable/disable.
4. Change thinking effort, save, open `~/.kimi-code/config.toml`, verify `[thinking]` block.
5. Add a permission rule and a hook, save, verify `[[permission.rules]]` and `[[hooks]]` in TOML.
6. Add a new model with alias `glm-5.2-xxx`, verify `max_context_size` defaults to `1000000`.
7. Add a new model with unknown alias, verify `max_context_size` defaults to `256000`.
8. Switch to Pi tab, confirm global settings panel is not visible.
- [ ] **Step 4: Commit any fixes**
If any fixes were needed during verification, commit them.
---
## Self-Review
- **Spec coverage:** Every requirement (5 cards, model defaults, i18n, Kimi-only visibility, manual override) maps to a task.
- **Placeholder scan:** No TBD/TODO/fill-in-details; all code is shown.
- **Type consistency:** `AgentSettings`, helper signatures, and component props match across tasks.