Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input

# Conflicts:
#	apps/cli/README.i18n.yaml
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl
#	packages/client/connection/src/client/fixture.ts
#	packages/client/connection/src/client/index.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/README.md
#	packages/client/runtime/README.zh.md
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/src/client/input/hub.ts
#	packages/client/ui-conversation/src/client/service.ts
#	packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
#	packages/compact/compact-basic/src/summarizer.ts
#	packages/compact/compact-basic/tests/compact-basic.spec.ts
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/host/apiproxy/src/api/rpc.ts
#	packages/host/apiproxy/src/api/sessions.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	packages/llm/llm-deepseek/tests/serialize.spec.ts
#	packages/llm/llm-pi-ai/tests/adapter.spec.ts
#	packages/llm/llm-pi-ai/tests/convert.spec.ts
#	packages/llm/llm-pi-ai/tests/provider-apis.e2e.ts
#	packages/llm/token-meter/tests/token-meter.spec.ts
#	packages/ui/tui/README.i18n.yaml
This commit is contained in:
Yichen Jiang
2026-07-29 10:18:53 +08:00
763 changed files with 19809 additions and 7096 deletions

View File

@@ -349,8 +349,8 @@ export class TokenMeterService extends Service {
}
assembler.push(sourceEvent.data.chunk)
}
const providerMessage = assembler.message()
return providerMessage.content.length === 0 ? 0 : this.estimateMessage(providerMessage)
const providerContent = assembler.blocks()
return providerContent.length === 0 ? 0 : this._estimateContent(providerContent) + ROLE_OVERHEAD
}
/** Price content blocks recursively under the fixed density heuristic. */

View File

