test: update conversation assembly coverage and docs
This commit is contained in:
@@ -5,6 +5,7 @@ import { createUserMessage, ProviderRequestId } from '@deepseek-ai/dsh-llm'
|
||||
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
|
||||
import InvariantService from '@deepseek-ai/dsh-invariants'
|
||||
import * as RetryInvariant from '@deepseek-ai/dsh-llm-retry/invariant'
|
||||
import { RetryId } from '@deepseek-ai/dsh-llm-retry/brand'
|
||||
import { providerForOpenStep } from '../src/history.ts'
|
||||
|
||||
async function setup(): Promise<Context> {
|
||||
@@ -38,6 +39,7 @@ function appendRetryTurn(session: Session, turn: number) {
|
||||
|
||||
const failure = { message: 'provider busy', code: 'RATE_LIMIT', status: 429 }
|
||||
const normal = {
|
||||
retryId: RetryId('normal-retry-chain'),
|
||||
provider: 'mock',
|
||||
mode: 'normal' as const,
|
||||
policyKey: 'normal-policy',
|
||||
@@ -47,6 +49,7 @@ const normal = {
|
||||
failure,
|
||||
}
|
||||
const always = {
|
||||
retryId: RetryId('always-retry-chain'),
|
||||
provider: 'mock',
|
||||
mode: 'always' as const,
|
||||
policyKey: 'always-policy',
|
||||
@@ -130,6 +133,7 @@ describe('llm-retry invariants', () => {
|
||||
})
|
||||
|
||||
it.each([
|
||||
['empty-retry-id', { ...normal, retryId: RetryId('') }, /retryId must be a non-empty string/],
|
||||
['retry-zero', { ...normal, retry: 0 }, /positive safe integer/],
|
||||
['retry-fraction', { ...normal, retry: 1.5 }, /positive safe integer/],
|
||||
['max-zero', { ...normal, maxRetries: 0 }, /positive safe maxRetries/],
|
||||
@@ -212,10 +216,65 @@ describe('llm-retry invariants', () => {
|
||||
reset.append('step/end', { turn: 1, step: 1 })
|
||||
reset.append('step/start', { turn: 1, step: 2 })
|
||||
expect(() => {
|
||||
reset.append('llm/retry', { turn: 1, step: 2, ...normal })
|
||||
reset.append('llm/retry', {
|
||||
turn: 1,
|
||||
step: 2,
|
||||
...normal,
|
||||
retryId: RetryId('reset-step-2-retry-chain'),
|
||||
})
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
it('keeps one retry identity per provider-policy chain', async () => {
|
||||
const ctx = await setup()
|
||||
const changed = openStep(ctx, 'retry-invariant-changed-chain-id')
|
||||
changed.append('llm/retry', { turn: 1, step: 1, ...normal })
|
||||
expect(() => changed.append('llm/retry', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
...normal,
|
||||
retry: 2,
|
||||
retryId: RetryId('changed-retry-chain'),
|
||||
})).toThrow(/must preserve retryId/)
|
||||
|
||||
const reused = openStep(ctx, 'retry-invariant-reused-chain-id')
|
||||
reused.append('llm/retry', { turn: 1, step: 1, ...normal })
|
||||
expect(() => reused.append('llm/retry', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
...always,
|
||||
retryId: normal.retryId,
|
||||
})).toThrow(/already owned by another chain/)
|
||||
})
|
||||
|
||||
it('validates retry-started correlation and uniqueness', async () => {
|
||||
const ctx = await setup()
|
||||
const empty = openStep(ctx, 'retry-started-empty-id')
|
||||
expect(() => empty.append('llm/retry-started', {
|
||||
retryId: RetryId(''), turn: 1, step: 1, retry: 1,
|
||||
})).toThrow(/retryId must be a non-empty string/)
|
||||
|
||||
const missing = openStep(ctx, 'retry-started-missing-schedule')
|
||||
expect(() => missing.append('llm/retry-started', {
|
||||
retryId: RetryId('missing-retry-chain'), turn: 1, step: 1, retry: 1,
|
||||
})).toThrow(/pairs no prior scheduled attempt/)
|
||||
|
||||
const mismatch = openStep(ctx, 'retry-started-location-mismatch')
|
||||
mismatch.append('llm/retry', { turn: 1, step: 1, ...normal })
|
||||
expect(() => mismatch.append('llm/retry-started', {
|
||||
retryId: normal.retryId, turn: 2, step: 1, retry: 1,
|
||||
})).toThrow(/turn\/step must match/)
|
||||
|
||||
const repeated = openStep(ctx, 'retry-started-repeated')
|
||||
repeated.append('llm/retry', { turn: 1, step: 1, ...normal })
|
||||
repeated.append('llm/retry-started', {
|
||||
retryId: normal.retryId, turn: 1, step: 1, retry: 1,
|
||||
})
|
||||
expect(() => repeated.append('llm/retry-started', {
|
||||
retryId: normal.retryId, turn: 1, step: 1, retry: 1,
|
||||
})).toThrow(/repeats one scheduled attempt/)
|
||||
})
|
||||
|
||||
it('starts a fresh retry chain after incomplete predecessor boundaries', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
@@ -261,4 +320,16 @@ describe('llm-retry invariants', () => {
|
||||
await ctx.plugin(InvariantService)
|
||||
await expect(ctx.plugin(RetryInvariant)).rejects.toThrow(/inside an open turn/)
|
||||
})
|
||||
|
||||
it('accepts a scheduled and started attempt on late registration', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
const session = openStep(ctx, 'retry-invariant-late-started')
|
||||
session.append('llm/retry', { turn: 1, step: 1, ...normal })
|
||||
session.append('llm/retry-started', {
|
||||
retryId: normal.retryId, turn: 1, step: 1, retry: 1,
|
||||
})
|
||||
await ctx.plugin(InvariantService)
|
||||
await expect(ctx.plugin(RetryInvariant)).resolves.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Context } from 'cordis'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import SessionPersistenceSqlite from '@deepseek-ai/dsh-session-persistence-sqlite'
|
||||
import { RetryId } from '@deepseek-ai/dsh-llm-retry/brand'
|
||||
import type {} from '../src/index.ts'
|
||||
|
||||
const dirs: string[] = []
|
||||
@@ -39,6 +40,7 @@ describe.each(['jsonl', 'sqlite'] as const)('%s retry-event persistence', (kind)
|
||||
reason: 'initial',
|
||||
})
|
||||
const event = session.append('llm/retry', {
|
||||
retryId: RetryId(`retry-${kind}-chain`),
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'mock',
|
||||
|
||||
@@ -191,7 +191,9 @@ describe('provider-routed retry policy', () => {
|
||||
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'go' }], source: { kind: 'user' } }))
|
||||
const event = await scheduled
|
||||
|
||||
expect(event.data.retryId).toEqual(expect.any(String))
|
||||
expect(event.data).toEqual({
|
||||
retryId: event.data.retryId,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'mock',
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||
import type { ContextBreakdownProjection } from '@deepseek-ai/dsh-token-meter/client'
|
||||
import { CompactionId } from '@deepseek-ai/dsh-compact/brand'
|
||||
import { contextBreakdownProjectionDefinition } from '../src/breakdown-projection.ts'
|
||||
import {
|
||||
estimateContent,
|
||||
@@ -59,6 +60,7 @@ function appendSummaryMeter(ctx: Context, session: Session, start: number, end:
|
||||
const endIdx = nodes.findIndex(node => node.seq === end)
|
||||
const shadowed = nodes.slice(startIdx, endIdx + 1)
|
||||
session.append('compact/summary', {
|
||||
compactionId: CompactionId('context-breakdown-summary'),
|
||||
summary: [{ type: 'text', text: 'summary' }],
|
||||
shadowedRange: { start, end },
|
||||
shadowedSeqs: shadowed.map(node => node.seq),
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { Session } from '@deepseek-ai/dsh-session'
|
||||
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||
import type { ContextPressureProjection, TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client'
|
||||
import { CompactionId } from '@deepseek-ai/dsh-compact/brand'
|
||||
|
||||
const ZERO: TokenUsageProjection = {
|
||||
uncachedInputTokens: 0,
|
||||
@@ -81,6 +82,7 @@ function appendSummaryMeter(ctx: Context, session: Session, start: number, end:
|
||||
const endIdx = nodes.findIndex(node => node.seq === end)
|
||||
const shadowed = nodes.slice(startIdx, endIdx + 1)
|
||||
session.append('compact/summary', {
|
||||
compactionId: CompactionId('token-usage-summary'),
|
||||
summary: [{ type: 'text', text: 'summary' }],
|
||||
shadowedRange: { start, end },
|
||||
shadowedSeqs: shadowed.map(node => node.seq),
|
||||
|
||||
Reference in New Issue
Block a user