Skip to content

fix: keep a trailing reasoning signature with the block it signs - #26

Merged
sena-labs merged 1 commit into
feat/openrouter-api-alignment-phase1from
claude/compassionate-brown-eomm20
Oct 6, 2026
Merged

sena-labs merged 1 commit into
feat/openrouter-api-alignment-phase1from
claude/compassionate-brown-eomm20

Conversation

@sena-labs

Copy link
Copy Markdown
Owner

Summary

Closes the one review finding on #25 that was still open on the current head. Targets feat/openrouter-api-alignment-phase1 rather than main, so it lands inside #25 instead of racing it.

A streamed reasoning block whose signature arrives in a final, unindexed delta was split into two records — the accumulated text, and a separate signature-only entry. Replayed into the next tool round the signature no longer accompanied the text it signs, which is exactly what #25 set out to fix.

Related Issue

Follows up #25 (review thread on openrouter_pipe.py:492). No separate issue.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to break)
  • Documentation update
  • Refactoring (no functional changes)
  • Test improvement

Changes Made

  • _accumulate_reasoning reads the seal off the accumulated slot, not the incoming fragment. The condition was detail.get("signature") is not None or detail.get("data") is not None — an incoming fragment carrying a signature always opened a new record. It is now slot.get("signature") is not None: a slot that already holds a signature is complete, so the next fragment starts a new record, while a trailing signature-only delta merges into the block it closes.
  • Dropped data from the seal test. The merge loop concatenates data strings, so sealing on data would have split a multi-fragment encrypted payload into one record per fragment — the same defect in a different coat. Keying on the slot's signature leaves those concatenating.
  • Three regression tests in test_alignment.py: a trailing signature-only delta stays with its text; a block following a signed one still starts a new record (the separation the old condition was reaching for is preserved); unindexed encrypted fragments concatenate.

The indexed path was already correct — index keys the slot, so the signature merged there. Only streams that omit index were affected.

Before / after

_accumulate_reasoning(s, [{"type": "reasoning.text", "text": "Let me "}])
_accumulate_reasoning(s, [{"type": "reasoning.text", "text": "think."}])
_accumulate_reasoning(s, [{"type": "reasoning.text", "signature": "sig-abc"}])
before: [{"type":"reasoning.text","text":"Let me think."},
         {"type":"reasoning.text","signature":"sig-abc"}]     ← signature stranded
after:  [{"type":"reasoning.text","text":"Let me think.","signature":"sig-abc"}]

Testing

  • Assertion suite passes (python test_pipe.py) — 1058 asserts, 0 failed
  • Maintenance regressions pass (python -m unittest -v test_maintenance test_alignment) — 60 tests OK (3 new)
  • OWUI compatibility smoke — not affected; no change to the save path, frontmatter or valves
  • New tests added for the changes
  • Integration tests — n/a
  • User-visible changes recorded in CHANGELOG.md — no new entry: [Unreleased] already carries "Replay signed/encrypted reasoning and assistant content through native tool rounds" from feat: align chat, media and routing flows with current OpenRouter APIs (Phase 1) #25, and this makes that line true in the unindexed case rather than adding a separate change. Per CONTRIBUTING.md, [Unreleased] holds pending entries only.

Both commands above are exactly what the Unit tests CI job runs.

Screenshots / Demo

N/A — behavioral fix; the before/after above is the reproduction.

Checklist

  • My code follows the project's coding standards
  • I have added docstrings to new public methods — none added; the three new tests carry docstrings and the changed branch has an explanatory comment
  • My changes do not introduce debug print statements beyond [OpenRouter Pipe] logging
  • No secrets, API keys, or credentials are included in this PR — changed files scanned
  • My commit messages follow Conventional Commits

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rw7cYoHbaXFqdGBTXiRfeh


Generated by Claude Code

