Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,12 @@ Inside a project's Studio, the same design system streams out multiple artifact
| [Pi Agent](https://github.com/badlogic/pi-mono) | ✅ Supported | `od mcp install pi` |
| [Mistral Vibe CLI](https://github.com/mistralai/mistral-vibe) | ✅ Supported | `od mcp install vibe` |
| [Hermes Agent](https://github.com/nousresearch/hermes-agent) | ✅ Supported | `od mcp install hermes` |
| [Kimchi](https://github.com/getkimchi/kimchi) | ✅ Supported | `od mcp install kimchi` |

`od mcp install <agent> --print` for a dry-run preview · `--uninstall` to remove · full list with `od mcp install --help`.

<p align="center">
<img src="https://repo-assets.open-design.ai/resources/images/coding-agents.png" alt="The 25 coding-agent CLIs Open Design supports — Claude Code · Codex · OpenCode · Hermes · Antigravity · Vela · Grok Build · Kimi · Cursor Agent · Qwen · Qoder · GitHub Copilot · Pi · Kiro · Kilo · Mistral Vibe · DeepSeek · Reasonix · Aider · Amp · CodeBuddy · Mimo · AtomCode · Devin · Trae" width="100%" />
<img src="https://repo-assets.open-design.ai/resources/images/coding-agents.png" alt="The 26 coding-agent CLIs Open Design supports — Claude Code · Codex · OpenCode · Hermes · Antigravity · Vela · Grok Build · Kimi · Cursor Agent · Qwen · Qoder · GitHub Copilot · Pi · Kiro · Kilo · Mistral Vibe · DeepSeek · Reasonix · Aider · Amp · CodeBuddy · Mimo · AtomCode · Devin · Trae · Kimchi" width="100%" />
</p>

**No CLI installed?** The BYOK proxy at `POST /api/proxy/{anthropic,openai,azure,google,ollama,senseaudio}/stream` gives you the same loop (no process spawn) — paste `baseUrl` + `apiKey` + `model`, with presets for OpenAI, Atlas Cloud, Anthropic, Azure OpenAI, Google Gemini, Ollama, LM Studio, vLLM, or any OpenAI-compatible endpoint. Atlas Cloud uses `https://api.atlascloud.ai/v1` with your own key and OpenAI-compatible model ids such as `qwen/qwen3.5-flash`. Per-target SSRF protection blocks internal IPs / link-local / CGNAT at the daemon edge.
Expand Down Expand Up @@ -571,7 +572,7 @@ Plugin registry endpoint: `GET /api/plugins`. Directory overview → [`plugins/R
┌──────────────────────────────────────────────────────────────────┐
│ Local runtime definitions come from runtimes/registry.ts; │
│ the base registry has 26 definitions (including byok-opencode), │
│ backed by 25 distinct local CLI executables because byok-opencode shares │
│ backed by 26 distinct local CLI executables because byok-opencode shares │
│ the OpenCode executable. See docs/agent-adapters.md. │
│ composes a functional skill or design template + DESIGN.md; writes files │
└──────────────────────────────────────────────────────────────────┘
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/src/app-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ const AGENT_CLI_ENV_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
['kimi', new Set(['KIMI_BIN'])],
['kiro', new Set(['KIRO_BIN'])],
['kilo', new Set(['KILO_BIN'])],
['kimchi', new Set(['KIMCHI_BIN', 'KIMCHI_API_KEY'])],
['opencode', new Set(['OPENCODE_BIN'])],
['pi', new Set(['PI_BIN'])],
['qoder', new Set(['QODER_BIN'])],
Expand Down
10 changes: 10 additions & 0 deletions apps/daemon/src/mcp-agent-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const AGENT_SLUGS = [
'kiro',
'trae',
'opencode',
'kimchi',
] as const;

export type AgentSlug = (typeof AGENT_SLUGS)[number];
Expand Down Expand Up @@ -298,6 +299,15 @@ export function planAgentInstall(
serverKey: serverName,
entry: jsonEntry(spec),
};
case 'kimchi':
return {
kind: 'json',
slug,
configPath: path.join(home, '.config', 'kimchi', 'harness', 'mcp.json'),
keyPath: ['mcpServers'],
serverKey: serverName,
entry: jsonEntry(spec),
};

// ----- Unverified formats: print-only, never write -----
case 'vibe':
Expand Down
5 changes: 3 additions & 2 deletions apps/daemon/src/memory-llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ function chatProtocolFromAgentId(agentId) {
if (id === 'claude') return 'anthropic';
if (id === 'gemini') return 'google';
// Codex, OpenCode, Qwen, DeepSeek, Kimi, Copilot, Pi, Kiro, Kilo,
// Vibe, Devin, Hermes, Cursor-Agent, Qoder all use the OpenAI chat-
// completions wire format.
// Vibe, Devin, Hermes, Cursor-Agent, Qoder, Kimchi all use the
// OpenAI chat-completions wire format.
if (
id === 'codex'
|| id === 'opencode'
Expand All @@ -300,6 +300,7 @@ function chatProtocolFromAgentId(agentId) {
|| id === 'hermes'
|| id === 'cursor-agent'
|| id === 'qoder'
|| id === 'kimchi'
) {
return 'openai';
}
Expand Down
73 changes: 73 additions & 0 deletions apps/daemon/src/runtimes/defs/kimchi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import path from "node:path";
import {
DEFAULT_MODEL_OPTION,
execAgentFile,
parsePiModels,
} from "./shared.js";
import type { RuntimeAgentDef } from "../types.js";

export const kimchiAgentDef = {
id: "kimchi",
name: "Kimchi",
bin: "kimchi",
versionArgs: ["--version"],
// Kimchi is built on the pi-mono SDK (same engine as pi), so it inherits
// pi's `--mode rpc` JSON-RPC-over-stdio transport, `--list-models` TSV
// output, `--thinking` levels, and `--append-system-prompt` for extra
// directories. Using pi-rpc instead of ACP gives us multimodal image
// support, reasoning levels, richer event stream, and avoids the ACP
// `mcpServers` rejection issue.
versionProbeTimeoutMs: 15_000,
fetchModels: async (resolvedBin, env) => {
try {
// Unlike pi (which prints to stderr), kimchi outputs the TSV table
// to stdout. Stderr carries config permission warnings only.
const { stdout } = await execAgentFile(resolvedBin, ["--list-models"], {
env,
timeout: 60_000,
maxBuffer: 8 * 1024 * 1024,
});
const parsed = parsePiModels(stdout);
if (!parsed || parsed.length === 0) return null;
return parsed;
} catch {
return null;
}
},
fallbackModels: [
DEFAULT_MODEL_OPTION,
{ id: "kimi-k2.7", label: "kimi-k2.7" },
{ id: "minimax-m3", label: "minimax-m3" },
{ id: "deepseek-v4-flash", label: "deepseek-v4-flash" },
],
reasoningOptions: [
{ id: "default", label: "Default" },
{ id: "off", label: "Off" },
{ id: "minimal", label: "Minimal" },
{ id: "low", label: "Low" },
{ id: "medium", label: "Medium" },
{ id: "high", label: "High" },
{ id: "xhigh", label: "XHigh" },
],
buildArgs: (_prompt, _imagePaths, extraAllowedDirs = [], options = {}) => {
const args = ["--mode", "rpc"];
if (options.model && options.model !== "default") {
args.push("--model", options.model);
}
if (options.reasoning && options.reasoning !== "default") {
args.push("--thinking", options.reasoning);
}
const dirs = (extraAllowedDirs || []).filter(
(d) => typeof d === "string" && path.isAbsolute(d),
);
for (const d of dirs) {
args.push("--append-system-prompt", d);
}
return args;
},
promptViaStdin: true,
streamFormat: "pi-rpc",
supportsImagePaths: true,
installUrl: "https://kimchi.dev",
docsUrl: "https://docs.kimchi.dev",
} satisfies RuntimeAgentDef;
1 change: 1 addition & 0 deletions apps/daemon/src/runtimes/executables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const AGENT_BIN_ENV_KEYS = new Map<string, string>([
['kimi', 'KIMI_BIN'],
['kiro', 'KIRO_BIN'],
['kilo', 'KILO_BIN'],
['kimchi', 'KIMCHI_BIN'],
['mimo', 'MIMO_BIN'],
['opencode', 'OPENCODE_BIN'],
['byok-opencode', 'OPENCODE_BIN'],
Expand Down
2 changes: 2 additions & 0 deletions apps/daemon/src/runtimes/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { antigravityAgentDef } from './defs/antigravity.js';
import { codebuddyAgentDef } from './defs/codebuddy.js';
import { reasonixAgentDef } from './defs/reasonix.js';
import { mimoAgentDef } from './defs/mimo.js';
import { kimchiAgentDef } from './defs/kimchi.js';
import { atomcodeAgentDef } from './defs/atomcode.js';
import { readLocalAgentProfileDefs as readLocalAgentProfileDefsFromFile } from './local-profiles.js';
import type { RuntimeAgentDef } from './types.js';
Expand Down Expand Up @@ -53,6 +54,7 @@ const BASE_AGENT_DEFS: RuntimeAgentDef[] = [
reasonixAgentDef,
codebuddyAgentDef,
mimoAgentDef,
kimchiAgentDef,
atomcodeAgentDef,
];

Expand Down
1 change: 1 addition & 0 deletions apps/daemon/tests/runtimes/env-and-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ test('resolveAgentExecutable supports configured binary overrides for non-Codex
['deepseek', 'deepseek', 'DEEPSEEK_BIN'],
['trae-cli', 'traecli', 'TRAE_CLI_BIN'],
['aider', 'aider', 'AIDER_BIN'],
['kimchi', 'kimchi', 'KIMCHI_BIN'],
];
const dir = mkdtempSync(join(tmpdir(), 'od-agent-bin-overrides-'));
try {
Expand Down
1 change: 1 addition & 0 deletions apps/daemon/tests/runtimes/helpers/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const qoder = requireAgent('qoder');
export const qwen = requireAgent('qwen');
export const opencode = requireAgent('opencode');
export const mimo = requireAgent('mimo');
export const kimchi = requireAgent('kimchi');
export const grokBuild = requireAgent('grok-build');
export const aider = requireAgent('aider');
export const antigravity = requireAgent('antigravity');
Expand Down
Binary file added apps/web/public/agent-icons/kimchi.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ const CANONICAL_AGENT_ORDER = [
'pi',
'kiro',
'kilo',
'kimchi',
'vibe',
'deepseek',
'aider',
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/AgentIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const ICON_EXT: Record<string, 'svg' | 'png'> = {
hermes: 'svg',
'grok-build': 'svg',
kimi: 'svg',
kimchi: 'png',
pi: 'svg',
kiro: 'svg',
kilo: 'svg',
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/components/HandoffButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const CLI_ORDER = [
'devin',
'kiro',
'kilo',
'kimchi',
'vibe',
'antigravity',
'aider',
Expand All @@ -95,6 +96,7 @@ const FALLBACK_CLI_TARGETS: CliTarget[] = [
{ id: 'deepseek', name: 'DeepSeek TUI', bin: 'deepseek', available: false },
{ id: 'kimi', name: 'Kimi CLI', bin: 'kimi', available: false },
{ id: 'hermes', name: 'Hermes', bin: 'hermes', available: false },
{ id: 'kimchi', name: 'Kimchi', bin: 'kimchi', available: false },
{ id: 'devin', name: 'Devin for Terminal', bin: 'devin', available: false },
{ id: 'kiro', name: 'Kiro CLI', bin: 'kiro-cli', available: false },
{ id: 'kilo', name: 'Kilo', bin: 'kilo', available: false },
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/MemoryModelInline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ function chatProtocolFromAgent(
|| id === 'hermes'
|| id === 'cursor-agent'
|| id === 'qoder'
|| id === 'kimchi'
) {
return 'openai';
}
Expand Down
13 changes: 12 additions & 1 deletion docs/agent-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,18 @@ the active-run staging implementation is in
shared failure classifier emits DeepSeek-specific guidance for those two
configuration paths instead of returning the raw non-actionable error.

### 5.12 Plain stream artifact handoff
### 5.12 Kimchi

- Invocation: `kimchi --mode rpc [--model <id>] [--thinking <level>] [--append-system-prompt <dir> …]`, with the composed prompt delivered over stdin via JSON-RPC. Kimchi is built on the [pi-mono](https://github.com/badlogic/pi-mono) coding agent SDK and inherits pi's RPC transport. Using `--mode rpc` (instead of `--mode acp`) gives us multimodal image support, reasoning levels, richer event stream, and avoids the ACP `mcpServers` rejection issue.
- Streaming: `pi-rpc` JSON-RPC over stdio. Events include `agent_start`, `turn_start/end`, `message_update` (text deltas, thinking deltas, tool calls), `tool_execution_start/end`, `compaction_start`, `auto_retry_start/end`, `extension_error`. `apps/daemon/src/pi-rpc.ts` maps these onto the same UI event set as the other stream parsers.
- Models: dynamic — `kimchi --list-models` prints a TSV table to stdout that the daemon parses into provider/model picker entries. Fallback hints for the default orchestrator/builder/explorer models (`kimi-k2.7`, `minimax-m3`, `deepseek-v4-flash`) are shipped for when the list command times out.
- Images: kimchi's RPC `prompt` command supports an `images` field (base64-encoded `ImageContent` objects), inherited from pi-mono. The daemon reads validated `imagePaths` at session attach time and includes them in the prompt command.
- Thinking: the daemon exposes kimchi's `--thinking` levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`) in the Settings model picker.
- Skills: prompt injection in v1. `extraAllowedDirs` (skill seed and design-system directories) are forwarded as `--append-system-prompt` repeatable flags so the agent knows these directories exist and can Read files inside them. Same pattern as the pi adapter.
- MCP: Kimchi does not accept `mcpServers` in the ACP `session/new` message — it rejects with `Invalid params: mcpServers is not supported`. MCP servers are configured via `~/.config/kimchi/harness/mcp.json` (same `mcpServers` JSON schema as Claude Desktop / Cursor). `od mcp install kimchi` writes to the JSON config file directly.
- Auth: API key resolved from `KIMCHI_API_KEY` env var or `~/.config/kimchi/config.json`. Run `kimchi setup` for interactive first-time configuration.

### 5.13 Plain stream artifact handoff

Adapters with `streamFormat: 'plain'` do not expose structured file-write tool calls to the daemon. Their stdout is still a valid artifact handoff when the model emits Anthropic-style source blocks:

Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/analytics/events/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ export function agentIdToTracking(agentId: string | null | undefined): TrackingC
return 'pi';
case 'kilo':
return 'kilo';
case 'kimchi':
return 'kimchi';
case 'amr':
return 'amr';
default:
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/analytics/events/shared-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export type TrackingCliProviderId =
| 'github_copilot_cli'
| 'pi'
| 'kilo'
| 'kimchi'
| 'amr'
| 'other';

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/src/analytics/events/ui-click.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,7 @@ export const TRACKING_HANDOFF_TARGET_IDS = [
// code-agent CLIs (HandoffButton CLI_ORDER; qoder / antigravity already above)
'amr', 'claude', 'codex', 'opencode', 'cursor-agent', 'gemini', 'qwen',
'copilot', 'grok-build', 'deepseek', 'kimi', 'hermes', 'devin', 'kiro',
'kilo', 'vibe', 'aider', 'trae-cli', 'pi', 'reasonix',
'kilo', 'kimchi', 'vibe', 'aider', 'trae-cli', 'pi', 'reasonix',
] as const;

export type TrackingHandoffTargetId =
Expand Down
Loading