@deepseek-ai/dsh-session-title 0.1.2-alpha.3 → 0.1.2-alpha.5

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/lib/index.js CHANGED
@@ -3,6 +3,7 @@ import z from "@deepseek-ai/schemastery";
3
3
  import { z as z$1 } from "zod";
4
4
  import { isAgentLoopRequest } from "@deepseek-ai/dsh-llm";
5
5
  import { assertNever, deepFreeze } from "@deepseek-ai/dsh-util-values";
6
+ import { SessionSeq } from "@deepseek-ai/dsh-session";
6
7
  //#region lib/types/normalize.js
7
8
  /** Title text normalization and UTF-8-safe truncation. */
8
9
  /** Operating-system-command escape sequences, including unterminated tails. */
@@ -133,13 +134,13 @@ const EMPTY_TITLE_INPUT = {
133
134
  lastSeq: null
134
135
  };
135
136
  const sessionTitleUserMessageSchema = z$1.object({
136
- seq: z$1.number().int().nonnegative(),
137
+ seq: z$1.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq),
137
138
  text: z$1.string()
138
139
  }).strict();
139
140
  const titleInputStateSchema = z$1.object({
140
141
  first: sessionTitleUserMessageSchema.nullable(),
141
142
  count: z$1.number().int().nonnegative(),
142
- lastSeq: z$1.number().int().nonnegative().nullable()
143
+ lastSeq: z$1.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq).nullable()
143
144
  }).strict().superRefine((state, context) => {
144
145
  const empty = state.first === null && state.lastSeq === null && state.count === 0;
145
146
  const populated = state.first !== null && state.lastSeq !== null && state.count > 0 && state.first.seq <= state.lastSeq;
@@ -278,7 +279,7 @@ var SessionTitleService = class extends Service {
278
279
  * @returns latest title snapshot, or `undefined` before eligible input.
279
280
  */
280
281
  get(session) {
281
- return foldSessionTitle(session.events);
282
+ return foldSessionTitle(session.snapshotEvents());
282
283
  }
283
284
  /**
284
285
  * Accept an explicit user title. Appends a `session/title` event with the
@@ -452,7 +453,7 @@ var SessionTitleService = class extends Service {
452
453
  this.assertCurrent(session, work);
453
454
  await this.ensureFallback(session);
454
455
  this.assertCurrent(session, work);
455
- const messages = collectSessionTitleMessages(session.events, work.throughSeq);
456
+ const messages = collectSessionTitleMessages(session.snapshotEvents(), work.throughSeq);
456
457
  const result = await work.registration.provider.generate({
457
458
  session,
458
459
  messages,
@@ -488,10 +489,11 @@ var SessionTitleService = class extends Service {
488
489
  const order = new Map(messages.map((message, index) => [message.seq, index]));
489
490
  let previous = -1;
490
491
  for (const seq of candidate.messageSeqs) {
491
- if (typeof seq !== "number") throw new Error("session-title provider messageSeqs must be unique, ordered seqs from the request");
492
- const index = order.get(seq);
493
- if (!Number.isSafeInteger(seq) || seq < 0 || index === void 0 || index <= previous) throw new Error("session-title provider messageSeqs must be unique, ordered seqs from the request");
494
- messageSeqs.push(seq);
492
+ if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq < 0) throw new Error("session-title provider messageSeqs must be unique, ordered seqs from the request");
493
+ const sessionSeq = SessionSeq(seq);
494
+ const index = order.get(sessionSeq);
495
+ if (index === void 0 || index <= previous) throw new Error("session-title provider messageSeqs must be unique, ordered seqs from the request");
496
+ messageSeqs.push(sessionSeq);
495
497
  previous = index;
496
498
  }
497
499
  const modelCandidate = candidate.model;
package/lib/invariant.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SessionSeq } from "@deepseek-ai/dsh-session";
1
2
  //#region lib/types/invariant.js
2
3
  /**
3
4
  * Package-owned invariant companion for `@deepseek-ai/dsh-session-title`.
@@ -16,16 +17,36 @@ const inject = ["invariants"];
16
17
  * relationship every appended `session/title` event must keep, whichever
17
18
  * writer produced it.
18
19
  */
20
+ function validate(session, event, fail) {
21
+ const { source, messageSeqs } = event.data;
22
+ if (messageSeqs.length === 0 !== (source.kind === "user")) {
23
+ const requirement = source.kind === "user" ? "cite no message seqs" : "cite at least one message seq";
24
+ fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`);
25
+ }
26
+ const seen = /* @__PURE__ */ new Set();
27
+ for (const seq of messageSeqs) {
28
+ let checked;
29
+ try {
30
+ checked = SessionSeq(seq);
31
+ } catch {
32
+ fail(`session/title event ${String(event.seq)} has an invalid message seq ${String(seq)}`);
33
+ }
34
+ if (seen.has(checked)) fail(`session/title event ${String(event.seq)} repeats message seq ${checked}`);
35
+ seen.add(checked);
36
+ const cited = checked < event.seq ? session.eventAt(checked) : void 0;
37
+ if (cited?.type !== "user/message" || cited.data.source.kind !== "user") fail(`session/title event ${String(event.seq)} message seq ${checked} must name an earlier human user/message`);
38
+ }
39
+ }
19
40
  const install = Object.assign((ctx, fail) => {
41
+ const validateExisting = (session) => {
42
+ for (const event of session.snapshotEvents()) if (event.type === "session/title") validate(session, event, fail);
43
+ };
44
+ ctx.sessions.list().forEach(validateExisting);
45
+ ctx.on("session/created", validateExisting, { global: true });
20
46
  ctx.on("internal/dispatch", (_mode, eventName, args) => {
21
47
  if (eventName !== "session/event") return;
22
- const [, event] = args;
23
- if (event.type !== "session/title") return;
24
- const { source, messageSeqs } = event.data;
25
- if (messageSeqs.length === 0 !== (source.kind === "user")) {
26
- const requirement = source.kind === "user" ? "cite no message seqs" : "cite at least one message seq";
27
- fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`);
28
- }
48
+ const [session, event] = args;
49
+ if (event.type === "session/title") validate(session, event, fail);
29
50
  }, { global: true });
30
51
  }, { inject: ["sessions"] });
