Skip to content

Share the stream-required discovery across OpenAiModel instances by endpoint - #98

Open
YellowSnnowmann wants to merge 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/stream-required-latch-shared-per-endpoint
Open

Share the stream-required discovery across OpenAiModel instances by endpoint#98
YellowSnnowmann wants to merge 4 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/stream-required-latch-shared-per-endpoint

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Hosts build a fresh OpenAiModel per workload (chat, summariser, titler, …), each pointed at the same OpenAI-compatible endpoint. The stream-required latch added for a streaming-only proxy was per-instance, so every new instance re-discovered the constraint and paid another guaranteed-400 ({"detail":"Stream must be set to true"}) round trip — the 400 kept reappearing on every workload, every turn, even after one instance had already learned better.

This shares the discovery process-wide, keyed by base_url (the constraint is a property of the endpoint/proxy, not of any one instance or model id):

  • requires_streaming() now adopts a sibling's discovery and caches it locally.
  • latch_stream_required() records the endpoint once and logs once per process (rather than once per instance).
  • Explicit with_requires_streaming() stays per-instance so it can still be cleared per instance (existing with_requires_streaming(false) semantics preserved).

After the first cold probe an endpoint is never re-probed. Written only by runtime discovery; entries are never removed (a streaming-only endpoint does not stop being one within a process).

Refs openhuman#5497 (the multi-instance shape of openhuman#5165).

API Or Behavior Changes

No public API changes. Behavior change: once any OpenAiModel instance discovers an endpoint requires streaming, other instances pointed at the same base_url skip the doomed non-streaming probe. with_requires_streaming remains per-instance and clearable.

Tests

  • cargo fmt --check
  • cargo clippy --all-targets -- -D warnings
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --all-targets
  • cargo build --all-targets --all-features
  • cargo test (--lib: 1577 passed, 0 failed)
  • cargo test --all-features

New test stream_required_discovery_is_shared_across_instances_by_endpoint covers cross-instance sharing (discoverer latches → a fresh instance for the same base_url inherits it → an unrelated base_url is unaffected). The two existing latch tests were moved to unique base_urls so runtime discovery doesn't leak into the process-global registry shared with the DEFAULT_BASE_URL tests. --all-features variants not run locally (repl/rhai feature); the change is feature-independent.

Documentation

No doc changes needed — the shared registry is documented inline where it is defined (STREAM_REQUIRED_ENDPOINTS), and the existing latch_stream_required doc comment is updated to reflect endpoint-wide sharing.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling for endpoints that require streaming requests.
    • Applied detected streaming requirements consistently across models using the same endpoint.
    • Kept streaming behavior isolated between different endpoints.
    • Improved reliability when sharing endpoint streaming settings.
    • Ensured explicit streaming opt-out settings take precedence over automatically detected requirements.
    • Enhanced diagnostics by redacting credentials, query parameters, and fragments from logged endpoint URLs, including malformed values.

The per-instance stream_required latch stops a single OpenAiModel from
re-probing a streaming-only endpoint, but hosts build a fresh OpenAiModel
per workload (chat, summariser, titler, …) all aimed at the same endpoint.
So every new instance re-discovered the constraint and paid another
guaranteed-400 ("Stream must be set to true") round trip — the 400 kept
reappearing on every workload, every turn, even after one instance had
already learned better.

Record the discovery in a process-global set keyed by base_url (the
constraint is a property of the endpoint, not of any one instance or model
id). requires_streaming() now adopts a sibling's discovery and caches it
locally; latch_stream_required() records it once per endpoint and logs once
per process. Explicit with_requires_streaming() stays per-instance so it can
still be cleared. After the first cold probe the endpoint is never re-probed.

Refs openhuman#5497 (multi-instance shape of openhuman#5165).
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The OpenAI transport now shares discovered stream: true requirements across model instances by base URL. Explicit per-instance settings take precedence. The transport records each endpoint once, redacts logged URLs, handles lock failures safely, and adds focused tests.

Changes

OpenAI streaming discovery

