Skip to content

feat: openai-compatible-strict-reasoning (2/2) - #34

Closed
myk1yt wants to merge 53 commits into
pr/b05a-strict-reasoning-v2from
pr/b17-provider-cost-v2
Closed

feat: openai-compatible-strict-reasoning (2/2)#34
myk1yt wants to merge 53 commits into
pr/b05a-strict-reasoning-v2from
pr/b17-provider-cost-v2

Conversation

@myk1yt

@myk1yt myk1yt commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Stack Position

Description

Full Feature Description

  • Feature Branch: feat/openai-compatible-strict-reasoning
  • Feature Name: OpenAI-Compatible Strict Reasoning and Provider Cost
  • Purpose: Resolves the problem where, despite differing levels of strict JSON schema and reasoning effort support across OpenAI-compatible endpoints, request control is scattered per provider, and cost is calculated as zero or inaccurately due to usage field differences. Enables users to explicitly opt in to strict tool schema and extended reasoning effort on supported endpoints, and normalizes response usage according to provider contracts to consistently display actual cost.
  • Full Change Description: B05a adds provider settings contracts, cached settings UI, strict schema opt-in, reasoning effort values, and base request shaping. B17 normalizes conditional fields, cached tokens, missing/zero values, and price lookup for OpenAI, OpenAI-compatible, Anthropic Vertex, and Qwen family usage. B05a is also shared as the foundation for the MiMo chain, but in this chain B17 is a sibling outcome that does not depend on B12.
  • Impact Scope: Affects provider-settings.ts, base-openai-compatible-provider.ts, base-provider.ts, OpenAICompatible.tsx, openai.ts, openai-compatible.ts, anthropic-vertex.ts, qwen-code.ts.
  • Errors and Edge Cases: Strict mode is opt-in and does not change existing schemas when disabled. Strict/reasoning fields are not sent to unsupported providers. MCP schemas are not aggressively hardened. The settings UI only modifies cachedState before saving. Cost calculation treats missing fields as unknown or zero per provider contract and does not produce negative tokens. Cached input/output tokens and provider-specific price units are not double-counted. B17 does not change request payload or tool-call policy.
  • Testing Method: Run B05a's settings serialization, schema conversion, opt-in/omitted-field, request fixture, and UI/locale tests, and B17's provider-specific usage/cost fixtures. Manually compare requests with strict toggle off vs. on, and feed fixed usage fixtures into OpenAI-compatible and non-OpenAI providers, comparing against documented calculation formulas and costs.

Why Split Into 17 PRs

Instead of submitting this feature as a single unified PR, it was split into individual PRs because as code size grows, safely reviewing a PR becomes very difficult. The feature was broken into mutually exclusive individual PRs so that each can be reviewed independently.

What This PR Specifically Changes

Normalizes OpenAI/OpenAI-compatible/Anthropic Vertex/Qwen usage fields and cached tokens, and calculates cost according to provider price lookup. Does not change request payload, strict UI, or MiMo tool policy.

Included Files

  • src/api/providers/openai.ts
  • src/api/providers/openai-compatible.ts
  • src/api/providers/anthropic-vertex.ts
  • src/api/providers/qwen-code.ts
  • Direct usage/cost tests for each provider

Exclusion Scope

  • Request execution method changes
  • MiMo tool-call policy
  • B05a strict-reasoning settings/UI
  • Unrelated provider fixture changes
  • All items in the common removal rules

@myk1yt myk1yt changed the title B17: Provider Cost v2 feat: openai-compatible-strict-reasoning (2/2) Aug 3, 2026
@myk1yt
myk1yt force-pushed the pr/b17-provider-cost-v2 branch from ca96c3c to 7f5fa9e Compare August 4, 2026 06:11
@myk1yt
myk1yt force-pushed the pr/b05a-strict-reasoning-v2 branch from 658c4f2 to 4692197 Compare August 4, 2026 11:34
@myk1yt
myk1yt force-pushed the pr/b17-provider-cost-v2 branch from 7f5fa9e to 42df2fa Compare August 4, 2026 11:40
@myk1yt
myk1yt force-pushed the pr/b05a-strict-reasoning-v2 branch 3 times, most recently from 77e7207 to a3b22a7 Compare August 4, 2026 20:40
@myk1yt
myk1yt force-pushed the pr/b17-provider-cost-v2 branch 5 times, most recently from 98dd23f to 9d2c50a Compare August 6, 2026 07:28
Zoo (VP) and others added 18 commits August 7, 2026 04:11
# Conflicts:
#	src/core/tools/error-interception/StructuralValidator.ts
# Conflicts:
#	src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts
#	src/core/assistant-message/__tests__/presentAssistantMessage-parser-dedup.integration.spec.ts
#	src/core/assistant-message/presentAssistantMessage.ts
# Conflicts:
#	src/core/assistant-message/__tests__/presentAssistantMessage-parser-dedup.integration.spec.ts
MimoHandler was passing raw tool schemas to the API without the
strict mode conversion that all other OpenAI-compatible providers use.
This caused tool call errors due to missing required/strict fields.

- Call this.convertToolsForOpenAI(tools) instead of raw assignment
- Adds strict: true, required properties, additionalProperties: false
An id-less argument-continuation chunk belongs to the most recent id chunk
seen at its index. When a provider reuses index 0 with a NEW id (a disguised
second parallel call), the new call's id chunk was dropped but its id-less
argument fragments were still kept and concatenated into the FIRST call's
accumulator, corrupting its JSON.

Track dropped indexes in filterToFirstToolCall state and drop subsequent
id-less fragments for those indexes. Also rewrite the function docblock,
which referenced a non-existent error-interception retry loop.
The parseErrors/parseFailures docblocks claimed presentAssistantMessage
routes recorded failures to an INVALID_JSON_ARGUMENTS error-interception
pattern. No such routing exists on this codebase; describe the actual
lifecycle (consumed via the consume* APIs, cleared on new API request).
Comment-only change, no behavior difference.
parseErrors/parseFailures static maps accumulated an entry per malformed
tool call and were never cleared in production (the consume* APIs have no
production callers), slowly leaking for the extension-host lifetime.

Add NativeToolCallParser.clearParseFailures() and call it in
Task.recursivelyMakeClineRequests alongside clearAllStreamingToolCalls()/
clearRawChunkState(), where other per-stream state is reset. The consume*
APIs keep working for tests.
MiMo sends tools through convertToolsForOpenAI(), which attaches a strict
flag to every function tool. An OpenAI-compatible endpoint that doesn't
support structured outputs rejects the request with a 400 and the turn
fails outright.

Mirror the existing parallel_tool_calls fallback: detect schema-rejection
errors narrowly (400 status plus a mention of strict/additionalProperties
in a tools context, so unrelated 400s like MiMo's missing-reasoning_content
rejection are not retried) and retry once with the original schemas and no
strict flag.
@myk1yt myk1yt closed this Aug 7, 2026
@myk1yt
myk1yt deleted the pr/b17-provider-cost-v2 branch August 7, 2026 13:06
@myk1yt
myk1yt restored the pr/b17-provider-cost-v2 branch August 7, 2026 13:32
@myk1yt myk1yt reopened this Aug 8, 2026
Zoo (VP) and others added 21 commits August 8, 2026 14:50
…1132)

CI failure: E2E Tests (Mocked) failed with '404 No fixture matched' because
provider-cost.test.ts calls startNewTask with probe tag 'provider-cost-e2e'
but no fixture existed.
…ames (Zoo-Code-Org#1073)

* fix(telemetry): record tool usage once centrally, sanitize raw tool names

* fix(telemetry): defer native MCP usage recording until validation passes

* fix(telemetry): narrow UseMcpToolTool callback, harden test mocks, close coverage gaps

* test(telemetry): complete native MCP mock so validateToolExists runs the real path
Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Roomote <roomote@roomote.dev>
…sages load (Zoo-Code-Org#1181)

* fix(task): skip saveClineMessages when history task aborts before messages load

* test: strengthen resume-eviction-race assertions and type mock provider

* test: add fallback mock for second getSavedClineMessages read in resume-eviction spec

* fix(task): prevent saving unhydrated history messages during abort

---------

Co-authored-by: Naved  Merchant <naved.merchant@gmail.com>
…rg#1141)

* refactor(webview): complete provider identifier migration

* test(webview): type selected model hook mocks

* test(webview): keep selected model spec out of provider migration

* test(webview): harden provider identifier migration tests

---------

Co-authored-by: Elliott de Launay <edelauna@gmail.com>
…de-Org#1193)

* chore(deps): update dependency mermaid to v11.16.1 [security]

* fix(webview): harden mermaid securityLevel and tighten dev-mode CSP

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Elliott de Launay <edelauna@gmail.com>
…-Org#1161)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…rg#1146)

* refactor(webview): canonicalize ApiOptions provider identifiers

* test(webview): strengthen ApiOptions interaction coverage

* fix(webview): use providerIdentifiers.openrouter in ApiOptions option sort

---------

Co-authored-by: Elliott de Launay <edelauna@gmail.com>
@myk1yt

myk1yt commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

Closing to recreate with main as target base branch. This PR had stale base branch references after fork sync.

@myk1yt myk1yt closed this Aug 10, 2026
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.

3 participants