@deepseek-ai/dsh-session-stats 0.1.2-rc.1 → 0.1.5-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/session/session-stats/README.md
5
- README.md: 829c90ed12ea17b1d03be56f94dcb818df66c03a
6
- README.zh.md: 6ce49ec864644c2cf2d0693daf2574cee771a732
5
+ README.md: 6585b0af7d3385ad6bf9186a3f246f76dadf824e
6
+ README.zh.md: 99652be5dba2a9d34216b4306eae7bd343040d96
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-session-stats` serves whole-log conversation figures — turn and step counts plus LLM, tool, first-token, and decode wall times as the `sessionStats` projection unit. Clients read the figures from the registry's snapshot and change feed, and paging or compaction cannot change them because they fold from the complete durable log. Choose it in compositions that already mount the projection registry, such as the web chat bundle whose stats strip is the reference consumer; assemblies without the registry are unaffected and their consumers fall back to window-scoped counting. Setup and field semantics come first; the fold internals live in a collapsible developer section below.
12
+ This package gives clients whole-session turn and step counts plus LLM, tool, first-token, and decode wall times through the public `sessionStats` value. The figures come from the complete durable log, so paging and compaction do not change them. Use it when a client must display consistent conversation statistics across reloads and reduced history. When whole-session statistics are unavailable, clients can use window-scoped counting instead.
13
13
 
14
14
  ## Table of Contents
15
15
 
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-session-stats` 提供全日志会话数字——轮/步计数以及 LLM、工具、首 token、解码墙钟时间——以 `sessionStats` 投影单元的形式对外提供。客户端从注册表的快照与变更流中读取数字,且由于它们从完整持久日志折叠而来,分页或压缩都无法改变它们。在已挂载投影注册表的组合中选择它,例如 Web 聊天包(其统计条是参考消费者);没有注册表的装配不受影响,其消费者回退到窗口口径计数。设置与字段语义在前;折叠内部细节放在下方可折叠的开发者章节中。
12
+ 本包通过公开的 `sessionStats` 值,为客户端提供全会话轮次与步骤计数,以及 LLM、工具、首 token 和解码墙钟时间。这些数字来自完整的持久日志,因此分页与压缩不会改变它们。当客户端必须在重新加载或缩减历史记录后显示一致的会话统计时,请使用本包。全会话统计不可用时,客户端可改用窗口口径计数。
13
13
 
14
14
  ## 目录
15
15
 
package/lib/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { z } from "zod";
2
+ import { assistantStreamFirstTokenTime } from "@deepseek-ai/dsh-llm";
2
3
  //#region lib/types/projection.js
3
4
  /**
4
5
  * The `sessionStats` projection unit: a pure fold of step boundaries, stream
5
- * chunks, tool pairs, and assembled assistant messages into whole-log counts
6
+ * embedded streams, tool pairs, and assembled assistant messages into whole-log counts
6
7
  * and wall times.
7
8
  *
8
9
  * `step/end` — not `assistant/message` — is the counted step event because it
@@ -24,15 +25,6 @@ import { z } from "zod";
24
25
  *
25
26
  * @module @deepseek-ai/dsh-session-stats/projection
26
27
  */