31
52
  /**
@@ -7,6 +7,7 @@ import z from '@deepseek-ai/schemastery';
7
7
  import { z as zod } from 'zod';
8
8
  import type { Branded } from '@deepseek-ai/dsh-brand';
9
9
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
10
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
10
11
  export type { SessionTitleEventData, SessionTitleModelProvenance, SessionTitleSnapshot, SessionTitleSource, SessionTitleUserMessage, TitleProjection, } from './types.ts';
11
12
  import type { SessionTitleEventData, SessionTitleModelProvenance, SessionTitleSnapshot, SessionTitleUserMessage } from './types.ts';
12
13
  /** Identifies one session-title provider registration. */
@@ -68,7 +69,7 @@ export interface SessionTitleProviderResult {
68
69
  /** Proposed title text. */
69
70
  readonly title: string;
70
71
  /** Exact seqs from `request.messages` used by this result. */
71
- readonly messageSeqs: readonly number[];
72
+ readonly messageSeqs: readonly SessionSeq[];
72
73
  /** Auxiliary LLM route, when generation used a model. */
73
74
  readonly model?: SessionTitleModelProvenance;
74
75
  }
@@ -7,6 +7,7 @@ import z from '@deepseek-ai/schemastery';
7
7
  import { z as zod } from 'zod';
8
8
  import { isAgentLoopRequest } from '@deepseek-ai/dsh-llm';
9
9
  import { assertNever, deepFreeze } from '@deepseek-ai/dsh-util-values';
10
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
10
11
  import { fallbackSessionTitle, normalizeSessionTitle } from "./normalize.js";
11
12
  /**
12
13
  * Brand a raw provider id.
@@ -75,13 +76,13 @@ function titleSnapshotFromState(state) {
75
76
  }
76
77
  const EMPTY_TITLE_INPUT = { first: null, count: 0, lastSeq: null };
77
78
  const sessionTitleUserMessageSchema = zod.object({
78
- seq: zod.number().int().nonnegative(),
79
+ seq: zod.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq),
79
80
  text: zod.string(),
80
81
  }).strict();
81
82
  const titleInputStateSchema = zod.object({
82
83
  first: sessionTitleUserMessageSchema.nullable(),
83
84
  count: zod.number().int().nonnegative(),
84
- lastSeq: zod.number().int().nonnegative().nullable(),
85
+ lastSeq: zod.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq).nullable(),
85
86
  }).strict().superRefine((state, context) => {
86
87
  const empty = state.first === null && state.lastSeq === null && state.count === 0;
87
88
  const populated = state.first !== null
@@ -235,7 +236,7 @@ export class SessionTitleService extends Service {
235
236
  * @returns latest title snapshot, or `undefined` before eligible input.
236
237
  */
