Summary
AI Gateway custom model IDs are accepted as strings, but later serialized into an unescaped @-delimited modelKey format:
aiGateway@<modelId>@teable
If the model ID itself contains @, for example custom/image-model@beta, the serialized key becomes:
aiGateway@custom/image-model@beta@teable
Current parsers split on @ and interpret this as model=custom/image-model and name=beta, so the original model ID is truncated and the teable provider suffix is lost.
Steps to reproduce
- Configure AI Gateway.
- Add a custom Gateway model ID containing
@, for example:
- Let Teable serialize it into a model key:
aiGateway@custom/image-model@beta@teable
- Parse or select this model through the AI model selection / image model config path.
Expected behavior
The Gateway model ID should round-trip as:
The provider suffix should still be recognized as:
The key should still be treated as an AI Gateway model.
Actual behavior
The key is parsed as:
{
type: 'aiGateway',
model: 'custom/image-model',
name: 'beta'
}
As a result:
- frontend Gateway recognition can fail because
name !== 'teable'
- image model metadata lookup uses the truncated ID
custom/image-model
- matching against the stored Gateway model ID
custom/image-model@beta fails
- backend model config / test paths can send the truncated model ID to the Gateway provider
Evidence
Relevant code anchors:
gatewayModelSchema defines id: z.string() and has no delimiter restriction.
- The UI path can forward custom search text as the model ID.
- The save path checks that
id and label are present before saving.
- The frontend serializes Gateway models as
aiGateway@${model.id}@teable.
- Frontend and backend parsers use
modelKey.split('@').
- Backend model config / test paths use the parsed
model as the Gateway model ID.
- Image model config also extracts the image model ID with
modelKey.split('@') and then matches it against gatewayModels[].id.
Observed minimal repro result:
{
"modelId": "custom/image-model@beta",
"modelKey": "aiGateway@custom/image-model@beta@teable",
"parsed": {
"type": "aiGateway",
"model": "custom/image-model",
"name": "beta"
},
"frontendIsGatewayModelKey": false,
"imageModelId": "custom/image-model",
"matchedGatewayModel": false
}
Suggested fix
Either:
- Reject
@ in Gateway model IDs at schema/UI/save time and show a validation message before saving, or
- Change
modelKey encoding to escape or structurally encode the model ID, then update all parsers consistently.
If preserving the current key format is preferred, the smaller fix is to validate Gateway model IDs and add regression tests for delimiter-bearing IDs.
Suggested tests:
- frontend
parseModelKey / isGatewayModelKey regression
- backend
parseModelKey or model test selection regression
- image model config lookup regression
- schema/UI validation test if
@ is intentionally disallowed
Additional context / Related coverage
A current GitHub search did not identify exact existing GitHub issue/PR coverage for terms such as aiGateway parseModelKey @ model id, gatewayModels modelKey split @, and parseModelKey aiGateway.
A live end-to-end Gateway call was not run because it requires a configured Teable stack and AI Gateway key. The issue is reproducible from the serialization/parsing contract and local parser behavior.
Submitted with Codex.
Summary
AI Gateway custom model IDs are accepted as strings, but later serialized into an unescaped
@-delimitedmodelKeyformat:If the model ID itself contains
@, for examplecustom/image-model@beta, the serialized key becomes:Current parsers split on
@and interpret this asmodel=custom/image-modelandname=beta, so the original model ID is truncated and theteableprovider suffix is lost.Steps to reproduce
@, for example:Expected behavior
The Gateway model ID should round-trip as:
The provider suffix should still be recognized as:
The key should still be treated as an AI Gateway model.
Actual behavior
The key is parsed as:
As a result:
name !== 'teable'custom/image-modelcustom/image-model@betafailsEvidence
Relevant code anchors:
gatewayModelSchemadefinesid: z.string()and has no delimiter restriction.idandlabelare present before saving.aiGateway@${model.id}@teable.modelKey.split('@').modelas the Gateway model ID.modelKey.split('@')and then matches it againstgatewayModels[].id.Observed minimal repro result:
{ "modelId": "custom/image-model@beta", "modelKey": "aiGateway@custom/image-model@beta@teable", "parsed": { "type": "aiGateway", "model": "custom/image-model", "name": "beta" }, "frontendIsGatewayModelKey": false, "imageModelId": "custom/image-model", "matchedGatewayModel": false }Suggested fix
Either:
@in Gateway model IDs at schema/UI/save time and show a validation message before saving, ormodelKeyencoding to escape or structurally encode the model ID, then update all parsers consistently.If preserving the current key format is preferred, the smaller fix is to validate Gateway model IDs and add regression tests for delimiter-bearing IDs.
Suggested tests:
parseModelKey/isGatewayModelKeyregressionparseModelKeyor model test selection regression@is intentionally disallowedAdditional context / Related coverage
A current GitHub search did not identify exact existing GitHub issue/PR coverage for terms such as
aiGateway parseModelKey @ model id,gatewayModels modelKey split @, andparseModelKey aiGateway.A live end-to-end Gateway call was not run because it requires a configured Teable stack and AI Gateway key. The issue is reproducible from the serialization/parsing contract and local parser behavior.
Submitted with Codex.