mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-11 12:12:19 -10:00
* feat(web-shell): render the compression outcome from structured meta The compression commands stream their outcome as English sentences over ACP, which a host that owns its own UI language cannot render. They now carry the outcome as machine-readable `_meta` next to the sentence, the transcript record and replay pass it through, and the web shell renders it in the user's language with the token counts formatted for that language. A compression that finds nothing to strip reports a terminal no-op phase rather than reusing the pending one: the payload-driven row has to state the outcome, and a pending row is dropped once the turn ends. The invocation note rides its own `_meta` key rather than sharing the result's. The daemon UI reducer folds a turn's text frames into one block and spreads `_meta` key by key, so a shared key would be overwritten by the frame that follows. Nothing else reads the new keys and no frame gains the discrete-message marker, so a host that renders text as-is sees the same sentences it saw before. Opening a session whose last action was a compression also reconciles the composer's context counters once. They are seeded from the last usage frame in the replay, and a compression emits none, so they described the context it replaced; a read is only written back while the session is live and its model and context window have settled, which is what the reconcile waits for. * docs(design): describe the structured compression outcome The design records why the outcome travels as metadata beside the sentence, why the invocation note needs its own key, why the no-op is a terminal phase, and how the composer counters reconcile on an attach that ends on a compression. It also keeps the compatibility analysis for other clients, which is a constraint of this change rather than a follow-up.
92 lines
2.9 KiB
TypeScript
92 lines
2.9 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* Structured companion to the English sentence `/compress` streams over ACP.
|
|
*
|
|
* The daemon ships on its own release cadence, so this is parsed defensively:
|
|
* an unreadable payload falls back to rendering the sentence as before rather
|
|
* than dropping the message.
|
|
*/
|
|
export interface ContextCompressionResult {
|
|
originalTokenCount: number;
|
|
newTokenCount: number;
|
|
originalTokenCountIsEstimated: boolean;
|
|
newTokenCountIsEstimated: boolean;
|
|
/** Server-authored advisory. Free-form text, not a translation key. */
|
|
warning?: string;
|
|
}
|
|
|
|
export type ContextCompressionMeta =
|
|
| { phase: 'notice'; instructionsLimit: number }
|
|
| { phase: 'progress' }
|
|
| { phase: 'noop' }
|
|
| { phase: 'done'; result: ContextCompressionResult };
|
|
|
|
export function parseContextCompressionMeta(
|
|
value: unknown,
|
|
): ContextCompressionMeta | undefined {
|
|
if (!isRecord(value)) return undefined;
|
|
if (value['phase'] === 'notice') {
|
|
const instructionsLimit = toCount(value['instructionsLimit']);
|
|
return instructionsLimit === undefined
|
|
? undefined
|
|
: { phase: 'notice', instructionsLimit };
|
|
}
|
|
if (value['phase'] === 'progress') return { phase: 'progress' };
|
|
if (value['phase'] === 'noop') return { phase: 'noop' };
|
|
if (value['phase'] !== 'done') return undefined;
|
|
const result = parseContextCompressionResult(value);
|
|
return result ? { phase: 'done', result } : undefined;
|
|
}
|
|
|
|
function parseContextCompressionResult(
|
|
value: unknown,
|
|
): ContextCompressionResult | undefined {
|
|
if (!isRecord(value)) return undefined;
|
|
const originalTokenCount = toCount(value['originalTokenCount']);
|
|
const newTokenCount = toCount(value['newTokenCount']);
|
|
if (originalTokenCount === undefined || newTokenCount === undefined) {
|
|
return undefined;
|
|
}
|
|
const warning = value['warning'];
|
|
return {
|
|
originalTokenCount,
|
|
newTokenCount,
|
|
originalTokenCountIsEstimated:
|
|
value['originalTokenCountIsEstimated'] === true,
|
|
newTokenCountIsEstimated: value['newTokenCountIsEstimated'] === true,
|
|
...(typeof warning === 'string' && warning.length > 0 ? { warning } : {}),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Token count for a compression line, grouped for the UI language rather than
|
|
* the browser's locale. `~` marks a locally estimated count, the same
|
|
* convention the TUI banner uses (#9309).
|
|
*/
|
|
export function formatCompressionTokens(
|
|
count: number,
|
|
isEstimated: boolean,
|
|
language: string,
|
|
): string {
|
|
return `${isEstimated ? '~' : ''}${count.toLocaleString(language)}`;
|
|
}
|
|
|
|
/** A finite, non-negative integer count — token totals and character limits. */
|
|
function toCount(value: unknown): number | undefined {
|
|
return typeof value === 'number' &&
|
|
Number.isFinite(value) &&
|
|
Number.isInteger(value) &&
|
|
value >= 0
|
|
? value
|
|
: undefined;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
}
|