Skip to content

feat(dte): dynamic thinking effort — full series trial (PRs 1-5 + e2e addenda merged) - #29

Open
easonLiangWorldedtech wants to merge 96 commits into
mainfrom
feat/dte-trial-all
Open

feat(dte): dynamic thinking effort — full series trial (PRs 1-5 + e2e addenda merged)#29
easonLiangWorldedtech wants to merge 96 commits into
mainfrom
feat/dte-trial-all

Conversation

@easonLiangWorldedtech

Copy link
Copy Markdown
Owner

Trial branch: all DTE (dynamic thinking effort) branches composed into ONE

This branch merges the entire DTE 5-PR series + both e2e addenda so it can be tried out as a single build. It is NOT a submission PR - the real PRs are on Zoo-Code-Org/Zoo-Code:

PR Branch Status
Zoo-Code-Org#1336 PR-1 experiment + settings + i18n feat/dte-1-experiment open, bot-clean
Zoo-Code-Org#1338 PR-2 task state + per-request metadata + adaptive envelope feat/dte-2-task-state open, bot-clean
Zoo-Code-Org#1354 PR-3 set_thinking_effort native tool (+i18n) feat/dte-3-native-tool @ 19954d3 open, 19/19 green
Zoo-Code-Org#1359 PR-4 webview UI (chip + composer toggle + in-chat display) feat/dte-4-webview-ui @ ac84f5e open, 19/19 green
Zoo-Code-Org#1355 PR-5 orchestrator new_task effort pass-through feat/dte-5-orchestrator @ 3ea9f63 open, 17/17 green
Zoo-Code-Org#1361 e2e addendum PR-3 feat/dte-3-e2e @ e83af72 draft, CI running
Zoo-Code-Org#1356 e2e addendum PR-5 feat/dte-5-e2e @ 471490b open, 17/17 green

Merge history (all additive, no rebase)

19954d3 (dte-3 head) -> merge dte-4 (3ae54d1; 17 locale i18n conflicts resolved as union: dte-3 translations + dte-4 new keys) -> merge dte-5 (5b33232; 2 conflicts in vscode-extension-host.ts resolved keep-both) -> merge dte-5-e2e (ce0e90b) -> merge dte-3-e2e (f152b98).

Verified

  • pnpm run check-types: 11/11 packages green
  • find-missing-translations.js --area=webview: no missing keys (17 locales x full thinkingEffort section parity)

How to try it

  1. Load this branch (or the built VSIX artifact) in VS Code.
  2. Settings -> Experiments: enable Dynamic thinking effort (dynamicThinkingEffort).
  3. Pick a model whose catalog entry supports per-request reasoning effort (e.g. DeepSeek / OpenRouter reasoning models); the composer Brain toggle shows the model's supported levels.
  4. UI surfaces: composer Brain toggle (per-task, source you), header chip (effort + source), in-chat notification line when effort changes (user or model).
  5. Model-driven: with the experiment on, the model gets a set_thinking_effort tool it can call mid-task (clamped to the model capability array, 3-upward-change cap, oscillation refusal).
  6. new_task: when the model requests a new task it can pass thinking_effort; validated against the child model capability (invalid values are rejected and returned to the model).

Base: main @ 78c712a (same as upstream main at branch time).

…nd adaptive effort envelope

