Skip to content

fix(xai): declare grok image input so combos are not advertised text-only#478

Merged
Ingwannu merged 2 commits into
lidge-jun:devfrom
jonathanli12:codex/fix-xai-grok-input-modalities
Jul 26, 2026
Merged

fix(xai): declare grok image input so combos are not advertised text-only#478
Ingwannu merged 2 commits into
lidge-jun:devfrom
jonathanli12:codex/fix-xai-grok-input-modalities

Conversation

@jonathanli12

@jonathanli12 jonathanli12 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Problem

Any combo containing an xAI target is published to Codex as text-only, so the Codex app refuses image attachments ("does not support image inputs") before a request is ever routed, even when every member of the combo accepts images.

Reproduced with the documented xAI -> Cursor grok-4.5 failover combo. From the generated Codex catalog:

combo/xai_grok_fallback      input_modalities: ["text"]      <- blocked
cursor/grok-4.5              input_modalities: ["text","image"]
xai/grok-4.5                 input_modalities: undefined     <- root cause

Cause

The xai entry in PROVIDER_REGISTRY declares no modelInputModalities, so catalog rows for xai/grok-4.5 carry inputModalities: undefined.

deriveComboCatalogModel intersects member modalities and defaults an undefined member to ["text"]:

const inputModalities = intersectStrings(
  members.map(member => member.inputModalities ?? ["text"]),
);

So one member with unknown modalities collapses the whole combo to text-only.

There is an ironic asymmetry today: cursor/grok-4.5 ends up correct because Cursor lists every model in noVisionModels, and applyProviderConfigHints deliberately appends image for sidecar-covered models (that code already documents why the catalog must still advertise image input, otherwise the Codex app blocks attachments client-side). xAI is neither sidecar-covered nor vision-declared, so it falls through both paths.

Fix

Declare the vision lineup for the grok-4.x chat models, per the xAI docs on image understanding:

modelInputModalities: {
  "grok-4.5": ["text", "image"],
  "grok-4.3": ["text", "image"],
  "grok-4.20-0309-reasoning": ["text", "image"],
  "grok-4.20-0309-non-reasoning": ["text", "image"],
},

grok-build-0.1 and grok-composer-2.5-fast are intentionally omitted; they stay in noVisionModels.

Notes

  • Intersection semantics are unchanged. A member whose modalities are genuinely unknown still narrows a combo to ["text"], which is the safe behaviour and is now pinned by a test.
  • No change to request-time handling. Combos still resolve to a concrete member via routeModel, so per-member vision protection (planVisionSidecar / stripImagesInPlace fail-closed) is untouched.

Tests

Added to tests/catalog-vision-sidecar-modalities.test.ts:

  1. xAI grok chat models declare image input in the registry (fails without this fix).
  2. A combo of two vision-capable members still advertises image.
  3. A member with unknown modalities still narrows the combo to text (guards the safety property).

Verified locally: 637 tests across the 45 catalog/combo/vision/provider/registry test files pass, and tsc --noEmit is clean.

Summary by CodeRabbit

  • Bug Fixes
    • Enabled image attachments for supported xAI Grok model variants.
    • Updated combined/derived model catalog entries to correctly advertise image input support when available.
    • Ensured combined entries fall back to text-only when a member’s vision support is missing or unverified.
  • Tests
    • Added/expanded automated coverage to verify modality propagation and correct narrowing behavior for combined models.

@github-actions github-actions Bot added the bug Something isn't working label Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

Target branch corrected

This pull request now targets dev.

The [WRONG BRANCH] title prefix has been removed. The pull request has been marked ready for review again.

@github-actions github-actions Bot changed the title fix(xai): declare grok image input so combos are not advertised text-only [WRONG BRANCH] fix(xai): declare grok image input so combos are not advertised text-only Jul 26, 2026
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9bf802d3-9724-4707-88d5-21d7e3b64acb

📥 Commits

Reviewing files that changed from the base of the PR and between d539bd6 and 9eb7f0b.

📒 Files selected for processing (3)
  • src/providers/derive.ts
  • src/providers/registry.ts
  • tests/catalog-vision-sidecar-modalities.test.ts

📝 Walkthrough

Walkthrough

The xAI registry marks selected Grok models as accepting text and image inputs. Provider enrichment now completes partial modality overrides from registry defaults, and tests verify propagation into derived combo catalog models.

Changes

Vision modality propagation

