Skip to content

feat: add combo rename and public aliases#238

Merged
Ingwannu merged 1 commit into
lidge-jun:devfrom
eachann1024:feat/combo-rename-alias-dev
Jul 22, 2026
Merged

feat: add combo rename and public aliases#238
Ingwannu merged 1 commit into
lidge-jun:devfrom
eachann1024:feat/combo-rename-alias-dev

Conversation

@eachann1024

@eachann1024 eachann1024 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Adds atomic combo renaming and configurable public model names, superseding the closed #214 implementation on the current dev branch.

Motivation

Combo IDs are internal configuration keys, but they currently leak into every public surface as combo/<id>. Users need to rename a combo safely and choose the exact client-facing model name during creation or editing, without breaking existing configuration references.

Features Added

  • Atomically rename a combo ID, including its config key and all persisted model references.
  • Choose a public model name while creating or editing a combo.
  • Use a bare name such as aka, a custom prefix such as vendor/aka, or leave it blank for the default combo/<id>.
  • Route requests made with either the canonical combo/<id> slug or the configured public alias.
  • Surface the public name consistently in request logs, the Codex catalog, /v1/models, the management API, and the GUI.
  • Define exact alias collisions as a public API contract: a combo alias wins over an identically named non-OpenAI provider selector while the combo exists; renaming the alias or deleting the combo restores the physical selector.

Changes

  • Added rename and alias management endpoints and GUI controls, including the add-combo flow.
  • Added shared public-slug resolution for combo routing and catalog generation.
  • Preserved independently callable raw /v1/models selectors while keeping exact combo-alias collisions deterministic.
  • Documented collision precedence and restoration behavior in the README.
  • Added regression coverage for routing, /v1/models, Codex catalog output, alias rename, and combo deletion using an exact deepseek/deepseek-chat collision.
  • Migrated combo references in disabledModels, subagentModels, injectionModel, shadowCallIntercept.model, and Claude Code model fields during rename or alias changes, then immediately refreshed generated Claude agent definitions.
  • Made the Models page and Dashboard injection-model picker alias-aware, including disabled-state round trips.
  • Polished the Models combo summary and sidebar language popover stacking so rounded clipping and row alignment remain correct across workspace layouts.
  • Added English, Russian, Simplified Chinese, Japanese, Korean, and German UI strings.
  • Added focused unit, API, catalog, request-log, workspace-data, and end-to-end coverage.

Benefits

  • Existing combo configuration remains valid after a rename.
  • Public model names can stay stable even when internal combo IDs change.
  • Clients are no longer forced to use the combo/ prefix.
  • GUI and API consumers see the same model slug used for routing.
  • Alias collisions have an explicit, documented, and reversible winner instead of depending on list order.

Testing

  • 163 affected-suite tests passed across combo management/routing, Codex catalog, and server failover/model-list coverage.
  • The collision lifecycle tests verify routing, /v1/models, Codex catalog output, alias rename restoration, and combo deletion restoration for deepseek/deepseek-chat.
  • The full local suite reported 3,449 passed and 5 failures. The same 5 tests/server-auth.test.ts failures reproduce on pure upstream dev (fdacd146); the newer base commit is documentation-only.
  • Root TypeScript typecheck passed.
  • GUI i18n lint and full lint passed.
  • GUI TypeScript and Vite production build passed; only the existing chunk-size warning remains.
  • Privacy scan and diff checks passed.
  • GPT-5.6 Sol High adversarial validation: passed. The core implementation review reported No findings. after its actionable findings were fixed and regression-tested; the later collision-contract update is supported by the explicit tests above.

Example Configuration

{
  "combos": {
    "coding": {
      "model": "openai/gpt-5.2-codex",
      "alias": "my-coding-model"
    }
  }
}

Both combo/coding and my-coding-model route to the same combo, while public catalog surfaces expose my-coding-model.

Related: #133
Supersedes: #214

@eachann1024

eachann1024 commented Jul 22, 2026

Copy link
Copy Markdown
Author

This revision was reviewed with GPT-5.6 Sol at high reasoning effort.

GPT-5.6 Sol High adversarial validation: passed.

The final review covered the complete 28-file merge diff at commit 4d25b376, rebased onto the latest dev (2aa4430c). The review identified two additional P2 issues before approval:

  • ordinary /v1/models deduplication used the encoded Codex slug even though that endpoint emits raw provider selectors, which could hide an independently callable nested selector;
  • combo rename or alias changes migrated Claude-facing configuration but did not immediately regenerate the owned Claude agent definition files.

