mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
refactor(session): localize token delta detection
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { randomUUID } from '@deepseek-ai/dsh-util-crypto'
|
||||
import { MessageId, type CallId } from './brand.ts'
|
||||
import { deepFreeze } from './call-config.ts'
|
||||
import type { ContentBlock, StreamChunk, ToolResultBlock } from './types.ts'
|
||||
import type { ContentBlock, ToolResultBlock } from './types.ts'
|
||||
|
||||
/** Provider/model identity and adapter-private replay data for an assistant message. */
|
||||
export interface AssistantProvenance {
|
||||
@@ -240,23 +240,3 @@ export function createToolResultMessage(input: ToolResultMessageInput): ToolResu
|
||||
}],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a stream chunk carries visible model output (the first-token
|
||||
* boundary shared by client step timing and the whole-log sessionStats
|
||||
* projection). Empty deltas (heartbeats, empty tool-call frames) do not count
|
||||
* as a first token.
|
||||
* @param chunk - the stream chunk to test.
|
||||
* @returns true when the chunk contains a non-empty text/reasoning/tool delta.
|
||||
*/
|
||||
export function isTokenDelta(chunk: StreamChunk): boolean {
|
||||
switch (chunk.type) {
|
||||
case 'text-delta':
|
||||
case 'reasoning-delta':
|
||||
return chunk.text !== ''
|
||||
case 'tool-call-delta':
|
||||
return chunk.argumentsDelta !== '' || chunk.name !== undefined
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,26 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import { isTokenDelta } from '@deepseek-ai/dsh-llm/message'
|
||||
import type { StreamChunk } from '@deepseek-ai/dsh-llm/types'
|
||||
import type { ProjectionDefinition } from '@deepseek-ai/dsh-session-projection'
|
||||
|
||||
/* jscpd:ignore-start -- Session Stats owns its whole-log timing projection independently. */
|
||||
|
||||
/** Whether a stream chunk carries a non-empty first-token delta. */
|
||||
function isTokenDelta(chunk: StreamChunk): boolean {
|
||||
switch (chunk.type) {
|
||||
case 'text-delta':
|
||||
case 'reasoning-delta':
|
||||
return chunk.text !== ''
|
||||
case 'tool-call-delta':
|
||||
return chunk.argumentsDelta !== '' || chunk.name !== undefined
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
/** Accumulated whole-log figures (the view is exactly these totals). */
|
||||
interface SessionStatsTotals {
|
||||
/** Distinct turns with at least one closed step so far. */
|
||||
|
||||
@@ -204,6 +204,35 @@ describe('sessionStats wall-time fold (controlled timestamps)', () => {
|
||||
])).toEqual(totals({ turns: 1, steps: 1, llmMs: 1_000, ttftMs: 400, ttftSteps: 1 }))
|
||||
})
|
||||
|
||||
it('uses non-empty Tool-call names or arguments as the first token', () => {
|
||||
expect(fold([
|
||||
at(1_000, 'step/start', { turn: 1, step: 1 }),
|
||||
at(1_100, 'assistant/chunk', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
chunk: { type: 'tool-call-delta', index: 0, id: 'call-1', argumentsDelta: '' },
|
||||
}),
|
||||
at(1_200, 'assistant/chunk', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
chunk: { type: 'tool-call-delta', index: 0, id: 'call-1', name: 'read', argumentsDelta: '' },
|
||||
}),
|
||||
at(2_000, 'assistant/message', { turn: 1, step: 1, message }),
|
||||
at(2_100, 'step/end', { turn: 1, step: 1 }),
|
||||
])).toEqual(totals({ turns: 1, steps: 1, llmMs: 1_000, ttftMs: 200, ttftSteps: 1 }))
|
||||
|
||||
expect(fold([
|
||||
at(1_000, 'step/start', { turn: 1, step: 1 }),
|
||||
at(1_300, 'assistant/chunk', {
|
||||
turn: 1,
|
||||
step: 1,
|
||||
chunk: { type: 'tool-call-delta', index: 0, id: 'call-1', argumentsDelta: '{' },
|
||||
}),
|
||||
at(2_000, 'assistant/message', { turn: 1, step: 1, message }),
|
||||
at(2_100, 'step/end', { turn: 1, step: 1 }),
|
||||
])).toEqual(totals({ turns: 1, steps: 1, llmMs: 1_000, ttftMs: 300, ttftSteps: 1 }))
|
||||
})
|
||||
|
||||
it('leaves a cancelled step untimed: counted by step/end, no assembled message to accrue from', () => {
|
||||
expect(fold([
|
||||
at(1_000, 'step/start', { turn: 1, step: 1 }),
|
||||
|
||||
Reference in New Issue
Block a user