Merge latest origin/master into compact-post-step-overflow-recovery

# Conflicts:
#	docs/agent-lifecycle.md
#	docs/architecture.md
#	docs/cookbook/extension-cookbook.i18n.yaml
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/rfc/INDEX.md
#	docs/rfc/implemented/architecture/2026-07-05-reconstructable-requests.md
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.i18n.yaml
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.md
#	docs/rfc/implemented/architecture/2026-07-15-replay-token-meter-service.zh.md
#	docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md
#	docs/rfc/implemented/feature/2026-07-07-session-prefix.md
#	packages/compact/compact-basic/README.md
#	packages/compact/compact-basic/src/config.ts
#	packages/compact/compact-basic/src/index.ts
#	packages/compact/compact-basic/src/summarizer.ts
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
#	packages/compact/compact-basic/tests/compact-loop-repro.spec.ts
#	packages/compact/compact/README.md
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/core/agent-loop/src/loop.ts
#	packages/core/agent-loop/tests/cancel.spec.ts
#	packages/llm/llm-deepseek/src/adapter.ts
#	packages/llm/llm-pi-ai/README.md
#	packages/llm/llm-pi-ai/src/stream.ts
#	packages/llm/llm-pi-ai/tests/convert.spec.ts
#	packages/llm/llm/README.md
#	packages/llm/llm/src/index.ts
#	packages/llm/llm/tests/service.spec.ts
#	scripts/gen-doc-graphs.ts
This commit is contained in:
Tianyi Cui
2026-07-19 12:06:23 +08:00
814 changed files with 42348 additions and 10067 deletions
+231 -8
View File
@@ -1,8 +1,10 @@
import { describe, expect, it } from 'vitest'
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE } from '@deepseek-ai/dsh-llm'
import { CallId, CONTEXT_WINDOW_EXCEEDED_CODE, LlmError } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm'
import type { AssistantMessage, AssistantMessageEvent, Usage } from '@earendil-works/pi-ai'
import { mapStopReason, mapUsage, toPiContext, toStreamChunks } from '@deepseek-ai/dsh-llm-pi-ai'
import { toPiContext } from '../src/context.ts'
import { toPiReplayState } from '../src/replay.ts'
import { mapStopReason, mapUsage, toStreamChunks } from '../src/stream.ts'
function usage(input = 0, output = 0, cacheRead = 0, cacheWrite = 0): Usage {
return {
@@ -42,6 +44,7 @@ async function collect(stream: AsyncIterable<StreamChunk>): Promise<StreamChunk[
describe('toPiContext', () => {
it('maps system prompt, user text, and tools', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'deepseek-v4-flash',
system: 'be helpful',
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
@@ -55,13 +58,14 @@ describe('toPiContext', () => {
})
it('omits empty tools and absent system prompt', () => {
const context = toPiContext({ model: 'm', messages: [], tools: [] })
const context = toPiContext({ provider: 'deepseek', model: 'm', messages: [], tools: [] })
expect(context.systemPrompt).toBeUndefined()
expect(context.tools).toBeUndefined()
})
it('maps assistant text/reasoning/tool-call blocks', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
@@ -76,8 +80,7 @@ describe('toPiContext', () => {
expect(message.role).toBe('assistant')
expect(message.stopReason).toBe('toolUse')
expect(message.content).toEqual([
// thinkingSignature names the replay field — DeepSeek's passback rule.
{ type: 'thinking', thinking: 'hmm', thinkingSignature: 'reasoning_content' },
{ type: 'thinking', thinking: 'hmm' },
{ type: 'text', text: 'calling' },
{ type: 'toolCall', id: 'c1', name: 'f', arguments: { a: 1 } },
])
@@ -85,6 +88,7 @@ describe('toPiContext', () => {
it('marks tool-call-free assistant messages with stopReason stop', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{ role: 'assistant', content: [{ type: 'text', text: 'done' }] }],
})
@@ -93,6 +97,7 @@ describe('toPiContext', () => {
it('parses malformed tool-call arguments to {}', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
@@ -105,6 +110,7 @@ describe('toPiContext', () => {
it('parses non-object argument JSON (arrays, scalars) to {}', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
@@ -116,6 +122,7 @@ describe('toPiContext', () => {
it('recovers toolName for tool results from the preceding assistant call', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [
{
@@ -140,6 +147,7 @@ describe('toPiContext', () => {
it('labels unmatched tool results with toolName unknown and keeps isError', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'user',
@@ -156,6 +164,7 @@ describe('toPiContext', () => {
it('splits mixed user text + tool results and folds history system messages', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [
{ role: 'system', content: [{ type: 'text', text: 'rule' }] },
@@ -173,6 +182,7 @@ describe('toPiContext', () => {
it('skips plugin-added (unknown) blocks in assistant content', () => {
const context = toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
@@ -184,6 +194,195 @@ describe('toPiContext', () => {
})
expect((context.messages[0] as AssistantMessage).content).toEqual([{ type: 'text', text: 'visible' }])
})
it('recombines durable content with pi-ai replay metadata across target providers and models', () => {
const state = toPiReplayState(assistant({
api: 'openai-responses',
provider: 'openai',
model: 'gpt-5',
responseModel: 'gpt-5-2026-01-01',
responseId: 'resp_123',
stopReason: 'toolUse',
content: [
{ type: 'thinking', thinking: 'private reasoning', thinkingSignature: 'think-sig', redacted: true },
{ type: 'text', text: 'calling', textSignature: 'text-sig' },
{ type: 'toolCall', id: 'c1', name: 'f', arguments: { a: 1 }, thoughtSignature: 'tool-sig' },
],
}))
const context = toPiContext({
provider: 'anthropic',
model: 'claude-next',
messages: [{
role: 'assistant',
content: [
{ type: 'reasoning', text: 'private reasoning' },
{ type: 'text', text: 'calling' },
{ type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{"a":1}' },
],
provenance: { provider: 'openai', model: 'gpt-5', replayState: state },
}],
})
expect(context.messages[0]).toMatchObject({
role: 'assistant',
api: 'openai-responses',
provider: 'openai',
model: 'gpt-5',
responseModel: 'gpt-5-2026-01-01',
responseId: 'resp_123',
stopReason: 'toolUse',
content: [
{ type: 'thinking', thinking: 'private reasoning', thinkingSignature: 'think-sig', redacted: true },
{ type: 'text', text: 'calling', textSignature: 'text-sig' },
{ type: 'toolCall', id: 'c1', name: 'f', arguments: { a: 1 }, thoughtSignature: 'tool-sig' },
],
})
})
it('replays all native block kinds when optional metadata is absent', () => {
const state = toPiReplayState(assistant({
content: [
{ type: 'thinking', thinking: 'private reasoning' },
{ type: 'text', text: 'calling' },
{ type: 'toolCall', id: 'c1', name: 'f', arguments: { a: 1 } },
],
}))
const context = toPiContext({
provider: 'deepseek',
model: 'new-model',
messages: [{
role: 'assistant',
content: [
{ type: 'reasoning', text: 'private reasoning' },
{ type: 'text', text: 'calling' },
{ type: 'tool-call', id: CallId('c1'), name: 'f', arguments: '{"a":1}' },
],
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash', replayState: state },
}],
})
expect(context.messages[0]).toMatchObject({
role: 'assistant',
content: [
{ type: 'thinking', thinking: 'private reasoning' },
{ type: 'text', text: 'calling' },
{ type: 'toolCall', id: 'c1', name: 'f', arguments: { a: 1 } },
],
})
expect(context.messages[0]).not.toHaveProperty('responseModel')
expect(context.messages[0]).not.toHaveProperty('responseId')
})
it('rejects unsupported replay-state versions with a stable error code', () => {
try {
toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
provenance: {
provider: 'deepseek',
model: 'old',
replayState: { kind: 'pi-ai', version: 2 },
},
}],
})
expect.fail('expected invalid replay state')
} catch (error: unknown) {
expect(error).toBeInstanceOf(LlmError)
expect((error as LlmError).code).toBe('INVALID_REPLAY_STATE')
expect((error as Error).message).toContain('unsupported version 2')
}
})
it('rejects replay metadata whose blocks do not match the durable content', () => {
const state = toPiReplayState(assistant({ content: [{ type: 'text', text: 'done' }] }))
expect(() => toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
content: [{ type: 'reasoning', text: 'done' }],
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash', replayState: state },
}],
})).toThrow(/block 0 does not match assistant content/)
})
it('rejects replay metadata whose block count differs from durable content', () => {
const state = toPiReplayState(assistant())
expect(() => toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash', replayState: state },
}],
})).toThrow(/block count does not match assistant content/)
})
const validReplay = {
kind: 'pi-ai',
version: 1,
api: 'openai-completions',
provider: 'deepseek',
model: 'deepseek-v4-flash',
stopReason: 'stop',
blocks: [{ type: 'text' }],
}
it.each([
['provider', { ...validReplay, provider: 'openai' }],
['model', { ...validReplay, model: 'deepseek-v4-pro' }],
])('rejects replay metadata whose %s differs from assistant provenance', (field, replayState) => {
try {
toPiContext({
provider: 'deepseek',
model: 'next-model',
messages: [{
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash', replayState },
}],
})
expect.fail('expected invalid replay state')
} catch (error: unknown) {
expect(error).toBeInstanceOf(LlmError)
expect((error as LlmError).code).toBe('INVALID_REPLAY_STATE')
expect((error as Error).message).toContain(`${field} does not match assistant provenance`)
}
})
it.each([
['number state', 1, 'expected an object'],
['null state', null, 'expected an object'],
['array state', [], 'expected an object'],
['unknown kind', { ...validReplay, kind: 'other' }, 'unknown state kind'],
['non-string api', { ...validReplay, api: 1 }, 'api must be a non-empty string'],
['empty provider', { ...validReplay, provider: '' }, 'provider must be a non-empty string'],
['missing model', { ...validReplay, model: undefined }, 'model must be a non-empty string'],
['unknown stop reason', { ...validReplay, stopReason: 'pause' }, 'unknown stopReason'],
['non-string response model', { ...validReplay, responseModel: 1 }, 'responseModel must be a string'],
['non-string response id', { ...validReplay, responseId: 1 }, 'responseId must be a string'],
['non-array blocks', { ...validReplay, blocks: 'text' }, 'blocks must be an array'],
['number block', { ...validReplay, blocks: [1] }, 'block 0 must be an object'],
['null block', { ...validReplay, blocks: [null] }, 'block 0 must be an object'],
['array block', { ...validReplay, blocks: [[]] }, 'block 0 must be an object'],
['unknown block type', { ...validReplay, blocks: [{ type: 'audio' }] }, 'block 0 has an unknown type'],
['non-string signature', { ...validReplay, blocks: [{ type: 'text', textSignature: 1 }] }, 'textSignature must be a string'],
['non-boolean redaction', { ...validReplay, blocks: [{ type: 'reasoning', redacted: 'yes' }] }, 'redacted must be boolean'],
])('rejects malformed replay state: %s', (_name, replayState, message) => {
expect(() => toPiContext({
provider: 'deepseek',
model: 'm',
messages: [{
role: 'assistant',
content: [{ type: 'text', text: 'done' }],
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash', replayState },
}],
})).toThrow(message)
})
})
describe('toStreamChunks', () => {
@@ -205,7 +404,19 @@ describe('toStreamChunks', () => {
{ type: 'text-delta', index: 0, text: 'hi' },
{ type: 'block-end', index: 0, block: { type: 'text', text: 'hi' } },
{ type: 'usage', usage: { inputTokens: 3, outputTokens: 2 } },
{ type: 'finish', reason: { kind: 'stop' } },
{
type: 'finish',
reason: { kind: 'stop' },
replayState: {
kind: 'pi-ai',
version: 1,
api: 'openai-completions',
provider: 'deepseek',
model: 'deepseek-v4-flash',
stopReason: 'stop',
blocks: [{ type: 'text' }],
},
},
])
})
@@ -234,7 +445,7 @@ describe('toStreamChunks', () => {
toolCall: { type: 'toolCall', id: 'call-1', name: 'f', arguments: { a: 1 } },
partial: partialWithToolCall,
},
{ type: 'done', reason: 'toolUse', message: assistant({ stopReason: 'toolUse' }) },
{ type: 'done', reason: 'toolUse', message: assistant({ content: partialWithToolCall.content, stopReason: 'toolUse' }) },
)))
expect(chunks).toEqual([
{ type: 'block-start', index: 0, blockType: 'tool-call' },
@@ -242,7 +453,19 @@ describe('toStreamChunks', () => {
{ type: 'tool-call-delta', index: 0, id: 'call-1', name: 'f', argumentsDelta: ':1}' },
{ type: 'block-end', index: 0, block: { type: 'tool-call', id: 'call-1', name: 'f', arguments: '{"a":1}' } },
{ type: 'usage', usage: { inputTokens: 0, outputTokens: 0 } },
{ type: 'finish', reason: { kind: 'tool-calls' } },
{
type: 'finish',
reason: { kind: 'tool-calls' },
replayState: {
kind: 'pi-ai',
version: 1,
api: 'openai-completions',
provider: 'deepseek',
model: 'deepseek-v4-flash',
stopReason: 'toolUse',
blocks: [{ type: 'tool-call' }],
},
},
])
})