Skip to content

fix(inspect): tolerate price tiers and cells with no when gate - #4

Open
Tomauskasz wants to merge 1 commit into
monid-ai:mainfrom
Tomauskasz:fix/ungated-price-tier-crash
Open

Tomauskasz wants to merge 1 commit into
monid-ai:mainfrom
Tomauskasz:fix/ungated-price-tier-crash

Conversation

@Tomauskasz

@Tomauskasz Tomauskasz commented Sep 7, 2026

Copy link
Copy Markdown

Problem

monid inspect crashes on any endpoint whose price card carries an ungated tier — a tier with no when on the wire, which always applies and is metered by its selector alone.

formatInspectResult calls Object.entries() directly on PriceTier.when and PriceVariant.when. When the key is absent, that throws Cannot convert undefined or null to object and aborts the whole render.

exa /search ships exactly that shape today (0.1.7, and 0.1.7-dev.20260905010537):

$ monid inspect -p exa -e /search
Pricing
  Type:   TIERED
  Amount: from $0.01/call
  Base:   $0.01 (always)
monid: error: Cannot convert undefined or null to object

Everything after the base price is lost — input schema, docs URL, and the run hints. The failure reads like the endpoint is unavailable rather than a formatting bug, so the natural next step is to go looking for a different provider. --json, discover, and run are all unaffected; only the human-readable render dies.

The card the backend actually returns for exa /search:

{
  "type": "TIERED",
  "default": { "type": "PER_CALL", "amount": { "value": 0.01, "currency": "USD" } },
  "tiers": [
    {
      "label": "Results above 10",
      "selector": { "label": "Results above 10", "key": "numResults", "in": "body", "offset": 10 },
      "price": { "type": "PER_RESULT", "amount": { "value": 0.001, "currency": "USD" } }
    }
  ]
}

No when — correctly so. The tier is not gated on a request value; it meters numResults above an offset.

Fix

  • Route both render sites through one formatPriceWhen() helper that treats a missing gate as empty.
  • Drop the clause when there is nothing to gate on, so the tier prints as + Results above 10: $0.001 / result rather than a dangling when. Same for a PER_UNIT_MATRIX cell with no coordinates — no orphaned - : separator.
  • Mark when optional on PriceTier and PriceVariant. The types claimed it was always present, which is what let this through; the wire has never guaranteed it.

This follows the existing convention in types.ts of tolerating every shape the backend sends rather than trusting one. A malformed card should degrade a line, not kill the command.

Tests

Three cases added to test/output/price.test.ts, built from the real exa payload above:

  • an ungated TIERED tier renders instead of throwing;
  • a gated tier still prints when deep=true (no regression);
  • a PER_UNIT_MATRIX variant with no when renders instead of throwing.

The two "instead of throwing" tests fail on main with the original error and pass with this change:

# main + new tests
(fail) renders a TIERED tier that has no `when` gate instead of throwing
(fail) renders a PER_UNIT_MATRIX variant that has no `when` instead of throwing
   13 pass, 2 fail

# this branch
   65 pass, 0 fail   (bun test, full suite)
   tsc --noEmit clean

Verified end to end against api.monid.aimonid inspect -p exa -e /search now renders the full card, schema, docs, and hints.

Summary by CodeRabbit

  • Bug Fixes

    • Price details without gating information are now handled safely.
    • Inspect output no longer shows empty gate or coordinate text.
    • Ungated pricing tiers are clearly displayed as always applicable.
  • Tests

    • Added coverage for ungated tiers and matrix variants to ensure accurate, error-free rendering.

`formatInspectResult` called `Object.entries()` directly on `PriceTier.when`
and `PriceVariant.when`. The wire omits `when` on an ungated tier — one that
always applies and is metered by its `selector` alone — so the call threw
"Cannot convert undefined or null to object" and aborted the entire render.

exa `/search` ships exactly that shape today: a TIERED card whose sole tier
charges per result above 10 with no `when`. `monid inspect -p exa -e /search`
died right after printing the base price:

    Pricing
      Type:   TIERED
      Amount: from $0.01/call
      Base:   $0.01 (always)
    monid: error: Cannot convert undefined or null to object

The endpoint schema, docs, and hints were all lost, and the exit path made it
look as though the endpoint itself was unavailable. `--json`, `discover`, and
`run` were unaffected.

Route both render sites through one `formatPriceWhen()` helper that treats a
missing gate as empty, and drop the clause when there is nothing to gate on,
so an ungated tier prints as `+ Results above 10: $0.001 / result` instead of
a dangling `when`. Mark `when` optional on `PriceTier` and `PriceVariant`: the
types claimed it was always present, which is what let the crash through.

Signed-off-by: Tomas <180413002+Tomauskasz@users.noreply.github.com>
@Tomauskasz
Tomauskasz requested review from a team and FeiyouG September 7, 2026 12:21
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: d63833d0-e72c-49bf-a1bb-df0486c14002

📥 Commits

Reviewing files that changed from the base of the PR and between 3b64605 and f917b01.

📒 Files selected for processing (3)
  • src/api/types.ts
  • src/output/format.ts
  • test/output/price.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Price variants and tiers now support omitted when gates. Output formatting handles ungated prices without exceptions or empty gate text. Tests cover ungated tiers, gated tiers, and ungated matrix variants.

Changes

Price gate rendering

Layer / File(s) Summary
Optional gate contract and rendering
src/api/types.ts, src/output/format.ts, test/output/price.test.ts
PriceVariant.when and PriceTier.when are optional. Shared formatting renders gate conditions when present and omits them when absent. Inspection tests verify ungated and gated output.
Estimated code review effort: 2 (Simple) ~10 minutes

Merge Risk: ⚪ Minimal · up to f917b

Inspection output now renders pricing entries that omit optional gate conditions without crashing or leaving dangling gate text. The relevant rendering behavior is covered, with no remaining merge-readiness risk identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: making inspect tolerate price tiers and matrix cells without a when gate.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

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