27
- /** Whether a stream chunk carries a non-empty first-token delta. */
28
- function isTokenDelta(chunk) {
29
- switch (chunk.type) {
30
- case "text-delta":
31
- case "reasoning-delta": return chunk.text !== "";
32
- case "tool-call-delta": return chunk.argumentsDelta !== "" || chunk.name !== void 0;
33
- default: return false;
34
- }
35
- }
36
28
  const sessionStatsSchema = z.object({
37
29
  turns: z.number().int().nonnegative(),
38
30
  steps: z.number().int().nonnegative(),
@@ -99,32 +91,34 @@ const sessionStatsProjectionDefinition = {
99
91
  firstTokenTime: null
100
92
  }
101
93
  };
102
- case "assistant/chunk": {
94
+ case "assistant/attempt": {
103
95
  const open = state.openStep;
104
96
  if (open === null || open.turn !== event.data.turn || open.step !== event.data.step) return state;
105
- if (open.firstTokenTime !== null || !isTokenDelta(event.data.chunk)) return state;
97
+ const first = assistantStreamFirstTokenTime(event.data.stream) ?? null;
98
+ if (open.firstTokenTime !== null || first === null) return state;
106
99
  return {
107
100
  ...state,
108
101
  openStep: {
109
102
  ...open,
110
- firstTokenTime: event.time
103
+ firstTokenTime: first
111
104
  }
112
105
  };
113
106
  }
114
107
  case "assistant/message": {
115
108
  const open = state.openStep;
116
109
  if (open === null || open.turn !== event.data.turn || open.step !== event.data.step) return state;
110
+ const firstToken = open.firstTokenTime ?? assistantStreamFirstTokenTime(event.data.stream) ?? null;
117
111
  const next = {
118
112
  ...state,
119
113
  llmMs: state.llmMs + Math.max(0, event.time - open.startTime),
120
114
  openStep: null
121
115
  };
122
- if (open.firstTokenTime !== null) {
123
- next.ttftMs += Math.max(0, open.firstTokenTime - open.startTime);
116
+ if (firstToken !== null) {
117
+ next.ttftMs += Math.max(0, firstToken - open.startTime);
124
118
  next.ttftSteps += 1;
125
119
  const outputTokens = usageOutputTokens(event.data.usage);
126
120
  if (outputTokens !== null) {
127
- next.decodeMs += Math.max(0, event.time - open.firstTokenTime);
121
+ next.decodeMs += Math.max(0, event.time - firstToken);
128
122
  next.decodeTokens += outputTokens;
129
123
  }
130
124
  }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `sessionStats` projection unit: a pure fold of step boundaries, stream
3
- * chunks, tool pairs, and assembled assistant messages into whole-log counts
3
+ * embedded streams, tool pairs, and assembled assistant messages into whole-log counts
4
4
  * and wall times.
5
5
  *
6
6
  * `step/end` — not `assistant/message` — is the counted step event because it
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `sessionStats` projection unit: a pure fold of step boundaries, stream
3
- * chunks, tool pairs, and assembled assistant messages into whole-log counts
3
+ * embedded streams, tool pairs, and assembled assistant messages into whole-log counts
4
4
  * and wall times.
5
5
  *
6
6
  * `step/end` — not `assistant/message` — is the counted step event because it
@@ -23,19 +23,7 @@
23
23
  * @module @deepseek-ai/dsh-session-stats/projection
24
24
  */
25
25
  import { z } from 'zod';
26
- /* jscpd:ignore-start -- Session Stats owns its whole-log timing projection independently. */
27
- /** Whether a stream chunk carries a non-empty first-token delta. */
28
- function isTokenDelta(chunk) {
29
- switch (chunk.type) {
30
- case 'text-delta':
31
- case 'reasoning-delta':
32
- return chunk.text !== '';
33
- case 'tool-call-delta':
34
- return chunk.argumentsDelta !== '' || chunk.name !== undefined;
35
- default:
36
- return false;
37
- }
38
- }
26
+ import { assistantStreamFirstTokenTime } from '@deepseek-ai/dsh-llm';
39
27
  const sessionStatsSchema = z.object({
40
28
  turns: z.number().int().nonnegative(),
41
29
  steps: z.number().int().nonnegative(),
@@ -100,18 +88,20 @@ export const sessionStatsProjectionDefinition = {
100
88
  ...state,
101
89
  openStep: { turn: event.data.turn, step: event.data.step, startTime: event.time, firstTokenTime: null },
102
90
  };
103
- case 'assistant/chunk': {
91
+ case 'assistant/attempt': {
104
92
  const open = state.openStep;
105
93
  if (open === null || open.turn !== event.data.turn || open.step !== event.data.step)
106
94
  return state;
107
- if (open.firstTokenTime !== null || !isTokenDelta(event.data.chunk))
95
+ const first = assistantStreamFirstTokenTime(event.data.stream) ?? null;
96
+ if (open.firstTokenTime !== null || first === null)
108
97
  return state;
109
- return { ...state, openStep: { ...open, firstTokenTime: event.time } };
98
+ return { ...state, openStep: { ...open, firstTokenTime: first } };
110
99
  }
111
100
  case 'assistant/message': {
112
101
  const open = state.openStep;
113
102
  if (open === null || open.turn !== event.data.turn || open.step !== event.data.step)
114
103
  return state;
104
+ const firstToken = open.firstTokenTime ?? assistantStreamFirstTokenTime(event.data.stream) ?? null;
115
105
  // One assembled message per step: closing the boundary means a
116
106
  // defensive duplicate cannot accrue twice.
117
107
  const next = {
@@ -119,12 +109,12 @@ export const sessionStatsProjectionDefinition = {
119
109
  llmMs: state.llmMs + Math.max(0, event.time - open.startTime),
120
110
  openStep: null,
121
111
  };
122
- if (open.firstTokenTime !== null) {
123
- next.ttftMs += Math.max(0, open.firstTokenTime - open.startTime);
112
+ if (firstToken !== null) {
113
+ next.ttftMs += Math.max(0, firstToken - open.startTime);
124
114
  next.ttftSteps += 1;
125
115
  const outputTokens = usageOutputTokens(event.data.usage);
126
116
  if (outputTokens !== null) {
127
- next.decodeMs += Math.max(0, event.time - open.firstTokenTime);
117
+ next.decodeMs += Math.max(0, event.time - firstToken);
128
118
  next.decodeTokens += outputTokens;
129
119
  }
130
120
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-stats",
3
3
  "description": "Whole-log conversation counts and wall times projection (sessionStats) for the DeepSeek Harness",
4
- "version": "0.1.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -36,20 +36,20 @@
36
36
  ],
37
37
  "license": "MIT",
38
38
  "peerDependencies": {
39
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
40
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
41
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1",
42
- "@deepseek-ai/cordis": "^4.0.2"
39
+ "@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.1",
40
+ "@deepseek-ai/cordis": "^4.0.2",
41
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
42
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1"
43
43
  },
44
44
  "dependencies": {
45
45
  "zod": "^4.4.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@deepseek-ai/cordis-plugin-include": "^1.0.7",
49
- "@deepseek-ai/dsh-session": "^0.1.2-rc.1",
50
- "@deepseek-ai/dsh-session-projection": "^0.1.2-rc.1",
51
- "@deepseek-ai/cordis": "^4.0.2",
52
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
53
- "@deepseek-ai/cordis-plugin-loader": "^1.0.3"
49
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
50
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
51
+ "@deepseek-ai/dsh-session": "^0.1.5-alpha.1",
52
+ "@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.1",
53
+ "@deepseek-ai/cordis": "^4.0.2"
54
54
  }
55
55
  }