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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ vibe-trading-mcp # start MCP server (stdio)
- OpenAI Codex can also be used with ChatGPT OAuth: set `LANGCHAIN_PROVIDER=openai-codex`, then run `vibe-trading provider login openai-codex`. This does not use `OPENAI_API_KEY`.
- GitHub Copilot can be used with an active Copilot subscription instead of a separately billed LLM API key. See [GitHub Copilot SDK provider](#github-copilot-sdk-provider).

> **Supported LLM providers:** OpenRouter, Requesty, OpenAI, Anthropic (native Messages API), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). When no `*_BASE_URL` is set, each provider falls back to its canonical endpoint, so just a key is enough. See `.env.example` for config.
> **Supported LLM providers:** aimlapi.com, OpenRouter, Requesty, OpenAI, Anthropic (native Messages API), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). When no `*_BASE_URL` is set, each provider falls back to its canonical endpoint, so just a key is enough. See `.env.example` for config.

> **Tip:** All markets work without any API keys thanks to automatic fallback. yfinance/Yahoo (HK/US/Canada/UK), OKX (crypto), mootdx (A-shares, TCP-direct, no IP throttle), and AKShare (A-shares, US, HK, futures, forex) are all free. LSE `.L` quotes must declare GBP or GBp so pence can be normalized before GBP accounting. Tushare token is optional — mootdx is the preferred no-token A-share fallback, with AKShare as a broader backup.

Expand Down
2 changes: 1 addition & 1 deletion README_ar.md
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ vibe-trading-mcp # start MCP server (stdio)
- **Docker** للمسار A
- يمكن استخدام OpenAI Codex أيضاً عبر ChatGPT OAuth: اضبط `LANGCHAIN_PROVIDER=openai-codex`، ثم شغل `vibe-trading provider login openai-codex`. هذا لا يستخدم `OPENAI_API_KEY`.

> **مزودو LLM المدعومون:** OpenRouter, Requesty, OpenAI, Anthropic (Messages API الأصلي), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). عند عدم ضبط أي `*_BASE_URL`، يتراجع كل مزوّد إلى نقطة نهايته القانونية، فيكفي مفتاح واحد. راجع `.env.example` للإعداد.
> **مزودو LLM المدعومون:** aimlapi.com, OpenRouter, Requesty, OpenAI, Anthropic (Messages API الأصلي), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). عند عدم ضبط أي `*_BASE_URL`، يتراجع كل مزوّد إلى نقطة نهايته القانونية، فيكفي مفتاح واحد. راجع `.env.example` للإعداد.

> **نصيحة:** تعمل كل الأسواق دون مفاتيح API بفضل fallback التلقائي. yfinance/Yahoo (HK/US/كندا/المملكة المتحدة)، وOKX (crypto)، وmootdx (أسهم A، اتصال TCP مباشر بدون قيود IP)، وAKShare (A-shares, US, HK, futures, forex) كلها مجانية. يجب أن تعلن أسعار LSE `.L` عملة GBP أو GBp لتطبيع البنس قبل المحاسبة بالجنيه. رمز Tushare اختياري — mootdx هو الـ fallback الموصى به لأسهم A بدون رمز، بينما يوفر AKShare احتياطياً أوسع تغطية.

Expand Down
2 changes: 1 addition & 1 deletion README_es.md
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ vibe-trading-mcp # inicia el servidor MCP (stdio)
- **Docker** para el Path A
- OpenAI Codex también se puede usar con ChatGPT OAuth: configura `LANGCHAIN_PROVIDER=openai-codex` y luego ejecuta `vibe-trading provider login openai-codex`. Esto no usa `OPENAI_API_KEY`.

> **Proveedores de LLM compatibles:** OpenRouter, Requesty, OpenAI, Anthropic (API de Messages nativa), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). Cuando no se configura ningún `*_BASE_URL`, cada proveedor recurre a su endpoint canónico, así que basta con una clave. Consulta `.env.example` para la configuración.
> **Proveedores de LLM compatibles:** aimlapi.com, OpenRouter, Requesty, OpenAI, Anthropic (API de Messages nativa), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama (local). Cuando no se configura ningún `*_BASE_URL`, cada proveedor recurre a su endpoint canónico, así que basta con una clave. Consulta `.env.example` para la configuración.

