Commit graph

1967 commits

Author SHA1 Message Date
teknium1
296494db0e fix: stop infinite loop when assistant content is a block list
strip_think_blocks() ran re.sub() directly on content that could be a
list of blocks (Anthropic via OpenRouter returns assistant content as
[{type:text,...},{type:thinking,...}]). A list reaching re.sub raised
'TypeError: expected string or bytes-like object, got list', which the
outer conversation loop swallowed and retried forever — the observed
infinite 'preparing terminal...' loop that re-emitted the same
assistant text every iteration.

The live-turn path normalized list content to a string, but
_interim_assistant_visible_text reads a *stored* history message whose
content was persisted as a list and passes it straight into the shared
strip_think_blocks helper. Fix at the shared choke point: coerce
list/dict content to visible text (dropping reasoning blocks, which is
the function's job) before any regex runs, so every caller is safe.
2026-07-17 15:47:10 -07:00
Teknium
4c0546c9cc fix(moa): surface stale presets without retries
Keep invalid persisted preset names fail-closed, list the valid configured choices, and classify the local lookup failure as deterministic so it reaches Desktop immediately.
2026-07-17 13:49:12 -07:00
Teknium
61bbc39330 fix(codex): harden final cache-key boundaries
Fold #62349's broader provider-boundary handling into the header fix: bound top-level and xAI override keys again at preflight after middleware, preserve unrelated headers, and cover boundaries and collisions.

Co-authored-by: Nick Taylor <nicktaylor@TheWorldofNick-Lappy.local>
2026-07-17 13:48:41 -07:00
webtecnica
8051ebae30 fix: cap cache-scope headers at 64 chars to avoid Codex 400 error (#66045) 2026-07-17 13:48:41 -07:00
Haider Sultan
18331b9bbd feat(codex): stream live app-server events to TUI/desktop tool cards
Extends the app-server event bridge (make_codex_app_server_event_bridge)
to fire the authoritative stable-ID tool_start_callback /
tool_complete_callback alongside the existing tool_progress_callback,
and route item/reasoning/summaryDelta through the reasoning channel.

Surfaces that render structured tool cards (TUI, desktop) — not just
progress bubbles — now correlate live cards with the projected history
entry after a resume, because the call ids mirror CodexEventProjector's
_deterministic_call_id. Guarded per-callback so a broken display
consumer can't tear down the codex turn loop.

Grafted from PR #65412 by @HaiderSultanArc onto the merged bridge (the
PR's parallel _codex_live_event implementation was reconciled into the
bridge's existing _fire_tool_started/_fire_tool_completed helpers).
2026-07-17 13:44:12 -07:00
brooklyn!
41fdcae688
fix(streaming): make the single-writer fence best-effort so a missing guard can't crash a turn (#66448)
A cron job ("Daily Buzz Report") died with 'AIAgent' object has no
attribute '_claim_stream_writer'. The #65991 single-writer fence lives on
AIAgent (run_agent.py), but the streaming paths that use it live in other
modules — chat_completion_helpers (chat / anthropic / bedrock) and
codex_runtime (codex responses) — and called it directly as
agent._claim_stream_writer() / agent._stream_writer_is_current(). That makes
those modules hard-depend on the method being present on whatever object is
passed as agent.

The fence is an *additive* safety net that may only ever drop a provably
superseded stream, never the sole legitimate writer. But the direct calls
turned any agent that doesn't expose it — a version-skewed checkout (the
streaming helper module newer than run_agent), a hot-reloaded gateway mid
git-pull, a duck-typed agent, or a test double — into a fatal AttributeError
that aborts the whole turn (and, on cron, fails the job).

Route every cross-module claim/check through agent/stream_single_writer.py.
claim_stream_writer(agent) returns 0 when the fence is unavailable (or
raises), and stream_writer_is_current(agent, token) treats a 0 token or an
absent guard as "current" — so a guard-less agent degrades to "no fence"
instead of crashing, while a real AIAgent keeps the full single-writer
protection. Internal self.* uses inside run_agent are unchanged (self is
always a full AIAgent there).
2026-07-17 18:31:09 +00:00
HexLab
594308d4bb
fix(credential-pool): throttle "no available entries" log to stop Windows log-lock storm (contributes to #62698) (#66338)
* fix(credential-pool): throttle "no available entries" log to stop Windows log-lock storm

Credential selection runs on a hot path (every model call plus auxiliary
tasks), so an empty/exhausted pool logged "no available entries" at INFO on
*every* selection. On Windows, where multiple Hermes processes share one
rotating log guarded by concurrent-log-handler's cross-process lock, that
per-selection volume storms the lock (RuntimeError: Cannot acquire lock after
20 attempts), pegs a core, and stalls the asyncio event loop long enough that
the Desktop backend readiness probe times out ("Timed out connecting to Hermes
backend after 15000ms") even though the backend already announced
HERMES_BACKEND_READY.

Log the condition at most once per 60s window, re-arming on a successful
selection so recovery->re-exhaustion still surfaces promptly. Same fix class as
the warn-once dedup in #58265.

* test(credential-pool): cover no-available-entries log throttle

Assert the empty-pool INFO line logs at most once per throttle window, logs
again after the window elapses, and re-arms on a successful selection so a
recover->re-exhaust transition surfaces promptly. Uses a deterministic fake
monotonic clock (no sleeps, no network).
2026-07-17 13:08:46 -04:00
Teknium
73057ed161 fix(auxiliary): scope runtime state to each turn 2026-07-17 09:08:30 -07:00
srojk34
bcce700783 fix(compression): reset failure cooldown on runtime switch 2026-07-17 09:08:30 -07:00
Krowd
c201b72f34 fix(auxiliary): sync runtime after fallback restoration 2026-07-17 09:08:30 -07:00
dfein38347g
fdc6c32d7d fix(auxiliary): isolate runtime cache by live context 2026-07-17 09:08:30 -07:00
Teknium
ec3d958425 feat(codex): webSearch bubbles + bare hermes-tools names in app-server bridge
Two more display gaps from #26541 grafted onto the merged bridge:

- webSearch: codex's built-in web search now produces a tool.started/
  tool.completed bubble pair (query as preview + args). Previously the
  item type wasn't in _CODEX_TOOL_ITEM_TYPES, so built-in searches
  showed nothing.
- mcp.hermes-tools.* stripping: tools codex invokes through Hermes' own
  hermes-tools MCP server display as their bare names (web_search,
  browser_navigate) instead of mcp.hermes-tools.web_search. The inner
  dispatch subprocess can't fire native progress events, so the
  codex-level event is the display event — name it the way users know
  the tool.

Credit: both behaviors designed and first implemented by @simpolism in
PR #26541 (May 15, earliest of the app-server display-bridge family).
2026-07-17 06:49:47 -07:00
snav
11a91a6d17 fix(codex): forward drained notifications to on_event during approval roundtrips
The approval-drain loop in CodexAppServerSession.run_turn drains up to 8
pending notifications to keep per-turn state current before answering a
server-initiated approval request — but never forwarded them to the
on_event display hook. Tool bubbles for items drained alongside an
approval (e.g. the item/started for the very command awaiting approval)
silently disappeared.

Mirror the main notification path's on_event invocation in the drain
loop. Regression test demonstrates RED→GREEN.

Grafted from PR #26541 by @simpolism — the earliest submission of the
codex app-server display-bridge family (May 15). Confirmed independently
by #64698 and #65412.
2026-07-17 06:49:47 -07:00
Teknium
c7205040c3
fix(compression): affirm tool use stays active in the compaction handoff prefix (#66291)
The REFERENCE ONLY framing ('treat as background reference, NOT as active
instructions... Do NOT answer questions or fulfill requests') was observed
bleeding into general tool-use suppression: a production session went
narration-only for 7 consecutive turns immediately after a compression
event, describing edits instead of calling tools (#65848 report).

Fix is additive: one clause stating the note does not restrict HOW the
agent works — tools remain fully active for the active task. Every
anti-resumption protection stays intact; the previous prefix generation
is frozen into _HISTORICAL_SUMMARY_PREFIXES per the module contract so
persisted summaries still get the directive-strip on re-compaction.

The #65848 rewrite was not taken: dropping the 'Do NOT answer questions'
line and the four-heading discard directive risks re-opening the
stale-task-resumption class those clauses exist to prevent (the carveout
era regressions #41607/#38364/#42812 documented in this file).

Report and root-cause analysis: @yasserbousrih (#65848).
2026-07-17 06:49:42 -07:00
Teknium
d32a6d4cca fix(codex): claim the stream-writer token on the codex_responses path too
Widen the #65991 single-writer fence to run_codex_stream: each codex
attempt claims the delta sink before consuming events, and the consume
loop's interrupt_check now also stops the instant a newer attempt
supersedes this one. Parity with the chat_completions / anthropic /
bedrock paths from the salvaged fix.

Two regression tests: superseded codex stream is fenced mid-stream;
sole-writer codex stream delivers unchanged.
2026-07-17 06:49:23 -07:00
HexLab98
0c9ac09313 fix(streaming): fence superseded streams out of the delta sink (single-writer)
When the stale-stream detector reconnects past a stream whose socket abort
raced (the close never actually stopped the old worker), the superseded stream
and the retry's stream both write deltas into the same turn. The persisted
transcript is then two coherent responses interleaved token-by-token —
de-interleaving the stored text by alternation yields two complete, independent
answers to the same prompt, which is a dual-writer race in the harness, not a
model/context failure (#65991).

The interrupt path already positively cancels before force-closing (#6600), but
the stale-kill path relies only on the socket abort, and nothing fenced late
chunks from a superseded stream out of the shared delta sink.

Enforce a single-writer invariant on the sink itself, guarded by attempt id
rather than only socket state: every streaming attempt (chat_completions,
anthropic_messages, and bedrock paths) claims a monotonic writer token before
it begins consuming its stream. A newer claim supersedes any older one, so the
consume loop bails the instant it is superseded and _fire_stream_delta /
_fire_reasoning_delta / _record_streamed_assistant_text drop chunks from a
stale writer. The token is stored per-thread, so a thread that never claimed
(a non-streaming delta caller) is never fenced — the guard can only ever drop a
superseded stream, never the single legitimate writer. Discards are counted and
logged sparsely so a real provider problem stays visible instead of being
silently swallowed.
2026-07-17 06:49:23 -07:00
Teknium
348e9912ff
fix(agent): execute valid tool calls in mixed batches with invalid names (#66317)
Degrading models (observed with gpt-5.6 past ~350K input) emit tool-call
batches like 6 valid named calls + 1 blank-name call. Previously the
whole turn was voided — every valid call got 'Skipped: another tool call
in this turn used an invalid name' — and three such batches tripped the
3-strike stop, killing sessions that were still making progress.

Now a mixed batch error-results ONLY the invalid call(s) (terse
anti-priming error for blank names per #47967, catalog dump for typos)
and dispatches the valid subset for execution. The assistant message
keeps every emitted call so provider-side tool_call/result pairing stays
intact. The 3-strike counter only advances when a turn contains NO valid
call, so a fully-degenerate model still stops while a mostly-coherent
one keeps working. Broken JSON args on a never-executing invalid call no
longer trigger the whole-turn JSON retry loop.

Field evidence: July 2026 debug bundle showed gpt-5.6-sol emitting
6-call batches with one blank-name rider at 559K/384K-token context in
two separate sessions; 13 valid tool calls were discarded before the
session stopped as partial.
2026-07-17 06:48:42 -07:00
Hermes Agent
174fc958ab fix(errors): classify Z.AI GLM token-limit message as context overflow
Port from anomalyco/opencode#35671: Z.AI / Zhipu GLM returns
'tokens in request more than max tokens allowed' (error code 1210) on
context overflow. This matched no pattern in _CONTEXT_OVERFLOW_PATTERNS,
so the error classified as unknown/retryable — the agent would retry the
oversized request instead of triggering context compression.

Proven live on main before the fix: classify_api_error() returned
FailoverReason.unknown for the exact Z.AI error shape; now returns
context_overflow.
2026-07-17 04:57:27 -07:00
teknium1
c356752b6b fix(memory): drain queued writes on shutdown 2026-07-17 04:55:58 -07:00
teknium1
b4221c6db2 Inspired by Claude Code: protect session transcripts 2026-07-17 04:54:34 -07:00
Teknium
b78ff50d8d fix(gemini): prune required entries missing from properties in tool schemas
Port from Kilo-Org/kilocode#11955: Gemini validates every object schema's
required list strictly against the same node's properties and fails the
ENTIRE GenerateContentRequest with HTTP 400 'required[0]: property is not
defined' when a name has no matching property. MCP servers (e.g. the GitHub
remote MCP) routinely emit array item schemas carrying required without
properties, which made every request on the native Gemini path fail before
any model output.

sanitize_gemini_schema() now filters required to names present in the node's
properties and drops the keyword when nothing valid remains. Applies
recursively (properties / items / anyOf). Tool handlers still validate
required fields at execution time, so nothing the model could actually use
is lost.

Scoped to the Gemini-facing sanitizer only — the universal
tools/schema_sanitizer.py already prunes typed object nodes, and its
remaining gap (untyped nodes) is contested by open PR #20151.
2026-07-17 04:54:06 -07:00
Teknium
780e098077 fix: widen UTF-8 BOM tolerance to all sibling frontmatter parsers
The previous commit fixes the canonical agent/skill_utils.parse_frontmatter.
Six more modules reimplement the '---' fence check locally and had the
same bug:

- tools/skill_manager_tool.py _validate_frontmatter — rejected BOM'd
  skill_manage create/edit content outright
- tools/skills_hub.py GitHubSource._parse_frontmatter_quick and
  OptionalSkillSource._parse_frontmatter — hub browse/install metadata
- hermes_cli/skills_hub.py — local skill install validation
- gateway/run.py — skill slug discovery for disabled-skill hints
- agent/prompt_builder.py _strip_yaml_frontmatter — BOM'd context files
  (AGENTS.md) leaked raw frontmatter into the system prompt
- tools/blueprints.py _split_frontmatter — str.lstrip() does not strip
  U+FEFF (not whitespace), so the existing lstrip never covered it

Sibling-surface regression tests added.
Bug class also fixed upstream in cline/cline#12218 (found by the weekly
Cline PR scout).
2026-07-17 04:52:02 -07:00
Que0x
a4ecb3da9a fix(skills): strip UTF-8 BOM before parsing SKILL.md frontmatter
A UTF-8 BOM saved into a SKILL.md (e.g. Notepad or PowerShell `>`) is kept by
read_text(encoding="utf-8"), so the string handed to parse_frontmatter starts
with the BOM and the startswith("---") fence check fails. The whole frontmatter
is then silently dropped: the skill loads with no name/description, `platforms`
gating falls open (a macOS-only skill becomes visible everywhere), and
required_environment_variables / metadata.hermes.config setup never fires.

Strip a single leading BOM at the top of parse_frontmatter, the shared
chokepoint for every local skill-loading path (_parse_skill_file,
discover_all_skill_config_vars, DESCRIPTION.md parsing, _inject_skill_config,
and the tools/skills_tool._parse_frontmatter re-export), so the whole class is
covered, not just the reported site. Only the leading marker is removed; a BOM
mid-content is left as data. Mirrors the existing file-tools BOM handling
(#35278) and CONTRIBUTING.md "File encoding".

Adds tests: BOM'd frontmatter parses identically to plain, the body is
BOM-free, platforms gating and config-var extraction survive a BOM, and an
end-to-end BOM-write / plain-read round trip (mirroring _parse_skill_file).
2026-07-17 04:52:02 -07:00
Teknium
78b9d98d76 fix(codex): surface nested error envelope in Responses type=error SSE frames
Port from anomalyco/opencode#36130: the Responses spec carries streaming
error details at the top level of the error frame, but the official OpenAI
SDK and several OpenAI-compatible proxies wrap them in an HTTP-style nested
envelope ({"type": "error", "error": {code, message, param}}).

_raise_stream_error only read top-level fields, so nested-envelope frames
collapsed to the generic 'stream emitted error event' placeholder with
code=None — the error classifier never saw the provider's real failure
reason, misrouting rate-limit / context-overflow / entitlement errors into
the generic retry path.

Top-level fields keep precedence; the envelope is a fallback. Null-tolerant
for spec-compliant frames with explicit nulls.
2026-07-17 04:51:14 -07:00
Teknium
14ea8de763 fix(agent): harden non-finite wait recovery
Only advertise finite watchdog deadlines that are still in the future, exercise the full MoA heartbeat path, and register the salvaged contributor attribution.
2026-07-17 04:46:59 -07:00
发飙的公牛
1a5d2a12d8 fix: handle infinite Codex wait deadlines 2026-07-17 04:46:59 -07:00
Teknium
60419dfb4b fix(codex): reconcile app-server bridge with #38835, gate commentary on show_commentary
Follow-ups on top of @xxxigm's salvaged bridge (#33294):

- Remove the now-dead narrow item/started-only mapper from #38835
  (_codex_note_to_tool_progress) — the full bridge supersedes it and
  keeps the same tool-name contract; its tests are repointed at the
  bridge helpers.
- Preserve main's request_routing/approval-bypass wiring on the
  CodexAppServerSession constructor (landed after the PR was filed).
- Gate agentMessage interim delivery on display.show_commentary so the
  app-server runtime honors the same toggle as the codex_responses
  commentary channel (tool progress is unaffected).
- Add json import (bridge helpers use json.dumps) and modernize the
  wiring test's stub agent for main's usage-accounting attributes.
2026-07-17 04:32:56 -07:00
xxxigm
7b63c4955a fix(codex): surface live tool-progress + commentary on app-server runtime (#33200)
Pass ``on_event=make_codex_app_server_event_bridge(agent)`` when
spawning the per-session ``CodexAppServerSession``. The session has
always had a raw event hook but ``run_codex_app_server_turn`` never
supplied one, so Discord / Telegram / TUI users saw nothing while
codex was working — only the final answer landed.

Now each ``item/started`` for a tool-shaped item fires
``tool_progress_callback("tool.started", ...)``, ``item/completed``
fires the matching ``"tool.completed"`` with duration + result,
``item/agentMessage/delta`` flows through ``_fire_stream_delta`` and
each completed ``agentMessage`` surfaces through
``_emit_interim_assistant_message`` so the gateway's
``already_streamed`` dedupe keeps interim commentary in the channel
without duplicating text the stream already showed.
2026-07-17 04:32:56 -07:00
xxxigm
e840cca1a9 feat(codex): add app-server event bridge for Hermes UI callbacks
Adds ``make_codex_app_server_event_bridge(agent)`` plus four small
mapping helpers (``_codex_item_to_tool_name`` / ``_codex_item_to_args``
/ ``_codex_item_to_preview`` / ``_codex_item_completion_payload``)
that translate codex JSON-RPC ``item/*`` notifications into the
exact shape Hermes' gateway UI callbacks expect — tool names match
``CodexEventProjector`` so the progress bubbles and the projected
``tool_calls`` entries use the same identifiers.

No behaviour change yet: the next commit wires the bridge into
``run_codex_app_server_turn`` (#33200).
2026-07-17 04:32:56 -07:00
Teknium
779019ef7d feat(agent): add display.show_commentary toggle for Codex commentary channel
Commentary delivery is on by default; users who find the extra mid-turn
narration noisy can set display.show_commentary: false to restore the
previous behavior (commentary routed to the reasoning channel, visible
only with show_reasoning).

- hermes_cli/config.py: display.show_commentary default true
- agent/agent_init.py: wire config -> agent.show_commentary
- run_agent.py: gate structured commentary extraction on the flag
- agent/codex_runtime.py: gate live-stream commentary callback (falls
  back to legacy reasoning-channel routing when off)
- docs + 2 tests (interim path off, live stream fallback)

Also adds AUTHOR_MAP entries for davidrobertson and 100yenadmin.
2026-07-16 23:27:12 -07:00
Eva
7041c56cdf feat(agent): stream Codex commentary separately 2026-07-16 23:27:12 -07:00
Eva
b008131b54 fix(agent): harden Codex commentary interim delivery 2026-07-16 23:27:12 -07:00
kshitijk4poor
73ad9136b7 refactor(credentials): consolidate single-use OAuth refresh lock scaffolding
The openai-codex and xai-oauth branches of _refresh_entry duplicated the
lock-timeout computation and _auth_store_lock acquisition. Extract the
shared scaffolding: a combined provider guard, a dispatch to the
provider-specific sync helper, and a _single_use_refresh_lock_timeout()
helper. Each provider's distinct post-sync decision logic (codex
needs-refresh short-circuit vs xai token-equality adoption) is preserved
verbatim. Behavior parity verified by the credential pool suite (98
passed) and a direct timeout-helper probe for both providers.

Follow-up to salvaged PR #62285.
2026-07-17 11:37:25 +05:30
cresslank
34837597d2 fix(auth): make xAI OAuth pools multi-account resilient
Keep each xAI OAuth auth-add login as an independent manual device-code pool entry and recognize xAI personal-team spending-limit 403 responses as billing exhaustion. Preserve the structured top-level error message so the failed credential is quarantined and the next healthy account is selected without attempting a pointless token refresh.

Route direct xAI HTTP consumers through the credential pool as well. Proactive and 401-reactive refreshes update the exact issuing manual entry, preserve validated xAI base URL overrides, and serialize single-use refresh-token rotation across concurrent pool instances.
2026-07-17 11:37:25 +05:30
Thatgfsj
ef1c622105 fix(title): prevent stale background title generation from reloading unloaded Ollama models
Add a runtime_validator callback to generate_title() / auto_title_session()
/ maybe_auto_title(). Callers snapshot the session's model+provider when
spawning the background titler; the validator runs right before the LLM
request and skips it silently when the live runtime no longer matches —
so a stale title request can't reload a model that strict_single_load
already evicted after a user model switch. Fail-open: a raising validator
never disables titling.

Wired at all four call sites (cli, gateway, tui_gateway, acp_adapter).

Surgical reapply of PR #19137 (base was 8k+ commits stale; the original
patch predates the pinned-language prompts, the atomic-write helper, and
the moved TUI/ACP call sites). Original work by @Thatgfsj. Closes #19027.
2026-07-16 23:07:13 -07:00
Trevor Gordon
9bf5822a2f fix(cron): robust session title generation (#50535, #50536, #50537) 2026-07-16 22:39:47 -07:00
Raşit Akyol
d05cd7c1ef fix(agent): make auto-title write atomic 2026-07-16 22:39:47 -07:00
Raşit Akyol
f725cf830f fix(agent): avoid overwriting manual session titles 2026-07-16 22:39:47 -07:00
Teknium
8222b16785 fix(title): follow-ups for salvaged #37349 — lazy config import, guard ordering, config example
- Make the config imports lazy inside _auto_title_enabled(), matching the
  existing _title_language() pattern (title_generator is imported from agent
  code paths where a module-level hermes_cli import risks circularity).
- Check the enabled flag after the cheap first-exchange guard in
  maybe_auto_title so config isn't read on every turn of a long session.
- Repoint the two new tests at the real import site.
- Document the key in cli-config.yaml.example and merge the enabled flag
  into the existing title_generation block in configuration.md.
- AUTHOR_MAP entry for the contributor.
2026-07-16 22:24:45 -07:00
Danny Huang
e20c3c1c29 fix: honor disabled title generation config 2026-07-16 22:24:45 -07:00
amanning3390
311a5b0a55 feat(kimi): discover K3 on coding endpoint 2026-07-16 13:33:02 -07:00
Erosika
8d1c96fd2f fix(memory): align external prefetch guard with fail-open contracts 2026-07-16 12:48:48 -07:00
LeonSGP43
d77c455d7d fix(memory): fail fast on stuck external prefetch 2026-07-16 12:48:48 -07:00
Teknium
31a3822b80
fix(title): contain auto-title thread exceptions instead of dumping tracebacks to the terminal (#65792)
auto_title_session runs as a bare daemon-thread target. Any exception
escaping it hits the default threading excepthook and sprays a raw
traceback into the user's terminal mid-session. The canonical trigger
is the post-'hermes update' stale-module window: the function's lazy
imports read NEW source from disk while already-imported modules
(agent.portal_tags) are still the OLD cached version, producing an
ImportError that repeats on every auto-title attempt until the
long-running process restarts (seen live after 9ce0e67f2 added
set_conversation_context).

The public entrypoint now wraps the body in a catch-all that logs one
WARNING naming the likely cause ('restart the running Hermes process'),
routes the exception through the existing failure_callback channel
(user-visible warning in CLI, debug-suppressed in gateway per #23246),
and never re-raises. This also makes the function honor its own
docstring contract ('silently skips if title generation fails').
2026-07-16 12:41:56 -07:00
briandevans
9078a838c7 fix(lmstudio): clamp max/ultra reasoning effort to LM Studio's ceiling
LM Studio's request vocabulary tops out at "xhigh", but Hermes' generic
effort ladder has since grown two stronger levels. "max" and "ultra" miss
the _LM_VALID_EFFORTS membership test, keep the initialized "medium"
default, and are thereby conflated with unparseable input -- so asking for
more reasoning yields less than "xhigh":

    high  -> 'high'     xhigh -> 'xhigh'
    max   -> 'medium'   ultra -> 'medium'

This is drift, not a design choice. The valid set was an exact mirror of
VALID_REASONING_EFFORTS when the file was authored; the ladder then grew
"max" and later "ultra", and the sweep that taught every other provider
about the new levels missed this module -- it has never been touched since
it was written.

Clamp the two stronger levels onto LM Studio's declared ceiling instead,
mirroring the ceiling clamp every other provider already applies. Widening
_LM_VALID_EFFORTS would instead assert that LM Studio accepts "max" on the
wire, which is a provider-side claim this repo cannot verify; clamping
consumes only the ceiling the file already declares for itself.

The clamp is kept separate from _LM_EFFORT_ALIASES because that mapping is
also applied to the model's published allowed_options, which must not be
rewritten. A clamped value stays subject to the allowed_options check, so a
model that does not publish "xhigh" still gets the field omitted and falls
back to its own default -- exactly how a directly-requested "xhigh" behaves.

The regression test asserts monotonicity over the canonical ladder rather
than the two values alone, so the next level added upstream cannot silently
reintroduce the inversion.
2026-07-16 07:57:51 -07:00
Teknium
b5bd0ef38b docs(kanban): port attachment guidance into KANBAN_GUIDANCE
PR #36019 documented the attachment tools in the kanban-worker skill,
but main removed that skill in #50473 and folded its content into the
KANBAN_GUIDANCE prompt block. Land the same guidance there instead so
every dispatcher-spawned worker sees it.
2026-07-16 07:33:14 -07:00
Teknium
03c0b00f45
fix(usage): read DeepSeek's native prompt_cache_hit_tokens cache field (#65678)
Some checks are pending
CI / Detect affected areas (push) Waiting to run
CI / Python tests (push) Blocked by required conditions
CI / Python lints (push) Blocked by required conditions
CI / JS & TS checks (push) Blocked by required conditions
CI / Docs Site (push) Blocked by required conditions
CI / Deny unrelated histories (push) Blocked by required conditions
CI / Check contributors (push) Blocked by required conditions
CI / Check uv.lock (push) Blocked by required conditions
CI / package-lock.json diff (push) Blocked by required conditions
CI / Lint Docker scripts (push) Blocked by required conditions
CI / Build&Test Docker image (push) Blocked by required conditions
CI / Supply-chain scan (push) Blocked by required conditions
CI / OSV scan (push) Waiting to run
CI / All required checks pass (push) Blocked by required conditions
CI / CI timing report (push) Blocked by required conditions
Deploy Site / deploy-vercel (push) Waiting to run
Deploy Site / deploy-docs (push) Waiting to run
auto-fix lint issues & formatting / Generate eslint --fix patch (push) Waiting to run
auto-fix lint issues & formatting / Apply patch (push) Blocked by required conditions
DeepSeek's own API (api.deepseek.com) reports context-cache hits as
top-level usage.prompt_cache_hit_tokens / prompt_cache_miss_tokens
(prompt_tokens = hit + miss), not the OpenAI nested
prompt_tokens_details.cached_tokens shape. Neither normalize_usage()
nor the chat_completions transport's extract_cache_stats() read those
fields, so direct DeepSeek sessions always showed 0 cache-hit tokens:
invisible in accounting, mis-billed at the full input rate, and 0%
cache display.

Both layers now fall back to prompt_cache_hit_tokens when the nested
shape is absent; the nested value wins when both are present (proxies).

Fixes #61871.
2026-07-16 07:29:53 -07:00
Teknium
adb647269a fix(auxiliary): apply review fixes to #36043 — guard named-custom routing, drop dead key assignment, tighten Palantir host match
Review follow-ups on the cherry-picked #36043 commit:

1. Guard the custom:<name> passthrough with a _get_named_custom_provider
   lookup. The PR unconditionally kept the full custom:<name> string, which
   broke config-less runtime custom providers (#34777 regression — entries
   that exist only in the live runtime, not config.yaml): the named arm
   found no entry and resolution fell through to Step 2. Now custom:<name>
   only takes the named arm when a config entry actually exists; otherwise
   it collapses to the anonymous-custom arm with the runtime endpoint,
   preserving pre-PR behavior.

2. Drop the dead 'explicit_api_key = runtime_api_key' assignment (and its
   misleading comment) in the named-entry branch. resolve_provider_client's
   named-custom arm derives the key exclusively from the entry's
   api_key/key_env and never reads explicit_api_key, so the assignment was
   a no-op. Wiring precedence in was not justified: for a named custom
   provider the runtime key IS the entry's key (set_runtime_main sources it
   from the same config), so deletion is the honest option.

3. Tighten the Palantir Bearer-auth check from a loose substring match
   ('palantirfoundry' in normalized) to a hostname match via
   base_url_host_matches(..., 'palantirfoundry.com'), so path segments or
   lookalike domains containing the string no longer trigger Bearer auth.

Tests: named-custom anthropic_messages end-to-end routing (full name kept,
AnthropicAuxiliaryClient at the original /anthropic URL, no /v1 rewrite)
plus Palantir Bearer-auth positive and substring-false-positive cases.
2026-07-16 07:28:07 -07:00
antydizajn
367d3758d5 fix(auxiliary): route custom:<name> through named-provider arm + Palantir Bearer auth
When the user's main provider is a named custom_providers entry exposing an
Anthropic Messages surface (e.g. Palantir Foundry's
/api/v2/llm/proxy/anthropic, custom LiteLLM/Bedrock proxies), auxiliary
tasks (title generation, compression, web extract, session search, etc.)
returned HTTP 404 NOT_FOUND for every call.

Root cause: `_resolve_auto` collapsed any `custom:<name>` main provider
to plain `"custom"` and passed runtime_base_url as explicit_base_url.
This landed in `resolve_provider_client`'s anonymous-custom arm
(`if provider == "custom":`), which unconditionally calls
`_to_openai_base_url` — that helper strips a trailing `/anthropic` and
substitutes `/v1` (designed for MiniMax/ZAI which expose both surfaces).
The result for Palantir is `/api/v2/llm/proxy/v1`, which does not exist
on the proxy — every auxiliary call 404s. The runtime `api_mode=
anthropic_messages` flag was discarded by this arm.

Fix: split the conditional so only the literal `"custom"` provider takes
the anonymous-custom path; `custom:<name>` keeps its full `custom:<name>`
string when handed to `resolve_provider_client`, where the
named-custom-provider arm (added in earlier work) honours the entry's
`api_mode` and routes through `AnthropicAuxiliaryClient` against the
original `/anthropic` URL.

Also: extend `_requires_bearer_auth` in `anthropic_adapter.py` to
recognise palantirfoundry hosts so the SDK sends `Authorization: Bearer`
instead of the default `x-api-key` (Palantir's proxy rejects x-api-key
with 401).

Verified end-to-end against a live Palantir Foundry deployment with both
claude-4-6-opus and claude-4-7-opus models — `generate_title` returns
real titles instead of 404ing.  Regression-tested:

  - anonymous `custom` (with base_url) still routes to OpenAI wire
  - built-in NVIDIA provider unchanged
  - custom-without-base_url still falls through to Step-2 chain
2026-07-16 07:28:07 -07:00
Dan Schnurbusch
f68fd80f41 fix(auth): preserve fallback routes and OAuth state
Switch provider and model together after setup-time auth failure. Serialize global auth-store merges under target-specific locks and preserve auth-to-shared lock ordering for profile OAuth refreshes.
2026-07-16 06:14:56 -07:00