@khorsheed/dsh-local-agent-kimi 0.2.0-rc.3 → 0.2.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.
@@ -15,7 +15,13 @@
15
15
  * its transcript position so a delta mirror can sum a round's total.
16
16
  * @module @khorsheed/dsh-local-agent-kimi/session-view
17
17
  */
18
- import type { TokenUsage } from '@deepseek-ai/dsh-llm';
18
+ import type { ReasoningEffortId, TokenUsage } from '@deepseek-ai/dsh-llm';
19
+ /** The request config one `llm.request` wire event declares (its own words). */
20
+ export interface KimiRequestConfig {
21
+ model?: string;
22
+ reasoningEffort?: ReasoningEffortId;
23
+ maxTokens?: number;
24
+ }
19
25
  /** One rendered transcript line. */
20
26
  export type KimiTranscriptLine = ({
21
27
  kind: 'user';
@@ -25,10 +31,14 @@ export type KimiTranscriptLine = ({
25
31
  kind: 'assistant';
26
32
  text: string;
27
33
  turn: number;
34
+ wireStep?: number;
35
+ requestConfig?: KimiRequestConfig;
28
36
  } | {
29
37
  kind: 'think';
30
38
  text: string;
31
39
  turn: number;
40
+ wireStep?: number;
41
+ requestConfig?: KimiRequestConfig;
32
42
  }
33
43
  /**
34
44
  * Tool activity: one call with its (possibly still pending) result. `id`
@@ -43,6 +53,8 @@ export type KimiTranscriptLine = ({
43
53
  args?: string;
44
54
  result?: string;
45
55
  turn: number;
56
+ wireStep?: number;
57
+ requestConfig?: KimiRequestConfig;
46
58
  }) & {
47
59
  model?: string;
48
60
  };
@@ -62,6 +74,13 @@ export interface KimiSessionTranscript {
62
74
  readonly line: number;
63
75
  readonly usage: TokenUsage;
64
76
  }[];
77
+ /**
78
+ * Wire steps (`${turn}:${wireStep}`, turn 1-based) whose `step.end` the wire
79
+ * recorded — a closed step's content is complete, so a mirror may fold it
80
+ * without waiting for the next step to begin. Older wires carry no step
81
+ * markers; their lines simply never appear here.
82
+ */
83
+ closedSteps: ReadonlySet<string>;
65
84
  /**
66
85
  * The model identifier the wire named: the `model` field of a `usage.record`
67
86
  * or `llm.request` event, LAST one seen (a resumed session's later rounds
@@ -89,7 +108,9 @@ export declare function readKimiTranscript(sessionDir: string): Promise<KimiSess
89
108
  * Map a wire `usage.record` payload onto the shared usage contract. kimi
90
109
  * reports input (uncached), cache read, cache creation, and output; the
91
110
  * creation counter is its cache-write analog. Non-numeric or missing
92
- * counters are dropped so the record stays usable when kimi omits a field.
111
+ * counters are dropped so the record stays usable when kimi omits a field;
112
+ * a reported cache-read counter is always written (zero included), so a
113
+ * reader can tell "no hits" from "not reported".
93
114
  * @param raw - the event's `usage` value.
94
115
  * @returns the shared usage record, or undefined when nothing was reported.
95
116
  */
@@ -148,11 +148,20 @@ export async function readKimiTranscript(sessionDir) {
148
148
  const sessionId = sessionDir.split('session_').pop() ?? sessionDir;
149
149
  const lines = [];
150
150
  const usageRecords = [];
151
+ const closedSteps = new Set();
151
152
  let model;
152
153
  let modelTurn;
153
154
  let lineModel;
155
+ // The latest llm.request's declared config; content/tool lines carry a
156
+ // snapshot of it, so the mirror can write each folded step's request/header
157
+ // in the wire's own words.
158
+ let lineConfig;
154
159
  const push = (line) => {
155
- lines.push({ ...line, ...lineModel === undefined ? {} : { model: lineModel } });
160
+ lines.push({
161
+ ...line,
162
+ ...lineModel === undefined ? {} : { model: lineModel },
163
+ ...line.kind !== 'user' && lineConfig !== undefined ? { requestConfig: { ...lineConfig } } : {},
164
+ });
156
165
  };
157
166
  // Wire turns are 0-based on loop events; a `turn.prompt` also opens a new
158
167
  // round. The transcript uses 1-based turns matching the dsh child session's
@@ -169,7 +178,7 @@ export async function readKimiTranscript(sessionDir) {
169
178
  }
170
179
  catch (error) {
171
180
  if (error.code === 'ENOENT') {
172
- return { sessionId, lines, usageRecords };
181
+ return { sessionId, lines, usageRecords, closedSteps };
173
182
  }
174
183
  throw error;
175
184
  }
@@ -192,6 +201,11 @@ export async function readKimiTranscript(sessionDir) {
192
201
  if (typeof event.model === 'string' && event.model !== '') {
193
202
  model = lineModel = event.model;
194
203
  modelTurn = turn || 1;
204
+ // The record names the alias spelling (kimi-code/k3), llm.request the
205
+ // raw one (k3) — the pending header config follows the alias so the
206
+ // header and the message source name the model identically.
207
+ if (lineConfig !== undefined)
208
+ lineConfig = { ...lineConfig, model: event.model };
195
209
  }
196
210
  const usage = usageFromWire(event.usage);
197
211
  if (usage !== undefined)
@@ -204,6 +218,13 @@ export async function readKimiTranscript(sessionDir) {
204
218
  model = event.model;
205
219
  modelTurn = turn || 1;
206
220
  }
221
+ // The header contract requires provider+model; a wire that names no
222
+ // model records no config at all rather than an invented one.
223
+ lineConfig = lineModel === undefined ? undefined : {
224
+ model: lineModel,
225
+ ...typeof event.thinkingEffort === 'string' && event.thinkingEffort !== '' ? { reasoningEffort: event.thinkingEffort } : {},
226
+ ...typeof event.maxTokens === 'number' && Number.isFinite(event.maxTokens) ? { maxTokens: event.maxTokens } : {},
227
+ };
207
228
  continue;
208
229
  }
209
230
  if (event.type === 'turn.prompt') {
@@ -252,13 +273,19 @@ export async function readKimiTranscript(sessionDir) {
252
273
  turn = Math.max(turn, loopTurn);
253
274
  turnSeen = true;
254
275
  }
276
+ // The wire's own 1-based step within the turn: one loop step is one LLM
277
+ // request (its think/text parts and tool calls share it), so the mirror
278
+ // folds per wire step. Older wires omit it — their lines fold singly.
279
+ const wireStep = typeof loop.step === 'number' ? loop.step : undefined;
255
280
  const part = loop.part;
256
281
  const lineTurn = turn === 0 ? 1 : turn;
282
+ if (loop.type === 'step.end' && wireStep !== undefined)
283
+ closedSteps.add(`${lineTurn}:${wireStep}`);
257
284
  if (part?.type === 'text' && part.text !== undefined && part.text.trim() !== '') {
258
- push({ kind: 'assistant', text: part.text, turn: lineTurn });
285
+ push({ kind: 'assistant', text: part.text, turn: lineTurn, ...wireStep === undefined ? {} : { wireStep } });
259
286
  }
260
287
  else if (part?.type === 'think' && part.think !== undefined && part.think.trim() !== '') {
261
- push({ kind: 'think', text: part.think, turn: lineTurn });
288
+ push({ kind: 'think', text: part.think, turn: lineTurn, ...wireStep === undefined ? {} : { wireStep } });
262
289
  }
263
290
  else if (loop.type === 'tool.call') {
264
291
  const call = loop;
@@ -273,6 +300,7 @@ export async function readKimiTranscript(sessionDir) {
273
300
  name,
274
301
  ...args === undefined ? {} : { args },
275
302
  turn: lineTurn,
303
+ ...wireStep === undefined ? {} : { wireStep },
276
304
  });
277
305
  if (typeof call.uuid === 'string')
278
306
  callsByUuid.set(call.uuid, lineIndex);
@@ -316,13 +344,15 @@ export async function readKimiTranscript(sessionDir) {
316
344
  }
317
345
  }
318
346
  }
319
- return { sessionId, lines, usageRecords, ...model === undefined ? {} : { model }, ...modelTurn === undefined ? {} : { modelTurn } };
347
+ return { sessionId, lines, usageRecords, closedSteps, ...model === undefined ? {} : { model }, ...modelTurn === undefined ? {} : { modelTurn } };
320
348
  }
321
349
  /**
322
350
  * Map a wire `usage.record` payload onto the shared usage contract. kimi
323
351
  * reports input (uncached), cache read, cache creation, and output; the
324
352
  * creation counter is its cache-write analog. Non-numeric or missing
325
- * counters are dropped so the record stays usable when kimi omits a field.
353
+ * counters are dropped so the record stays usable when kimi omits a field;
354
+ * a reported cache-read counter is always written (zero included), so a
355
+ * reader can tell "no hits" from "not reported".
326
356
  * @param raw - the event's `usage` value.
327
357
  * @returns the shared usage record, or undefined when nothing was reported.
328
358
  */
@@ -345,7 +375,7 @@ export function usageFromWire(raw) {
345
375
  inputTokens: input ?? 0,
346
376
  outputTokens: output ?? 0,
347
377
  };
348
- if (cacheRead !== undefined && cacheRead > 0)
378
+ if (cacheRead !== undefined)
349
379
  usage.cacheReadTokens = cacheRead;
350
380
  if (cacheWrite !== undefined && cacheWrite > 0)
351
381
  usage.cacheWriteTokens = cacheWrite;
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "local-agent",
9
9
  "kimi"
10
10
  ],
11
- "version": "0.2.0-rc.3",
11
+ "version": "0.2.1",
12
12
  "type": "module",
13
13
  "main": "lib/index.js",
14
14
  "types": "lib/types/index.d.ts",
@@ -69,7 +69,7 @@
69
69
  "dsh": ">=0.1.5-rc.1 <0.3.0-0"
70
70
  },
71
71
  "dependencies": {
72
- "@khorsheed/dsh-local-agent": "^0.2.0-rc.3"
72
+ "@khorsheed/dsh-local-agent": "^0.2.1"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "@deepseek-ai/cordis": "^4.0.1",