Add Submodel channel support#1774
Conversation
WalkthroughAdds a new “Submodel” channel (type 53) and API type, wires it into adaptor selection, provides a new relay adaptor with basic OpenAI pass-through and request/response handling, defines model list and channel name, updates default model ratios, and exposes the new channel in the web UI with predefined models. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Web UI
participant Relay as Relay API
participant Adapt as Submodel Adaptor
participant Up as Submodel Upstream
User->>UI: Select channel 53 (SubModel)
UI->>Relay: OpenAI-style request (stream? flag)
Relay->>Adapt: GetAdaptor(APITypeSubmodel)
Note right of Adapt: Build URL, set Authorization header
Adapt->>Up: POST /v1/... with body
alt Streaming
Up-->>Adapt: SSE stream
Adapt-->>Relay: stream chunks
Relay-->>UI: stream chunks
else Non-stream
Up-->>Adapt: JSON response
Adapt-->>Relay: JSON response
Relay-->>UI: JSON response
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
Pre-merge checks (3 passed)✅ Passed checks (3 passed)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (8)
relay/channel/submodel/constants.go (2)
16-16: Use const for ChannelName.Slightly tighter typing; avoids accidental reassignment.
-var ChannelName = "submodel" +const ChannelName = "submodel"
3-14: Avoid frontend/backend model list duplication.These models are hardcoded here and again in the web modal; they’ll drift. Prefer serving this list from the backend (e.g., via GetModelList) and letting the UI consume it.
web/src/constants/channel.constants.js (1)
162-166: Minor label casing consistency.Most labels are proper-cased words (e.g., “OpenRouter”, “MiniMax”); recommend “Submodel” rather than “SubModel”.
- label: 'SubModel', + label: 'Submodel',web/src/helpers/render.jsx (1)
345-347: Redundant explicit case.Default already returns null; keeping this case is fine for clarity, but it’s not required.
relay/relay_adaptor.go (1)
3-41: Import grouping/style.Go tooling will reformat, but keep imports grouped/sorted to avoid lint noise.
web/src/components/table/channels/modals/EditTagModal.jsx (1)
121-123: DRY: avoid duplicating the model list here.Prefer sourcing from a single place (e.g., backend ModelList via helper or API) to prevent drift from relay/channel/submodel/constants.go.
setting/ratio_setting/model_ratio.go (1)
255-266: Keep model names DRY and in sync with the Submodel model list.These entries duplicate the list in relay/channel/submodel/constants.go. Consider centralizing the model names (or generating this section) to avoid drift when the upstream list changes.
Would you like a small generator to validate that defaultModelRatio contains all entries from submodel.ModelList and flags extras?
relay/channel/submodel/adaptor.go (1)
31-33: Optional: initialize guarded capabilities.If this channel intentionally supports only chat/completions, set a capability flag in Init (or channel meta) and short-circuit unsupported relay modes earlier with a consistent error type.
I can add a small capability gate to fail fast with a typed error and unit tests.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
common/api_type.go(1 hunks)constant/api_type.go(1 hunks)constant/channel.go(2 hunks)relay/channel/submodel/adaptor.go(1 hunks)relay/channel/submodel/constants.go(1 hunks)relay/relay_adaptor.go(2 hunks)setting/ratio_setting/model_ratio.go(1 hunks)web/src/components/table/channels/modals/EditTagModal.jsx(1 hunks)web/src/constants/channel.constants.js(1 hunks)web/src/helpers/render.jsx(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-05T17:14:17.246Z
Learnt from: neotf
PR: QuantumNous/new-api#1511
File: setting/ratio_setting/model_ratio.go:118-123
Timestamp: 2025-08-05T17:14:17.246Z
Learning: Claude models handle "-thinking" variants differently from Gemini models. For Claude models, only the base model (without "-thinking") gets an entry in defaultModelRatio map. The "-thinking" variants rely on the Claude relay handler stripping the suffix using strings.TrimSuffix(textRequest.Model, "-thinking") before looking up the ratio, so they automatically use the base model's ratio.
Applied to files:
setting/ratio_setting/model_ratio.go
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.
Applied to files:
web/src/components/table/channels/modals/EditTagModal.jsx
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
PR: QuantumNous/new-api#1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.
Applied to files:
web/src/components/table/channels/modals/EditTagModal.jsx
🧬 Code graph analysis (3)
common/api_type.go (2)
constant/channel.go (1)
ChannelTypeSubmodel(53-53)constant/api_type.go (1)
APITypeSubmodel(36-36)
relay/relay_adaptor.go (3)
constant/api_type.go (1)
APITypeSubmodel(36-36)relay/channel/submodel/adaptor.go (1)
Adaptor(16-17)relay/channel/adapter.go (1)
Adaptor(13-30)
relay/channel/submodel/adaptor.go (11)
relay/common/relay_info.go (1)
RelayInfo(74-120)dto/claude.go (1)
ClaudeRequest(187-203)dto/audio.go (1)
AudioRequest(9-15)relay/common/relay_utils.go (1)
GetFullRequestURL(11-23)relay/channel/api_request.go (2)
SetupApiRequestHeader(25-37)DoApiRequest(39-72)dto/openai_request.go (2)
GeneralOpenAIRequest(25-78)OpenAIResponsesRequest(768-790)dto/rerank.go (1)
RerankRequest(10-18)dto/embedding.go (1)
EmbeddingRequest(21-32)types/error.go (1)
NewAPIError(82-90)relay/channel/openai/relay-openai.go (2)
OaiStreamHandler(111-175)OpenaiHandler(177-256)relay/channel/submodel/constants.go (2)
ModelList(3-14)ChannelName(16-16)
🔇 Additional comments (9)
common/api_type.go (1)
70-72: Mapping looks correct.ChannelTypeSubmodel → APITypeSubmodel wiring aligns with the new adaptor path.
constant/channel.go (2)
53-57: ChannelType placement is OK relative to Dummy sentinel.Inserted before ChannelTypeDummy as required by the sentinel comment. Ensure all channel-type–indexed arrays/slices account for the new index.
113-114: Base URL index alignment check.Index 53 maps to https://llm.submodel.ai; verify no off-by-one in ChannelBaseURLs consumers.
relay/relay_adaptor.go (1)
105-107: Factory wiring LGTM.New case returns &submodel.Adaptor{} as expected.
relay/channel/submodel/adaptor.go (5)
34-36: URL construction is correct and consistent with existing channels.Using relaycommon.GetFullRequestURL handles special gateways and keeps routing uniform.
38-42: Header setup looks good for OpenAI-compatible JSON and SSE.Delegating to channel.SetupApiRequestHeader plus Authorization bearer aligns with other adaptors.
Confirm Submodel requires standard "Authorization: Bearer " (no vendor-specific header). If not, I can patch this method.
44-49: Pass-through ConvertOpenAIRequest is fine for OpenAI-compatible payloads.Minimal validation is appropriate here.
67-74: Response handling via OpenAI handlers is appropriate.This assumes Submodel returns OpenAI-compatible shapes for both streaming and non-streaming.
If Submodel’s SSE/event format diverges, we’ll need a custom stream handler; let me know and I can add it.
76-82: Handle SubModel channel in EditTagModal.jsx
EditTagModal.jsx’slocalModelsswitch handlescase 52(Vidu) but never handlescase 53(SubModel, backend’s ChannelTypeSubmodel = 53), so the SubModel list never appears. In the switch around line 120, add acase 53:branch that assigns the adaptor’sGetModelList()tolocalModels.⛔ Skipped due to learnings
Learnt from: AAEE86 PR: QuantumNous/new-api#1658 File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569 Timestamp: 2025-08-27T02:15:25.448Z Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.Learnt from: AAEE86 PR: QuantumNous/new-api#1658 File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569 Timestamp: 2025-08-27T02:15:25.448Z Learning: In EditChannelModal.jsx, the database stores mapped keys (display names) in the models field after applying model mapping transformations. When loading a channel, data.models contains the mapped keys, not the original model names. The filtering logic if (data.models.includes(key)) in the initialization is correct.
| APITypeMoonshot // this one is only for count, do not add any channel after this | ||
| APITypeDummy // this one is only for count, do not add any channel after this | ||
| APITypeSubmodel | ||
| ) |
There was a problem hiding this comment.
💡 Verification agent
❓ Verification inconclusive
Do not append after sentinels; place Submodel before Moonshot/Dummy to keep iota invariants.
Both APITypeMoonshot and APITypeDummy are marked “do not add any channel after this”. Adding APITypeSubmodel after them violates that contract and risks subtle enum drift elsewhere.
Apply this diff to move Submodel before the sentinels:
- APITypeMoonshot // this one is only for count, do not add any channel after this
- APITypeDummy // this one is only for count, do not add any channel after this
- APITypeSubmodel
+ APITypeSubmodel
+ APITypeMoonshot // this one is only for count, do not add any channel after this
+ APITypeDummy // this one is only for count, do not add any channel after thisAlso consider freezing enum values (explicit numbers) to prevent future accidental shifts.
Move APITypeSubmodel before the sentinel constants (APITypeMoonshot/APITypeDummy). Placing it after them breaks the intended iota invariant and risks subtle enum drift.
🤖 Prompt for AI Agents
In constant/api_type.go around lines 34 to 37, the enum value APITypeSubmodel is
placed after the sentinel count constants (APITypeMoonshot/APITypeDummy), which
breaks the intended iota ordering and can cause enum drift; fix by moving
APITypeSubmodel so it appears before the sentinel constants, preserving the iota
sequence, and update or keep comments to indicate the sentinel entries are only
for count and must remain last.
Summary by CodeRabbit