Both findings were fixed and regression-tested. The same GPT-5.6 Sol High reviewer then rechecked the complete diff on the latest base and reported No findings.

Final validation:

  • exactly one PR commit
  • 222 focused tests passed
  • root TypeScript typecheck passed
  • GUI i18n lint, full lint, and production build passed
  • Chrome create-flow and 962×890 visual QA passed
  • privacy scan, diff check, commit guardrails, and PR metadata guardrails passed

The final changes in this PR were made from the GPT-5.6 Sol High findings.

@eachann1024
eachann1024 force-pushed the feat/combo-rename-alias-dev branch 3 times, most recently from aa86686 to 4d25b37 Compare July 22, 2026 06:58

@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.

This is a substantial and potentially useful feature, and no backdoor/code-execution issue was found. It is still a draft with no CI, and the bare-alias policy needs explicit resolution: aliases are resolved before provider routing and can shadow non-OpenAI provider model IDs in routing and the public catalog. Please document and test the intended collision policy (or require namespaced aliases), then run the full matrix before marking ready.

@eachann1024

Copy link
Copy Markdown
Author

Other repairs:

Resources 2026-07-22 at 14 28 47@2x

Copy link
Copy Markdown
Owner

Thanks for the update. I rechecked the PR timeline: the current head is still 4d25b37, committed before my review, so there is no new code addressing the requested change. The added screenshot and external-model review summary do not resolve the blocking policy issue.

Please explicitly address bare alias collisions with provider model IDs. Either:

  1. require combo aliases to remain namespaced, or
  2. document the precedence as an intentional public API contract and add regression tests covering routing, /v1/models, catalog output, rename, and deletion when an alias matches an independently callable non-OpenAI provider selector.

After that, push the changes, run the full CI matrix, and mark the PR ready for review. The PR remains CHANGES_REQUESTED until those items are present.

@eachann1024
eachann1024 force-pushed the feat/combo-rename-alias-dev branch from 4d25b37 to 0cbc947 Compare July 22, 2026 07:31
@eachann1024
eachann1024 force-pushed the feat/combo-rename-alias-dev branch from 0cbc947 to 3535632 Compare July 22, 2026 07:44
@eachann1024

eachann1024 commented Jul 22, 2026

Copy link
Copy Markdown
Author

@Ingwannu

Addressed in 35356324 on the latest dev base (3a5f984d). I chose option 2 and made the precedence an explicit public API contract in the README:

  • an exact combo alias wins over an identically named non-OpenAI provider selector in routing, /v1/models, and the Codex catalog while the combo exists;
  • renaming the alias or deleting the combo immediately restores the physical provider selector.

The requested regression matrix now uses an exact deepseek/deepseek-chat collision:

  • routing plus rename/deletion restoration: tests/combos.test.ts
  • /v1/models plus rename/deletion restoration: tests/server-combo-failover-e2e.test.ts
  • Codex catalog collision plus rename/deletion restoration: tests/codex-catalog.test.ts
  • raw nested provider selectors that do not exactly collide remain independently visible: tests/server-combo-failover-e2e.test.ts

Validation completed locally:

  • 163 affected-suite tests passed
  • TypeScript typecheck passed
  • GUI lint passed
  • privacy scan and diff checks passed
  • full suite: 3,449 passed, 5 failed; the same 5 tests/server-auth.test.ts failures reproduce on pure upstream dev (fdacd146), and the newer base commit is documentation-only

I am marking the PR ready for review now so the full CI matrix can run on the updated head.

Update: GitHub created the Cross-platform CI and React Doctor runs for 35356324, but both ended as action_required before creating any jobs. A maintainer approval is required for the fork workflows to start.

@eachann1024
eachann1024 marked this pull request as ready for review July 22, 2026 07:45
@eachann1024
eachann1024 requested a review from lidge-jun as a code owner July 22, 2026 07:45

@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: 353563247b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/combos/resolve.ts

