Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
195 commits
Select commit Hold shift + click to select a range
0738b80
fix(tui): rebuild when ink bundle is missing
helix4u Apr 24, 2026
6d69945
feat(tools): read vision max_tokens from auxiliary.vision config
phonezawphyo Apr 24, 2026
05d8f11
fix(/model): show provider-enforced context length, not raw models.de…
teknium1 Apr 25, 2026
3e61703
fix(nix): use --rebuild in fix-lockfiles to bypass cached FOD store p…
alt-glitch Apr 25, 2026
3cf1374
fix(matrix): bind PgCryptoStore device_id so fresh E2EE installs work
cgarwood82 Apr 24, 2026
2182de5
fix(matrix): drop needless DeviceID import + mock put_device_id in tests
cgarwood82 Apr 24, 2026
1c8ce33
fix(tui): proactive mouse disable on ConPTY + /mouse toggle command
Apr 25, 2026
0ed37c0
docs(delegate): document max_concurrent_children and max_spawn_depth …
MorAlekss Apr 24, 2026
0a59994
fix(cli-config): keep delegation overrides commented in example
teknium1 Apr 25, 2026
6407b3d
Merge pull request #15488 from kevin-ho/fix/tui-mouse-toggle
OutThisLife Apr 25, 2026
023b1bf
fix(delegate): resolve subagent approval prompts without deadlocking …
teknium1 Apr 25, 2026
e5647d7
docs: consolidate dashboard themes and plugins into Extending the Das…
teknium1 Apr 25, 2026
5401a00
fix: recalculate token budgets on model switch in ContextCompressor
vominh1919 Apr 25, 2026
9d7b64b
fix(tools): normalize numeric entries and clear stale no_mcp in _save…
alt-glitch Apr 24, 2026
9fde22d
fix the reset of model change by /model.
Readon Apr 23, 2026
df48562
chore(release): map Readon's git email to GitHub login
teknium1 Apr 25, 2026
f433197
feat(installer): FHS layout for root installs on Linux (#15608)
teknium1 Apr 25, 2026
5ac5365
feat(cron): add context_from field for cron job output chaining
MorAlekss Apr 23, 2026
e4a91cc
test(cron): add PermissionError coverage for context_from
MorAlekss Apr 23, 2026
eb92222
fix(cron): silent skip when context_from job has no output yet
MorAlekss Apr 25, 2026
0d548d1
fix(cron): wire context_from through the update action
teknium1 Apr 25, 2026
9830905
fix(tools): recover non-configurable toolsets from composite resolution
alt-glitch Apr 24, 2026
81987f0
feat(discord): split discord_server into discord + discord_admin tools
alt-glitch Apr 24, 2026
db09477
feat(feishu): wire feishu doc/drive tools into hermes-feishu composite
alt-glitch Apr 24, 2026
0702231
feat(session): add guild_id/parent_chat_id/message_id to SessionSource
alt-glitch Apr 24, 2026
47b02e9
feat(discord): populate guild_id, parent_chat_id, message_id on Sessi…
alt-glitch Apr 24, 2026
5ae07e7
fix(session): gate stale "no Discord APIs" note on DISCORD_BOT_TOKEN
alt-glitch Apr 24, 2026
591deeb
feat(session): inject Discord IDs block when discord tool is loaded
alt-glitch Apr 24, 2026
6ed37e0
feat(tools): make discord/discord_admin opt-in, Discord-only
teknium1 Apr 25, 2026
f67a61d
fix(flush_memories): strip temperature from codex_responses fallback …
teknium1 Apr 25, 2026
facea84
fix(auxiliary): retry without temperature when any provider rejects it
ash Apr 25, 2026
b35d692
chore(release): map ash@users.noreply.github.com to ash
teknium1 Apr 25, 2026
f92006c
fix(compression): reserve system+tools headroom when aux binds thresh…
teknium1 Apr 25, 2026
3c1c65e
fix(auxiliary): generalize unsupported-parameter detector and harden …
teknium1 Apr 25, 2026
ac05daa
fix(tools): dedupe bundled plugin toolsets with built-in entries (#15…
teknium1 Apr 25, 2026
6e561ff
fix(update): poll is-active instead of one-shot sleep(3) after gatewa…
teknium1 Apr 25, 2026
97d54f0
fix(terminal): three-layer defense against watch_patterns notificatio…
teknium1 Apr 25, 2026
af22421
feat(dashboard): page-scoped plugin slots for built-in pages (#15658)
teknium1 Apr 25, 2026
cf2fabc
docs(dashboard): document page-scoped plugin slots (#15662)
teknium1 Apr 25, 2026
d635e2d
fix(compression): pass provider to context length resolver in feasibi…
kshitijk4poor Apr 25, 2026
ea01bdc
refactor(memory): remove flush_memories entirely (#15696)
teknium1 Apr 25, 2026
7c8c031
feat: add `hermes -z <prompt>` one-shot mode (#15702)
teknium1 Apr 25, 2026
a9fa73a
feat(oneshot): add --model / --provider / HERMES_INFERENCE_MODEL (#15…
teknium1 Apr 25, 2026
5006b22
fix(update): honor RestartSec when polling for gateway respawn (#15707)
teknium1 Apr 25, 2026
7c17acc
fix: /stop now immediately aborts streaming retry loop
kshitijk4poor Apr 25, 2026
648b899
fix: use output_text for assistant message content in Codex Responses…
kshitijk4poor Apr 25, 2026
9daa062
fix(agent): ordering fix in _copy_reasoning_content_for_api — cross-p…
Zjianru Apr 25, 2026
ee0728c
Merge pull request #15351 from helix4u/fix/tui-rebuild-missing-ink-bu…
OutThisLife Apr 25, 2026
e9c47c7
fix(tui): honor launch model overrides
OutThisLife Apr 25, 2026
57b43fd
fix(tui): preserve provider precedence on startup
OutThisLife Apr 25, 2026
4db58d4
fix(tui): address startup provider review
OutThisLife Apr 25, 2026
2dfcc80
fix(tui): avoid network lookup during startup
OutThisLife Apr 25, 2026
e48a497
fix(tui): share static model detection
OutThisLife Apr 25, 2026
5e52011
fix(tui): bind provider as model alias
OutThisLife Apr 25, 2026
48bdd24
fix(tui): apply ui-tui fix pass and restore type-check
OutThisLife Apr 25, 2026
fdcbd22
fix(tui): resolve startup model aliases statically
OutThisLife Apr 25, 2026
a046483
fix(tui): share overlay close controls
OutThisLife Apr 25, 2026
c6fdf48
fix(tui): sync inference model after switches
OutThisLife Apr 25, 2026
6e83d90
refactor(tui): tighten overlay helpers
OutThisLife Apr 25, 2026
919274b
fix(tui): align overlay q shortcut casing
OutThisLife Apr 25, 2026
283c8fd
Merge pull request #15755 from NousResearch/bb/tui-model-flag
OutThisLife Apr 25, 2026
bcc5362
fix(tui): honor client copy shortcut over ssh
OutThisLife Apr 25, 2026
a68793b
refactor(tui): share remote shell detection
OutThisLife Apr 25, 2026
876bb60
fix(tui): trim whitespace-only selection chrome
OutThisLife Apr 25, 2026
132620b
refactor(tui): simplify remote copy hotkey hints
OutThisLife Apr 25, 2026
bba1694
fix(tui): preserve rendered indentation in selections
OutThisLife Apr 25, 2026
1735ced
fix(tui): preserve code block indentation in selection
OutThisLife Apr 25, 2026
bd66e55
fix(tui): track rendered spaces for selection copy
OutThisLife Apr 25, 2026
b1c18e5
refactor(tui): format screen imports
OutThisLife Apr 25, 2026
31d7f19
fix(tui): clamp copied selection bounds
OutThisLife Apr 25, 2026
edc78e2
Merge pull request #15766 from NousResearch/bb/tui-ssh-copy
OutThisLife Apr 25, 2026
88b65cc
Update run_agent.py
OutThisLife Apr 25, 2026
5ae6081
fix: remove has_reasoning guard — inject empty reasoning_content for …
Zjianru Apr 25, 2026
f93d462
Merge pull request #15749 from Zjianru/fix/copy-reasoning-content-ord…
OutThisLife Apr 25, 2026
47420a8
docs(obliteratus): link YouTube video guide in SKILL.md (#15808)
teknium1 Apr 25, 2026
dc4d92f
docs: embed tutorial videos on webhooks + auxiliary models pages (#15…
teknium1 Apr 25, 2026
ad0ac89
fix: DeepSeek/Kimi thinking mode requires reasoning_content on ALL as…
Apr 25, 2026
489bed6
Merge pull request #15478 from yes999zc/fix-deepseek-reasoning-all-as…
OutThisLife Apr 26, 2026
3944b22
fix(tui): suspend Ink properly when opening $EDITOR via Ctrl+G
OutThisLife Apr 26, 2026
c58956a
fix(tui): accept Alt+G as Ctrl+G fallback in VSCode/Cursor terminals
OutThisLife Apr 26, 2026
4c797bf
fix(cli): accept Alt+G as Ctrl+G fallback in VSCode/Cursor terminals
OutThisLife Apr 26, 2026
25ba6a4
fix(gateway): make reasoning session-scoped by default
irispillars Apr 25, 2026
b2d3308
fix(doctor): accept bare custom provider
helix4u Apr 25, 2026
01cf2c6
chore(release): map iris@growthpillars.co to irispillars (#15825)
teknium1 Apr 26, 2026
2c56dce
fix(model): preserve custom endpoint credentials and accept cloud mod…
kshitijk4poor Apr 25, 2026
5fac6c3
fix(cli): write editor draft to prompt.md so syntax highlighting works
OutThisLife Apr 26, 2026
1fdc31b
fix(config): preserve custom provider api key refs
helix4u Apr 26, 2026
8bbeaea
fix(config): broaden api-key ref lookup to templated base_url
teknium1 Apr 26, 2026
db7c573
fix: prefer vim over nano for $EDITOR fallback (CLI + TUI)
OutThisLife Apr 26, 2026
1b8ca92
fix(tui): save live transcript from slash command
helix4u Apr 25, 2026
2536a36
fix(tui): route /save through session.save JSON-RPC
teknium1 Apr 26, 2026
d056b61
fix: avoid prompt_toolkit complex tempfile bug and prefer nvim first
OutThisLife Apr 26, 2026
7fd8dc0
fix: preserve prompt_toolkit editor picker and mirror it in TUI
OutThisLife Apr 26, 2026
81e01f6
fix(agent): preserve Codex message items for replay
Nerijusas Apr 25, 2026
4d17013
chore(release): map nerijusn76@gmail.com to Nerijusas (#15833)
teknium1 Apr 26, 2026
83129e7
refactor(tui): tighten editor handoff helpers
OutThisLife Apr 26, 2026
45e1228
fix(cli): suppress OSError EIO on interrupt shutdown
voidborne-d Apr 21, 2026
edce752
chore(release): add AUTHOR_MAP entry for voidborne-d personal email
teknium1 Apr 26, 2026
1d80e92
test(discord): add guild to fake e2e messages
wysie Apr 25, 2026
14dd8e9
fix(tui): address Copilot review on editor handoff
OutThisLife Apr 26, 2026
ff851ba
Merge pull request #15821 from NousResearch/fix/tui-ctrl-g-editor
OutThisLife Apr 26, 2026
dc5e02e
feat(cli): implement hermes update --check flag (fixes #10318)
Feranmi10 Apr 25, 2026
ce0513d
chore(release): map Feranmi10 personal email
teknium1 Apr 26, 2026
0a15dbd
feat(api_server): add POST /v1/runs/{run_id}/stop endpoint
EKKOLearnAI Apr 25, 2026
01535a4
fix(api_server): cap stop-run wait at 5s so interrupt can't hang handler
teknium1 Apr 26, 2026
4c591c2
chore(release): map fqsy1416@gmail.com to EKKOLearnAI
teknium1 Apr 26, 2026
125de02
fix(context): honor custom_providers context_length on /model switch …
teknium1 Apr 26, 2026
3a7653d
feat: Add Azure Foundry provider with OpenAI/Anthropic API mode selec…
TechPrototyper Apr 13, 2026
6ef3a47
fix: use Azure API key directly for Azure endpoints, bypass OAuth tok…
HangGlidersRule Apr 1, 2026
d8e4c72
fix: Azure Anthropic short-circuit in resolve_runtime_provider — bypa…
HangGlidersRule Apr 2, 2026
7bfa944
fix: skip OAuth token refresh for Azure Anthropic endpoints — prevent…
HangGlidersRule Apr 2, 2026
c15064f
fix: pass api-version as default_query param, not in base_url — SDK w…
HangGlidersRule Apr 2, 2026
24b4b24
fix: preserve URL query params for Azure OpenAI and custom endpoints
pein892 Apr 13, 2026
ac57114
fix(agent): support Azure OpenAI gpt-5.x on chat/completions endpoint
ak771977 Apr 26, 2026
731e1ef
feat(azure-foundry): auto-detect transport, models, context length
teknium1 Apr 26, 2026
7c50ed7
docs(azure-foundry): add provider guide, env vars, release AUTHOR_MAP
teknium1 Apr 26, 2026
91a7a0a
fix(tui): restore skills search RPC
OutThisLife Apr 26, 2026
cec0af0
Merge pull request #15870 from NousResearch/bb/fix-skills-search
OutThisLife Apr 26, 2026
a55de5b
feat(setup): auto-reconfigure on existing installs (#15879)
teknium1 Apr 26, 2026
eb28145
feat(approval): hardline blocklist for unrecoverable commands (#15878)
teknium1 Apr 26, 2026
59b56d4
feat(hooks): add duration_ms to post_tool_call + transform_tool_resul…
teknium1 Apr 26, 2026
192e7eb
fix(nous): don't trip cross-session rate breaker on upstream-capacity…
teknium1 Apr 26, 2026
76042f5
feat(review): class-first skill review prompt (#16026)
teknium1 Apr 26, 2026
2ccdadc
fix(deepseek): bump V4 family context window to 1M tokens
zkl2333 Apr 24, 2026
438db0c
fix(cli): /model picker honors provider-specific context caps (#16030)
teknium1 Apr 26, 2026
d09ab8f
fix(mcp-oauth): preserve server_url path for protected-resource valid…
teknium1 Apr 26, 2026
8553669
feat(models): remote model catalog manifest for OpenRouter + Nous Por…
teknium1 Apr 26, 2026
a562420
fix(tui): robust clipboard handling with debug logging and headless d…
0xharryriddle Apr 26, 2026
0f3a6f0
fix(clipboard): dashboard Ctrl+C direct copy; TUI honest feedback; HE…
0xharryriddle Apr 26, 2026
2511207
chore: revert docs
0xharryriddle Apr 26, 2026
e8441c4
fix(clipboard): report native/tmux success, keep Ctrl+Shift+C on dash…
teknium1 Apr 26, 2026
35c57cc
fix(gateway): suppress tool-progress bubbles after interrupt (#16034)
teknium1 Apr 26, 2026
67dcace
docs(config): show options in comments for display settings (#16038)
teknium1 Apr 26, 2026
4bda9dc
fix(gateway): honor voice.auto_tts config in auto-TTS gate (#16007) (…
teknium1 Apr 26, 2026
83c1c20
feat(onboarding): contextual first-touch hints for /busy and /verbose…
teknium1 Apr 26, 2026
1e37ddc
feat(cli): add 'hermes fallback' command to manage fallback providers…
teknium1 Apr 26, 2026
ffd2621
feat(onboarding): port first-touch hints to the TUI (#16054)
teknium1 Apr 26, 2026
9a70260
Revert "feat(onboarding): port first-touch hints to the TUI (#16054)"…
teknium1 Apr 26, 2026
7fa70b6
refactor: /btw is now an alias for /background (#16053)
teknium1 Apr 26, 2026
70f56e7
fix(gateway): let /btw dispatch mid-turn instead of being rejected
teknium1 Apr 26, 2026
454d883
refactor: drop persist_session plumbing + fix broken btw mid-turn byp…
teknium1 Apr 26, 2026
15937a6
feat(kanban): durable multi-profile collaboration board (#16081)
teknium1 Apr 26, 2026
63bf7a2
fix(run_agent): prevent reasoning_content regression in DeepSeek/Kimi…
Apr 26, 2026
c5196f1
chore(release): map focusflow.app.help@gmail.com to yes999zc
teknium1 Apr 26, 2026
9ef1ae1
fix(docker): don't chown config.yaml after gosu drop (#15865) (#16096)
teknium1 Apr 26, 2026
06f8175
Revert "feat(kanban): durable multi-profile collaboration board (#160…
teknium1 Apr 26, 2026
e3901d5
fix(run_agent): background review fork inherits parent's live runtime…
teknium1 Apr 26, 2026
8443998
fix(auth): resolve API keys from ~/.hermes/.env and credential_pool
zzy-git Apr 26, 2026
27f4dba
test: add unit tests for credential pool env fallback
zzy-git Apr 26, 2026
f2d6555
fix(auth): hoist get_env_value import + strengthen .env fallback tests
teknium1 Apr 26, 2026
d7a3468
fix(prompts): replace [SYSTEM: with [IMPORTANT: to avoid Azure conten…
ygd58 Apr 9, 2026
20cb706
chore: extend [SYSTEM:→[IMPORTANT: rename + AUTHOR_MAP
teknium1 Apr 26, 2026
de24315
fix(gateway): preserve inactivity clock on interrupt-recursive cached…
briandevans Apr 25, 2026
4e35609
fixup! fix(gateway): preserve inactivity clock on interrupt-recursive…
briandevans Apr 26, 2026
eaa7e2d
feat(cli,tui): surface /queue, /bg, /steer in agent-running placehold…
teknium1 Apr 26, 2026
0e2a53e
feat(skills): show enabled/disabled status in 'skills list' (#16129)
teknium1 Apr 26, 2026
42c076d
feat(browser): auto-spawn local Chromium for LAN/localhost URLs in cl…
teknium1 Apr 26, 2026
0824ba6
fix(/branch): redirect session_log_file and expose branch sessions in…
teknium1 Apr 26, 2026
9662e32
fix(tui): call maybe_auto_title for TUI sessions (#15949) (#16151)
teknium1 Apr 26, 2026
9397767
chore(skills): remove empty feeds category (#16153)
teknium1 Apr 26, 2026
9be8372
docs(docker-backend): clarify container is shared across sessions, no…
teknium1 Apr 26, 2026
087e74d
feat(slack): register every gateway command as a native slash (Discor…
teknium1 Apr 26, 2026
5b2c595
feat(terminal): collapse subagent task_ids to shared container (#16177)
teknium1 Apr 26, 2026
1dfcc2f
fix(gateway): /queue is now a true FIFO — each invocation gets its ow…
teknium1 Apr 26, 2026
d993a3f
fix(gateway): use /hermes sethome in onboarding hint on Slack
zhiyanliu Apr 23, 2026
c730f6c
test(gateway): cover Slack vs non-Slack home-channel onboarding hint
sgaofen Apr 26, 2026
ae7687c
chore(release): map zhiyanliu in AUTHOR_MAP
teknium1 Apr 26, 2026
b1be86e
fix(gateway): bridge slack.reply_in_thread config
briandevans Apr 15, 2026
4b5a88d
fix(slack): honor reply_in_thread=false for top-level channel messages
teknium1 Apr 26, 2026
897dc3a
fix(install+update): add /usr/local/bin PATH guard for RHEL root non-…
teknium1 Apr 26, 2026
aea4a90
feat(slack): add opt-in slack.strict_mention gate for channel threads
ching-kaching Apr 18, 2026
50dd67c
fix(slack): skip _mentioned_threads registration when strict_mention …
hhhonzik Apr 26, 2026
878c196
chore(release): map hhhonzik in AUTHOR_MAP
teknium1 Apr 26, 2026
4d119bb
test: blank platform-gating env vars in hermetic fixture
teknium1 Apr 26, 2026
541cd73
chore(models): drop deepseek from OpenRouter and Nous Portal curated …
teknium1 Apr 26, 2026
802c7ac
fix(Slack): resolve Slack channels by raw ID and enumerate joined cha…
hhuang91 Apr 26, 2026
75d3eaa
fix(slack): exclude U/W user IDs from explicit target regex
briandevans Apr 26, 2026
6a3102f
chore(release): map hhuang91 in AUTHOR_MAP
teknium1 Apr 26, 2026
10e3618
fix(cli): wire approvals in background tasks
helix4u Apr 26, 2026
c0d25df
fix(slack): preserve thread-parent context when cron/bot posted the p…
Satoshi-agi Apr 19, 2026
f414df3
fix(slack): include team_id in thread-context cache key
flobo3 Apr 19, 2026
f988513
fix(slack): download files in Slack Connect channels
kunlabs Apr 26, 2026
edadeaf
chore(release): map Satoshi-agi and kunlabs in AUTHOR_MAP
teknium1 Apr 26, 2026
2d86e97
fix(run_agent): shut down background review memory providers
mrhwick Apr 24, 2026
36e352a
preserve the original comment
mrhwick Apr 24, 2026
aa7b5ac
pass attribution check
mrhwick Apr 24, 2026
45bfcb9
test: update bare-agent helper for live-runtime attrs added by #16099
teknium1 Apr 26, 2026
778fd18
fix(slack): surface attachment access diagnostics
xinbenlv Apr 26, 2026
bf05b8f
fix(gateway): clean up cached agents on shutdown (#11205)
Tranquil-Flow Apr 20, 2026
18beb69
fix(memory): close embedded Hindsight async client cleanly
maxims-oss Apr 26, 2026
822b507
chore(release): map maxims-oss in AUTHOR_MAP
teknium1 Apr 26, 2026
4921b26
fix(cron): keep homeassistant toolset enabled when HASS_TOKEN is set …
teknium1 Apr 26, 2026
6087e04
fix(slack): extract rich_text quotes/lists and link unfurl previews
Wang-tianhao Apr 26, 2026
755a280
chore(release): map Wang-tianhao in AUTHOR_MAP
teknium1 Apr 26, 2026
ee10474
Merge branch 'feat/vision-max-tokens-config' into tmp
phonezawphyo Apr 26, 2026
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
17 changes: 13 additions & 4 deletions agent/anthropic_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,16 @@ def build_anthropic_client(api_key: str, base_url: str = None, timeout: float =
"timeout": Timeout(timeout=float(_read_timeout), connect=10.0),
}
if normalized_base_url:
kwargs["base_url"] = normalized_base_url
# Azure Anthropic endpoints require an ``api-version`` query parameter.
# Pass it via default_query so the SDK appends it to every request URL
# without corrupting the base_url (appending it directly produces
# malformed paths like /anthropic?api-version=.../v1/messages).
_is_azure_endpoint = "azure.com" in normalized_base_url.lower()
if _is_azure_endpoint and "api-version" not in normalized_base_url:
kwargs["base_url"] = normalized_base_url.rstrip("/")
kwargs["default_query"] = {"api-version": "2025-04-15"}
else:
kwargs["base_url"] = normalized_base_url
common_betas = _common_betas_for_base_url(normalized_base_url)

if _is_kimi_coding_endpoint(base_url):
Expand Down Expand Up @@ -1680,9 +1689,9 @@ def build_anthropic_kwargs(

# ── Strip sampling params on 4.7+ ─────────────────────────────────
# Opus 4.7 rejects any non-default temperature/top_p/top_k with a 400.
# Callers (auxiliary_client, flush_memories, etc.) may set these for
# older models; drop them here as a safety net so upstream 4.6 → 4.7
# migrations don't require coordinated edits everywhere.
# Callers (auxiliary_client, etc.) may set these for older models;
# drop them here as a safety net so upstream 4.6 → 4.7 migrations
# don't require coordinated edits everywhere.
if _forbids_sampling_params(model):
for _sampling_key in ("temperature", "top_p", "top_k"):
kwargs.pop(_sampling_key, None)
Expand Down
147 changes: 134 additions & 13 deletions agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from pathlib import Path # noqa: F401 — used by test mocks
from types import SimpleNamespace
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urlparse, parse_qs, urlunparse

from openai import OpenAI

Expand All @@ -52,6 +53,17 @@

logger = logging.getLogger(__name__)


def _extract_url_query_params(url: str):
"""Extract query params from URL, return (clean_url, default_query dict or None)."""
parsed = urlparse(url)
if parsed.query:
clean = urlunparse(parsed._replace(query=""))
params = {k: v[0] for k, v in parse_qs(parsed.query).items()}
return clean, params
return url, None


# Module-level flag: only warn once per process about stale OPENAI_BASE_URL.
_stale_base_url_warned = False

Expand Down Expand Up @@ -390,7 +402,7 @@ def create(self, **kwargs) -> Any:
# Note: the Codex endpoint (chatgpt.com/backend-api/codex) does NOT
# support max_output_tokens or temperature — omit to avoid 400 errors.

# Tools support for flush_memories and similar callers
# Tools support for auxiliary callers (e.g. skills_hub) that pass function schemas
tools = kwargs.get("tools")
if tools:
converted = []
Expand Down Expand Up @@ -1157,8 +1169,10 @@ def _try_custom_endpoint() -> Tuple[Optional[Any], Optional[str]]:
return None, None
model = _read_main_model() or "gpt-4o-mini"
logger.debug("Auxiliary client: custom endpoint (%s, api_mode=%s)", model, custom_mode or "chat_completions")
_clean_base, _dq = _extract_url_query_params(custom_base)
_extra = {"default_query": _dq} if _dq else {}
if custom_mode == "codex_responses":
real_client = OpenAI(api_key=custom_key, base_url=custom_base)
real_client = OpenAI(api_key=custom_key, base_url=_clean_base, **_extra)
return CodexAuxiliaryClient(real_client, model), model
if custom_mode == "anthropic_messages":
# Third-party Anthropic-compatible gateway (MiniMax, Zhipu GLM,
Expand All @@ -1172,12 +1186,12 @@ def _try_custom_endpoint() -> Tuple[Optional[Any], Optional[str]]:
"Custom endpoint declares api_mode=anthropic_messages but the "
"anthropic SDK is not installed — falling back to OpenAI-wire."
)
return OpenAI(api_key=custom_key, base_url=custom_base), model
return OpenAI(api_key=custom_key, base_url=_clean_base, **_extra), model
return (
AnthropicAuxiliaryClient(real_client, model, custom_key, custom_base, is_oauth=False),
model,
)
return OpenAI(api_key=custom_key, base_url=custom_base), model
return OpenAI(api_key=custom_key, base_url=_clean_base, **_extra), model


def _try_codex() -> Tuple[Optional[Any], Optional[str]]:
Expand Down Expand Up @@ -1349,6 +1363,49 @@ def _is_auth_error(exc: Exception) -> bool:
return "error code: 401" in err_lower or "authenticationerror" in type(exc).__name__.lower()


def _is_unsupported_parameter_error(exc: Exception, param: str) -> bool:
"""Detect provider 400s for an unsupported request parameter.

Different OpenAI-compatible endpoints phrase the same class of error a few
ways: ``Unsupported parameter: X``, ``unsupported_parameter`` with a
``param`` field, ``X is not supported``, ``unknown parameter: X``,
``unrecognized request argument: X``. We match on both the parameter
name and a generic "unsupported/unknown/unrecognized parameter" marker so
call sites can reactively retry without the offending key instead of
surfacing a noisy auxiliary failure.

Generalizes the temperature-specific detector that originally shipped
with PR #15621 so the same retry strategy can cover ``max_tokens``,
``seed``, ``top_p``, and any future quirk. Credit @nicholasrae (PR #15416)
for the generalization pattern.
"""
param_lower = (param or "").lower()
if not param_lower:
return False
err_lower = str(exc).lower()
if param_lower not in err_lower:
return False
return any(marker in err_lower for marker in (
"unsupported parameter",
"unsupported_parameter",
"not supported",
"does not support",
"unknown parameter",
"unrecognized request argument",
"unrecognized parameter",
"invalid parameter",
))


def _is_unsupported_temperature_error(exc: Exception) -> bool:
"""Back-compat wrapper: detect API errors where the model rejects ``temperature``.

Delegates to :func:`_is_unsupported_parameter_error`; kept as a separate
public symbol because existing tests and call sites import it by name.
"""
return _is_unsupported_parameter_error(exc, "temperature")


def _evict_cached_clients(provider: str) -> None:
"""Drop cached auxiliary clients for a provider so fresh creds are used."""
normalized = _normalize_aux_provider(provider)
Expand Down Expand Up @@ -1782,12 +1839,15 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = ""):
provider,
)
extra = {}
_clean_base, _dq = _extract_url_query_params(custom_base)
if _dq:
extra["default_query"] = _dq
if base_url_host_matches(custom_base, "api.kimi.com"):
extra["default_headers"] = {"User-Agent": "claude-code/0.1.0"}
elif base_url_host_matches(custom_base, "api.githubcopilot.com"):
from hermes_cli.models import copilot_default_headers
extra["default_headers"] = copilot_default_headers()
client = OpenAI(api_key=custom_key, base_url=custom_base, **extra)
client = OpenAI(api_key=custom_key, base_url=_clean_base, **extra)
client = _wrap_if_needed(client, final_model, custom_base)
return (_to_async_client(client, final_model) if async_mode
else (client, final_model))
Expand Down Expand Up @@ -1824,6 +1884,8 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = ""):
model or custom_entry.get("model") or _read_main_model() or "gpt-4o-mini",
provider,
)
_clean_base2, _dq2 = _extract_url_query_params(custom_base)
_extra2 = {"default_query": _dq2} if _dq2 else {}
logger.debug(
"resolve_provider_client: named custom provider %r (%s, api_mode=%s)",
provider, final_model, entry_api_mode or "chat_completions")
Expand All @@ -1841,7 +1903,7 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = ""):
"installed — falling back to OpenAI-wire.",
provider,
)
client = OpenAI(api_key=custom_key, base_url=custom_base)
client = OpenAI(api_key=custom_key, base_url=_clean_base2, **_extra2)
return (_to_async_client(client, final_model) if async_mode
else (client, final_model))
sync_anthropic = AnthropicAuxiliaryClient(
Expand All @@ -1850,7 +1912,7 @@ def _wrap_if_needed(client_obj, final_model_str: str, base_url_str: str = ""):
if async_mode:
return AsyncAnthropicAuxiliaryClient(sync_anthropic), final_model
return sync_anthropic, final_model
client = OpenAI(api_key=custom_key, base_url=custom_base)
client = OpenAI(api_key=custom_key, base_url=_clean_base2, **_extra2)
# codex_responses or inherited auto-detect (via _wrap_if_needed).
# _wrap_if_needed reads the closed-over `api_mode` (the task-level
# override). Named-provider entry api_mode=codex_responses also
Expand Down Expand Up @@ -2760,8 +2822,8 @@ def _build_call_kwargs(
temperature = fixed_temperature

# Opus 4.7+ rejects any non-default temperature/top_p/top_k — silently
# drop here so auxiliary callers that hardcode temperature (e.g. 0.3 on
# flush_memories, 0 on structured-JSON extraction) don't 400 the moment
# drop here so auxiliary callers that hardcode temperature (e.g. 0 on
# structured-JSON extraction) don't 400 the moment
# the aux model is flipped to 4.7.
if temperature is not None:
from agent.anthropic_adapter import _forbids_sampling_params
Expand Down Expand Up @@ -2849,7 +2911,7 @@ def call_llm(

Args:
task: Auxiliary task name ("compression", "vision", "web_extract",
"session_search", "skills_hub", "mcp", "flush_memories").
"session_search", "skills_hub", "mcp", "title_generation").
Reads provider:model from config/env. Ignored if provider is set.
provider: Explicit provider override.
model: Explicit model override.
Expand Down Expand Up @@ -2952,13 +3014,45 @@ def call_llm(
if _is_anthropic_compat_endpoint(resolved_provider, _client_base):
kwargs["messages"] = _convert_openai_images_to_anthropic(kwargs["messages"])

# Handle max_tokens vs max_completion_tokens retry, then payment fallback.
# Handle unsupported temperature, max_tokens vs max_completion_tokens retry,
# then payment fallback.
try:
return _validate_llm_response(
client.chat.completions.create(**kwargs), task)
except Exception as first_err:
if "temperature" in kwargs and _is_unsupported_temperature_error(first_err):
retry_kwargs = dict(kwargs)
retry_kwargs.pop("temperature", None)
logger.info(
"Auxiliary %s: provider rejected temperature; retrying once without it",
task or "call",
)
try:
return _validate_llm_response(
client.chat.completions.create(**retry_kwargs), task)
except Exception as retry_err:
retry_err_str = str(retry_err)
# If retry still fails, fall through to the max_tokens /
# payment / auth chains below using the temperature-stripped
# kwargs. Re-raise only if the retry hit something those
# chains won't handle.
if not (
_is_payment_error(retry_err)
or _is_connection_error(retry_err)
or _is_auth_error(retry_err)
or "max_tokens" in retry_err_str
or "unsupported_parameter" in retry_err_str
):
raise
first_err = retry_err
kwargs = retry_kwargs

err_str = str(first_err)
if "max_tokens" in err_str or "unsupported_parameter" in err_str:
if max_tokens is not None and (
"max_tokens" in err_str
or "unsupported_parameter" in err_str
or _is_unsupported_parameter_error(first_err, "max_tokens")
):
kwargs.pop("max_tokens", None)
kwargs["max_completion_tokens"] = max_tokens
try:
Expand Down Expand Up @@ -3221,8 +3315,35 @@ async def async_call_llm(
return _validate_llm_response(
await client.chat.completions.create(**kwargs), task)
except Exception as first_err:
if "temperature" in kwargs and _is_unsupported_temperature_error(first_err):
retry_kwargs = dict(kwargs)
retry_kwargs.pop("temperature", None)
logger.info(
"Auxiliary %s (async): provider rejected temperature; retrying once without it",
task or "call",
)
try:
return _validate_llm_response(
await client.chat.completions.create(**retry_kwargs), task)
except Exception as retry_err:
retry_err_str = str(retry_err)
if not (
_is_payment_error(retry_err)
or _is_connection_error(retry_err)
or _is_auth_error(retry_err)
or "max_tokens" in retry_err_str
or "unsupported_parameter" in retry_err_str
):
raise
first_err = retry_err
kwargs = retry_kwargs

err_str = str(first_err)
if "max_tokens" in err_str or "unsupported_parameter" in err_str:
if max_tokens is not None and (
"max_tokens" in err_str
or "unsupported_parameter" in err_str
or _is_unsupported_parameter_error(first_err, "max_tokens")
):
kwargs.pop("max_tokens", None)
kwargs["max_completion_tokens"] = max_tokens
try:
Expand Down
Loading