mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 03:30:40 +00:00
feat(cli): make ACP message rewrite timeout configurable (#3475)
* feat(cli): make ACP message rewrite timeout configurable The rewrite LLM call timeout was hardcoded to 30s. For business scenarios where the final turn contains a large KPI table or report body, that call can exceed 30s and get aborted silently — losing the user-visible conclusion. Adds optional `timeoutMs` to `MessageRewriteConfig` (default 30000) so large/slow rewrites can be tuned per deployment. Fixes #3474 * docs(cli): translate timeoutMs note to English, fix code block
This commit is contained in:
parent
bf561fa495
commit
4d1d430390
4 changed files with 110 additions and 3 deletions
|
|
@ -203,4 +203,102 @@ describe('MessageRewriteMiddleware', () => {
|
|||
expect(meta['turnIndex']).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeoutMs config', () => {
|
||||
it('should use configured timeoutMs for the rewrite abort signal', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const capturedSignals: AbortSignal[] = [];
|
||||
const { LlmRewriter } = await import('./LlmRewriter.js');
|
||||
(
|
||||
LlmRewriter as unknown as {
|
||||
mockImplementation: (fn: unknown) => void;
|
||||
}
|
||||
).mockImplementation(() => ({
|
||||
rewrite: vi.fn((_content: unknown, signal: AbortSignal) => {
|
||||
capturedSignals.push(signal);
|
||||
return new Promise((_resolve, reject) => {
|
||||
signal.addEventListener('abort', () =>
|
||||
reject(new Error('aborted')),
|
||||
);
|
||||
});
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockSendUpdate = vi.fn().mockResolvedValue(undefined);
|
||||
const middleware = new MessageRewriteMiddleware(
|
||||
{} as Config,
|
||||
{
|
||||
enabled: true,
|
||||
target: 'all',
|
||||
prompt: 'test prompt',
|
||||
timeoutMs: 5_000,
|
||||
},
|
||||
mockSendUpdate,
|
||||
);
|
||||
|
||||
await middleware.interceptUpdate({
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
content: { type: 'text', text: 'content' },
|
||||
} as unknown as SessionUpdate);
|
||||
await middleware.flushTurn();
|
||||
|
||||
expect(capturedSignals).toHaveLength(1);
|
||||
expect(capturedSignals[0].aborted).toBe(false);
|
||||
|
||||
// Advance past the configured 5s timeout
|
||||
await vi.advanceTimersByTimeAsync(5_100);
|
||||
expect(capturedSignals[0].aborted).toBe(true);
|
||||
|
||||
await middleware.waitForPendingRewrites();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it('should default to 30s when timeoutMs is not provided', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const capturedSignals: AbortSignal[] = [];
|
||||
const { LlmRewriter } = await import('./LlmRewriter.js');
|
||||
(
|
||||
LlmRewriter as unknown as {
|
||||
mockImplementation: (fn: unknown) => void;
|
||||
}
|
||||
).mockImplementation(() => ({
|
||||
rewrite: vi.fn((_content: unknown, signal: AbortSignal) => {
|
||||
capturedSignals.push(signal);
|
||||
return new Promise((_resolve, reject) => {
|
||||
signal.addEventListener('abort', () =>
|
||||
reject(new Error('aborted')),
|
||||
);
|
||||
});
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockSendUpdate = vi.fn().mockResolvedValue(undefined);
|
||||
const middleware = new MessageRewriteMiddleware(
|
||||
{} as Config,
|
||||
{ enabled: true, target: 'all', prompt: 'test prompt' },
|
||||
mockSendUpdate,
|
||||
);
|
||||
|
||||
await middleware.interceptUpdate({
|
||||
sessionUpdate: 'agent_message_chunk',
|
||||
content: { type: 'text', text: 'content' },
|
||||
} as unknown as SessionUpdate);
|
||||
await middleware.flushTurn();
|
||||
|
||||
expect(capturedSignals).toHaveLength(1);
|
||||
await vi.advanceTimersByTimeAsync(29_000);
|
||||
expect(capturedSignals[0].aborted).toBe(false);
|
||||
await vi.advanceTimersByTimeAsync(1_500);
|
||||
expect(capturedSignals[0].aborted).toBe(true);
|
||||
|
||||
await middleware.waitForPendingRewrites();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,10 +27,13 @@ const debugLogger = createDebugLogger('MESSAGE_REWRITE');
|
|||
* LlmRewriter rewrites the accumulated content
|
||||
* 4. Rewritten text is emitted as agent_message_chunk with _meta.rewritten=true
|
||||
*/
|
||||
const DEFAULT_REWRITE_TIMEOUT_MS = 30_000;
|
||||
|
||||
export class MessageRewriteMiddleware {
|
||||
private readonly turnBuffer: TurnBuffer;
|
||||
private readonly rewriter: LlmRewriter;
|
||||
private readonly target: MessageRewriteConfig['target'];
|
||||
private readonly timeoutMs: number;
|
||||
private turnIndex = 0;
|
||||
|
||||
constructor(
|
||||
|
|
@ -41,6 +44,7 @@ export class MessageRewriteMiddleware {
|
|||
this.turnBuffer = new TurnBuffer();
|
||||
this.rewriter = new LlmRewriter(config, rewriteConfig);
|
||||
this.target = rewriteConfig.target;
|
||||
this.timeoutMs = rewriteConfig.timeoutMs ?? DEFAULT_REWRITE_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,8 +113,8 @@ export class MessageRewriteMiddleware {
|
|||
this.turnIndex++;
|
||||
const turnIdx = this.turnIndex;
|
||||
|
||||
// Always enforce a 30s timeout, combined with caller's signal if provided
|
||||
const timeoutSignal = AbortSignal.timeout(30_000);
|
||||
// Always enforce a timeout, combined with caller's signal if provided
|
||||
const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
|
||||
const rewriteSignal = signal
|
||||
? AbortSignal.any([signal, timeoutSignal])
|
||||
: timeoutSignal;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ Add to `settings.json`:
|
|||
"target": "all",
|
||||
"promptFile": ".qwen/rewrite-prompt.txt",
|
||||
"model": "qwen3-plus",
|
||||
"contextTurns": 1
|
||||
"contextTurns": 1,
|
||||
"timeoutMs": 60000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`timeoutMs` sets the per-rewrite LLM call timeout in milliseconds. Defaults to 30000.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ export interface MessageRewriteConfig {
|
|||
* 1 = last rewrite only (default), "all" = all previous rewrites,
|
||||
* 0 = no context, N = last N rewrites. */
|
||||
contextTurns?: number | 'all';
|
||||
/** Per-rewrite LLM call timeout in milliseconds. Defaults to 30000 (30s). */
|
||||
timeoutMs?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue