Skip to content
Merged
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
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@
[`learn-claude-code`](https://github.com/shareAI-lab/learn-claude-code) 的 Harness
Engineering 思路:模型负责判断下一步做什么,Harness 负责提供工具、上下文、权限边界、持久化和用户界面。

当前版本为 `0.1.0`,使用 DeepSeek 模型,提供一次性命令行执行和交互式 TUI 两种使用方式。
当前版本为 `0.1.0`,默认使用 DeepSeek,也支持 OpenAI Chat Completions,
提供一次性命令行执行和交互式 TUI 两种使用方式。

## 工作区结构

```text
kuncode-cli ──▶ kuncode-agent ──▶ kuncode-core ──▶ DeepSeek API
kuncode-cli ──▶ kuncode-agent ──▶ kuncode-core ──▶ LLM API
│ │ │
│ │ └─ 消息、Completion、流式协议、Provider
│ └─ Agent Loop、工具、权限、会话、压缩与编排
└─ 参数、配置、审批、一次性输出与 TUI
```

- `kuncode-core`:Provider-neutral 的消息与 Completion 抽象,以及 DeepSeek Provider。
- `kuncode-core`:Provider-neutral 的消息与 Completion 抽象,以及 DeepSeek、OpenAI Provider。
- `kuncode-agent`:Agent 运行时、工具调度、权限、Hook、Todo、会话持久化和上下文压缩。
- `kuncode-cli`:命令行参数、项目配置、终端审批、普通输出和交互式 TUI。

## 环境要求

- Rust stable,项目使用 Rust 2024 edition。
- DeepSeek API Key。
- DeepSeek 或 OpenAI API Key。
- 支持 ANSI 终端;交互模式需要 stdin 和 stdout 都连接到真实终端。

## 快速开始
Expand All @@ -40,6 +41,24 @@ export DEEPSEEK_API_KEY="your-api-key"
DEEPSEEK_API_KEY=your-api-key
```

使用 OpenAI 官方接口时,在 `.kuncode/settings.json` 配置:

```json
{
"model": {
"provider": "openai",
"name": "gpt-5.1",
"maxTokens": 16384
}
}
```

并设置对应环境变量:

```bash
export OPENAI_API_KEY="your-api-key"
```

一次性执行任务:

```bash
Expand Down Expand Up @@ -76,6 +95,7 @@ cargo build --release -p kuncode-cli
"defaultMode": "default"
},
"model": {
"provider": "deepseek",
"name": "deepseek-v4-pro",
"maxTokens": 65536
},
Expand All @@ -94,8 +114,14 @@ cargo build --release -p kuncode-cli

补充说明:

