@khorsheed/dsh-client-message-tools 0.1.0

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +82 -0
  4. package/README.i18n.yaml +6 -0
  5. package/README.md +82 -0
  6. package/cordis.patch.yml +6 -0
  7. package/lib/client.js +5824 -0
  8. package/lib/client.js.map +1 -0
  9. package/lib/index.js +644 -0
  10. package/lib/invariant.js +27 -0
  11. package/lib/tsconfig.client.tsbuildinfo +1 -0
  12. package/lib/tsconfig.host.tsbuildinfo +1 -0
  13. package/lib/typert.host.d.ts +3 -0
  14. package/lib/typert.host.js +250 -0
  15. package/lib/typert.remote-client.d.ts +26 -0
  16. package/lib/typert.remote-client.d.ts.map +1 -0
  17. package/lib/typert.remote-client.js +154 -0
  18. package/lib/types/client/ModelChip.d.ts +29 -0
  19. package/lib/types/client/ModelChip.d.ts.map +1 -0
  20. package/lib/types/client/ModelChip.js +86 -0
  21. package/lib/types/client/RestoredMessageView.d.ts +17 -0
  22. package/lib/types/client/RestoredMessageView.d.ts.map +1 -0
  23. package/lib/types/client/RestoredMessageView.js +9 -0
  24. package/lib/types/client/UserMessageView.d.ts +16 -0
  25. package/lib/types/client/UserMessageView.d.ts.map +1 -0
  26. package/lib/types/client/UserMessageView.js +172 -0
  27. package/lib/types/client/WithdrawnDividerView.d.ts +17 -0
  28. package/lib/types/client/WithdrawnDividerView.d.ts.map +1 -0
  29. package/lib/types/client/WithdrawnDividerView.js +59 -0
  30. package/lib/types/client/backfill.d.ts +19 -0
  31. package/lib/types/client/backfill.d.ts.map +1 -0
  32. package/lib/types/client/backfill.js +20 -0
  33. package/lib/types/client/dom-hider.d.ts +61 -0
  34. package/lib/types/client/dom-hider.d.ts.map +1 -0
  35. package/lib/types/client/dom-hider.js +195 -0
  36. package/lib/types/client/edit-in-place.d.ts +107 -0
  37. package/lib/types/client/edit-in-place.d.ts.map +1 -0
  38. package/lib/types/client/edit-in-place.js +97 -0
  39. package/lib/types/client/icons.d.ts +23 -0
  40. package/lib/types/client/icons.d.ts.map +1 -0
  41. package/lib/types/client/icons.js +11 -0
  42. package/lib/types/client/index.d.ts +39 -0
  43. package/lib/types/client/index.d.ts.map +1 -0
  44. package/lib/types/client/index.js +187 -0
  45. package/lib/types/client/locales.d.ts +74 -0
  46. package/lib/types/client/locales.d.ts.map +1 -0
  47. package/lib/types/client/locales.js +65 -0
  48. package/lib/types/client/slots.d.ts +44 -0
  49. package/lib/types/client/slots.d.ts.map +1 -0
  50. package/lib/types/client/slots.js +1 -0
  51. package/lib/types/client/withdraw-backfill.d.ts +25 -0
  52. package/lib/types/client/withdraw-backfill.d.ts.map +1 -0
  53. package/lib/types/client/withdraw-backfill.js +21 -0
  54. package/lib/types/client/withdrawn-node.d.ts +163 -0
  55. package/lib/types/client/withdrawn-node.d.ts.map +1 -0
  56. package/lib/types/client/withdrawn-node.js +297 -0
  57. package/lib/types/index.d.ts +69 -0
  58. package/lib/types/index.d.ts.map +1 -0
  59. package/lib/types/index.js +175 -0
  60. package/lib/types/invariant.d.ts +16 -0
  61. package/lib/types/invariant.d.ts.map +1 -0
  62. package/lib/types/invariant.js +25 -0
  63. package/lib/types/marker.d.ts +94 -0
  64. package/lib/types/marker.d.ts.map +1 -0
  65. package/lib/types/marker.js +131 -0
  66. package/lib/types/types.d.ts +116 -0
  67. package/lib/types/types.d.ts.map +1 -0
  68. package/lib/types/types.js +1 -0
  69. package/lib/types/withdraw.d.ts +112 -0
  70. package/lib/types/withdraw.d.ts.map +1 -0
  71. package/lib/types/withdraw.js +279 -0
  72. package/package.json +108 -0
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Slot-facing types of the message-tools client half: the injected action
3
+ * face and the composed props of its two `conversation.chat.node` entries.
4
+ */
5
+ import type { ModelSelection } from '@deepseek-ai/dsh-api-remotes/client';
6
+ import type { ModelDirectoryState } from '@deepseek-ai/dsh-client-ui-model-selection/client';
7
+ import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
8
+ /** Injected action face of the shadowed user-message entry. */
9
+ export interface MessageToolsInjected {
10
+ hooks: {
11
+ /** The session's shared model directory (the composer seat's own store; a frozen empty stub when ui-model-selection is absent). */
12
+ modelDirectory: HostObservable<ModelDirectoryState>;
13
+ };
14
+ /** Whether the editor's model chip can switch models in this session. */
15
+ modelsAvailable: boolean;
16
+ /** Refresh the advisory model directory (fire-and-forget; errors land on the store). */
17
+ loadModels: () => void;
18
+ /** Submit a provider/model/effort selection; resolves to host acceptance. */
19
+ selectModel: (selection: ModelSelection) => Promise<boolean>;
20
+ /**
21
+ * Edit one user message in place: replace it (and the surface tail) with
22
+ * the edited text, then start the regeneration turn.
23
+ */
24
+ editMessage: (targetSeq: number, text: string) => Promise<void>;
25
+ /** Withdraw one user message and everything after it. */
26
+ withdrawMessage: (targetSeq: number) => Promise<void>;
27
+ /**
28
+ * Backfill text into the session's composer draft (append on a new line
29
+ * when the draft is non-empty) and surface an info notice; never sends.
30
+ * Wired to a successful withdrawal — the「重新编辑」divider action shares
31
+ * the same path.
32
+ */
33
+ backfillDraft: (text: string) => void;
34
+ }
35
+ /** Full props of the shadowed 'user'/'steering'/'message-tools-edited'/'message-tools-restored' renderer (all keys share it). */
36
+ export type UserMessageViewProps = PropsRuntime<'conversation.chat.node', 'user' | 'steering' | 'message-tools-edited' | 'message-tools-restored'> & InjectFace<MessageToolsInjected> & PropsLocale<'message-tools'>;
37
+ /** Injected action face of the withdrawal divider entry. */
38
+ export interface WithdrawnDividerInjected {
39
+ /** Restore the span's withdrawn user message as a fresh tail message. */
40
+ restoreMessage: (targetSeq: number) => Promise<void>;
41
+ }
42
+ /** Full props of the withdrawal divider renderer. */
43
+ export type WithdrawnDividerViewProps = PropsRuntime<'conversation.chat.node', 'message-tools-withdrawn'> & InjectFace<WithdrawnDividerInjected> & PropsLocale<'message-tools'>;
44
+ //# sourceMappingURL=slots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slots.d.ts","sourceRoot":"","sources":["../../../src/client/slots.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AACzE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAA;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAO7G,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE;QACL,mIAAmI;QACnI,cAAc,EAAE,cAAc,CAAC,mBAAmB,CAAC,CAAA;KACpD,CAAA;IACD,yEAAyE;IACzE,eAAe,EAAE,OAAO,CAAA;IACxB,wFAAwF;IACxF,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,6EAA6E;IAC7E,WAAW,EAAE,CAAC,SAAS,EAAE,cAAc,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5D;;;OAGG;IACH,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/D,yDAAyD;IACzD,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrD;;;;;OAKG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC;AAED,iIAAiI;AACjI,MAAM,MAAM,oBAAoB,GAC9B,YAAY,CAAC,wBAAwB,EAAE,MAAM,GAAG,UAAU,GAAG,sBAAsB,GAAG,wBAAwB,CAAC,GAC7G,UAAU,CAAC,oBAAoB,CAAC,GAChC,WAAW,CAAC,eAAe,CAAC,CAAA;AAEhC,4DAA4D;AAC5D,MAAM,WAAW,wBAAwB;IACvC,yEAAyE;IACzE,cAAc,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAED,qDAAqD;AACrD,MAAM,MAAM,yBAAyB,GACnC,YAAY,CAAC,wBAAwB,EAAE,yBAAyB,CAAC,GAC/D,UAAU,CAAC,wBAAwB,CAAC,GACpC,WAAW,CAAC,eAAe,CAAC,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Withdraw-with-backfill choreography for the shadowed user renderer: a
3
+ * successful withdrawal automatically backfills the withdrawn message's
4
+ * original text into the session composer draft (never sends); a failed
5
+ * withdrawal backfills nothing. Kept free of services so the unit tests
6
+ * drive it with stub verbs (the edit-in-place.ts pattern). The in-place EDIT
7
+ * path never goes through here — editing has its own channel.
8
+ */
9
+ /** The verbs of one withdraw-with-backfill, bound to a session by the caller. */
10
+ export interface WithdrawBackfillSteps {
11
+ /** Withdraw the target message (rejects on failure). */
12
+ withdraw: () => Promise<void>;
13
+ /** Backfill text into the composer draft. */
14
+ backfill: (text: string) => void;
15
+ }
16
+ /**
17
+ * Withdraw one message, then backfill its text into the draft — only after
18
+ * the withdrawal lands, and only when there is text to fill (an image-only
19
+ * message has no draft-worthy text).
20
+ * @param steps - the withdraw/backfill verbs.
21
+ * @param text - the withdrawn message's original text.
22
+ * @returns completion; a failed withdrawal rejects without backfilling.
23
+ */
24
+ export declare function withdrawAndBackfill(steps: WithdrawBackfillSteps, text: string): Promise<void>;
25
+ //# sourceMappingURL=withdraw-backfill.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withdraw-backfill.d.ts","sourceRoot":"","sources":["../../../src/client/withdraw-backfill.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACjC;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGnG"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Withdraw-with-backfill choreography for the shadowed user renderer: a
3
+ * successful withdrawal automatically backfills the withdrawn message's
4
+ * original text into the session composer draft (never sends); a failed
5
+ * withdrawal backfills nothing. Kept free of services so the unit tests
6
+ * drive it with stub verbs (the edit-in-place.ts pattern). The in-place EDIT
7
+ * path never goes through here — editing has its own channel.
8
+ */
9
+ /**
10
+ * Withdraw one message, then backfill its text into the draft — only after
11
+ * the withdrawal lands, and only when there is text to fill (an image-only
12
+ * message has no draft-worthy text).
13
+ * @param steps - the withdraw/backfill verbs.
14
+ * @param text - the withdrawn message's original text.
15
+ * @returns completion; a failed withdrawal rejects without backfilling.
16
+ */
17
+ export async function withdrawAndBackfill(steps, text) {
18
+ await steps.withdraw();
19
+ if (text !== '')
20
+ steps.backfill(text);
21
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Withdrawal projection on the chat node store: the Definition that claims
3
+ * message-tools replacement events into divider rows, the Definitions that
4
+ * claim message-tools restore events into replayed rows (user-message bubbles
5
+ * and assistant-text lines), the Definition that claims edit replacements
6
+ * into edited bubbles, plus the pure folds the renderers use (hidden spans,
7
+ * span counts, read-only replay entries). The assembler matches every
8
+ * Definition per event and offers no suppression seam; hiding happens in the
9
+ * renderers and the DOM hider.
10
+ */
11
+ import type { ChatConversationViewNode, ConversationNodeDefinition } from '@deepseek-ai/dsh-client-runtime/client';
12
+ /** Chat node data of one withdrawal divider row. */
13
+ export interface WithdrawnDividerData {
14
+ /** Seq of the replacement event; the divider anchors here and the hidden span ends (exclusive). */
15
+ readonly seq: number;
16
+ /** First hidden seq: the withdrawn user message itself. */
17
+ readonly hiddenStartSeq: number;
18
+ }
19
+ /** Chat node data of one edited-message bubble row (the edit replacement itself). */
20
+ export interface EditedMessageData {
21
+ /** Seq of the edit replacement event; the bubble anchors here and the hidden span ends (exclusive). */
22
+ readonly seq: number;
23
+ /** Unix epoch ms from the edit replacement event. */
24
+ readonly time: number;
25
+ /** The edited content (what the model now reads in place). */
26
+ readonly content: readonly unknown[];
27
+ /** First hidden seq: the pre-edit message the replacement covers. */
28
+ readonly hiddenStartSeq: number;
29
+ }
30
+ /** Chat node data of one restored (replayed at the tail) user message. */
31
+ export interface RestoredMessageData {
32
+ /** Seq of the restore event. */
33
+ readonly seq: number;
34
+ /** Unix epoch ms from the restore event. */
35
+ readonly time: number;
36
+ /** Seq of the withdrawn original this replays, when cited. */
37
+ readonly restoredFromSeq: number | undefined;
38
+ /** The replayed content blocks (verbatim — images included). */
39
+ readonly content: readonly unknown[];
40
+ /** The original text, replayed verbatim. */
41
+ readonly text: string;
42
+ }
43
+ /** Chat node data of one restored assistant-text replay line. */
44
+ export interface RestoredAssistantMessageData {
45
+ /** Seq of the restore-assistant replay event. */
46
+ readonly seq: number;
47
+ /** Unix epoch ms from the replay event. */
48
+ readonly time: number;
49
+ /** Seq of the withdrawn assistant message this replays, when cited. */
50
+ readonly restoredFromSeq: number | undefined;
51
+ /** The assistant reply text (model-facing frame stripped for display). */
52
+ readonly text: string;
53
+ }
54
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
55
+ interface ChatNodeDataMap {
56
+ /** message-tools withdrawal divider marking where a span was hidden. */
57
+ 'message-tools-withdrawn': WithdrawnDividerData;
58
+ /** message-tools restore row: a withdrawn user message replayed at the tail. */
59
+ 'message-tools-restored': RestoredMessageData;
60
+ /** message-tools restore line: a withdrawn assistant reply's text replayed at the tail. */
61
+ 'message-tools-restored-assistant': RestoredAssistantMessageData;
62
+ /** message-tools edit row: the edit replacement rendered as the edited bubble in place. */
63
+ 'message-tools-edited': EditedMessageData;
64
+ }
65
+ }
66
+ /**
67
+ * The withdrawal divider Definition: one single-event Context per
68
+ * message-tools replacement event, anchored at the replacement's seq so the
69
+ * divider sits exactly where the span was hidden.
70
+ */
71
+ export declare const withdrawnDividerDefinition: ConversationNodeDefinition<WithdrawnDividerData>;
72
+ /**
73
+ * The edited-bubble Definition: one single-event Context per message-tools
74
+ * edit replacement event — the replacement's content IS the edited text, so
75
+ * the row renders as a normal user bubble (with an「已编辑」badge) exactly
76
+ * where the span was discarded.
77
+ */
78
+ export declare const editedMessageDefinition: ConversationNodeDefinition<EditedMessageData>;
79
+ /**
80
+ * Fold the hidden seq spans out of the live chat nodes, flattened to
81
+ * `[start, endExclusive, ...]` pairs ordered by start. Both withdrawal
82
+ * dividers and edit bubbles carry `{ seq, hiddenStartSeq }` spans. The flat
83
+ * number array keeps the useSession selector stable under shallowEqual until
84
+ * a withdrawal or edit lands.
85
+ * @param nodes - every materialized chat node (any order).
86
+ * @returns the flattened hidden spans.
87
+ */
88
+ export declare function foldHiddenRanges(nodes: readonly ChatConversationViewNode[]): number[];
89
+ /**
90
+ * The restore-row Definition: one single-event Context per message-tools
91
+ * user-message restore event (append-surface, plugin-tagged), replaying the
92
+ * withdrawn message's content at the tail.
93
+ */
94
+ export declare const restoredMessageDefinition: ConversationNodeDefinition<RestoredMessageData>;
95
+ /**
96
+ * The restore-assistant-line Definition: one single-event Context per
97
+ * message-tools assistant-text restore replay (append-surface, op
98
+ * 'restore-assistant'), rendering the reply text at the tail. The
99
+ * model-facing frame is stripped for display — it belongs to the model
100
+ * context, not the transcript row.
101
+ */
102
+ export declare const restoredAssistantMessageDefinition: ConversationNodeDefinition<RestoredAssistantMessageData>;
103
+ /**
104
+ * Count the user-visible messages hidden by one withdrawn span. This mirrors
105
+ * {@link collectWithdrawnEntries}: context injections, tool calls, turn tails,
106
+ * and other UI chrome are not counted as “messages”, while restored/edited
107
+ * rows and reasoning-only assistant steps are.
108
+ * @param nodes - every materialized chat node.
109
+ * @param startSeq - first hidden seq (inclusive).
110
+ * @param endSeq - the divider's seq (exclusive).
111
+ * @returns the hidden message count.
112
+ */
113
+ export declare function countHiddenInSpan(nodes: readonly ChatConversationViewNode[], startSeq: number, endSeq: number): number;
114
+ /** One read-only replay entry of the divider's expand area. */
115
+ export interface WithdrawnEntry {
116
+ /** Source node kind ('user' original or 'assistant-step' summary). */
117
+ readonly kind: 'user' | 'assistant';
118
+ /** The entry's text (full for user originals, a text join for assistant steps). */
119
+ readonly text: string;
120
+ }
121
+ /**
122
+ * Collect the read-only replay of one withdrawn span: user originals and
123
+ * assistant text, in anchor order. Other kinds carry no user-readable text
124
+ * and are skipped. Assistant-step data is kind-keyed (`data.blocks`, with
125
+ * `data.finalNode.blocks` once settled — `conversation-nodes/assistant.ts`),
126
+ * not type-keyed like message content. Reads the live node store — call from
127
+ * event handlers.
128
+ * @param nodes - every materialized chat node.
129
+ * @param startSeq - first hidden seq (inclusive).
130
+ * @param endSeq - the divider's seq (exclusive).
131
+ * @returns the replay entries.
132
+ */
133
+ export declare function collectWithdrawnEntries(nodes: readonly ChatConversationViewNode[], startSeq: number, endSeq: number): WithdrawnEntry[];
134
+ /**
135
+ * Whether one withdrawn span has a LIVE restore row (a restored node citing
136
+ * the span's first hidden seq and not itself inside a hidden span). When the
137
+ * restore rows are withdrawn again, the badge clears and the divider's
138
+ * restore action re-enables — the restore events stay in the log either way.
139
+ * @param nodes - every materialized chat node.
140
+ * @param hiddenStartSeq - the span's first hidden seq.
141
+ * @param ranges - the flattened hidden spans from foldHiddenRanges.
142
+ * @returns true when a live restore row for the span exists.
143
+ */
144
+ export declare function hasRestoreForSpan(nodes: readonly ChatConversationViewNode[], hiddenStartSeq: number, ranges: readonly number[]): boolean;
145
+ /**
146
+ * Whether a span's restore rows have themselves been withdrawn by a later
147
+ * span. The earlier divider remains as history, but the later divider is the
148
+ * active restore point; offering restore on both would duplicate the same
149
+ * logical content when the user toggles withdraw → restore → withdraw.
150
+ * @param nodes - every materialized chat node.
151
+ * @param hiddenStartSeq - the span's first hidden seq.
152
+ * @param ranges - the flattened hidden spans from foldHiddenRanges.
153
+ * @returns true when this span's restore rows are hidden inside a later span.
154
+ */
155
+ export declare function isRestoreSuperseded(nodes: readonly ChatConversationViewNode[], hiddenStartSeq: number, ranges: readonly number[]): boolean;
156
+ /**
157
+ * Whether a node seq falls inside a hidden span.
158
+ * @param ranges - the flattened spans from foldHiddenRanges.
159
+ * @param seq - the node seq to test.
160
+ * @returns true when the seq is hidden.
161
+ */
162
+ export declare function isSeqHidden(ranges: readonly number[], seq: number): boolean;
163
+ //# sourceMappingURL=withdrawn-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"withdrawn-node.d.ts","sourceRoot":"","sources":["../../../src/client/withdrawn-node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,297 @@
1
+ import { isMessageToolsEdit, isMessageToolsReplacement, isMessageToolsRestore, isMessageToolsRestoreAssistant, stripRestoreAssistantFrame, } from '../marker.js';
2
+ /**
3
+ * The withdrawal divider Definition: one single-event Context per
4
+ * message-tools replacement event, anchored at the replacement's seq so the
5
+ * divider sits exactly where the span was hidden.
6
+ */
7
+ export const withdrawnDividerDefinition = {
8
+ kind: 'message-tools-withdrawn',
9
+ target: 'chat',
10
+ match: event => isMessageToolsReplacement(event)
11
+ ? { id: String(event.seq), role: 'start' }
12
+ : null,
13
+ start: (_context, match) => {
14
+ const event = match.event;
15
+ if (!isMessageToolsReplacement(event)) {
16
+ throw new Error('message-tools-withdrawn start requires a message-tools replacement event');
17
+ }
18
+ return { seq: event.seq, hiddenStartSeq: event.surfaceOp.start };
19
+ },
20
+ update: context => context.state,
21
+ buildViewNode: context => context.state === undefined
22
+ ? null
23
+ : {
24
+ key: context.key,
25
+ kind: 'message-tools-withdrawn',
26
+ id: context.id,
27
+ target: 'chat',
28
+ anchorSeq: context.state.seq,
29
+ location: context.start?.location ?? { kind: 'unresolved' },
30
+ visibility: 'visible',
31
+ data: context.state,
32
+ },
33
+ };
34
+ /**
35
+ * The edited-bubble Definition: one single-event Context per message-tools
36
+ * edit replacement event — the replacement's content IS the edited text, so
37
+ * the row renders as a normal user bubble (with an「已编辑」badge) exactly
38
+ * where the span was discarded.
39
+ */
40
+ export const editedMessageDefinition = {
41
+ kind: 'message-tools-edited',
42
+ target: 'chat',
43
+ match: event => isMessageToolsEdit(event)
44
+ ? { id: String(event.seq), role: 'start' }
45
+ : null,
46
+ start: (_context, match) => {
47
+ const event = match.event;
48
+ if (!isMessageToolsEdit(event)) {
49
+ throw new Error('message-tools-edited start requires a message-tools edit replacement event');
50
+ }
51
+ return {
52
+ seq: event.seq,
53
+ time: event.time,
54
+ content: event.data.content,
55
+ hiddenStartSeq: event.surfaceOp.start,
56
+ };
57
+ },
58
+ update: context => context.state,
59
+ buildViewNode: context => context.state === undefined
60
+ ? null
61
+ : {
62
+ key: context.key,
63
+ kind: 'message-tools-edited',
64
+ id: context.id,
65
+ target: 'chat',
66
+ anchorSeq: context.state.seq,
67
+ location: context.start?.location ?? { kind: 'unresolved' },
68
+ visibility: 'visible',
69
+ data: context.state,
70
+ },
71
+ };
72
+ /**
73
+ * Fold the hidden seq spans out of the live chat nodes, flattened to
74
+ * `[start, endExclusive, ...]` pairs ordered by start. Both withdrawal
75
+ * dividers and edit bubbles carry `{ seq, hiddenStartSeq }` spans. The flat
76
+ * number array keeps the useSession selector stable under shallowEqual until
77
+ * a withdrawal or edit lands.
78
+ * @param nodes - every materialized chat node (any order).
79
+ * @returns the flattened hidden spans.
80
+ */
81
+ export function foldHiddenRanges(nodes) {
82
+ const pairs = [];
83
+ for (const node of nodes) {
84
+ if (node.kind !== 'message-tools-withdrawn' && node.kind !== 'message-tools-edited')
85
+ continue;
86
+ const data = node.data;
87
+ pairs.push([data.hiddenStartSeq, data.seq]);
88
+ }
89
+ pairs.sort((left, right) => left[0] - right[0]);
90
+ return pairs.flat();
91
+ }
92
+ /** Join text blocks into one plain string. */
93
+ function joinText(blocks) {
94
+ return blocks
95
+ .filter((block) => block.type === 'text'
96
+ && typeof block.text === 'string')
97
+ .map(block => block.text)
98
+ .join('');
99
+ }
100
+ /** Join AssistantBlock text (kind-keyed, unlike message content's type-keyed blocks). */
101
+ function joinAssistantText(blocks) {
102
+ const text = blocks
103
+ .filter((block) => block.kind === 'text'
104
+ && typeof block.text === 'string')
105
+ .map(block => block.text)
106
+ .join('');
107
+ if (text !== '')
108
+ return text;
109
+ // Interrupted assistant steps may contain only reasoning; preserve it in
110
+ // the divider expand instead of showing an empty span.
111
+ return blocks
112
+ .filter((block) => block.kind === 'reasoning'
113
+ && typeof block.text === 'string')
114
+ .map(block => block.text)
115
+ .join('');
116
+ }
117
+ /**
118
+ * The restore-row Definition: one single-event Context per message-tools
119
+ * user-message restore event (append-surface, plugin-tagged), replaying the
120
+ * withdrawn message's content at the tail.
121
+ */
122
+ export const restoredMessageDefinition = {
123
+ kind: 'message-tools-restored',
124
+ target: 'chat',
125
+ match: event => isMessageToolsRestore(event)
126
+ ? { id: String(event.seq), role: 'start' }
127
+ : null,
128
+ start: (_context, match) => {
129
+ const event = match.event;
130
+ if (!isMessageToolsRestore(event)) {
131
+ throw new Error('message-tools-restored start requires a message-tools restore event');
132
+ }
133
+ return {
134
+ seq: event.seq,
135
+ time: event.time,
136
+ restoredFromSeq: event.sourceEventSeqs?.[0],
137
+ content: event.data.content,
138
+ text: joinText(event.data.content),
139
+ };
140
+ },
141
+ update: context => context.state,
142
+ buildViewNode: context => context.state === undefined
143
+ ? null
144
+ : {
145
+ key: context.key,
146
+ kind: 'message-tools-restored',
147
+ id: context.id,
148
+ target: 'chat',
149
+ anchorSeq: context.state.seq,
150
+ location: context.start?.location ?? { kind: 'unresolved' },
151
+ visibility: 'visible',
152
+ data: context.state,
153
+ },
154
+ };
155
+ /**
156
+ * The restore-assistant-line Definition: one single-event Context per
157
+ * message-tools assistant-text restore replay (append-surface, op
158
+ * 'restore-assistant'), rendering the reply text at the tail. The
159
+ * model-facing frame is stripped for display — it belongs to the model
160
+ * context, not the transcript row.
161
+ */
162
+ export const restoredAssistantMessageDefinition = {
163
+ kind: 'message-tools-restored-assistant',
164
+ target: 'chat',
165
+ match: event => isMessageToolsRestoreAssistant(event)
166
+ ? { id: String(event.seq), role: 'start' }
167
+ : null,
168
+ start: (_context, match) => {
169
+ const event = match.event;
170
+ if (!isMessageToolsRestoreAssistant(event)) {
171
+ throw new Error('message-tools-restored-assistant start requires a restore-assistant replay event');
172
+ }
173
+ return {
174
+ seq: event.seq,
175
+ time: event.time,
176
+ restoredFromSeq: event.sourceEventSeqs?.[0],
177
+ text: stripRestoreAssistantFrame(joinText(event.data.content)),
178
+ };
179
+ },
180
+ update: context => context.state,
181
+ buildViewNode: context => context.state === undefined
182
+ ? null
183
+ : {
184
+ key: context.key,
185
+ kind: 'message-tools-restored-assistant',
186
+ id: context.id,
187
+ target: 'chat',
188
+ anchorSeq: context.state.seq,
189
+ location: context.start?.location ?? { kind: 'unresolved' },
190
+ visibility: 'visible',
191
+ data: context.state,
192
+ },
193
+ };
194
+ /**
195
+ * Count the user-visible messages hidden by one withdrawn span. This mirrors
196
+ * {@link collectWithdrawnEntries}: context injections, tool calls, turn tails,
197
+ * and other UI chrome are not counted as “messages”, while restored/edited
198
+ * rows and reasoning-only assistant steps are.
199
+ * @param nodes - every materialized chat node.
200
+ * @param startSeq - first hidden seq (inclusive).
201
+ * @param endSeq - the divider's seq (exclusive).
202
+ * @returns the hidden message count.
203
+ */
204
+ export function countHiddenInSpan(nodes, startSeq, endSeq) {
205
+ return collectWithdrawnEntries(nodes, startSeq, endSeq).length;
206
+ }
207
+ /**
208
+ * Collect the read-only replay of one withdrawn span: user originals and
209
+ * assistant text, in anchor order. Other kinds carry no user-readable text
210
+ * and are skipped. Assistant-step data is kind-keyed (`data.blocks`, with
211
+ * `data.finalNode.blocks` once settled — `conversation-nodes/assistant.ts`),
212
+ * not type-keyed like message content. Reads the live node store — call from
213
+ * event handlers.
214
+ * @param nodes - every materialized chat node.
215
+ * @param startSeq - first hidden seq (inclusive).
216
+ * @param endSeq - the divider's seq (exclusive).
217
+ * @returns the replay entries.
218
+ */
219
+ export function collectWithdrawnEntries(nodes, startSeq, endSeq) {
220
+ const inSpan = nodes
221
+ .filter(node => node.anchorSeq >= startSeq && node.anchorSeq < endSeq)
222
+ .sort((left, right) => left.anchorSeq - right.anchorSeq);
223
+ const entries = [];
224
+ for (const node of inSpan) {
225
+ if (node.kind === 'user' || node.kind === 'steering' || node.kind === 'message-tools-edited') {
226
+ const content = node.data.content ?? [];
227
+ const text = joinText(content);
228
+ if (text !== '')
229
+ entries.push({ kind: 'user', text });
230
+ continue;
231
+ }
232
+ if (node.kind === 'message-tools-restored') {
233
+ const text = node.data.text ?? '';
234
+ if (text !== '')
235
+ entries.push({ kind: 'user', text });
236
+ continue;
237
+ }
238
+ if (node.kind === 'message-tools-restored-assistant') {
239
+ const text = node.data.text ?? '';
240
+ if (text !== '')
241
+ entries.push({ kind: 'assistant', text });
242
+ continue;
243
+ }
244
+ if (node.kind === 'assistant-step') {
245
+ const data = node.data;
246
+ const text = joinAssistantText(data.finalNode?.blocks ?? data.blocks ?? []);
247
+ if (text !== '')
248
+ entries.push({ kind: 'assistant', text });
249
+ }
250
+ }
251
+ return entries;
252
+ }
253
+ /**
254
+ * Whether one withdrawn span has a LIVE restore row (a restored node citing
255
+ * the span's first hidden seq and not itself inside a hidden span). When the
256
+ * restore rows are withdrawn again, the badge clears and the divider's
257
+ * restore action re-enables — the restore events stay in the log either way.
258
+ * @param nodes - every materialized chat node.
259
+ * @param hiddenStartSeq - the span's first hidden seq.
260
+ * @param ranges - the flattened hidden spans from foldHiddenRanges.
261
+ * @returns true when a live restore row for the span exists.
262
+ */
263
+ export function hasRestoreForSpan(nodes, hiddenStartSeq, ranges) {
264
+ return nodes.some(node => node.kind === 'message-tools-restored'
265
+ && node.data.restoredFromSeq === hiddenStartSeq
266
+ && !isSeqHidden(ranges, node.anchorSeq));
267
+ }
268
+ /**
269
+ * Whether a span's restore rows have themselves been withdrawn by a later
270
+ * span. The earlier divider remains as history, but the later divider is the
271
+ * active restore point; offering restore on both would duplicate the same
272
+ * logical content when the user toggles withdraw → restore → withdraw.
273
+ * @param nodes - every materialized chat node.
274
+ * @param hiddenStartSeq - the span's first hidden seq.
275
+ * @param ranges - the flattened hidden spans from foldHiddenRanges.
276
+ * @returns true when this span's restore rows are hidden inside a later span.
277
+ */
278
+ export function isRestoreSuperseded(nodes, hiddenStartSeq, ranges) {
279
+ return nodes.some(node => (node.kind === 'message-tools-restored' || node.kind === 'message-tools-restored-assistant')
280
+ && node.data.restoredFromSeq === hiddenStartSeq
281
+ && isSeqHidden(ranges, node.anchorSeq));
282
+ }
283
+ /**
284
+ * Whether a node seq falls inside a hidden span.
285
+ * @param ranges - the flattened spans from foldHiddenRanges.
286
+ * @param seq - the node seq to test.
287
+ * @returns true when the seq is hidden.
288
+ */
289
+ export function isSeqHidden(ranges, seq) {
290
+ for (let index = 0; index + 1 < ranges.length; index += 2) {
291
+ const start = ranges[index];
292
+ const end = ranges[index + 1];
293
+ if (start !== undefined && end !== undefined && seq >= start && seq < end)
294
+ return true;
295
+ }
296
+ return false;
297
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * message-tools host half: the `messageTools` Typert Remote service. Its
3
+ * withdraw/edit methods land surface replacements — the compaction mechanism —
4
+ * so the target message and everything after it leave the model-visible
5
+ * surface (edit replacements carry the edited text as their content), and its
6
+ * restore method tail-replays a withdrawn span's replayable entries, while
7
+ * the browser half shadows the user-message renderer and projects the
8
+ * withdrawal divider, edited bubble, and restored rows.
9
+ * @module @khorsheed/dsh-client-message-tools
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
13
+ import type { MessageToolsEditRequest, MessageToolsEditResult, MessageToolsRestoreRequest, MessageToolsRestoreResult, MessageToolsWithdrawRequest, MessageToolsWithdrawResult } from './types.ts';
14
+ export type * from './types.ts';
15
+ declare module '@deepseek-ai/cordis' {
16
+ interface Context {
17
+ /** Withdrawal Remote owned by the message-tools plugin. */
18
+ messageTools: MessageToolsService;
19
+ }
20
+ }
21
+ /**
22
+ * messageTools Remote service: appends the withdrawal replacement to the
23
+ * owning Session and flushes it durable. The replacement event's
24
+ * plugin-tagged source is the audit trail; no new session event type is
25
+ * introduced, so session reload stays safe for harnesses without this plugin.
26
+ */
27
+ export declare class MessageToolsService extends TypertRemoteService {
28
+ static inject: string[];
29
+ /**
30
+ * @param ctx - host context carrying the session store.
31
+ */
32
+ constructor(ctx: Context);
33
+ /**
34
+ * Withdraw a user message: replace it and every surface node after it with
35
+ * a minimal plugin-sourced placeholder, hiding the span from the model.
36
+ * @param request - session identity and target message seq.
37
+ * @returns the replacement receipt, or a rejection from the closed failure union.
38
+ */
39
+ withdraw(request: MessageToolsWithdrawRequest): Promise<MessageToolsWithdrawResult>;
40
+ /**
41
+ * Restore a withdrawn span: replay every replayable entry of it (user
42
+ * messages verbatim — including the last edit's new text — and framed
43
+ * assistant text; tool calls/results never replay) as fresh plugin-tagged
44
+ * `user/message` appends at the conversation tail, in original order, each
45
+ * citing its original event in `sourceEventSeqs`. The surface replace model
46
+ * is positional — a replaced span folds into one node — so the span cannot
47
+ * be restored in place; the replayed entries are simply the newest messages
48
+ * the model sees. Assistant text replays as framed user-role messages:
49
+ * `assistant/message` cannot carry a plugin source and the turn/step trace
50
+ * forbids assistant appends outside a step.
51
+ * @param request - session identity and withdrawn message seq.
52
+ * @returns the restore receipt, or a rejection from the closed failure union.
53
+ */
54
+ restore(request: MessageToolsRestoreRequest): Promise<MessageToolsRestoreResult>;
55
+ /**
56
+ * Edit a user message in place: replace it and every surface node after it
57
+ * with the edited text itself (no placeholder, no tail duplicate — the
58
+ * edited text appears exactly once, in the replacement event), then start a
59
+ * regeneration turn. The harness has no wake-without-append path, so the
60
+ * turn is started by `agent.followup` carrying a minimal plugin-sourced
61
+ * trigger message (the ankh-guard/schedule pattern); the trigger is not the
62
+ * edited text and lands once, as itself.
63
+ * @param request - session identity, target seq, edited text.
64
+ * @returns the replacement receipt and whether the turn was triggered, or a rejection.
65
+ */
66
+ edit(request: MessageToolsEditRequest): Promise<MessageToolsEditResult>;
67
+ }
68
+ export default MessageToolsService;
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,OAAO,EAAU,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAM9E,OAAO,KAAK,EACV,uBAAuB,EAAE,sBAAsB,EAC/C,0BAA0B,EAAE,yBAAyB,EACrD,2BAA2B,EAAE,0BAA0B,EACxD,MAAM,YAAY,CAAA;AAEnB,mBAAmB,YAAY,CAAA;AAE/B,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,2DAA2D;QAC3D,YAAY,EAAE,mBAAmB,CAAA;KAClC;CACF;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,mBAAmB;IAC1D,MAAM,CAAC,MAAM,WAAyB;IAEtC;;OAEG;IACH,YAAY,GAAG,EAAE,OAAO,EAEvB;IAED;;;;;OAKG;IAEG,QAAQ,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAiBxF;IAED;;;;;;;;;;;;;OAaG;IAEG,OAAO,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAwBrF;IAED;;;;;;;;;;OAUG;IAEG,IAAI,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqB5E;CACF;eAEc,mBAAmB"}