Skip to content

feat(platform): add auto model selection and expand error categories#1441

Merged
larryro merged 3 commits into
mainfrom
feat/auto-model-and-error-categories
Apr 12, 2026
Merged

feat(platform): add auto model selection and expand error categories#1441
larryro merged 3 commits into
mainfrom
feat/auto-model-and-error-categories

Conversation

@larryro
Copy link
Copy Markdown
Collaborator

@larryro larryro commented Apr 12, 2026

Closes #1432

Summary

  • Add "Auto" option to the model selector that automatically picks the best available model with fallback, replacing the previous governance-default logic. Model overrides now expire after 24 hours to prevent stale selections.
  • Expand chat error sanitization with three new categories: auth errors (401/403, invalid keys), model-not-found (404, unavailable models), and provider errors (5xx, overloaded). Known error types no longer expose rawMessage to avoid leaking stack traces to the UI.
  • Add i18n strings for new error categories and auto-model in both en and de locales.

Test plan

  • Verify "Auto" appears as the default option in the model selector dropdown
  • Select a specific model, confirm it persists, then verify it resets after 24h (or clear localStorage to simulate)
  • Trigger auth/model-not-found/provider errors and confirm user-friendly messages are shown without raw stack traces
  • Run sanitize-chat-error unit tests pass with new categories and no-rawMessage assertions

larryro added 3 commits April 12, 2026 19:55
Add "Auto" option to model selector that picks the best available model
with fallback, replacing the previous governance-default logic. Model
overrides now expire after 24 hours to prevent stale selections.

Expand chat error sanitization with auth, model-not-found, and provider
error categories. Known error types no longer expose rawMessage to avoid
leaking stack traces to the UI.
…#1432)

Add pattern to sanitize-chat-error so missing auth header errors show
a user-friendly message instead of raw stack traces.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

This PR introduces automatic model selection mode in the chat feature by adding an AUTO_MODEL sentinel ('auto') to the model selector component. When no explicit per-agent model override exists, the component returns this sentinel instead of a concrete model; selecting the 'auto' option clears the override. The override storage mechanism in chat layout context was restructured from a flat record to support expiry timestamps with a 24-hour TTL, while maintaining the same consumer-facing interface. Additionally, error categorization was expanded with three new categories (authError, modelNotFound, providerError) and corresponding pattern-matching rules, with rawMessage now excluded from sanitized output for known error patterns. Localization strings were added in English and German for the new UI option and error messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the two main changes: introducing auto model selection and expanding error categories with new classifications.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/auto-model-and-error-categories

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/platform/app/features/chat/utils/sanitize-chat-error.ts (1)

25-58: ⚠️ Potential issue | 🟠 Major

Bare 401/403/404 matches now mask tool failures.

These status-code patterns run before toolFailure, so errors like Tool error: upstream returned 404 or Tool error: 401 unauthorized will be shown as auth/model issues instead of tool failures. Tighten the auth/model regexes or move toolFailure ahead of the raw status-code rules.

💡 Minimal fix
   {
+    pattern: /tool.*error|tool.*fail|unable to complete/i,
+    category: 'toolFailure',
+  },
+  {
     pattern:
       /\b401\b|\b403\b|invalid.*key|expired.*key|api.?key.*invalid|unauthorized|forbidden|authentication.*fail|user not found|missing.*authentication/i,
     category: 'authError',
   },
   {
     pattern: /model.*not found|model.*not available|invalid model|\b404\b/i,
     category: 'modelNotFound',
   },
