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
14 changes: 14 additions & 0 deletions src/aish/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,11 @@ class ConfigModel(BaseModel):
),
)

is_free_key: bool = Field(
default=False,
description="Whether the current configuration uses a free API key",
)

@field_validator("tool_arg_preview", mode="before")
@classmethod
def normalize_tool_arg_preview(cls, v: Any) -> dict[str, ToolArgPreviewSettings]:
Expand Down Expand Up @@ -567,6 +572,15 @@ def set_api_key(self, api_key: Optional[str]) -> None:
self.config_model.api_key = api_key
self.save_config()

def is_free_key(self) -> bool:
"""Check if using free API key"""
return self.config_model.is_free_key

def set_is_free_key(self, is_free_key: bool) -> None:
"""Set whether using free API key"""
self.config_model.is_free_key = is_free_key
self.save_config()

@property
def model_config(self) -> ConfigModel:
"""Get the underlying Pydantic model"""
Expand Down
44 changes: 44 additions & 0 deletions src/aish/i18n/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,45 @@ cli:
no_spaces: "API key must not contain spaces."

setup:
entry_title: "Initial setup"
entry_header: "Choose how you want to configure your API access."
step_free_key: "Free key setup"
step_provider: "Step 1/3: Choose provider"
step_provider_endpoint: "Step 1.5/3: Choose endpoint"
step_zai_endpoint: "Step 1.5/3: Choose Z.AI endpoint"
step_key: "Step 2/3: Enter API key"
step_model: "Step 3/3: Choose model"
free_key_header: "Get a free API key using this device fingerprint."
free_key_detecting_location: "Detecting service region..."
free_key_location_detected: "Detected region: {location}"
free_key_location_cn: "Regional"
free_key_location_overseas: "Global"
free_key_registering: "Registering free API key..."
free_key_success: "Free API key acquired"
free_key_missing_key: "Registration succeeded but no API key was returned"
free_key_already_registered: "This device has already registered a free key"
free_key_fingerprint_failed: "Unable to collect device information for fingerprint"
free_key_failed_with_reason: "Free API key registration failed: {reason}"
free_key_provider_label: "Free API"
free_key_privacy_title: "Privacy Notice"
free_key_privacy_notice: |
To provide the free API Key service, we need to collect the following device information:

• Device fingerprint (a hash generated from hardware info like MAC address, disk serial number, motherboard serial number, etc.)
• Your service region (for optimal server assignment)

Purpose:
• Device fingerprint is used to prevent abuse, ensuring each device can only obtain one free key
• Service region is used to assign you the nearest server node

We promise:
• No personal identity information will be collected
• Device fingerprint is an irreversible hash, original hardware info cannot be recovered
• Data is used solely for this service and will not be shared with third parties
free_key_quota_exhausted: "Free quota exhausted"
free_key_quota_exhausted_hint: "Your free API Key quota has been exhausted. Please configure your own API Key to continue."
free_key_service_unavailable: "Service temporarily unavailable"
free_key_service_unavailable_hint: "Server resources have reached the limit or the service is temporarily unavailable. Please contact the administrator to increase the quota, or configure your own API Key to continue."
provider_header: "Choose a provider (type to filter)"
provider_filter_prompt: "Filter: "
provider_filter_hint: "Type to filter, use ↑/↓ to select, Enter to confirm, Esc to cancel."
Expand Down Expand Up @@ -121,9 +155,16 @@ cli:
action_retry_model: "Choose another model"
action_retry_api_base: "Re-enter API Base"
action_retry_api_key: "Re-enter API Key"
action_retry_free_key: "Retry free key registration"
action_fallback_manual: "Switch to manual setup"
action_use_existing_key: "Use existing key"
action_use_free_key: "Use free API key"
action_manual_setup: "Manual provider setup"
action_change_provider: "Change provider"
action_continue: "Continue anyway (not recommended)"
action_exit: "Exit setup"
action_agree: "Agree and continue"
action_disagree: "Disagree"
retry_api_base_prompt: "Enter API Base (type back to return)"
retry_api_base_prompt_with_current: "Enter API Base (current: {current}, type back to return)"
retry_api_base_required: "API Base is required"
Expand Down Expand Up @@ -264,6 +305,9 @@ shell:
llm_error_details_title: "Debug Details"
tool_args_json_invalid_hint: "❌ Request failed, please retry."

hint:
run_setup: "Type /setup to reconfigure your API Key"

prompt:
confirm_execute: "?Execute this command? [y/n]:"
ai_hint: "Use ';' to ask AI"
Expand Down
44 changes: 44 additions & 0 deletions src/aish/i18n/zh-CN.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,45 @@ cli:
no_spaces: "API Key 不能包含空格。"

