Skip to content

Commit 0323f54

Browse files
committed
feat: 增强模型配置和版本自动升级功能
添加模型配置的 maxOutputTokens 支持并优化自动升级流程 改进用户取消操作时的提示信息 优化模型添加后的自动切换逻辑 缩短版本检查缓存时间至1小时
1 parent afb11a3 commit 0323f54

11 files changed

Lines changed: 81 additions & 24 deletions

File tree

src/agent/Agent.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ export class Agent {
185185
baseUrl: modelConfig.baseUrl,
186186
temperature: modelConfig.temperature ?? this.config.temperature,
187187
maxContextTokens: modelConfig.maxContextTokens ?? this.config.maxContextTokens, // 上下文窗口(压缩判断)
188-
maxOutputTokens: this.config.maxOutputTokens, // 输出限制(API max_tokens)
188+
maxOutputTokens: modelConfig.maxOutputTokens ?? this.config.maxOutputTokens, // 输出限制(API max_tokens)
189189
timeout: this.config.timeout,
190190
});
191191

@@ -278,8 +278,8 @@ export class Agent {
278278
: await this.runLoop(enhancedMessage, context, loopOptions);
279279

280280
if (!result.success) {
281-
// 如果是用户中止,返回空字符串(不抛出异常)
282-
if (result.error?.type === 'aborted') {
281+
// 如果是用户中止或用户拒绝,返回空字符串(不抛出异常)
282+
if (result.error?.type === 'aborted' || result.metadata?.shouldExitLoop) {
283283
return ''; // 返回空字符串,让调用方自行处理
284284
}
285285
// 其他错误则抛出异常
@@ -530,7 +530,6 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl
530530
// checkAndCompactInLoop 返回是否发生了压缩
531531
// 🆕 传入上一轮 LLM 返回的真实 prompt tokens(比估算更准确)
532532
const didCompact = await this.checkAndCompactInLoop(
533-
messages,
534533
context,
535534
turnsCount,
536535
lastPromptTokens, // 首轮为 undefined,使用估算;后续轮次使用真实值
@@ -863,7 +862,7 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl
863862
}
864863
logger.debug('==================================\n');
865864

866-
// 🆕 检查是否应该退出循环(ExitPlanMode 返回时设置此标记
865+
// 🆕 检查是否应该退出循环(ExitPlanMode 或用户拒绝时设置此标记
867866
if (result.metadata?.shouldExitLoop) {
868867
logger.debug('🚪 检测到退出循环标记,结束 Agent 循环');
869868

@@ -1366,15 +1365,13 @@ IMPORTANT: Execute according to the approved plan above. Follow the steps exactl
13661365
* 在 Agent 循环中检查并执行压缩
13671366
* 仅使用 LLM 返回的真实 usage.promptTokens 进行判断(不再估算)
13681367
*
1369-
* @param messages - 实际发送给 LLM 的消息数组
13701368
* @param context - 聊天上下文
13711369
* @param currentTurn - 当前轮次
13721370
* @param actualPromptTokens - LLM 返回的真实 prompt tokens(必须,来自上一轮响应)
13731371
* @param onCompacting - 压缩状态回调
13741372
* @returns 是否发生了压缩
13751373
*/
13761374
private async checkAndCompactInLoop(
1377-
messages: Message[],
13781375
context: ChatContext,
13791376
currentTurn: number,
13801377
actualPromptTokens?: number,

src/agent/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export interface LoopResult {
169169
configuredMaxTurns?: number;
170170
actualMaxTurns?: number;
171171
hitSafetyLimit?: boolean;
172-
shouldExitLoop?: boolean; // ExitPlanMode 设置此标记以退出循环
172+
shouldExitLoop?: boolean; // ExitPlanMode 或用户拒绝时设置此标记以退出循环
173173
targetMode?: PermissionMode; // Plan 模式批准后的目标权限模式
174174
planContent?: string; // Plan 模式批准后的方案内容
175175
};

src/config/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface ModelConfig {
5959
// 可选:模型特定参数
6060
temperature?: number;
6161
maxContextTokens?: number; // 上下文窗口大小
62+
maxOutputTokens?: number; // 输出 token 限制
6263
topP?: number;
6364
topK?: number;
6465

src/services/VersionChecker.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const CACHE_DIR = path.join(
1818
);
1919
const CACHE_FILE = path.join(CACHE_DIR, 'version-cache.json');
2020

21-
// 缓存有效期:24 小时
22-
const CACHE_TTL = 24 * 60 * 60 * 1000;
21+
// 缓存有效期:1 小时
22+
const CACHE_TTL = 1 * 60 * 60 * 1000;
2323

2424
// npm registry URL
2525
const NPM_REGISTRY_URL = `https://registry.npmmirror.com/${PACKAGE_NAME}/latest`;
@@ -224,13 +224,58 @@ export function formatUpdateMessage(result: VersionCheckResult): string | null {
224224
}
225225

226226
/**
227-
* 启动时版本检查(后台执行,不阻塞)
227+
* 执行自动升级(后台进程,不阻塞主进程)
228+
* @returns 升级提示消息
229+
*/
230+
async function performUpgrade(
231+
currentVersion: string,
232+
latestVersion: string
233+
): Promise<string> {
234+
const { spawn } = await import('child_process');
235+
236+
try {
237+
const updateCommand = `npm install -g blade-code@${latestVersion} --registry https://registry.npmjs.org`;
238+
239+
// 使用 spawn + detached + unref 在后台运行升级
240+
// 这样主进程退出后,升级进程会继续运行完成安装
241+
const updateProcess = spawn(updateCommand, {
242+
stdio: 'ignore',
243+
shell: true,
244+
detached: true,
245+
});
246+
updateProcess.unref();
247+
248+
return `⬆️ 正在后台升级 ${currentVersion}${latestVersion},下次启动生效`;
249+
} catch {
250+
return (
251+
`\x1b[33m⚠️ Update available: ${currentVersion}${latestVersion}\x1b[0m\n` +
252+
` Run \x1b[36mnpm install -g ${PACKAGE_NAME}@latest\x1b[0m to update`
253+
);
254+
}
255+
}
256+
257+
/**
258+
* 启动时版本检查并自动升级
228259
*
229-
* @returns Promise<string | null> 更新提示消息,如果没有更新则返回 null
260+
* @param autoUpgrade - 是否自动升级(默认 true)
261+
* @returns Promise<string | null> 提示消息,如果没有更新则返回 null
230262
*/
231-
export async function checkVersionOnStartup(): Promise<string | null> {
263+
export async function checkVersionOnStartup(
264+
autoUpgrade = true
265+
): Promise<string | null> {
232266
try {
233267
const result = await checkVersion();
268+
269+
if (!result.hasUpdate || !result.latestVersion) {
270+
return null;
271+
}
272+
273+
// 自动升级
274+
if (autoUpgrade) {
275+
return await performUpgrade(result.currentVersion, result.latestVersion);
276+
}
277+
278+
// 仅显示提示
234279
return formatUpdateMessage(result);
235280
} catch {
236281
return null;

src/store/slices/sessionSlice.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,5 +258,6 @@ export const createSessionSlice: StateCreator<
258258
},
259259
}));
260260
},
261+
261262
},
262263
});

src/tools/execution/PipelineStages.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,10 @@ export class ConfirmationStage implements PipelineStage {
359359
await confirmationHandler.requestConfirmation(confirmationDetails);
360360

361361
if (!response.approved) {
362-
execution.abort(`User rejected execution: ${response.reason || 'No reason provided'}`);
362+
execution.abort(
363+
`User rejected execution: ${response.reason || 'No reason provided'}`,
364+
{ shouldExitLoop: true }
365+
);
363366
return;
364367
}
365368

src/tools/types/ExecutionTypes.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class ToolExecution {
9696
return this.aborted || (this.context.signal?.aborted ?? false);
9797
}
9898

99-
abort(reason?: string): void {
99+
abort(reason?: string, options?: { shouldExitLoop?: boolean }): void {
100100
this.aborted = true;
101101
this.result = {
102102
success: false,
@@ -106,6 +106,7 @@ export class ToolExecution {
106106
type: ToolErrorType.EXECUTION_ERROR,
107107
message: reason || 'Execution aborted',
108108
},
109+
metadata: options?.shouldExitLoop ? { shouldExitLoop: true } : undefined,
109110
};
110111
}
111112

src/ui/App.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,11 @@ export const AppWrapper: React.FC<AppProps> = (props) => {
126126
}
127127
}
128128

129-
// 8. 后台检查版本更新(不阻塞启动)
130-
checkVersionOnStartup().then((updateMessage) => {
131-
if (updateMessage) {
129+
// 8. 后台检查版本更新并自动升级(不阻塞启动)
130+
checkVersionOnStartup().then((message) => {
131+
if (message) {
132132
console.log('');
133-
console.log(updateMessage);
133+
console.log(message);
134134
console.log('');
135135
}
136136
});

src/ui/components/BladeInterface.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,13 @@ export const BladeInterface: React.FC<BladeInterfaceProps> = ({
310310
appActions.showModelEditWizard(model);
311311
});
312312

313+
const handleModelAddComplete = useMemoizedFn((addedConfig: SetupConfig) => {
314+
sessionActions.addAssistantMessage(
315+
`✅ 已添加模型配置: ${addedConfig.name},并已切换到该模型`
316+
);
317+
closeModal();
318+
});
319+
313320
const handleModelEditComplete = useMemoizedFn((updatedConfig: SetupConfig) => {
314321
sessionActions.addAssistantMessage(`✅ 已更新模型配置: ${updatedConfig.name}`);
315322
closeModal();
@@ -537,7 +544,7 @@ export const BladeInterface: React.FC<BladeInterfaceProps> = ({
537544
onComplete={
538545
inlineModelWizardMode === 'edit'
539546
? handleModelEditComplete
540-
: closeModal
547+
: handleModelAddComplete
541548
}
542549
onCancel={closeModal}
543550
/>

src/ui/components/ModelConfigWizard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,8 +464,9 @@ export const ModelConfigWizard: React.FC<ModelConfigWizardProps> = ({
464464
// setup 模式:由父组件(BladeInterface)负责创建模型
465465
onComplete(setupConfig);
466466
} else if (mode === 'add') {
467-
// add 模式:直接在这里创建模型,然后通知父组件关闭
468-
await configActions().addModel(setupConfig);
467+
// add 模式:直接在这里创建模型,然后自动切换到新模型
468+
const newModel = await configActions().addModel(setupConfig);
469+
await configActions().setCurrentModel(newModel.id);
469470
onComplete(setupConfig);
470471
} else {
471472
if (!modelId) {

0 commit comments

Comments
 (0)