> **Consejo:** Todos los mercados funcionan sin ninguna clave de API gracias al fallback automático. yfinance/Yahoo (HK/US/Canadá/Reino Unido), OKX (cripto), mootdx (acciones A, conexión TCP directa, sin limitación de IP) y AKShare (acciones A, EE. UU., HK, futuros, forex) son gratuitos. Las cotizaciones LSE `.L` deben declarar GBP o GBp para normalizar los peniques antes de la contabilidad en GBP. El token de Tushare es opcional — mootdx es el fallback preferido sin token para acciones A, con AKShare como respaldo más amplio.

Expand Down
2 changes: 1 addition & 1 deletion README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ vibe-trading-mcp # start MCP server (stdio)
- Path A では **Docker**
- OpenAI Codex は ChatGPT OAuth でも利用できます。`LANGCHAIN_PROVIDER=openai-codex` を設定し、`vibe-trading provider login openai-codex` を実行してください。`OPENAI_API_KEY` は使いません。

> **Supported LLM providers:** OpenRouter、Requesty、OpenAI、Anthropic(ネイティブ Messages API)、DeepSeek、Gemini、Groq、DashScope/Qwen、Zhipu、Moonshot/Kimi、MiniMax、SiliconFlow(CN + Global)、Xiaomi MIMO、Novita AI、iFlytek Spark、Z.ai、NVIDIA NIM、ModelScope、GitHub Copilot、Ollama(local)。`*_BASE_URL` が未設定の場合、各プロバイダーは canonical なエンドポイントにフォールバックするため、key だけで十分です。設定は `.env.example` を参照してください。
> **Supported LLM providers:** aimlapi.com、OpenRouter、Requesty、OpenAI、Anthropic(ネイティブ Messages API)、DeepSeek、Gemini、Groq、DashScope/Qwen、Zhipu、Moonshot/Kimi、MiniMax、SiliconFlow(CN + Global)、Xiaomi MIMO、Novita AI、iFlytek Spark、Z.ai、NVIDIA NIM、ModelScope、GitHub Copilot、Ollama(local)。`*_BASE_URL` が未設定の場合、各プロバイダーは canonical なエンドポイントにフォールバックするため、key だけで十分です。設定は `.env.example` を参照してください。

> **Tip:** 自動フォールバックにより、すべての市場は API key なしで利用できます。yfinance/Yahoo(HK/US/カナダ/英国)、OKX(crypto)、mootdx(A 株、TCP 直結で IP 制限なし)、AKShare(A-shares、US、HK、futures、forex)はすべて無料です。LSE `.L` クォートは GBP または GBp の明示が必要で、GBP 会計の前にペンスを正規化します。Tushare token は任意で、A 株は mootdx が推奨の no-token fallback、AKShare がより広いカバレッジのバックアップになります。

Expand Down
2 changes: 1 addition & 1 deletion README_ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ vibe-trading-mcp # start MCP server (stdio)
- 경로 A용 **Docker**
- OpenAI Codex도 ChatGPT OAuth로 사용할 수 있습니다. `LANGCHAIN_PROVIDER=openai-codex`를 설정한 뒤 `vibe-trading provider login openai-codex`를 실행하세요. 이 방식은 `OPENAI_API_KEY`를 사용하지 않습니다.

> **지원 LLM provider:** OpenRouter, Requesty, OpenAI, Anthropic (native Messages API), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama(local). `*_BASE_URL`이 설정되지 않으면 각 provider는 canonical endpoint로 폴백하므로 key만 있으면 충분합니다. 설정은 `.env.example`을 참고하세요.
> **지원 LLM provider:** aimlapi.com, OpenRouter, Requesty, OpenAI, Anthropic (native Messages API), DeepSeek, Gemini, Groq, DashScope/Qwen, Zhipu, Moonshot/Kimi, MiniMax, SiliconFlow (CN + Global), Xiaomi MIMO, Novita AI, iFlytek Spark, Z.ai, NVIDIA NIM, ModelScope, GitHub Copilot, Ollama(local). `*_BASE_URL`이 설정되지 않으면 각 provider는 canonical endpoint로 폴백하므로 key만 있으면 충분합니다. 설정은 `.env.example`을 참고하세요.

