mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 17:46:16 -10:00
fix(core): keep the hook context block on the turn that compacts
A suppressed UserPromptSubmit block is decided against pre-compaction history, but automatic compaction replaces that history inside the same send, so the block's only copy is dropped before the request is built and the model sees it zero times on that turn. Pass the suppressed payload down and re-append it when the send compacts. Also restore the unconditional append in the ACP path when the prompt resolved to no parts: `parts.length > 0` gated the append as well as the suppression, so an empty-parts turn reached `toParts([])` and threw. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmvec3c4pgi
This commit is contained in:
1 parent
4449af7f75
commit
bdb593edb3
5 files changed
+58
-5
No files matched your search
@@ -6826,10 +6826,10 @@ export class Session implements SessionContext {
|
||||
const additionalContext = hookOutput?.getAdditionalContext();
|
||||
if (
|
||||
additionalContext &&
|
||||
parts.length > 0 &&
|
||||
!this.#getCurrentChat().findLastHistoryEntry((entry) =>
|
||||
hasUserPromptSubmitContext(entry, additionalContext),
|
||||
)
|
||||
(parts.length === 0 ||
|
||||
!this.#getCurrentChat().findLastHistoryEntry((entry) =>
|
||||
hasUserPromptSubmitContext(entry, additionalContext),
|
||||
))
|
||||
) {
|
||||
parts = [
|
||||
...parts,
|
||||
|
||||
@@ -3996,6 +3996,8 @@ export class LlmClient {
|
||||
let userPromptRecordPayload: UserPromptRecordPayload | undefined;
|
||||
/** Hook context appended to this turn's request, if any. */
|
||||
let appendedHookContext: string | undefined;
|
||||
/** Hook context suppressed as already delivered; compression may need it back. */
|
||||
let suppressedHookContext: string | undefined;
|
||||
let hooksEnabled: boolean;
|
||||
let messageBus: ReturnType<Config['getMessageBus']>;
|
||||
let userPromptSubmitFailureMessage = 'UserPromptSubmit hook failed';
|
||||
@@ -4122,6 +4124,8 @@ export class LlmClient {
|
||||
createUserPromptSubmitContextPart(additionalContext),
|
||||
];
|
||||
appendedHookContext = additionalContext;
|
||||
} else {
|
||||
suppressedHookContext = additionalContext;
|
||||
}
|
||||
if (messageType === SendMessageType.UserQuery) {
|
||||
// A suppressed turn appends nothing, so it records no
|
||||
@@ -4680,6 +4684,7 @@ export class LlmClient {
|
||||
messageType === SendMessageType.UserQuery ? prompt_id : undefined,
|
||||
options?.retractDeliveredOutputOnRetry,
|
||||
options?.onPreparedRequest,
|
||||
suppressedHookContext,
|
||||
);
|
||||
|
||||
// Assemble the outgoing request. IDE context is merged into the
|
||||
|
||||
@@ -3576,6 +3576,31 @@ describe('LlmChat', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('re-appends a suppressed hook block when the send compacts', async () => {
|
||||
mockCompressOnce(
|
||||
compressed(1000, 200, [userText('summary'), modelText('ok')]),
|
||||
);
|
||||
mockStream(textStream('answer'));
|
||||
|
||||
await drain(
|
||||
await chat.sendMessageStream(
|
||||
'test-model',
|
||||
{ message: 'go' },
|
||||
'prompt-id-hook-compaction',
|
||||
undefined,
|
||||
{ suppressedHookContext: 'CTX' },
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
chat
|
||||
.getHistory()
|
||||
.some((entry) =>
|
||||
entry.parts?.some((part) => part.text?.includes('CTX')),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('yields a COMPRESSED stream event as the first event after auto-compression succeeds', async () => {
|
||||
mockCompressOnce(
|
||||
compressed(1000, 200, [userText('summary'), modelText('ok')]),
|
||||
|
||||
@@ -123,6 +123,7 @@ import {
|
||||
makeChatCompressionEvent,
|
||||
} from '../telemetry/types.js';
|
||||
import type { UiTelemetryService } from '../telemetry/uiTelemetry.js';
|
||||
import { createUserPromptSubmitContextPart } from '../hooks/user-prompt-submit-context.js';
|
||||
import {
|
||||
type ChatCompressionInfo,
|
||||
CompressionStatus,
|
||||
@@ -679,6 +680,12 @@ export interface LlmChatSendOptions {
|
||||
* public transcript (#13319).
|
||||
*/
|
||||
retractDeliveredOutputOnRetry?: boolean;
|
||||
/**
|
||||
* A `UserPromptSubmit` block suppressed as already delivered. Compression
|
||||
* can drop the history entry holding its only copy inside this same send,
|
||||
* so it is re-appended when the send compacts.
|
||||
*/
|
||||
suppressedHookContext?: string;
|
||||
}
|
||||
|
||||
/** @deprecated Use `LlmChatSendOptions`; retained until a future major release. */
|
||||
@@ -3396,6 +3403,17 @@ export class LlmChat {
|
||||
},
|
||||
);
|
||||
}
|
||||
if (
|
||||
options?.suppressedHookContext &&
|
||||
compressionInfo.compressionStatus === CompressionStatus.COMPRESSED
|
||||
) {
|
||||
// Compaction replaced the history holding the block's only copy, so
|
||||
// the model would otherwise not see it on this turn at all.
|
||||
userContent.parts = [
|
||||
...(userContent.parts ?? []),
|
||||
createUserPromptSubmitContextPart(options.suppressedHookContext),
|
||||
];
|
||||
}
|
||||
const localPromptTokensAfterCompression = shouldForceFromHard
|
||||
? estimatePromptTokens(
|
||||
this.lastPromptTokenCount > 0 ? [] : this.getHistoryShallow(true),
|
||||
|
||||
@@ -721,6 +721,7 @@ export class Turn {
|
||||
private readonly promptIdentity?: string,
|
||||
private readonly retractDeliveredOutputOnRetry?: boolean,
|
||||
private readonly onPreparedRequest?: LlmPreparedRequestCallback,
|
||||
private readonly suppressedHookContext?: string,
|
||||
) {
|
||||
this.goalContext = goalContext ? { ...goalContext } : undefined;
|
||||
}
|
||||
@@ -739,11 +740,15 @@ export class Turn {
|
||||
const sendOptions =
|
||||
this.promptIdentity !== undefined ||
|
||||
this.retractDeliveredOutputOnRetry === true ||
|
||||
this.onPreparedRequest !== undefined
|
||||
this.onPreparedRequest !== undefined ||
|
||||
this.suppressedHookContext !== undefined
|
||||
? {
|
||||
...(this.onPreparedRequest
|
||||
? { onPreparedRequest: this.onPreparedRequest }
|
||||
: {}),
|
||||
...(this.suppressedHookContext
|
||||
? { suppressedHookContext: this.suppressedHookContext }
|
||||
: {}),
|
||||
...(this.promptIdentity ? { promptId: this.promptIdentity } : {}),
|
||||
...(this.retractDeliveredOutputOnRetry
|
||||
? { retractDeliveredOutputOnRetry: true }
|
||||
|
||||
Reference in new issue
Block a user