From d354c3495f8409b98765f8d9de4dea31a2305818 Mon Sep 17 00:00:00 2001 From: JJasonSun Date: Mon, 14 Sep 2026 00:35:59 +0800 Subject: [PATCH] Add WorkBuddy desktop setup recipe and 2026-09-12 thinking deviations New file: - references/workbuddy_setup.md: field-by-field WorkBuddy custom-model configuration, including the bare-array format requirement, the startup purge cycle, effort field semantics, and runtime verification. New dated deviations (live-2026-09-12-c, Windows x64, Node.js fetch): - ecnu-reasoner alias default thinking: bare alias requests activate thinking server-side without a client-side thinking parameter. - ecnu-max reasoning_effort as thinking trigger: reasoning_effort alone activates thinking on ecnu-max, contradicting the documented gating. - Unavailable reasoning effort tiers: minimal and medium return intermittent HTTP 500; xhigh is undocumented but stable. - Rapid-request 401 metadata failure: sub-second sequential requests return 401 mimicking auth failure; four-second spacing eliminates it. Updated routing tables in SKILL.md, README.md, AGENTS.md, workflows.md, examples.md, and models.md to cross-reference the new recipe and deviations. All entries follow the existing dated-evidence format and pass validate_skill.py and the full test suite (69 passed, 1 skipped). --- AGENTS.md | 1 + README.md | 1 + SKILL.md | 1 + references/examples.md | 15 ++++ references/known_deviations.md | 90 +++++++++++++++++++ references/models.md | 10 +++ references/workbuddy_setup.md | 160 +++++++++++++++++++++++++++++++++ references/workflows.md | 4 + 8 files changed, 282 insertions(+) create mode 100644 references/workbuddy_setup.md diff --git a/AGENTS.md b/AGENTS.md index ca5aeab..ac8081b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ These instructions apply to maintaining this repository, not consuming apps. - `references/api_reference.md`: official endpoint links, not copied field tables. - `references/models.md`: selection defaults and current account-source pointers. - `references/examples.md`: ECNU-specific integration recipes. +- `references/workbuddy_setup.md`: WorkBuddy desktop custom-model configuration recipe. - `references/workflows.md`: symptom-driven, opt-in diagnostics. - `references/known_deviations.md`: dated live evidence; preserve dates and scope. - `references/agent_development.md`: historical guidance cited by old observations. diff --git a/README.md b/README.md index ed3f10d..98e54c8 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ credentials should not prevent the Agent from writing or reviewing code. | Start a normal integration | [SKILL.md](SKILL.md) | | Find the relevant official endpoint contract | [Endpoint map](references/api_reference.md) | | LangChain embeddings, thinking/tool history, Anthropic SDK | [Integration recipes](references/examples.md) | +| WorkBuddy desktop custom-model setup | [WorkBuddy recipe](references/workbuddy_setup.md) | | Model choice or current account facts | [Model/account pointers](references/models.md) | | Diagnose a failure or select a live probe | [Targeted diagnosis](references/workflows.md) | | Inspect dated evidence | [Known deviations](references/known_deviations.md) | diff --git a/SKILL.md b/SKILL.md index e409eb7..a5f5202 100644 --- a/SKILL.md +++ b/SKILL.md @@ -57,6 +57,7 @@ Its full messages URL ends in `/open/api/anthropic/v1/messages`. | LangChain embeddings | [Embedding recipe](references/examples.md#langchain-embeddings) | | Anthropic SDK setup | [Anthropic recipe](references/examples.md#anthropic-sdk) | | Thinking and tool continuation | [Tool-history recipe](references/examples.md#thinking-and-tool-history) plus the linked ECNU contract | +| WorkBuddy desktop custom-model setup | [WorkBuddy recipe](references/workbuddy_setup.md) | | Model choice, prices, quotas, or deployment questions | [Model and account pointers](references/models.md) | | An error, unexpected result, or requested live check | [Targeted diagnosis](references/workflows.md); consult only the matching dated deviation | diff --git a/references/examples.md b/references/examples.md index bce77f2..8b74a73 100644 --- a/references/examples.md +++ b/references/examples.md @@ -57,6 +57,21 @@ thinking through `thinking.type`; direct `reasoning_effort` uses `low`, `high`, or `max` on `ecnu-max` and is ignored by `ecnu-plus`. Pass ECNU extensions through `extra_body` with the OpenAI SDK. Do not substitute upstream template switches. +Two observed behaviors refine this contract: + +- The alias `ecnu-reasoner` activates thinking server-side; a bare request with + no `thinking` parameter still returns non-zero reasoning tokens. Use it when + you want always-on thinking without managing the parameter. See + [alias default thinking](known_deviations.md#ecnu-reasoner-alias-default-thinking). +- On `ecnu-max`, sending `reasoning_effort` alone (without `thinking`) is + sufficient to activate thinking, contrary to the documented gating. The + `thinking` parameter remains optional. See + [effort as trigger](known_deviations.md#ecnu-max-reasoning-effort-as-thinking-trigger). + +Restrict `reasoning_effort` to `low`, `high`, `xhigh`, and `max`. The values +`minimal` and `medium` return intermittent HTTP 500; `xhigh` is undocumented +but stable. See [unavailable tiers](known_deviations.md#unavailable-reasoning-effort-tiers). + For a tool exchange, append the complete actual assistant message before the matching tool results. ECNU documents preserving `reasoning_content` for thinking-mode tool calls through subsequent user turns. Keep it only in process diff --git a/references/known_deviations.md b/references/known_deviations.md index 5713273..864ca0f 100644 --- a/references/known_deviations.md +++ b/references/known_deviations.md @@ -23,6 +23,17 @@ langchain-openai 0.3.35, and httpx 0.28.1. Requests used synthetic text, 60-second timeouts, no retries, and the same cumulative 50-credit ceiling. Only response structure, field-preservation checks, and usage were retained. +Test environment `live-2026-09-12-c` used the account owner's personal tokens +(two distinct keys), Windows x64, Node.js 24.14.1 `fetch` direct HTTP, and +Asia/Shanghai local dates 2026-09-12 through 2026-09-14, during an +owner-authorized interactive integration session rather than the smoke runner. +Reliability runs were serial with roughly four-second spacing; unspaced bursts +are called out per entry. Repeated attempts in those runs were deliberate +characterization samples, not uncertainty retries, and credit consumption was +not metered against the runner's ceiling. Only synthetic arithmetic and +vision-fixture inputs were used. Prompts, generated content, reasoning text, +and credentials were not retained. + ## Invalid bearer on model discovery - **Tested at:** 2026-08-23; reproduced 2026-09-12 @@ -328,6 +339,85 @@ estimated cumulative consumption was 3.92822 credits. Neither is verified account debit. This targeted run does not revalidate other historical cases, other SDK versions, streaming, or arbitrary framework adapters. +## ecnu-reasoner alias default thinking + +- **Tested at:** 2026-09-12 +- **Environment:** live-2026-09-12-c, Node.js `fetch` direct HTTP +- **Protocol and endpoint:** OpenAI-compatible `POST /chat/completions` +- **Documented expectation:** The alias `ecnu-reasoner` equals `ecnu-max` with `thinking: {"type": "enabled"}`; a client must send the `thinking` parameter to activate thinking on `ecnu-max` itself. +- **Observed behavior:** A bare `ecnu-reasoner` request with no `thinking` field returned `200` and a non-zero `usage.completion_tokens_details.reasoning_tokens` count (range 18-198 across samples). A matching bare `ecnu-max` control returned `reasoning_tokens: 0`. The alias activates thinking server-side without any client-side `thinking` parameter. +- **Reproduction conditions:** Send two otherwise identical minimal requests, one with `model: "ecnu-reasoner"` and one with `model: "ecnu-max"`, both omitting `thinking`; compare `usage.completion_tokens_details.reasoning_tokens`. +- **Impact:** A client that relies on sending `thinking` to detect whether reasoning is active will misclassify `ecnu-reasoner` responses. The absence of `message.reasoning_content` is not a reliable thinking indicator either (see the prior "Max thinking response fields" entry). +- **Recommended fallback:** Detect active thinking via `usage.completion_tokens_details.reasoning_tokens`, not via `message.reasoning_content` or the presence of a client-side `thinking` parameter. Treat `ecnu-reasoner` as always-thinking for routing decisions. +- **Status:** active + +## ecnu-max reasoning_effort as thinking trigger + +- **Tested at:** 2026-09-12 +- **Environment:** live-2026-09-12-c, Node.js `fetch` direct HTTP +- **Protocol and endpoint:** OpenAI-compatible `POST /chat/completions` +- **Documented expectation:** The thinking page states that `reasoning_effort` only takes effect when thinking mode is enabled via `thinking: {"type": "enabled"}`; without it, `reasoning_effort` is ignored. +- **Observed behavior:** `ecnu-max` requests with `reasoning_effort` set but no `thinking` field returned `200` with non-zero `usage.completion_tokens_details.reasoning_tokens`. Dose-response was clean across `low` (median ~117), `high` (~155), and `max` (~343). Adding `thinking: {"type": "enabled"}` alongside `reasoning_effort` did not increase the token count beyond the same-effort baseline. +- **Reproduction conditions:** Send `ecnu-max` requests with `reasoning_effort` set to `low`, `high`, and `max` but omit `thinking`; record `usage.completion_tokens_details.reasoning_tokens` for each. +- **Impact:** Clients following the documented precondition may unnecessarily send a `thinking` parameter, or may wrongly conclude thinking is off when only `reasoning_effort` is sent. The documented gating does not match the current deployment. +- **Recommended fallback:** For `ecnu-max`, sending `reasoning_effort` alone is sufficient to activate thinking; the `thinking` parameter is optional. Do not treat the documented gating as authoritative until rechecked. Keep the `thinking` parameter if an upstream SDK requires it, but do not require it for function. +- **Status:** active + +## Unavailable reasoning effort tiers + +- **Tested at:** 2026-09-12 +- **Environment:** live-2026-09-12-c, Node.js `fetch` direct HTTP +- **Protocol and endpoint:** OpenAI-compatible `POST /chat/completions` +- **Documented expectation:** The thinking page lists `low`, `high`, and `max` as the three supported `reasoning_effort` values for `ecnu-max`. +- **Observed behavior:** `minimal` returned HTTP 500 on 5 of 7 attempts; `medium` returned HTTP 500 on 7 of 9 attempts. Both occasionally succeeded but cannot be relied on. `xhigh`, although absent from the documentation, returned `200` on all attempts with a reasoning-token median (~178) between `high` and `max`. `low`, `high`, and `max` were stable across all samples. +- **Reproduction conditions:** Send `ecnu-max` requests with each of `minimal`, `medium`, `xhigh`, `low`, `high`, and `max` as `reasoning_effort`; use serial requests with roughly four-second spacing to avoid the rate-limit deviation below. +- **Impact:** A client that sends `minimal` or `medium` (for example, via an SDK default or a UI selector that does not filter values) will hit intermittent 500 errors. `xhigh` is a usable but undocumented tier. +- **Recommended fallback:** Restrict `reasoning_effort` to `low`, `high`, `xhigh`, and `max` in client-side selectors and validators. Do not send `minimal` or `medium`. If an SDK or framework injects those values, intercept and remap them before the request. Treat `xhigh` as usable but recheck before relying on it for a production default. +- **Status:** active + +## Rapid-request 401 metadata failure + +- **Tested at:** 2026-09-12 +- **Environment:** live-2026-09-12-c, Node.js `fetch` direct HTTP +- **Protocol and endpoint:** OpenAI-compatible `POST /chat/completions` and `GET /models` +- **Documented expectation:** `401` indicates an authentication failure; the official guidance only advises avoiding parallel calls. +- **Observed behavior:** Rapid sequential requests (under roughly one second apart) to either endpoint returned `401` with `{"detail": "获取第三方元数据失败"}` even though the same bearer passed spaced requests. Spacing requests by roughly four seconds eliminated the failures. The error shape is identical to the unsupported-model case above, making them indistinguishable without a working control. +- **Reproduction conditions:** Send the same valid bearer to the same documented model in a tight loop (sub-second spacing); then repeat with four-second spacing. +- **Impact:** A client may mistake rate-limiting for a credential failure and trigger key rotation or an auth stop. A retry loop without backoff can sustain the 401 and exhaust credits on failed attempts. +- **Recommended fallback:** On a `401` with this detail string, first retry once after a four-second delay before treating it as an authentication failure. Always serialize requests to ECNU; do not use parallel call patterns. Do not rotate keys based solely on this error shape without a spaced-control request. +- **Status:** active + +## Verified coverage on 2026-09-12 through 2026-09-14 + +Environment `live-2026-09-12-c` made characterization requests across two +personal tokens on Windows x64, Node.js 24.14.1, over Asia/Shanghai dates +2026-09-12 through 2026-09-14. Requests were serial with roughly four-second +spacing except where unspaced bursts are noted. No credit metering was applied; +this was an owner-authorized interactive session, not a smoke-runner batch. + +Successful coverage included: + +- Both `ecnu-max` and `ecnu-reasoner` returned `200` for `reasoning_effort` + values `low`, `high`, `xhigh`, and `max`, with a clean dose-response curve + (median reasoning tokens: low ~117, high ~155, xhigh ~178, max ~343). +- `ecnu-reasoner` bare requests returned non-zero reasoning tokens; `ecnu-max` + bare requests returned zero. The alias activates thinking server-side. +- `ecnu-max` with `reasoning_effort` but no `thinking` parameter activated + thinking, contradicting the documented gating. +- `minimal` and `medium` efforts returned intermittent HTTP 500 and are + unreliable; `xhigh` is undocumented but stable. +- Both models correctly recognized a synthetic 96x96 red-circle PNG and + returned the expected "circle red" description, with and without thinking. +- Function calling with thinking coexisted normally on both models; a + two-turn tool exchange without preserved `reasoning_content` returned `200`. +- Rapid sub-second request bursts returned `401` + `{"detail": "获取第三方元数据失败"}`; four-second spacing eliminated it. + +No SDK, streaming, long-context, embedding, rerank, TTS, or image-generation +revalidation was performed in this run. The dose-response medians are +characterization samples, not quality benchmarks. Older observations retain +their original dates and statuses. + ## Update rules 1. Record the date, non-secret environment, protocol, endpoint, expectation, diff --git a/references/models.md b/references/models.md index 04b0579..d0238f5 100644 --- a/references/models.md +++ b/references/models.md @@ -27,6 +27,16 @@ Upstream model cards can explain model design, but do not establish ECNU's request fields, thinking defaults, context units, output limits, performance, or deployment path. Do not copy upstream serving flags into requests. +For thinking mode, the alias `ecnu-reasoner` activates thinking server-side +without a client-side `thinking` parameter +([observed](known_deviations.md#ecnu-reasoner-alias-default-thinking)). +On `ecnu-max`, `reasoning_effort` alone triggers thinking, contrary to the +documented gating +([observed](known_deviations.md#ecnu-max-reasoning-effort-as-thinking-trigger)). +Restrict `reasoning_effort` to `low`, `high`, `xhigh`, and `max`; `minimal` +and `medium` are unreliable +([observed](known_deviations.md#unavailable-reasoning-effort-tiers)). + For a cost calculation, fetch current prices and show the input/output and cache assumptions. Do not assume a cache-hit ratio or treat estimated usage as verified account debit. A code-only task does not require a cost calculation. diff --git a/references/workbuddy_setup.md b/references/workbuddy_setup.md new file mode 100644 index 0000000..c9671f2 --- /dev/null +++ b/references/workbuddy_setup.md @@ -0,0 +1,160 @@ +# WorkBuddy desktop integration + +Connect the ECNU Open Platform to the WorkBuddy desktop application (Tencent's +AI office workbench) as a custom OpenAI-compatible model source. This recipe +records the field-by-field configuration that passes WorkBuddy's validation and +survives its startup model-purge cycle. It is not a general Agent skill or a +WorkBuddy product guide. + +## Configuration file + +WorkBuddy reads custom models from `%USERPROFILE%\.workbuddy\models.json` on +Windows (or `~/.workbuddy/models.json` on macOS/Linux). The path can be +redirected via the `WORKBUDDY_CONFIG_DIR` environment variable. + +The file **must be a top-level JSON array** of model objects, not the +`{"models": [...]}` object shape shown in the bundled CodeBuddy CLI +documentation. Two consumers share this file: + +- The CLI custom-models provider reads it via `extractConfig`, which accepts + either shape. +- The desktop local-model service reads it via `ModelsJsonStorage.update`, + which treats the file as an array: `Array.isArray(current) ? [...current] : []`. + On hardware-gate failure (CPU/GPU not in the whitelist), it rewrites the file + unconditionally. An object-shaped file is collapsed to `[]` on every startup. + +A bare array satisfies both: `extractConfig` returns `{models: parsed}` for an +array, and the purge's `filter(m => m?.local !== true)` preserves entries +without a `local` field. The `availableModels` field cannot be expressed in +array form; omit it to show all models. + +Do not save from WorkBuddy's Settings → Custom Models editor: its save path +uses `parseModelsJson`, which does not accept arrays and will overwrite the +file with an object shape, which is then purged to `[]` on the next restart. +Edit the JSON file directly. + +## Minimal working entry + +```json +[ + { + "id": "ecnu-reasoner", + "name": "ECNU Max (thinking)", + "vendor": "ECNU", + "url": "https://chat.ecnu.edu.cn/open/api/v1/chat/completions", + "apiKey": "sk-REDACTED", + "maxInputTokens": 1000000, + "maxOutputTokens": 65536, + "supportsToolCall": true, + "supportsImages": true, + "supportsReasoning": true, + "reasoning": { + "defaultEffort": "max", + "supportedEfforts": ["low", "high", "xhigh", "max"], + "canDisableThinking": false + }, + "relatedModels": { + "lite": "ecnu-max", + "reasoning": "ecnu-reasoner" + }, + "onlyReasoning": true + }, + { + "id": "ecnu-max", + "name": "ECNU Max", + "vendor": "ECNU", + "url": "https://chat.ecnu.edu.cn/open/api/v1/chat/completions", + "apiKey": "sk-REDACTED", + "maxInputTokens": 1000000, + "maxOutputTokens": 65536, + "supportsToolCall": true, + "supportsImages": true, + "supportsReasoning": true, + "reasoning": { + "defaultEffort": "max", + "supportedEfforts": ["low", "high", "xhigh", "max"], + "canDisableThinking": false + }, + "relatedModels": { + "lite": "ecnu-max", + "reasoning": "ecnu-reasoner" + }, + "onlyReasoning": true + } +] +``` + +Never commit a real key. Store it in the file locally or reference an +environment variable via `"apiKey": "${ECNU_API_KEY}"`. + +## Field notes + +| Field | Value | Notes | +|---|---|---| +| `id` | `ecnu-max` / `ecnu-reasoner` | The id is also the `model` field sent to the API; do not change it to an upstream name like `deepseek-v4-flash`—ECNU rejects unknown ids with `{"detail":"获取第三方元数据失败"}`. | +| `url` | `…/v1/chat/completions` | Must end with `/chat/completions`. WorkBuddy auto-appends it unless `useCustomProtocol: true` is set. | +| `vendor` | `ECNU` or `Custom` | Cosmetic; does not affect routing. | +| `supportsReasoning` | `true` | Required for the effort selector and `/effort` command to appear. | +| `reasoning.defaultEffort` | `max` | The effort sent when no session or global override is set. | +| `reasoning.supportedEfforts` | `["low","high","xhigh","max"]` | Filters the UI selector and the `thinkingLevelMap`. Do not include `minimal` or `medium`—they return intermittent 500 ([observed](known_deviations.md#unavailable-reasoning-effort-tiers)). | +| `reasoning.canDisableThinking` | `false` | Both ECNU thinking models cannot disable thinking; this hides the "Off" entry in the selector. | +| `onlyReasoning` | `true` | Hides the "disable thinking" entry; matches the server-side behavior of `ecnu-reasoner`. | +| `relatedModels.lite` | `ecnu-max` | Model used for background tasks (summaries, titles, Explore subagent). Point it to a non-thinking model to avoid wasting reasoning tokens on low-value work. | +| `relatedModels.reasoning` | `ecnu-reasoner` | Model used when WorkBuddy internally needs a reasoning model. | +| `temperature` | omit | ECNU documents that sampling parameters may be ignored under thinking mode. Omitting it lets the server use its default. | +| `useCustomProtocol` | `false` (or omit) | When `true`, WorkBuddy uses the URL as-is without appending `/chat/completions`. Leave `false` for ECNU. | + +## How effort reaches the API + +WorkBuddy resolves the effort value in this priority order: + +1. Session override (UI "Deep Thinking" selector or `/effort ` command) +2. Global `reasoningEffort` setting (persisted by `/effort`) +3. Model entry `reasoning.defaultEffort` +4. Model entry `reasoning.effort` +5. Fallback `high` if thinking is enabled and no effort is found + +The value is passed through `withSupportedEffortsFallback`, which builds an +identity `thinkingLevelMap` from `supportedEfforts` (each value maps to itself). +Values not in the map are passed through unchanged—this is why `minimal` and +`medium` still reach ECNU and fail. + +The `/effort` command accepts all six WorkBuddy tiers +(`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) without filtering by +`supportedEfforts`. Only the UI selector respects the filter. Avoid +`/effort minimal` and `/effort medium`; they bypass the filter and trigger 500. + +## Why `thinking` is not in the request + +WorkBuddy's `thinking-format-translator` rule is the only code path that +injects `thinking: {"type": "enabled"}` into the request body. It triggers only +when the model is in WorkBuddy's built-in capability catalog (a models.dev +snapshot bundled in `codebuddy.js`, ~1061 entries) and has a `thinkingFormat` +value. `ecnu-max` and `ecnu-reasoner` are not in that catalog, so WorkBuddy +never sends the `thinking` parameter. + +This is harmless for ECNU: `ecnu-reasoner` activates thinking server-side +regardless, and `ecnu-max` activates it via `reasoning_effort` alone +([observed](known_deviations.md#ecnu-max-reasoning-effort-as-thinking-trigger)). +The `supportsReasoning` / `supportedEfforts` / `defaultEffort` fields control +which effort value WorkBuddy sends, and the effort value itself is the trigger. + +## Verification + +After editing `models.json`, WorkBuddy hot-reloads within ~1 second. Check the +daemon log for `Loaded custom models config from user: …models.json` with no +error. The model selector should show both entries with a `custom` tag. + +To confirm thinking is active at runtime, inspect the response's +`usage.completion_tokens_details.reasoning_tokens`. Do not rely on +`message.reasoning_content`—it is frequently `null` even when thinking is on +([observed](known_deviations.md#max-thinking-response-fields)). + +## ECNU rate-limiting under rapid requests + +Sub-second sequential requests to ECNU can return `401` with +`{"detail": "获取第三方元数据失败"}`, mimicking an authentication failure. +Space requests by roughly four seconds to avoid it +([observed](known_deviations.md#rapid-request-401-metadata-failure)). WorkBuddy's +internal request loop serializes calls, so this mainly affects manual debugging +or custom scripts. diff --git a/references/workflows.md b/references/workflows.md index d34e35f..1c796d1 100644 --- a/references/workflows.md +++ b/references/workflows.md @@ -18,8 +18,12 @@ These links are dated observations, not claims that the issue still reproduces. | Missing auth produces an unexpected error | Preserve actual status and body type: [missing authorization](known_deviations.md#missing-authorization-on-model-discovery) | | A new model ID appears but fails | Discovery is not endpoint capability: [runtime-only model](known_deviations.md#undocumented-model-visible-at-runtime) | | `401` only for an unsupported model or `[1m]` | Compare a documented working control; do not rotate keys blindly: [unsupported model](known_deviations.md#unsupported-chat-model-error), [suffix conditions](known_deviations.md#anthropic-long-context-suffix-metadata) | +| `401` with `获取第三方元数据失败` under rapid requests | Rate-limit, not auth failure; space requests ~4s and retry once: [rapid-request 401](known_deviations.md#rapid-request-401-metadata-failure) | | SDK works for chat but embeddings fail | Check raw strings and local validation: [embedding recipe](examples.md#langchain-embeddings) | | Tool continuation loses state or reasoning fields | Preserve the actual message and inspect serialization: [recipe](examples.md#thinking-and-tool-history), [field variation](known_deviations.md#max-thinking-response-fields) | +| `reasoning_content` is absent but thinking seems active | Check `usage.completion_tokens_details.reasoning_tokens`, not `message.reasoning_content`: [field variation](known_deviations.md#max-thinking-response-fields), [alias default thinking](known_deviations.md#ecnu-reasoner-alias-default-thinking) | +| `ecnu-max` thinking won't activate without `thinking` parameter | `reasoning_effort` alone triggers it; the documented gating is not enforced: [effort as trigger](known_deviations.md#ecnu-max-reasoning-effort-as-thinking-trigger) | +| `minimal` or `medium` effort returns intermittent 500 | Restrict to `low`/`high`/`xhigh`/`max`; remap or filter unsupported tiers: [unavailable tiers](known_deviations.md#unavailable-reasoning-effort-tiers) | | TTS error parsing crashes | Tolerate non-JSON errors: [invalid voice](known_deviations.md#invalid-tts-voice-error-shape) | | PCM bytes arrive without format metadata | Configure the format explicitly: [missing headers](known_deviations.md#successful-tts-response-headers) | | Historical max-vision limitation conflicts with current docs | Check the chosen protocol and current contract: [resolved fixture](known_deviations.md#direct-ecnu-max-image-input) |