DTE series 2/5 (part of Zoo-Code-Org#1329).

- ApiHandlerCreateMessageMetadata.reasoningEffort: per-request override channel
- resolveEffectiveReasoningEffort: single shared resolution point (override > settings > model default)
- AnthropicHandler: adaptive output_config.effort envelope in both requestParams branches (in-range only)
- Task: setRuntimeThinkingEffort/getRuntimeThinkingEffort with in-memory apiConfiguration merge/restore, per-request metadata at all four createMessage sites, dispose() reset; never persisted
DTE series 2/5 — addresses the CodeRabbit review finding on Zoo-Code-Org#1338:
when a task-local thinking-effort override is active, updateApiConfiguration()
now re-captures the incoming profile's reasoningEffort as the restore value
and re-applies the override on top of the new in-memory copy, so clearing the
override restores the NEW profile value instead of the stale one. Additive:
activation and clearing semantics are otherwise unchanged.

Adds two regression tests (override active + profile switch restores new
value; inactive updateApiConfiguration unchanged behavior).
DTE series 2/5 — addresses the CodeRabbit docstring-coverage warning on Zoo-Code-Org#1338
(33.33% < 80% across the functions touched by the diff):
- AnthropicHandler.createMessage: documents the shared effective-effort
  resolution and the adaptive output_config.effort envelope (in-range only).
- Task.dispose: documents centralized teardown incl. the transient task-local
  override reset.
- Task.updateApiConfiguration: documents the override-preservation behavior
  (re-captured restore value + re-applied override on the new in-memory copy).

Comment-only change: 30/30 patch lines and 10/10 branches unchanged;
317/317 tests and tsc --noEmit re-verified green.
Add the set_thinking_effort native tool (DTE series 3/5): the model adjusts
its own per-turn thinking effort mid-task with no approval gate.

- Guardrails: one-line chat notification (success or refusal), escalation cap
  (max 3 upward changes per task), A->B->A oscillation refusal, hard clamp to
  the model capability array (ties toward the lower level).
- Gating: dynamicThinkingEffort experiment + model supportsReasoningEffort
  (non-empty array or true), evaluated at task start so the tool list stays
  stable within a task (prompt-cache safety).
- Display: webview ChatRow one-line row (applied / oscillation / escalation
  refusal), i18n keys in all 17 locales; partial streaming updates the same
  line.
- Tests: executor (clamp/cap/oscillation/no-op/no-approval/display), parser
  (partial + complete), dispatch, gating matrix, schema wiring, ChatRow
  display.

Stacked on DTE PR-1 (experiment flag) and PR-2 (task-local runtime effort
state). Closes Zoo-Code-Org#1330.
Address PR review feedback on set_thinking_effort (DTE series 3/5):

- Executor: seed the per-task guard history with the task's effective
  baseline so returning from a changed value to the original baseline is
  refused as oscillation (A -> B -> A); existing no-op behavior preserved.
- Parser: only build nativeArgs when effort AND reason are strings; a
  non-string payload now fails at parse time and cannot reach the executor.
- Gating: a supportsReasoningEffort array that only lists 'disable' no
  longer exposes the tool (it could apply no level).
- i18n: translate the new thinkingEffort chat strings into all 17
  non-English webview locales (placeholders preserved).
- Tests: regression tests for each change plus branch-coverage for the
  previously partial lines (non-string args, 'disable'-only capability,
  baseline oscillation, partial streaming without params, description
  fallback, capability robustness). All touched patch lines are now
  fully branch-covered (codecov patch partials resolved).

CodeRabbit: Zoo-Code-Org#1354
… post-mode-switch revalidation, ask prefill normalization)
The toolCallId matcher only inspects the last message of the request, but post-tool requests now end with a fresh user env-details message, so the old thinking-effort-tool fixture could never match (aimock 404 -> 30s e2e-mock timeout). This merge brings the turnIndex-scoped aimock fixtures, the shared OpenRouter capture proxy, and the new effort-switching suite; the 5 conflicting locales are resolved as translated name/description (e2e branch) plus the F7 supportedReasoningEfforts hint (HEAD).
CodeRabbit pre-merge check on the addendum (docstring coverage 14.29% < 80%,
7 functions across 3 files): add JSDoc to the five internal proxy helpers
and firstRequestCarrying so every function touched by this diff is
self-documenting (withOpenRouterCaptureProxy was already documented).
… event race)

CI e2e-mock failed 2 !== 3 on "exactly three thinkingEffort display says":
the final display say is observed on the Message channel after the
TaskCompleted event resolved waitUntilCompleted (separate event channels,
no cross-channel ordering guarantee; under CI load the queue lags by more
than one turn). Await the expected says with a bounded settle (5s, 100ms)
before detaching the listener: a genuine shortfall still fails the same
assertion, the race no longer does.
…l-in

CodeRabbit docstring-coverage pre-merge check on the stacked diff flags the
six provider getModel() overrides this PR touches (base-openai-compatible,
friendli, openai, lm-studio, native-ollama, router-provider). Document each
with the F7 fill-in-the-gap semantic so every function introduced or touched
by this PR's own delta is self-documenting.
Follow-up to the CodeRabbit docstring-coverage pre-merge check. Document the
functions introduced or touched by this PR's stacked diff that still lacked
JSDoc:

- SetThinkingEffortTool: effortRank, getGuardState, execute, handlePartial
- filter-tools-for-mode: applyModelToolCustomization (its doc block was
  orphaned by an intervening interface; moved it directly above the
  function)
- router-provider: supportsTemperature (line re-touched by the F7 diff)

Comments only; no behavior change.
Follow-up to the 2026-08-24 CodeRabbit full review of this stacked PR. Four
major findings, three fixed, one documented as design:

1. new_task schema strict-mode violation: thinking_effort was in properties
   but not required, which the Anthropic API rejects under strict: true +
   additionalProperties: false (the whole tool definition fails). It now
   uses the same ["string", "null"] + required pattern as todos; null is the
   omitted-value sentinel the tool treats as absent (unit-tested).
2. NewTaskTool: the invalid thinking_effort path now advances the
   consecutive-mistake guardrail and records the tool error like every other
   failure path, so a model repeating an unsupported effort trips the
   mistake loop (unit-tested).
3. E2E suite teardown: the new_task suite switches the profile to the
   Anthropic provider with an ephemeral proxy base URL and sets the global
   reasoning-effort fields; the teardown now explicitly clears them
   (anthropicBaseUrl, apiModelId, enableReasoningEffort, reasoningEffort)
   so a later suite selecting the anthropic provider is not pointed at the
   closed local port and does not inherit this suite's effort baseline.