@@ -1,7 +1,7 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { Context } from 'cordis'
import { AttachmentId } from '@deepseek-ai/dsh-attachment'
import { CallId } from '@deepseek-ai/dsh-llm'
import { createUserMessage, CallId, createMessage } from '@deepseek-ai/dsh-llm'
import type { ContentBlock, Message, TokenUsage } from '@deepseek-ai/dsh-llm'
import SessionStore, { Session, SessionId, canonicalHeader } from '@deepseek-ai/dsh-session'
import type { EpochHeader, SessionEvent } from '@deepseek-ai/dsh-session'
@@ -13,7 +13,13 @@ function header(model: string, extras: Omit<EpochHeader, 'config'> = {}): EpochH
}
function textMessage(text: string, role: Message['role'] = 'user'): Message {
return { role, content: [{ type: 'text', text }] }
return createMessage({
role,
content: [{ type: 'text', text }],
source: role === 'assistant'
? { kind: 'model', provider: 'mock', model: 'mock' }
: { kind: 'user' },
})
}
function appendHeader(session: Session, value: EpochHeader): void {
@@ -66,13 +72,19 @@ function appendSuccessfulCall(
? { surfaceOp: 'append' as const }
: { surfaceOp: 'append' as const, sourceEventSeqs: provenance === 'empty' ? [] : sources }
session.append('assistant/message', {
provenance: {
provider: value.config.provider,
model: value.config.model,
},
turn,
step,
content: durableText.length === 0 ? [] : [{ type: 'text', text: durableText }],
message: createMessage({
role: 'assistant',
content: durableText.length === 0 ? [] : [{ type: 'text', text: durableText }],
source: {
kind: 'model',
...{
provider: value.config.provider,
model: value.config.model,
},
},
}),
...options.usage === undefined ? {} : { usage: options.usage },
}, intent)
session.append('step/end', { turn, step })
@@ -136,7 +148,10 @@ describe('TokenMeterService pricing', () => {
},
{ type: 'future-block', payload: 'abcd' } as unknown as ContentBlock,
]
const estimated = service.estimateMessage({ role: 'assistant', content: blocks })
const estimated = service.estimateMessage(createMessage({
role: 'assistant', content: blocks,
source: { kind: 'plugin', plugin: 'test' },
}))
expect(estimated).toBe(813)
expect(service.estimateMessage(textMessage('abcd'))).toBe(9)
})
@@ -165,10 +180,10 @@ describe('TokenMeterService pricing', () => {
it('keeps an earlier unified snapshot detached from later replay', () => {
const service = meter()
const session = new Session(SessionId('detached'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'first' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const snapshot = service.measure(session)
const snapshotCopy = structuredClone(snapshot)
expect(Object.isFrozen(snapshot.nodes)).toBe(true)
@@ -181,10 +196,10 @@ describe('TokenMeterService pricing', () => {
;(snapshot.nodes[0] as { seq: number; tokens: number }).tokens = 1
}).toThrow(TypeError)
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'second' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const advanced = service.measure(session)
expect(advanced.logRevision).toBe(2)
expect(advanced.nodes).toHaveLength(2)
@@ -197,10 +212,10 @@ describe('TokenMeterService pricing', () => {
it('prices header, tools, and surface when no reusable usage exists', () => {
const service = meter()
const session = new Session(SessionId('heuristic'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'question' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
appendHeader(session, header('deepseek-v4-flash', {
system: 'system',
tools: [{ name: 'read', description: 'read', parameters: { type: 'object' } }],
@@ -215,10 +230,10 @@ describe('TokenMeterService pricing', () => {
it('keeps request-header overrides out of the returned surface', () => {
const service = meter()
const session = new Session(SessionId('override-surface'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'question' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const logged = service.measure(session)
const overridden = service.measure(session, header('another-model', {
@@ -243,10 +258,10 @@ describe('replay anchors and surface folds', () => {
it('uses disjoint provider usage and signed durable-output rewrites', () => {
const service = meter()
const session = new Session(SessionId('usage'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'before' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
appendSuccessfulCall(session, header('deepseek-v4-flash'), {
providerText: 'short',
durableText: 'a much longer rewritten durable assistant answer',
@@ -274,10 +289,10 @@ describe('replay anchors and surface folds', () => {
const anchored = service.measure(session)
expect(anchored.baseline.kind).toBe('estimated')
const assistant = anchored.nodes[0]!.seq
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'short' }],
source: { kind: 'plugin', plugin: 'test' },
}, {
}), {
surfaceOp: { op: 'replace', start: assistant, end: assistant },
sourceEventSeqs: [assistant],
})
@@ -301,10 +316,10 @@ describe('replay anchors and surface folds', () => {
const anchored = service.measure(session)
expect(anchored.baseline.kind).toBe('estimated')
expect(anchored.surfaceDeltaTokens).toBe(0)
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'later' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const advanced = service.measure(session)
expect(advanced.surfaceDeltaTokens).toBeGreaterThan(0)
})
@@ -389,10 +404,10 @@ describe('replay anchors and surface folds', () => {
usage: USAGE,
providerText: 'long provider answer '.repeat(100),
})
original.append('user/message', {
original.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'new tail' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
const seeded = new Session(SessionId('surface-seeded'), original.events)
const before = service.measure(seeded)
expect(before.nodes).toHaveLength(2)
@@ -400,10 +415,10 @@ describe('replay anchors and surface folds', () => {
expectSurfaceTotal(before)
const first = seeded.surface.nodes[0]!
seeded.append('user/message', {
seeded.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'replacement' }],
source: { kind: 'plugin', plugin: 'test' },
}, { surfaceOp: { op: 'replace', start: first, end: first }, sourceEventSeqs: [first] })
}), { surfaceOp: { op: 'replace', start: first, end: first }, sourceEventSeqs: [first] })
const after = service.measure(seeded)
expect(after.nodes).toHaveLength(2)
expect(after.nodes[0]!.seq).toBe(seeded.events.length - 1)
@@ -442,10 +457,16 @@ describe('malformed replay and listener lifecycle', () => {
const session = new Session(SessionId('bad-step'))
appendHeader(session, header('deepseek-v4-flash'))
session.append('assistant/message', {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [{ type: 'text', text: 'bad' }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: 'bad' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
}, { surfaceOp: 'append', sourceEventSeqs: [] })
expectRepeatedFailure(meter(), session, /no matching step\/start/)
})
@@ -465,10 +486,16 @@ describe('malformed replay and listener lifecycle', () => {
appendHeader(late, header('deepseek-v4-flash'))
late.append('step/end', { turn: 1, step: 1 })
late.append('assistant/message', {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [],
message: createMessage({
role: 'assistant',
content: [],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
}, { surfaceOp: 'append', sourceEventSeqs: [] })
expectRepeatedFailure(
meter(),
@@ -495,10 +522,10 @@ describe('malformed replay and listener lifecycle', () => {
{
name: 'non-chunk',
appendSource(session) {
return [session.append('user/message', {
return [session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'x' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' }).seq]
}), { surfaceOp: 'append' }).seq]
},
pattern: /is not assistant\/chunk/,
},
@@ -520,10 +547,16 @@ describe('malformed replay and listener lifecycle', () => {
appendHeader(session, header('deepseek-v4-flash'))
const sourceEventSeqs = testCase.appendSource(session)
session.append('assistant/message', {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [{ type: 'text', text: 'bad' }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: 'bad' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
usage: { inputTokens: 1, outputTokens: 1 },
}, { surfaceOp: 'append', sourceEventSeqs })
expect(() => meter().measure(session)).toThrow(testCase.pattern)
@@ -544,10 +577,16 @@ describe('malformed replay and listener lifecycle', () => {
seq: duplicate.seq,
time: 0,
data: {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [],
message: createMessage({
role: 'assistant',
content: [],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
usage: { inputTokens: 1, outputTokens: 0 },
},
surfaceOp: 'append',
@@ -563,10 +602,16 @@ describe('malformed replay and listener lifecycle', () => {
seq: future.seq,
time: 0,
data: {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [],
message: createMessage({
role: 'assistant',
content: [],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
usage: { inputTokens: 1, outputTokens: 0 },
},
surfaceOp: 'append',
@@ -577,17 +622,23 @@ describe('malformed replay and listener lifecycle', () => {
it('does not partially apply a malformed assistant replacement', () => {
const session = new Session(SessionId('transactional-replace'))
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'head' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
appendHeader(session, header('deepseek-v4-flash'))
const head = session.events[0]!.seq
session.append('assistant/message', {
provenance: { provider: 'mock', model: 'deepseek-v4-flash' },
turn: 1,
step: 1,
content: [{ type: 'text', text: 'replacement' }],
message: createMessage({
role: 'assistant',
content: [{ type: 'text', text: 'replacement' }],
source: {
kind: 'model',
...{ provider: 'mock', model: 'deepseek-v4-flash' },
},
}),
}, { surfaceOp: { op: 'replace', start: head, end: head }, sourceEventSeqs: [head] })
expectRepeatedFailure(
meter(),
@@ -598,18 +649,18 @@ describe('malformed replay and listener lifecycle', () => {
it('rejects corrupt replacement ranges without advancing the replay cursor', () => {
const session = new Session(SessionId('bad-replace'))
const head = session.append('user/message', {
const head = session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'head' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' }).seq
}), { surfaceOp: 'append' }).seq
appendUnchecked(session, {
type: 'user/message',
seq: session.seq,
time: 0,
data: {
data: createUserMessage({
content: [{ type: 'text', text: 'bad' }],
source: { kind: 'user' },
},
}),
surfaceOp: { op: 'replace', start: 99, end: 99 },
sourceEventSeqs: [head],
})
@@ -633,10 +684,10 @@ describe('malformed replay and listener lifecycle', () => {
data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
}] })
activeMeter.measure(session)
session.append('user/message', {
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'one' }],
source: { kind: 'user' },
}, { surfaceOp: 'append' })
}), { surfaceOp: 'append' })
expect(revisions).toEqual([2])
expect(activeMeter.measure(session).logRevision).toBe(2)