mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 18:37:24 +00:00
241 lines
9.5 KiB
TypeScript
241 lines
9.5 KiB
TypeScript
import { afterEach, expect, it, vi } from 'vitest'
|
|
import crypto from 'node:crypto'
|
|
import { decodeClaudeTranscriptLine } from './transcript-line-decoders-claude'
|
|
import { decodeClaudeTurnLifecycle } from './transcript-turn-lifecycle'
|
|
import { sanitizeNativeChatRpcMessage } from '../runtime/rpc/methods/native-chat/native-chat-rpc-block-sanitize'
|
|
import * as glue from '../../shared/native-chat/transcript/native-chat-pending-glue'
|
|
import * as imageText from '../../shared/native-chat/transcript/native-chat-image-transcript-markers'
|
|
import * as scanner from '../ai-vault/session-scanner-values'
|
|
import { opencodeMessages } from './transcript-opencode-part-blocks'
|
|
import { boundWorkerTranscriptMessages } from '../runtime/orchestration/workers/transcripts/worker-transcript-payload'
|
|
import { transcriptInputContent } from './transcript-input-content'
|
|
import { nativeChatRpcAppendBatches } from '../runtime/rpc/methods/native-chat/native-chat-rpc-page-bounds'
|
|
import type { NativeChatMessage } from '../../shared/native-chat/native-chat-types'
|
|
import {
|
|
prunePendingSends,
|
|
visibleNativeChatPendingSends,
|
|
type NativeChatPendingSend
|
|
} from '../../shared/native-chat/transcript/native-chat-pending-sends'
|
|
|
|
afterEach(() => vi.restoreAllMocks())
|
|
|
|
function user(text: string, id: string): NativeChatMessage {
|
|
return {
|
|
id,
|
|
role: 'user',
|
|
timestamp: null,
|
|
source: 'transcript',
|
|
blocks: [{ type: 'text', text }],
|
|
inputContent: transcriptInputContent('opencode', text)
|
|
}
|
|
}
|
|
|
|
it.each([10, 100])(
|
|
'parses each of %i source parts once and retains constant-size text evidence',
|
|
(size) => {
|
|
const parse = vi.spyOn(scanner, 'parseJsonObject')
|
|
const parts = Array.from({ length: size }, () => ({
|
|
message_id: 'user',
|
|
time_updated: 0,
|
|
data: JSON.stringify({ type: 'text', text: 'x'.repeat(1000) })
|
|
}))
|
|
const [message] = opencodeMessages(
|
|
{ id: 'user', role: 'user', timestamp: null },
|
|
parts,
|
|
[],
|
|
true
|
|
)
|
|
expect(parse).toHaveBeenCalledTimes(size)
|
|
// One provider name, a 64-character digest and an empty path list fit within 128 bytes.
|
|
expect(Buffer.byteLength(JSON.stringify(message.inputContent))).toBeLessThanOrEqual(128)
|
|
}
|
|
)
|
|
|
|
it.each([10, 100])('measures each of %i appended rows once before transport', (size) => {
|
|
let serializations = 0
|
|
const rows = Array.from({ length: size }, (_, index) => {
|
|
const row = user('body', `user-${index}`)
|
|
return {
|
|
...row,
|
|
toJSON: () => {
|
|
serializations++
|
|
return row
|
|
}
|
|
}
|
|
})
|
|
expect(nativeChatRpcAppendBatches(rows).flat()).toHaveLength(size)
|
|
expect(serializations).toBe(size)
|
|
})
|
|
|
|
it.each([100, 1000])(
|
|
'derives only the appended source key with %i old rows and eight pending sends',
|
|
(size) => {
|
|
const pending: NativeChatPendingSend[] = Array.from({ length: 8 }, (_, index) => ({
|
|
id: `send-${index}`,
|
|
sourceAgent: 'opencode',
|
|
text: `arriving-${index}`,
|
|
sentAt: 1
|
|
}))
|
|
const history = Array.from({ length: size }, (_, index) => user(`old-${index}`, `old-${index}`))
|
|
expect(prunePendingSends(pending, history)).toBe(pending)
|
|
const messages = [
|
|
...history,
|
|
user('arriving-0', 'new'),
|
|
{ ...user('answer', 'answer'), role: 'assistant' as const, inputContent: undefined }
|
|
]
|
|
const serialize = vi.spyOn(JSON, 'stringify')
|
|
const remaining = prunePendingSends(pending, messages)
|
|
const visible = visibleNativeChatPendingSends(pending, messages)
|
|
expect(serialize).toHaveBeenCalledTimes(1)
|
|
expect(remaining.map((entry) => entry.id)).toEqual([
|
|
'send-1',
|
|
'send-2',
|
|
'send-3',
|
|
'send-4',
|
|
'send-5',
|
|
'send-6',
|
|
'send-7'
|
|
])
|
|
expect(visible).toEqual(remaining)
|
|
}
|
|
)
|
|
|
|
it.each([10, 100])('hashes only evidence used by desktop for %i decoded user rows', (size) => {
|
|
const hash = vi.spyOn(crypto, 'createHash')
|
|
const messages = Array.from({ length: size }, (_, index) => {
|
|
const line = JSON.stringify({
|
|
type: 'user',
|
|
uuid: `user-${index}`,
|
|
message: { role: 'user', content: [{ type: 'text', text: 'exact input' }] }
|
|
})
|
|
expect(decodeClaudeTurnLifecycle(line, 'fallback')?.state).toBe('working')
|
|
return decodeClaudeTranscriptLine(line, 'fallback')!
|
|
})
|
|
expect(hash).not.toHaveBeenCalled()
|
|
JSON.stringify(messages.map((message) => sanitizeNativeChatRpcMessage(message, 'mobile')))
|
|
expect(hash).not.toHaveBeenCalled()
|
|
const desktop = messages.map((message) => sanitizeNativeChatRpcMessage(message, 'runtime'))
|
|
const serialized = JSON.stringify(desktop)
|
|
const cloned = structuredClone(desktop)
|
|
expect(JSON.stringify(cloned)).toBe(serialized)
|
|
expect(hash).toHaveBeenCalledTimes(size)
|
|
expect(cloned[0].inputContent?.textDigest).toBe(
|
|
'5d199cb821847bdb649a17bc8d105b453a6538f0af5ce520959964e520fdcfe5'
|
|
)
|
|
})
|
|
|
|
it.each([5, 50])('does not evaluate receipt evidence in %i worker transcript rows', (size) => {
|
|
const messages = Array.from({ length: size }, (_, index) => user('exact input', `u${index}`))
|
|
const hash = vi.spyOn(crypto, 'createHash')
|
|
const result = boundWorkerTranscriptMessages(messages)
|
|
JSON.stringify(result)
|
|
structuredClone(result)
|
|
expect(result.messages).toHaveLength(size)
|
|
expect(result.messages.every((message) => !message.inputContent)).toBe(true)
|
|
expect(hash).not.toHaveBeenCalled()
|
|
// Native desktop still materializes one digest per complete source unit, cached through cloning.
|
|
const native = messages.map((message) => sanitizeNativeChatRpcMessage(message, 'runtime'))
|
|
JSON.stringify(native)
|
|
structuredClone(native)
|
|
expect(hash).toHaveBeenCalledTimes(size)
|
|
})
|
|
|
|
it.each([1000, 10000])('does no glue work on %i rows preceding live legacy boundaries', (size) => {
|
|
const history = Array.from({ length: size }, (_, index) => user(`old-${index}`, `old-${index}`))
|
|
const boundary = {
|
|
...user('boundary', 'boundary'),
|
|
role: 'assistant' as const,
|
|
inputContent: undefined
|
|
}
|
|
const unrelated = user('unrelated', 'append')
|
|
for (const count of [1, 8]) {
|
|
const pending: NativeChatPendingSend[] = Array.from({ length: count }, (_, index) => ({
|
|
id: `p${index}`,
|
|
text: `piece-${index}`,
|
|
sentAt: 1,
|
|
afterMessageId: 'boundary'
|
|
}))
|
|
const normalize = vi.spyOn(imageText, 'normalizedNativeChatUserMessageText')
|
|
const prepare = vi.spyOn(imageText, 'normalizeNativeChatUserText')
|
|
const match = vi.spyOn(glue, 'selectPendingIndicesRepresentedByUserRows')
|
|
const kept = prunePendingSends(pending, [...history, boundary, unrelated])
|
|
expect(kept).toBe(pending)
|
|
// One eligible appended user row, regardless of history; a lone ordinary input needs no glue.
|
|
expect(normalize.mock.calls.length).toBeLessThanOrEqual(count + (count === 1 ? 0 : 1))
|
|
// Content/match keys, prepared pieces and this suffix need bounded text work per obligation.
|
|
expect(prepare.mock.calls.length).toBeLessThanOrEqual(4 * count + 2)
|
|
expect(match.mock.calls.length).toBeLessThanOrEqual(count === 1 ? 0 : 255)
|
|
normalize.mockRestore()
|
|
prepare.mockRestore()
|
|
match.mockRestore()
|
|
}
|
|
})
|
|
|
|
it.each([
|
|
[8, 'continue', false],
|
|
[80, 'continue', false],
|
|
[8, 'continue continue', false],
|
|
[80, 'continue continue', false],
|
|
[8, 'continue continue', true],
|
|
[80, 'continue continue', true]
|
|
] as const)(
|
|
'bounds allocation work for eight identical legacy sends across %i eligible rows of %s',
|
|
(rows, text, image) => {
|
|
const count = 8
|
|
const pending: NativeChatPendingSend[] = Array.from({ length: count }, (_, index) => ({
|
|
id: `same-${index}`,
|
|
text: image && index < 2 ? 'continue continue' : 'continue',
|
|
imagePaths: image && index === 0 ? ['/tmp/i.png'] : undefined,
|
|
sentAt: 1
|
|
}))
|
|
const messages = [
|
|
...Array.from({ length: rows }, (_, index) => user(text, `same-row-${index}`)),
|
|
{ ...user('', 'reply'), role: 'assistant' as const }
|
|
]
|
|
if (image) {
|
|
const group = [pending[0], pending[2], pending[3]]
|
|
expect(visibleNativeChatPendingSends(group, [messages[0]])).toEqual([])
|
|
}
|
|
const BaseMap = globalThis.Map
|
|
let writes = 0
|
|
let lookups = 0
|
|
class CountingMap<K, V> extends BaseMap<K, V> {
|
|
override get(key: K): V | undefined {
|
|
lookups++
|
|
return super.get(key)
|
|
}
|
|
override set(key: K, value: V): this {
|
|
writes++
|
|
return super.set(key, value)
|
|
}
|
|
}
|
|
vi.stubGlobal('Map', CountingMap)
|
|
try {
|
|
const startedAt = performance.now()
|
|
const remaining = prunePendingSends(pending, messages)
|
|
const elapsedMs = performance.now() - startedAt
|
|
const allocationLookups = lookups
|
|
expect(remaining).toEqual([])
|
|
// Eight legal row alternatives per signature; each state assigns, requires or omits each send.
|
|
const states = 3 ** count
|
|
const witnessesPerRow = (image ? 2 : 1) * (2 ** count - 1) + count
|
|
// This corpus has two exact-rank patterns and one advance pattern; each signature keeps at most P rows.
|
|
const exactPatterns = image ? 2 : 1
|
|
const maxAllocationWrites = count * exactPatterns * states * (witnessesPerRow + 1)
|
|
expect(writes).toBeLessThanOrEqual(maxAllocationWrites + rows + 2 ** count)
|
|
// Every candidate allocates before its next-state lookup, including candidates never written.
|
|
expect(allocationLookups).toBeLessThanOrEqual(maxAllocationWrites + rows * count + 2 ** count)
|
|
console.info('legacy allocation count', {
|
|
rows,
|
|
text,
|
|
pending: count,
|
|
mapWrites: writes,
|
|
candidateAllocationUpperBound: allocationLookups,
|
|
elapsedMs
|
|
})
|
|
} finally {
|
|
vi.stubGlobal('Map', BaseMap)
|
|
}
|
|
}
|
|
)
|