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
21 changes: 21 additions & 0 deletions docs/agents/deepseek-official.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Official DeepSeek Harness ACP in Codeg

This built-in is the first-party DeepSeek Harness (DSH) ACP profile. It is a **separate** agent (`deepseek_official`) from Codeg's existing `deepseek` integration using the community `deepseek-acp` bridge. Previously saved DeepSeek sessions and existing custom `dsh` registrations remain distinct.

## Requirements

Install Node.js **24.9.0+** and configure DSH's model-provider credentials (for example `DEEPSEEK_API_KEY`) with DSH itself or Codeg's agent environment editor. Codeg can install the pinned npm release through its normal agent installer. To install the same release manually:

```sh
npm install -g @deepseek-ai/dsh@0.2.1-rc.4
```

Codeg launches `dsh --profile acp` on stdio (it is an ACP server, not an interactive terminal). Choose **DeepSeek Harness (Official ACP)** from the agent picker; the legacy entry continues to launch `deepseek-acp`.

## Supported features and limits

The official ACP profile provides `session/new`, `session/list`, `session/resume`, `session/close`, `session/prompt`, cancellation, permissions, model selection and (for supporting models) reasoning effort. Session-scoped MCP servers are supported over stdio and Streamable HTTP, including Codeg's MCP collaboration companion. StdIO MCP commands **must have absolute executable paths**; SSE MCP is unsupported.

It deliberately omits `session/load` transcript replay, `session/fork`, deletion, additional directories, interactive modes/plans/terminals, and editor-specific presentation cards. Codeg keeps its **own ACP transcript** for sessions started through this built-in. It does not parse or import DSH's private session format into this new agent; existing community-bridge history remains untouched. The legacy DeepSeek-specific model catalog editor and rich editor features belong to the legacy bridge, not to this profile.

Sources: [DSH CLI](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/README.md), [ACP contract](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/acp/acp/README.md), [Issue #1057](https://github.com/spacering-net/codeg/issues/1057).
33 changes: 26 additions & 7 deletions src-tauri/src/acp/delegation/companion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4673,13 +4673,32 @@ mod tests {
agents.len(),
crate::models::agent::BUILTIN_AGENT_TYPES.len()
);
assert_eq!(agents[0], "claude_code");
assert_eq!(agents[14], "antigravity");
assert_eq!(agents[15], "kiro");
assert_eq!(agents[16], "zcode");
assert_eq!(agents[17], "trae");
assert_eq!(agents[18], "command_code");
assert_eq!(agents[19], "minimax_code");
assert_eq!(
Value::Array(agents),
serde_json::json!([
"claude_code",
"codex",
"open_code",
"gemini",
"cline",
"open_claw",
"hermes",
"code_buddy",
"kimi_code",
"pi",
"grok",
"cursor",
"deepseek",
"deepseek_official",
"qoder",
"antigravity",
"kiro",
"zcode",
"trae",
"command_code",
"minimax_code"
])
);
}

