refactor(llm): classify auxiliary request purpose
This commit is contained in:
@@ -13,7 +13,7 @@ This backend owns the compaction policy:
|
||||
- **Model-free pruning** — after pressure or canonical overflow qualifies, the optional [`ctx.toolResultPrune`](../compact-tool-result-prune/README.md) service rewrites oversized tool results before range selection. Compact-basic remeasures through `ctx.tokenMeter`, skips summarization when pressure becomes safe, and otherwise summarizes the pruned surface. Below-pressure post-step checks never prune.
|
||||
- **Retention** — compact the oldest whole surface units while preserving a recent tail and balanced tool-call/result cuts through the [`dsh-compact` boundary helpers](../compact/README.md#tool-pairing-boundaries). Turn boundaries do not protect old steps inside a runaway turn. An open indivisible tail declines until it closes. The optional pruner can repair an oversized closed tool unit when its text-bearing result is the removable bulk; indivisible non-tool units and non-prunable tool remainders remain out of scope.
|
||||
- **Convergence** — retry head-checkpoint compaction up to `compactionRetries`; reject a summary that does not shrink its source, and throw if retries cannot return below threshold.
|
||||
- **Summarization** — a direct `llm/stream` call uses the configured provider/model pair and cap, falling back to the latest logged request target and then the agent target, without running the loop-only `agent/request` seam. The call replays the conversation's own system prompt, tools, and shadowed-region messages verbatim and appends the compaction instruction as the final user message, so it reuses the provider's warm prefix cache instead of invalidating it. It sets `GenerateOptions.compact`, which adapters may forward as request attribution (the DeepSeek adapter sends `x-deepseek-harness-compact: 1`) without touching the model-visible body. Only returned text enters the checkpoint, excluding reasoning and tool calls that would leak private reasoning or create an orphaned call.
|
||||
- **Summarization** — a direct `llm/stream` call uses the configured provider/model pair and cap, falling back to the latest logged request target and then the agent target, without running the loop-only `agent/request` seam. The call replays the conversation's own system prompt, tools, and shadowed-region messages verbatim and appends the compaction instruction as the final user message, so it reuses the provider's warm prefix cache instead of invalidating it. It sets `GenerateOptions.purpose` to `compaction`, which adapters may forward as request attribution (the DeepSeek adapter sends `x-deepseek-harness-compact: 1`) without touching the model-visible body. Only returned text enters the checkpoint, excluding reasoning and tool calls that would leak private reasoning or create an orphaned call.
|
||||
- **Framing** — the replacement user message marks established checkpoint context with `<compacted-summary>` tags. The raw summary remains on the provenance event, and later automatic cycles merge the prior checkpoint.
|
||||
- **Lifecycle** — `compactRegion()` mutates `agent.session` and records its start, summary, replacement, and end. After asynchronous summarization it rejects a changed surface-node snapshot, while unrelated log-only events may append without invalidating the selected span. The serial `agent/post-step` listener checks pressure after successful output and tool work are durable but before `step/end`. Canonical provider overflow is handled through `agent/request-error` after the failed step closes.
|
||||
- **Overflow recovery** — provider-confirmed overflow needs no capacity metadata: it bypasses normal pressure and retention, prunes, then attempts one maximal balanced head reduction while leaving the newest indivisible unit. Retry is authorized whenever `surface.replaceGeneration` advances, including when pruning lands before later summary work throws. No replacement, an exhausted target-specific cap, cancellation, or an unknown/noncanonical error preserves the original provider failure.
|
||||
|
||||
@@ -138,7 +138,7 @@ export async function summarizeWithLlm(
|
||||
...input.tools === undefined ? {} : { tools: [...input.tools] },
|
||||
maxTokens: config.maxTokens,
|
||||
sessionId: agent.session.id,
|
||||
compact: true,
|
||||
purpose: 'compaction',
|
||||
...signal === undefined ? {} : { signal },
|
||||
}
|
||||
for await (const chunk of ctx.llm.stream(options)) assembler.push(chunk)
|
||||
|
||||
@@ -1094,7 +1094,7 @@ describe('default one-shot summarizer', () => {
|
||||
maxTokens: 321,
|
||||
signal: SIGNAL,
|
||||
sessionId: session.id,
|
||||
compact: true,
|
||||
purpose: 'compaction',
|
||||
})
|
||||
const instruction = adapter.lastOptions?.messages.at(-1)?.content[0]
|
||||
expect(instruction?.type === 'text' ? instruction.text : '').toContain('## Primary Request and Intent')
|
||||
|
||||
@@ -1334,7 +1334,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'GenerateOptions',
|
||||
declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n messages: Message[];\n system?: string;\n tools?: ToolSchema[];\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n compact?: boolean;\n}',
|
||||
declaration: 'export interface GenerateOptions {\n provider: string;\n model: string;\n messages: Message[];\n system?: string;\n tools?: ToolSchema[];\n temperature?: number;\n maxTokens?: number;\n stop?: string[];\n signal?: AbortSignal;\n sessionId?: Branded<\'SessionId\'>;\n purpose?: \'compaction\';\n}',
|
||||
},
|
||||
{
|
||||
name: 'GenericCallView',
|
||||
|
||||
@@ -38,7 +38,7 @@ The plugin registers the single provider route `deepseek`. A request selects it
|
||||
|
||||
## App attribution
|
||||
|
||||
Every request carries the shared attribution header from dsh-llm's `attributionHeaders()` - the mandatory `User-Agent` baseline identifying the harness (see [dsh-llm § App attribution](../llm/README.md#app-attribution-attributionts)). Direct DeepSeek requests and OpenAI-compatible gateway requests get no provider-specific app-attribution headers under this adapter contract; OpenRouter app attribution is deferred to a future explicit OpenRouter adapter or mode. A request with `GenerateOptions.compact` set (dsh-compact-basic's auxiliary summarization call) additionally carries `x-deepseek-harness-compact: 1`, so the host can separate compaction traffic from conversation requests.
|
||||
Every request carries the shared attribution header from dsh-llm's `attributionHeaders()` - the mandatory `User-Agent` baseline identifying the harness (see [dsh-llm § App attribution](../llm/README.md#app-attribution-attributionts)). Direct DeepSeek requests and OpenAI-compatible gateway requests get no provider-specific app-attribution headers under this adapter contract; OpenRouter app attribution is deferred to a future explicit OpenRouter adapter or mode. A request whose `GenerateOptions.purpose` is `compaction` (dsh-compact-basic's auxiliary summarization call) additionally carries `x-deepseek-harness-compact: 1`, so the host can separate compaction traffic from conversation requests.
|
||||
|
||||
## Wire-format notes (verified live + against the official docs)
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ export class DeepSeekAdapter extends LlmAdapter {
|
||||
...options.sessionId !== undefined
|
||||
? { 'x-deepseek-harness-session-id': String(options.sessionId) }
|
||||
: {},
|
||||
...options.compact === true
|
||||
...options.purpose === 'compaction'
|
||||
? { 'x-deepseek-harness-compact': '1' }
|
||||
: {},
|
||||
}
|
||||
|
||||
@@ -129,7 +129,6 @@ describe('DeepSeekAdapter against a mock server', () => {
|
||||
expect(server.headers[0]).not.toHaveProperty('http-referer')
|
||||
expect(server.headers[0]).not.toHaveProperty('x-openrouter-title')
|
||||
expect(server.headers[0]).not.toHaveProperty('x-openrouter-categories')
|
||||
// A conversation request carries no compaction marker.
|
||||
expect(server.headers[0]).not.toHaveProperty('x-deepseek-harness-compact')
|
||||
})
|
||||
|
||||
@@ -168,7 +167,7 @@ describe('DeepSeekAdapter against a mock server', () => {
|
||||
await assemble(ctx, {
|
||||
model: 'deepseek-v4-flash',
|
||||
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
|
||||
compact: true,
|
||||
purpose: 'compaction',
|
||||
})
|
||||
|
||||
expect(server.headers[0]?.['x-deepseek-harness-compact']).toBe('1')
|
||||
|
||||
@@ -227,10 +227,9 @@ export interface GenerateOptions {
|
||||
*/
|
||||
sessionId?: Branded<'SessionId'>
|
||||
/**
|
||||
* Marks the auxiliary compaction (summarization) call. The DeepSeek adapter
|
||||
* forwards it as the `x-deepseek-harness-compact: 1` request header so the
|
||||
* host can separate compaction traffic from conversation requests; it never
|
||||
* enters the model-visible request body. Loop-built requests leave it unset.
|
||||
* Provider-neutral classification for an auxiliary model call. Adapters may
|
||||
* map the purpose to model-hidden transport metadata. Ordinary conversation
|
||||
* requests leave it unset.
|
||||
*/
|
||||
compact?: boolean
|
||||
purpose?: 'compaction'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user