_accumulate_reasoning started a new record whenever an incoming unindexed
fragment carried a signature. A provider that streams its signature in a
final delta — the usual shape for signed reasoning — therefore produced two
records: the accumulated text, and a separate signature-only entry. Replayed
into the next tool round, the signature no longer accompanied the text it
signs, and providers that verify signed reasoning can reject the continuation.

The seal is now read off the accumulated slot instead of the incoming
fragment: a slot that already has a signature is complete, so the next
fragment starts a new record, while a trailing signature-only delta merges
into the block it closes. Keying on the slot also leaves multi-fragment
encrypted payloads concatenating, which sealing on `data` would have split.

The indexed path was already correct; this only affects streams that omit
`index`.

Addresses the unresolved review finding on PR #25 (openrouter_pipe.py:492).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rw7cYoHbaXFqdGBTXiRfeh
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T19:14:24.475009Z 9ae2073 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sena-labs
sena-labs merged commit e59c4f9 into feat/openrouter-api-alignment-phase1 Oct 6, 2026
7 checks passed
@sena-labs
sena-labs deleted the claude/compassionate-brown-eomm20 branch October 6, 2026 19:10
sena-labs added a commit that referenced this pull request Oct 11, 2026
…s (Phase 1) (#25)

* feat: align chat, media and routing flows with current OpenRouter APIs

Phase 1 of docs/PLAN-OPENROUTER-ALIGNMENT.md:

- merge provider/reasoning controls without losing explicit settings;
  enforce ZDR/deny-data policy against user preferences
- replay reasoning_details and assistant content across tool rounds;
  keep server tools alongside OWUI functions; run sync callables off-loop
- cumulative cost accounting across tool rounds and TTS chunks (USD only)
- variant resolver: routing tags (:online/:nitro/:floor/:exacto) stripped
  for catalog lookup; never fabricate media or free variants
- free filter now excludes paid media models whose token prices are 0/0
- auth validation on /key with 60s cache; public discovery with personal
  keys; icon registry via /api/frontend/v1/all-providers
- TTS: single-flight, Seed Audio limits (3000 chars, speed 0.5-2.0),
  cumulative usage aggregation, credit prefetch on cache hits
- video: reject before submit under ZDR/deny-data; terminal states
  failed/cancelled/expired surfaced with job id
- tests: test_alignment.py (39 regressions), updated test_pipe.py, CI
- docs: audit report, alignment plan, README/TESTING/CONTRIBUTING/CHANGELOG
- e2e_live.py: budget-guarded paid live checks (chat/tool loop, TTS, video)

* fix: use keyed per-process fingerprints for credential cache identity

CodeQL flagged plain SHA-256 over the bearer token where it is used as a
cache/scope key (py/hashing-sensitive-data). Those digests identify cache
entries, they are never stored or used for authentication, but a keyed
construction is strictly better: HMAC-SHA256 with a per-process random
secret makes the fingerprints non-comparable across restarts.

- add Pipe._credential_fingerprint (HMAC, os.urandom(32) per-process key)
- use it for: models-list cache key, /key validation cache, credit cache
  (read+write), TTS single-flight scope and TTS cache key
- update test_pipe.py helper that primes the credit cache directly

* fix: address review findings and CodeQL on credential fingerprints

- credential fingerprint: keyed blake2b instead of HMAC-SHA256
- skip /key validation on custom OPENROUTER_BASE_URL
- merge unindexed streamed reasoning fragments, split distinct records
- e2e_live: import time, read nested /credits data, pre-call budget checkpoint

* fix: replace credential hashing with opaque per-process tokens (CodeQL)

* fix: keep a trailing reasoning signature with the block it signs (#26)

Read the seal off the accumulated slot rather than the incoming fragment,
so a trailing signature-only delta merges into the reasoning block it
closes instead of opening a separate record. Dropping `data` from the
seal test keeps multi-fragment encrypted payloads concatenating.

Closes the last open review thread on #25.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants