@khorsheed/dsh-client-message-tools 0.2.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 +8 -0
- package/README.en.md +4 -4
- package/README.i18n.yaml +2 -2
- package/README.md +4 -4
- package/lib/client.js +51 -36
- package/lib/index.js +52 -57
- package/lib/types/client/UserMessageView.js +7 -4
- package/lib/types/client/WithdrawnDividerView.js +2 -2
- package/lib/types/client/withdrawn-node.js +3 -3
- package/lib/types/index.js +2 -2
- package/lib/types/marker.d.ts +2 -2
- package/lib/types/withdraw.js +67 -75
- package/package.json +19 -19
- 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
package/lib/types/withdraw.js
CHANGED
|
@@ -111,10 +111,10 @@ 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) {
|
|
@@ -127,7 +127,7 @@ function findWithdrawnSpan(events, targetSeq) {
|
|
|
127
127
|
if (sources?.includes(targetSeq) !== true)
|
|
128
128
|
continue;
|
|
129
129
|
const span = [];
|
|
130
|
-
for (let seq = event.surfaceOp.
|
|
130
|
+
for (let seq = event.surfaceOp.startSeq; seq < event.seq; seq++)
|
|
131
131
|
span.push(seq);
|
|
132
132
|
return span;
|
|
133
133
|
}
|
|
@@ -140,64 +140,27 @@ function findWithdrawnSpan(events, targetSeq) {
|
|
|
140
140
|
* restore-assistant replays already carry the frame). Skipped: withdrawal
|
|
141
141
|
* placeholders, edit triggers, other plugin context, and tool calls/results.
|
|
142
142
|
*
|
|
143
|
-
* Assistant text normally comes from `assistant/message` surface events
|
|
144
|
-
* turn
|
|
145
|
-
* `
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
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.
|
|
149
152
|
*/
|
|
150
153
|
function replayEntries(events, spanSeqs) {
|
|
151
154
|
const entries = [];
|
|
152
|
-
|
|
155
|
+
// A step's final message supersedes its attempts; collect the finalized
|
|
156
|
+
// steps first so an attempt is skipped regardless of log order.
|
|
153
157
|
const finalizedSteps = new Set();
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const text = [...pending.text.values()].join('');
|
|
159
|
-
const reasoning = [...pending.reasoning.values()].join('');
|
|
160
|
-
const replay = text !== '' ? text : reasoning;
|
|
161
|
-
if (replay !== '') {
|
|
162
|
-
entries.push({
|
|
163
|
-
role: 'assistant',
|
|
164
|
-
text: `${RESTORED_ASSISTANT_NOTICE}\n${replay}`,
|
|
165
|
-
sourceSeq: pending.sourceSeq,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
pending = undefined;
|
|
170
|
-
};
|
|
171
|
-
const pushChunk = (event) => {
|
|
172
|
-
if (finalizedSteps.has(`${event.data.turn}:${event.data.step}`))
|
|
173
|
-
return;
|
|
174
|
-
if (pending === undefined
|
|
175
|
-
|| pending.turn !== event.data.turn
|
|
176
|
-
|| pending.step !== event.data.step) {
|
|
177
|
-
flushPending();
|
|
178
|
-
pending = {
|
|
179
|
-
turn: event.data.turn,
|
|
180
|
-
step: event.data.step,
|
|
181
|
-
sourceSeq: event.seq,
|
|
182
|
-
text: new Map(),
|
|
183
|
-
reasoning: new Map(),
|
|
184
|
-
finalized: false,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
const chunk = event.data.chunk;
|
|
188
|
-
if (chunk.type === 'text-delta') {
|
|
189
|
-
pending.text.set(chunk.index, (pending.text.get(chunk.index) ?? '') + chunk.text);
|
|
190
|
-
}
|
|
191
|
-
else if (chunk.type === 'block-end' && chunk.block.type === 'text') {
|
|
192
|
-
pending.text.set(chunk.index, chunk.block.text);
|
|
193
|
-
}
|
|
194
|
-
else if (chunk.type === 'reasoning-delta') {
|
|
195
|
-
pending.reasoning.set(chunk.index, (pending.reasoning.get(chunk.index) ?? '') + chunk.text);
|
|
196
|
-
}
|
|
197
|
-
else if (chunk.type === 'block-end' && chunk.block.type === 'reasoning') {
|
|
198
|
-
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}`);
|
|
199
162
|
}
|
|
200
|
-
}
|
|
163
|
+
}
|
|
201
164
|
const pushAssistantText = (text, sourceSeq) => {
|
|
202
165
|
if (text !== '') {
|
|
203
166
|
entries.push({ role: 'assistant', text: `${RESTORED_ASSISTANT_NOTICE}\n${text}`, sourceSeq });
|
|
@@ -208,16 +171,16 @@ function replayEntries(events, spanSeqs) {
|
|
|
208
171
|
if (event === undefined)
|
|
209
172
|
continue;
|
|
210
173
|
if (isMessageToolsEdit(event)) {
|
|
211
|
-
flushPending();
|
|
212
174
|
entries.push({ role: 'user', content: event.data.content, sourceSeq: seq });
|
|
213
175
|
continue;
|
|
214
176
|
}
|
|
215
|
-
if (event.type === 'assistant/
|
|
216
|
-
|
|
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);
|
|
217
181
|
continue;
|
|
218
182
|
}
|
|
219
183
|
if (event.type === 'user/message') {
|
|
220
|
-
flushPending();
|
|
221
184
|
if (event.surfaceOp !== 'append')
|
|
222
185
|
continue;
|
|
223
186
|
if (event.data.source.kind === 'user' || isMessageToolsRestore(event)) {
|
|
@@ -231,25 +194,54 @@ function replayEntries(events, spanSeqs) {
|
|
|
231
194
|
continue;
|
|
232
195
|
}
|
|
233
196
|
if (event.type === 'assistant/message') {
|
|
234
|
-
// A finalized step supersedes any pending chunk accumulation for the
|
|
235
|
-
// same step: flush without emitting the partial, then replay the final.
|
|
236
|
-
finalizedSteps.add(`${event.data.turn}:${event.data.step}`);
|
|
237
|
-
if (pending !== undefined
|
|
238
|
-
&& pending.turn === event.data.turn
|
|
239
|
-
&& pending.step === event.data.step) {
|
|
240
|
-
pending.finalized = true;
|
|
241
|
-
}
|
|
242
|
-
flushPending();
|
|
243
197
|
pushAssistantText(joinAssistantMessageText(event.data.message.content), seq);
|
|
244
198
|
continue;
|
|
245
199
|
}
|
|
246
|
-
// Log-only events (boundaries, headers, titles)
|
|
247
|
-
// the pending assistant step is flushed at the next replayable boundary or
|
|
248
|
-
// when a different step starts.
|
|
200
|
+
// Log-only events (boundaries, headers, titles) contribute nothing.
|
|
249
201
|
}
|
|
250
|
-
flushPending();
|
|
251
202
|
return entries;
|
|
252
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
|
+
}
|
|
253
245
|
/**
|
|
254
246
|
* Plan a restore: the target must be an editable user message (the same
|
|
255
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",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"platform": "web"
|
|
54
54
|
},
|
|
55
55
|
"compat": {
|
|
56
|
-
"minHost": "0.1.
|
|
57
|
-
"verifiedHost": "0.1.
|
|
56
|
+
"minHost": "0.1.5-rc.1",
|
|
57
|
+
"verifiedHost": "0.1.5-rc.1"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"license": "MIT",
|
|
@@ -74,22 +74,22 @@
|
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
77
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
78
|
-
"@deepseek-ai/dsh-api-remotes": "^0.1.
|
|
79
|
-
"@deepseek-ai/dsh-api-session-controller": "^0.1.
|
|
80
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
81
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
82
|
-
"@deepseek-ai/dsh-client-test-runtime": "^0.1.
|
|
83
|
-
"@deepseek-ai/dsh-client-ui-chat": "^0.1.
|
|
84
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
85
|
-
"@deepseek-ai/dsh-client-ui-model-selection": "^0.1.
|
|
86
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
87
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
88
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
89
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
90
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
91
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
92
|
-
"@deepseek-ai/dsh-typert-protocol": "^0.1.
|
|
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",
|
|
93
93
|
"@testing-library/react": "^16.1.0",
|
|
94
94
|
"@types/node": "^26.2.0",
|
|
95
95
|
"@types/react": "~18.3.1",
|