Layer / File(s) Summary
Registry metadata and provider enrichment
src/providers/registry.ts, src/providers/derive.ts
Selected Grok models declare ["text", "image"], while partial provider modality maps are completed with unspecified registry entries.
Combo modality regression coverage
tests/catalog-vision-sidecar-modalities.test.ts
Tests verify registry declarations, vision-capable combo output, text-only narrowing for unverifiable members, and partial override behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ingwannu, lidge-jun

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the xAI Grok image-input metadata fix that prevents combos from being advertised as text-only.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/catalog-vision-sidecar-modalities.test.ts`:
- Around line 155-161: Expand the test in “xAI grok chat models declare image
input in the registry” to assert every newly vision-capable xAI model ID from
PROVIDER_REGISTRY, including the other three declarations alongside “grok-4.5”.
Also assert “grok-composer-2.5-fast” is absent from modelInputModalities and
present in noVisionModels, preserving the existing text-only exclusion checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e63a1066-c226-4665-9613-8fd90a48e0ee

📥 Commits

Reviewing files that changed from the base of the PR and between 0ebb8fc and d539bd6.

📒 Files selected for processing (2)
  • src/providers/registry.ts
  • tests/catalog-vision-sidecar-modalities.test.ts

Comment on lines +155 to +161
test("xAI grok chat models declare image input in the registry", () => {
const xai = PROVIDER_REGISTRY.find(entry => entry.id === "xai");
expect(xai?.modelInputModalities?.["grok-4.5"]).toEqual(["text", "image"]);
// Text-only members stay out of the vision map (they are already in noVisionModels).
expect(xai?.modelInputModalities?.["grok-build-0.1"]).toBeUndefined();
expect(xai?.noVisionModels).toContain("grok-build-0.1");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover every changed xAI model declaration and exclusion.

Lines 400-403 in src/providers/registry.ts add four vision-capable IDs, but this test asserts only grok-4.5; it also omits the grok-composer-2.5-fast exclusion documented on Lines 397-398. A regression in any unasserted entry would pass.

Proposed test expansion
-    expect(xai?.modelInputModalities?.["grok-4.5"]).toEqual(["text", "image"]);
-    // Text-only members stay out of the vision map (they are already in noVisionModels).
-    expect(xai?.modelInputModalities?.["grok-build-0.1"]).toBeUndefined();
-    expect(xai?.noVisionModels).toContain("grok-build-0.1");
+    for (const model of [
+      "grok-4.5",
+      "grok-4.3",
+      "grok-4.20-0309-reasoning",
+      "grok-4.20-0309-non-reasoning",
+    ]) {
+      expect(xai?.modelInputModalities?.[model]).toEqual(["text", "image"]);
+    }
+    for (const model of ["grok-build-0.1", "grok-composer-2.5-fast"]) {
+      expect(xai?.modelInputModalities?.[model]).toBeUndefined();
+      expect(xai?.noVisionModels).toContain(model);
+    }

As per path instructions, this src/ behavior change needs focused regression coverage in tests/.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("xAI grok chat models declare image input in the registry", () => {
const xai = PROVIDER_REGISTRY.find(entry => entry.id === "xai");
expect(xai?.modelInputModalities?.["grok-4.5"]).toEqual(["text", "image"]);
// Text-only members stay out of the vision map (they are already in noVisionModels).
expect(xai?.modelInputModalities?.["grok-build-0.1"]).toBeUndefined();
expect(xai?.noVisionModels).toContain("grok-build-0.1");
});
test("xAI grok chat models declare image input in the registry", () => {
const xai = PROVIDER_REGISTRY.find(entry => entry.id === "xai");
for (const model of [
"grok-4.5",
"grok-4.3",
"grok-4.20-0309-reasoning",
"grok-4.20-0309-non-reasoning",
]) {
expect(xai?.modelInputModalities?.[model]).toEqual(["text", "image"]);
}
for (const model of ["grok-build-0.1", "grok-composer-2.5-fast"]) {
expect(xai?.modelInputModalities?.[model]).toBeUndefined();
expect(xai?.noVisionModels).toContain(model);
}
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/catalog-vision-sidecar-modalities.test.ts` around lines 155 - 161,
Expand the test in “xAI grok chat models declare image input in the registry” to
assert every newly vision-capable xAI model ID from PROVIDER_REGISTRY, including
the other three declarations alongside “grok-4.5”. Also assert
“grok-composer-2.5-fast” is absent from modelInputModalities and present in
noVisionModels, preserving the existing text-only exclusion checks.

