diff --git a/cli.py b/cli.py index 424594acf8d6..f78c21259ea2 100644 --- a/cli.py +++ b/cli.py @@ -2471,7 +2471,10 @@ def _normalize_model_for_provider(self, resolved_provider: str) -> bool: if resolved_provider not in _AGGREGATOR_PROVIDERS: normalized_model = normalize_model_for_provider(current_model, resolved_provider) if normalized_model and normalized_model != current_model: - if not self._model_is_default: + # Vertex's OpenAPI endpoint requires a publisher prefix (google/*) + # — adding it silently is correct behaviour, not a user error. + _silent_prefix = (resolved_provider == "vertex" and normalized_model == f"google/{current_model}") + if not self._model_is_default and not _silent_prefix: self._console_print( f"[yellow]⚠️ Normalized model '{current_model}' to '{normalized_model}' for {resolved_provider}.[/]" ) diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 00685436dbdd..e245f22fb8af 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -356,6 +356,14 @@ class ProviderConfig: api_key_env_vars=(), base_url_env_var="BEDROCK_BASE_URL", ), + "vertex": ProviderConfig( + id="vertex", + name="Google Vertex AI", + auth_type="api_key", + inference_base_url="", + api_key_env_vars=("VERTEX_CREDENTIALS_PATH", "GOOGLE_APPLICATION_CREDENTIALS"), + base_url_env_var="VERTEX_REGION", + ) } diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 55245228fea9..381e6cd829ee 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -1624,6 +1624,8 @@ def _named_custom_provider_map(cfg) -> dict[str, dict[str, str]]: _model_flow_stepfun(config, current_model) elif selected_provider == "bedrock": _model_flow_bedrock(config, current_model) + elif selected_provider == "vertex": + _model_flow_vertex(config, current_model) elif selected_provider in ( "gemini", "deepseek", @@ -3965,6 +3967,89 @@ def _sort_key(m): print(" No change.") +def _model_flow_vertex(config, current_model=""): + """Google Vertex AI provider flow. + + Credentials come from a service account JSON file or ADC — no API key + prompt. Region is the only optional override. + """ + from hermes_cli.auth import ( + _prompt_model_selection, + _save_model_choice, + deactivate_provider, + ) + from hermes_cli.config import get_env_value, save_env_value, load_config, save_config + from hermes_cli.models import _PROVIDER_MODELS + from hermes_cli.vertex_adapter import get_vertex_config, DEFAULT_REGION + + # 1. Resolve credentials (SA file or ADC) — just show status, don't prompt + creds_path = ( + get_env_value("VERTEX_CREDENTIALS_PATH") + or os.getenv("VERTEX_CREDENTIALS_PATH", "") + or get_env_value("GOOGLE_APPLICATION_CREDENTIALS") + or os.getenv("GOOGLE_APPLICATION_CREDENTIALS", "") + ) + if creds_path: + short = creds_path if len(creds_path) <= 40 else f"...{creds_path[-37:]}" + print(f" Credentials: {short} ✓") + else: + print(" Credentials: Application Default Credentials (ADC)") + print(" (Set VERTEX_CREDENTIALS_PATH or GOOGLE_APPLICATION_CREDENTIALS") + print(" to a service account JSON file, or run") + print(" `gcloud auth application-default login`.)") + print() + + # Quick probe to catch missing google-auth or invalid creds early + token, _ = get_vertex_config(credentials_path=creds_path or None) + if not token: + print(" ✗ Could not obtain a Vertex AI access token.") + print(" Check that google-auth is installed and credentials are valid.") + return + + # 2. Optional region override + current_region = ( + get_env_value("VERTEX_REGION") + or os.getenv("VERTEX_REGION", "") + or get_env_value("VERTEX_LOCATION") + or os.getenv("VERTEX_LOCATION", "") + or DEFAULT_REGION + ) + try: + region_input = input(f" Region [{current_region}]: ").strip() + except (KeyboardInterrupt, EOFError): + print() + return + region = region_input or current_region + if region != current_region: + save_env_value("VERTEX_REGION", region) + print(f" Region saved: {region}") + print() + + # 3. Model selection + model_list = _PROVIDER_MODELS.get("vertex", []) + if not model_list: + model_list = ["gemini-2.5-pro", "gemini-2.5-flash"] + + selected = _prompt_model_selection(model_list, current_model) + if not selected: + print(" No change.") + return + + _save_model_choice(selected) + + cfg = load_config() + model = cfg.get("model") + if not isinstance(model, dict): + model = {"default": model} if model else {} + cfg["model"] = model + model["provider"] = "vertex" + model.pop("base_url", None) + model.pop("api_mode", None) + save_config(cfg) + deactivate_provider() + print(f" Default model set to: {selected} (via Google Vertex AI, {region})") + + def _model_flow_api_key_provider(config, provider_id, current_model=""): """Generic flow for API-key providers (z.ai, MiniMax, OpenCode, etc.).""" from hermes_cli.auth import ( diff --git a/hermes_cli/model_normalize.py b/hermes_cli/model_normalize.py index 99e6c34e4818..fe36408e1eef 100644 --- a/hermes_cli/model_normalize.py +++ b/hermes_cli/model_normalize.py @@ -391,6 +391,13 @@ def normalize_model_for_provider(model_input: str, target_provider: str) -> str: # --- Aggregators: need vendor/model format --- if provider in _AGGREGATOR_PROVIDERS: return _prepend_vendor(name) + + # --- Vertex AI: OpenAPI endpoint strictly requires publisher prefix --- + if provider == "vertex": + bare = _strip_matching_provider_prefix(name, provider) + if not bare.startswith("google/"): + return f"google/{bare}" + return bare # --- OpenCode Zen: Claude stays hyphenated; other models keep dots --- if provider == "opencode-zen": diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 3a902ffdf5a6..5ce2b2d0a90e 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -383,6 +383,14 @@ def _codex_curated_models() -> list[str]: "us.meta.llama4-maverick-17b-instruct-v1:0", "us.meta.llama4-scout-17b-instruct-v1:0", ], + # Google vertex available models supported by the ChatCompletions API + "vertex": [ + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-2.5-flash-lite", + "gemini-2.0-flash-001", + "gemini-2.0-flash-lite-001", + ] } # Vercel AI Gateway: derive the bare-model-id catalog from the curated @@ -740,6 +748,7 @@ class ProviderEntry(NamedTuple): ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (35+ curated models, pay-as-you-go)"), ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (open models, $10/month subscription)"), ProviderEntry("bedrock", "AWS Bedrock", "AWS Bedrock (Claude, Nova, Llama, DeepSeek — IAM or API key)"), + ProviderEntry("vertex", "Vertex AI", "Google Vertex AI (GCP Service Account JSON)"), ] # Derived dicts — used throughout the codebase @@ -2510,6 +2519,8 @@ def validate_requested_model( requested, api_key=api_key, ) or requested + elif normalized == "vertex" and requested_for_lookup.startswith("google/"): + requested_for_lookup = requested_for_lookup[len("google/"):] if not requested: return { diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index f65ceac7ae87..58aa5d04443e 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -71,6 +71,12 @@ class HermesOverlay: auth_type="oauth_external", base_url_override="cloudcode-pa://google", ), + "vertex": HermesOverlay( + transport="openai_chat", + auth_type="api_key", + extra_env_vars=("VERTEX_CREDENTIALS_PATH", "GOOGLE_APPLICATION_CREDENTIALS"), + base_url_env_var="VERTEX_REGION", + ), "copilot-acp": HermesOverlay( transport="codex_responses", auth_type="external_process", @@ -271,6 +277,10 @@ class ProviderDef: "gemini-cli": "google-gemini-cli", "gemini-oauth": "google-gemini-cli", + # google vertex + "vertex-ai": "vertex", + "google-vertex": "vertex", + # huggingface "hf": "huggingface", diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index cbfcbdbd6caf..232b6c7eb69a 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -212,6 +212,19 @@ def _resolve_runtime_from_pool_entry( if cfg_provider == "anthropic": cfg_base_url = str(model_cfg.get("base_url") or "").strip().rstrip("/") base_url = cfg_base_url or base_url or "https://api.anthropic.com" + elif provider == "vertex": + api_mode = "chat_completions" + from hermes_cli.vertex_adapter import get_vertex_config + region = base_url or None # base_url slot carries the region when set explicitly + token, resolved_url = get_vertex_config(credentials_path=api_key or None, region=region) + if not token or not resolved_url: + raise ValueError( + "Vertex AI credentials not found. Set VERTEX_CREDENTIALS_PATH or " + "GOOGLE_APPLICATION_CREDENTIALS to a service account JSON file, " + "or run `gcloud auth application-default login`." + ) + api_key = token + base_url = resolved_url elif provider == "openrouter": base_url = base_url or OPENROUTER_BASE_URL elif provider == "xai": diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index e28acd41b895..4a1a64fe5561 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -93,6 +93,10 @@ def _supports_same_provider_pool_setup(provider: str) -> bool: "gemini-3.1-pro-preview", "gemini-3-pro-preview", "gemini-3-flash-preview", "gemini-3.1-flash-lite-preview", ], + "vertex": [ + "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite", + "gemini-2.0-flash-001", "gemini-2.0-flash-lite-001", + ], "zai": ["glm-5.1", "glm-5", "glm-4.7", "glm-4.5", "glm-4.5-flash"], "kimi-coding": ["kimi-k2.6", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"], "kimi-coding-cn": ["kimi-k2.6", "kimi-k2.5", "kimi-k2-thinking", "kimi-k2-turbo-preview"], diff --git a/hermes_cli/vertex_adapter.py b/hermes_cli/vertex_adapter.py new file mode 100644 index 000000000000..e32c0159ca42 --- /dev/null +++ b/hermes_cli/vertex_adapter.py @@ -0,0 +1,137 @@ +"""Vertex AI (Google Cloud) adapter for Hermes CLI. + +Provides authentication and configuration for Vertex AI's OpenAI-compatible +endpoint, allowing Hermes to use Gemini models via Google Cloud with +enterprise-grade rate limits and quotas. + +Requires: pip install google-auth + +Environment variables honored (all optional — ADC is used as a fallback): + VERTEX_CREDENTIALS_PATH — path to a service account JSON file (takes precedence). + GOOGLE_APPLICATION_CREDENTIALS — standard GCP credential path. + VERTEX_PROJECT_ID — override the project_id embedded in creds. + VERTEX_REGION / VERTEX_LOCATION — override default region ("us-central1" unless set). +""" + +import logging +import os +import time +from typing import Optional, Tuple + +try: + import google.auth + import google.auth.transport.requests + from google.oauth2 import service_account +except ImportError: + google = None # type: ignore[assignment] + +logger = logging.getLogger(__name__) + +DEFAULT_REGION = "us-central1" +_creds_cache: dict = {} + + +def _resolve_credentials_path(explicit: Optional[str]) -> Optional[str]: + if explicit and os.path.exists(explicit): + return explicit + for env_var in ("VERTEX_CREDENTIALS_PATH", "GOOGLE_APPLICATION_CREDENTIALS"): + path = os.environ.get(env_var) + if path and os.path.exists(path): + return path + return None + + +def _refresh_credentials(creds) -> None: + auth_req = google.auth.transport.requests.Request() + creds.refresh(auth_req) + + +def get_vertex_credentials(credentials_path: Optional[str] = None) -> Tuple[Optional[str], Optional[str]]: + """Return a (fresh access_token, project_id) pair or (None, None) on failure. + + Caches the underlying Credentials object and refreshes it when within + 5 minutes of expiry, so repeated calls don't thrash the token endpoint. + Supports both service account files and Application Default Credentials. + """ + if google is None: + logger.warning( + "google-auth package not installed. " + "Install it via: pip install hermes-agent[vertex]" + ) + return None, None + + resolved_path = _resolve_credentials_path(credentials_path) + cache_key = resolved_path or "__adc__" + + try: + cached = _creds_cache.get(cache_key) + if cached is None: + if resolved_path: + creds = service_account.Credentials.from_service_account_file( + resolved_path, + scopes=["https://www.googleapis.com/auth/cloud-platform"], + ) + project_id = creds.project_id + else: + creds, project_id = google.auth.default( + scopes=["https://www.googleapis.com/auth/cloud-platform"] + ) + _creds_cache[cache_key] = (creds, project_id) + else: + creds, project_id = cached + + needs_refresh = ( + not getattr(creds, "token", None) + or getattr(creds, "expired", False) + or ( + getattr(creds, "expiry", None) is not None + and (creds.expiry.timestamp() - time.time()) < 300 + ) + ) + if needs_refresh: + _refresh_credentials(creds) + + override_project = os.environ.get("VERTEX_PROJECT_ID") + if override_project: + project_id = override_project + + return creds.token, project_id + + except Exception as e: + logger.error("Failed to resolve Vertex AI credentials: %s", e) + _creds_cache.pop(cache_key, None) + # If ADC failed, retry with a service account file that may have been + # added after initial startup. + if cache_key == "__adc__": + sa_path = _resolve_credentials_path(credentials_path) + if sa_path: + logger.info("ADC failed, retrying with service account: %s", sa_path) + return get_vertex_credentials(sa_path) + return None, None + + +def build_vertex_base_url(project_id: str, region: str = DEFAULT_REGION) -> str: + """Build the OpenAI-compatible base URL for Vertex AI.""" + return ( + f"https://{region}-aiplatform.googleapis.com" + f"/v1beta1/projects/{project_id}/locations/{region}/endpoints/openapi" + ) + + +def get_vertex_config( + credentials_path: Optional[str] = None, + region: Optional[str] = None, +) -> Tuple[Optional[str], Optional[str]]: + """Resolve (access_token, base_url) for Vertex AI, or (None, None) on failure.""" + token, project_id = get_vertex_credentials(credentials_path) + if not token or not project_id: + return None, None + + effective_region = ( + region + or os.environ.get("VERTEX_REGION") + or os.environ.get("VERTEX_LOCATION") + or DEFAULT_REGION + ) + base_url = build_vertex_base_url(project_id, effective_region) + return token, base_url diff --git a/pyproject.toml b/pyproject.toml index 4b7e8816ac8b..fa3c107949fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,6 +87,7 @@ google = [ "google-auth-oauthlib>=1.0,<2", "google-auth-httplib2>=0.2,<1", ] +vertex = ["google-auth>=2.0.0,<3", "requests>=2.0.0,<3"] # `hermes dashboard` (localhost SPA + API). Not in core to keep the default install lean. web = ["fastapi>=0.104.0,<1", "uvicorn[standard]>=0.24.0,<1"] rl = [ diff --git a/run_agent.py b/run_agent.py index 8db343a2a56a..47bafd72e028 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1362,7 +1362,7 @@ def __init__( # but no credentials were found, fail fast with a clear # message instead of silently routing through OpenRouter. _explicit = (self.provider or "").strip().lower() - if _explicit and _explicit not in ("auto", "openrouter", "custom"): + if _explicit and _explicit not in ("auto", "openrouter", "custom", "vertex"): # Look up the actual env var name from the provider # config — some providers use non-standard names # (e.g. alibaba → DASHSCOPE_API_KEY, not ALIBABA_API_KEY). diff --git a/uv.lock b/uv.lock index dfb2f786b07a..581ce8cb93b3 100644 --- a/uv.lock +++ b/uv.lock @@ -9,7 +9,7 @@ resolution-markers = [ ] [options] -exclude-newer = "2026-04-17T16:49:45.944715922Z" +exclude-newer = "2026-04-19T09:42:25.003522Z" exclude-newer-span = "P7D" [[package]] @@ -1759,6 +1759,77 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, ] +[[package]] +name = "google-api-core" +version = "2.30.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/502a57fb0ec752026d24df1280b162294b22a0afb98a326084f9a979138b/google_api_core-2.30.3.tar.gz", hash = "sha256:e601a37f148585319b26db36e219df68c5d07b6382cff2d580e83404e44d641b", size = 177001, upload-time = "2026-04-10T00:41:28.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/15/e56f351cf6ef1cfea58e6ac226a7318ed1deb2218c4b3cc9bd9e4b786c5a/google_api_core-2.30.3-py3-none-any.whl", hash = "sha256:a85761ba72c444dad5d611c2220633480b2b6be2521eca69cca2dbb3ffd6bfe8", size = 173274, upload-time = "2026-04-09T22:57:16.198Z" }, +] + +[[package]] +name = "google-api-python-client" +version = "2.194.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-httplib2" }, + { name = "httplib2" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/ab/e83af0eb043e4ccc49571ca7a6a49984e9d00f4e9e6e6f1238d60bc84dce/google_api_python_client-2.194.0.tar.gz", hash = "sha256:db92647bd1a90f40b79c9618461553c2b20b6a43ce7395fa6de07132dc14f023", size = 14443469, upload-time = "2026-04-08T23:07:35.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/34/5a624e49f179aa5b0cb87b2ce8093960299030ff40423bfbde09360eb908/google_api_python_client-2.194.0-py3-none-any.whl", hash = "sha256:61eaaac3b8fc8fdf11c08af87abc3d1342d1b37319cc1b57405f86ef7697e717", size = 15016514, upload-time = "2026-04-08T23:07:33.093Z" }, +] + +[[package]] +name = "google-auth" +version = "2.49.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/fc/e925290a1ad95c975c459e2df070fac2b90954e13a0370ac505dff78cb99/google_auth-2.49.2.tar.gz", hash = "sha256:c1ae38500e73065dcae57355adb6278cf8b5c8e391994ae9cbadbcb9631ab409", size = 333958, upload-time = "2026-04-10T00:41:21.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" }, +] + +[[package]] +name = "google-auth-httplib2" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "httplib2" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/99/107612bef8d24b298bb5a7c8466f908ecda791d43f9466f5c3978f5b24c1/google_auth_httplib2-0.3.1.tar.gz", hash = "sha256:0af542e815784cb64159b4469aa5d71dd41069ba93effa006e1916b1dcd88e55", size = 11152, upload-time = "2026-03-30T22:50:26.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/e9/93afb14d23a949acaa3f4e7cc51a0024671174e116e35f42850764b99634/google_auth_httplib2-0.3.1-py3-none-any.whl", hash = "sha256:682356a90ef4ba3d06548c37e9112eea6fc00395a11b0303a644c1a86abc275c", size = 9534, upload-time = "2026-03-30T22:49:03.384Z" }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/82/62482931dcbe5266a2680d0da17096f2aab983ecb320277d9556700ce00e/google_auth_oauthlib-1.3.1.tar.gz", hash = "sha256:14c22c7b3dd3d06dbe44264144409039465effdd1eef94f7ce3710e486cc4bfa", size = 21663, upload-time = "2026-03-30T22:49:56.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/e0/cb454a95f460903e39f101e950038ec24a072ca69d0a294a6df625cc1627/google_auth_oauthlib-1.3.1-py3-none-any.whl", hash = "sha256:1a139ef23f1318756805b0e95f655c238bffd29655329a2978218248da4ee7f8", size = 19247, upload-time = "2026-03-30T20:02:23.894Z" }, +] + [[package]] name = "googleapis-common-protos" version = "1.73.0" @@ -1912,6 +1983,9 @@ all = [ { name = "elevenlabs" }, { name = "fastapi" }, { name = "faster-whisper" }, + { name = "google-api-python-client" }, + { name = "google-auth-httplib2" }, + { name = "google-auth-oauthlib" }, { name = "honcho-ai" }, { name = "lark-oapi" }, { name = "markdown", marker = "sys_platform == 'linux'" }, @@ -1965,6 +2039,11 @@ feishu = [ { name = "lark-oapi" }, { name = "qrcode" }, ] +google = [ + { name = "google-api-python-client" }, + { name = "google-auth-httplib2" }, + { name = "google-auth-oauthlib" }, +] homeassistant = [ { name = "aiohttp" }, ] @@ -2025,6 +2104,10 @@ termux = [ tts-premium = [ { name = "elevenlabs" }, ] +vertex = [ + { name = "google-auth" }, + { name = "requests" }, +] voice = [ { name = "faster-whisper" }, { name = "numpy" }, @@ -2064,6 +2147,10 @@ requires-dist = [ { name = "faster-whisper", marker = "extra == 'voice'", specifier = ">=1.0.0,<2" }, { name = "fire", specifier = ">=0.7.1,<1" }, { name = "firecrawl-py", specifier = ">=4.16.0,<5" }, + { name = "google-api-python-client", marker = "extra == 'google'", specifier = ">=2.100,<3" }, + { name = "google-auth", marker = "extra == 'vertex'", specifier = ">=2.0.0,<3" }, + { name = "google-auth-httplib2", marker = "extra == 'google'", specifier = ">=0.2,<1" }, + { name = "google-auth-oauthlib", marker = "extra == 'google'", specifier = ">=1.0,<2" }, { name = "hermes-agent", extras = ["acp"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["acp"], marker = "extra == 'termux'" }, { name = "hermes-agent", extras = ["bedrock"], marker = "extra == 'all'" }, @@ -2075,6 +2162,7 @@ requires-dist = [ { name = "hermes-agent", extras = ["dev"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["dingtalk"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["feishu"], marker = "extra == 'all'" }, + { name = "hermes-agent", extras = ["google"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["homeassistant"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["honcho"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["honcho"], marker = "extra == 'termux'" }, @@ -2120,6 +2208,7 @@ requires-dist = [ { name = "qrcode", marker = "extra == 'feishu'", specifier = ">=7.0,<8" }, { name = "qrcode", marker = "extra == 'messaging'", specifier = ">=7.0,<8" }, { name = "requests", specifier = ">=2.33.0,<3" }, + { name = "requests", marker = "extra == 'vertex'", specifier = ">=2.0.0,<3" }, { name = "rich", specifier = ">=14.3.3,<15" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "simple-term-menu", marker = "extra == 'cli'", specifier = ">=1.0,<2" }, @@ -2136,7 +2225,7 @@ requires-dist = [ { name = "wandb", marker = "extra == 'rl'", specifier = ">=0.15.0,<1" }, { name = "yc-bench", marker = "python_full_version >= '3.12' and extra == 'yc-bench'", git = "https://github.com/collinear-ai/yc-bench.git?rev=bfb0c88062450f46341bd9a5298903fc2e952a5c" }, ] -provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "bedrock", "termux", "dingtalk", "feishu", "web", "rl", "yc-bench", "all"] +provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "bedrock", "termux", "dingtalk", "feishu", "google", "vertex", "web", "rl", "yc-bench", "all"] [[package]] name = "hf-transfer" @@ -2238,6 +2327,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httplib2" +version = "0.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, +] + [[package]] name = "httptools" version = "0.7.1" @@ -3277,6 +3378,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" }, ] +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + [[package]] name = "obstore" version = "0.8.2" @@ -3855,6 +3965,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] +[[package]] +name = "proto-plus" +version = "1.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/0d/94dfe80193e79d55258345901acd2917523d56e8381bc4dee7fd38e3868a/proto_plus-1.27.2.tar.gz", hash = "sha256:b2adde53adadf75737c44d3dcb0104fde65250dfc83ad59168b4aa3e574b6a24", size = 57204, upload-time = "2026-03-26T22:18:57.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/f3/1fba73eeffafc998a25d59703b63f8be4fe8a5cb12eaff7386a0ba0f7125/proto_plus-1.27.2-py3-none-any.whl", hash = "sha256:6432f75893d3b9e70b9c412f1d2f03f65b11fb164b793d14ae2ca01821d22718", size = 50450, upload-time = "2026-03-26T22:13:42.927Z" }, +] + [[package]] name = "protobuf" version = "6.33.5" @@ -3929,6 +4051,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" }, ] +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -4529,6 +4672,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + [[package]] name = "requests-toolbelt" version = "1.0.0" @@ -5268,6 +5424,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/a7/563b2d8fb7edc07320bf69ac6a7eedcd7a1a9d663a6bb90a4d9bd2eda5f7/unpaddedbase64-2.1.0-py3-none-any.whl", hash = "sha256:485eff129c30175d2cd6f0cd8d2310dff51e666f7f36175f738d75dfdbd0b1c6", size = 6083, upload-time = "2021-03-09T11:35:46.7Z" }, ] +[[package]] +name = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + [[package]] name = "urllib3" version = "2.6.3"