setup:
entry_title: "初始化配置"
entry_header: "请选择 API 接入方式。"
step_free_key: "免费 Key 配置"
step_provider: "步骤 1/3:选择 Provider"
step_provider_endpoint: "步骤 1.5/3:选择端点"
step_zai_endpoint: "步骤 1.5/3:选择 Z.AI 端点"
step_key: "步骤 2/3:填写 API Key"
step_model: "步骤 3/3:选择模型"
free_key_header: "通过本机设备指纹获取免费 API Key。"
free_key_detecting_location: "正在检测服务区域..."
free_key_location_detected: "检测到区域:{location}"
free_key_location_cn: "国内节点"
free_key_location_overseas: "国际节点"
free_key_registering: "正在注册免费 API Key..."
free_key_success: "已获取免费 API Key"
free_key_missing_key: "注册成功,但服务端未返回 API Key"
free_key_already_registered: "该设备已注册过免费Key"
free_key_fingerprint_failed: "无法采集设备信息,指纹生成失败"
free_key_failed_with_reason: "免费 API Key 注册失败:{reason}"
free_key_provider_label: "免费 API"
free_key_privacy_title: "隐私声明"
free_key_privacy_notice: |
为了提供免费 API Key 服务,我们需要收集以下设备信息:

• 设备指纹(基于 MAC 地址、磁盘序列号、主板序列号等硬件信息生成的哈希值)
• 服务区域(用于分配最近的服务节点)

用途说明:
• 设备指纹用于防止滥用,确保每个设备只能获取一次免费 Key
• 服务区域用于为您分配最近的服务节点

我们承诺:
• 不会收集任何个人身份信息
• 设备指纹为不可逆的哈希值,无法还原原始硬件信息
• 数据仅用于本服务,不会与第三方共享
free_key_quota_exhausted: "免费额度已用完"
free_key_quota_exhausted_hint: "您的免费 API Key 额度已耗尽,请配置您自己的 API Key 继续使用。"
free_key_service_unavailable: "服务暂时不可用"
free_key_service_unavailable_hint: "服务器资源已达上限或服务暂时不可用,请联系管理员增加配额,或配置您自己的 API Key 继续使用。"
provider_header: "选择 Provider"
provider_filter_prompt: "过滤:"
provider_filter_hint: "输入关键词实时过滤,↑/↓ 选择,回车确认,Esc 取消"
Expand Down Expand Up @@ -121,9 +155,16 @@ cli:
action_retry_model: "重新选择模型"
action_retry_api_base: "重新输入 API Base"
action_retry_api_key: "重新输入 API Key"
action_retry_free_key: "重试免费 Key 注册"
action_fallback_manual: "切换到手动配置"
action_use_existing_key: "使用已有的 Key"
action_use_free_key: "使用免费 API Key"
action_manual_setup: "手动配置 Provider"
action_change_provider: "更换 Provider"
action_continue: "继续保存(不推荐)"
action_exit: "退出配置"
action_agree: "同意并继续"
action_disagree: "不同意"
retry_api_base_prompt: "请输入 API Base(输入 back 返回)"
retry_api_base_prompt_with_current: "请输入 API Base(当前:{current},输入 back 返回)"
retry_api_base_required: "API Base 不能为空"
Expand Down Expand Up @@ -264,6 +305,9 @@ shell:
llm_error_details_title: "调试详情"
tool_args_json_invalid_hint: "❌ 请求失败,请重试。"

hint:
run_setup: "输入 /setup 重新配置 API Key"

prompt:
confirm_execute: "?是否执行此命令? [y/n]: "
ai_hint: "';' Ask AI ..."
Expand Down
56 changes: 56 additions & 0 deletions src/aish/shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,7 @@ async def handle_setup_command(self, user_input: str) -> None:
new_model = new_config.model
new_api_base = new_config.api_base
new_api_key = new_config.api_key
new_is_free_key = new_config.is_free_key

