@khorsheed/dsh-client-message-tools 0.2.0 → 0.3.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/CHANGELOG.md +21 -0
- package/README.en.md +10 -10
- package/README.i18n.yaml +2 -2
- package/README.md +9 -9
- package/lib/client.js +285 -112
- package/lib/index.js +238 -102
- package/lib/typert.host.js +40 -28
- package/lib/typert.remote-client.js +33 -21
- package/lib/types/client/ModelChip.js +4 -4
- package/lib/types/client/RestoredMessageView.js +2 -2
- package/lib/types/client/UserMessageView.js +11 -7
- package/lib/types/client/WithdrawnDividerView.d.ts +3 -2
- package/lib/types/client/WithdrawnDividerView.js +11 -5
- package/lib/types/client/chat-hook.js +1 -0
- package/lib/types/client/dom-hider.js +11 -4
- package/lib/types/client/icons-local.d.ts +23 -0
- package/lib/types/client/icons-local.js +11 -0
- package/lib/types/client/icons.d.ts +18 -19
- package/lib/types/client/icons.js +5 -10
- package/lib/types/client/index.js +6 -1
- package/lib/types/client/withdrawn-node.d.ts +17 -10
- package/lib/types/client/withdrawn-node.js +40 -22
- package/lib/types/index.d.ts +11 -11
- package/lib/types/index.js +21 -16
- package/lib/types/marker.d.ts +47 -15
- package/lib/types/marker.js +57 -30
- package/lib/types/restore-projection.d.ts +55 -0
- package/lib/types/restore-projection.js +123 -0
- package/lib/types/withdraw.js +67 -75
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +9 -28
- package/lib/client.js.map +0 -1
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.host.tsbuildinfo +0 -1
- package/lib/typert.remote-client.d.ts.map +0 -1
- package/lib/types/client/ModelChip.d.ts.map +0 -1
- package/lib/types/client/RestoredMessageView.d.ts.map +0 -1
- package/lib/types/client/UserMessageView.d.ts.map +0 -1
- package/lib/types/client/WithdrawnDividerView.d.ts.map +0 -1
- package/lib/types/client/backfill.d.ts.map +0 -1
- package/lib/types/client/chat-hook.d.ts.map +0 -1
- package/lib/types/client/dom-hider.d.ts.map +0 -1
- package/lib/types/client/edit-in-place.d.ts.map +0 -1
- package/lib/types/client/icons.d.ts.map +0 -1
- package/lib/types/client/index.d.ts.map +0 -1
- package/lib/types/client/locales.d.ts.map +0 -1
- package/lib/types/client/slots.d.ts.map +0 -1
- package/lib/types/client/withdraw-backfill.d.ts.map +0 -1
- package/lib/types/client/withdrawn-node.d.ts.map +0 -1
- package/lib/types/index.d.ts.map +0 -1
- package/lib/types/invariant.d.ts.map +0 -1
- package/lib/types/marker.d.ts.map +0 -1
- package/lib/types/types.d.ts.map +0 -1
- package/lib/types/withdraw.d.ts.map +0 -1
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* draws its own.
|
|
8
|
-
* @param props - size and className.
|
|
9
|
-
* @returns the icon element.
|
|
10
|
-
*/
|
|
11
|
-
export const IconUndoOutline16 = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M2.2 8.7 A5.8 5.8 0 1 0 5.9 3.3 L2.6 6.6" }), _jsx("path", { d: "M2.6 2.4 V6.6 H6.2", fill: "none" })] }));
|
|
2
|
+
export const IconCheckOutlineMedium = ({ size = 16, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M2.25 8.5L5.49732 11.7473C5.90519 12.1552 6.57263 12.1344 6.95426 11.7018L13.75 4", stroke: "currentColor" }) }));
|
|
3
|
+
export const IconChevronDownOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6", stroke: "currentColor" }) }));
|
|
4
|
+
export const IconChevronRightOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M6 12L9.29289 8.70711C9.68342 8.31658 9.68342 7.68342 9.29289 7.29289L6 4", stroke: "currentColor" }) }));
|
|
5
|
+
export const IconCopyOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("rect", { x: "1.52075", y: "4.07373", width: "10.3932", height: "10.3932", rx: "2", stroke: "currentColor" }), _jsx("path", { d: "M11.9792 1.53296C13.36 1.53296 14.4792 2.65225 14.4792 4.03296V9.42847C14.4792 10.3756 13.9521 11.1987 13.1755 11.6228V10.3298C13.3652 10.0787 13.4792 9.7674 13.4792 9.42847V4.03296C13.4792 3.20453 12.8077 2.53296 11.9792 2.53296H6.58374C6.27966 2.53301 5.99684 2.6235 5.7605 2.77905H4.42358C4.85652 2.03463 5.66056 1.53304 6.58374 1.53296H11.9792Z", fill: "currentColor" })] }));
|
|
6
|
+
export const IconEditOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: [_jsx("path", { d: "M8.85596 2.69971H4.19971C3.37141 2.69971 2.69992 3.37146 2.69971 4.19971V11.8003C2.69992 12.6285 3.37141 13.3003 4.19971 13.3003H11.8003C12.6283 13.2999 13.3001 12.6283 13.3003 11.8003V7.89893H14.3003V11.8003C14.3001 13.1806 13.1806 14.2999 11.8003 14.3003H4.19971C2.81913 14.3003 1.69992 13.1808 1.69971 11.8003V4.19971C1.69992 2.81918 2.81913 1.69971 4.19971 1.69971H8.85596V2.69971Z", fill: "currentColor" }), _jsx("path", { d: "M7.7849 8.23878L13.888 2.13574", stroke: "currentColor" })] }));
|
|
@@ -179,7 +179,12 @@ export async function apply(ctx) {
|
|
|
179
179
|
},
|
|
180
180
|
selectModel: selection => directory === undefined
|
|
181
181
|
? Promise.resolve(false)
|
|
182
|
-
|
|
182
|
+
// Host 0.1.6-alpha.2 turned select() into a never-rejecting
|
|
183
|
+
// RemoteResult; 0.1.5 still rejects on failure. Read the settled
|
|
184
|
+
// shape: a value carrying `ok` is the new contract, anything else is
|
|
185
|
+
// the old void success, and a rejection only happens on the old line
|
|
186
|
+
// (the official consumer reads `!result.ok` the same way).
|
|
187
|
+
: directory.select(selection).then(result => (result && typeof result === 'object' && 'ok' in result ? result.ok : true), () => false),
|
|
183
188
|
hooks: {
|
|
184
189
|
modelDirectory: directory === undefined ? EMPTY_MODEL_DIRECTORY : directory.store,
|
|
185
190
|
},
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* renderers and the DOM hider.
|
|
10
10
|
*/
|
|
11
11
|
import type { ChatConversationViewNode } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
12
|
-
import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
12
|
+
import type { ConversationNodeDefinition, MessageImageSource } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
13
13
|
/** Chat node data of one withdrawal divider row. */
|
|
14
14
|
export interface WithdrawnDividerData {
|
|
15
15
|
/** Seq of the replacement event; the divider anchors here and the hidden span ends (exclusive). */
|
|
@@ -89,7 +89,7 @@ export declare const editedMessageDefinition: ConversationNodeDefinition<EditedM
|
|
|
89
89
|
export declare function foldHiddenRanges(nodes: readonly ChatConversationViewNode[]): number[];
|
|
90
90
|
/**
|
|
91
91
|
* The restore-row Definition: one single-event Context per message-tools
|
|
92
|
-
* user-message restore event (append-surface,
|
|
92
|
+
* user-message restore event (append-surface, producer-tagged), replaying the
|
|
93
93
|
* withdrawn message's content at the tail.
|
|
94
94
|
*/
|
|
95
95
|
export declare const restoredMessageDefinition: ConversationNodeDefinition<RestoredMessageData>;
|
|
@@ -105,7 +105,7 @@ export declare const restoredAssistantMessageDefinition: ConversationNodeDefinit
|
|
|
105
105
|
* Count the user-visible messages hidden by one withdrawn span. This mirrors
|
|
106
106
|
* {@link collectWithdrawnEntries}: context injections, tool calls, turn tails,
|
|
107
107
|
* and other UI chrome are not counted as “messages”, while restored/edited
|
|
108
|
-
* rows
|
|
108
|
+
* rows, reasoning-only assistant steps, and image-only user messages are.
|
|
109
109
|
* @param nodes - every materialized chat node.
|
|
110
110
|
* @param startSeq - first hidden seq (inclusive).
|
|
111
111
|
* @param endSeq - the divider's seq (exclusive).
|
|
@@ -116,16 +116,23 @@ export declare function countHiddenInSpan(nodes: readonly ChatConversationViewNo
|
|
|
116
116
|
export interface WithdrawnEntry {
|
|
117
117
|
/** Source node kind ('user' original or 'assistant-step' summary). */
|
|
118
118
|
readonly kind: 'user' | 'assistant';
|
|
119
|
-
/** The entry's text (full for user originals, a text join for assistant steps). */
|
|
119
|
+
/** The entry's text (full for user originals, a text join for assistant steps; empty for an image-only message). */
|
|
120
120
|
readonly text: string;
|
|
121
|
+
/**
|
|
122
|
+
* The entry's user-attached images, in source order — what the withdrawal
|
|
123
|
+
* hid alongside the text. Always empty for assistant entries: the replay is
|
|
124
|
+
* user originals plus assistant text, and assistant image blocks stay out
|
|
125
|
+
* of that vocabulary (see the module note on the replay).
|
|
126
|
+
*/
|
|
127
|
+
readonly images: readonly MessageImageSource[];
|
|
121
128
|
}
|
|
122
129
|
/**
|
|
123
|
-
* Collect the read-only replay of one withdrawn span: user originals
|
|
124
|
-
* assistant text, in anchor order. Other kinds
|
|
125
|
-
* and are skipped. Assistant-step data is
|
|
126
|
-
* `data.finalNode.blocks` once settled —
|
|
127
|
-
* not type-keyed like message content.
|
|
128
|
-
* event handlers.
|
|
130
|
+
* Collect the read-only replay of one withdrawn span: user originals (text
|
|
131
|
+
* and attached images alike) and assistant text, in anchor order. Other kinds
|
|
132
|
+
* carry no user-readable content and are skipped. Assistant-step data is
|
|
133
|
+
* kind-keyed (`data.blocks`, with `data.finalNode.blocks` once settled —
|
|
134
|
+
* `conversation-nodes/assistant.ts`), not type-keyed like message content.
|
|
135
|
+
* Reads the live node store — call from event handlers.
|
|
129
136
|
* @param nodes - every materialized chat node.
|
|
130
137
|
* @param startSeq - first hidden seq (inclusive).
|
|
131
138
|
* @param endSeq - the divider's seq (exclusive).
|
|
@@ -26,17 +26,17 @@ export const withdrawnDividerDefinition = {
|
|
|
26
26
|
kind: 'message-tools-withdrawn',
|
|
27
27
|
target: 'chat',
|
|
28
28
|
match: (event) => {
|
|
29
|
-
//
|
|
29
|
+
// Attempt and boundary rows share the bus; the guards speak SessionEvent only.
|
|
30
30
|
if (event.type !== 'user/message')
|
|
31
31
|
return null;
|
|
32
32
|
return isMessageToolsReplacement(event) ? { id: String(event.seq), role: 'start' } : null;
|
|
33
33
|
},
|
|
34
34
|
start: (_context, match) => {
|
|
35
35
|
const event = match.event;
|
|
36
|
-
if (!isMessageToolsReplacement(event)) {
|
|
36
|
+
if (event.type !== 'user/message' || !isMessageToolsReplacement(event)) {
|
|
37
37
|
throw new Error('message-tools-withdrawn start requires a message-tools replacement event');
|
|
38
38
|
}
|
|
39
|
-
return { seq: event.seq, hiddenStartSeq: event.surfaceOp.
|
|
39
|
+
return { seq: event.seq, hiddenStartSeq: event.surfaceOp.startSeq };
|
|
40
40
|
},
|
|
41
41
|
update: context => context.state,
|
|
42
42
|
buildViewNode: context => context.state === undefined
|
|
@@ -59,14 +59,14 @@ export const editedMessageDefinition = {
|
|
|
59
59
|
},
|
|
60
60
|
start: (_context, match) => {
|
|
61
61
|
const event = match.event;
|
|
62
|
-
if (!isMessageToolsEdit(event)) {
|
|
62
|
+
if (event.type !== 'user/message' || !isMessageToolsEdit(event)) {
|
|
63
63
|
throw new Error('message-tools-edited start requires a message-tools edit replacement event');
|
|
64
64
|
}
|
|
65
65
|
return {
|
|
66
66
|
seq: event.seq,
|
|
67
67
|
time: event.time,
|
|
68
68
|
content: event.data.content,
|
|
69
|
-
hiddenStartSeq: event.surfaceOp.
|
|
69
|
+
hiddenStartSeq: event.surfaceOp.startSeq,
|
|
70
70
|
};
|
|
71
71
|
},
|
|
72
72
|
update: context => context.state,
|
|
@@ -94,6 +94,21 @@ export function foldHiddenRanges(nodes) {
|
|
|
94
94
|
pairs.sort((left, right) => left[0] - right[0]);
|
|
95
95
|
return pairs.flat();
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Fold one message's content blocks into the gallery slot's image input, in
|
|
99
|
+
* source order. Only `type: 'image'` blocks with an attachment qualify; a
|
|
100
|
+
* withdrawn user message must keep its images visible in the divider replay
|
|
101
|
+
* (they are exactly what the withdrawal hid).
|
|
102
|
+
*/
|
|
103
|
+
function imageSources(blocks) {
|
|
104
|
+
const images = [];
|
|
105
|
+
for (const block of blocks) {
|
|
106
|
+
const b = block;
|
|
107
|
+
if (b.type === 'image' && b.attachment !== undefined)
|
|
108
|
+
images.push({ attachment: b.attachment });
|
|
109
|
+
}
|
|
110
|
+
return images;
|
|
111
|
+
}
|
|
97
112
|
/** Join text blocks into one plain string. */
|
|
98
113
|
function joinText(blocks) {
|
|
99
114
|
return blocks
|
|
@@ -121,7 +136,7 @@ function joinAssistantText(blocks) {
|
|
|
121
136
|
}
|
|
122
137
|
/**
|
|
123
138
|
* The restore-row Definition: one single-event Context per message-tools
|
|
124
|
-
* user-message restore event (append-surface,
|
|
139
|
+
* user-message restore event (append-surface, producer-tagged), replaying the
|
|
125
140
|
* withdrawn message's content at the tail.
|
|
126
141
|
*/
|
|
127
142
|
export const restoredMessageDefinition = {
|
|
@@ -134,7 +149,7 @@ export const restoredMessageDefinition = {
|
|
|
134
149
|
},
|
|
135
150
|
start: (_context, match) => {
|
|
136
151
|
const event = match.event;
|
|
137
|
-
if (!isMessageToolsRestore(event)) {
|
|
152
|
+
if (event.type !== 'user/message' || !isMessageToolsRestore(event)) {
|
|
138
153
|
throw new Error('message-tools-restored start requires a message-tools restore event');
|
|
139
154
|
}
|
|
140
155
|
return {
|
|
@@ -167,7 +182,7 @@ export const restoredAssistantMessageDefinition = {
|
|
|
167
182
|
},
|
|
168
183
|
start: (_context, match) => {
|
|
169
184
|
const event = match.event;
|
|
170
|
-
if (!isMessageToolsRestoreAssistant(event)) {
|
|
185
|
+
if (event.type !== 'user/message' || !isMessageToolsRestoreAssistant(event)) {
|
|
171
186
|
throw new Error('message-tools-restored-assistant start requires a restore-assistant replay event');
|
|
172
187
|
}
|
|
173
188
|
return {
|
|
@@ -186,7 +201,7 @@ export const restoredAssistantMessageDefinition = {
|
|
|
186
201
|
* Count the user-visible messages hidden by one withdrawn span. This mirrors
|
|
187
202
|
* {@link collectWithdrawnEntries}: context injections, tool calls, turn tails,
|
|
188
203
|
* and other UI chrome are not counted as “messages”, while restored/edited
|
|
189
|
-
* rows
|
|
204
|
+
* rows, reasoning-only assistant steps, and image-only user messages are.
|
|
190
205
|
* @param nodes - every materialized chat node.
|
|
191
206
|
* @param startSeq - first hidden seq (inclusive).
|
|
192
207
|
* @param endSeq - the divider's seq (exclusive).
|
|
@@ -196,12 +211,12 @@ export function countHiddenInSpan(nodes, startSeq, endSeq) {
|
|
|
196
211
|
return collectWithdrawnEntries(nodes, startSeq, endSeq).length;
|
|
197
212
|
}
|
|
198
213
|
/**
|
|
199
|
-
* Collect the read-only replay of one withdrawn span: user originals
|
|
200
|
-
* assistant text, in anchor order. Other kinds
|
|
201
|
-
* and are skipped. Assistant-step data is
|
|
202
|
-
* `data.finalNode.blocks` once settled —
|
|
203
|
-
* not type-keyed like message content.
|
|
204
|
-
* event handlers.
|
|
214
|
+
* Collect the read-only replay of one withdrawn span: user originals (text
|
|
215
|
+
* and attached images alike) and assistant text, in anchor order. Other kinds
|
|
216
|
+
* carry no user-readable content and are skipped. Assistant-step data is
|
|
217
|
+
* kind-keyed (`data.blocks`, with `data.finalNode.blocks` once settled —
|
|
218
|
+
* `conversation-nodes/assistant.ts`), not type-keyed like message content.
|
|
219
|
+
* Reads the live node store — call from event handlers.
|
|
205
220
|
* @param nodes - every materialized chat node.
|
|
206
221
|
* @param startSeq - first hidden seq (inclusive).
|
|
207
222
|
* @param endSeq - the divider's seq (exclusive).
|
|
@@ -216,27 +231,30 @@ export function collectWithdrawnEntries(nodes, startSeq, endSeq) {
|
|
|
216
231
|
if (node.kind === 'user' || node.kind === 'steering' || node.kind === 'message-tools-edited') {
|
|
217
232
|
const content = node.data.content ?? [];
|
|
218
233
|
const text = joinText(content);
|
|
219
|
-
|
|
220
|
-
|
|
234
|
+
const images = imageSources(content);
|
|
235
|
+
if (text !== '' || images.length > 0)
|
|
236
|
+
entries.push({ kind: 'user', text, images });
|
|
221
237
|
continue;
|
|
222
238
|
}
|
|
223
239
|
if (node.kind === 'message-tools-restored') {
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
240
|
+
const data = node.data;
|
|
241
|
+
const text = data.text ?? '';
|
|
242
|
+
const images = imageSources(data.content ?? []);
|
|
243
|
+
if (text !== '' || images.length > 0)
|
|
244
|
+
entries.push({ kind: 'user', text, images });
|
|
227
245
|
continue;
|
|
228
246
|
}
|
|
229
247
|
if (node.kind === 'message-tools-restored-assistant') {
|
|
230
248
|
const text = node.data.text ?? '';
|
|
231
249
|
if (text !== '')
|
|
232
|
-
entries.push({ kind: 'assistant', text });
|
|
250
|
+
entries.push({ kind: 'assistant', text, images: [] });
|
|
233
251
|
continue;
|
|
234
252
|
}
|
|
235
253
|
if (node.kind === 'assistant-step') {
|
|
236
254
|
const data = node.data;
|
|
237
255
|
const text = joinAssistantText(data.finalNode?.blocks ?? data.blocks ?? []);
|
|
238
256
|
if (text !== '')
|
|
239
|
-
entries.push({ kind: 'assistant', text });
|
|
257
|
+
entries.push({ kind: 'assistant', text, images: [] });
|
|
240
258
|
}
|
|
241
259
|
}
|
|
242
260
|
return entries;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ declare module '@deepseek-ai/cordis' {
|
|
|
21
21
|
/**
|
|
22
22
|
* messageTools Remote service: appends the withdrawal replacement to the
|
|
23
23
|
* owning Session and flushes it durable. The replacement event's
|
|
24
|
-
*
|
|
24
|
+
* producer-owned source is the audit trail; no new session event type is
|
|
25
25
|
* introduced, so session reload stays safe for harnesses without this plugin.
|
|
26
26
|
*/
|
|
27
27
|
export declare class MessageToolsService extends TypertRemoteService {
|
|
@@ -32,7 +32,7 @@ export declare class MessageToolsService extends TypertRemoteService {
|
|
|
32
32
|
constructor(ctx: Context);
|
|
33
33
|
/**
|
|
34
34
|
* Withdraw a user message: replace it and every surface node after it with
|
|
35
|
-
* a minimal
|
|
35
|
+
* a minimal producer-sourced placeholder, hiding the span from the model.
|
|
36
36
|
* @param request - session identity and target message seq.
|
|
37
37
|
* @returns the replacement receipt, or a rejection from the closed failure union.
|
|
38
38
|
*/
|
|
@@ -40,14 +40,14 @@ export declare class MessageToolsService extends TypertRemoteService {
|
|
|
40
40
|
/**
|
|
41
41
|
* Restore a withdrawn span: replay every replayable entry of it (user
|
|
42
42
|
* messages verbatim — including the last edit's new text — and framed
|
|
43
|
-
* assistant text; tool calls/results never replay) as fresh
|
|
44
|
-
* `user/message` appends at the conversation tail, in
|
|
45
|
-
* citing its original event in `sourceEventSeqs`. The
|
|
46
|
-
* is positional — a replaced span folds into one
|
|
47
|
-
* be restored in place; the replayed entries are
|
|
48
|
-
* the model sees. Assistant text replays as
|
|
49
|
-
* `assistant/message`
|
|
50
|
-
* forbids assistant appends outside a step.
|
|
43
|
+
* assistant text; tool calls/results never replay) as fresh
|
|
44
|
+
* producer-sourced `user/message` appends at the conversation tail, in
|
|
45
|
+
* original order, each citing its original event in `sourceEventSeqs`. The
|
|
46
|
+
* surface replace model is positional — a replaced span folds into one
|
|
47
|
+
* node — so the span cannot be restored in place; the replayed entries are
|
|
48
|
+
* simply the newest messages the model sees. Assistant text replays as
|
|
49
|
+
* framed user-role messages: `assistant/message` requires the model source
|
|
50
|
+
* and the turn/step trace forbids assistant appends outside a step.
|
|
51
51
|
* @param request - session identity and withdrawn message seq.
|
|
52
52
|
* @returns the restore receipt, or a rejection from the closed failure union.
|
|
53
53
|
*/
|
|
@@ -57,7 +57,7 @@ export declare class MessageToolsService extends TypertRemoteService {
|
|
|
57
57
|
* with the edited text itself (no placeholder, no tail duplicate — the
|
|
58
58
|
* edited text appears exactly once, in the replacement event), then start a
|
|
59
59
|
* regeneration turn. The harness has no wake-without-append path, so the
|
|
60
|
-
* turn is started by `agent.followup` carrying a minimal
|
|
60
|
+
* turn is started by `agent.followup` carrying a minimal producer-sourced
|
|
61
61
|
* trigger message (the ankh-guard/schedule pattern); the trigger is not the
|
|
62
62
|
* edited text and lands once, as itself.
|
|
63
63
|
* @param request - session identity, target seq, edited text.
|
package/lib/types/index.js
CHANGED
|
@@ -36,12 +36,13 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
36
36
|
import { SessionSeq } from '@deepseek-ai/dsh-session';
|
|
37
37
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
38
38
|
import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
39
|
-
import { EDIT_TRIGGER_NOTICE,
|
|
39
|
+
import { EDIT_TRIGGER_NOTICE, WITHDRAWN_NOTICE, editReplacementSource, editTriggerSource, messageToolsSource, restoreAssistantSource, } from './marker.js';
|
|
40
|
+
import { registerRestoreProjection } from './restore-projection.js';
|
|
40
41
|
import { planEdit, planRestore, planWithdrawal } from './withdraw.js';
|
|
41
42
|
/**
|
|
42
43
|
* messageTools Remote service: appends the withdrawal replacement to the
|
|
43
44
|
* owning Session and flushes it durable. The replacement event's
|
|
44
|
-
*
|
|
45
|
+
* producer-owned source is the audit trail; no new session event type is
|
|
45
46
|
* introduced, so session reload stays safe for harnesses without this plugin.
|
|
46
47
|
*/
|
|
47
48
|
let MessageToolsService = (() => {
|
|
@@ -68,10 +69,14 @@ let MessageToolsService = (() => {
|
|
|
68
69
|
constructor(ctx) {
|
|
69
70
|
super(ctx, 'messageTools');
|
|
70
71
|
__runInitializers(this, _instanceExtraInitializers);
|
|
72
|
+
// Seam registry S12: on a host whose session fold can carry a user/message
|
|
73
|
+
// projection, restore replays derive assistant-role without the frame;
|
|
74
|
+
// every degrade path leaves the framed user-role channel verbatim.
|
|
75
|
+
registerRestoreProjection(ctx);
|
|
71
76
|
}
|
|
72
77
|
/**
|
|
73
78
|
* Withdraw a user message: replace it and every surface node after it with
|
|
74
|
-
* a minimal
|
|
79
|
+
* a minimal producer-sourced placeholder, hiding the span from the model.
|
|
75
80
|
* @param request - session identity and target message seq.
|
|
76
81
|
* @returns the replacement receipt, or a rejection from the closed failure union.
|
|
77
82
|
*/
|
|
@@ -84,9 +89,9 @@ let MessageToolsService = (() => {
|
|
|
84
89
|
return { ok: false, error: { code: planned.code } };
|
|
85
90
|
const replacement = session.append('user/message', createUserMessage({
|
|
86
91
|
content: [{ type: 'text', text: WITHDRAWN_NOTICE }],
|
|
87
|
-
source:
|
|
92
|
+
source: messageToolsSource(),
|
|
88
93
|
}), {
|
|
89
|
-
surfaceOp: { op: 'replace',
|
|
94
|
+
surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
|
|
90
95
|
sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
|
|
91
96
|
});
|
|
92
97
|
await this.ctx.sessions.flush(session);
|
|
@@ -98,14 +103,14 @@ let MessageToolsService = (() => {
|
|
|
98
103
|
/**
|
|
99
104
|
* Restore a withdrawn span: replay every replayable entry of it (user
|
|
100
105
|
* messages verbatim — including the last edit's new text — and framed
|
|
101
|
-
* assistant text; tool calls/results never replay) as fresh
|
|
102
|
-
* `user/message` appends at the conversation tail, in
|
|
103
|
-
* citing its original event in `sourceEventSeqs`. The
|
|
104
|
-
* is positional — a replaced span folds into one
|
|
105
|
-
* be restored in place; the replayed entries are
|
|
106
|
-
* the model sees. Assistant text replays as
|
|
107
|
-
* `assistant/message`
|
|
108
|
-
* forbids assistant appends outside a step.
|
|
106
|
+
* assistant text; tool calls/results never replay) as fresh
|
|
107
|
+
* producer-sourced `user/message` appends at the conversation tail, in
|
|
108
|
+
* original order, each citing its original event in `sourceEventSeqs`. The
|
|
109
|
+
* surface replace model is positional — a replaced span folds into one
|
|
110
|
+
* node — so the span cannot be restored in place; the replayed entries are
|
|
111
|
+
* simply the newest messages the model sees. Assistant text replays as
|
|
112
|
+
* framed user-role messages: `assistant/message` requires the model source
|
|
113
|
+
* and the turn/step trace forbids assistant appends outside a step.
|
|
109
114
|
* @param request - session identity and withdrawn message seq.
|
|
110
115
|
* @returns the restore receipt, or a rejection from the closed failure union.
|
|
111
116
|
*/
|
|
@@ -121,7 +126,7 @@ let MessageToolsService = (() => {
|
|
|
121
126
|
const message = entry.role === 'user'
|
|
122
127
|
? createUserMessage({
|
|
123
128
|
content: [...entry.content],
|
|
124
|
-
source:
|
|
129
|
+
source: messageToolsSource(),
|
|
125
130
|
})
|
|
126
131
|
: createUserMessage({
|
|
127
132
|
content: [{ type: 'text', text: entry.text }],
|
|
@@ -141,7 +146,7 @@ let MessageToolsService = (() => {
|
|
|
141
146
|
* with the edited text itself (no placeholder, no tail duplicate — the
|
|
142
147
|
* edited text appears exactly once, in the replacement event), then start a
|
|
143
148
|
* regeneration turn. The harness has no wake-without-append path, so the
|
|
144
|
-
* turn is started by `agent.followup` carrying a minimal
|
|
149
|
+
* turn is started by `agent.followup` carrying a minimal producer-sourced
|
|
145
150
|
* trigger message (the ankh-guard/schedule pattern); the trigger is not the
|
|
146
151
|
* edited text and lands once, as itself.
|
|
147
152
|
* @param request - session identity, target seq, edited text.
|
|
@@ -158,7 +163,7 @@ let MessageToolsService = (() => {
|
|
|
158
163
|
content: [{ type: 'text', text: request.text }],
|
|
159
164
|
source: editReplacementSource(),
|
|
160
165
|
}), {
|
|
161
|
-
surfaceOp: { op: 'replace',
|
|
166
|
+
surfaceOp: { op: 'replace', startSeq: SessionSeq(planned.plan.start), endSeq: SessionSeq(planned.plan.end) },
|
|
162
167
|
sourceEventSeqs: planned.plan.sourceEventSeqs.map(seq => SessionSeq(seq)),
|
|
163
168
|
});
|
|
164
169
|
await this.ctx.sessions.flush(session);
|
package/lib/types/marker.d.ts
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import type { MessageSource } from '@deepseek-ai/dsh-llm/message';
|
|
1
|
+
import type { ContextFormed, MessageSource } from '@deepseek-ai/dsh-llm/message';
|
|
2
2
|
import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The producer-owned source kind identifying every message-tools event. It is
|
|
5
|
+
* also the legacy wrapper's `plugin` value: released V3 logs carry
|
|
6
|
+
* `{ kind: 'plugin', plugin: 'message-tools' }` (a pre-V4 host still serves
|
|
7
|
+
* them verbatim), and the official V3→V4 migration rewrites those to
|
|
8
|
+
* `kind: 'plugin:message-tools'` — {@link isMessageToolsSource} accepts all
|
|
9
|
+
* three durable forms.
|
|
10
|
+
*/
|
|
4
11
|
export declare const MESSAGE_TOOLS_PLUGIN = "message-tools";
|
|
12
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
13
|
+
interface MessageSourceMap {
|
|
14
|
+
/** message-tools producer: withdrawal/edit replacements, edit triggers, restore replays. */
|
|
15
|
+
'message-tools': {
|
|
16
|
+
kind: 'message-tools';
|
|
17
|
+
readonly op?: MessageToolsOp;
|
|
18
|
+
} & ContextFormed;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
5
21
|
/**
|
|
6
22
|
* Model-facing placeholder left on the surface in place of the withdrawn
|
|
7
23
|
* span. The span itself is hidden from the model; this minimal user message
|
|
@@ -17,21 +33,36 @@ export declare const WITHDRAWN_NOTICE = "(\u7528\u6237\u64A4\u56DE\u4E86\u8FD9\u
|
|
|
17
33
|
export declare const EDIT_TRIGGER_NOTICE = "(\u7528\u6237\u7F16\u8F91\u4E86\u4E0A\u4E00\u6761\u6D88\u606F\uFF0C\u8BF7\u6309\u7F16\u8F91\u540E\u7684\u5185\u5BB9\u91CD\u65B0\u56DE\u7B54)";
|
|
18
34
|
/**
|
|
19
35
|
* Model-facing frame preceding a replayed assistant text in a restore: the
|
|
20
|
-
* replay lands as a user-role message (assistant/message
|
|
21
|
-
*
|
|
22
|
-
* appends), so the frame keeps the role honest for the model.
|
|
36
|
+
* replay lands as a user-role message (assistant/message requires the model
|
|
37
|
+
* source, and the session turn/step trace forbids ad-hoc assistant
|
|
38
|
+
* appends), so the frame keeps the role honest for the model. On hosts whose
|
|
39
|
+
* session fold carries the S12 `user/message` projection the frame never
|
|
40
|
+
* reaches the model — the projection strips it and corrects the role at
|
|
41
|
+
* derivation time; everywhere else it is the role-honesty device.
|
|
23
42
|
*/
|
|
24
43
|
export declare const RESTORED_ASSISTANT_NOTICE = "(\u4EE5\u4E0B\u662F\u5148\u524D\u88AB\u64A4\u56DE\u3001\u73B0\u968F\u6062\u590D\u653E\u56DE\u7684\u52A9\u624B\u56DE\u590D)";
|
|
25
|
-
/** The `op` discriminator on a message-tools
|
|
44
|
+
/** The `op` discriminator on a message-tools source. */
|
|
26
45
|
export type MessageToolsOp = 'edit' | 'edit-trigger' | 'restore-assistant';
|
|
27
|
-
/** The
|
|
46
|
+
/** The producer-owned source of a withdrawal replacement or a user-message restore replay (no op). */
|
|
47
|
+
export declare function messageToolsSource(): MessageSource;
|
|
48
|
+
/** The producer-owned source of an edit replacement (op: 'edit'). */
|
|
28
49
|
export declare function editReplacementSource(): MessageSource;
|
|
29
|
-
/** The
|
|
50
|
+
/** The producer-owned source of an edit trigger message (op: 'edit-trigger'). */
|
|
30
51
|
export declare function editTriggerSource(): MessageSource;
|
|
31
|
-
/** The
|
|
52
|
+
/** The producer-owned source of a restored assistant-text replay (op: 'restore-assistant'). */
|
|
32
53
|
export declare function restoreAssistantSource(): MessageSource;
|
|
33
|
-
/** Read the op discriminator off a message source (absent on
|
|
54
|
+
/** Read the op discriminator off a message source (absent on plain withdrawal/restore events). */
|
|
34
55
|
export declare function messageToolsOp(source: unknown): MessageToolsOp | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a durable source belongs to message-tools, in every form the log
|
|
58
|
+
* can hold: the current producer-owned kind (`message-tools`), the V3→V4
|
|
59
|
+
* migrated kind (`plugin:message-tools` — released V3 files rewritten once by
|
|
60
|
+
* the official migration), and the released V3 wrapper a pre-V4 host still
|
|
61
|
+
* serves verbatim (`{ kind: 'plugin', plugin: 'message-tools' }`).
|
|
62
|
+
* @param source - the persisted message source to test.
|
|
63
|
+
* @returns true when the source names this producer.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isMessageToolsSource(source: unknown): boolean;
|
|
35
66
|
/**
|
|
36
67
|
* Strip the model-facing restore frame from a replayed assistant text for
|
|
37
68
|
* DISPLAY: the frame belongs to the model context (role honesty), never to
|
|
@@ -44,8 +75,8 @@ export declare function stripRestoreAssistantFrame(text: string): string;
|
|
|
44
75
|
export type MessageToolsReplacementEvent = SessionEvent<'user/message'> & {
|
|
45
76
|
readonly surfaceOp: {
|
|
46
77
|
readonly op: 'replace';
|
|
47
|
-
readonly
|
|
48
|
-
readonly
|
|
78
|
+
readonly startSeq: number;
|
|
79
|
+
readonly endSeq: number;
|
|
49
80
|
};
|
|
50
81
|
};
|
|
51
82
|
/** One message-tools edit replacement event, narrowed (same shape, `op: 'edit'` source). */
|
|
@@ -56,8 +87,9 @@ export type MessageToolsRestoreEvent = SessionEvent<'user/message'> & {
|
|
|
56
87
|
};
|
|
57
88
|
/**
|
|
58
89
|
* Whether the event is a message-tools WITHDRAWAL replacement: a
|
|
59
|
-
* `user/message` surface replacement whose source
|
|
60
|
-
*
|
|
90
|
+
* `user/message` surface replacement whose source names this producer
|
|
91
|
+
* (any durable form — see {@link isMessageToolsSource}) with no `op`
|
|
92
|
+
* discriminator.
|
|
61
93
|
* @param event - the session event to test.
|
|
62
94
|
* @returns the event narrowed to a withdrawal replacement when matched.
|
|
63
95
|
*/
|
|
@@ -78,7 +110,7 @@ export declare function isMessageToolsEdit(event: SessionEvent): event is Messag
|
|
|
78
110
|
export declare function isMessageToolsRestore(event: SessionEvent): event is MessageToolsRestoreEvent;
|
|
79
111
|
/**
|
|
80
112
|
* Whether the event is a message-tools edit trigger: the minimal
|
|
81
|
-
*
|
|
113
|
+
* producer-sourced message that starts the regeneration turn after an edit.
|
|
82
114
|
* @param event - the session event to test.
|
|
83
115
|
* @returns the event narrowed to an edit trigger when matched.
|
|
84
116
|
*/
|