@khorsheed/dsh-client-message-tools 0.1.0 → 0.3.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.
- package/CHANGELOG.md +16 -0
- package/README.en.md +16 -12
- package/README.i18n.yaml +2 -2
- package/README.md +35 -31
- package/lib/client.js +295 -135
- package/lib/index.js +63 -67
- package/lib/typert.host.js +3 -3
- package/lib/typert.remote-client.js +3 -3
- package/lib/types/client/RestoredMessageView.d.ts +1 -1
- package/lib/types/client/RestoredMessageView.js +20 -1
- package/lib/types/client/UserMessageView.d.ts +1 -1
- package/lib/types/client/UserMessageView.js +16 -10
- package/lib/types/client/WithdrawnDividerView.d.ts +1 -1
- package/lib/types/client/WithdrawnDividerView.js +13 -9
- package/lib/types/client/chat-hook.d.ts +36 -0
- package/lib/types/client/chat-hook.js +51 -0
- package/lib/types/client/dom-hider.d.ts +3 -2
- package/lib/types/client/dom-hider.js +20 -13
- package/lib/types/client/edit-in-place.d.ts +2 -1
- package/lib/types/client/index.d.ts +6 -4
- package/lib/types/client/index.js +42 -8
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.js +8 -0
- package/lib/types/client/withdrawn-node.d.ts +3 -2
- package/lib/types/client/withdrawn-node.js +45 -54
- package/lib/types/index.js +10 -8
- package/lib/types/marker.d.ts +2 -2
- package/lib/types/withdraw.js +76 -80
- package/package.json +19 -18
- 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/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
package/lib/types/withdraw.js
CHANGED
|
@@ -111,21 +111,25 @@ function joinAssistantMessageText(blocks) {
|
|
|
111
111
|
* (the message left the surface through another producer, e.g. compaction).
|
|
112
112
|
*
|
|
113
113
|
* `sourceEventSeqs` is authoritative for the shadowed *surface* nodes, but an
|
|
114
|
-
* interrupted assistant step may exist only as `assistant/
|
|
115
|
-
* `assistant/message` ever landed).
|
|
116
|
-
* they are absent from `sourceEventSeqs`. The replacement's own `
|
|
117
|
-
* `seq` delimit the whole withdrawn log interval — `[
|
|
114
|
+
* interrupted assistant step may exist only as `assistant/attempt` events (no
|
|
115
|
+
* `assistant/message` ever landed). Attempts are not surface nodes, so
|
|
116
|
+
* they are absent from `sourceEventSeqs`. The replacement's own `startSeq` and
|
|
117
|
+
* `seq` delimit the whole withdrawn log interval — `[startSeq, seq)` — and are
|
|
118
118
|
* still read from the replacement itself, never guessed from another producer.
|
|
119
119
|
*/
|
|
120
120
|
function findWithdrawnSpan(events, targetSeq) {
|
|
121
121
|
for (let index = events.length - 1; index > targetSeq; index--) {
|
|
122
122
|
const event = events[index];
|
|
123
|
-
if (event
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
if (event === undefined || !isMessageToolsReplacement(event))
|
|
124
|
+
continue;
|
|
125
|
+
// The brands are compile-time only; the fold addresses the log by plain number.
|
|
126
|
+
const sources = event.sourceEventSeqs;
|
|
127
|
+
if (sources?.includes(targetSeq) !== true)
|
|
128
|
+
continue;
|
|
129
|
+
const span = [];
|
|
130
|
+
for (let seq = event.surfaceOp.startSeq; seq < event.seq; seq++)
|
|
131
|
+
span.push(seq);
|
|
132
|
+
return span;
|
|
129
133
|
}
|
|
130
134
|
return undefined;
|
|
131
135
|
}
|
|
@@ -136,64 +140,27 @@ function findWithdrawnSpan(events, targetSeq) {
|
|
|
136
140
|
* restore-assistant replays already carry the frame). Skipped: withdrawal
|
|
137
141
|
* placeholders, edit triggers, other plugin context, and tool calls/results.
|
|
138
142
|
*
|
|
139
|
-
* Assistant text normally comes from `assistant/message` surface events
|
|
140
|
-
* turn
|
|
141
|
-
* `
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
143
|
+
* Assistant text normally comes from `assistant/message` surface events (a
|
|
144
|
+
* cancelled turn finalizes its delivered prefix there with `interrupted:
|
|
145
|
+
* true`). An attempt that committed no surface message — failed, retried, or
|
|
146
|
+
* stream-error — settles as an `assistant/attempt` log event carrying its
|
|
147
|
+
* exact compact stream. Each such attempt folds into one assistant replay
|
|
148
|
+
* when no final `assistant/message` exists for that step, so
|
|
149
|
+
* withdrawing/restoring an interrupted reply does not lose the partial
|
|
150
|
+
* assistant content. A retried step's earlier attempts are superseded by the
|
|
151
|
+
* step's final message and do not replay.
|
|
145
152
|
*/
|
|
146
153
|
function replayEntries(events, spanSeqs) {
|
|
147
154
|
const entries = [];
|
|
148
|
-
|
|
155
|
+
// A step's final message supersedes its attempts; collect the finalized
|
|
156
|
+
// steps first so an attempt is skipped regardless of log order.
|
|
149
157
|
const finalizedSteps = new Set();
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const text = [...pending.text.values()].join('');
|
|
155
|
-
const reasoning = [...pending.reasoning.values()].join('');
|
|
156
|
-
const replay = text !== '' ? text : reasoning;
|
|
157
|
-
if (replay !== '') {
|
|
158
|
-
entries.push({
|
|
159
|
-
role: 'assistant',
|
|
160
|
-
text: `${RESTORED_ASSISTANT_NOTICE}\n${replay}`,
|
|
161
|
-
sourceSeq: pending.sourceSeq,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
pending = undefined;
|
|
166
|
-
};
|
|
167
|
-
const pushChunk = (event) => {
|
|
168
|
-
if (finalizedSteps.has(`${event.data.turn}:${event.data.step}`))
|
|
169
|
-
return;
|
|
170
|
-
if (pending === undefined
|
|
171
|
-
|| pending.turn !== event.data.turn
|
|
172
|
-
|| pending.step !== event.data.step) {
|
|
173
|
-
flushPending();
|
|
174
|
-
pending = {
|
|
175
|
-
turn: event.data.turn,
|
|
176
|
-
step: event.data.step,
|
|
177
|
-
sourceSeq: event.seq,
|
|
178
|
-
text: new Map(),
|
|
179
|
-
reasoning: new Map(),
|
|
180
|
-
finalized: false,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
const chunk = event.data.chunk;
|
|
184
|
-
if (chunk.type === 'text-delta') {
|
|
185
|
-
pending.text.set(chunk.index, (pending.text.get(chunk.index) ?? '') + chunk.text);
|
|
186
|
-
}
|
|
187
|
-
else if (chunk.type === 'block-end' && chunk.block.type === 'text') {
|
|
188
|
-
pending.text.set(chunk.index, chunk.block.text);
|
|
189
|
-
}
|
|
190
|
-
else if (chunk.type === 'reasoning-delta') {
|
|
191
|
-
pending.reasoning.set(chunk.index, (pending.reasoning.get(chunk.index) ?? '') + chunk.text);
|
|
192
|
-
}
|
|
193
|
-
else if (chunk.type === 'block-end' && chunk.block.type === 'reasoning') {
|
|
194
|
-
pending.reasoning.set(chunk.index, chunk.block.text);
|
|
158
|
+
for (const seq of spanSeqs) {
|
|
159
|
+
const event = events[seq];
|
|
160
|
+
if (event?.type === 'assistant/message') {
|
|
161
|
+
finalizedSteps.add(`${event.data.turn}:${event.data.step}`);
|
|
195
162
|
}
|
|
196
|
-
}
|
|
163
|
+
}
|
|
197
164
|
const pushAssistantText = (text, sourceSeq) => {
|
|
198
165
|
if (text !== '') {
|
|
199
166
|
entries.push({ role: 'assistant', text: `${RESTORED_ASSISTANT_NOTICE}\n${text}`, sourceSeq });
|
|
@@ -204,16 +171,16 @@ function replayEntries(events, spanSeqs) {
|
|
|
204
171
|
if (event === undefined)
|
|
205
172
|
continue;
|
|
206
173
|
if (isMessageToolsEdit(event)) {
|
|
207
|
-
flushPending();
|
|
208
174
|
entries.push({ role: 'user', content: event.data.content, sourceSeq: seq });
|
|
209
175
|
continue;
|
|
210
176
|
}
|
|
211
|
-
if (event.type === 'assistant/
|
|
212
|
-
|
|
177
|
+
if (event.type === 'assistant/attempt') {
|
|
178
|
+
if (finalizedSteps.has(`${event.data.turn}:${event.data.step}`))
|
|
179
|
+
continue;
|
|
180
|
+
pushAssistantText(joinAttemptStreamText(event.data.stream), seq);
|
|
213
181
|
continue;
|
|
214
182
|
}
|
|
215
183
|
if (event.type === 'user/message') {
|
|
216
|
-
flushPending();
|
|
217
184
|
if (event.surfaceOp !== 'append')
|
|
218
185
|
continue;
|
|
219
186
|
if (event.data.source.kind === 'user' || isMessageToolsRestore(event)) {
|
|
@@ -227,25 +194,54 @@ function replayEntries(events, spanSeqs) {
|
|
|
227
194
|
continue;
|
|
228
195
|
}
|
|
229
196
|
if (event.type === 'assistant/message') {
|
|
230
|
-
// A finalized step supersedes any pending chunk accumulation for the
|
|
231
|
-
// same step: flush without emitting the partial, then replay the final.
|
|
232
|
-
finalizedSteps.add(`${event.data.turn}:${event.data.step}`);
|
|
233
|
-
if (pending !== undefined
|
|
234
|
-
&& pending.turn === event.data.turn
|
|
235
|
-
&& pending.step === event.data.step) {
|
|
236
|
-
pending.finalized = true;
|
|
237
|
-
}
|
|
238
|
-
flushPending();
|
|
239
197
|
pushAssistantText(joinAssistantMessageText(event.data.message.content), seq);
|
|
240
198
|
continue;
|
|
241
199
|
}
|
|
242
|
-
// Log-only events (boundaries, headers, titles)
|
|
243
|
-
// the pending assistant step is flushed at the next replayable boundary or
|
|
244
|
-
// when a different step starts.
|
|
200
|
+
// Log-only events (boundaries, headers, titles) contribute nothing.
|
|
245
201
|
}
|
|
246
|
-
flushPending();
|
|
247
202
|
return entries;
|
|
248
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Join one attempt's compact stream into its visible text. Text deltas win;
|
|
206
|
+
* reasoning is the fallback so an attempt that streamed only reasoning does
|
|
207
|
+
* not lose the only assistant content the user saw (the same preference
|
|
208
|
+
* {@link joinAssistantMessageText} applies to finalized messages). Packed
|
|
209
|
+
* delta runs append per block index; a raw `block-end` record carries the
|
|
210
|
+
* assembled block and replaces its index's accumulated deltas. Tool-call
|
|
211
|
+
* runs never replay (their side effects are not replayable).
|
|
212
|
+
*/
|
|
213
|
+
function joinAttemptStreamText(stream) {
|
|
214
|
+
const text = new Map();
|
|
215
|
+
const reasoning = new Map();
|
|
216
|
+
const append = (map, index, fragment) => {
|
|
217
|
+
map.set(index, (map.get(index) ?? '') + fragment);
|
|
218
|
+
};
|
|
219
|
+
for (const record of stream) {
|
|
220
|
+
if (record.type === 'text-chunks') {
|
|
221
|
+
append(text, record.index, record.texts.join(''));
|
|
222
|
+
}
|
|
223
|
+
else if (record.type === 'reasoning-chunks') {
|
|
224
|
+
append(reasoning, record.index, record.texts.join(''));
|
|
225
|
+
}
|
|
226
|
+
else if (record.type === 'chunk') {
|
|
227
|
+
const chunk = record.chunk;
|
|
228
|
+
if (chunk.type === 'text-delta') {
|
|
229
|
+
append(text, chunk.index, chunk.text);
|
|
230
|
+
}
|
|
231
|
+
else if (chunk.type === 'reasoning-delta') {
|
|
232
|
+
append(reasoning, chunk.index, chunk.text);
|
|
233
|
+
}
|
|
234
|
+
else if (chunk.type === 'block-end' && chunk.block.type === 'text') {
|
|
235
|
+
text.set(chunk.index, chunk.block.text);
|
|
236
|
+
}
|
|
237
|
+
else if (chunk.type === 'block-end' && chunk.block.type === 'reasoning') {
|
|
238
|
+
reasoning.set(chunk.index, chunk.block.text);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const joined = [...text.values()].join('');
|
|
243
|
+
return joined !== '' ? joined : [...reasoning.values()].join('');
|
|
244
|
+
}
|
|
249
245
|
/**
|
|
250
246
|
* Plan a restore: the target must be an editable user message (the same
|
|
251
247
|
* vocabulary as withdrawal — original, restore replay, or edit replacement)
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"cordis",
|
|
8
8
|
"messages"
|
|
9
9
|
],
|
|
10
|
-
"version": "0.
|
|
10
|
+
"version": "0.3.0",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "lib/index.js",
|
|
13
13
|
"types": "lib/types/index.d.ts",
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
},
|
|
46
46
|
"client": {
|
|
47
47
|
"inject": [
|
|
48
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
49
48
|
"@deepseek-ai/dsh-api-remotes",
|
|
50
49
|
"@deepseek-ai/dsh-client-locale",
|
|
51
50
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
@@ -54,8 +53,8 @@
|
|
|
54
53
|
"platform": "web"
|
|
55
54
|
},
|
|
56
55
|
"compat": {
|
|
57
|
-
"minHost": "0.1.
|
|
58
|
-
"verifiedHost": "0.1.
|
|
56
|
+
"minHost": "0.1.5-rc.1",
|
|
57
|
+
"verifiedHost": "0.1.5-rc.1"
|
|
59
58
|
}
|
|
60
59
|
},
|
|
61
60
|
"license": "MIT",
|
|
@@ -63,7 +62,6 @@
|
|
|
63
62
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
64
63
|
"@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
|
|
65
64
|
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
66
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
67
65
|
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.0-rc.6",
|
|
68
66
|
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.0-rc.6",
|
|
69
67
|
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
@@ -76,19 +74,22 @@
|
|
|
76
74
|
},
|
|
77
75
|
"devDependencies": {
|
|
78
76
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
79
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-
|
|
82
|
-
"@deepseek-ai/dsh-client-
|
|
83
|
-
"@deepseek-ai/dsh-client-
|
|
84
|
-
"@deepseek-ai/dsh-client-
|
|
85
|
-
"@deepseek-ai/dsh-client-ui-
|
|
86
|
-
"@deepseek-ai/dsh-client-ui-
|
|
87
|
-
"@deepseek-ai/dsh-client-ui-
|
|
88
|
-
"@deepseek-ai/dsh-
|
|
89
|
-
"@deepseek-ai/dsh-
|
|
90
|
-
"@deepseek-ai/dsh-
|
|
91
|
-
"@deepseek-ai/dsh-
|
|
77
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-rc.1",
|
|
78
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.5-rc.1",
|
|
79
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.5-rc.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
|
|
81
|
+
"@deepseek-ai/dsh-client-store": "^0.1.5-rc.1",
|
|
82
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.5-rc.1",
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.5-rc.1",
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
|
|
85
|
+
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.5-rc.1",
|
|
86
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.5-rc.1",
|
|
87
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.1",
|
|
88
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
|
|
89
|
+
"@deepseek-ai/dsh-invariants": "^0.1.5-rc.1",
|
|
90
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-rc.1",
|
|
91
|
+
"@deepseek-ai/dsh-session": "^0.1.5-rc.1",
|
|
92
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.5-rc.1",
|
|
92
93
|
"@testing-library/react": "^16.1.0",
|
|
93
94
|
"@types/node": "^26.2.0",
|
|
94
95
|
"@types/react": "~18.3.1",
|