mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Resolve translation-pairing hashes after the merge and adapt the math-rendering e2e fixture to the simplified turn/start event shape.
235 lines
7.7 KiB
TypeScript
235 lines
7.7 KiB
TypeScript
// Synthetic long-chat history for browser behavior contracts. The fixture is
|
|
// generated through Session so pagination exercises the same event shapes as
|
|
// persisted conversations, while unique markers identify semantic rows
|
|
// without depending on CSS-module names or virtualizer DOM positions.
|
|
import {
|
|
CallId,
|
|
createAssistantMessage,
|
|
createToolResultMessage,
|
|
createUserMessage,
|
|
} from '@deepseek-ai/dsh-llm'
|
|
import {
|
|
SESSION_FORMAT_VERSION,
|
|
Session,
|
|
SessionId,
|
|
} from '@deepseek-ai/dsh-session'
|
|
// Carries the session/title event declaration into this fixture builder.
|
|
import type {} from '@deepseek-ai/dsh-session-title'
|
|
|
|
/** Options for one deterministic long-chat fixture. */
|
|
export interface ChatScrollFixtureOptions {
|
|
/** Marker namespace, used when two sessions share one browser world. */
|
|
readonly markerPrefix: string
|
|
/** Searchable title projected into the sidebar. */
|
|
readonly title: string
|
|
/** Number of closed turns to generate. */
|
|
readonly turns?: number
|
|
}
|
|
|
|
/** Semantic marker helpers returned with a generated fixture. */
|
|
interface ChatScrollMarkers {
|
|
/** Marker painted in the human message for a turn. */
|
|
user(turn: number): string
|
|
/** Marker painted in the final assistant message for a turn. */
|
|
assistant(turn: number): string
|
|
/** Marker painted in one seeded bash call and result. */
|
|
tool(turn: number, index: number): string
|
|
}
|
|
|
|
/** Generated JSONL plus the stable facts browser scenarios assert. */
|
|
export interface ChatScrollFixture {
|
|
readonly log: string
|
|
readonly markers: ChatScrollMarkers
|
|
readonly title: string
|
|
readonly turns: number
|
|
}
|
|
|
|
const DEFAULT_TURNS = 88
|
|
const TOOL_INTERVAL = 8
|
|
const CODE_INTERVAL = 11
|
|
|
|
function text(value: string): { type: 'text'; text: string }[] {
|
|
return [{ type: 'text', text: value }]
|
|
}
|
|
|
|
function suffix(turn: number): string {
|
|
return String(turn).padStart(3, '0')
|
|
}
|
|
|
|
function markerHelpers(prefix: string): ChatScrollMarkers {
|
|
return {
|
|
user: turn => `CHAT_SCROLL_${prefix}_USER_${suffix(turn)}`,
|
|
assistant: turn => `CHAT_SCROLL_${prefix}_ASSISTANT_${suffix(turn)}`,
|
|
tool: (turn, index) => `CHAT_SCROLL_${prefix}_TOOL_${suffix(turn)}_${String(index)}`,
|
|
}
|
|
}
|
|
|
|
function appendRequestHeader(session: Session, turn: number, step: number): void {
|
|
session.append('request/header', {
|
|
header: {
|
|
config: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
|
|
system: `Synthetic chat-scroll request for turn ${String(turn)}, step ${String(step)}.`,
|
|
},
|
|
reason: turn === 1 && step === 1 ? 'initial' : 'change',
|
|
})
|
|
}
|
|
|
|
function appendAssistant(session: Session, turn: number, step: number, body: string): void {
|
|
session.append('assistant/message', {
|
|
turn,
|
|
step,
|
|
message: createAssistantMessage({
|
|
content: text(body),
|
|
source: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
|
|
}),
|
|
usage: {
|
|
inputTokens: 2_000 + turn * 7,
|
|
outputTokens: 180 + step * 20,
|
|
},
|
|
}, { surfaceOp: 'append' })
|
|
}
|
|
|
|
function codeBlock(turn: number): string {
|
|
if (turn % CODE_INTERVAL !== 0) return ''
|
|
const lines = Array.from(
|
|
{ length: 30 },
|
|
(_, index) => `const scroll_case_${suffix(turn)}_${String(index).padStart(2, '0')} = ${String(turn + index)}`,
|
|
)
|
|
return `\n\n\`\`\`ts\n${lines.join('\n')}\n\`\`\``
|
|
}
|
|
|
|
function appendToolStep(
|
|
session: Session,
|
|
markers: ChatScrollMarkers,
|
|
turn: number,
|
|
): void {
|
|
const calls = [1, 2].map((index) => {
|
|
const marker = markers.tool(turn, index)
|
|
const callId = CallId(`chat-scroll-${suffix(turn)}-${String(index)}`)
|
|
const args = JSON.stringify({
|
|
command: `printf '${marker}\\n'`,
|
|
description: marker,
|
|
})
|
|
return { args, callId, marker }
|
|
})
|
|
|
|
session.append('assistant/message', {
|
|
turn,
|
|
step: 1,
|
|
message: createAssistantMessage({
|
|
content: [
|
|
{ type: 'reasoning', text: `Inspecting two scroll fixtures for turn ${String(turn)}.` },
|
|
...calls.map(call => ({
|
|
type: 'tool-call' as const,
|
|
id: call.callId,
|
|
name: 'bash',
|
|
arguments: call.args,
|
|
})),
|
|
],
|
|
source: { provider: 'deepseek-official', model: 'deepseek-v4-flash' },
|
|
}),
|
|
usage: { inputTokens: 2_000 + turn * 7, outputTokens: 240, reasoningTokens: 30 },
|
|
}, { surfaceOp: 'append' })
|
|
|
|
for (const call of calls) {
|
|
const source = session.append('tool/call', {
|
|
turn,
|
|
step: 1,
|
|
callId: call.callId,
|
|
name: 'bash',
|
|
arguments: call.args,
|
|
})
|
|
session.append('tool/result', {
|
|
turn,
|
|
step: 1,
|
|
message: createToolResultMessage({
|
|
callId: call.callId,
|
|
content: text(Array.from(
|
|
{ length: 12 },
|
|
(_, line) => `${call.marker} output line ${String(line + 1).padStart(2, '0')}`,
|
|
).join('\n')),
|
|
isError: false,
|
|
}),
|
|
}, { surfaceOp: 'append', sourceEventSeqs: [source.seq] })
|
|
}
|
|
}
|
|
|
|
function fixtureLog(session: Session): string {
|
|
return [
|
|
JSON.stringify({
|
|
type: 'session',
|
|
version: SESSION_FORMAT_VERSION,
|
|
id: '{{sessionId}}',
|
|
createdAt: Date.now() - 60_000,
|
|
cwd: '{{cwd}}',
|
|
delegationDepth: 0,
|
|
}),
|
|
...session.events.map(event => JSON.stringify(event)),
|
|
'',
|
|
].join('\n')
|
|
}
|
|
|
|
/**
|
|
* Build a multi-page conversation with prose, fenced code, and paired bash
|
|
* calls/results. Every turn is closed, so cold resume cannot repair or mutate
|
|
* the seed before the browser observes it.
|
|
* @param options - Fixture identity and optional turn count.
|
|
* @returns Canonical JSONL and semantic marker helpers.
|
|
*/
|
|
export function createChatScrollFixture(options: ChatScrollFixtureOptions): ChatScrollFixture {
|
|
const turns = options.turns ?? DEFAULT_TURNS
|
|
const markers = markerHelpers(options.markerPrefix)
|
|
const session = Session.create(SessionId(`chat-scroll-${options.markerPrefix.toLowerCase()}-template`))
|
|
|
|
for (let turn = 1; turn <= turns; turn += 1) {
|
|
session.append('turn/start', {
|
|
turn,
|
|
})
|
|
const user = session.append('user/message', createUserMessage({
|
|
content: text(
|
|
`${markers.user(turn)} Review the long-running conversation state for turn ${String(turn)}. `
|
|
+ 'Keep the visible message stable while history, tools, and new output change around it.',
|
|
),
|
|
source: { kind: 'user' },
|
|
}), { surfaceOp: 'append' })
|
|
if (turn === 1) {
|
|
session.append('session/title', {
|
|
title: options.title,
|
|
messageSeqs: [user.seq],
|
|
source: { kind: 'fallback' },
|
|
})
|
|
}
|
|
|
|
session.append('step/start', { turn, step: 1 })
|
|
appendRequestHeader(session, turn, 1)
|
|
if (turn % TOOL_INTERVAL === 0) {
|
|
appendToolStep(session, markers, turn)
|
|
session.append('step/end', { turn, step: 1 })
|
|
session.append('step/start', { turn, step: 2 })
|
|
appendRequestHeader(session, turn, 2)
|
|
appendAssistant(
|
|
session,
|
|
turn,
|
|
2,
|
|
`${markers.assistant(turn)} Both tool results are accounted for. `
|
|
+ `This settled response keeps turn ${String(turn)} identifiable after paging.${codeBlock(turn)}`,
|
|
)
|
|
session.append('step/end', { turn, step: 2 })
|
|
} else {
|
|
appendAssistant(
|
|
session,
|
|
turn,
|
|
1,
|
|
`${markers.assistant(turn)} The conversation remains readable after several paragraphs.\n\n`
|
|
+ `Turn ${String(turn)} deliberately carries enough prose to wrap at narrower viewport widths. `
|
|
+ 'The semantic marker stays near the start so geometry probes can find the same rendered row.\n\n'
|
|
+ `The closing paragraph makes this a realistic assistant response rather than a one-line list item.${codeBlock(turn)}`,
|
|
)
|
|
session.append('step/end', { turn, step: 1 })
|
|
}
|
|
session.append('turn/end', { turn, reason: { kind: 'completed' } })
|
|
}
|
|
|
|
return { log: fixtureLog(session), markers, title: options.title, turns }
|
|
}
|