|
@@ -15,7 +15,6 @@ export type SessionRow = typeof sessions.$inferSelect
|
|
|
|
|
|
|
|
const createSchema: z.ZodType<CreateSessionBody> = z.object({
|
|
const createSchema: z.ZodType<CreateSessionBody> = z.object({
|
|
|
profileId: z.string().optional(),
|
|
profileId: z.string().optional(),
|
|
|
- provider: z.enum(['claude', 'codex']).optional(),
|
|
|
|
|
model: z.string().min(1).optional(),
|
|
model: z.string().min(1).optional(),
|
|
|
title: z.string().max(200).optional(),
|
|
title: z.string().max(200).optional(),
|
|
|
permissionMode: z.enum(['ask', 'auto', 'readonly']).optional(),
|
|
permissionMode: z.enum(['ask', 'auto', 'readonly']).optional(),
|
|
@@ -30,7 +29,6 @@ const patchSchema = z.object({
|
|
|
// null 表示恢复为跟随 Profile(仅后台)
|
|
// null 表示恢复为跟随 Profile(仅后台)
|
|
|
permissionMode: z.enum(['ask', 'auto', 'readonly']).nullable().optional(),
|
|
permissionMode: z.enum(['ask', 'auto', 'readonly']).nullable().optional(),
|
|
|
systemPromptAppend: z.string().max(20000).optional(),
|
|
systemPromptAppend: z.string().max(20000).optional(),
|
|
|
- provider: z.enum(['claude', 'codex']).optional(),
|
|
|
|
|
profileId: z.string().optional(),
|
|
profileId: z.string().optional(),
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -50,16 +48,17 @@ export class SessionService {
|
|
|
) {}
|
|
) {}
|
|
|
|
|
|
|
|
async create(source: string, body: CreateSessionBody): Promise<SessionRow> {
|
|
async create(source: string, body: CreateSessionBody): Promise<SessionRow> {
|
|
|
|
|
+ rejectProvider(body)
|
|
|
const data = parse(createSchema, body)
|
|
const data = parse(createSchema, body)
|
|
|
const isAdmin = source === 'admin'
|
|
const isAdmin = source === 'admin'
|
|
|
// 权限模式由 Profile 决定,平台不能指定(ADR-0015)
|
|
// 权限模式由 Profile 决定,平台不能指定(ADR-0015)
|
|
|
if (!isAdmin && data.permissionMode !== undefined) throw badRequest('权限模式由 Profile 决定,不能在创建会话时指定')
|
|
if (!isAdmin && data.permissionMode !== undefined) throw badRequest('权限模式由 Profile 决定,不能在创建会话时指定')
|
|
|
const profile = this.profileFor(source, data.profileId)
|
|
const profile = this.profileFor(source, data.profileId)
|
|
|
- const provider: ProviderId = data.provider ?? profile.provider
|
|
|
|
|
|
|
+ // 引擎由 Profile 决定(ADR-0015)
|
|
|
|
|
+ const provider: ProviderId = profile.provider
|
|
|
const s = this.settings.get()
|
|
const s = this.settings.get()
|
|
|
- const providerSettings = s.providers[provider]
|
|
|
|
|
- if (!providerSettings.enabled) throw badRequest(`引擎 ${provider} 未启用`)
|
|
|
|
|
- const model = data.model ?? (provider === profile.provider ? profile.model : providerSettings.defaultModel)
|
|
|
|
|
|
|
+ if (!s.providers[provider].enabled) throw badRequest(`引擎 ${provider} 未启用`)
|
|
|
|
|
+ const model = data.model ?? profile.model
|
|
|
this.checkModel(provider, model, profile)
|
|
this.checkModel(provider, model, profile)
|
|
|
|
|
|
|
|
const id = newId('ses')
|
|
const id = newId('ses')
|
|
@@ -146,16 +145,16 @@ export class SessionService {
|
|
|
|
|
|
|
|
patch(id: string, source: string, body: unknown): SessionRow {
|
|
patch(id: string, source: string, body: unknown): SessionRow {
|
|
|
const row = this.getRow(id, source)
|
|
const row = this.getRow(id, source)
|
|
|
|
|
+ rejectProvider(body)
|
|
|
const data = parse(patchSchema, body)
|
|
const data = parse(patchSchema, body)
|
|
|
if (source !== 'admin' && data.permissionMode !== undefined) throw badRequest('权限模式由 Profile 决定,不能修改')
|
|
if (source !== 'admin' && data.permissionMode !== undefined) throw badRequest('权限模式由 Profile 决定,不能修改')
|
|
|
const changesRunConfig = data.model !== undefined || data.permissionMode !== undefined || data.systemPromptAppend !== undefined
|
|
const changesRunConfig = data.model !== undefined || data.permissionMode !== undefined || data.systemPromptAppend !== undefined
|
|
|
- || data.provider !== undefined || data.profileId !== undefined
|
|
|
|
|
|
|
+ || data.profileId !== undefined
|
|
|
if (changesRunConfig && this.statusOf(id) !== 'idle') throw conflict('session_busy', '会话正在运行,请等待完成后再修改')
|
|
if (changesRunConfig && this.statusOf(id) !== 'idle') throw conflict('session_busy', '会话正在运行,请等待完成后再修改')
|
|
|
- const switching = (data.provider && data.provider !== row.provider) || (data.profileId && data.profileId !== row.profileId)
|
|
|
|
|
- if (switching && row.providerSessionId) throw badRequest('会话已经开始对话,不能再更换引擎或 Profile')
|
|
|
|
|
|
|
+ if (data.profileId && data.profileId !== row.profileId && row.providerSessionId) throw badRequest('会话已经开始对话,不能再更换 Profile')
|
|
|
|
|
|
|
|
const profile = data.profileId ? this.profileFor(source, data.profileId) : this.effectiveProfile(row).profile
|
|
const profile = data.profileId ? this.profileFor(source, data.profileId) : this.effectiveProfile(row).profile
|
|
|
- const provider = data.provider ?? (data.profileId ? profile.provider : row.provider)
|
|
|
|
|
|
|
+ const provider = data.profileId ? profile.provider : row.provider
|
|
|
if (!this.settings.get().providers[provider].enabled) throw badRequest(`引擎 ${provider} 未启用`)
|
|
if (!this.settings.get().providers[provider].enabled) throw badRequest(`引擎 ${provider} 未启用`)
|
|
|
// 更换 Profile 且没有指定模型时,改用新 Profile 的默认模型
|
|
// 更换 Profile 且没有指定模型时,改用新 Profile 的默认模型
|
|
|
const model = data.model ?? (data.profileId ? profile.model : row.model)
|
|
const model = data.model ?? (data.profileId ? profile.model : row.model)
|
|
@@ -191,3 +190,8 @@ export class SessionService {
|
|
|
await this.files.removeWorkspace(row.workspaceDir, this.settings.get().paths.workspaceRoot)
|
|
await this.files.removeWorkspace(row.workspaceDir, this.settings.get().paths.workspaceRoot)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/** 引擎由 Profile 决定,不能单独指定(明确报错,避免被静默忽略) */
|
|
|
|
|
+function rejectProvider(body: unknown) {
|
|
|
|
|
+ if (body && typeof body === 'object' && 'provider' in body) throw badRequest('引擎由 Profile 决定,不能单独指定;请选择对应引擎的 Profile')
|
|
|
|
|
+}
|