237
238
  get(session) {
238
- return foldSessionTitle(session.events);
239
+ return foldSessionTitle(session.snapshotEvents());
239
240
  }
240
241
  /**
241
242
  * Accept an explicit user title. Appends a `session/title` event with the
@@ -439,7 +440,7 @@ export class SessionTitleService extends Service {
439
440
  this.assertCurrent(session, work);
440
441
  await this.ensureFallback(session);
441
442
  this.assertCurrent(session, work);
442
- const messages = collectSessionTitleMessages(session.events, work.throughSeq);
443
+ const messages = collectSessionTitleMessages(session.snapshotEvents(), work.throughSeq);
443
444
  const result = await work.registration.provider.generate({
444
445
  session,
445
446
  messages,
@@ -483,14 +484,15 @@ export class SessionTitleService extends Service {
483
484
  const order = new Map(messages.map((message, index) => [message.seq, index]));
484
485
  let previous = -1;
485
486
  for (const seq of candidate.messageSeqs) {
486
- if (typeof seq !== 'number') {
487
+ if (typeof seq !== 'number' || !Number.isSafeInteger(seq) || seq < 0) {
487
488
  throw new Error('session-title provider messageSeqs must be unique, ordered seqs from the request');
488
489
  }
489
- const index = order.get(seq);
490
- if (!Number.isSafeInteger(seq) || seq < 0 || index === undefined || index <= previous) {
490
+ const sessionSeq = SessionSeq(seq);
491
+ const index = order.get(sessionSeq);
492
+ if (index === undefined || index <= previous) {
491
493
  throw new Error('session-title provider messageSeqs must be unique, ordered seqs from the request');
492
494
  }
493
- messageSeqs.push(seq);
495
+ messageSeqs.push(sessionSeq);
494
496
  previous = index;
495
497
  }
496
498
  const modelCandidate = candidate.model;
@@ -2,6 +2,7 @@
2
2
  * Package-owned invariant companion for `@deepseek-ai/dsh-session-title`.
3
3
  * @module @deepseek-ai/dsh-session-title/invariant
4
4
  */
5
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
5
6
  const PACKAGE_NAME = '@deepseek-ai/dsh-session-title';
6
7
  /** Cordis companion plugin name. */
7
8
  export const name = 'session-title-invariant';
@@ -15,20 +16,48 @@ export const inject = ['invariants'];
15
16
  * relationship every appended `session/title` event must keep, whichever
16
17
  * writer produced it.
17
18
  */