export function tryPickComboModel(config: OcxConfig, modelId: string): ComboPick | null {
const comboId = parseComboModelId(modelId);
const comboId = resolveComboId(config, modelId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Bypass aliases when routing combo child attempts

When a combo target's concrete selector is also a combo alias (for example combo A targets a/m1 and combo B uses alias a/m1), resolving aliases here makes routeModel() treat the child request from handleComboResponses as combo B even though options.comboAttempt only skipped the outer failover loop. The attempt recorded for A can be sent to B's provider/model, or fail because B has no available targets, so combo failover/round-robin no longer honors its configured targets; combo child routing needs a no-alias path.

Useful? React with 👍 / 👎.

Comment thread src/codex/catalog.ts
Comment on lines +1483 to 1486
// Combo management stores the public alias, while canonical `combo/<id>` references
// remain valid for backward compatibility through slugEquals below.
if (m.alias !== undefined && stored === catalogModelSlug(m)) return false;
if (slugEquals(stored, m.provider, m.id)) return false;

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 Avoid advertising providers still shadowed by disabled combos

When disabledModels contains an aliased combo, this removes the combo from the catalog input while the provider model with the same public slug remains eligible, so buildCatalogEntries can advertise the provider row under that slug. Requests for that picker row still resolve through resolveComboId() first because routing ignores disabledModels, so users who disable a shadowing combo can be shown a restored provider selector that actually hits the hidden combo; either keep the provider row hidden or make disabled combo aliases yield to the provider.

Useful? React with 👍 / 👎.

Comment on lines +1804 to +1808
if (config.claudeCode) {
const claudeCode = { ...config.claudeCode };
for (const field of ["model", "smallFastModel"] as const) {
if (claudeCode[field]) claudeCode[field] = migrateAgentReference(claudeCode[field]);
}

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 Migrate sidecar model references on combo renames

When a combo alias/id changes while a web-search or vision sidecar is configured to use the old public model (webSearchSidecar.model, visionSidecar.model, or the Claude-specific overrides), this migration leaves those model references unchanged even though it rewrites disabled/subagent/injection/shadow/Claude main fields. The next sidecar request will try the retired alias/canonical id and either fall back to the default provider or fail, so renames are not atomic for sidecar users; include the sidecar model fields in this migration.

Useful? React with 👍 / 👎.

Comment thread src/codex/catalog.ts
Comment on lines 1927 to +1929
.filter(m => typeof m.slug === "string"
&& !(m.slug as string).includes("/")
&& m.owned_by !== COMBO_NAMESPACE

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 Preserve bare combo aliases during empty catalog syncs

When routed model discovery returns empty, the sync code has a fallback that preserves existing routed rows, but bare combo aliases are now marked owned_by: "combo" and contain no /. This line removes them from the native preservation set, while the routed fallback below only keeps slash-containing slugs, so a transient discovery/cache failure deletes still-configured bare alias rows from the Codex catalog until a later successful sync restores them; treat combo-owned bare aliases as routed rows in the preservation path.

Useful? React with 👍 / 👎.

Comment on lines +1182 to +1186
const visibleRouted = [...new Set(models
.filter(m => ![...disabled].some(stored =>
stored === catalogModelSlug(m) || slugEquals(stored, m.provider, m.id)
))
.map(catalogModelSlug))];

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 Register combo aliases for Claude agent context marking

When this picker returns an aliased combo via catalogModelSlug(m), saving it stores the public alias in config.subagentModels. The Claude agent sync later looks up context windows using combo/<id>/Claude aliases derived from provider+id, not CatalogModel.alias, so an aliased combo with a large context window will generate ocx-* agents without the needed [1m] marker and Claude Code can under-account context/compact too late; either register alias keys in the context-window map or keep the canonical combo id for that downstream path.

Useful? React with 👍 / 👎.

@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.

Re-reviewed commit 3535632 after the requested alias-collision contract and regression matrix were added. The fix consistently applies combo precedence across routing, /v1/models, catalog output, rename, and deletion restoration. Full local suite: 3,454 tests passed; typecheck, GUI lint/build, privacy scan, and diff check passed. GitHub Ubuntu/macOS/Windows and React Doctor checks passed. Full 30-file security review found no malware, backdoor, credential exfiltration, auth bypass, or unsafe command/file/network execution. Approved for dev.

@Ingwannu
Ingwannu merged commit 6bc48b7 into lidge-jun:dev Jul 22, 2026
9 of 10 checks passed
@eachann1024
eachann1024 deleted the feat/combo-rename-alias-dev branch July 22, 2026 08:52
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.

2 participants