Layer / File(s) Summary
Streaming precedence and instance state
src/harness/providers/openai/transport.rs
The transport tracks explicit streaming configuration and resolves requirements from explicit settings, instance discovery, then endpoint-wide discovery.
Process-wide streaming registry
src/harness/providers/openai/transport.rs
The transport stores streaming-required base URLs in synchronized process-wide state. It records each endpoint once, redacts logged URLs, and fails open when locking is unavailable.
Endpoint sharing and precedence validation
src/harness/providers/openai/test.rs
Tests verify endpoint isolation, URL redaction, shared discovery across model instances, and explicit streaming opt-in and opt-out precedence.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OpenAiModel
  participant OpenAiTransport
  participant StreamingRegistry
  OpenAiModel->>OpenAiTransport: Resolve streaming requirement
  OpenAiTransport->>StreamingRegistry: Read requirement by base URL
  StreamingRegistry-->>OpenAiTransport: Return shared endpoint state
  OpenAiTransport-->>OpenAiModel: Apply streaming precedence
Loading

Suggested reviewers: senamakel, m3ga-mind

Poem

A rabbit maps each streaming trail,
Shared endpoints leave a tale.
Explicit choices lead the way,
Redacted logs keep secrets at bay.
Fresh URLs hop apart.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes sharing stream-required discovery across OpenAiModel instances by endpoint.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/harness/providers/openai/transport.rs`:
- Around line 507-511: Update the logging in the stream-latching path around the
provider transport method to pass self.base_url through the existing
URL-redaction utility before assigning it to the tracing field. Preserve the
provider and model fields, and add a tracing test using a credential-bearing URL
that verifies userinfo, query parameters, and fragments are absent from the
emitted log.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d48860fc-648c-4c1d-ba06-6636f4344df0

📥 Commits

Reviewing files that changed from the base of the PR and between 27a3f39 and 41a3498.

📒 Files selected for processing (2)
  • src/harness/providers/openai/test.rs
  • src/harness/providers/openai/transport.rs

Comment thread src/harness/providers/openai/transport.rs
@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 12, 2026

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

             $0.0266 · 19,112 in / 8,112 out · 11,383 cached (60%) · z-ai/glm-5.2
critique:    $0.0114 · 5,426 in  / 4,176 out · 4,267 cached (79%)  · z-ai/glm-5.2
security:    $0.0066 · 5,382 in  / 1,101 out · 128 cached (2%)     · z-ai/glm-5.2
tests:       $0.0053 · 3,796 in  / 1,859 out · 3,202 cached (84%)  · z-ai/glm-5.2
description: $0.0034 · 4,508 in  / 976 out   · 3,786 cached (84%)  · z-ai/glm-5.2

The endpoint-wide latch discovery logs base_url. An OpenAI-compatible proxy
can carry a credential in the URL (userinfo or a query param), so logging the
raw value risks leaking a secret. Route it through redact_base_url_for_log,
which strips userinfo + query + fragment (keeping scheme://host[:port]/path)
and replaces any value that does not parse as a URL. Add a unit test covering
a credential-bearing URL, a clean URL, and an unparseable value.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/harness/providers/openai/test.rs`:
- Around line 757-772: The redaction test currently checks only the URL prefix
and selected query tokens; replace the starts_with assertion in the redacted URL
test with an exact equality assertion against the expected scheme, host, port,
and path, confirming that query and fragment components are removed.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 93bd6a3e-d5dd-420d-b93b-7aaf426479bc

📥 Commits

Reviewing files that changed from the base of the PR and between 41a3498 and ac8a78f.

📒 Files selected for processing (2)
  • src/harness/providers/openai/test.rs
  • src/harness/providers/openai/transport.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/harness/providers/openai/transport.rs

Comment thread src/harness/providers/openai/test.rs Outdated
@tinysweeper tinysweeper Bot added priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later. and removed priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. labels Aug 12, 2026
starts_with plus negative token checks did not prove the query and fragment
were removed (a URL with a different query/fragment would pass). Replace with an
exact-equality assertion against the fully redacted value, which proves userinfo,
query and fragment are all gone while scheme, host, port and path survive.

@CodeGhost21 CodeGhost21 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the branch locally on 1.96.1 (the pinned stable can't build if let guards in transport.rs): cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test --lib all clean — 1578 passed, both parallel and --test-threads=1 (worth checking the single-threaded run given the new process-global state; it's fine).

The core change is right: the constraint genuinely belongs to the endpoint, the read-then-insert in remember_endpoint_requires_streaming returns insert()'s result so the log fires exactly once even under a concurrent first probe, and with_base_url already trims the trailing slash so the key is normalised. The local write-back in requires_streaming() keeps the shared read off the hot path after the first hit.

One thing I'd change before merge — the escape hatch is gone.

