Skip to content

fix(omp): limit thinking levels to model's reported efforts#2171

Open
bendavid wants to merge 1 commit into
getpaseo:mainfrom
bendavid:fix/omp-thinking-level-filtering
Open

fix(omp): limit thinking levels to model's reported efforts#2171
bendavid wants to merge 1 commit into
getpaseo:mainfrom
bendavid:fix/omp-thinking-level-filtering

Conversation

@bendavid

Copy link
Copy Markdown

Problem

When running Paseo with an omp model configured with a limited subset of thinking levels (e.g. only high and xhigh), the full set of six thinking levels (off/minimal/low/medium/high/xhigh) still appeared in the thinking dropdown menu.

Root cause

The omp provider's mapOmpModel exposed all six thinking levels whenever a model had reasoning: true, ignoring the per-model thinking config that omp reports via RPC. The OmpModelSchema didn't parse the thinking field at all, so the model's efforts subset and defaultLevel were discarded.

Additionally, createSession hardcoded medium as the thinking fallback when the client didn't send a thinkingOptionId — overriding omp's own model default with a level that may not even be in the model's effort set.

Fix

  • rpc-types.ts — Added OmpModelThinkingSchema (mode/efforts/defaultLevel/effortMap, passthrough) and wired it into OmpModelSchema as the thinking field.

  • agent.ts — Rewrote mapOmpModel to delegate to resolveOmpThinkingConfig(model):

    • reasoning: false/absent → no thinking options (unchanged).
    • reasoning: true with no thinking.efforts (older omp) → full set, medium default (back-compat).
    • reasoning: true with efforts → only the options whose id appears in efforts, in canonical order. Default is thinking.defaultLevel if it's in the filtered set, otherwise the first (lowest) effort. If none of the reported efforts are known ids, falls back to the full set.
    • Exported mapOmpModel for unit testing.
  • agent.ts (createSession) — Changed the thinking fallback from DEFAULT_OMP_THINKING_LEVEL ("medium") to undefined, so buildOmpLaunch doesn't emit --thinking and omp uses its own model default when the client doesn't specify a thinking option.

Verification

  • 9 new unit tests in map-omp-model.test.ts covering: filtered subset, full set when no config, full set when efforts empty, omitted when reasoning false/absent, defaultLevel not in efforts → first option, no defaultLevel → first option, all-unknown efforts → full set, id/label/metadata preservation.
  • Updated agent.test.ts launch-configuration test (no --thinking flag when no thinking option specified) and added a test verifying --thinking xhigh IS passed when explicitly specified.
  • All 31 omp tests pass. Typecheck, lint, and format clean.
  • E2E confirmed against real omp v17.0.2 binary: the GLM-5.2 model (configured with efforts: ["high","xhigh"], defaultLevel: "xhigh") now maps to exactly ["high","xhigh"] with xhigh as default, instead of all six options with medium as default.

The omp provider exposed all six thinking levels (off/minimal/low/
medium/high/xhigh) whenever a model had reasoning enabled, ignoring the
per-model thinking config that omp reports via RPC. The OmpModelSchema
didn't parse the thinking field at all, so the model's efforts subset
and defaultLevel were discarded.

Parse model.thinking (efforts/defaultLevel/effortMap) in OmpModelSchema
and filter the thinking options in mapOmpModel to only the model's
reported efforts. The default is the reported defaultLevel when it's
in the filtered set, otherwise the first (lowest) effort. Older omp
versions that don't report thinking.efforts keep getting the full set.

Also fix createSession to not hardcode 'medium' as the thinking fallback
when the client doesn't send a thinkingOptionId — pass undefined so omp
uses its own model default instead of overriding it with a level that
may not even be in the model's effort set.
@bendavid
bendavid force-pushed the fix/omp-thinking-level-filtering branch from 2b599f4 to 25a3cd2 Compare July 17, 2026 20:57
@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the omp provider to respect per-model thinking configs from the RPC response instead of always showing all six thinking levels. The schema addition, the createSession fallback change, and the core filtering logic in resolveOmpThinkingConfig are all correct and well-intentioned.

  • rpc-types.ts: Adds OmpModelThinkingSchema with passthrough and wires it into OmpModelSchema — clean and straightforward.
  • agent.ts: Rewrites mapOmpModel to use resolveOmpThinkingConfig, which correctly filters options to the model-reported effort subset and defers to omp's defaultLevel. The createSession fallback is also correctly changed from DEFAULT_OMP_THINKING_LEVEL to undefined.
  • agent.ts / resolveOmpThinkingConfig: When efforts is non-empty but all IDs are unrecognized, the code falls back to the full option set (correct) but picks options[0]?.id ("off") as the default instead of DEFAULT_OMP_THINKING_LEVEL ("medium"), diverging from the equivalent !efforts || efforts.length === 0 early-return path. The unit test for this branch doesn't assert defaultThinkingOptionId, leaving the inconsistency uncaught.

Confidence Score: 3/5

Safe to merge for all current omp model configurations; the divergent default only surfaces when a model reports effort IDs that are entirely unrecognized by the client.

The resolveOmpThinkingConfig all-unknown-efforts branch falls back to the full option set but picks "off" (no reasoning) as the default, while the functionally equivalent !efforts || efforts.length === 0 early-return path uses "medium". The unit test for this branch never asserts defaultThinkingOptionId, so the inconsistency ships silently. Should a future omp model report effort labels not in the current known set, the dropdown would open with reasoning disabled by default on a model that expects it.