Source: Path instructions

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d539bd6e68

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/providers/registry.ts
Comment on lines +399 to +400
modelInputModalities: {
"grok-4.5": ["text", "image"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Merge vision defaults into partially customized modality maps

When an existing xai config already contains any modelInputModalities entry, enrichProviderFromRegistry skips the registry map entirely instead of merging it per key. Consequently, gatherRoutedModels still leaves models such as xai/grok-4.5 without image modalities and affected combos remain advertised as text-only, even though request routing merges the same maps per key. Merge registry defaults beneath user overrides during catalog enrichment and cover a persisted partial map through gatherRoutedModels.

AGENTS.md reference: AGENTS.md:L75-L77

Useful? React with 👍 / 👎.

…only

xAI's registry entry had no modelInputModalities, so catalog rows for
xai/grok-4.5 carried inputModalities: undefined.

deriveComboCatalogModel intersects member modalities and defaults an
undefined member to ["text"], so every combo containing an xAI target was
published to Codex with input_modalities ["text"]. The Codex app gates
attachments client-side on that field, so images were rejected before a
request was ever routed, even though both combo members accept images.

This was visible with the documented xai -> cursor grok-4.5 failover combo:
cursor/grok-4.5 advertised ["text","image"] (via the noVisionModels sidecar
augmentation in applyProviderConfigHints) while xai/grok-4.5 advertised
nothing, and the resulting combo collapsed to text-only.

Declare the vision lineup for the grok-4.x chat models per
docs.x.ai model capabilities. grok-build-0.1 and grok-composer-2.5-fast are
intentionally omitted; they remain in noVisionModels.

Intersection semantics are unchanged: a member whose modalities are unknown
still narrows a combo to text, which the added regression test pins.
@jonathanli12
jonathanli12 force-pushed the codex/fix-xai-grok-input-modalities branch from d539bd6 to 9441ed3 Compare July 26, 2026 04:28
@jonathanli12 jonathanli12 changed the title [WRONG BRANCH] fix(xai): declare grok image input so combos are not advertised text-only fix(xai): declare grok image input so combos are not advertised text-only Jul 26, 2026
@jonathanli12
jonathanli12 changed the base branch from main to dev July 26, 2026 04:28
Addresses review feedback on the xAI vision fix.

enrichProviderFromRegistry filled modelInputModalities all-or-nothing, so a
single customized model in a persisted config suppressed the registry map
entirely. A user who narrowed one grok model would leave grok-4.5 with no
declared modalities, which put the combo aggregator back on its ["text"]
default and re-hid image support.

Routing already merges these maps per key (mergeRecordFill in src/router.ts).
Catalog enrichment now matches, so registry defaults fill in beneath the
user's entries instead of being skipped wholesale. Explicit user values still
win per model.

Also broaden the registry assertion to cover all four vision-capable grok ids
and both text-only exclusions, so a regression in any single entry fails.
@jonathanli12

Copy link
Copy Markdown
Contributor Author

Thanks, both addressed in 9eb7f0b.

Test coverage (trivial): the registry assertion now loops all four vision-capable grok ids and both text-only exclusions (grok-build-0.1, grok-composer-2.5-fast), so a regression in any single entry fails.

P2 partial-map merge: confirmed reproducible, and worth fixing. With a persisted config containing modelInputModalities: { "grok-4.3": ["text"] }:

before   enrich -> {"grok-4.3":["text"]}
         xai/grok-4.5 modalities: undefined      <- registry map skipped entirely
after    enrich -> {"grok-4.5":["text","image"], "grok-4.3":["text"], ...}
         xai/grok-4.5 modalities: ["text","image"]

enrichProviderFromRegistry filled modelInputModalities all-or-nothing, so one customized model hid the registry's knowledge about every other model and put the combo aggregator back on its ["text"] default. Since these maps are keyed per model, the fill is now per key, matching what mergeRecordFill already does for the same field in src/router.ts. Explicit user values still win per model (the grok-4.3: ["text"] narrowing above is preserved).

Scoped deliberately to modelInputModalities rather than converting every fill in derive.ts: that broader change would alter behaviour for many unrelated capability maps and belongs in its own PR.

Added a regression test covering the persisted partial map through enrichProviderFromRegistry + applyProviderConfigHints.

Verified: tsc --noEmit clean, and 858 tests across the 60 catalog/combo/vision/provider/registry/router/model test files pass.

@Ingwannu Ingwannu left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at exact head 9eb7f0b36cbb10a87551f5b1e0324d77dcfacb19. xAI official model pages advertise Text+Image input for all four declared Grok models. The registry change fixes the real combo-catalog collapse without weakening unknown-member intersection semantics, and the per-model fill preserves explicit user overrides while restoring defaults only for missing keys. Verified the 12 focused modality/vision/combo regressions and typecheck locally. The unrelated Windows GUI flake passed on rerun; all current required checks are green.

@Ingwannu
Ingwannu merged commit 7fbccc8 into lidge-jun:dev Jul 26, 2026
21 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants