diff --git a/apps/admin-web/src/api/adminApiTypes.ts b/apps/admin-web/src/api/adminApiTypes.ts
index 715d40d64..ceb6db413 100644
--- a/apps/admin-web/src/api/adminApiTypes.ts
+++ b/apps/admin-web/src/api/adminApiTypes.ts
@@ -1174,6 +1174,7 @@ export interface AdminWalletRestrictionResponse {
wallet: AdminProfileWalletPayload;
}
export type AdminAgcAgentMode = 'codex' | 'cc';
+export type AdminAgcModelAccess = 'basic' | 'full';
export type AdminAgcModelProtocol =
| 'openai_responses'
| 'openai_chat'
@@ -1187,6 +1188,8 @@ export interface AdminAgcModel {
agentMode: AdminAgcAgentMode;
/** 上游线协议,与执行器正交。 */
protocol?: AdminAgcModelProtocol;
+ /** 该模型要求的会员档位,缺省 `basic`(失败开放)。 */
+ access?: AdminAgcModelAccess;
}
export interface AdminAgcModelCatalog {
diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx
index feb4ce9d3..3279a5342 100644
--- a/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx
+++ b/apps/admin-web/src/pages/AdminAgcModelsPage.test.tsx
@@ -101,10 +101,46 @@ test('new models default to Codex and save the selected Agent mode', async () =>
modelId: '',
enabled: true,
agentMode: 'cc',
+ access: 'basic',
});
});
});
+test('权限档可按模型单独选择并随目录保存', async () => {
+ const catalog = {
+ revision: 3,
+ defaultModelId: 'quality',
+ models: [
+ {
+ id: 'quality',
+ alias: '高质量',
+ modelId: 'gpt-6-astra',
+ enabled: true,
+ agentMode: 'codex' as const,
+ access: 'basic' as const,
+ },
+ ],
+ };
+ vi.mocked(getAgcModelCatalog).mockResolvedValue(catalog);
+ vi.mocked(saveAgcModelCatalog).mockImplementation(async (_, input) => ({
+ ...input,
+ revision: 4,
+ }));
+ render();
+ await screen.findByDisplayValue('gpt-6-astra');
+
+ const access = screen.getByLabelText('模型 1 权限档') as HTMLSelectElement;
+ expect(access.value).toBe('basic');
+ fireEvent.change(access, { target: { value: 'full' } });
+ fireEvent.click(screen.getByRole('button', { name: '保存' }));
+
+ await waitFor(() => {
+ const calls = vi.mocked(saveAgcModelCatalog).mock.calls;
+ const payload = calls[calls.length - 1]?.[1];
+ expect(payload?.models[0]).toMatchObject({ access: 'full' });
+ });
+});
+
test('协议与 Agent 模式是独立字段,可按模型单独选择', async () => {
const catalog = {
revision: 3,
diff --git a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx
index b009aa222..9e14400cb 100644
--- a/apps/admin-web/src/pages/AdminAgcModelsPage.tsx
+++ b/apps/admin-web/src/pages/AdminAgcModelsPage.tsx
@@ -118,6 +118,7 @@ export function AdminAgcModelsPage({
{ key: 'modelId', label: '实际模型名' },
{ key: 'agentMode', label: 'Agent 模式' },
{ key: 'protocol', label: '协议' },
+ { key: 'access', label: '权限档' },
{ key: 'enabled', label: '启用' },
{ key: 'default', label: '默认' },
{ key: 'actions', label: '操作' },
@@ -159,6 +160,7 @@ export function AdminAgcModelsPage({
enabled: true,
agentMode: 'codex',
protocol: 'openai_responses',
+ access: 'basic',
},
],
},
@@ -246,6 +248,21 @@ export function AdminAgcModelsPage({
+
+
+ |
, AppError>>()?;
@@ -297,6 +302,7 @@ fn catalog_dto(catalog: AgcModelCatalog) -> AdminAgcModelCatalog {
enabled: m.enabled,
agent_mode: m.agent_mode.as_str().to_string(),
protocol: Some(protocol),
+ access: m.access.as_str().to_string(),
}
})
.collect(),
diff --git a/server-rs/crates/shared-contracts/src/admin.rs b/server-rs/crates/shared-contracts/src/admin.rs
index 6516d4e20..a0ff50bbd 100644
--- a/server-rs/crates/shared-contracts/src/admin.rs
+++ b/server-rs/crates/shared-contracts/src/admin.rs
@@ -1704,6 +1704,11 @@ fn default_codex_agent_mode() -> String {
"codex".to_string()
}
+/// 存量后台请求缺省为 `basic`(失败开放),只有人工标注才收紧到 `full`。
+fn default_basic_agc_model_access() -> String {
+ "basic".to_string()
+}
+
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AdminAgcModel {
@@ -1718,6 +1723,9 @@ pub struct AdminAgcModel {
/// 服务端回读时始终给出解析后的具体值。
#[serde(default, skip_serializing_if = "Option::is_none")]
pub protocol: Option,
+ /// 该模型要求的会员档位:`basic`(缺省,对全部档位开放)/ `full`(后台人工标注)。
+ #[serde(default = "default_basic_agc_model_access")]
+ pub access: String,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|