#[tokio::test]
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/acp/delegation/tool_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"grok",
"cursor",
"deepseek",
"deepseek_official",
"qoder",
"antigravity",
"kiro",
Expand Down Expand Up @@ -117,7 +118,7 @@
},
{
"name": "ask_user_question",
"description": "Ask the user one or more multiple-choice questions and BLOCK until they answer. The questions render as interactive cards above the user's input box; this call returns only once the user submits their choices (or dismisses the card). Use ONLY when you are blocked on a decision that is genuinely the user's to make — one you cannot resolve from the request, the code, or sensible defaults (e.g. which approach, which format, which library, which of several files). Do NOT use it to ask 'should I proceed?' or to confirm an obvious default, and do NOT use it for open-ended questions that aren't a small set of discrete choices. The user can always pick 'Other' and type a custom answer, so don't add your own 'Other' option. If you want to recommend a choice, put it first and append ' (Recommended)' to its label. Prefer ONE call with a few related questions over many sequential calls. Once the user has answered \u2014 including picking a recommended option or telling you to proceed \u2014 act on the answer immediately. Do NOT call this tool again to confirm execution or to ask whether you should proceed.",
"description": "Ask the user one or more multiple-choice questions and BLOCK until they answer. The questions render as interactive cards above the user's input box; this call returns only once the user submits their choices (or dismisses the card). Use ONLY when you are blocked on a decision that is genuinely the user's to make — one you cannot resolve from the request, the code, or sensible defaults (e.g. which approach, which format, which library, which of several files). Do NOT use it to ask 'should I proceed?' or to confirm an obvious default, and do NOT use it for open-ended questions that aren't a small set of discrete choices. The user can always pick 'Other' and type a custom answer, so don't add your own 'Other' option. If you want to recommend a choice, put it first and append ' (Recommended)' to its label. Prefer ONE call with a few related questions over many sequential calls. Once the user has answered — including picking a recommended option or telling you to proceed — act on the answer immediately. Do NOT call this tool again to confirm execution or to ask whether you should proceed.",
"inputSchema": {
"type": "object",
"required": [
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/acp/file_system_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,8 @@ fn agent_root_slots(agent_type: AgentType) -> &'static [RootSlot] {
// 1.86.3, 1.88.0 and 1.88.1). So is MiniMax Code: its tools run
// in-process and it sends codeg no `fs/*` or `terminal/*` request
// (verified against 0.7.0).
AgentType::Custom(_)
AgentType::DeepSeekOfficial
| AgentType::Custom(_)
| AgentType::ZCode
| AgentType::Trae
| AgentType::CommandCode
Expand Down
40 changes: 38 additions & 2 deletions src-tauri/src/acp/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ pub fn current_platform() -> &'static str {
}
}

/// The twenty built-in agents. Excludes user-registered custom agents — use
/// The twenty-one built-in agents. Excludes user-registered custom agents — use
/// [`all_acp_agents`] for the live set.
pub fn builtin_acp_agents() -> Vec<AgentType> {
vec![
Expand All @@ -273,6 +273,7 @@ pub fn builtin_acp_agents() -> Vec<AgentType> {
AgentType::Grok,
AgentType::Cursor,
AgentType::DeepSeek,
AgentType::DeepSeekOfficial,
AgentType::Qoder,
AgentType::Antigravity,
AgentType::Kiro,
Expand All @@ -283,7 +284,7 @@ pub fn builtin_acp_agents() -> Vec<AgentType> {
]
}

/// Every agent codeg can currently drive: the twenty built-ins followed by
/// Every agent codeg can currently drive: the twenty-one built-ins followed by
/// the user's registered custom ACP agents (sorted by id).
pub fn all_acp_agents() -> Vec<AgentType> {
let mut agents = builtin_acp_agents();
Expand All @@ -306,6 +307,8 @@ pub fn registry_id_for(agent_type: AgentType) -> &'static str {
AgentType::Grok => "grok-build",
AgentType::Cursor => "cursor",
AgentType::DeepSeek => "deepseek-acp",
// Avoid taking over existing custom:dsh registrations.
AgentType::DeepSeekOfficial => "codeg-deepseek-official",
AgentType::Qoder => "qoder-cli",
AgentType::Antigravity => "antigravity-acp",
AgentType::Kiro => "kiro-cli",
Expand Down Expand Up @@ -344,6 +347,7 @@ pub fn from_registry_id(id: &str) -> Option<AgentType> {
"grok-build" => Some(AgentType::Grok),
"cursor" => Some(AgentType::Cursor),
"deepseek-acp" => Some(AgentType::DeepSeek),
"codeg-deepseek-official" => Some(AgentType::DeepSeekOfficial),
"qoder-cli" => Some(AgentType::Qoder),
"antigravity-acp" => Some(AgentType::Antigravity),
"kiro-cli" => Some(AgentType::Kiro),
Expand Down Expand Up @@ -5480,6 +5484,23 @@ pub fn get_agent_meta(agent_type: AgentType) -> AcpAgentMeta {
node_required: Some("22.0.0"),
},
},
AgentType::DeepSeekOfficial => AcpAgentMeta {
agent_type,
supports_mcp: true,
name: "DeepSeek Harness (Official ACP)",
description: "First-party DSH automation ACP over stdio",
// The native profile supports ACP session/new, list, resume, close,
// prompts, config options and MCP, but not session/load or fork.
// Keep its Codeg ACP transcripts separate from deepseek-acp logs.
distribution: AgentDistribution::Npx {
version: "0.2.1-rc.4",
package: "@deepseek-ai/dsh@0.2.1-rc.4",
cmd: "dsh",
args: &["--profile", "acp"],
env: &[],
node_required: Some("24.9.0"),
},
},
AgentType::DeepSeek => AcpAgentMeta {
agent_type,
supports_mcp: true,
Expand Down Expand Up @@ -6836,6 +6857,21 @@ mod tests {
"deepseek-acp@0.9.0",
Some("22.0.0"),
);
assert_npx_version(
AgentType::DeepSeekOfficial,
"0.2.1-rc.4",
"@deepseek-ai/dsh@0.2.1-rc.4",
Some("24.9.0"),
);
match get_agent_meta(AgentType::DeepSeekOfficial).distribution {
AgentDistribution::Npx { cmd, args, .. } => {
assert_eq!(cmd, "dsh");
assert_eq!(args, &["--profile", "acp"]);
}
other => panic!("expected official DSH npx distribution, got {other:?}"),
}
assert!(get_agent_meta(AgentType::DeepSeekOfficial).supports_mcp);
assert_eq!(from_registry_id("codeg-deepseek-official"), Some(AgentType::DeepSeekOfficial));
assert_npx_version(
AgentType::Qoder,
"1.1.75",
Expand Down
15 changes: 12 additions & 3 deletions src-tauri/src/commands/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9544,7 +9544,7 @@ pub(crate) fn skill_storage_spec(agent_type: AgentType) -> Option<SkillStorageSp
// directory comes first so linking targets it without cross-agent side
// effects on the shared `.agents` store — the same ordering rationale
// as pi and Cursor.
AgentType::DeepSeek => Some(SkillStorageSpec {
AgentType::DeepSeek | AgentType::DeepSeekOfficial => Some(SkillStorageSpec {
kind: SkillStorageKind::SkillDirectoryOrMarkdownFile,
global_dirs: vec![
crate::parsers::deepseek::resolve_dsh_home_dir().join("skills"),
Expand Down Expand Up @@ -9865,7 +9865,7 @@ fn project_skill_dirs(
fn project_skill_bases(agent_type: AgentType, workspace: &str) -> Vec<PathBuf> {
let workspace = PathBuf::from(workspace);
let every_level = match agent_type {
AgentType::DeepSeek | AgentType::KimiCode => false,
AgentType::DeepSeek | AgentType::DeepSeekOfficial | AgentType::KimiCode => false,
AgentType::ZCode => true,
_ => return vec![workspace],
};
Expand Down Expand Up @@ -10898,6 +10898,12 @@ fn agent_env_keys(agent_type: AgentType) -> (&'static str, &'static str, &'stati
"DEEPSEEK_API_KEY",
"DEEPSEEK_ACP_MODEL",
),
// Official DSH selects models through ACP config options, not env.
AgentType::DeepSeekOfficial => (
"DEEPSEEK_BASE_URL",
"DEEPSEEK_API_KEY",
"DSH_MODEL", // no-op fallback; do not inherit OPENAI_MODEL
),
// Qoder's non-interactive credential is `QODER_PERSONAL_ACCESS_TOKEN`
// — "设置后自动使用 PAT 认证" in the 1.1.23 package's own README, and the
// only way to authenticate a headless/server/Docker install, where the
Expand Down Expand Up @@ -11288,6 +11294,9 @@ pub(crate) fn parse_provider_model(
trimmed_raw.map(str::to_string),
);
}
AgentType::DeepSeekOfficial => {
// Model changes use session/set_config_option, not a provider env var.
}
AgentType::Codex => {
// The provider stores a structured model config (JSON) or a legacy
// plain slug; OPENAI_MODEL is the default slug either way.
Expand Down Expand Up @@ -11417,7 +11426,7 @@ fn cascade_update_agent_config(
// (`acpUpdateAgentEnv`); it does not write provider creds into
// ~/.grok/config.toml and does not participate in the cascade.
}
AgentType::DeepSeek => {
AgentType::DeepSeek | AgentType::DeepSeekOfficial => {
// deepseek-acp authenticates via `DEEPSEEK_API_KEY` (or
// `~/.dsh/.credentials.yaml`, which codeg never writes), injected
// as a runtime env var through the generic agent settings panel;
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/experts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ fn supported_agents() -> Vec<AgentType> {
AgentType::Grok,
AgentType::Cursor,
AgentType::DeepSeek,
AgentType::DeepSeekOfficial,
AgentType::Qoder,
AgentType::Antigravity,
AgentType::Kiro,
Expand Down
35 changes: 35 additions & 0 deletions src-tauri/src/commands/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3707,6 +3707,11 @@ pub fn read_servers_for_agent_type(
// unlike Kimi/Grok/Cursor, DeepSeek must stay OFF the forward skip
// list in `connection.rs` or these servers never arrive.
AgentType::DeepSeek => read_deepseek_servers(),
// The official DSH profile is a separate agent with no Codeg-managed
// MCP store yet. Do not inherit the community bridge's assignments
// from `$DSH_HOME/mcp.json`; the collaboration companion is injected
// separately over the ACP wire.
AgentType::DeepSeekOfficial => Ok(BTreeMap::new()),
AgentType::ZCode => read_zcode_servers(),
// Qoder reads MCP servers from its own settings.json `mcpServers`
// (gemini-schema settings file) at startup — see the Qoder section
Expand Down Expand Up @@ -8449,6 +8454,36 @@ mod tests {
}
}

#[test]
fn deepseek_official_does_not_read_legacy_mcp_assignments() {
use crate::models::agent::AgentType;

let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("mcp.json");
let raw = json!({"mcpServers": {
"legacy-only": {"command": "legacy-mcp", "env": {"TOKEN": "fixture"}}
}})
.to_string();
fs::write(&path, &raw).unwrap();

temp_env::with_var("DSH_HOME", Some(dir.path()), || {
let legacy = read_servers_for_agent_type(AgentType::DeepSeek).unwrap();
assert_eq!(legacy["legacy-only"]["command"], "legacy-mcp");
assert!(read_servers_for_agent_type(AgentType::DeepSeekOfficial)
.unwrap()
.is_empty());
assert_eq!(fs::read_to_string(&path).unwrap(), raw);

// An unrelated malformed legacy store must not affect official DSH.
fs::write(&path, "{broken").unwrap();
assert!(read_servers_for_agent_type(AgentType::DeepSeek).is_err());
assert!(read_servers_for_agent_type(AgentType::DeepSeekOfficial)
.unwrap()
.is_empty());
assert_eq!(fs::read_to_string(&path).unwrap(), "{broken");
});
}

#[test]
fn deepseek_mcp_json_round_trips_the_canonical_spec() {
// `$DSH_HOME/mcp.json` is codeg's OWN store (deepseek-acp reads no MCP
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/office_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1061,6 +1061,7 @@ fn supported_agents() -> Vec<AgentType> {
AgentType::Grok,
AgentType::Cursor,
AgentType::DeepSeek,
AgentType::DeepSeekOfficial,
AgentType::Qoder,
AgentType::Antigravity,
AgentType::Kiro,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/commands/science.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ fn supported_agents() -> Vec<AgentType> {
AgentType::Grok,
AgentType::Cursor,
AgentType::DeepSeek,
AgentType::DeepSeekOfficial,
AgentType::Qoder,
AgentType::Antigravity,
AgentType::Kiro,
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/db/service/agent_setting_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fn default_enabled(agent_type: AgentType) -> bool {
| AgentType::Grok
| AgentType::Cursor
| AgentType::DeepSeek
| AgentType::DeepSeekOfficial
| AgentType::Qoder
| AgentType::Antigravity
| AgentType::Kiro
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/db/service/import_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::models::{AgentType, ConversationSummary, ImportResult};
use crate::parsers::{build_agent_parser, path_eq_for_matching, AgentEnv, AgentEnvs, AgentParser};

/// Every locally-parsable agent, in the canonical parser order.
const ALL_PARSER_AGENTS: [AgentType; 20] = [
const ALL_PARSER_AGENTS: [AgentType; 21] = [
AgentType::ClaudeCode,
AgentType::Codex,
AgentType::OpenCode,
Expand All @@ -27,6 +27,7 @@ const ALL_PARSER_AGENTS: [AgentType; 20] = [
AgentType::Grok,
AgentType::Cursor,
AgentType::DeepSeek,
AgentType::DeepSeekOfficial,
AgentType::Qoder,
AgentType::Antigravity,
AgentType::Kiro,
Expand Down
Loading
Loading