The PR body says "with_requires_streaming remains per-instance and clearable", and the doc comment on requires_streaming() still says the value is "either declared up front via with_requires_streaming or learned from a provider rejection". Both are now only true until some sibling latches the same base_url:

let m = OpenAiModel::new("k").with_base_url(shared).with_requires_streaming(false);
m.requires_streaming() // -> true, if any instance ever latched `shared`

That matters because invoke has no path back: once requires_streaming() is true the unary attempt is never retried (transport.rs:2662), so a wrong shared inference is unfixable for the rest of the process. The case where that bites is an aggregating proxy (LiteLLM/OpenRouter-shaped) fronting mixed upstreams on one base_url — the chat model discovers stream-only against upstream A, and a titler model routed to upstream B that rejects stream: true gets forced onto the streaming path with no way for the host to opt out. Keying by (base_url, model) isn't the fix (it would defeat the point — your own test has the sibling on a different model id), but an explicit opt-out should win over inherited discovery. Something like a tri-state Option<bool> for the explicit setting, or an explicitly_set: AtomicBool that short-circuits the registry lookup, keeps the claim in the PR body true.

If you'd rather keep it simple and let the shared record win unconditionally, that's a defensible call for a constraint that's almost always endpoint-wide — but then please update the PR body and the requires_streaming() / with_requires_streaming() doc comments to say so, so the next reader doesn't reach for with_requires_streaming(false) as an override.

Nits:

  1. test.rs:745 — the openhuman#5497 comment block (hosts build a fresh model per workload, so discovery is shared process-wide) sits above redact_base_url_for_log_strips_credentials_and_query, but it describes stream_required_discovery_is_shared_across_instances_by_endpoint. Move it down one test.

  2. The process-global registry creates an invisible rule for the test module: no test may ever latch DEFAULT_BASE_URL, or requires_streaming_flag_skips_non_streaming_attempt (test.rs:684-699) starts failing for reasons that have nothing to do with it. The two moved tests carry a comment; a line on fn model() itself would put the warning where someone writing the next latch test will actually see it.

  3. Out of scope for this PR, but the redaction helper has other customers: base_url still reaches user-facing error strings unredacted at transport.rs:1069, transport.rs:1220 and local.rs:354-363, and those land in logs/Sentry the same way. Worth a follow-up.

Behaviour and tests look sound otherwise — the sharing test covers all three directions (discoverer latches, sibling inherits, unrelated endpoint unaffected), and the exact-match assertion in the redaction test is the right call over substring checks.

… discovery

Review of tinyhumansai#98: once discovery went process-global, with_requires_streaming(false)
stopped being a real opt-out — requires_streaming() would return true for an
instance that explicitly set false the moment any sibling latched the same
base_url, and invoke() has no path back once it is true. That bites an
aggregating proxy fronting mixed upstreams on one base_url (a titler routed to a
non-streaming upstream forced onto the streaming path).

Track whether the value was set explicitly (stream_required_explicit); when it
was, requires_streaming() returns the instance's own value and does not consult
the shared registry, so an explicit opt-out (or opt-in) always wins. Add a test
covering opt-out-beats-sibling-discovery and opt-in-needs-no-discovery, and fix
the doc comments to describe the real precedence.

Also address review nits: move the misplaced openhuman#5497 comment onto the
sharing test it describes, and warn on fn model() that latching the default base
URL leaks into other tests via the global registry.
@YellowSnnowmann

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough local verification and the escape-hatch catch — that's a real regression in the API contract. Fixed in ff12169.

Escape hatch restored (the main point). Added a stream_required_explicit flag. with_requires_streaming(...) now records that the value was set explicitly, and requires_streaming() short-circuits on it — returning the instance's own value without consulting the shared registry. So an explicit with_requires_streaming(false) stays a genuine opt-out even after a sibling latches the same base_url, which covers exactly your aggregating-proxy case (a titler routed to a non-streaming upstream keeps the unary path). Precedence is now: explicit setting → this instance's own run-time latch → sibling's endpoint-wide discovery. The requires_streaming() / with_requires_streaming() doc comments are rewritten to state that, so the PR-body claim ("per-instance and clearable") is true again rather than aspirational.

New test explicit_requires_streaming_opt_out_wins_over_sibling_discovery covers opt-out-beats-sibling-discovery and opt-in-needs-no-discovery. Verified cargo fmt --check, clippy --all-targets -D warnings, cargo test --lib (1579 passed) — both parallel and --test-threads=1.

