Files
creatixchu 42164508c8 feat(llm): 在 compaction 中按路由为图片请求压力计价
Closes #2848.

- dsh-llm 新增 LlmAdapter.imageRequestPricing 同步钩子与 LlmImageRequestPricing/LlmImageRequestPrice 词汇,ctx.llm 按路由解析
- llm-deepseek 用官方公布的 v4 视觉计算器逐句移植(14px patch、3:1 降采样、384 上限、最坏对齐 pad)实现该钩子,复现请求投影的最旧优先 offload 与像素预算缩放;纯几何 requestImageDimensions 上移到 dsh-attachment
- token-meter 表层 fold 存储与路由无关的节点事实,measure() 按生效 envelope 的路由为图片出现处定价;锚点存快照并按同一路由重定价;TokenSurfaceNode 同时携带路由价 tokens 与固定启发式 heuristicTokens
- compaction 触发、保留与选段读取同一套路由价,记录的 shadowedTokenCount 保持启发式以维持 O(1) 投影 fold 一致
- llm-replay 支持按模型的 imageRequestTokens 声明;新增 keyless 的 image-compaction ACP 快照场景端到端验证装配应用
2026-08-24 19:15:30 +08:00

131 lines
5.4 KiB
TypeScript

/** Deterministic provider-independent image normalization. */
import sharp, { type Sharp } from 'sharp'
import { AttachmentError, requestImageDimensions } from '@deepseek-ai/dsh-attachment'
import type { ImageMediaType } from '@deepseek-ai/dsh-attachment'
import { encodeFirstWithinLimit, encodingLadder, isExhaustedEncoding } from './encoding.ts'
import { detectImage, encodedAlphaIsCompatible } from './image.ts'
import type { DetectedImage } from './image.ts'
/** Deployment-resolved policy for the persisted normalized attachment. */
export interface NormalizationPolicy {
/** Total-pixel budget; larger sources are downscaled proportionally. */
maxPixels: number
/** Long-edge cap in pixels applied after the total-pixel budget, bounding extreme aspect ratios. */
maxDimension: number
/** Encoded-byte target for the quality ladder; the smallest ladder output is kept when no quality fits. */
maxBytes: number
}
/** Normalized bytes beside the facts recorded by a durable reference. */
export interface NormalizedImage {
data: Uint8Array
mediaType: ImageMediaType
width: number
height: number
}
/**
* Whether bytes already satisfy the normalization requirements.
* @param detected - fully decoded source facts.
* @param bytes - encoded source length.
* @param policy - resolved normalization limits.
* @returns whether the source can pass through byte-identically.
*/
export function canPassThroughNormalization(
detected: DetectedImage,
bytes: number,
policy: NormalizationPolicy,
): boolean {
return detected.mediaType !== 'image/gif'
&& !detected.animated
&& !detected.carriesMetadata
&& detected.depth === 'uchar'
&& detected.space === 'srgb'
&& bytes <= policy.maxBytes
&& detected.width * detected.height <= policy.maxPixels
&& Math.max(detected.width, detected.height) <= policy.maxDimension
}
/** Assert that a normalized output is an 8-bit sRGB/sRGBA single-frame image with matching facts. */
async function verifyNormalizedImage(
image: NormalizedImage,
expectedAlpha: boolean | undefined,
): Promise<NormalizedImage> {
const detected = await detectImage(image.data)
if (detected.mediaType !== image.mediaType
|| detected.width !== image.width
|| detected.height !== image.height
|| detected.animated
|| detected.carriesMetadata
|| detected.depth !== 'uchar'
|| detected.space !== 'srgb'
|| !encodedAlphaIsCompatible(expectedAlpha, detected)) {
throw new AttachmentError(
'Image normalization did not produce a single-frame 8-bit sRGB image with matching metadata.',
'ATTACHMENT_WRITE_FAILED',
)
}
return image
}
/** Build one fixed-size, oriented, metadata-free sRGB pipeline from submitted bytes. */
function preparedPipeline(data: Uint8Array, width: number, height: number): Sharp {
return sharp(data, { failOn: 'error', limitInputPixels: false })
.rotate()
.toColourspace('srgb')
.resize({ width, height, fit: 'inside', withoutEnlargement: true })
}
/** Dimensions under the total-pixel budget, then the long-edge cap, without changing aspect ratio. */
function initialDimensions(detected: DetectedImage, policy: NormalizationPolicy): { width: number; height: number } {
const budgeted = requestImageDimensions(detected.width, detected.height, policy.maxPixels)
const longEdge = Math.max(budgeted.width, budgeted.height)
if (longEdge <= policy.maxDimension) return budgeted
const scale = policy.maxDimension / longEdge
return {
width: Math.max(1, Math.floor(budgeted.width * scale)),
height: Math.max(1, Math.floor(budgeted.height * scale)),
}
}
/**
* Produce the persisted provider-independent normalized version of one fully decoded source.
* The source is passed through only when it is already clean, single-frame, 8-bit sRGB/sRGBA,
* and inside every normalization limit. Re-encoding never removes transparency. When every
* ladder quality exceeds the byte target, the smallest ladder output is kept; provider byte
* caps stay enforced at the route that transmits the bytes.
* @param data - complete admitted source bytes.
* @param detected - fully decoded source facts.
* @param policy - resolved independent normalization limits.
* @returns verified provider-independent normalized bytes and metadata.
*/
export async function normalizeImage(
data: Uint8Array,
detected: DetectedImage,
policy: NormalizationPolicy,
): Promise<NormalizedImage> {
if (canPassThroughNormalization(detected, data.byteLength, policy)) {
return { data, mediaType: detected.mediaType, width: detected.width, height: detected.height }
}
try {
const { width, height } = initialDimensions(detected, policy)
const encoded = await encodeFirstWithinLimit(
encodingLadder(preparedPipeline(data, width, height), detected.hasAlpha),
policy.maxBytes,
)
const chosen = isExhaustedEncoding(encoded) ? encoded.smallest : encoded
return await verifyNormalizedImage(chosen, detected.mediaType === 'image/gif' ? undefined : detected.hasAlpha)
} catch (error) {
if (error instanceof AttachmentError) throw error
const source = detected.mediaType === 'image/png' && detected.depth !== 'uchar'
? `${detected.depth === 'ushort' ? '16-bit' : detected.depth} PNG`
: `${detected.depth} ${detected.mediaType.slice('image/'.length).toUpperCase()}`
throw new AttachmentError(
`The ${source} could not be converted to the normalized 8-bit sRGB form.`,
'ATTACHMENT_WRITE_FAILED',
{ cause: error },
)
}
}