diff --git a/bin/omarchy-cn-ai-default b/bin/omarchy-cn-ai-default index c2927ab8..6f58ca3a 100755 --- a/bin/omarchy-cn-ai-default +++ b/bin/omarchy-cn-ai-default @@ -36,7 +36,7 @@ if omarchy-cmd-missing "$command"; then $install fi -key=$(omarchy-cn-ai-secret get "$provider") +key=$(cn_ai_resolve_key "$provider") case "$harness" in claude-code) @@ -45,7 +45,8 @@ claude-code) settings="$HOME/.claude/settings.json" mkdir -p "${settings%/*}" base=$(cn_ai_endpoint "$provider" anthropic) - fast=$(cn_ai_fast_model "$provider") + # Dynamic-model providers have no fast alias: every tier maps to the model + fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model") current="{}" if [[ -s $settings ]]; then current=$(cat "$settings") diff --git a/bin/omarchy-cn-ai-doctor b/bin/omarchy-cn-ai-doctor index d8533a83..1b7f1d01 100755 --- a/bin/omarchy-cn-ai-doctor +++ b/bin/omarchy-cn-ai-doctor @@ -38,6 +38,8 @@ fi if [[ $proto == "native" ]]; then echo "INFO ai: $harness 自管鉴权,跳过 OmarchyCN Key 检查" +elif cn_ai_static_token "$provider" > /dev/null 2>&1; then + echo "PASS ai: $provider 使用内置 token,无需存储 Key" elif omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then echo "PASS ai: $provider API Key stored" else @@ -47,7 +49,7 @@ fi if [[ $proto != "native" ]]; then base=$(cn_ai_endpoint "$provider" "$proto") - host=$(sed -E 's|https?://([^/]+).*|\1|' <<<"$base") + host=$(sed -E 's|https?://([^/:]+).*|\1|' <<<"$base") if getent hosts "$host" > /dev/null 2>&1; then echo "PASS ai: endpoint DNS resolves ($host)" else diff --git a/bin/omarchy-cn-ai-launch b/bin/omarchy-cn-ai-launch index 4ce827b4..c4800d22 100755 --- a/bin/omarchy-cn-ai-launch +++ b/bin/omarchy-cn-ai-launch @@ -32,7 +32,7 @@ fi case "$config_method" in env) - key=$(omarchy-cn-ai-secret get "$provider") + key=$(cn_ai_resolve_key "$provider") if [[ $harness == "codex" ]]; then cn_ai_render_codex_config "$provider" "$model" "$key" exec "$command" diff --git a/bin/omarchy-cn-ai-profile-create b/bin/omarchy-cn-ai-profile-create index e05b3f4c..ca291482 100755 --- a/bin/omarchy-cn-ai-profile-create +++ b/bin/omarchy-cn-ai-profile-create @@ -33,7 +33,9 @@ if [[ $level == "unsupported" ]]; then fi allowlist=$(cn_ai_combo_models "$harness" "$provider") -if [[ -n $allowlist ]]; then +if cn_ai_models_dynamic "$provider"; then + : # runtime-listed models; omarchycn ai test validates against the live server +elif [[ -n $allowlist ]]; then # Combo-restricted model set (e.g. codex uses its own catalog slugs) if ! grep -qxF "$model" <<<"$allowlist"; then echo "Model $model not supported for $harness x $provider" >&2 @@ -68,6 +70,6 @@ secret_ref = "omarchycn://ai/$provider" EOF echo "Profile $name: $harness x $provider x $model (compat: $level)" -if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then +if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then echo "提示: 尚未存储 $provider 的 API Key,运行: omarchycn ai secret set $provider" fi diff --git a/bin/omarchy-cn-ai-setup b/bin/omarchy-cn-ai-setup index 7ed8833b..5255525a 100755 --- a/bin/omarchy-cn-ai-setup +++ b/bin/omarchy-cn-ai-setup @@ -24,7 +24,31 @@ provider=$(printf '%s\n' "${providers[@]}" | gum choose --header "选择 Provide harness_proto=$(cn_ai_harness_field "$harness" protocol) allowlist=$(cn_ai_combo_models "$harness" "$provider") -if [[ -n $allowlist ]]; then +if cn_ai_models_dynamic "$provider"; then + base=$(cn_ai_endpoint "$provider" "$harness_proto") + if [[ $provider == "ollama" ]]; then + if omarchy-cmd-missing ollama; then + gum confirm "Ollama 未安装,现在安装本地服务?" || exit 1 + if omarchy-cmd-present nvidia-smi; then + ollama_pkg=ollama-cuda + elif omarchy-cmd-present rocminfo; then + ollama_pkg=ollama-rocm + else + ollama_pkg=ollama + fi + omarchy-pkg-add "$ollama_pkg" + fi + if ! curl -sf --connect-timeout 3 "$base/api/tags" > /dev/null; then + sudo systemctl enable --now ollama + fi + fi + models=$(curl -sf --connect-timeout 3 "$base/api/tags" | jq -r '.models[].name' || true) + if [[ -z $models ]]; then + echo "无法列出本地模型(服务未运行或尚未拉取):先 ollama pull <模型>(如 qwen3-coder)再重试" >&2 + exit 1 + fi + model=$(gum choose --header "选择本地模型" <<<"$models") +elif [[ -n $allowlist ]]; then model=$(gum choose --header "选择模型" <<<"$allowlist") else # Hide models bound to a different wire protocol than the harness speaks @@ -48,7 +72,7 @@ if [[ $proto == "native" ]]; then exit 0 fi -if ! omarchy-cn-ai-secret get "$provider" > /dev/null 2>&1; then +if ! cn_ai_resolve_key "$provider" > /dev/null 2>&1; then key=$(gum input --password --header "$provider API Key(获取: $(jq -r --arg p "$provider" '.providers[$p].key_url' "$CN_AI_PROVIDERS"))") printf '%s' "$key" | omarchy-cn-ai-secret set "$provider" fi diff --git a/bin/omarchy-cn-ai-test b/bin/omarchy-cn-ai-test index db4eb20f..3ac1c544 100755 --- a/bin/omarchy-cn-ai-test +++ b/bin/omarchy-cn-ai-test @@ -31,7 +31,7 @@ fi base="${OMARCHYCN_AI_TEST_BASE_URL:-$(cn_ai_endpoint "$provider" "${proto%-responses}")}" echo "INFO ai-test: $profile ($harness x $provider x $model) -> $base" -if ! key=$(omarchy-cn-ai-secret get "$provider" 2>/dev/null); then +if ! key=$(cn_ai_resolve_key "$provider" 2>/dev/null); then echo "FAIL ai-test: 未存储 $provider 的 API Key (omarchycn ai secret set $provider)" exit 1 fi diff --git a/cn/lib/ai.sh b/cn/lib/ai.sh index 12928488..6f3a39d1 100644 --- a/cn/lib/ai.sh +++ b/cn/lib/ai.sh @@ -31,6 +31,21 @@ cn_ai_harness_field() { jq -re --arg h "$1" --arg f "$2" '.harnesses[$h][$f]' "$CN_AI_HARNESSES" } +# Providers with a fixed token (local servers) need no stored secret. +# `// empty` keeps stdout clean when absent (bare -re would print "null"). +cn_ai_static_token() { + jq -re --arg p "$1" '.providers[$p].static_token // empty' "$CN_AI_PROVIDERS" +} + +cn_ai_resolve_key() { + cn_ai_static_token "$1" 2>/dev/null || omarchy-cn-ai-secret get "$1" +} + +# Dynamic-model providers list models at runtime, not in the registry +cn_ai_models_dynamic() { + jq -e --arg p "$1" '.providers[$p].models_dynamic == true' "$CN_AI_PROVIDERS" > /dev/null +} + cn_ai_combo_level() { jq -r --arg h "$1" --arg p "$2" '.combos[$h][$p].level // "unsupported"' "$CN_AI_COMPAT" } @@ -68,7 +83,8 @@ cn_ai_render_env() { case "$harness" in claude-code) base=$(cn_ai_endpoint "$provider" anthropic) - fast=$(cn_ai_fast_model "$provider") + # Dynamic-model providers have no fast alias: every tier maps to the model + fast=$(cn_ai_fast_model "$provider" 2>/dev/null || printf '%s' "$model") printf 'export ANTHROPIC_BASE_URL=%q\n' "$base" # Docs vary between AUTH_TOKEN (deepseek/zai) and API_KEY (kimi): set both printf 'export ANTHROPIC_AUTH_TOKEN=%q\n' "$key" diff --git a/cn/registry/ai-compatibility.json b/cn/registry/ai-compatibility.json index 30482104..ce15ac3e 100644 --- a/cn/registry/ai-compatibility.json +++ b/cn/registry/ai-compatibility.json @@ -21,6 +21,11 @@ "level": "stable", "doc": "https://platform.minimax.io/docs/token-plan/claude-code", "adapter": true + }, + "ollama": { + "level": "stable", + "doc": "https://docs.ollama.com/api/anthropic-compatibility", + "adapter": true } }, "codex": { diff --git a/cn/registry/ai-providers.json b/cn/registry/ai-providers.json index b14dd112..a37cd102 100644 --- a/cn/registry/ai-providers.json +++ b/cn/registry/ai-providers.json @@ -1,5 +1,5 @@ { - "_verified": "2026-08-25, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code)", + "_verified": "2026-08-27, from official docs (see OmarchyCN PRD §27; minimax: platform.minimax.io/docs/token-plan/claude-code; ollama: docs.ollama.com/api/anthropic-compatibility, fixed token \"ollama\", models are whatever is pulled locally)", "providers": { "deepseek": { "display_name": "DeepSeek", @@ -124,6 +124,18 @@ } ] }, + "ollama": { + "display_name": "Ollama 本地", + "auth": [ + "none" + ], + "static_token": "ollama", + "endpoints": { + "anthropic": "http://localhost:11434" + }, + "models": [], + "models_dynamic": true + }, "minimax": { "display_name": "MiniMax", "auth": [