19
+ function validate(session, event, fail) {
20
+ const { source, messageSeqs } = event.data;
21
+ if ((messageSeqs.length === 0) !== (source.kind === 'user')) {
22
+ const requirement = source.kind === 'user' ? 'cite no message seqs' : 'cite at least one message seq';
23
+ fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`);
24
+ }
25
+ const seen = new Set();
26
+ for (const seq of messageSeqs) {
27
+ let checked;
28
+ try {
29
+ checked = SessionSeq(seq);
30
+ }
31
+ catch {
32
+ fail(`session/title event ${String(event.seq)} has an invalid message seq ${String(seq)}`);
33
+ }
34
+ if (seen.has(checked)) {
35
+ fail(`session/title event ${String(event.seq)} repeats message seq ${checked}`);
36
+ }
37
+ seen.add(checked);
38
+ const cited = checked < event.seq ? session.eventAt(checked) : undefined;
39
+ if (cited?.type !== 'user/message' || cited.data.source.kind !== 'user') {
40
+ fail(`session/title event ${String(event.seq)} message seq ${checked} must name an earlier human user/message`);
41
+ }
42
+ }
43
+ }
18
44
  const install = Object.assign((ctx, fail) => {
45
+ const validateExisting = (session) => {
46
+ for (const event of session.snapshotEvents()) {
47
+ if (event.type === 'session/title')
48
+ validate(session, event, fail);
49
+ }
50
+ };
51
+ ctx.sessions.list().forEach(validateExisting);
52
+ ctx.on('session/created', validateExisting, { global: true });
19
53
  // internal/dispatch interception rejects the append before publication
20
54
  // (the session/event listener would only observe the already-committed log).
21
55
  ctx.on('internal/dispatch', (_mode, eventName, args) => {
22
56
  if (eventName !== 'session/event')
23
57
  return;
24
- const [, event] = args;
25
- if (event.type !== 'session/title')
26
- return;
27
- const { source, messageSeqs } = event.data;
28
- if ((messageSeqs.length === 0) !== (source.kind === 'user')) {
29
- const requirement = source.kind === 'user' ? 'cite no message seqs' : 'cite at least one message seq';
30
- fail(`session/title event ${String(event.seq)} with source "${source.kind}" must ${requirement}; got ${String(messageSeqs.length)}`);
31
- }
58
+ const [session, event] = args;
59
+ if (event.type === 'session/title')
60
+ validate(session, event, fail);
32
61
  }, { global: true });
33
62
  }, { inject: ['sessions'] });
34
63
  /**
@@ -9,6 +9,7 @@
9
9
  */
10
10
  export {};
11
11
  import type { Branded } from '@deepseek-ai/dsh-brand';
12
+ import type { OptionalSessionSeq, SessionSeq } from '@deepseek-ai/dsh-session/types';
12
13
  /** Identifies one session-title provider registration. */
13
14
  export type SessionTitleProviderId = Branded<'SessionTitleProviderId'>;
14
15
  /** Exact auxiliary model route that produced a title. */
@@ -34,14 +35,14 @@ export interface SessionTitleEventData {
34
35
  /** Normalized non-empty title text. */
35
36
  readonly title: string;
36
37
  /** Exact human `user/message` seqs used to derive this title; empty for an explicit user rename. */
37
- readonly messageSeqs: number[];
38
+ readonly messageSeqs: SessionSeq[];
38
39
  /** Whether the built-in fallback, a registered provider, or the user supplied the title. */
39
40
  readonly source: SessionTitleSource;
40
41
  }
41
42
  /** Latest folded title plus the title event's durable envelope facts. */
42
43
  export interface SessionTitleSnapshot extends SessionTitleEventData {
43
44
  /** Seq of the latest `session/title` event. */
44
- readonly eventSeq: number;
45
+ readonly eventSeq: SessionSeq;
45
46
  /** Timestamp of the latest `session/title` event. */
46
47
  readonly updatedAt: number;
47
48
  }
@@ -50,7 +51,7 @@ export type TitleProjection = SessionTitleSnapshot;
50
51
  /** One eligible human text message exposed to title providers. */
51
52
  export interface SessionTitleUserMessage {
52
53
  /** Source `user/message` event seq. */
53
- readonly seq: number;
54
+ readonly seq: SessionSeq;
54
55
  /** Exact concatenated text-block content. */
55
56
  readonly text: string;
56
57
  }
@@ -61,7 +62,7 @@ export interface TitleInputState {
61
62
  /** Total eligible messages folded so far. */
62
63
  readonly count: number;
63
64
  /** Seq of the newest eligible message, or null before any. */
64
- readonly lastSeq: number | null;
65
+ readonly lastSeq: OptionalSessionSeq;
65
66
  }
66
67
  declare module '@deepseek-ai/dsh-session-projection/types' {
67
68
  interface SessionProjectionStateMap {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-session-title",
3
3
  "description": "Log-backed session title service and provider registry for the DeepSeek Harness",
4
- "version": "0.1.2-alpha.3",
4
+ "version": "0.1.2-alpha.5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -41,28 +41,28 @@
41
41
  ],
42
42
  "license": "MIT",
43
43
  "peerDependencies": {
44
+ "@deepseek-ai/dsh-brand": "^0.1.2-alpha.5",
45
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.5",
44
46
  "@deepseek-ai/cordis": "^4.0.2",
45
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
46
- "@deepseek-ai/dsh-brand": "^0.1.2-alpha.3",
47
- "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
48
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
49
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
50
- "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3"
47
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.5",
48
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
49
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.5",
50
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.5"
51
51
  },
52
52
  "dependencies": {
53
53
  "zod": "^4.4.3",
54
54
  "@deepseek-ai/schemastery": "^3.18.2",
55
- "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.3"
55
+ "@deepseek-ai/dsh-util-values": "^0.1.2-alpha.5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@deepseek-ai/cordis": "^4.0.2",
59
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
60
- "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.3",
61
- "@deepseek-ai/dsh-brand": "^0.1.2-alpha.3",
62
- "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
63
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
64
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
65
- "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.2-alpha.3",
66
- "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3"
59
+ "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.5",
60
+ "@deepseek-ai/dsh-brand": "^0.1.2-alpha.5",
61
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.5",
62
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.5",
63
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.5",
64
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.5",
65
+ "@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.5",
66
+ "@deepseek-ai/dsh-session-persistence-jsonl": "^0.1.2-alpha.5"
67
67
  }
68
68
  }