- `DEEPSEEK_MODEL` 可以覆盖配置文件中的模型名称。
- `KUNCODE_MODEL` 可以覆盖配置文件中的模型名称;`DEEPSEEK_MODEL` 作为兼容别名保留。
- `model.provider` 支持 `deepseek` 和 `openai`;两者分别使用固定官方 endpoint,
并读取 `DEEPSEEK_API_KEY` 或 `OPENAI_API_KEY`。
- 内置模型配置包括 `deepseek-v4-pro` 和 `deepseek-v4-flash`。
- 没有内置能力档案的模型,`model.maxTokens` 默认值为 `16384`;从旧版
`32768` 默认值升级时,如配置了 `compaction.reservedOutput`,需同步调整或显式设置
`model.maxTokens`。
- 非内置模型启用上下文压缩时,需要显式设置 `compaction.contextLimit`。
- `compaction.mode` 支持 `disabled`、`shadow` 和 `enabled`,默认是 `disabled`。
- `shadow` 只计算和报告压缩候选,不替换当前上下文。
- `enabled` 会在达到预算阈值时执行压缩,并要求会话持久化状态保持健康。
Expand Down
4 changes: 2 additions & 2 deletions crates/kuncode-agent/src/compaction/summary/summarizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ mod tests {
use kuncode_core::{
completion::{
AssistantContent, CompletionError, CompletionModel, CompletionRequest,
CompletionResponse, CompletionStream, Message, ReasoningEffort, ToolChoice, Usage,
CompletionResponse, CompletionStream, Message, ReasoningEffort, Usage,
},
non_empty_vec::NonEmptyVec,
};
Expand Down Expand Up @@ -308,7 +308,7 @@ mod tests {
assert!(sent.model.is_none());
assert_eq!(sent.chat_history.len(), 2);
assert!(sent.tools.is_empty());
assert_eq!(sent.tool_choice, Some(ToolChoice::None));
assert!(sent.tool_choice.is_none());
assert_eq!(sent.temperature, Some(0.0));
assert_eq!(sent.max_tokens, Some(2_048));
assert_eq!(sent.reasoning, Some(ReasoningEffort::Off));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use kuncode_core::{
completion::{
AssistantContent, CompletionError, CompletionModel, CompletionRequestBuilder, Message,
ReasoningEffort, ToolChoice, Usage,
ReasoningEffort, Usage,
},
non_empty_vec::NonEmptyVec,
};
Expand Down Expand Up @@ -42,11 +42,11 @@ pub(super) async fn run_attempt<M>(
where
M: CompletionModel,
{
// An empty tool set already makes `none` the provider default.
let completion = CompletionRequestBuilder::from_messages(prompt)
.temperature(Some(0.0))
.max_tokens(Some(max_output_tokens))
.reasoning(Some(ReasoningEffort::Off))
.tool_choice(Some(ToolChoice::None))
.output_schema(Some(schema))
.build();
let response = model
Expand Down
38 changes: 25 additions & 13 deletions crates/kuncode-cli/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ use kuncode_agent::system_prompt::{
};
use kuncode_agent::workspace::Workspace;
use kuncode_core::completion::{CompletionModel, RetryModel, RetryPolicy};
use kuncode_core::providers::deepseek::{DeepSeekClient, DeepSeekCompletionModel};
use kuncode_core::providers::{
any_chat::{AnyChatClient, AnyChatCompletionModel},
deepseek::DeepSeekClient,
openai::OpenAiClient,
};

use crate::config::{PermissionFlags, resolve_permissions};
use crate::settings::{ProjectSettings, ProjectTrust, load_project_settings};
use crate::settings::{ProjectSettings, ProjectTrust, ProviderKind, load_project_settings};
use crate::{Cli, logging::LoggingObserver};

/// Identity and behavioral instructions rendered as the first system-prompt
Expand All @@ -46,7 +50,7 @@ Keep working until the task is done, then give a short, direct final answer.";
/// observer + approver, plus the bits a frontend renders directly
/// ([`model_name`](Self::model_name), [`mode`](Self::mode)). Generic over the
/// model so a test or a future provider can supply its own `M`; [`assemble`]
/// pins it to the CLI's [`DeepSeekCompletionModel`] wrapped in a
/// pins it to the configured [`AnyChatCompletionModel`] wrapped in a
/// [`RetryModel`] so transient provider failures are retried transparently.
///
/// [`assemble`]: Self::assemble
Expand All @@ -64,21 +68,21 @@ pub struct CliRuntime<M> {
persistence_error: Option<String>,
}

impl CliRuntime<RetryModel<DeepSeekCompletionModel>> {
impl CliRuntime<RetryModel<AnyChatCompletionModel>> {
/// Builds the runtime from parsed CLI args and the project settings file.
///
/// Resolves permissions from built-in ∪ project file ∪ CLI flags (mode
/// precedence CLI > project > Default), assembles the system prompt from its
/// identity/environment/tools sections, and wires the DeepSeek model + the
/// default workspace tool registry.
/// identity/environment/tools sections, and wires the configured model +
/// the default workspace tool registry.
///
/// # Errors
///
/// Fails if the current directory is not a usable workspace, the project
/// settings or resolved permissions are invalid, active compaction cannot
/// be bound to the selected model, or the DeepSeek client cannot be built
/// from the environment. Failure to open the optional session store is
/// retained as degraded persistence state rather than failing assembly.
/// be bound to the selected model, or the provider client cannot be built
/// from its fixed credential environment. Failure to open the optional session
/// store is retained as degraded persistence state rather than failing assembly.
pub async fn assemble(cli: &Cli) -> Result<Self, Box<dyn std::error::Error>> {
let workspace = Workspace::from_current_dir().await?;
tracing::debug!(
Expand All @@ -97,6 +101,7 @@ impl CliRuntime<RetryModel<DeepSeekCompletionModel>> {
let project = load_project_settings(workspace.root(), project_trust)?;
let model_name = project.model_name.clone();
let config = agent_config(&project)?;
let client = provider_client(&project)?;
let flags = PermissionFlags {
allow: &cli.allow,
ask: &cli.ask,
Expand Down Expand Up @@ -160,11 +165,10 @@ impl CliRuntime<RetryModel<DeepSeekCompletionModel>> {
(None, Some("home directory unavailable".to_string()))
}
};
let client = DeepSeekClient::from_env()?;
// Normal turns inherit the default retry budget. Semantic summaries use
// a separate one-retry wrapper so their fallback latency is bounded
// independently of ordinary model calls.
let provider = DeepSeekCompletionModel::make(&client, model_name.clone());
let provider = AnyChatCompletionModel::make(&client, model_name.clone());
let model = RetryModel::with_policy(provider.clone(), RetryPolicy::default());
let summary_model = RetryModel::with_policy(provider, summary_retry_policy());
let registry = ToolRegistry::with_default_workspace_tools(workspace)?;
Expand All @@ -185,6 +189,13 @@ impl CliRuntime<RetryModel<DeepSeekCompletionModel>> {
}
}

fn provider_client(project: &ProjectSettings) -> Result<AnyChatClient, Box<dyn std::error::Error>> {
match project.provider {
ProviderKind::DeepSeek => Ok(AnyChatClient::DeepSeek(DeepSeekClient::from_env()?)),
ProviderKind::OpenAi => Ok(AnyChatClient::OpenAi(OpenAiClient::from_env()?)),
}
}

fn agent_config(project: &ProjectSettings) -> Result<AgentConfig, AgentCompactionConfigError> {
let compaction = project
.compaction
Expand Down Expand Up @@ -282,7 +293,7 @@ impl<M: CompletionModel> CliRuntime<M> {
#[cfg(test)]
mod tests {
use super::*;
use crate::settings::{ProjectSettings, load_project_settings_from};
use crate::settings::{ModelOverrides, ProjectSettings, load_project_settings_from};
use std::fs;

fn compaction_settings(tag: &str) -> ProjectSettings {
Expand All @@ -301,7 +312,8 @@ mod tests {
)
.expect("write settings");
let settings =
load_project_settings_from(&dir, None, ProjectTrust::Untrusted).expect("load settings");
load_project_settings_from(&dir, ModelOverrides::default(), ProjectTrust::Untrusted)
.expect("load settings");
let _ = fs::remove_dir_all(&dir);
settings
}
Expand Down
Loading