Merge remote-tracking branch 'github/master' into xtr/trajectory-inspection-ui

# Conflicts:
#	packages/client/runtime/src/client/sessions/fold-adapter.ts
#	packages/compact/compact-basic/src/summarizer.ts
This commit is contained in:
_Kerman
2026-07-28 21:56:02 +08:00
495 changed files with 8443 additions and 5556 deletions

View File

@@ -11,6 +11,7 @@ import {
toolPairingBalancedBefore,
} from '@deepseek-ai/dsh-compact'
import type { CompactionResult } from '@deepseek-ai/dsh-compact'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import type { Message } from '@deepseek-ai/dsh-llm'
import type { TokenMeasurement, TokenMeterService } from '@deepseek-ai/dsh-token-meter'
import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
@@ -134,10 +135,11 @@ export async function compactSurfaceRegion(
throw new Error('compaction: session surface changed during summarization')
}
const framedSummary = frameSummary(summary)
const framedSummaryTokenCount = dependencies.meter.estimateMessage({
role: 'user',
const checkpointMessage = createUserMessage({
content: framedSummary,
source: COMPACT_CHECKPOINT_SOURCE,
})
const framedSummaryTokenCount = dependencies.meter.estimateMessage(checkpointMessage)
if (framedSummaryTokenCount >= shadowedTokenCount) {
throw new Error(
`summary is not smaller than the shadowed content (${framedSummaryTokenCount} estimated framed tokens >= ${shadowedTokenCount})`,
@@ -155,10 +157,7 @@ export async function compactSurfaceRegion(
...maxTokens === undefined ? {} : { maxTokens },
...usage === undefined ? {} : { usage },
})
session.append('user/message', {
content: framedSummary,
source: COMPACT_CHECKPOINT_SOURCE,
}, {
session.append('user/message', checkpointMessage, {
surfaceOp: { op: 'replace', start, end },
sourceEventSeqs: [startEvent.seq, summaryEvent.seq, ...shadowedSeqs],
})

View File

@@ -5,7 +5,7 @@
*/
import type { Context } from 'cordis'
import { BlockAssembler } from '@deepseek-ai/dsh-llm'
import { createUserMessage, BlockAssembler } from '@deepseek-ai/dsh-llm'
import type {
ContentBlock, FinishReason, GenerateOptions, Message, TokenUsage, ToolSchema,
} from '@deepseek-ai/dsh-llm'
@@ -134,7 +134,10 @@ export async function summarizeWithLlm(
const assembler = new BlockAssembler()
const messages: Message[] = [
...input.messages,
{ role: 'user', content: [{ type: 'text', text: COMPACTION_INSTRUCTION }] },
createUserMessage({
content: [{ type: 'text', text: COMPACTION_INSTRUCTION }],
source: { kind: 'plugin', plugin: 'dsh-compact-basic' },
}),
]
const options: GenerateOptions = {
provider: target.provider,
@@ -151,7 +154,7 @@ export async function summarizeWithLlm(
const error = finishError(assembler.finish)
if (error !== undefined) throw error
const rawOutput = assembler.message().content
const rawOutput = assembler.blocks()
const summary = textOnly(rawOutput)
if (!summary.some(block => block.text.trim().length > 0)) {
throw new Error('summarization produced no text summary content')

View File

@@ -11,7 +11,7 @@ import {
resolveTargetPolicy,
} from '@deepseek-ai/dsh-compact-basic/src/config.ts'
import type { CompactionResult } from '@deepseek-ai/dsh-compact'
import LlmService, { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, LlmAdapter } from '@deepseek-ai/dsh-llm'
import LlmService, { createUserMessage, CallId, CONTEXT_WINDOW_EXCEEDED_CODE, createToolResultMessage, LlmAdapter , createMessage } from '@deepseek-ai/dsh-llm'
import type {
ContentBlock,
GenerateOptions,
@@ -95,7 +95,10 @@ function summarizedText(input: SummarizationInput): string {
/** A minimal replayed prefix carrying one user message of the given text. */
function promptInput(text: string): SummarizationInput {
return { messages: [{ role: 'user', content: [{ type: 'text', text }] }] }
return { messages: [createUserMessage({
content: [{ type: 'text', text }],
source: { kind: 'plugin', plugin: 'test' },
})] }
}
/** Closed two-message turns followed by one open turn for durable compaction events. */
@@ -103,10 +106,10 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
const session = new Session(SessionId(`conversation-${turns}`))
for (let turn = 1; turn <= turns; turn += 1) {
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `${text} user ${turn}` }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
session.append('step/start', { turn, step: 1 })
if (turn === 1) {
session.append('request/header', {
@@ -115,10 +118,16 @@ function conversation(turns = 4, text = 'fixture '.repeat(40).trim()): Session {
})
}
session.append('assistant/message', {
provenance: { provider: MODEL, model: MODEL },
turn,
step: 1,
content: [{ type: 'text', text: `${text} assistant ${turn}` }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: `${text} assistant ${turn}` }],
source: {
kind: 'model',
...{ provider: MODEL, model: MODEL },
},
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn, step: 1 })
session.append('turn/end', { turn, reason: { kind: 'completed' } })
@@ -135,10 +144,10 @@ function toolConversation(): Session {
for (let turn = 1; turn <= 3; turn += 1) {
const callId = CallId(`call-${turn}`)
session.append('turn/start', { turn, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `request ${turn} `.repeat(300) }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
session.append('step/start', { turn, step: 1 })
if (turn === 1) {
session.append('request/header', {
@@ -147,21 +156,29 @@ function toolConversation(): Session {
})
}
session.append('assistant/message', {
provenance: { provider: MODEL, model: MODEL },
turn,
step: 1,
content: [
{ type: 'text', text: `calling ${turn} `.repeat(300) },
{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' },
],
message: createMessage({
role: 'assistant',
content: [
{ type: 'text', text: `calling ${turn} `.repeat(300) },
{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' },
],
source: {
kind: 'model',
...{ provider: MODEL, model: MODEL },
},
}),
}, { surfaceOp: 'append' })
session.append('tool/call', { turn, step: 1, callId, name: 'read', arguments: '{}' })
session.append('tool/result', {
turn,
step: 1,
callId,
content: [{ type: 'text', text: `result ${turn} `.repeat(300) }],
isError: false,
message: createToolResultMessage({
callId,
content: [{ type: 'text', text: `result ${turn} `.repeat(300) }],
isError: false,
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn, step: 1 })
session.append('turn/end', { turn, reason: { kind: 'completed' } })
@@ -176,10 +193,10 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
const callId = CallId('oversized')
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
if (withCompactablePrompt) {
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'older history '.repeat(200) }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
}
session.append('step/start', { turn: 1, step: 1 })
session.append('request/header', {
@@ -189,16 +206,24 @@ function oversizedToolResult(chars = 3_000, withCompactablePrompt = false): Sess
session.append('assistant/message', {
turn: 1,
step: 1,
content: [{ type: 'tool-call', id: callId, name: 'bash', arguments: '{}' }],
provenance: { provider: MODEL, model: MODEL },
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: callId, name: 'bash', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: MODEL, model: MODEL },
},
}),
}, { surfaceOp: 'append' })
session.append('tool/call', { turn: 1, step: 1, callId, name: 'bash', arguments: '{}' })
session.append('tool/result', {
turn: 1,
step: 1,
callId,
content: [{ type: 'text', text: 'X'.repeat(chars) }],
isError: false,
message: createToolResultMessage({
callId,
content: [{ type: 'text', text: 'X'.repeat(chars) }],
isError: false,
}),
meta: { presentation: 'preserved' },
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
@@ -551,18 +576,26 @@ describe('pressure measurement and retention', () => {
reason: 'initial',
})
session.append('assistant/message', {
provenance: { provider: MODEL, model: MODEL },
turn: 1,
step: 1,
content: [{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' }],
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: MODEL, model: MODEL },
},
}),
}, { surfaceOp: 'append' })
session.append('tool/call', { turn: 1, step: 1, callId, name: 'read', arguments: '{}' })
session.append('tool/result', {
turn: 1,
step: 1,
callId,
content: [{ type: 'text', text: 'result' }],
isError: false,
message: createToolResultMessage({
callId,
content: [{ type: 'text', text: 'result' }],
isError: false,
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
const generation = session.surface.replaceGeneration
@@ -705,18 +738,26 @@ describe('pressure measurement and retention', () => {
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('step/start', { turn: 1, step: 1 })
session.append('assistant/message', {
provenance: { provider: MODEL, model: MODEL },
turn: 1,
step: 1,
content: [{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' }],
message: createMessage({
role: 'assistant',
content: [{ type: 'tool-call', id: callId, name: 'read', arguments: '{}' }],
source: {
kind: 'model',
...{ provider: MODEL, model: MODEL },
},
}),
}, { surfaceOp: 'append' })
session.append('tool/call', { turn: 1, step: 1, callId, name: 'read', arguments: '{}' })
session.append('tool/result', {
turn: 1,
step: 1,
callId,
content: [{ type: 'text', text: 'result' }],
isError: false,
message: createToolResultMessage({
callId,
content: [{ type: 'text', text: 'result' }],
isError: false,
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
@@ -790,7 +831,7 @@ describe('optional model-free tool-result pruning', () => {
expect(await compactIfNeeded(compact, session)).not.toBeNull()
expect(compact.calls).toHaveLength(1)
const original = session.events.find(event => event.type === 'tool/result')
expect(original?.type === 'tool/result' && original.data.content[0])
expect(original?.type === 'tool/result' && original.data.message.content[0].content[0])
.toEqual({ type: 'text', text: 'X'.repeat(3_000) })
expect(session.events.filter(event =>
event.type === 'tool/result' && event.surfaceOp !== 'append')).toHaveLength(0)
@@ -916,10 +957,10 @@ describe('compaction region transaction', () => {
it('rejects a session with no turn boundary at all', async () => {
const compact = service()
const session = new Session(SessionId('turnless'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'orphan' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const node = session.surface.nodes[0]!
await expect(compact.compactRegion(
@@ -1001,10 +1042,10 @@ describe('compaction region transaction', () => {
const compact = service()
const session = conversation(2)
compact.mutateDuringSummary = () => {
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'concurrent surface mutation' }],
source: { kind: 'plugin', plugin: 'test' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
}
const nodes = session.surface.nodes
@@ -1037,16 +1078,22 @@ describe('compaction region transaction', () => {
const compact = service()
const session = new Session(SessionId('model-less-region'))
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'history '.repeat(100) }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
session.append('step/start', { turn: 1, step: 1 })
session.append('assistant/message', {
provenance: { provider: 'historical', model: 'historical' },
turn: 1,
step: 1,
content: [{ type: 'text', text: 'answer '.repeat(100) }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: 'answer '.repeat(100) }],
source: {
kind: 'model',
...{ provider: 'historical', model: 'historical' },
},
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn: 1, step: 1 })
const nodes = session.surface.nodes
@@ -1161,7 +1208,10 @@ describe('default one-shot summarizer', () => {
it('replays the conversation prefix and appends the instruction as the final message', async () => {
const { adapter, compact } = await summarizerHarness([{ type: 'text', text: 'summary' }])
const tools = [{ name: 'do_thing', description: 'd', parameters: { type: 'object' } }]
const prefix: Message = { role: 'user', content: [{ type: 'text', text: 'earlier turn' }] }
const prefix: Message = createUserMessage({
content: [{ type: 'text', text: 'earlier turn' }],
source: { kind: 'plugin', plugin: 'test' },
})
await compact.runSummarize({
system: 'REPLAYED SYSTEM',
tools,
@@ -1197,7 +1247,10 @@ describe('default one-shot summarizer', () => {
)
const policyAdapter = new ScriptedAdapter([{ type: 'text', text: 'policy summary' }])
ctx.llm.registerAdapter(['policy-summary'], policyAdapter)
const prefix: Message = { role: 'user', content: [{ type: 'text', text: 'warm prefix' }] }
const prefix: Message = createUserMessage({
content: [{ type: 'text', text: 'warm prefix' }],
source: { kind: 'plugin', plugin: 'test' },
})
const output = await compact.runSummarize({
system: 'WARM SYSTEM',

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { toolPairingBalancedAfter, toolPairingBalancedBefore } from '@deepseek-ai/dsh-compact'
import { CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
import { createUserMessage, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, resolveRetryPolicy , createMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, GenerateOptions, LlmResolvedModelInfo, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm'
import { CallId, LlmAdapter } from '@deepseek-ai/dsh-llm'
import { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
@@ -192,16 +192,22 @@ function overflowHistorySeed(): SessionEvent[] {
turn,
trigger: { kind: 'message', source: { kind: 'user' } },
})
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: `${sentinel} ${'old context '.repeat(200)}` }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
session.append('step/start', { turn, step: 1 })
session.append('assistant/message', {
provenance: { provider: 'mock', model: 'mock' },
turn,
step: 1,
content: [{ type: 'text', text: `historical response ${turn} ${'detail '.repeat(200)}` }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: `historical response ${turn} ${'detail '.repeat(200)}` }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'mock' },
},
}),
}, { surfaceOp: 'append' })
session.append('step/end', { turn, step: 1 })
session.append('turn/end', { turn, reason: { kind: 'completed' } })
@@ -220,7 +226,7 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
provider: 'unconfigured-agent-fallback',
model: 'unconfigured-agent-fallback',
})
agent.followup({ content: [{ type: 'text', text: 'do a routed multi-step task' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'do a routed multi-step task' }], source: { kind: 'user' } }))
await waitForIdle(ctx, agent)
expect(agent.session.requestHeader()?.config.model).toBe('mock')
@@ -238,7 +244,7 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
const { ctx } = await harness(8)
try {
const agent = ctx.agentLoop.create(SessionId('post-step-order'), { provider: 'mock', model: 'mock' })
agent.followup({ content: [{ type: 'text', text: 'do tool work' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'do tool work' }], source: { kind: 'user' } }))
await waitForIdle(ctx, agent)
const events = [...agent.session.events]
@@ -270,7 +276,7 @@ describe('CBR-001: a real-loop checkpoint is a valid boundary on both sides', ()
const { ctx } = await harness(8)
try {
const agent = ctx.agentLoop.create(SessionId('repro'), { provider: 'mock', model: 'mock' })
agent.followup({ content: [{ type: 'text', text: 'do a long multi-step task' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'do a long multi-step task' }], source: { kind: 'user' } }))
await waitForIdle(ctx, agent)
const events = [...agent.session.events]
@@ -331,7 +337,7 @@ describe('context-overflow recovery across the real loop and compact-basic', ()
},
})
agent.followup({ content: [{ type: 'text', text: 'continue from history' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'continue from history' }], source: { kind: 'user' } }))
await agent.whenIdle()
expect(adapter.conversationRequests).toHaveLength(2)
@@ -402,7 +408,7 @@ describe('context-overflow recovery across the real loop and compact-basic', ()
seed: overflowHistorySeed(),
agentOptions: { provider: 'mock', model: 'mock' },
})
agent.followup({ content: [{ type: 'text', text: 'continue from history' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'continue from history' }], source: { kind: 'user' } }))
await agent.whenIdle()
expect(adapter.conversationRequests).toHaveLength(3)