Skip to content

fix(dashboard): guard null modelAliases values in model picker#5792

Merged
diegosouzapw merged 3 commits into
release/v3.8.43from
fix/port-pr-2247-null-alias-guard
Jul 2, 2026
Merged

fix(dashboard): guard null modelAliases values in model picker#5792
diegosouzapw merged 3 commits into
release/v3.8.43from
fix/port-pr-2247-null-alias-guard

Conversation

@diegosouzapw

@diegosouzapw diegosouzapw commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Opening Create Combo for a custom provider node crashed with a TypeError whenever modelAliases held a null/undefined value (a stale/partial entry persisted to settings). The custom-provider ("node") branch in ModelSelectModal filtered aliases with a raw Object.entries(modelAliases).filter(([, fullModel]) => fullModel.startsWith(...)), calling .startsWith without first checking the value was a string.

The sibling passthrough-alias branch already had this guard (typeof fullModel === "string", via buildPassthroughAliasModels / #485); the node-alias branch did not.

Changes

  • Extracted a new pure helper buildNodeAliasModels(modelAliases, providerId, nodePrefix) into src/shared/components/modelSelectModalHelpers.ts, mirroring buildPassthroughAliasModels but rewriting value with the node display nodePrefix. It requires typeof fullModel === "string" before calling .startsWith.
  • src/shared/components/ModelSelectModal.tsx now calls the extracted helper instead of the inline Object.entries(...).filter(...).map(...) block.
  • Logic-only change — no new UI strings / i18n.

Attribution

Thanks to @wahyuzero for the original implementation.

Testing

TDD (Hard Rule #18): new test tests/unit/model-select-null-alias-guard-2247.test.ts fails before the fix (helper missing) and passes after (3/3). Sibling model-select-passthrough-alias-485.test.ts unaffected (6/6). Existing ModelSelectModal vitest component tests unaffected (16/16). typecheck:core, eslint, check:docs-sync all clean.

Opening Create Combo for a custom provider node threw a TypeError
whenever `modelAliases` held a null/undefined value: the node-alias
branch in ModelSelectModal called `fullModel.startsWith(...)` on every
entry without checking its type first. Extract the filter/map logic
into a new `buildNodeAliasModels` helper (modelSelectModalHelpers.ts),
mirroring the sibling `buildPassthroughAliasModels` guard from #485,
which requires `typeof fullModel === "string"` before calling
`.startsWith`.

Regression guard: tests/unit/model-select-null-alias-guard-2247.test.ts.

Co-authored-by: wahyuzero <wahyuzero@users.noreply.github.com>
Inspired-by: decolua/9router#2247
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request extracts the logic for building alias-derived model rows for custom providers into a new helper function buildNodeAliasModels and adds a null/undefined guard to prevent TypeErrors when parsing stale or partial model aliases. It also adds unit tests to prevent regressions. The review feedback suggests enhancing type safety by updating the parameter type of modelAliases to allow null/undefined values, which in turn allows removing several redundant type assertions in the component and test files. Additionally, it recommends optimizing the helper function to avoid redundant string replacements.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +61 to +75
export function buildNodeAliasModels(
modelAliases: Record<string, string>,
providerId: string,
nodePrefix: string
): NodeAliasModel[] {
const prefix = `${providerId}/`;
return Object.entries(modelAliases || {})
.filter(([, fullModel]) => typeof fullModel === "string" && fullModel.startsWith(prefix))
.map(([aliasName, fullModel]) => ({
id: fullModel.replace(prefix, ""),
name: aliasName,
value: `${nodePrefix}/${fullModel.replace(prefix, "")}`,
source: "alias" as const,
}));
}

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.

medium

Type Safety & Efficiency Improvement

  1. Type Safety: Since modelAliases can contain null or undefined values (which is the core issue this PR addresses), typing the parameter as Record<string, string> is technically inaccurate and bypasses TypeScript's strict checks. Typing it as Record<string, string | null | undefined> and using a proper type guard in .filter ensures type safety and prevents future developers from accidentally removing the typeof fullModel === "string" check as "dead code".
  2. Efficiency: The string replacement fullModel.replace(prefix, "") is currently performed twice per mapped element. We can compute it once and reuse it.
export function buildNodeAliasModels(
  modelAliases: Record<string, string | null | undefined> | null | undefined,
  providerId: string,
  nodePrefix: string
): NodeAliasModel[] {
  const prefix = providerId + "/";
  return Object.entries(modelAliases || {})
    .filter((entry): entry is [string, string] => {
      const [, fullModel] = entry;
      return typeof fullModel === "string" && fullModel.startsWith(prefix);
    })
    .map(([aliasName, fullModel]) => {
      const modelId = fullModel.replace(prefix, "");
      return {
        id: modelId,
        name: aliasName,
        value: nodePrefix + "/" + modelId,
        source: "alias" as const,
      };
    });
}

Comment on lines +277 to +281
const nodeModels = buildNodeAliasModels(
modelAliases as Record<string, string>,
providerId,
nodePrefix
);

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.

medium

With the updated signature of buildNodeAliasModels that properly types modelAliases as nullable, the redundant type assertion as Record<string, string> can be safely removed.

Suggested change
const nodeModels = buildNodeAliasModels(
modelAliases as Record<string, string>,
providerId,
nodePrefix
);
const nodeModels = buildNodeAliasModels(
modelAliases,
providerId,
nodePrefix
);

Comment on lines +17 to +22
test("buildNodeAliasModels: skips null/undefined alias values instead of throwing", () => {
const modelAliases = {
a: "prov/x",
b: null,
c: undefined,
} as unknown as Record<string, string>;

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.

medium

With the updated type signature of buildNodeAliasModels, the type assertion as unknown as Record<string, string> is no longer necessary and can be removed.

Suggested change
test("buildNodeAliasModels: skips null/undefined alias values instead of throwing", () => {
const modelAliases = {
a: "prov/x",
b: null,
c: undefined,
} as unknown as Record<string, string>;
test("buildNodeAliasModels: skips null/undefined alias values instead of throwing", () => {
const modelAliases = {
a: "prov/x",
b: null,
c: undefined,
};

Comment on lines +45 to +48
assert.deepEqual(
buildNodeAliasModels({ x: undefined as unknown as string }, "prov", "prov"),
[]
);

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.

medium

With the updated type signature of buildNodeAliasModels, the type assertion as unknown as string is no longer necessary and can be removed.

Suggested change
assert.deepEqual(
buildNodeAliasModels({ x: undefined as unknown as string }, "prov", "prov"),
[]
);
assert.deepEqual(
buildNodeAliasModels({ x: undefined }, "prov", "prov"),
[]
);

@diegosouzapw
diegosouzapw merged commit 3323b5b into release/v3.8.43 Jul 2, 2026
3 of 7 checks passed
@diegosouzapw diegosouzapw mentioned this pull request Jul 2, 2026
@diegosouzapw
diegosouzapw deleted the fix/port-pr-2247-null-alias-guard branch July 3, 2026 04:58
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