@@
-  {
-    pattern: /tool.*error|tool.*fail|unable to complete/i,
-    category: 'toolFailure',
-  },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/utils/sanitize-chat-error.ts` around
lines 25 - 58, The current regex order in sanitize-chat-error.ts causes bare
status-code rules (authError and modelNotFound) to match messages coming from
tools, masking tool failures; update the rules so the object with category
'toolFailure' is evaluated before the status-code-based rules (the entries with
category 'authError' and 'modelNotFound'), or alternatively tighten those
regexes to avoid matching when the message contains tool context (e.g., require
word boundaries plus absence of "tool" or "upstream" so expressions like "Tool
error: 404" don't match auth/model patterns); locate the pattern array in
sanitize-chat-error.ts and either move the { category: 'toolFailure', pattern:
/tool.*error|.../ } entry above the authError/modelNotFound objects or constrain
the auth/model regexes accordingly.
services/platform/app/features/chat/components/model-selector.tsx (1)

141-170: ⚠️ Potential issue | 🟠 Major

Don't hide the Auto state when only one concrete model is available.

After adding autoOption, filteredModels.length <= 1 no longer means there is nothing to choose. In the current flow, a user who previously pinned the lone accessible model can never switch back to Auto, because the component falls through to the read-only branch before the new option is usable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/components/model-selector.tsx` around
lines 141 - 170, Move creation of autoOption and modelOptions (the autoOption
constant and the modelOptions mapping using modelInfoMap and getDisplayName)
before the single-model read-only check and change the guard from checking
filteredModels.length <= 1 to checking the total options length (e.g. compute
options = [autoOption, ...modelOptions] first and then if options.length <= 1
return the read-only span). This ensures AUTO_MODEL (AUTO_MODEL / autoOption) is
considered when deciding whether to render the select so a user can switch back
to Auto even when there is one concrete model.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/features/chat/context/chat-layout-context.tsx`:
- Around line 100-118: The current selectedModelOverrides memo drops legacy
string entries (Record<string,string>) which wipes users' saved selections;
update the logic that reads rawModelOverrides (the useMemo for
selectedModelOverrides) to detect string values, convert them into
ModelOverrideEntry objects with a fresh expiresAt (e.g. Date.now() +
24*60*60*1000) and persist the migration back via setRawModelOverrides so legacy
entries are upgraded once; keep the existing expiry check for ModelOverrideEntry
values and ensure you reference rawModelOverrides, selectedModelOverrides,
setRawModelOverrides and modelOverridesKey when implementing the one-time
migration.
- Around line 104-118: The memoized selectedModelOverrides uses Date.now() but
only recalculates when rawModelOverrides changes, so expirations won't take
effect over time; update the logic to re-evaluate periodically by introducing a
time tick into the dependency list (e.g., a state like now updated via a
useEffect setInterval) or by using a timeout that triggers a state update at the
nearest expiresAt, then reference that state in the useMemo dependency array so
selectedModelOverrides (and any consumers) recompute when entries actually
expire; touch symbols: selectedModelOverrides, rawModelOverrides, useMemo,
Date.now(), and add a useEffect/setInterval or scheduled timeout to drive
updates.

---

Outside diff comments:
In `@services/platform/app/features/chat/components/model-selector.tsx`:
- Around line 141-170: Move creation of autoOption and modelOptions (the
autoOption constant and the modelOptions mapping using modelInfoMap and
getDisplayName) before the single-model read-only check and change the guard
from checking filteredModels.length <= 1 to checking the total options length
(e.g. compute options = [autoOption, ...modelOptions] first and then if
options.length <= 1 return the read-only span). This ensures AUTO_MODEL
(AUTO_MODEL / autoOption) is considered when deciding whether to render the
select so a user can switch back to Auto even when there is one concrete model.

In `@services/platform/app/features/chat/utils/sanitize-chat-error.ts`:
- Around line 25-58: The current regex order in sanitize-chat-error.ts causes
bare status-code rules (authError and modelNotFound) to match messages coming
from tools, masking tool failures; update the rules so the object with category
'toolFailure' is evaluated before the status-code-based rules (the entries with
category 'authError' and 'modelNotFound'), or alternatively tighten those
regexes to avoid matching when the message contains tool context (e.g., require
word boundaries plus absence of "tool" or "upstream" so expressions like "Tool
error: 404" don't match auth/model patterns); locate the pattern array in
sanitize-chat-error.ts and either move the { category: 'toolFailure', pattern:
/tool.*error|.../ } entry above the authError/modelNotFound objects or constrain
the auth/model regexes accordingly.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6cff8e40-5cac-414c-9b46-b2013b64f242

📥 Commits

Reviewing files that changed from the base of the PR and between 303ddb0 and 4284063.

📒 Files selected for processing (6)
  • services/platform/app/features/chat/components/model-selector.tsx
  • services/platform/app/features/chat/context/chat-layout-context.tsx
  • services/platform/app/features/chat/utils/__tests__/sanitize-chat-error.test.ts
  • services/platform/app/features/chat/utils/sanitize-chat-error.ts
  • services/platform/messages/de.json
  • services/platform/messages/en.json

Comment on lines +100 to +118
const [rawModelOverrides, setRawModelOverrides] = usePersistedState<
Record<string, ModelOverrideEntry | string>
>(modelOverridesKey, {});

// Expose a flat Record<string, string> to consumers, filtering out expired entries.
const selectedModelOverrides = useMemo(() => {
const now = Date.now();
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(rawModelOverrides)) {
if (typeof value === 'string') {
// Legacy format (no expiry) — treat as expired
continue;
}
if (value.expiresAt > now) {
result[key] = value.modelId;
}
}
return result;
}, [rawModelOverrides]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't discard legacy overrides on first load.

Legacy Record<string, string> entries are skipped here, which resets every pre-existing saved model selection as soon as users load this build. If the intended rollout behavior is “expire after 24 hours,” these values need a one-time migration to ModelOverrideEntry with a fresh expiresAt instead of being treated as already expired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/context/chat-layout-context.tsx` around
lines 100 - 118, The current selectedModelOverrides memo drops legacy string
entries (Record<string,string>) which wipes users' saved selections; update the
logic that reads rawModelOverrides (the useMemo for selectedModelOverrides) to
detect string values, convert them into ModelOverrideEntry objects with a fresh
expiresAt (e.g. Date.now() + 24*60*60*1000) and persist the migration back via
setRawModelOverrides so legacy entries are upgraded once; keep the existing
expiry check for ModelOverrideEntry values and ensure you reference
rawModelOverrides, selectedModelOverrides, setRawModelOverrides and
modelOverridesKey when implementing the one-time migration.