The resolveOmpThinkingConfig function in agent.ts (lines 907–914) and the corresponding test in map-omp-model.test.ts for the all-unknown-efforts case need attention.

Important Files Changed

Filename Overview
packages/server/src/server/agent/providers/omp/agent.ts Extracted resolveOmpThinkingConfig to compute thinking options from per-model config; inconsistent default ("off" vs "medium") in the all-unknown-efforts fallback branch; also adds mapThinkingOptionWithDefault which duplicates mapThinkingOption.
packages/server/src/server/agent/providers/omp/rpc-types.ts Adds OmpModelThinkingSchema (with passthrough) and wires it into OmpModelSchema; straightforward schema addition, no issues.
packages/server/src/server/agent/providers/omp/map-omp-model.test.ts New unit tests for mapOmpModel — good coverage of the happy path, backward compat, and edge cases, but the all-unknown-efforts test doesn't assert defaultThinkingOptionId (leaving the "off" default bug uncaught), and mapOmpModel was only exported to satisfy these tests.
packages/server/src/server/agent/providers/omp/agent.test.ts Updated launch-config test to expect no --thinking flag by default; added test verifying --thinking xhigh is emitted when explicitly specified. Both changes are correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[mapOmpModel] --> B{model.reasoning?}
    B -- false/absent --> C[thinkingOptions: undefined]
    B -- true --> D{efforts exists and non-empty?}
    D -- No --> E[Full option set, default = medium]
    D -- Yes --> F[Filter by effort IDs]
    F --> G{Any known efforts matched?}
    G -- No, all unknown --> H[Full option set, default = off - differs from path E]
    G -- Yes --> I[Filtered options]
    I --> J{defaultLevel in filtered set?}
    J -- Yes --> K[default = defaultLevel]
    J -- No/absent --> L[default = first option id]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[mapOmpModel] --> B{model.reasoning?}
    B -- false/absent --> C[thinkingOptions: undefined]
    B -- true --> D{efforts exists and non-empty?}
    D -- No --> E[Full option set, default = medium]
    D -- Yes --> F[Filter by effort IDs]
    F --> G{Any known efforts matched?}
    G -- No, all unknown --> H[Full option set, default = off - differs from path E]
    G -- Yes --> I[Filtered options]
    I --> J{defaultLevel in filtered set?}
    J -- Yes --> K[default = defaultLevel]
    J -- No/absent --> L[default = first option id]
Loading

Reviews (1): Last reviewed commit: "fix(omp): limit thinking levels to model..." | Re-trigger Greptile

Comment on lines +907 to +914
const effortSet = new Set(efforts);
const filtered = OMP_THINKING_OPTIONS.filter((option) => effortSet.has(option.id));
const options = filtered.length > 0 ? filtered : [...OMP_THINKING_OPTIONS];
const reportedDefault = model.thinking?.defaultLevel;
const defaultThinkingOptionId =
reportedDefault && options.some((option) => option.id === reportedDefault)
? reportedDefault
: (options[0]?.id ?? DEFAULT_OMP_THINKING_LEVEL);

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.

P1 Inconsistent default when all efforts are unknown

When efforts is non-empty but every ID is unrecognized (e.g. a future omp version uses "ultra"/"turbo"), options becomes the full set via the filtered.length > 0 ? filtered : [...OMP_THINKING_OPTIONS] fallback — identical to the early-return path for !efforts || efforts.length === 0. However, the early-return path returns defaultThinkingOptionId: DEFAULT_OMP_THINKING_LEVEL ("medium"), while this path resolves defaultThinkingOptionId to options[0]?.id which is "off". For a model with reasoning: true, silently defaulting to "off" (no thinking) is materially wrong, and the unit test for this branch ("falls back to the full set when every reported effort is unknown") does not assert defaultThinkingOptionId, so the divergence goes uncaught.

Comment on lines +313 to +326
function mapThinkingOptionWithDefault(
option: (typeof OMP_THINKING_OPTIONS)[number],
isDefault: boolean,
): AgentSelectOption {
const mapped: AgentSelectOption = {
id: option.id,
label: option.label,
description: option.description,
};
if (isDefault) {
mapped.isDefault = true;
}
return mapped;
}

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 Duplicate helper adds without unifying

mapThinkingOptionWithDefault does what mapThinkingOption already does, differing only in how isDefault is sourced. mapThinkingOption reads option.isDefault from the static array; mapThinkingOptionWithDefault accepts it as a parameter. Instead of a second function called from one place, mapThinkingOption could accept an optional override — or both callers could use the same function by computing isDefault at the call site. As written, any future change to the shape of AgentSelectOption must be applied in both places.

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +1 to +10
import { describe, expect, test } from "vitest";

import { mapOmpModel } from "./agent.js";
import type { OmpModel } from "./rpc-types.js";

function baseModel(overrides: Partial<OmpModel> = {}): OmpModel {
return {
provider: "pioneer",
id: "canada-quant/glm-5.2",
name: "GLM-5.2",

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 Tests directly import a symbol exported only for testing

mapOmpModel was exported specifically so this test file could reach it (per the PR description). Per the project's test discipline, tests should cross the same module interface as callers — not import private implementation files directly. If the omp module has a designed public entry point, mapOmpModel should either be exposed from it or the tests should exercise the behavior through OmpAgentClient.getModels() / session creation paths. Exporting an internal function to satisfy a test is a sign the test is asserting the wrong thing.

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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.

1 participant