Nit 1 — moved the openhuman#5497 comment block off the redaction test and onto stream_required_discovery_is_shared_across_instances_by_endpoint, which it actually describes.

Nit 2 — added the "no test may latch DEFAULT_BASE_URL" warning directly on fn model(), where the next latch-test author will see it (the two moved tests keep their inline note too).

Nit 3 — agreed, out of scope here: base_url still reaches user-facing error strings unredacted at transport.rs (~1069, ~1220) and local.rs (~354-363). I'll open a follow-up to route those through redact_base_url_for_log (or make it the single sink) so proxy credentials can't leak into logs/Sentry from those paths either.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/harness/providers/openai/transport.rs (1)

513-520: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Extract the endpoint registry from transport.rs.

endpoint_requires_streaming and remember_endpoint_requires_streaming define process-wide registry behavior. Move the registry state and its focused helpers into a dedicated OpenAI module. Keep OpenAiModel responsible only for precedence and latching.

As per coding guidelines, “Keep each surface in its own module directory … rather than spreading feature code across broad files. Make the module root wire the pieces together and expose the smallest useful API.”

Also applies to: 532-542

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/harness/providers/openai/transport.rs` around lines 513 - 520, Extract
the process-wide endpoint registry state and helpers endpoint_requires_streaming
and remember_endpoint_requires_streaming from transport.rs into a dedicated
OpenAI module. Update the module root to wire and expose only the minimal
registry API, while keeping OpenAiModel limited to precedence checks and
latching via stream_required.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/harness/providers/openai/transport.rs`:
- Around line 513-520: Extract the process-wide endpoint registry state and
helpers endpoint_requires_streaming and remember_endpoint_requires_streaming
from transport.rs into a dedicated OpenAI module. Update the module root to wire
and expose only the minimal registry API, while keeping OpenAiModel limited to
precedence checks and latching via stream_required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 39f26ddd-0781-4e30-af62-73aa33b6890a

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa8376 and ff12169.

📒 Files selected for processing (2)
  • src/harness/providers/openai/test.rs
  • src/harness/providers/openai/transport.rs

@CodeGhost21 CodeGhost21 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at ff12169 on 1.96.1. The escape hatch is properly restored — approving.

On the fix. stream_required_explicit is the right shape: requires_streaming() short-circuits on it before touching the registry, so with_requires_streaming(false) is authoritative again and the aggregating-proxy case (titler routed to a non-streaming upstream behind a shared base_url) keeps the unary path. Precedence 1→2→3 in the doc comment now matches the code, and the PR-body claim is true rather than aspirational. Confirmed every consumer reads through requires_streaming() (transport.rs:2659, :2693) — nothing reads the raw stream_required field and bypasses precedence — and there's no Clone impl that could silently drop the new field.

The property I most wanted to check isn't covered by a test, so I wrote one locally: an instance explicitly opted out against an endpoint that genuinely is streaming-only still latches correctly on the real 400 (latch_stream_required writes stream_required, and the explicit branch reads that same cell), and its discovery still propagates to siblings. So a wrong opt-out costs one 400 on that instance and then self-corrects — the escape hatch can't wedge a model into a permanent 400 loop. Worth folding that case in as a test if you touch this again, but not worth another round trip.

Also re-verified trailing-slash normalisation collapses to one registry key, and redact_base_url_for_log now asserts exact equality so it proves userinfo/query/fragment are all gone rather than just the sample tokens.

Nits 1 and 2 are both addressed — the openhuman#5497 block sits on the test it describes, and the DEFAULT_BASE_URL warning is on fn model() where the next author will actually hit it. Nit 3 as a follow-up is the right call.

Verification: cargo fmt --check, cargo clippy --all-targets -- -D warnings clean; cargo test --lib 1579 passed both parallel and --test-threads=1 (re-checked single-threaded given the process-global registry). The --all-features boxes left unchecked in the PR body are all covered by ci.yml (clippy, build, test, llvm-cov) and CI is green.

Non-blocking: CodeRabbit's "extract the registry into its own module" is reasonable in the abstract, but it's two ~6-line helpers next to their only caller in a file that already holds plenty of module-local free functions. Declining it here is fine; if the endpoint registry grows a second constraint, that's the moment to lift it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p2 Soon. Real but survivable — a rough edge, a gap, a thing that will bite later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants