Skip to content

fix: restore streaming usage for openai-compatible providers#50045

Closed
xiwuqi wants to merge 8 commits into
openclaw:mainfrom
xiwuqi:wuxi/fix-custom-openai-streaming-usage
Closed

fix: restore streaming usage for openai-compatible providers#50045
xiwuqi wants to merge 8 commits into
openclaw:mainfrom
xiwuqi:wuxi/fix-custom-openai-streaming-usage

Conversation

@xiwuqi

@xiwuqi xiwuqi commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: non-native openai-completions models were normalized with compat.supportsUsageInStreaming = false, so OpenClaw stopped sending stream_options: { include_usage: true } for OpenAI-compatible streaming requests.
  • Why it matters: custom and proxy-backed OpenAI-compatible providers then report zero token usage in streamed runs, which breaks session token accounting and usage dashboards.
  • What changed: normalizeModelCompat() now keeps streaming usage enabled by default for non-native openai-completions models, while still forcing developer role and JSON strict mode off unless explicitly opted in.
  • What did NOT change (scope boundary): native api.openai.com behavior is unchanged, and providers/models that explicitly set compat.supportsUsageInStreaming: false still opt out.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

OpenAI-compatible providers that stream token usage in a final usage-only chunk now keep nonzero usage accounting by default, without requiring users to manually set compat.supportsUsageInStreaming: true.

Security Impact (required)

  • New permissions/capabilities? (Yes/No): No
  • Secrets/tokens handling changed? (Yes/No): No
  • New/changed network calls? (Yes/No): No
  • Command/tool execution surface changed? (Yes/No): No
  • Data access scope changed? (Yes/No): No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: Windows 11
  • Runtime/container: Node v20.19.0 / pnpm v10.23.0
  • Model/provider: OpenAI-compatible custom provider (openai-completions)
  • Integration/channel (if any): embedded/model streaming usage accounting
  • Relevant config (redacted): custom provider with non-native baseUrl, no explicit compat.supportsUsageInStreaming

Steps

  1. Configure a non-native openai-completions provider (custom/proxy/OpenAI-compatible).
  2. Run a streaming request against that provider without explicitly setting compat.supportsUsageInStreaming.
  3. Inspect the request payload and final recorded usage.

Expected

  • The streaming request includes stream_options: { include_usage: true }.
  • The final usage-only SSE chunk is reflected in recorded token usage.

Actual

  • Before this change, OpenClaw normalized non-native endpoints to supportsUsageInStreaming: false.
  • That suppressed stream_options.include_usage, so streamed runs recorded zero usage unless users explicitly opted in.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • src/agents/model-compat.test.ts now confirms generic custom OpenAI-compatible providers default to supportsUsageInStreaming: true.
    • Added a request/stream regression that exercises streamSimpleOpenAICompletions() end-to-end with a mocked SSE response and verifies both stream_options.include_usage and the final persisted usage values.
    • Confirmed explicit compat.supportsUsageInStreaming: false still stays false.
  • Edge cases checked:
    • Native api.openai.com models remain untouched.
    • supportsDeveloperRole and supportsStrictMode are still forced off for non-native endpoints when not explicitly set.
  • What you did not verify:
    • Full pnpm build is blocked in this Windows environment because canvas:a2ui:bundle shells out to bash scripts/bundle-a2ui.sh and bash is unavailable here.
    • Full pnpm check fails in this environment on pre-existing missing optional extension dependencies unrelated to this diff.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes/No): Yes
  • Config/env changes? (Yes/No): No
  • Migration needed? (Yes/No): No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this commit, or set compat.supportsUsageInStreaming: false on the affected provider/model.
  • Files/config to restore: src/agents/model-compat.ts, src/agents/model-compat.test.ts
  • Known bad symptoms reviewers should watch for: an OpenAI-compatible backend that rejects stream_options.include_usage would start erroring unless it already has (or is given) an explicit opt-out.

Risks and Mitigations

  • Risk: some non-native OpenAI-compatible providers may still reject stream_options: { include_usage: true }.
    • Mitigation: explicit compat.supportsUsageInStreaming: false remains supported and is preserved exactly as before.

AI Assistance

This change was prepared with AI assistance via Codex, then reviewed, validated, and submitted by the author.

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 18, 2026
@greptile-apps

greptile-apps Bot commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where non-native openai-completions providers (custom/proxy endpoints) had supportsUsageInStreaming force-defaulted to false by normalizeModelCompat(), suppressing stream_options: { include_usage: true } in streaming requests and causing zero token-usage to be recorded for those sessions.

The fix is a single two-line change in model-compat.ts that flips the default to true, leaving the explicit opt-out (compat.supportsUsageInStreaming: false) — already used by Venice and Chutes models — fully intact. The test suite is updated to match and a new end-to-end integration test mocks globalThis.fetch to verify both that the outgoing request carries stream_options.include_usage: true and that the final usage-only SSE chunk is reflected in the recorded message usage.

Key points:

  • Native api.openai.com models are not affected (the needsForce guard short-circuits before reaching the changed lines).
  • Providers with an explicit compat.supportsUsageInStreaming: false (Venice, Chutes) are not affected — the hasStreamingUsageOverride guard preserves their value unchanged.
  • Backward-compatibility risk: any non-native provider that rejects stream_options: { include_usage: true } will start receiving that field. The PR acknowledges this and provides an explicit opt-out, but operators of such deployments will need to set the opt-out flag manually.
  • The decodeBodyText helper in the test does not handle a ReadableStream body; if pi-ai ever switches to a streaming request body the captured requestBody in the integration test would silently be empty. This is not a problem with current pi-ai behaviour but is worth noting for future maintenance.