> **팁:** 자동 fallback 덕분에 모든 시장은 API key 없이도 작동합니다. yfinance/Yahoo(HK/US/캐나다/영국), OKX(crypto), mootdx(A주, TCP 직결, IP 제한 없음), AKShare(A주, US, HK, futures, forex)는 모두 무료입니다. LSE `.L` 호가는 GBP 또는 GBp로 명시되어야 하며, GBP 회계 전에 펜스를 정규화합니다. Tushare token은 선택 사항이며, mootdx가 권장 no-token A주 fallback이고 AKShare는 더 넓은 커버리지의 백업입니다.

Expand Down
2 changes: 1 addition & 1 deletion README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ vibe-trading-mcp # start MCP server (stdio)
- 路径 A 需要 **Docker**
- OpenAI Codex 也可通过 ChatGPT OAuth 使用:设置 `LANGCHAIN_PROVIDER=openai-codex`,然后运行 `vibe-trading provider login openai-codex`。它不使用 `OPENAI_API_KEY`。

> **支持的 LLM providers:** OpenRouter、Requesty、OpenAI、Anthropic(原生 Messages API)、DeepSeek、Gemini、Groq、DashScope/Qwen、Zhipu、Moonshot/Kimi、MiniMax、SiliconFlow(CN + Global)、Xiaomi MIMO、Novita AI、iFlytek 星火、Z.ai、NVIDIA NIM、ModelScope、GitHub Copilot、Ollama(本地)。未设置 `*_BASE_URL` 时,每个 provider 会回退到其规范端点,因此只需一个 key 即可。配置见 `.env.example`。
> **支持的 LLM providers:** aimlapi.com、OpenRouter、Requesty、OpenAI、Anthropic(原生 Messages API)、DeepSeek、Gemini、Groq、DashScope/Qwen、Zhipu、Moonshot/Kimi、MiniMax、SiliconFlow(CN + Global)、Xiaomi MIMO、Novita AI、iFlytek 星火、Z.ai、NVIDIA NIM、ModelScope、GitHub Copilot、Ollama(本地)。未设置 `*_BASE_URL` 时,每个 provider 会回退到其规范端点,因此只需一个 key 即可。配置见 `.env.example`。

> **提示:** 由于自动 fallback,所有市场都可以在没有任何 API key 的情况下工作。yfinance/Yahoo(港股/美股/加拿大/英国)、OKX(加密)、mootdx(A 股,TCP 直连不封 IP)和 AKShare(A 股、美股、港股、期货、外汇)都是免费的。LSE `.L` 报价必须明示为 GBP 或 GBp,以便在英镑记账前将便士归一化。Tushare token 是可选项 —— mootdx 是首选的免 token A 股 fallback,AKShare 作为覆盖更广的兜底。

Expand Down
9 changes: 9 additions & 0 deletions agent/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,15 @@ OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
# MODELSCOPE_API_KEY=xxx
# MODELSCOPE_BASE_URL=https://api-inference.modelscope.cn/v1

# --- aimlapi.com (AI/ML API) ---
# OpenAI-compatible gateway; one key for 350+ chat models under vendor/model ids.
# Key from https://aimlapi.com/app/keys. There is no /v1/completions endpoint —
# only /v1/chat/completions and /v1/responses.
# LANGCHAIN_PROVIDER=aimlapi
# LANGCHAIN_MODEL_NAME=deepseek/deepseek-v4-pro # or openai/gpt-5-5 / anthropic/claude-sonnet-4.5
# AIMLAPI_API_KEY=xxx
# AIMLAPI_BASE_URL=https://api.aimlapi.com/v1

# --- Ollama (local) ---
# LANGCHAIN_PROVIDER=ollama
# LANGCHAIN_MODEL_NAME=qwen2.5:32b
Expand Down
12 changes: 12 additions & 0 deletions agent/cli/_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ def _provider_key_env(provider: str | None) -> str | None:
"iflytek": "SPARK_API_KEY",
"zai": "ZAI_API_KEY",
"modelscope": "MODELSCOPE_API_KEY",
"aimlapi": "AIMLAPI_API_KEY",
}.get((provider or "").lower())


Expand All @@ -479,6 +480,7 @@ def _provider_base_env(provider: str | None) -> str | None:
"iflytek": "SPARK_BASE_URL",
"zai": "ZAI_BASE_URL",
"modelscope": "MODELSCOPE_BASE_URL",
"aimlapi": "AIMLAPI_BASE_URL",
"ollama": "OLLAMA_BASE_URL",
}.get((provider or "").lower())

