feat(client-runtime): add target-owned conversation snapshots

This commit is contained in:
imccyu
2026-08-10 18:37:59 +08:00
parent aa6d0b6efd
commit 3e79f7106e
37 changed files with 183 additions and 117 deletions

View File

@@ -126,6 +126,7 @@ describe('runtime client apply', () => {
const rebuild = vi.spyOn(Session.prototype, 'rebuildConversationRegistry')
const definition: ConversationNodeDefinition<null> = {
kind: 'registry-probe',
target: 'chat',
match: () => null,
start: () => null,
update: context => context.state,

View File

@@ -30,10 +30,16 @@ interface TestSnapshot {
}
class TestEventDefinitions {
readonly definitions: readonly ConversationNodeDefinition[]
readonly fallback: ConversationNodeDefinition | undefined
constructor(
readonly definitions: readonly ConversationNodeDefinition[],
readonly fallback?: ConversationNodeDefinition,
) {}
definitions: readonly ConversationNodeDefinition[],
fallback?: ConversationNodeDefinition,
) {
this.definitions = definitions.map(asChatDefinition)
this.fallback = fallback === undefined ? undefined : asChatDefinition(fallback)
}
entries(): readonly ConversationNodeDefinition[] {
return this.definitions
@@ -44,6 +50,12 @@ class TestEventDefinitions {
}
}
function asChatDefinition(definition: ConversationNodeDefinition): ConversationNodeDefinition {
return definition.buildViewNode === undefined || definition.target !== undefined
? definition
: { ...definition, target: 'chat' }
}
class TestViewDefinitions {
constructor(readonly definitions: readonly ConversationViewDefinition[]) {}
@@ -93,7 +105,10 @@ function chatSnapshot(assembler: ConversationNodeAssembler): TestSnapshot | unde
return assembler.snapshot('chat') as TestSnapshot | undefined
}
function node(context: Parameters<ConversationNodeDefinition['buildViewNode']>[0], data: unknown): ConversationViewNode {
function node(
context: Parameters<NonNullable<ConversationNodeDefinition['buildViewNode']>>[0],
data: unknown,
): ConversationViewNode {
return {
key: context.key,
kind: context.kind,

View File

@@ -13,6 +13,7 @@ import { FakeApiClient, ok } from './fake-api.ts'
function eventDefinition(kind: string): ConversationNodeDefinition<null> {
return {
kind,
target: 'chat',
match: () => null,
start: () => null,
update: context => context.state,

View File

@@ -123,12 +123,13 @@ function testViewDefinition(): ConversationViewDefinition<ChatConversationViewNo
const TEST_EVENT_DEFINITION: ConversationNodeDefinition<TestEventState> = {
kind: 'runtime-test-event',
target: 'chat',
match: event => ({ id: String(event.seq), role: 'start' }),
start: (_context, match) => ({ event: match.event, view: match.view }),
update: context => context.state,
publication: match => match.event.type === 'assistant/chunk' ? 'animation-frame' : 'immediate',
buildViewNode: (context, target) => {
if (target !== 'chat' || context.state === undefined || context.start === undefined) return null
buildViewNode: (context) => {
if (context.state === undefined || context.start === undefined) return null
return {
key: context.key,
kind: 'runtime-test-event',