mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-17 18:25:42 +02:00
[eric] conventions: one-line-comment + no-gratuitous-blank rules in CLAUDE.md; apply to configure_provider_env (271->223)
This commit is contained in:
@@ -42,6 +42,18 @@ Personal / per-machine notes go in `CLAUDE.local.md` (gitignored), **not** here.
|
||||
are dynamic-key maps (a registry keyed by a runtime id) and external protocol shapes (the Claude
|
||||
Agent SDK hook returns, `model_dump` output).
|
||||
|
||||
## Comments
|
||||
- **Every comment is ONE physical line. No exceptions.** Never wrap a comment across multiple `#`
|
||||
lines — collapse it into a single line (long is fine; multi-line is not).
|
||||
- **Delete comments that aren't pulling weight.** Keep only the WHY (a non-obvious reason, gotcha, or
|
||||
ambiguity); delete anything that restates the code, and delete dead/commented-out code outright.
|
||||
- A module/function docstring is exempt (it's a docstring, not a `#` comment).
|
||||
|
||||
## Whitespace
|
||||
- **No gratuitous blank lines.** One blank line separates logical units; never stack 2+ blank lines.
|
||||
- **Tight imports** — no blank lines inside an import block beyond the single separator between
|
||||
stdlib / third-party / local groups. Delete any blank line that isn't doing real readability work.
|
||||
|
||||
When you add or change code, the files you touch must be clean under these rules. Pre-existing debt
|
||||
in files you are not otherwise editing is grandfathered via the linter's exception lists — do not
|
||||
mass-migrate untouched files.
|
||||
|
||||
@@ -13,6 +13,10 @@ All conventions in the root `CLAUDE.md` apply here. Python-specific emphasis:
|
||||
structured data — model it. Legitimate dicts: dynamic-key registries and external protocol shapes
|
||||
(SDK hook returns, `model_dump` output).
|
||||
- **Single-purpose file naming** — a one-export file is named after its export.
|
||||
- **Comments are ONE line each, no exceptions** — never wrap across multiple `#` lines; keep only
|
||||
WHY/gotcha comments, delete restating or dead-code comments. Docstrings are exempt.
|
||||
- **No gratuitous blank lines** — never stack 2+ blanks; keep imports tight (only the single
|
||||
stdlib/third-party/local group separators).
|
||||
|
||||
Touch a file → it must be clean under these rules. Pre-existing debt is grandfathered in
|
||||
`linter/config/config.json`; don't mass-migrate untouched files.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""Configure the SDK environment for the run's provider route: set ANTHROPIC/OPENAI/GOOGLE
|
||||
auth env vars (direct key, OpenSwarm Pro proxy, OpenRouter, or 9Router) and pin subagent models,
|
||||
ensuring 9Router is up where the route needs it. Lifted out of the agent loop; mutates
|
||||
options_kwargs[\"env\"] in place exactly as inline. sub_conns is the active-connection list used
|
||||
for subagent-model fallback (empty today)."""
|
||||
ensuring 9Router is up where the route needs it. sub_conns is the active-connection list for
|
||||
subagent-model fallback (empty today)."""
|
||||
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
@@ -41,25 +40,14 @@ async def configure_provider_env(
|
||||
options_kwargs["env"] = {
|
||||
"ANTHROPIC_API_KEY": global_settings.anthropic_api_key,
|
||||
"ANTHROPIC_BASE_URL": "https://api.anthropic.com",
|
||||
# Pin subagent envs so they don't drift back to the proxy.
|
||||
# Pin subagents so they don't drift back to the proxy.
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6",
|
||||
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5",
|
||||
}
|
||||
logger.info(f"[MCP-DEBUG] Using direct Anthropic API key (route=api) for {session.model}")
|
||||
elif is_pinned_api_route and api_route_provider == "openai" and getattr(global_settings, "openai_api_key", None):
|
||||
# Goes through 9Router's Anthropic→OpenAI translator like
|
||||
# other own-key routes, but we point OPENAI_BASE_URL at a
|
||||
# tiny local pass-through (/api/openai-passthrough/v1) that
|
||||
# renames max_tokens → max_completion_tokens before relaying
|
||||
# to api.openai.com. OpenAI's GPT-5 family rejects max_tokens
|
||||
# with HTTP 400, and 9Router 0.3.60 doesn't know about
|
||||
# max_completion_tokens yet (its CLI<->OpenAI translator
|
||||
# emits the legacy field). The pin on 0.3.60 is intentional
|
||||
# (newer 9Router versions regress WebSearch, see
|
||||
# nine_router.py comment) so we patch the boundary instead
|
||||
# of bumping. Pre-fix: every gpt-5.* / gpt-5.* own-key
|
||||
# session 400'd silently.
|
||||
# openai-passthrough renames max_tokens->max_completion_tokens before relaying (GPT-5 400s on max_tokens; 9Router 0.3.60, pinned for WebSearch, emits the legacy field).
|
||||
passthrough_url = f"http://127.0.0.1:{os.environ.get('OPENSWARM_PORT', '8324')}/api/openai-passthrough/v1"
|
||||
options_kwargs["env"] = {
|
||||
"OPENAI_API_KEY": global_settings.openai_api_key,
|
||||
@@ -69,9 +57,7 @@ async def configure_provider_env(
|
||||
}
|
||||
logger.info(f"[MCP-DEBUG] Using direct OpenAI API key (route=api) for {session.model} via openai-passthrough")
|
||||
elif is_pinned_api_route and api_route_provider == "custom":
|
||||
# User-configured OpenAI-compatible endpoint (Ollama Cloud,
|
||||
# Together, local Ollama, etc.). Routes through 9Router's
|
||||
# openai-compatible provider node we synced from settings.
|
||||
# User OpenAI-compatible endpoint (Ollama/Together/LM Studio) via 9Router's synced provider node.
|
||||
from backend.apps.nine_router import ensure_running as p_9r_ensure_c
|
||||
if not nine_router_running():
|
||||
logger.info(f"[MCP-DEBUG] custom provider selected but 9Router not running; waiting for startup")
|
||||
@@ -90,34 +76,24 @@ async def configure_provider_env(
|
||||
"ENABLE_TOOL_SEARCH": "auto",
|
||||
}
|
||||
if cp:
|
||||
# Local OpenAI-compatible servers (LM Studio, Ollama, ...)
|
||||
# often run with auth disabled, the user leaves api_key
|
||||
# blank in Settings. The OpenAI-style SDK insists on a
|
||||
# non-empty key; substitute a harmless placeholder so the
|
||||
# CLI can issue requests. Servers that DO check auth always
|
||||
# have a real key configured.
|
||||
# Local servers often run auth-disabled; placeholder key since the OpenAI SDK requires non-empty.
|
||||
env["OPENAI_API_KEY"] = (cp.api_key or "").strip() or "no-auth-required"
|
||||
from backend.apps.nine_router import normalize_openai_compat_base_url as norm_cp_url
|
||||
env["OPENAI_BASE_URL"] = norm_cp_url(cp.base_url or "")
|
||||
# Pin subagent ids, without these, CLI's default Haiku 4.5
|
||||
# gets sent to the custom provider and 404s.
|
||||
# Pin subagents or CLI's default Haiku 4.5 404s on the custom provider.
|
||||
if global_settings.anthropic_api_key:
|
||||
env["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-sonnet-4-6"
|
||||
env["ANTHROPIC_SMALL_FAST_MODEL"] = "claude-haiku-4-5-20251001"
|
||||
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "claude-haiku-4-5-20251001"
|
||||
else:
|
||||
# Pin to the same custom-provider model so subagents stay
|
||||
# within the user's configured endpoint instead of hitting
|
||||
# an unconfigured Anthropic lane.
|
||||
# No Anthropic key: pin subagents to the custom model so they stay on the user's endpoint.
|
||||
env["CLAUDE_CODE_SUBAGENT_MODEL"] = resolved_model
|
||||
env["ANTHROPIC_SMALL_FAST_MODEL"] = resolved_model
|
||||
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = resolved_model
|
||||
options_kwargs["env"] = env
|
||||
logger.info(f"[MCP-DEBUG] Using custom provider for {session.model} → {resolved_model}")
|
||||
elif is_pinned_api_route and api_route_provider == "gemini" and getattr(global_settings, "google_api_key", None):
|
||||
# Routed through the local anthropic-proxy so it can scrub the
|
||||
# JSON-Schema fields Gemini's API rejects ($schema, additionalProperties,
|
||||
# propertyNames, exclusiveMinimum, nested const) that 9Router 0.3.60 misses.
|
||||
# Local anthropic-proxy scrubs JSON-Schema fields Gemini rejects ($schema, additionalProperties, propertyNames, exclusiveMinimum, nested const) that 9Router 0.3.60 misses.
|
||||
proxy_url = f"http://127.0.0.1:{os.environ.get('OPENSWARM_PORT', '8324')}/api/anthropic-proxy"
|
||||
options_kwargs["env"] = {
|
||||
"GEMINI_API_KEY": global_settings.google_api_key,
|
||||
@@ -127,12 +103,7 @@ async def configure_provider_env(
|
||||
}
|
||||
logger.info(f"[MCP-DEBUG] Using direct Google API key (route=api) for {session.model} via local proxy")
|
||||
elif api_type == "openrouter" and getattr(global_settings, "openrouter_api_key", None):
|
||||
# OpenRouter primary. The route="openrouter" entry's
|
||||
# router_model_id is `openrouter/<vendor>/<model>` so
|
||||
# 9Router routes via the apikey connection synced from
|
||||
# CLI's WebSearch delegation needs an Anthropic-shaped lane;
|
||||
# if the user has no Anthropic key/sub/Pro, fall back to OR's
|
||||
# resold Claude so subagents stay on the same OR billing.
|
||||
# OpenRouter via 9Router; with no Anthropic key/sub, fall back to OR's resold Claude for subagents (incl. WebSearch delegation) so they stay on the same OR billing.
|
||||
if not nine_router_running():
|
||||
from backend.apps.nine_router import ensure_running as nine_router_ensure
|
||||
logger.info(f"[MCP-DEBUG] OpenRouter selected but 9Router not running; waiting for startup")
|
||||
@@ -166,33 +137,24 @@ async def configure_provider_env(
|
||||
options_kwargs["env"] = {
|
||||
"ANTHROPIC_AUTH_TOKEN": bearer,
|
||||
"ANTHROPIC_BASE_URL": proxy_url,
|
||||
# Pin subagent ids; CLI default 'claude-haiku-4-5-20251001'
|
||||
# gets rejected by Pro's surface as "No credentials for provider: anthropic".
|
||||
# (Free-trial clamps to its allowed Claude set + weights credits server-side.)
|
||||
# Pin subagents; Pro rejects CLI's default haiku as "No credentials for provider: anthropic".
|
||||
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-6",
|
||||
"ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5-20251001",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001",
|
||||
# auto, never the bare default: tengu_defer_all_bn4 marks every tool
|
||||
# defer_loading=true, which collides with our cache_control and 400s the
|
||||
# first tool-laden request (the other anthropic branches all set this).
|
||||
# auto: tengu_defer_all_bn4 defers every tool, which collides with cache_control and 400s the first tool-laden request.
|
||||
"ENABLE_TOOL_SEARCH": "auto",
|
||||
}
|
||||
# Free lane meters one run per agent task: tag every call of this task (and its
|
||||
# subagents, which inherit the env) AND its aux calls (title-gen, see generate_title)
|
||||
# with the session id, so a query plus its title generation is ONE run, not two.
|
||||
# The base goes straight to the cloud (no 9Router), so the header rides through.
|
||||
# Tag every call (subagents inherit env) + aux calls with the task id so a query plus its title-gen meter as ONE free run; base goes straight to cloud (no 9Router) so the header rides.
|
||||
if getattr(global_settings, "connection_mode", "own_key") == "free-trial":
|
||||
options_kwargs["env"]["ANTHROPIC_CUSTOM_HEADERS"] = f"X-Openswarm-Task-Id: {session.id}"
|
||||
# The cloud serves every free run as Haiku, so keep the subagent on Haiku too:
|
||||
# a sonnet subagent makes the CLI attach `effort`, which Haiku 400s on.
|
||||
# Cloud serves every free run as Haiku; keep the subagent on Haiku too (sonnet adds `effort`, Haiku 400s).
|
||||
options_kwargs["env"]["CLAUDE_CODE_SUBAGENT_MODEL"] = "claude-haiku-4-5-20251001"
|
||||
logger.info(f"[MCP-DEBUG] Using OpenSwarm cloud proxy at {proxy_url}")
|
||||
elif api_type == "anthropic" and not resolved_is_9router and global_settings.anthropic_api_key:
|
||||
options_kwargs["env"] = {"ANTHROPIC_API_KEY": global_settings.anthropic_api_key}
|
||||
logger.info("[MCP-DEBUG] Using direct Anthropic API key")
|
||||
elif nine_router_running():
|
||||
# Gemini-bound ids go through the local proxy for schema scrubbing;
|
||||
# everything else hits 9Router directly.
|
||||
# Gemini-bound ids go through the local proxy for schema scrubbing; everything else hits 9Router directly.
|
||||
is_gemini_bound = (
|
||||
isinstance(resolved_model, str)
|
||||
and resolved_model.startswith(("gemini/", "gc/", "ag/"))
|
||||
@@ -208,12 +170,7 @@ async def configure_provider_env(
|
||||
"ANTHROPIC_API_KEY": "9router",
|
||||
"ANTHROPIC_BASE_URL": "http://localhost:20128",
|
||||
}
|
||||
# Pin subagent ids to whichever lane the user has, else CLI's
|
||||
# default Haiku 4.5 hits 9Router with no Claude route and 401s.
|
||||
# NOTE: sub_conns is the connection list passed in. Callers currently pass []
|
||||
# (see RunOptions), so `active` is empty and this pinning is inert until the
|
||||
# real connection list is wired through — a latent regression from the run/ split,
|
||||
# surfaced by pyright (the old inline `_conns` reference was left dangling here).
|
||||
# Pin subagents to whichever lane the user has, else CLI's default Haiku 4.5 hits 9Router with no Claude route and 401s. NOTE: callers pass sub_conns=[] today so this is inert (latent regression from the run/ split; pyright caught the dangling _conns ref).
|
||||
active = {c.get("provider") for c in sub_conns
|
||||
if isinstance(c, dict) and c.get("isActive")}
|
||||
sub_model = None
|
||||
@@ -241,11 +198,7 @@ async def configure_provider_env(
|
||||
logger.info(
|
||||
f"[MCP-DEBUG] 9Router direct, subagent_model={sub_model}, small_fast={small_model}"
|
||||
)
|
||||
# ENABLE_TOOL_SEARCH=auto: without it, CLI's tengu_defer_all_bn4
|
||||
# Statsig flag defers 16 tools with no way to load them on non-
|
||||
# Anthropic networks. "auto" eagerly loads tools when schema
|
||||
# budget fits in ~10% of context. Don't pass --bare, sets
|
||||
# CLAUDE_CODE_SIMPLE=1 which strips the system prompt scaffolding.
|
||||
# auto eagerly loads tools when the schema budget fits; without it tengu_defer_all_bn4 defers 16 tools unloadable off Anthropic networks. Don't use --bare (strips system prompt).
|
||||
env["ENABLE_TOOL_SEARCH"] = "auto"
|
||||
options_kwargs["env"] = env
|
||||
logger.info(f"[MCP-DEBUG] Using 9Router (api_type={api_type})")
|
||||
|
||||
@@ -7,6 +7,9 @@ Python-only rules do not. Specifically:
|
||||
- **Single-purpose file naming** — a one-export file is named after its export.
|
||||
- **Type everything** under strict `tsconfig`; model data with typed interfaces, not untyped
|
||||
objects (the TS equivalent of "no bare dicts").
|
||||
- **Comments are ONE line each, no exceptions** (`//` — never stack into a multi-line block); keep
|
||||
only WHY/gotcha, delete restating or dead-code comments. JSDoc blocks are exempt.
|
||||
- **No gratuitous blank lines** — never stack 2+ blanks; keep imports tight.
|
||||
- **Does not apply (Python-only):** `@typechecked`, pydantic `BaseModel`, the no-relative-imports
|
||||
rule (the frontend uses the `@/` path alias and local relative imports), and `p-private`.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user