mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
# Conflicts: # apps/web/tests/markdown-images.e2e.ts # docs/cordis-catalog/services.md # docs/core-data-structures/session.i18n.yaml # docs/event-producer-consumer.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/compact/compact-basic/tests/compact-basic.spec.ts # packages/context/time-context/tests/invariant.spec.ts # packages/context/time-context/tests/time-context.spec.ts # packages/core/agent-loop/src/invariant.ts # packages/core/agent-loop/tests/contract-regressions.spec.ts # packages/core/agent-loop/tests/request-reconstruction.spec.ts # packages/core/agent/tests/agent.spec.ts # packages/core/session/README.i18n.yaml # packages/core/session/src/index.ts # packages/core/session/tests/derived-cache.spec.ts # packages/core/session/tests/request-header.spec.ts # packages/core/session/tests/session.spec.ts # packages/core/session/tests/surface.spec.ts # packages/fs/tool-str-replace-editor/tests/tools.spec.ts # packages/goal/goal/tests/goal.spec.ts # packages/plan/plan-mode/tests/plan-mode.spec.ts # packages/pty/pty-local/tests/index.spec.ts # packages/pty/pty-local/tests/local.spec.ts # packages/pty/pty/tests/service.spec.ts # packages/pty/tool-bash-persistent/tests/loader-composition.spec.ts # packages/pty/tool-bash-persistent/tests/tools.spec.ts # packages/pty/tool-pty/tests/loader-composition.spec.ts # packages/pty/tool-pty/tests/tools.spec.ts # packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts # packages/tasks/tasks-local/tests/tasks.spec.ts # packages/ui/user-approval/tests/approval.spec.ts
166 lines
6.4 KiB
TypeScript
166 lines
6.4 KiB
TypeScript
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { Context } from 'cordis'
|
|
import {
|
|
COMPACT_CHECKPOINT_SOURCE,
|
|
CompactService,
|
|
isCompactCheckpointSource,
|
|
} from '@deepseek-ai/dsh-compact'
|
|
import type { CompactionResult, CompactionTrigger } from '@deepseek-ai/dsh-compact'
|
|
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
|
import type { CompactAgentContext } from '@deepseek-ai/dsh-compact'
|
|
import type { ManualCompactAgentContext } from '@deepseek-ai/dsh-compact'
|
|
|
|
/**
|
|
* A trivial concrete CompactService implementing the abstract contract. The
|
|
* interface package owns no algorithm — these tests exercise the seam itself:
|
|
* service registration, the abstract method shape, and the `compact/*` event
|
|
* declaration merge.
|
|
*/
|
|
class StubCompactService extends CompactService {
|
|
/** Records the signal handed to the most recent call, to prove it threads through. */
|
|
lastSignal: AbortSignal | undefined
|
|
|
|
override async compactIfNeeded(
|
|
_agent: CompactAgentContext,
|
|
_trigger: CompactionTrigger,
|
|
signal: AbortSignal,
|
|
): Promise<CompactionResult | null> {
|
|
this.lastSignal = signal
|
|
return null
|
|
}
|
|
|
|
override async compactNow(
|
|
_agent: ManualCompactAgentContext,
|
|
signal: AbortSignal,
|
|
): Promise<CompactionResult | null> {
|
|
this.lastSignal = signal
|
|
return null
|
|
}
|
|
|
|
override async compactRegion(
|
|
start: number,
|
|
end: number,
|
|
agent: CompactAgentContext,
|
|
signal?: AbortSignal,
|
|
): Promise<CompactionResult> {
|
|
this.lastSignal = signal
|
|
const session = agent.session
|
|
const summary = [{ type: 'text' as const, text: 'stub' }]
|
|
const surface = session.surface.nodes
|
|
const startIndex = surface.indexOf(start)
|
|
const endIndex = surface.indexOf(end)
|
|
if (startIndex < 0 || endIndex < startIndex) throw new Error('stub compact range is invalid')
|
|
const shadowedSeqs = surface.slice(startIndex, endIndex + 1)
|
|
// Minimal stub honoring the lock + log-only event contract.
|
|
const startEvent = session.append('compact/start', { turn: 0 })
|
|
const summaryEvent = session.append('compact/summary', {
|
|
summary,
|
|
shadowedRange: { start, end },
|
|
shadowedSeqs,
|
|
shadowedTokenCount: 0,
|
|
provider: 'mock',
|
|
model: 'stub',
|
|
})
|
|
session.append('user/message', createUserMessage({
|
|
content: summary,
|
|
source: COMPACT_CHECKPOINT_SOURCE,
|
|
}), {
|
|
surfaceOp: { op: 'replace', start, end },
|
|
sourceEventSeqs: [startEvent.seq, summaryEvent.seq, ...shadowedSeqs],
|
|
})
|
|
const endEvent = session.append('compact/end', { turn: 0 })
|
|
return {
|
|
startSeq: startEvent.seq,
|
|
summarySeq: summaryEvent.seq,
|
|
endSeq: endEvent.seq,
|
|
summary,
|
|
shadowedRange: { start, end },
|
|
shadowedSeqs,
|
|
shadowedTokenCount: 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
describe('CompactService seam', () => {
|
|
function stubAgent(session: Session, model?: string): CompactAgentContext {
|
|
return { session, options: model === undefined ? {} : { model } }
|
|
}
|
|
|
|
it('registers as ctx.compact', () => {
|
|
const ctx = new Context()
|
|
void new StubCompactService(ctx)
|
|
expect(ctx.compact).toBeDefined()
|
|
expect(ctx.compact).toBeInstanceOf(StubCompactService)
|
|
})
|
|
|
|
it('disposing the fiber unregisters ctx.compact (HMR safety)', async () => {
|
|
const ctx = new Context()
|
|
const fiber = await ctx.plugin(StubCompactService)
|
|
expect(ctx.compact).toBeInstanceOf(StubCompactService)
|
|
await fiber.dispose()
|
|
expect(ctx.compact).toBeUndefined()
|
|
})
|
|
|
|
it('exposes the abstract contract methods', async () => {
|
|
const ctx = new Context()
|
|
const svc = new StubCompactService(ctx)
|
|
const session = Session.create(SessionId('s'))
|
|
expect(await svc.compactIfNeeded(stubAgent(session), 'pressure', new AbortController().signal)).toBeNull()
|
|
const signal = new AbortController().signal
|
|
expect(await svc.compactNow({
|
|
...stubAgent(session),
|
|
runMaintenance: task => task(new AbortController().signal),
|
|
}, signal)).toBeNull()
|
|
expect(svc.lastSignal).toBe(signal)
|
|
})
|
|
|
|
it('compact/* events merge into SessionEventMap and are log-only', async () => {
|
|
const ctx = new Context()
|
|
const svc = new StubCompactService(ctx)
|
|
const session = Session.create(SessionId('s'))
|
|
const original = session.append('user/message', createUserMessage({
|
|
content: [{ type: 'text', text: 'original' }],
|
|
source: { kind: 'user' },
|
|
}), { surfaceOp: 'append' })
|
|
|
|
const result = await svc.compactRegion(original.seq, original.seq, stubAgent(session, 'm'))
|
|
|
|
const startEvent = session.events.find(e => e.type === 'compact/start')
|
|
expect(startEvent).toBeDefined()
|
|
// Log-only: the compiler rejects surfaceOp on compact/* (not a SurfaceEventType);
|
|
// verify the runtime value is absent.
|
|
const raw = startEvent as unknown as { surfaceOp?: unknown }
|
|
expect(raw.surfaceOp).toBeUndefined()
|
|
expect(result.summary).toEqual([{ type: 'text', text: 'stub' }])
|
|
expect(result.summarySeq).toBeGreaterThan(result.startSeq)
|
|
expect(result.endSeq).toBeGreaterThan(result.summarySeq)
|
|
expect(result.shadowedRange).toEqual({ start: original.seq, end: original.seq })
|
|
expect(result.shadowedSeqs).toEqual([original.seq])
|
|
const checkpoint = session.events.find(event => event.type === 'user/message'
|
|
&& isCompactCheckpointSource(event.data.source))
|
|
expect(checkpoint?.type === 'user/message' && checkpoint.data.source).toEqual(COMPACT_CHECKPOINT_SOURCE)
|
|
expect(isCompactCheckpointSource({ kind: 'plugin', plugin: 'other' })).toBe(false)
|
|
expect(isCompactCheckpointSource({ kind: 'user' })).toBe(false)
|
|
expect(session.events.filter(e => e.type.startsWith('compact/')).map(e => e.type))
|
|
.toEqual(['compact/start', 'compact/summary', 'compact/end'])
|
|
})
|
|
|
|
it('threads the cancellation signal through to the backend', async () => {
|
|
const ctx = new Context()
|
|
const svc = new StubCompactService(ctx)
|
|
const session = Session.create(SessionId('s'))
|
|
const controller = new AbortController()
|
|
const original = session.append('user/message', createUserMessage({
|
|
content: [{ type: 'text', text: 'original' }],
|
|
source: { kind: 'user' },
|
|
}), { surfaceOp: 'append' })
|
|
|
|
await svc.compactRegion(original.seq, original.seq, stubAgent(session, 'm'), controller.signal)
|
|
expect(svc.lastSignal).toBe(controller.signal)
|
|
|
|
await svc.compactIfNeeded(stubAgent(session), 'context-overflow', controller.signal)
|
|
expect(svc.lastSignal).toBe(controller.signal)
|
|
})
|
|
})
|