4. Task-local effort on OpenAI-compatible providers: documented in the PR
   discussion rather than changed - setRuntimeThinkingEffort rewrites the
   per-task apiConfiguration and rebuilds the API handler, so provider
   requests are built from the task-local config (the switching e2e asserts
   the wire envelope changes on the request after an applied change); the
   metadata.reasoningEffort per-request override is the PR-2 Anthropic
   channel, and this PR's design deliberately keeps existing wire emit
   unchanged (plan section 12.1, user-confirmed caveat that some local
   servers ignore the parameter).

Type surfaces: NativeToolArgs.new_task.thinking_effort and ToolUse.params
now admit the null sentinel. tsc clean, eslint clean, 55 unit tests + 5 DTE
e2e suites passing locally.
A task reopened from history constructed a fresh Task with no runtime
thinking effort, so the displayed and effective effort silently fell back
to the settings value even when the task had a per-task override.

- historyItemSchema: optional thinkingEffort + thinkingEffortSource
- taskMetadata: accepts and spreads both (only when active)
- Task.saveClineMessages: writes the active override via
  getRuntimeThinkingEffort()
- Task ctor (historyItem branch): restores it via
  setRuntimeThinkingEffort, which also merges into the in-memory
  apiConfiguration copy and rebuilds the handler
- spec: 4 new persistence round-trip cases (restore, no-op without
  effort, save writes effort, save omits effort while inactive)
packages/types compiles with node16 module resolution, where relative
import specifiers need an explicit file extension (./model.js).
OpenAI-compatible (self-hosted) profiles never declared a reasoning
effort capability, so the dynamic thinking effort surfaces stayed hidden
and the per-request effort envelope was not offered:

- add a SupportedEffortLevels declaration control to the OpenAI-compatible
  provider settings (bound through setApiConfigurationField, persisted on
  Save); an empty declaration unchecks the Enable Reasoning Effort switch
  so UI and wire state cannot drift;
- ThinkingBudget options now derive from the custom-model entry's own
  capability, else the declared levels, else the default set;
- resolveReasoningEffortCapability synthesizes a minimal ModelInfo from the
  declaration when no model info reaches the webview at all;
- new i18n keys in all 18 locales (parity-checked).
A custom model's own capability array may include the "disable" level
(e.g. ollama think/no-think style models). Selecting it previously stored
reasoningEffort="disable" while enableReasoningEffort stayed true, so the
parent switch showed reasoning as enabled although the selected effort
disables it. The effort binding now clears the stored model effort and
unchecks the switch — the same end state as unchecking it by hand.

CodeRabbit finding (functional correctness, minor) on PR Zoo-Code-Org#1366.
… is not superseded

The setTaskThinkingEffort handler rendered its in-chat line with a plain
say() call, which bumps Task.lastMessageTs. If the task was blocked on a
pending ask (e.g. the followup ask after a subtask returned), Task.ask's
pWaitFor treated the ask as superseded (AskIgnoredError) and the request
loop died; the next user message then reached handleWebviewAskResponse
with no waiter — no API call and a frozen UI.

Pass { isNonInteractive: true } (same contract as checkpoint_saved and
other display-only says) so the line is posted to the webview without
entering the ask superseding flow. Add a handler-level regression test.
…and Brain icon

The new_task ask effort selector (DTE series 5/5) rendered borderless and
without an affordance: it used the --vscode-input-* tokens, and in the
default themes the input border is transparent.

- Switch the trigger to the --vscode-dropdown-{border,background,foreground}
  tokens (same family as the settings selects) plus borderRadius 4,
  cursor pointer and no focus outline, matching the McpView select styling.
- Add a Brain (lucide) glyph inside the trigger via a position:relative
  wrapper — native <option> elements cannot carry icons, so the trigger is
  the only place the level affordance can live. The icon is decorative
  (aria-hidden, pointer-events none) and uses descriptionForeground.
- Add data-testid="new-task-effort-select" for e2e/visual targeting.
- Unit test asserts the dropdown tokens, radius, cursor, left padding and
  the icon's presence/positioning.

No behavior change: aria-label, option values, prefill and the posted
effort value are untouched.
The e2e-mock restart-persistence suite failed on a "completed task should
persist API conversation history" assertion, but this commit's diff is
webview-ui only (ChatView.tsx + spec) and the identical extension code
passed the same suite at the previous head (3ea9f63, run of 2026-08-24).

No code changes — this empty commit re-triggers the CI matrix so the
flake can be re-rolled.
CodeRabbit a11y note on 4b655b4: the inline outline: "none" removed
the native keyboard focus indicator without a replacement :focus-visible
style, so keyboard users could not identify the active control.

Drop the outline suppression and let the browser's native focus ring
render; assert in the unit test that no outline property is set.
… coverage

- NewTaskTool: add the disable-only capability case so the empty support-list error hint (...none) is exercised.

- ClineProvider: add a non-Error say rejection case so the String(error) fallback in the non-fatal catch is exercised.

- ChatView: drop the unreachable ?? fallback on the effort select value; the prefill invariant guarantees the value is defined whenever the selector renders, so the rendered and posted values agree by construction.
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