self.llm_session.update_model(
new_model,
Expand All @@ -1450,6 +1451,7 @@ async def handle_setup_command(self, user_input: str) -> None:
self.config.model = new_model
self.config.api_base = new_api_base
self.config.api_key = new_api_key
self.config.is_free_key = new_is_free_key
if self.session_record is not None:
self.session_record.model = new_model

Expand Down Expand Up @@ -3094,6 +3096,34 @@ def handle_error_event(self, event: LLMEvent):
error_type = event.data.get("error_type", "general")
error_details = event.data.get("error_details")

# Check for quota/rate limit errors (only for free key mode)
# self.config is ConfigModel, use .is_free_key attribute
is_free_key = getattr(self.config, "is_free_key", False)
quota_exhausted = False
service_unavailable = False
if is_free_key:
error_lower = str(error_message).lower()
# Check for quota exhaustion (rate limit, quota exceeded)
quota_exhausted = any(
keyword in error_lower
for keyword in [
"rate limit", "quota", "insufficient", "429", "exceeded",
"too many requests"
]
)
# Check for service unavailability (auth errors, not found, server errors)
# These may indicate the free key is invalid or server resources exhausted
if not quota_exhausted:
service_unavailable = any(
keyword in error_lower
for keyword in [
"401", "403", "404", "500", "502", "503", "504",
"authentication", "unauthorized", "forbidden",
"not found", "internal server error", "bad gateway",
"service unavailable", "gateway timeout"
]
)

if error_type == "streaming_error":
self.console.print(f"❌ Streaming Error: {error_message}", style="red")
elif error_type == "litellm_error":
Expand All @@ -3112,6 +3142,32 @@ def handle_error_event(self, event: LLMEvent):
border_style="dim",
)
)
# Show quota exhausted hint
if quota_exhausted:
self.console.print(
Panel(
t("cli.setup.free_key_quota_exhausted_hint"),
title=t("cli.setup.free_key_quota_exhausted"),
border_style="yellow",
)
)
self.console.print(
f"💡 {t('shell.hint.run_setup')}",
style="dim"
)
# Show service unavailable hint (for free key)
elif service_unavailable:
self.console.print(
Panel(
t("cli.setup.free_key_service_unavailable_hint"),
title=t("cli.setup.free_key_service_unavailable"),
border_style="yellow",
)
)
self.console.print(
f"💡 {t('shell.hint.run_setup')}",
style="dim"
)
else:
self.console.print(f"❌ Error: {error_message}", style="red")

Expand Down
43 changes: 30 additions & 13 deletions src/aish/wizard/provider_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,36 @@

from dataclasses import dataclass

from .constants import (_AI_GATEWAY_DEFAULT_MODEL, _AI_GATEWAY_MODELS,
_HUGGINGFACE_DEFAULT_MODEL, _HUGGINGFACE_MODELS,
_KILOCODE_DEFAULT_MODEL, _KILOCODE_MODELS,
_MINIMAX_DEFAULT_MODELS, _MINIMAX_ENDPOINTS,
_MINIMAX_MODELS, _MISTRAL_DEFAULT_MODEL,
_MISTRAL_MODELS, _MOONSHOT_DEFAULT_MODELS,
_MOONSHOT_ENDPOINTS, _MOONSHOT_MODELS,
_OLLAMA_DEFAULT_MODEL, _OLLAMA_MODELS, _QIANFAN_MODELS,
_QWEN_DEFAULT_MODEL, _QWEN_MODELS,
_TOGETHER_DEFAULT_MODEL, _TOGETHER_MODELS,
_VLLM_DEFAULT_MODEL, _VLLM_MODELS, _XAI_DEFAULT_MODEL,
_XAI_MODELS, _ZAI_DEFAULT_MODELS, _ZAI_ENDPOINTS,
_ZAI_MODELS)
from .constants import (
_AI_GATEWAY_DEFAULT_MODEL,
_AI_GATEWAY_MODELS,
_HUGGINGFACE_DEFAULT_MODEL,
_HUGGINGFACE_MODELS,
_KILOCODE_DEFAULT_MODEL,
_KILOCODE_MODELS,
_MINIMAX_DEFAULT_MODELS,
_MINIMAX_ENDPOINTS,
_MINIMAX_MODELS,
_MISTRAL_DEFAULT_MODEL,
_MISTRAL_MODELS,
_MOONSHOT_DEFAULT_MODELS,
_MOONSHOT_ENDPOINTS,
_MOONSHOT_MODELS,
_OLLAMA_DEFAULT_MODEL,
_OLLAMA_MODELS,
_QIANFAN_MODELS,
_QWEN_DEFAULT_MODEL,
_QWEN_MODELS,
_TOGETHER_DEFAULT_MODEL,
_TOGETHER_MODELS,
_VLLM_DEFAULT_MODEL,
_VLLM_MODELS,
_XAI_DEFAULT_MODEL,
_XAI_MODELS,
_ZAI_DEFAULT_MODELS,
_ZAI_ENDPOINTS,
_ZAI_MODELS,
)


@dataclass
Expand Down
9 changes: 7 additions & 2 deletions src/aish/wizard/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@

from ..i18n import t
from ..litellm_loader import load_litellm
from .constants import (_PROVIDER_ALIASES, _PROVIDER_BASES, _PROVIDER_ENV_KEYS,
_PROVIDER_LABELS, _PROVIDER_PRIORITY)
from .constants import (
_PROVIDER_ALIASES,
_PROVIDER_BASES,
_PROVIDER_ENV_KEYS,
_PROVIDER_LABELS,
_PROVIDER_PRIORITY,
)
from .helpers import _is_valid_url, _looks_like_api_base, _matches_filter_query
from .types import ProviderOption

Expand Down
Loading
Loading