hyperagents 1.2.3rc5.post3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agents/__init__.py +31 -0
- agents/background_monitor.py +453 -0
- agents/learning.py +460 -0
- agents/planner.py +421 -0
- agents/transcriber.py +341 -0
- civilization/__init__.py +51 -0
- civilization/api/__init__.py +7 -0
- civilization/api/civilization_api.py +361 -0
- civilization/graph/__init__.py +19 -0
- civilization/graph/knowledge_linker.py +348 -0
- civilization/graph/org_graph.py +348 -0
- civilization/graph/process_graph.py +386 -0
- civilization/ingestion/__init__.py +20 -0
- civilization/ingestion/chunker.py +273 -0
- civilization/ingestion/document_ingestor.py +358 -0
- civilization/ingestion/embedder.py +262 -0
- civilization/ingestion/extractor.py +224 -0
- civilization/interview/__init__.py +19 -0
- civilization/interview/gap_detector.py +380 -0
- civilization/interview/interviewer.py +323 -0
- civilization/interview/questioner.py +363 -0
- civilization/retrieval/__init__.py +17 -0
- civilization/retrieval/civilization_rag.py +316 -0
- civilization/retrieval/context_injector.py +342 -0
- civilization/retrieval/relevance_ranker.py +287 -0
- civilization/schema.py +299 -0
- civilization/store.py +177 -0
- civilization/sync/__init__.py +16 -0
- civilization/sync/confluence_sync.py +317 -0
- civilization/sync/gdrive_sync.py +262 -0
- civilization/sync/notion_sync.py +237 -0
- civilization/versioning/__init__.py +19 -0
- civilization/versioning/diff_engine.py +351 -0
- civilization/versioning/staleness_detector.py +303 -0
- civilization/versioning/version_manager.py +293 -0
- cli/__init__.py +1 -0
- cli/doctor.py +334 -0
- cli/hyperclaw.py +1357 -0
- cli/onboarding.py +469 -0
- core/__init__.py +1 -0
- core/connectors/__init__.py +119 -0
- core/connectors/infrastructure.py +772 -0
- core/connectors/messaging.py +586 -0
- core/connectors/productivity.py +764 -0
- core/connectors/smarthome.py +528 -0
- core/connectors/social.py +730 -0
- core/hyperrouter/__init__.py +23 -0
- core/hyperrouter/bandit.py +218 -0
- core/hyperrouter/fast_loop.py +68 -0
- core/hyperrouter/slow_loop.py +143 -0
- core/hyperstate/__init__.py +24 -0
- core/hyperstate/certifier.py +154 -0
- core/hyperstate/schema.py +160 -0
- core/hyperstate/state_manager.py +103 -0
- core/hyperstate/store.py +163 -0
- core/providers/__init__.py +121 -0
- core/providers/embeddings.py +389 -0
- core/providers/image.py +534 -0
- core/providers/llm.py +1935 -0
- core/providers/search.py +608 -0
- core/providers/voice.py +474 -0
- hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
- hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
- hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
- hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
- hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
- hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
- hyperclaw/__init__.py +67 -0
- hyperclaw/__main__.py +220 -0
- hyperclaw/agent.py +775 -0
- hyperclaw/agent_coordinator.py +577 -0
- hyperclaw/anthropic_params.py +225 -0
- hyperclaw/api_utils.py +85 -0
- hyperclaw/approval_telegram.py +209 -0
- hyperclaw/approval_terminal.py +80 -0
- hyperclaw/attachments.py +177 -0
- hyperclaw/branding.py +55 -0
- hyperclaw/brief/__init__.py +11 -0
- hyperclaw/brief/cli.py +204 -0
- hyperclaw/brief/collect.py +146 -0
- hyperclaw/brief/compose.py +333 -0
- hyperclaw/brief/diff.py +110 -0
- hyperclaw/brief/errors.py +99 -0
- hyperclaw/brief/google.py +269 -0
- hyperclaw/brief/google_auth.py +294 -0
- hyperclaw/brief/links.py +124 -0
- hyperclaw/brief/service.py +424 -0
- hyperclaw/brief/store.py +205 -0
- hyperclaw/brief/tasks.py +200 -0
- hyperclaw/chat_scope.py +115 -0
- hyperclaw/cli_tui.py +497 -0
- hyperclaw/client_guard.py +717 -0
- hyperclaw/connection_check.py +113 -0
- hyperclaw/cost_guard.py +1728 -0
- hyperclaw/default_config/agents.yaml +534 -0
- hyperclaw/default_config/hyperclaw.yaml +27 -0
- hyperclaw/default_config/models.yaml +161 -0
- hyperclaw/deployment.py +39 -0
- hyperclaw/digest/__init__.py +9 -0
- hyperclaw/digest/cli.py +117 -0
- hyperclaw/digest/errors.py +100 -0
- hyperclaw/digest/gitlab.py +187 -0
- hyperclaw/digest/http.py +113 -0
- hyperclaw/digest/redact.py +32 -0
- hyperclaw/digest/service.py +271 -0
- hyperclaw/digest/slack.py +223 -0
- hyperclaw/digest/state.py +124 -0
- hyperclaw/digest/summarize.py +198 -0
- hyperclaw/digest/tokens.py +143 -0
- hyperclaw/distribution.py +46 -0
- hyperclaw/documents.py +351 -0
- hyperclaw/feeds.py +339 -0
- hyperclaw/google_workspace/__init__.py +5 -0
- hyperclaw/google_workspace/api.py +151 -0
- hyperclaw/google_workspace/auth.py +397 -0
- hyperclaw/google_workspace/calendar.py +157 -0
- hyperclaw/google_workspace/cli.py +72 -0
- hyperclaw/google_workspace/drive.py +236 -0
- hyperclaw/google_workspace/errors.py +58 -0
- hyperclaw/google_workspace/gmail.py +295 -0
- hyperclaw/google_workspace/meet.py +122 -0
- hyperclaw/google_workspace/tools.py +319 -0
- hyperclaw/hyperspeed.py +57 -0
- hyperclaw/integrations_layer.py +1427 -0
- hyperclaw/live_state.py +188 -0
- hyperclaw/managed_config.py +245 -0
- hyperclaw/media_hub.py +179 -0
- hyperclaw/memory_manager.py +584 -0
- hyperclaw/model_catalog.py +551 -0
- hyperclaw/model_policy.py +211 -0
- hyperclaw/model_router.py +595 -0
- hyperclaw/model_selector.py +72 -0
- hyperclaw/naming.py +284 -0
- hyperclaw/notices.py +65 -0
- hyperclaw/oai_stream.py +106 -0
- hyperclaw/orchestrator.py +591 -0
- hyperclaw/outbox.py +82 -0
- hyperclaw/persona.py +367 -0
- hyperclaw/prometheus.py +219 -0
- hyperclaw/prompt_assembly.py +157 -0
- hyperclaw/provider_setup.py +146 -0
- hyperclaw/providers.py +340 -0
- hyperclaw/qualification.py +341 -0
- hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
- hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
- hyperclaw/reasoning_display.py +35 -0
- hyperclaw/scheduler.py +454 -0
- hyperclaw/server.py +847 -0
- hyperclaw/setup.py +627 -0
- hyperclaw/skills.py +717 -0
- hyperclaw/solomon.py +442 -0
- hyperclaw/swarm.py +533 -0
- hyperclaw/taint.py +832 -0
- hyperclaw/team/__init__.py +28 -0
- hyperclaw/team/dispatcher.py +431 -0
- hyperclaw/team/genesis.py +106 -0
- hyperclaw/team/lead.py +110 -0
- hyperclaw/team/model.py +135 -0
- hyperclaw/team/morning.py +238 -0
- hyperclaw/team/repos.py +66 -0
- hyperclaw/team/roster.py +264 -0
- hyperclaw/team/specialist.py +235 -0
- hyperclaw/team/status.py +163 -0
- hyperclaw/team/watchers.py +112 -0
- hyperclaw/teamsetup/__init__.py +17 -0
- hyperclaw/teamsetup/checks.py +208 -0
- hyperclaw/teamsetup/item.py +391 -0
- hyperclaw/teamsetup/oauth_loopback.py +84 -0
- hyperclaw/teamsetup/onboard.py +550 -0
- hyperclaw/teamsetup/profile.py +82 -0
- hyperclaw/teamsetup/secrets.py +313 -0
- hyperclaw/teamsetup/selfcheck.py +295 -0
- hyperclaw/teamsetup/service.py +495 -0
- hyperclaw/teamsetup/slack_oauth.py +442 -0
- hyperclaw/teamsetup/tty.py +62 -0
- hyperclaw/telegram_bot.py +1106 -0
- hyperclaw/telegram_intake.py +126 -0
- hyperclaw/telegram_pairing.py +360 -0
- hyperclaw/terminal_provider.py +323 -0
- hyperclaw/tools/__init__.py +1 -0
- hyperclaw/tools/approval.py +949 -0
- hyperclaw/tools/args.py +212 -0
- hyperclaw/tools/catalog.py +244 -0
- hyperclaw/tools/classify.py +266 -0
- hyperclaw/tools/effects.py +724 -0
- hyperclaw/tools/egress.py +210 -0
- hyperclaw/tools/envelope.py +188 -0
- hyperclaw/tools/guard.py +367 -0
- hyperclaw/tools/ledger.py +339 -0
- hyperclaw/tools/receipts.py +70 -0
- hyperclaw/tools/registry.py +318 -0
- hyperclaw/tools/service_guard.py +151 -0
- hyperclaw/tools/surfaces.py +54 -0
- hyperclaw/tools/workspace.py +87 -0
- hyperclaw/trading/__init__.py +77 -0
- hyperclaw/truth_guard.py +791 -0
- hyperclaw/tts.py +124 -0
- hyperclaw/tui.py +4255 -0
- hyperclaw/tui_bridge.py +938 -0
- hyperclaw/unattended.py +60 -0
- hyperclaw/updates/__init__.py +54 -0
- hyperclaw/updates/check.py +131 -0
- hyperclaw/updates/index.py +178 -0
- hyperclaw/updates/install.py +110 -0
- hyperclaw/updates/managed.py +161 -0
- hyperclaw/updates/manifest.py +138 -0
- hyperclaw/updates/notices.py +86 -0
- hyperclaw/updates/paths.py +53 -0
- hyperclaw/updates/release.py +146 -0
- hyperclaw/updates/upgrade.py +253 -0
- hyperclaw/updates/versioning.py +104 -0
- hyperclaw/userconfig.py +119 -0
- hyperclaw/xai.py +83 -0
- integrations/__init__.py +31 -0
- integrations/automation/__init__.py +1 -0
- integrations/automation/make.py +87 -0
- integrations/automation/zapier.py +87 -0
- integrations/base.py +476 -0
- integrations/communication/__init__.py +1 -0
- integrations/communication/twilio.py +279 -0
- integrations/communication/zoom.py +290 -0
- integrations/data/__init__.py +1 -0
- integrations/data/google_analytics.py +135 -0
- integrations/data/postgres.py +182 -0
- integrations/data/supabase_connector.py +202 -0
- integrations/data/weather_feeds.py +450 -0
- integrations/developer/__init__.py +9 -0
- integrations/developer/github.py +406 -0
- integrations/developer/gitlab.py +298 -0
- integrations/enterprise/__init__.py +13 -0
- integrations/enterprise/confluence.py +266 -0
- integrations/enterprise/hubspot.py +293 -0
- integrations/enterprise/jira.py +314 -0
- integrations/enterprise/salesforce.py +286 -0
- integrations/finance/__init__.py +1 -0
- integrations/finance/polymarket_client.py +247 -0
- integrations/finance/quickbooks.py +281 -0
- integrations/finance/stripe.py +261 -0
- integrations/gateway.py +238 -0
- integrations/messaging/__init__.py +32 -0
- integrations/messaging/discord.py +159 -0
- integrations/messaging/email.py +401 -0
- integrations/messaging/imessage.py +443 -0
- integrations/messaging/signal.py +175 -0
- integrations/messaging/slack.py +217 -0
- integrations/messaging/sms.py +175 -0
- integrations/messaging/teams.py +208 -0
- integrations/messaging/telegram.py +260 -0
- integrations/messaging/whatsapp.py +203 -0
- integrations/productivity/__init__.py +17 -0
- integrations/productivity/airtable.py +217 -0
- integrations/productivity/asana.py +248 -0
- integrations/productivity/google/__init__.py +19 -0
- integrations/productivity/google/base.py +89 -0
- integrations/productivity/google/calendar.py +272 -0
- integrations/productivity/google/docs.py +202 -0
- integrations/productivity/google/drive.py +268 -0
- integrations/productivity/google/gmail.py +279 -0
- integrations/productivity/google/meet.py +218 -0
- integrations/productivity/google/sheets.py +222 -0
- integrations/productivity/google/tasks.py +199 -0
- integrations/productivity/linear.py +279 -0
- integrations/productivity/microsoft/__init__.py +13 -0
- integrations/productivity/microsoft/base.py +93 -0
- integrations/productivity/microsoft/calendar.py +207 -0
- integrations/productivity/microsoft/onedrive.py +222 -0
- integrations/productivity/microsoft/outlook.py +241 -0
- integrations/productivity/microsoft/sharepoint.py +211 -0
- integrations/productivity/notion.py +243 -0
- integrations/productivity/todoist.py +250 -0
- integrations/productivity/trello.py +244 -0
- integrations/storage/__init__.py +1 -0
- integrations/storage/box.py +279 -0
- integrations/storage/dropbox.py +215 -0
- integrations/telegram_inbound.py +179 -0
- integrations/telegram_poller.py +151 -0
- memory/__init__.py +1 -0
- memory/causal_graph.py +125 -0
- memory/impact_tracker.py +88 -0
- memory/vector_memory.py +436 -0
- models/__init__.py +12 -0
- models/chatjimmy_client.py +210 -0
- models/claude_client.py +235 -0
- models/claude_code_subagent.py +192 -0
- models/openai_client.py +120 -0
- models/router.py +162 -0
- recursive/discovery_loop.py +118 -0
- recursive/optimization_loop.py +135 -0
- recursive/validation_loop.py +38 -0
- security/__init__.py +1 -0
- security/audit_logger.py +106 -0
- security/filesystem_guard.py +100 -0
- security/hypershield.py +99 -0
- security/network_guard.py +97 -0
- security/policy_engine.py +198 -0
- swarm/__init__.py +1 -0
- swarm/agents/__init__.py +0 -0
- swarm/agents/base.py +230 -0
- swarm/agents/business/__init__.py +0 -0
- swarm/agents/business/counsel.py +31 -0
- swarm/agents/business/herald.py +32 -0
- swarm/agents/business/ledger.py +26 -0
- swarm/agents/business/nexus.py +30 -0
- swarm/agents/business/ops.py +30 -0
- swarm/agents/business/pipeline.py +28 -0
- swarm/agents/business/revenue.py +30 -0
- swarm/agents/business/sovereign.py +31 -0
- swarm/agents/business/strategos.py +27 -0
- swarm/agents/business/talent.py +27 -0
- swarm/agents/business/venture.py +32 -0
- swarm/agents/comms/__init__.py +1 -0
- swarm/agents/comms/cipher.py +32 -0
- swarm/agents/comms/echo_comms.py +31 -0
- swarm/agents/comms/envoy.py +31 -0
- swarm/agents/comms/herald_scheduler.py +33 -0
- swarm/agents/comms/pulse.py +29 -0
- swarm/agents/creative/__init__.py +0 -0
- swarm/agents/creative/author.py +27 -0
- swarm/agents/creative/lens.py +25 -0
- swarm/agents/executive/__init__.py +1 -0
- swarm/agents/intelligence/__init__.py +1 -0
- swarm/agents/intelligence/arbiter.py +30 -0
- swarm/agents/intelligence/sentinel.py +30 -0
- swarm/agents/media/__init__.py +0 -0
- swarm/agents/personal/__init__.py +0 -0
- swarm/agents/personal/atlas.py +27 -0
- swarm/agents/personal/hearth.py +26 -0
- swarm/agents/personal/midas.py +27 -0
- swarm/agents/personal/navigator.py +27 -0
- swarm/agents/personal/nourish.py +31 -0
- swarm/agents/personal/vitals.py +33 -0
- swarm/agents/recursive/__init__.py +0 -0
- swarm/agents/recursive/alchemist.py +73 -0
- swarm/agents/recursive/calibrator.py +45 -0
- swarm/agents/recursive/scout.py +97 -0
- swarm/agents/scientific/__init__.py +0 -0
- swarm/agents/scientific/cosmos.py +36 -0
- swarm/agents/scientific/gaia.py +26 -0
- swarm/agents/scientific/medicus.py +33 -0
- swarm/agents/scientific/oracle_agent.py +39 -0
- swarm/agents/scientific/scribe.py +39 -0
- swarm/agents/talent/__init__.py +0 -0
- swarm/agents/talent/deal.py +28 -0
- swarm/agents/talent/roster.py +29 -0
- swarm/agents/talent/scout.py +29 -0
- swarm/agents/talent/stage.py +27 -0
- swarm/agents/tech/__init__.py +0 -0
- swarm/agents/tech/aegis.py +28 -0
- swarm/agents/tech/bridge.py +28 -0
- swarm/agents/tech/forge.py +28 -0
- swarm/agents/trading/__init__.py +1 -0
- swarm/agents/trading/global_prediction_engine.py +611 -0
- swarm/agents/trading/polymarket_trader.py +165 -0
- swarm/agents/trading/prediction_strategist.py +218 -0
- swarm/autogen_bridge.py +68 -0
- swarm/bid_protocol.py +196 -0
- swarm/dispatcher.py +527 -0
- swarm/nexus.py +181 -0
- swarm/registry.py +112 -0
- telegram_listener.py +403 -0
- ui/tui/app.py +214 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Resolve terminal credentials without changing the environment or saved settings."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
from typing import Mapping
|
|
6
|
+
from urllib.parse import urlsplit
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TerminalConfigurationError(ValueError):
|
|
12
|
+
"""An actionable configuration problem that must be caught before an API call."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TerminalProvider(BaseModel):
|
|
16
|
+
model_config = ConfigDict(frozen=True)
|
|
17
|
+
|
|
18
|
+
name: str
|
|
19
|
+
kind: str = "openai_compat" # "anthropic" (Messages API) or "openai_compat"
|
|
20
|
+
api_key: str = Field(repr=False)
|
|
21
|
+
model: str
|
|
22
|
+
base_url: str | None = None
|
|
23
|
+
label: str = "" # banner label of the matching registry provider
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def display_label(self) -> str:
|
|
27
|
+
if self.label:
|
|
28
|
+
return self.label
|
|
29
|
+
# Customer providers only: Hyper models always carry their own label.
|
|
30
|
+
try:
|
|
31
|
+
from hyperclaw.provider_setup import OWN_KINDS
|
|
32
|
+
name = OWN_KINDS.get(self.name, self.name)
|
|
33
|
+
except Exception: # noqa: BLE001 -- a label must never break startup
|
|
34
|
+
name = self.name
|
|
35
|
+
return f"your own provider ({name})"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_hyper_endpoint(prefix: str, base_url: str, env: Mapping[str, str]) -> bool:
|
|
39
|
+
"""True when the resolved OpenAI-compatible group is Hyper's own model:
|
|
40
|
+
the HYPERSPEED_* group, or any group pointing at the HyperSpeed endpoint."""
|
|
41
|
+
from hyperclaw import hyperspeed
|
|
42
|
+
if prefix == "HYPERSPEED":
|
|
43
|
+
return True
|
|
44
|
+
hyper_urls = {hyperspeed.DEFAULT_BASE_URL.rstrip("/"),
|
|
45
|
+
(env.get(hyperspeed.BASE_URL_ENV, "") or "").strip().rstrip("/")} - {""}
|
|
46
|
+
return base_url.strip().rstrip("/") in hyper_urls
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _hyper_label(base_url: str) -> str:
|
|
50
|
+
from hyperclaw import hyperspeed
|
|
51
|
+
if base_url.strip().rstrip("/") != hyperspeed.DEFAULT_BASE_URL.rstrip("/"):
|
|
52
|
+
return f"{hyperspeed.LABEL} (custom endpoint)"
|
|
53
|
+
return hyperspeed.LABEL
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _validated_url(base_url: str) -> str:
|
|
57
|
+
try:
|
|
58
|
+
parsed = urlsplit(base_url)
|
|
59
|
+
valid = (parsed.scheme in ("http", "https") and parsed.hostname
|
|
60
|
+
and not parsed.username and not parsed.password)
|
|
61
|
+
parsed.port # Validate malformed ports before constructing an SDK client.
|
|
62
|
+
except ValueError:
|
|
63
|
+
valid = False
|
|
64
|
+
if not valid:
|
|
65
|
+
raise TerminalConfigurationError(
|
|
66
|
+
"The configured model URL must be an HTTP(S) URL without embedded credentials."
|
|
67
|
+
)
|
|
68
|
+
return base_url.rstrip("/")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def resolve_terminal_provider(env: Mapping[str, str] | None = None) -> TerminalProvider:
|
|
72
|
+
env = os.environ if env is None else env
|
|
73
|
+
|
|
74
|
+
def value(name: str) -> str:
|
|
75
|
+
return env.get(name, "").strip()
|
|
76
|
+
|
|
77
|
+
# Keep each endpoint paired with its own credentials. An OpenAI key must
|
|
78
|
+
# never be combined with a different provider's URL as a field fallback.
|
|
79
|
+
openai_url = value("OPENAI_BASE_URL") or value("OPENAI_API_BASE")
|
|
80
|
+
hyperspeed_url = value("HYPERSPEED_BASE_URL")
|
|
81
|
+
name = value("LLM_PROVIDER")
|
|
82
|
+
if not name:
|
|
83
|
+
prefix = "OPENAI" if openai_url or not hyperspeed_url else "HYPERSPEED"
|
|
84
|
+
compatible_ready = (
|
|
85
|
+
(openai_url or hyperspeed_url) and value(f"{prefix}_API_KEY")
|
|
86
|
+
and (value(f"{prefix}_MODEL") or value("HYPERCLAW_MODEL"))
|
|
87
|
+
)
|
|
88
|
+
name = "openai_compat" if compatible_ready else "anthropic"
|
|
89
|
+
|
|
90
|
+
if name == "bedrock":
|
|
91
|
+
# Bedrock (a customer's own AWS provider, chat-only until qualified)
|
|
92
|
+
# is started by `hyperclaw start` itself, never through this resolver.
|
|
93
|
+
raise TerminalConfigurationError(
|
|
94
|
+
"Bedrock is not an endpoint this resolver can select. If an earlier "
|
|
95
|
+
"Hyperspeed outage selected Bedrock, restart with "
|
|
96
|
+
"LLM_PROVIDER=openai_compat hyperclaw start using your existing endpoint "
|
|
97
|
+
"and key. Saved settings have not been changed."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if name == "openai_compat":
|
|
101
|
+
prefix = "OPENAI" if openai_url or not hyperspeed_url else "HYPERSPEED"
|
|
102
|
+
base_url = openai_url if prefix == "OPENAI" else hyperspeed_url
|
|
103
|
+
api_key = value(f"{prefix}_API_KEY")
|
|
104
|
+
model = value(f"{prefix}_MODEL") or value("HYPERCLAW_MODEL")
|
|
105
|
+
if not base_url or not api_key or not model:
|
|
106
|
+
raise TerminalConfigurationError(
|
|
107
|
+
f"Configure {prefix}_BASE_URL, {prefix}_API_KEY and {prefix}_MODEL "
|
|
108
|
+
"for the same endpoint (OPENAI_API_BASE is also accepted). "
|
|
109
|
+
"Existing credentials have not been changed."
|
|
110
|
+
)
|
|
111
|
+
checked_url = _validated_url(base_url)
|
|
112
|
+
label = _hyper_label(checked_url) if _is_hyper_endpoint(prefix, checked_url, env) else ""
|
|
113
|
+
return TerminalProvider(name=name, kind="openai_compat", api_key=api_key, model=model,
|
|
114
|
+
base_url=checked_url, label=label)
|
|
115
|
+
|
|
116
|
+
if name not in ("anthropic", "anthropic_compat"):
|
|
117
|
+
raise TerminalConfigurationError(
|
|
118
|
+
"Unsupported LLM_PROVIDER. Select openai_compat, anthropic or anthropic_compat."
|
|
119
|
+
)
|
|
120
|
+
api_key = value("ANTHROPIC_API_KEY")
|
|
121
|
+
if not api_key:
|
|
122
|
+
raise TerminalConfigurationError("ANTHROPIC_API_KEY is not set for the selected provider.")
|
|
123
|
+
base_url = value("ANTHROPIC_BASE_URL")
|
|
124
|
+
if name == "anthropic_compat" and not base_url:
|
|
125
|
+
raise TerminalConfigurationError("ANTHROPIC_BASE_URL is required for anthropic_compat.")
|
|
126
|
+
return TerminalProvider(
|
|
127
|
+
name=name, kind="anthropic", api_key=api_key, model=value("HYPERCLAW_MODEL") or "claude-sonnet-4-6",
|
|
128
|
+
base_url=_validated_url(base_url) if base_url else None,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ───────────────────── terminal route (provider + tool gating) ─────────────────────
|
|
133
|
+
#
|
|
134
|
+
# Two contracts meet here:
|
|
135
|
+
# * terminal recovery (MR !1): an explicit LLM_PROVIDER is always honored,
|
|
136
|
+
# credentials are never mixed across endpoint groups, endpoints are
|
|
137
|
+
# validated before a client exists, Bedrock is refused with recovery text,
|
|
138
|
+
# and nothing rewrites saved settings.
|
|
139
|
+
# * truth guard (release 1.2.1/1.2.2/1.2.3): the terminal gets tools ONLY
|
|
140
|
+
# from a provider that declares ``tool_use`` in models.yaml AND holds a
|
|
141
|
+
# Hyper-signed qualification record; anything else runs chat-only, and
|
|
142
|
+
# nothing configured means refuse to run (fail closed). A Hyper install
|
|
143
|
+
# uses Hyper's providers only.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _same_url(a: str, b: str) -> bool:
|
|
147
|
+
return (a or "").strip().rstrip("/") == (b or "").strip().rstrip("/")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _declared_mode(registry, config: TerminalProvider) -> str:
|
|
151
|
+
"""Tool gating for an explicitly selected provider: find the live
|
|
152
|
+
models.yaml provider that uses these exact credentials/endpoint and use
|
|
153
|
+
its declared capabilities. No match fails closed to chat-only."""
|
|
154
|
+
for prov in getattr(registry, "providers", {}).values():
|
|
155
|
+
if not prov.live or not prov.api_key or prov.api_key != config.api_key:
|
|
156
|
+
continue
|
|
157
|
+
if config.kind == "anthropic":
|
|
158
|
+
if prov.kind != "anthropic":
|
|
159
|
+
continue
|
|
160
|
+
else:
|
|
161
|
+
if prov.kind not in ("openai_compat", "openai"):
|
|
162
|
+
continue
|
|
163
|
+
if prov.base_url:
|
|
164
|
+
if not _same_url(prov.base_url, config.base_url or ""):
|
|
165
|
+
continue
|
|
166
|
+
elif not (prov.kind == "openai"
|
|
167
|
+
and urlsplit(config.base_url or "").hostname == "api.openai.com"):
|
|
168
|
+
continue
|
|
169
|
+
# Declared AND qualified (spec §11): tool_use is earned by a
|
|
170
|
+
# Hyper-signed qualification record, never by declaration alone.
|
|
171
|
+
caps = prov.effective_capabilities(config.model)
|
|
172
|
+
if "tool_use" in caps:
|
|
173
|
+
return "tools"
|
|
174
|
+
if "chat" in caps:
|
|
175
|
+
return "chat_only"
|
|
176
|
+
return "chat_only"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _registry_config(prov, model: str, env: Mapping[str, str]) -> TerminalProvider:
|
|
180
|
+
"""TerminalProvider for a registry provider + model."""
|
|
181
|
+
kind = "anthropic" if prov.kind == "anthropic" else "openai_compat"
|
|
182
|
+
base_url = prov.base_url or None
|
|
183
|
+
if kind == "anthropic" and env.get("ANTHROPIC_BASE_URL", "").strip():
|
|
184
|
+
base_url = env.get("ANTHROPIC_BASE_URL", "").strip()
|
|
185
|
+
try:
|
|
186
|
+
from hyperclaw.model_policy import model_label
|
|
187
|
+
label = model_label(prov, model)
|
|
188
|
+
except Exception: # noqa: BLE001 -- a label must never break routing
|
|
189
|
+
label = getattr(prov, "display_label", "") or ""
|
|
190
|
+
return TerminalProvider(
|
|
191
|
+
name=prov.name, kind=kind, api_key=prov.api_key, model=model,
|
|
192
|
+
base_url=_validated_url(base_url) if base_url else None,
|
|
193
|
+
label=label,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
REGISTRY_ONLY = ("xai", "openai")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _named_registry_route(registry, env: Mapping[str, str], hyper: bool):
|
|
201
|
+
"""LLM_PROVIDER=xai / openai: those providers have no raw-env group in
|
|
202
|
+
resolve_terminal_provider, so they come from the registry (key from
|
|
203
|
+
.env, tools only once qualified). None when LLM_PROVIDER names another."""
|
|
204
|
+
name = env.get("LLM_PROVIDER", "").strip().lower()
|
|
205
|
+
if hyper or name not in REGISTRY_ONLY:
|
|
206
|
+
return None
|
|
207
|
+
prov = getattr(registry, "providers", {}).get(name)
|
|
208
|
+
if prov is None or not prov.live:
|
|
209
|
+
key = getattr(prov, "api_key_env", "") or f"{name.upper()}_API_KEY"
|
|
210
|
+
raise TerminalConfigurationError(
|
|
211
|
+
f"LLM_PROVIDER={name} is selected but {key} is not set. "
|
|
212
|
+
"Existing settings have not been changed.")
|
|
213
|
+
model = prov.model_for()
|
|
214
|
+
caps = prov.effective_capabilities(model)
|
|
215
|
+
return ("tools" if "tool_use" in caps else "chat_only"), \
|
|
216
|
+
_registry_config(prov, model, env), model
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def select_terminal_route(registry, env: Mapping[str, str] | None = None) -> tuple:
|
|
220
|
+
"""Return ``(mode, TerminalProvider, model)`` for the terminal.
|
|
221
|
+
|
|
222
|
+
mode is "tools" or "chat_only"; ``(None, None, None)`` means nothing usable
|
|
223
|
+
is configured (caller must refuse to run). Raises
|
|
224
|
+
TerminalConfigurationError for an actionable configuration problem.
|
|
225
|
+
|
|
226
|
+
* A model chosen with /model this session (hyperclaw.model_catalog)
|
|
227
|
+
wins; its mode comes from its own declared + qualified capabilities.
|
|
228
|
+
* Hyper install (hyperclaw.deployment): registry only, LLM_PROVIDER and
|
|
229
|
+
raw env groups ignored; nothing live means (None, None, None).
|
|
230
|
+
* LLM_PROVIDER set: honored via resolve_terminal_provider(); tools only
|
|
231
|
+
if the matching models.yaml provider declares tool_use AND it is
|
|
232
|
+
qualified (Provider.effective_capabilities).
|
|
233
|
+
* LLM_PROVIDER=hyperspeed (written by onboarding): the registry's
|
|
234
|
+
HyperSpeed V2 provider; no Hyper access key raises a plain
|
|
235
|
+
missing-key error (never a switch to another provider).
|
|
236
|
+
* LLM_PROVIDER unset: truth_guard.route_chat() over the registry (tool-
|
|
237
|
+
capable provider first). If the registry finds nothing, a complete
|
|
238
|
+
OPENAI_*/HYPERSPEED_* group (incl. the legacy OPENAI_API_BASE alias) is
|
|
239
|
+
still accepted, chat-only unless declared otherwise.
|
|
240
|
+
"""
|
|
241
|
+
from hyperclaw.deployment import is_hyper
|
|
242
|
+
from hyperclaw.truth_guard import route_chat
|
|
243
|
+
|
|
244
|
+
env = os.environ if env is None else env
|
|
245
|
+
hyper = is_hyper()
|
|
246
|
+
# /model (docs/MODEL-SWITCHING.md): a model chosen in this session wins.
|
|
247
|
+
# Tools only if that exact provider+model is declared AND qualified.
|
|
248
|
+
from hyperclaw.model_catalog import TERMINAL, selected_route
|
|
249
|
+
chosen = selected_route(registry, TERMINAL)
|
|
250
|
+
if chosen is not None:
|
|
251
|
+
prov, model = chosen
|
|
252
|
+
caps = prov.effective_capabilities(model)
|
|
253
|
+
mode = "tools" if "tool_use" in caps else "chat_only"
|
|
254
|
+
return mode, _registry_config(prov, model, env), model
|
|
255
|
+
# LLM_PROVIDER naming a registry-only provider (xai, openai) on a
|
|
256
|
+
# standard install: that provider, from the registry.
|
|
257
|
+
named = _named_registry_route(registry, env, hyper)
|
|
258
|
+
if named is not None:
|
|
259
|
+
return named
|
|
260
|
+
# A Hyper-deployed install runs on Hyper's providers only (spec §11):
|
|
261
|
+
# LLM_PROVIDER and raw OPENAI_*/ANTHROPIC_* keys are ignored there.
|
|
262
|
+
if hyper or not env.get("LLM_PROVIDER", "").strip():
|
|
263
|
+
mode, prov, model = route_chat(registry)
|
|
264
|
+
if mode is not None:
|
|
265
|
+
return mode, _registry_config(prov, model, env), model
|
|
266
|
+
if hyper:
|
|
267
|
+
return (None, None, None)
|
|
268
|
+
try:
|
|
269
|
+
config = resolve_terminal_provider(env)
|
|
270
|
+
except TerminalConfigurationError:
|
|
271
|
+
return (None, None, None)
|
|
272
|
+
return _declared_mode(registry, config), config, config.model
|
|
273
|
+
|
|
274
|
+
if env.get("LLM_PROVIDER", "").strip().lower() == HYPERSPEED:
|
|
275
|
+
return _hyperspeed_route(registry)
|
|
276
|
+
|
|
277
|
+
config = resolve_terminal_provider(env)
|
|
278
|
+
return _declared_mode(registry, config), config, config.model
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
HYPERSPEED = "hyperspeed"
|
|
282
|
+
HYPER_KEY_COMMAND = "hyperclaw provider hyper-key"
|
|
283
|
+
SWITCH_TO_OWN_COMMAND = "hyperclaw provider remove hyperspeed"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def hyper_key_missing_message(registry) -> str:
|
|
287
|
+
"""What the terminal says when HyperSpeed V2 is selected
|
|
288
|
+
(LLM_PROVIDER=hyperspeed) but no Hyper access key is configured. Names
|
|
289
|
+
the fix, and an own provider that is already configured, without ever
|
|
290
|
+
switching to it."""
|
|
291
|
+
lines = [
|
|
292
|
+
"HyperSpeed V2 is selected (LLM_PROVIDER=hyperspeed in your .env), but your "
|
|
293
|
+
"Hyper access key is missing.",
|
|
294
|
+
f"Add it with: {HYPER_KEY_COMMAND}",
|
|
295
|
+
]
|
|
296
|
+
own = [p for p in getattr(registry, "providers", {}).values()
|
|
297
|
+
if getattr(p, "live", False) and getattr(p, "origin", "") != "hyper"]
|
|
298
|
+
if own:
|
|
299
|
+
labels = ", ".join(sorted({getattr(p, "display_label", "") or p.name for p in own}))
|
|
300
|
+
lines.append(
|
|
301
|
+
f"You also have {labels} configured. To use it instead of HyperSpeed V2, run: "
|
|
302
|
+
f"{SWITCH_TO_OWN_COMMAND} (this clears HyperSpeed V2 as your default; "
|
|
303
|
+
f"{HYPER_KEY_COMMAND} sets it back).")
|
|
304
|
+
lines.append("Nothing has been changed.")
|
|
305
|
+
return "\n".join(lines)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _hyperspeed_route(registry) -> tuple:
|
|
309
|
+
"""LLM_PROVIDER=hyperspeed (what onboarding writes for HyperSpeed V2):
|
|
310
|
+
the registry's HyperSpeed V2 provider, tools only once qualified. No key
|
|
311
|
+
is an actionable error, never a silent switch to another provider."""
|
|
312
|
+
prov = getattr(registry, "providers", {}).get(HYPERSPEED)
|
|
313
|
+
if prov is None or not prov.live:
|
|
314
|
+
raise TerminalConfigurationError(hyper_key_missing_message(registry))
|
|
315
|
+
model = prov.model_for()
|
|
316
|
+
caps = prov.effective_capabilities(model)
|
|
317
|
+
mode = "tools" if "tool_use" in caps else "chat_only"
|
|
318
|
+
config = TerminalProvider(
|
|
319
|
+
name=prov.name, kind="openai_compat", api_key=prov.api_key, model=model,
|
|
320
|
+
base_url=_validated_url(prov.base_url) if prov.base_url else None,
|
|
321
|
+
label=getattr(prov, "display_label", "") or "",
|
|
322
|
+
)
|
|
323
|
+
return mode, config, model
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""HyperClaw tool standard (TOOL-INVENTORY-2026-10-02 §3; docs/TOOL-STANDARD.md)."""
|