fix(cli): use live context for /btw side questions (#3429)
Some checks are pending
Qwen Code CI / Lint (push) Waiting to run
Qwen Code CI / Test (push) Blocked by required conditions
Qwen Code CI / Test-5 (push) Blocked by required conditions
Qwen Code CI / Test-8 (push) Blocked by required conditions
Qwen Code CI / Test-1 (push) Blocked by required conditions
Qwen Code CI / Test-2 (push) Blocked by required conditions
Qwen Code CI / Test-3 (push) Blocked by required conditions
Qwen Code CI / Test-4 (push) Blocked by required conditions
Qwen Code CI / Test-6 (push) Blocked by required conditions
Qwen Code CI / Test-7 (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run

This commit is contained in:
易良 2026-04-19 15:06:14 +08:00 committed by GitHub
parent 6ebe28453d
commit 8ad9a5b467
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 146 additions and 1 deletions

View file

@ -52,6 +52,12 @@ describe('btwCommand', () => {
beforeEach(() => {
vi.clearAllMocks();
mockGetCacheSafeParams.mockReturnValue({
generationConfig: {},
history: [],
model: 'test-model',
version: 1,
});
mockContext = createMockCommandContext({
services: {
config: createConfig(),
@ -157,6 +163,107 @@ describe('btwCommand', () => {
);
});
it('should fall back to live Gemini client context when no saved cache params exist', async () => {
mockGetCacheSafeParams.mockReturnValue(null);
mockRunForkedAgent.mockResolvedValue({
text: 'answer',
usage: { inputTokens: 5, outputTokens: 2, cacheHitTokens: 0 },
});
const geminiClient = {
getHistory: vi
.fn()
.mockReturnValue([
{ role: 'user', parts: [{ text: '杭州天气如何?' }] },
]),
getChat: vi.fn().mockReturnValue({
getGenerationConfig: vi.fn().mockReturnValue({
systemInstruction: 'You are helpful',
tools: [],
}),
}),
};
const liveContext = createMockCommandContext({
services: {
config: createConfig({
getGeminiClient: () => geminiClient,
}),
},
});
await btwCommand.action!(liveContext, 'how ?');
await flushPromises();
expect(mockRunForkedAgent).toHaveBeenCalledWith(
expect.objectContaining({
cacheSafeParams: expect.objectContaining({
generationConfig: expect.objectContaining({
systemInstruction: 'You are helpful',
}),
history: [{ role: 'user', parts: [{ text: '杭州天气如何?' }] }],
model: 'test-model',
}),
userMessage: expect.stringContaining('how ?'),
}),
);
});
it('should prefer live Gemini client history over a stale saved cache snapshot', async () => {
mockGetCacheSafeParams.mockReturnValue({
generationConfig: {
systemInstruction: 'stale system prompt',
tools: [],
},
history: [{ role: 'user', parts: [{ text: '旧问题' }] }],
model: 'stale-model',
version: 99,
});
mockRunForkedAgent.mockResolvedValue({
text: 'answer',
usage: { inputTokens: 5, outputTokens: 2, cacheHitTokens: 0 },
});
const geminiClient = {
getHistory: vi.fn().mockReturnValue([
{ role: 'user', parts: [{ text: '杭州天气如何?' }] },
{ role: 'user', parts: [{ text: '请顺便解释一下湿度怎么看' }] },
]),
getChat: vi.fn().mockReturnValue({
getGenerationConfig: vi.fn().mockReturnValue({
systemInstruction: 'live system prompt',
tools: [],
}),
}),
};
const liveContext = createMockCommandContext({
services: {
config: createConfig({
getGeminiClient: () => geminiClient,
}),
},
});
await btwCommand.action!(liveContext, 'how ?');
await flushPromises();
expect(mockRunForkedAgent).toHaveBeenCalledWith(
expect.objectContaining({
cacheSafeParams: expect.objectContaining({
generationConfig: expect.objectContaining({
systemInstruction: 'live system prompt',
}),
history: [
{ role: 'user', parts: [{ text: '杭州天气如何?' }] },
{ role: 'user', parts: [{ text: '请顺便解释一下湿度怎么看' }] },
],
model: 'test-model',
}),
}),
);
});
it('should add error item on failure and clear btwItem', async () => {
mockRunForkedAgent.mockRejectedValue(new Error('API error'));

View file

@ -47,6 +47,44 @@ function buildBtwPrompt(question: string): string {
].join('\n');
}
function getBtwCacheSafeParams(
context: CommandContext,
): ReturnType<typeof getCacheSafeParams> {
const geminiClient = context.services.config?.getGeminiClient();
if (
geminiClient &&
typeof geminiClient === 'object' &&
typeof geminiClient.getChat === 'function' &&
typeof geminiClient.getHistory === 'function'
) {
const chat = geminiClient.getChat();
if (
chat &&
typeof chat === 'object' &&
typeof chat.getGenerationConfig === 'function'
) {
const generationConfig = chat.getGenerationConfig();
if (generationConfig) {
const fullHistory = geminiClient.getHistory(true);
const maxHistoryEntries = 40;
const history =
fullHistory.length > maxHistoryEntries
? fullHistory.slice(-maxHistoryEntries)
: fullHistory;
return {
generationConfig,
history,
model: context.services.config?.getModel() ?? '',
version: 0,
};
}
}
}
return getCacheSafeParams();
}
/**
* Run a side question using runForkedAgent (cache path).
*
@ -63,7 +101,7 @@ async function askBtw(
const { config } = context.services;
if (!config) throw new Error('Config not loaded');
const cacheSafeParams = getCacheSafeParams();
const cacheSafeParams = getBtwCacheSafeParams(context);
if (!cacheSafeParams)
throw new Error(t('No conversation context available for /btw'));