Confidence Score: 4/5

  • Safe to merge; introduces a well-guarded default change with comprehensive tests and a clear opt-out path.
  • The implementation is correct and narrowly scoped. All existing explicit overrides are preserved, native OpenAI endpoints are untouched, and the new integration test validates the full request/response path. The one point of caution is that non-native providers which silently reject stream_options: { include_usage: true } will start receiving that parameter, which could surface errors in deployments that previously worked (albeit with zero usage). The explicit opt-out mechanism mitigates this, but it requires operator awareness. No security surface is changed.
  • No files require special attention beyond the acknowledged backward-compatibility note about providers that reject stream_options.

Last reviewed commit: "fix: restore streami..."

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dc136c8f6

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/model-compat.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3880391f5b

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/model-compat.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b8a42a4a66

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/agents/model-compat.ts Outdated
@zhuqingxun

Copy link
Copy Markdown

+1 for merging this. We're hitting this exact issue with Bailian (DashScope) providers on OpenClaw 2026.3.13.

Confirmed in production:

  1. Bailian API correctly returns usage in the final SSE chunk (verified via direct curl)
  2. Setting compat.supportsUsageInStreaming: true in model config has no effect because normalizeModelCompat() overwrites it to false
  3. Long-running agent sessions bloat indefinitely — compaction never triggers — and eventually get force-reset, losing all conversation history

The default-to-true approach with explicit opt-out for known-broken providers is the right call. The backward-compatibility risk is minimal — providers that reject stream_options will surface a clear error rather than silently breaking compaction.

Suggestion: consider adding a log warning when stream_options.include_usage is sent but the final chunk contains no usage data — this would help users identify providers that need the explicit opt-out.

This PR + #50845 together would fully resolve the compaction failure chain for non-native providers. Would love to see both merged.

@jalehman jalehman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Changes Requested

Thank you for this contribution! We've reviewed this PR and have some feedback before it can move forward.

Generic default-on usage streaming is still too broad

This change flips supportsUsageInStreaming back on for generic non-native openai-completions providers by default, but that is the same broad behavior we previously had to revert after several OpenAI-compatible backends rejected stream_options with 400/422 responses. As written, the new carveouts are still narrower than the set of providers and base URL patterns that were already known to fail.

Please keep the default conservative for those generic backends, or expand the carveouts and regression coverage so the previously broken cases are explicitly protected before broadening the default again. In particular, we need confidence that LM Studio, LocalAI, TGI, Ollama /v1, Mistral API, and any similar known-bad paths will not start sending include_usage again.


Once these are addressed, we'll re-review. Feel free to ask questions if anything is unclear.

@xiwuqi

xiwuqi commented Mar 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, this makes sense.

I checked the current upstream/main behavior again before changing anything else. The remaining effect of this PR is the generic default-on broadening for non-native openai-completions backends, and I agree that I do not have enough compatibility coverage to justify that blast radius yet.

Given that, I am not going to keep pushing the default wider in this PR. If it makes sense later, I can revisit this as a narrower allowlist/provider-specific change with explicit regression coverage for the known-bad paths you called out.

@xiwuqi

xiwuqi commented Mar 22, 2026

Copy link
Copy Markdown
Contributor Author

I've pushed a narrower follow-up on top of this PR.

What changed:

  • Reverted the generic non-native default back to conservative false
  • Kept auto-enable only for allowlisted endpoints with evidence in this thread / issue:
    • Scaleway AI compatible endpoints
    • DashScope compatible-mode endpoints
    • modern Azure OpenAI API versions
  • Added regression coverage to keep supportsUsageInStreaming off for the known-bad paths you called out:
    • LM Studio
    • LocalAI
    • TGI
    • Ollama /v1
    • Mistral API

Validation I reran locally:

  • vitest src/agents/model-compat.test.ts
  • vitest src/agents/models-config.providers.modelstudio.test.ts
  • targeted moonshot non-regression: opts native Moonshot baseUrls into streaming usage only after the final compat pass

If this direction looks right, I'm happy to keep iterating from here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d3d175dad

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +112 to +115
const provider = normalizeProviderId(model.provider);
if (provider === "azure-openai") {
return !usesLegacyAzureChatCompletionsApiVersion(model.baseUrl);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Detect Azure custom endpoints by host, not only provider ID

Modern Azure OpenAI endpoints added through the supported custom-provider flow still won't pick up this compat default. src/commands/onboard-custom.ts:576-583 stores Azure URLs under a generated provider ID derived from the endpoint host, not the literal azure-openai, but shouldEnableStreamingUsageByDefault() only takes the Azure branch when normalizeProviderId(model.provider) === "azure-openai". Those onboarded Azure configs therefore fall through to the generic false path and continue dropping streamed usage unless the user manually adds compat.supportsUsageInStreaming: true.

Useful? React with 👍 / 👎.

@xiwuqi
xiwuqi requested a review from jalehman March 23, 2026 04:26
@jalehman
jalehman force-pushed the wuxi/fix-custom-openai-streaming-usage branch from 7d3d175 to 43234da Compare March 23, 2026 19:18
@openclaw-barnacle openclaw-barnacle Bot added the cli CLI command changes label Mar 23, 2026
@xiwuqi

xiwuqi commented Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Closing this one to stop iterating on a stale/conflicting branch. The current branch is far behind main and still carries the older broadening direction that maintainers asked to keep conservative. If I revisit this issue, I’ll do it as a fresh, narrower PR from current main with provider/endpoint-specific coverage instead of continuing to patch this branch.

@xiwuqi xiwuqi closed this Mar 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling cli CLI command changes size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenAI-compatible custom providers lose streaming usage data, causing zero session tokens and unknown dashboard usage

3 participants