Expand Down Expand Up @@ -5539,6 +5541,16 @@ def _handle_prompt_command(
_INIT_ENV_PATH = Path.home() / ".vibe-trading" / ".env"

_PROVIDER_CHOICES: list[dict[str, str | None]] = [
{
"label": "aimlapi.com (recommended - 350+ chat models)",
"provider": "aimlapi",
"key_env": "AIMLAPI_API_KEY",
"base_env": "AIMLAPI_BASE_URL",
"base_url": "https://api.aimlapi.com/v1",
"model": "deepseek/deepseek-v4-pro",
"key_prefix": None,
"key_placeholder": "api-key...",
},
{
"label": "OpenRouter (recommended - multiple models)",
"provider": "openrouter",
Expand Down
6 changes: 6 additions & 0 deletions agent/cli/onboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ class Provider:


PROVIDERS: Final[tuple[Provider, ...]] = (
Provider("aimlapi", "aimlapi.com", "recommended — 350+ chat models, one key",
"deepseek/deepseek-v4-pro",
"AIMLAPI_API_KEY", "AIMLAPI_BASE_URL",
"https://api.aimlapi.com/v1", None,
("deepseek/deepseek-v4-pro", "openai/gpt-5-5",
"anthropic/claude-sonnet-4.5", "google/gemini-3.5-flash")),
Provider("openrouter", "OpenRouter", "recommended — 200+ models, one key",
"deepseek/deepseek-v4-pro",
"OPENROUTER_API_KEY", "OPENROUTER_BASE_URL",
Expand Down
46 changes: 46 additions & 0 deletions agent/src/providers/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from functools import lru_cache
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from types import MappingProxyType
from typing import Mapping, Optional


Expand Down Expand Up @@ -111,6 +112,50 @@ def _gh_cli_token() -> str:
default_headers={"User-Agent": _VIBE_USER_AGENT},
)

#: Attribution headers for AI/ML API requests. ``HTTP-Referer`` and ``X-Title``
#: are the OpenRouter convention and name the *calling application* — this
#: project — not the gateway. The two ``X-AIMLAPI-*`` headers are the gateway's
#: own partner/channel attribution. Read-only so the constant cannot be mutated
#: by a caller that receives the merged header dict; ``build_llm`` copies it per
#: request.
#:
#: These are scoped by request host at the call site
#: (``_aimlapi_attribution_headers`` in ``llm.py``) rather than by provider
#: label, because ``AIMLAPI_BASE_URL`` can point the same label at a corporate
#: proxy or a look-alike domain, and an identifier must not follow it there.
AIMLAPI_ATTRIBUTION_HEADERS: Mapping[str, str] = MappingProxyType(
{
"HTTP-Referer": "https://github.com/HKUDS/Vibe-Trading",
"X-Title": "Vibe-Trading",
"X-AIMLAPI-Partner-ID": "part_m4LUWIsqHcT74jLfzge4zJAJ",
"X-AIMLAPI-Source": "agent/vibe-trading",
}
)

#: Hosts that really are AI/ML API. Matched against the resolved base URL's
#: hostname *exactly*: a substring or suffix test would also accept
#: ``api.aimlapi.com.example.net``.
AIMLAPI_ATTRIBUTION_HOSTS: frozenset[str] = frozenset(
{"api.aimlapi.com", "aimlapi.com"}
)

# AI/ML API is an OpenAI-compatible aggregator: one key, 350+ chat models under
# ``vendor/model`` ids. Verified live on 2026-09-03 against
# ``deepseek/deepseek-v4-pro``, ``openai/gpt-5-5``,
# ``anthropic/claude-sonnet-4.5``, ``google/gemini-3.5-flash``,
# ``alibaba/qwen3.5-plus`` and ``z-ai/glm-5.3-flash``: reasoning models return
# ``reasoning_content``, and a top-level ``reasoning_effort`` was accepted by
# every one of them, which is the bar ``top_level_reasoning_effort`` sets.
# ``send_reasoning_content`` stays off — replaying reasoning on assistant turns
# is unverified here, exactly as it is for OpenRouter.
_AIMLAPI_CAPABILITIES = ProviderCapabilities(
"aimlapi",
"AIMLAPI_API_KEY",
"AIMLAPI_BASE_URL",
capture_reasoning=True,
top_level_reasoning_effort=True,
)

# GLM thinking models (glm-4.5+/glm-5.x) stream the chain-of-thought as
# ``reasoning_content`` with the final answer in ``content``. Capture the
# reasoning like DeepSeek; do NOT replay it on assistant turns —
Expand Down Expand Up @@ -231,6 +276,7 @@ def _gh_cli_token() -> str:
"MODELSCOPE_API_KEY",
"MODELSCOPE_BASE_URL",
),
"aimlapi": _AIMLAPI_CAPABILITIES,
"ollama": ProviderCapabilities("ollama", None, "OLLAMA_BASE_URL"),
"copilot": _COPILOT_CAPABILITIES,
"github-copilot": _COPILOT_CAPABILITIES,
Expand Down
46 changes: 40 additions & 6 deletions agent/src/providers/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from src.config.accessor import get_env_config, reset_env_config
from src.providers.capabilities import (
AIMLAPI_ATTRIBUTION_HEADERS,
AIMLAPI_ATTRIBUTION_HOSTS,
ProviderCapabilities,
get_llm_credentials,
get_provider_capabilities,
Expand Down Expand Up @@ -1329,6 +1331,35 @@ def _sends_top_level_reasoning_effort(caps: ProviderCapabilities) -> bool:
return _supports_top_level_reasoning_effort(caps) and _openai_label_points_at_openai(caps)


def _aimlapi_attribution_headers(
caps: ProviderCapabilities, base_url: str
) -> dict[str, str]:
"""Return AI/ML API attribution headers when the request really goes there.

Args:
caps: Resolved provider capabilities.
base_url: Base URL the request will actually be sent to.

Returns:
A fresh header dict for AI/ML API's own hosts, empty otherwise.

Notes:
The gate is the resolved *hostname*, not the provider label and not a
substring of the URL. ``AIMLAPI_BASE_URL`` (or an ambient
``OPENAI_BASE_URL``) can point the label at a corporate proxy, and
``api.aimlapi.com.example.net`` contains the real host as a prefix —
neither should receive an identifier meant for one vendor.
"""
if caps.name != "aimlapi":
return {}
host = (
urlparse(base_url if "//" in base_url else f"https://{base_url}").hostname or ""
)
if host.lower() not in AIMLAPI_ATTRIBUTION_HOSTS:
return {}
return dict(AIMLAPI_ATTRIBUTION_HEADERS)


def uses_responses_api(
provider: str,
configured_responses_api: bool | None,
Expand Down Expand Up @@ -1592,12 +1623,15 @@ def build_llm(*, model_name: Optional[str] = None, callbacks: Any = None) -> Any
"vibe_provider": provider,
"vibe_api_key": api_key,
}
if caps.default_headers:
headers = dict(caps.default_headers)
if caps.name in {"moonshot", "kimi-coding"}:
custom_ua = get_env_config().llm.moonshot_user_agent.strip()
if custom_ua:
headers["User-Agent"] = custom_ua
headers = dict(caps.default_headers)
if caps.name in {"moonshot", "kimi-coding"}:
custom_ua = get_env_config().llm.moonshot_user_agent.strip()
if custom_ua:
headers["User-Agent"] = custom_ua
# Attribution goes underneath: anything the provider record already
# configures for this key wins, and the shared constant is never mutated.
headers = {**_aimlapi_attribution_headers(caps, creds["base_url"]), **headers}
if headers:
_validate_explicit_headers(headers, source=f"{caps.name} provider configuration")
kwargs["default_headers"] = headers
if get_env_config().llm.vibe_trading_disable_http_proxy:
Expand Down
9 changes: 9 additions & 0 deletions agent/src/providers/llm_providers.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
[
{
"name": "aimlapi",
"label": "aimlapi.com",
"api_key_env": "AIMLAPI_API_KEY",
"base_url_env": "AIMLAPI_BASE_URL",
"default_model": "deepseek/deepseek-v4-pro",
"default_base_url": "https://api.aimlapi.com/v1",
"api_key_required": true
},
{
"name": "openrouter",
"label": "OpenRouter",
Expand Down
1 change: 1 addition & 0 deletions agent/src/swarm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
_REASONING_EFFORTS = frozenset({"none", "low", "medium", "high", "max"})
_PUBLIC_PROVIDERS = frozenset(
{
"aimlapi",
"anthropic",
"dashscope",
"deepseek",
Expand Down
Loading