diff --git a/c/cagent.c b/c/cagent.c index c5cc86c..bc1b141 100644 --- a/c/cagent.c +++ b/c/cagent.c @@ -58,7 +58,7 @@ static void image_paste_wrapper(char **out, size_t *outlen) { int main(int argc, char *argv[]) { srand((unsigned int)time(NULL) ^ (unsigned int)getpid()); /* 默认值 */ - const char *provider = util_env("BASH_AGENT_PROVIDER", "claude"); + const char *provider = "claude"; const char *model = NULL; const char *api_key = NULL; const char *base_url = NULL; @@ -169,44 +169,42 @@ int main(int argc, char *argv[]) { char *effective_model = NULL; char *effective_base_url = NULL; + /* 先解析当前 provider 的显式参数和环境变量;DeepSeek 回退后才设置模型与传输配置。 */ if (strcmp(provider, "claude") == 0) { - effective_api_key = util_strdup(api_key ? api_key : util_env("ANTHROPIC_API_KEY", "")); - effective_base_url = util_strdup(base_url ? base_url : util_env("ANTHROPIC_BASE_URL", "")); - effective_model = util_strdup(model ? model : (getenv("MODEL") && getenv("MODEL")[0] ? getenv("MODEL") : "claude-sonnet-4-20250514")); - - /* DeepSeek 自动检测 */ - if (!effective_api_key[0] && !effective_base_url[0]) { - const char *ds_key = getenv("DEEPSEEK_API_KEY"); - if (ds_key && ds_key[0]) { - free(effective_api_key); - free(effective_base_url); - effective_api_key = util_strdup(ds_key); - effective_base_url = util_strdup("https://api.deepseek.com/anthropic"); - if (!model) { - free(effective_model); - effective_model = util_strdup("deepseek-v4-flash"); - } - } - } - - if (!effective_api_key[0] && !effective_base_url[0]) { - fprintf(stderr, "Error: no API key. Set ANTHROPIC_API_KEY or use --api-key\n"); - return 1; - } + effective_api_key = util_strdup(api_key && api_key[0] ? api_key : util_env("ANTHROPIC_API_KEY", "")); + effective_base_url = util_strdup(base_url && base_url[0] ? base_url : util_env("ANTHROPIC_BASE_URL", "")); } else if (strcmp(provider, "openai") == 0) { - effective_api_key = util_strdup(api_key ? api_key : util_env("OPENAI_API_KEY", "")); - effective_base_url = util_strdup(base_url ? base_url : util_env("OPENAI_BASE_URL", "")); - effective_model = util_strdup(model ? model : (getenv("MODEL") && getenv("MODEL")[0] ? getenv("MODEL") : "gpt-4o")); - - if (!effective_api_key[0] && !effective_base_url[0]) { - fprintf(stderr, "Error: no API key. Set OPENAI_API_KEY or use --api-key\n"); - return 1; - } + effective_api_key = util_strdup(api_key && api_key[0] ? api_key : util_env("OPENAI_API_KEY", "")); + effective_base_url = util_strdup(base_url && base_url[0] ? base_url : util_env("OPENAI_BASE_URL", "")); } else { fprintf(stderr, "Error: unknown provider '%s' (use claude|openai)\n", provider); return 1; } + /* 仅当前 provider 没有显式配置时,自动回退至 DeepSeek。 */ + if (!effective_api_key[0] && !effective_base_url[0]) { + const char *ds_key = getenv("DEEPSEEK_API_KEY"); + if (ds_key && ds_key[0]) { + provider = "claude"; + free(effective_api_key); + free(effective_base_url); + effective_api_key = util_strdup(ds_key); + effective_base_url = util_strdup("https://api.deepseek.com/anthropic"); + if (!model || !model[0]) effective_model = util_strdup("deepseek-v4-flash"); + } + } + + if (!effective_api_key[0] && !effective_base_url[0]) { + fprintf(stderr, "Error: no API key. Set %s_API_KEY or use --api-key\n", + strcmp(provider, "openai") == 0 ? "OPENAI" : "ANTHROPIC"); + return 1; + } + + if (!effective_model) { + effective_model = util_strdup(model && model[0] ? model : + (strcmp(provider, "openai") == 0 ? "gpt-4o" : "claude-sonnet-4-20250514")); + } + /* 继续 session */ char *effective_session = util_strdup(session_id); if ((do_continue || do_fork) && (!effective_session || !effective_session[0])) { diff --git a/go/cmd/goagent/main.go b/go/cmd/goagent/main.go index e492e15..a8e3329 100644 --- a/go/cmd/goagent/main.go +++ b/go/cmd/goagent/main.go @@ -127,29 +127,15 @@ Examples: cfg.Model = model } - // Provider 默认值 + // 先解析当前 provider 的显式参数和环境变量;DeepSeek 回退后才设置模型与传输配置。 switch cfg.Provider { case "claude": if cfg.APIKey == "" { cfg.APIKey = os.Getenv("ANTHROPIC_API_KEY") } - // DeepSeek auto-detection: DEEPSEEK_API_KEY 且未显式配置其他 base URL - if cfg.APIKey == "" && cfg.BaseURL == "" && - os.Getenv("DEEPSEEK_API_KEY") != "" && - os.Getenv("ANTHROPIC_BASE_URL") == "" && - os.Getenv("OPENAI_BASE_URL") == "" { - cfg.APIKey = os.Getenv("DEEPSEEK_API_KEY") - cfg.BaseURL = "https://api.deepseek.com/anthropic" - if cfg.Model == "" { - cfg.Model = "deepseek-v4-flash" - } - } if cfg.BaseURL == "" { cfg.BaseURL = os.Getenv("ANTHROPIC_BASE_URL") } - if cfg.Model == "" { - cfg.Model = "claude-sonnet-4-20250514" - } case "openai": if cfg.APIKey == "" { cfg.APIKey = os.Getenv("OPENAI_API_KEY") @@ -157,8 +143,26 @@ Examples: if cfg.BaseURL == "" { cfg.BaseURL = os.Getenv("OPENAI_BASE_URL") } + default: + fmt.Fprintf(os.Stderr, "Unknown provider: %s (use claude|openai)\n", cfg.Provider) + os.Exit(1) + } + + // 仅当前 provider 没有显式配置时,自动回退至 DeepSeek。 + if cfg.APIKey == "" && cfg.BaseURL == "" && os.Getenv("DEEPSEEK_API_KEY") != "" { + cfg.Provider = "claude" + cfg.APIKey = os.Getenv("DEEPSEEK_API_KEY") + cfg.BaseURL = "https://api.deepseek.com/anthropic" if cfg.Model == "" { + cfg.Model = "deepseek-v4-flash" + } + } + + if cfg.Model == "" { + if cfg.Provider == "openai" { cfg.Model = "gpt-4o" + } else { + cfg.Model = "claude-sonnet-4-20250514" } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b75435c..657547f 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -198,35 +198,15 @@ pub mod config { } } + // 先解析当前 provider 的显式参数和环境变量;DeepSeek 回退后才设置模型与传输配置。 match cfg.provider.as_str() { "claude" => { if cfg.api_key.is_empty() { cfg.api_key = std::env::var("ANTHROPIC_API_KEY").unwrap_or_default(); } - // DeepSeek auto-detection - if cfg.api_key.is_empty() - && cfg.base_url.is_empty() - && std::env::var("DEEPSEEK_API_KEY") - .map(|v| !v.is_empty()) - .unwrap_or(false) - && std::env::var("ANTHROPIC_BASE_URL").is_err() - && std::env::var("OPENAI_BASE_URL").is_err() - { - cfg.api_key = std::env::var("DEEPSEEK_API_KEY").unwrap(); - cfg.base_url = "https://api.deepseek.com/anthropic".to_string(); - if cfg.model.is_empty() { - cfg.model = "deepseek-v4-flash".to_string(); - } - } if cfg.base_url.is_empty() { cfg.base_url = std::env::var("ANTHROPIC_BASE_URL").unwrap_or_default(); } - if cfg.model.is_empty() { - cfg.model = "claude-sonnet-4-20250514".to_string(); - } - if cfg.api_key.is_empty() && cfg.base_url.is_empty() { - bail!("no API key. Set ANTHROPIC_API_KEY or use --api-key"); - } } "openai" => { if cfg.api_key.is_empty() { @@ -235,15 +215,39 @@ pub mod config { if cfg.base_url.is_empty() { cfg.base_url = std::env::var("OPENAI_BASE_URL").unwrap_or_default(); } - if cfg.model.is_empty() { - cfg.model = "gpt-4o".to_string(); - } - if cfg.api_key.is_empty() && cfg.base_url.is_empty() { - bail!("no API key. Set OPENAI_API_KEY or use --api-key"); - } } _ => bail!("unknown provider: {} (use claude|openai)", cfg.provider), } + + // 仅当前 provider 没有显式配置时,自动回退至 DeepSeek。 + if cfg.api_key.is_empty() && cfg.base_url.is_empty() { + if let Ok(ds_key) = std::env::var("DEEPSEEK_API_KEY") { + if !ds_key.is_empty() { + cfg.provider = "claude".to_string(); + cfg.api_key = ds_key; + cfg.base_url = "https://api.deepseek.com/anthropic".to_string(); + if cfg.model.is_empty() { + cfg.model = "deepseek-v4-flash".to_string(); + } + } + } + } + + if cfg.api_key.is_empty() && cfg.base_url.is_empty() { + match cfg.provider.as_str() { + "claude" => bail!("no API key. Set ANTHROPIC_API_KEY or use --api-key"), + "openai" => bail!("no API key. Set OPENAI_API_KEY or use --api-key"), + _ => unreachable!(), + } + } + + if cfg.model.is_empty() { + cfg.model = match cfg.provider.as_str() { + "claude" => "claude-sonnet-4-20250514", + "openai" => "gpt-4o", + _ => unreachable!(), + }.to_string(); + } Ok(()) } @@ -255,7 +259,7 @@ pub mod config { } else { cfg.base_url.as_str() }; - format!("{}/messages", base.trim_end_matches('/')) + format!("{}/messages", base) } "openai" => { let base = if cfg.base_url.is_empty() { @@ -263,7 +267,7 @@ pub mod config { } else { cfg.base_url.as_str() }; - format!("{}/chat/completions", base.trim_end_matches('/')) + format!("{}/chat/completions", base) } _ => String::new(), } diff --git a/src/agent.sh b/src/agent.sh index 2f72487..cd11a3e 100755 --- a/src/agent.sh +++ b/src/agent.sh @@ -418,12 +418,20 @@ store_session_get_dir() { } store_session_get_latest_dir() { - local project_dir latest="" latest_ts=0 dir ts + local project_dir latest="" latest_ts=0 dir ts stat_cmd project_dir="$(store_session_get_dir)" [[ -d "$project_dir" ]] || return 1 + if stat -c "%Y" "$project_dir" &>/dev/null; then + stat_cmd=(stat -c "%Y") + else + stat_cmd=(stat -f "%m") + fi for dir in "$project_dir"/*/; do [[ -d "$dir" ]] || continue - ts=$(stat -f "%m" "$dir/events.jsonl" 2>/dev/null || stat -c "%Y" "$dir/events.jsonl" 2>/dev/null || stat -f "%m" "$dir" 2>/dev/null || stat -c "%Y" "$dir" 2>/dev/null || echo 0) + local target="$dir" + [[ -f "$dir/events.jsonl" ]] && target="$dir/events.jsonl" + ts=$("${stat_cmd[@]}" "$target" 2>/dev/null || echo 0) + [[ "$ts" =~ ^[0-9]+$ ]] || ts=0 (( ts > latest_ts )) && { latest_ts=$ts; latest="$dir"; } done [[ -n "$latest" ]] && basename "$latest" || return 1 @@ -1626,10 +1634,41 @@ list_sessions() { } validate_config() { + # Resolve the selected provider's environment configuration first. Model defaults and + # transport settings are applied only after DeepSeek fallback selects the final provider. case "$PROVIDER" in claude) - : "${API_KEY:=$ANTHROPIC_API_KEY}" + : "${API_KEY:=${ANTHROPIC_API_KEY:-}}" : "${BASE_URL:=${ANTHROPIC_BASE_URL:-}}" + ;; + openai) + : "${API_KEY:=${OPENAI_API_KEY:-}}" + : "${BASE_URL:=${OPENAI_BASE_URL:-}}" + ;; + *) + util_die "Unknown provider: $PROVIDER (use claude|openai)" + ;; + esac + + # Auto-detect DeepSeek only when the selected provider has no explicit configuration. + # MODEL is still empty here unless the user supplied -m/--model. + if [[ -z "$API_KEY" && -z "$BASE_URL" && -n "${DEEPSEEK_API_KEY:-}" ]]; then + PROVIDER="claude" + API_KEY="$DEEPSEEK_API_KEY" + BASE_URL="https://api.deepseek.com/anthropic" + : "${MODEL:=deepseek-v4-flash}" + fi + + if [[ -z "$API_KEY" && -z "$BASE_URL" ]]; then + case "$PROVIDER" in + claude) util_die "No API key. Set ANTHROPIC_API_KEY or use --api-key" ;; + openai) util_die "No API key. Set OPENAI_API_KEY or use --api-key" ;; + esac + fi + + # Build the final transport only after provider fallback has completed. + case "$PROVIDER" in + claude) : "${MODEL:=claude-sonnet-4-20250514}" API_URL="${BASE_URL:-https://api.anthropic.com/v1}/messages" HEADER_ARGS=( @@ -1643,8 +1682,6 @@ validate_config() { sse_convert() { cat; } ;; openai) - : "${API_KEY:=$OPENAI_API_KEY}" - : "${BASE_URL:=${OPENAI_BASE_URL:-}}" : "${MODEL:=gpt-4o}" API_URL="${BASE_URL:-https://api.openai.com/v1}/chat/completions" HEADER_ARGS=( @@ -1655,26 +1692,8 @@ validate_config() { util_body_convert() { util_awk_run -f "$AWK_DIR/json.awk" -f "$AWK_DIR/transport_openai_body.awk"; } sse_convert() { util_awk_run -f "$AWK_DIR/json.awk" -f "$AWK_DIR/transport_openai_sse.awk"; } ;; - *) - util_die "Unknown provider: $PROVIDER (use claude|openai)" - ;; esac - # Auto-detect DeepSeek API key: if DEEPSEEK_API_KEY is set, use DeepSeek's Anthropic endpoint - if [[ -z "$API_KEY" && -z "$BASE_URL" && -n "${DEEPSEEK_API_KEY:-}" ]]; then - PROVIDER="claude" - API_KEY="$DEEPSEEK_API_KEY" - BASE_URL="https://api.deepseek.com/anthropic" - : "${MODEL:=deepseek-v4-flash}" - fi - - if [[ -z "$API_KEY" && -z "$BASE_URL" ]]; then - case "$PROVIDER" in - claude) util_die "No API key. Set ANTHROPIC_API_KEY or use --api-key" ;; - openai) util_die "No API key. Set OPENAI_API_KEY or use --api-key" ;; - esac - fi - # Unified SSE parser — same for all providers (sse_convert normalizes to Claude format) sse_parse() { util_awk_run -v verbose="${VERBOSE:-false}" -f "$AWK_DIR/json.awk" -f "$AWK_DIR/protocol.awk" -f "$AWK_DIR/todo_protocol.awk" -f "$AWK_DIR/claude_sse.awk"; } }