Comment on lines +104 to +118
// Expose a flat Record<string, string> to consumers, filtering out expired entries.
const selectedModelOverrides = useMemo(() => {
const now = Date.now();
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(rawModelOverrides)) {
if (typeof value === 'string') {
// Legacy format (no expiry) — treat as expired
continue;
}
if (value.expiresAt > now) {
result[key] = value.modelId;
}
}
return result;
}, [rawModelOverrides]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The 24-hour TTL won't expire in an already-open tab.

Date.now() is only consulted inside a useMemo keyed by rawModelOverrides, so time passing alone never removes an override. A session left open overnight will keep using the pinned model past expiresAt until the override map changes or the page remounts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/context/chat-layout-context.tsx` around
lines 104 - 118, The memoized selectedModelOverrides uses Date.now() but only
recalculates when rawModelOverrides changes, so expirations won't take effect
over time; update the logic to re-evaluate periodically by introducing a time
tick into the dependency list (e.g., a state like now updated via a useEffect
setInterval) or by using a timeout that triggers a state update at the nearest
expiresAt, then reference that state in the useMemo dependency array so
selectedModelOverrides (and any consumers) recompute when entries actually
expire; touch symbols: selectedModelOverrides, rawModelOverrides, useMemo,
Date.now(), and add a useEffect/setInterval or scheduled timeout to drive
updates.

@larryro larryro merged commit 42687ce into main Apr 12, 2026
24 checks passed
@larryro larryro deleted the feat/auto-model-and-error-categories branch April 12, 2026 12:06
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.

Chat response generation fails with “Insufficient credits” error and prevents normal conversation flow

1 participant