@morlay/ui-conversation-message-actions 0.0.12 → 0.0.13
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/dist/client.js +23 -23
- package/dist/index.d.mts +21 -69
- package/dist/index.mjs +2 -606
- package/dist/invariant.d.mts +1 -2
- package/dist/invariant.mjs +0 -2
- package/dist/plan-Bw4zoI4N.d.mts +86 -0
- package/dist/plan.d.mts +2 -0
- package/dist/plan.mjs +225 -0
- package/dist/src-CXYW0Bc0.mjs +408 -0
- package/dist/testing.d.mts +707 -0
- package/dist/testing.mjs +23690 -0
- package/package.json +32 -12
- package/src/index.ts +79 -365
- package/src/plan.ts +322 -0
- package/src/testing.ts +151 -0
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/invariant.d.mts.map +0 -1
- package/dist/invariant.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,607 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { SESSION_BRANCH_VERSION_SCHEMA, SESSION_BRANCH_VERSION_SCHEMA as SESSION_BRANCH_VERSION_SCHEMA$1, SessionBranchError, balanceRewindPrefix } from "@morlay/session-branch";
|
|
4
|
-
//#region src/shared.ts
|
|
5
|
-
const SESSION_EDITOR_PATH = "/session-editor";
|
|
6
|
-
function toTimelinePayload(sessionId, timeline, messages, retryableTurns) {
|
|
7
|
-
const currentPath = /* @__PURE__ */ new Set();
|
|
8
|
-
for (const node of timeline.nodes) currentPath.add(String(node.sessionId));
|
|
9
|
-
const versions = timeline.nodes.map((node) => ({
|
|
10
|
-
sessionId: String(node.sessionId),
|
|
11
|
-
...node.parentSessionId === void 0 ? {} : { parentSessionId: String(node.parentSessionId) },
|
|
12
|
-
...node.effect === void 0 ? {} : {
|
|
13
|
-
effectId: node.effect.id,
|
|
14
|
-
inverseSessionId: String(node.inverseSessionId),
|
|
15
|
-
operation: node.effect.operation,
|
|
16
|
-
cascade: node.effect.cascade,
|
|
17
|
-
targetTurn: node.effect.targetTurn,
|
|
18
|
-
...node.effect.blockKind === void 0 ? {} : { blockKind: node.effect.blockKind },
|
|
19
|
-
...node.effect.before === void 0 ? {} : { before: node.effect.before },
|
|
20
|
-
...node.effect.after === void 0 ? {} : { after: node.effect.after }
|
|
21
|
-
},
|
|
22
|
-
createdAt: node.createdAt,
|
|
23
|
-
depth: depthOf(timeline, node.sessionId),
|
|
24
|
-
current: String(node.sessionId) === String(sessionId),
|
|
25
|
-
onCurrentEffectPath: currentPath.has(String(node.sessionId))
|
|
26
|
-
}));
|
|
27
|
-
const versionsById = new Map(versions.map((version) => [version.sessionId, version]));
|
|
28
|
-
const undoStack = [];
|
|
29
|
-
let cursor = versionsById.get(String(sessionId));
|
|
30
|
-
while (cursor?.inverseSessionId !== void 0) {
|
|
31
|
-
if (undoStack.includes(cursor.inverseSessionId)) break;
|
|
32
|
-
undoStack.push(cursor.inverseSessionId);
|
|
33
|
-
cursor = versionsById.get(cursor.inverseSessionId);
|
|
34
|
-
}
|
|
35
|
-
const redoSessionIds = versions.filter((version) => version.inverseSessionId === String(sessionId)).map((version) => version.sessionId);
|
|
36
|
-
return {
|
|
37
|
-
sessionId: String(sessionId),
|
|
38
|
-
messages,
|
|
39
|
-
retryableTurns,
|
|
40
|
-
versions,
|
|
41
|
-
undoStack,
|
|
42
|
-
redoSessionIds
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function depthOf(timeline, sessionId) {
|
|
46
|
-
const byId = new Map(timeline.nodes.map((node) => [String(node.sessionId), node]));
|
|
47
|
-
let depth = 0;
|
|
48
|
-
let cursor = byId.get(String(sessionId));
|
|
49
|
-
const seen = /* @__PURE__ */ new Set();
|
|
50
|
-
while (cursor?.parentSessionId !== void 0 && !seen.has(String(cursor.sessionId))) {
|
|
51
|
-
seen.add(String(cursor.sessionId));
|
|
52
|
-
depth += 1;
|
|
53
|
-
cursor = byId.get(String(cursor.parentSessionId));
|
|
54
|
-
}
|
|
55
|
-
return depth;
|
|
56
|
-
}
|
|
57
|
-
//#endregion
|
|
58
|
-
//#region src/index.ts
|
|
59
|
-
function closedTurns(events) {
|
|
60
|
-
const result = [];
|
|
61
|
-
let current;
|
|
62
|
-
for (const event of events) {
|
|
63
|
-
if (event.type === "turn/start") {
|
|
64
|
-
current = {
|
|
65
|
-
turn: event.data.turn,
|
|
66
|
-
startSeq: event.seq,
|
|
67
|
-
assistants: []
|
|
68
|
-
};
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (current === void 0) continue;
|
|
72
|
-
if (event.type === "user/message" && current.user === void 0 && event.data.source.kind === "user") {
|
|
73
|
-
current.user = event;
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (event.type === "assistant/message" && event.data.turn === current.turn) {
|
|
77
|
-
current.assistants.push(event);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (event.type === "turn/end" && event.data.turn === current.turn) {
|
|
81
|
-
result.push({
|
|
82
|
-
...current,
|
|
83
|
-
endSeq: event.seq,
|
|
84
|
-
closed: true
|
|
85
|
-
});
|
|
86
|
-
current = void 0;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
if (current !== void 0) result.push({
|
|
90
|
-
...current,
|
|
91
|
-
closed: false
|
|
92
|
-
});
|
|
93
|
-
return result;
|
|
94
|
-
}
|
|
95
|
-
function isTextualBlock(block) {
|
|
96
|
-
return block?.type === "text" || block?.type === "reasoning";
|
|
97
|
-
}
|
|
98
|
-
function userText(message) {
|
|
99
|
-
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("\n");
|
|
100
|
-
}
|
|
101
|
-
function cloneUser(message, content = structuredClone(message.content)) {
|
|
102
|
-
return Object.freeze({
|
|
103
|
-
id: randomUUID(),
|
|
104
|
-
role: "user",
|
|
105
|
-
content: Object.freeze(content),
|
|
106
|
-
source: Object.freeze({ kind: "user" })
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
function replaceTextBlock(content, blockIndex, text) {
|
|
110
|
-
const block = content[blockIndex];
|
|
111
|
-
if (!isTextualBlock(block)) throw new SessionBranchError("所选内容块不是可编辑文本。", "INVALID_BOUNDARY");
|
|
112
|
-
return content.map((candidate, index) => index === blockIndex ? {
|
|
113
|
-
...candidate,
|
|
114
|
-
text
|
|
115
|
-
} : structuredClone(candidate));
|
|
116
|
-
}
|
|
117
|
-
function editableMessages(turns) {
|
|
118
|
-
const result = [];
|
|
119
|
-
for (const turn of turns) {
|
|
120
|
-
if (turn.user !== void 0) for (const [blockIndex, block] of turn.user.data.content.entries()) {
|
|
121
|
-
if (block.type !== "text") continue;
|
|
122
|
-
result.push({
|
|
123
|
-
key: `${String(turn.user.seq)}:${String(blockIndex)}`,
|
|
124
|
-
turn: turn.turn,
|
|
125
|
-
eventSeq: turn.user.seq,
|
|
126
|
-
blockIndex,
|
|
127
|
-
kind: "user",
|
|
128
|
-
text: block.text,
|
|
129
|
-
time: turn.user.time
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
if (!turn.closed) continue;
|
|
133
|
-
for (const event of turn.assistants) for (const [blockIndex, block] of event.data.message.content.entries()) {
|
|
134
|
-
if (!isTextualBlock(block)) continue;
|
|
135
|
-
result.push({
|
|
136
|
-
key: `${String(event.seq)}:${String(blockIndex)}`,
|
|
137
|
-
turn: turn.turn,
|
|
138
|
-
eventSeq: event.seq,
|
|
139
|
-
blockIndex,
|
|
140
|
-
kind: block.type === "reasoning" ? "assistant.reasoning" : "assistant.response",
|
|
141
|
-
text: block.text,
|
|
142
|
-
time: event.time
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return result;
|
|
147
|
-
}
|
|
148
|
-
function retryableTurns(turns) {
|
|
149
|
-
return turns.flatMap((turn) => turn.user === void 0 || !turn.closed ? [] : [{
|
|
150
|
-
turn: turn.turn,
|
|
151
|
-
userEventSeq: turn.user.seq,
|
|
152
|
-
preview: userText(turn.user.data),
|
|
153
|
-
time: turn.user.time
|
|
154
|
-
}]);
|
|
155
|
-
}
|
|
156
|
-
function downstreamUsers(turns, start) {
|
|
157
|
-
return turns.slice(start).flatMap((turn) => turn.user === void 0 ? [] : [cloneUser(turn.user.data)]);
|
|
158
|
-
}
|
|
159
|
-
function assistantReplacement(event, blockIndex, text) {
|
|
160
|
-
const replaced = replaceTextBlock(event.data.message.content, blockIndex, text).filter((block) => block.type === "text" || block.type === "reasoning");
|
|
161
|
-
return Object.freeze({
|
|
162
|
-
id: randomUUID(),
|
|
163
|
-
role: "assistant",
|
|
164
|
-
content: Object.freeze(replaced),
|
|
165
|
-
source: Object.freeze({
|
|
166
|
-
kind: "model",
|
|
167
|
-
provider: event.data.message.source.provider,
|
|
168
|
-
model: event.data.message.source.model
|
|
169
|
-
})
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
function pairVersionEffect(sourceSessionId, effect) {
|
|
173
|
-
return {
|
|
174
|
-
schemaVersion: SESSION_BRANCH_VERSION_SCHEMA$1,
|
|
175
|
-
effect: {
|
|
176
|
-
...effect,
|
|
177
|
-
id: randomUUID()
|
|
178
|
-
},
|
|
179
|
-
inverse: {
|
|
180
|
-
kind: "restore-version",
|
|
181
|
-
sessionId: sourceSessionId
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function editPlan(operation, turns) {
|
|
186
|
-
const turnIndex = turns.findIndex((turn) => operation.eventSeq > turn.startSeq && (turn.endSeq === void 0 || operation.eventSeq < turn.endSeq));
|
|
187
|
-
const turn = turns[turnIndex];
|
|
188
|
-
if (turn === void 0) throw new SessionBranchError("所选消息不属于已落定回合。", "INVALID_BOUNDARY");
|
|
189
|
-
const event = turn.user?.seq === operation.eventSeq ? turn.user : turn.assistants.find((candidate) => candidate.seq === operation.eventSeq);
|
|
190
|
-
if (event === void 0) throw new SessionBranchError("所选消息不存在或不可编辑。", "INVALID_BOUNDARY");
|
|
191
|
-
if (event.type === "user/message") {
|
|
192
|
-
const before = event.data.content[operation.blockIndex];
|
|
193
|
-
if (before?.type !== "text") throw new SessionBranchError("所选用户消息块不是文本。", "INVALID_BOUNDARY");
|
|
194
|
-
const edited = cloneUser(event.data, replaceTextBlock(event.data.content, operation.blockIndex, operation.text));
|
|
195
|
-
const later = operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex + 1) : [];
|
|
196
|
-
return {
|
|
197
|
-
anchorSeq: turn.startSeq,
|
|
198
|
-
...turn.closed ? {} : { rewindBoundary: event.seq },
|
|
199
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
200
|
-
operation: "edit",
|
|
201
|
-
cascade: operation.cascade,
|
|
202
|
-
targetTurn: turn.turn,
|
|
203
|
-
targetEventSeq: event.seq,
|
|
204
|
-
targetBlockIndex: operation.blockIndex,
|
|
205
|
-
blockKind: "user",
|
|
206
|
-
before: before.text,
|
|
207
|
-
after: operation.text
|
|
208
|
-
}),
|
|
209
|
-
queuedUsers: [edited, ...later]
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (!turn.closed) throw new SessionBranchError("未闭合轮次的助手消息不可编辑。", "INVALID_BOUNDARY");
|
|
213
|
-
const before = event.data.message.content[operation.blockIndex];
|
|
214
|
-
if (!isTextualBlock(before)) throw new SessionBranchError("所选助手消息块不是文本或思考。", "INVALID_BOUNDARY");
|
|
215
|
-
const blockKind = before.type === "reasoning" ? "assistant.reasoning" : "assistant.response";
|
|
216
|
-
if (turn.user === void 0) throw new SessionBranchError("所选助手消息没有可重建的用户输入。", "INVALID_BOUNDARY");
|
|
217
|
-
return {
|
|
218
|
-
anchorSeq: turn.startSeq,
|
|
219
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
220
|
-
operation: "edit",
|
|
221
|
-
cascade: operation.cascade,
|
|
222
|
-
targetTurn: turn.turn,
|
|
223
|
-
targetEventSeq: event.seq,
|
|
224
|
-
targetBlockIndex: operation.blockIndex,
|
|
225
|
-
blockKind,
|
|
226
|
-
before: before.text,
|
|
227
|
-
after: operation.text
|
|
228
|
-
}),
|
|
229
|
-
manualTurn: {
|
|
230
|
-
turn: turn.turn,
|
|
231
|
-
user: cloneUser(turn.user.data),
|
|
232
|
-
assistant: assistantReplacement(event, operation.blockIndex, operation.text)
|
|
233
|
-
},
|
|
234
|
-
queuedUsers: operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex + 1) : []
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
function retryPlan(operation, turns) {
|
|
238
|
-
const turnIndex = turns.findIndex((turn) => turn.turn === operation.turn);
|
|
239
|
-
const turn = turns[turnIndex];
|
|
240
|
-
if (turn?.user === void 0) throw new SessionBranchError("所选回合没有可重放的用户输入。", "INVALID_BOUNDARY");
|
|
241
|
-
return {
|
|
242
|
-
anchorSeq: turn.startSeq,
|
|
243
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
244
|
-
operation: "retry",
|
|
245
|
-
cascade: operation.cascade,
|
|
246
|
-
targetTurn: turn.turn,
|
|
247
|
-
targetEventSeq: turn.user.seq
|
|
248
|
-
}),
|
|
249
|
-
queuedUsers: operation.cascade === "preserve" ? downstreamUsers(turns, turnIndex) : [cloneUser(turn.user.data)]
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
function rerollPlan(operation, turns) {
|
|
253
|
-
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
254
|
-
const turn = turns[index];
|
|
255
|
-
if (turn?.user === void 0 || !turn.closed) continue;
|
|
256
|
-
const target = turn.assistants.findLast((event) => event.data.message.content.some(isTextualBlock));
|
|
257
|
-
if (target === void 0) continue;
|
|
258
|
-
return {
|
|
259
|
-
anchorSeq: turn.startSeq,
|
|
260
|
-
version: pairVersionEffect(operation.sessionId, {
|
|
261
|
-
operation: "reroll",
|
|
262
|
-
cascade: "truncate",
|
|
263
|
-
targetTurn: turn.turn,
|
|
264
|
-
targetEventSeq: target.seq
|
|
265
|
-
}),
|
|
266
|
-
queuedUsers: [cloneUser(turn.user.data)]
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
throw new SessionBranchError("当前会话没有可重生成的已落定助手回复。", "INVALID_BOUNDARY");
|
|
270
|
-
}
|
|
271
|
-
function appendLogSeedEvent(events, type, data, ignorable = false) {
|
|
272
|
-
events.push({
|
|
273
|
-
type,
|
|
274
|
-
seq: events.length,
|
|
275
|
-
time: Date.now(),
|
|
276
|
-
data,
|
|
277
|
-
...ignorable ? { ignorable: true } : {}
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
function appendSurfaceSeedEvent(events, type, data, intent) {
|
|
281
|
-
events.push({
|
|
282
|
-
type,
|
|
283
|
-
seq: events.length,
|
|
284
|
-
time: Date.now(),
|
|
285
|
-
data,
|
|
286
|
-
surfaceOp: intent.surfaceOp,
|
|
287
|
-
...intent.sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: intent.sourceEventSeqs }
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
function appendManualTurn(events, manual) {
|
|
291
|
-
const { turn, user, assistant } = manual;
|
|
292
|
-
appendLogSeedEvent(events, "turn/start", { turn });
|
|
293
|
-
appendSurfaceSeedEvent(events, "user/message", user, { surfaceOp: "append" });
|
|
294
|
-
appendLogSeedEvent(events, "step/start", {
|
|
295
|
-
turn,
|
|
296
|
-
step: 1
|
|
297
|
-
});
|
|
298
|
-
appendSurfaceSeedEvent(events, "assistant/message", {
|
|
299
|
-
turn,
|
|
300
|
-
step: 1,
|
|
301
|
-
message: assistant
|
|
302
|
-
}, {
|
|
303
|
-
surfaceOp: "append",
|
|
304
|
-
sourceEventSeqs: []
|
|
305
|
-
});
|
|
306
|
-
appendLogSeedEvent(events, "step/end", {
|
|
307
|
-
turn,
|
|
308
|
-
step: 1
|
|
309
|
-
});
|
|
310
|
-
appendLogSeedEvent(events, "turn/end", {
|
|
311
|
-
turn,
|
|
312
|
-
reason: { kind: "completed" }
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
function appendSeedSuffixLive(session, seedSuffix) {
|
|
316
|
-
for (const event of seedSuffix) {
|
|
317
|
-
if (event.ignorable === true) {
|
|
318
|
-
const s = session;
|
|
319
|
-
s.log.push({
|
|
320
|
-
...event,
|
|
321
|
-
seq: s.log.length
|
|
322
|
-
});
|
|
323
|
-
s.eventsSnapshot = void 0;
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
const s = session;
|
|
327
|
-
const raw = event;
|
|
328
|
-
if (raw.surfaceOp !== void 0) s.append(event.type, event.data, {
|
|
329
|
-
surfaceOp: raw.surfaceOp,
|
|
330
|
-
...raw.sourceEventSeqs === void 0 ? {} : { sourceEventSeqs: raw.sourceEventSeqs }
|
|
331
|
-
});
|
|
332
|
-
else s.append(event.type, event.data);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
var SessionEditor = class extends Service {
|
|
336
|
-
static inject = [
|
|
337
|
-
"sessionBranch",
|
|
338
|
-
"sessionPersistence",
|
|
339
|
-
"sessions"
|
|
340
|
-
];
|
|
341
|
-
constructor(ctx) {
|
|
342
|
-
super(ctx, "sessionEditor");
|
|
343
|
-
registerHttpRoutes(ctx);
|
|
344
|
-
}
|
|
345
|
-
readBranchPrefix(id, atSeq, mode, signal) {
|
|
346
|
-
return this.ctx.sessionBranch.readBranchPrefix(id, atSeq, mode, signal);
|
|
347
|
-
}
|
|
348
|
-
fork(sourceId, atSeq, childSessionId, meta, signal) {
|
|
349
|
-
return this.ctx.sessionBranch.forkFrom(sourceId, {
|
|
350
|
-
...atSeq === void 0 ? {} : { atSeq },
|
|
351
|
-
...childSessionId === void 0 ? {} : { childSessionId },
|
|
352
|
-
...meta === void 0 ? {} : { meta }
|
|
353
|
-
}, signal);
|
|
354
|
-
}
|
|
355
|
-
rewind(id, toBoundary, signal) {
|
|
356
|
-
return this.ctx.sessionBranch.rewind(id, toBoundary, signal);
|
|
357
|
-
}
|
|
358
|
-
timeline(sessionId, signal) {
|
|
359
|
-
return this.ctx.sessionBranch.timeline(sessionId, signal);
|
|
360
|
-
}
|
|
361
|
-
edit(operation, signal) {
|
|
362
|
-
return this.branchOperation(operation, signal);
|
|
363
|
-
}
|
|
364
|
-
reroll(operation, signal) {
|
|
365
|
-
return this.branchOperation(operation, signal);
|
|
366
|
-
}
|
|
367
|
-
retry(operation, signal) {
|
|
368
|
-
return this.branchOperation(operation, signal);
|
|
369
|
-
}
|
|
370
|
-
async editableMessages(sessionId, signal) {
|
|
371
|
-
return editableMessages(closedTurns(await this.readEvents(sessionId, signal)));
|
|
372
|
-
}
|
|
373
|
-
async retryableTurns(sessionId, signal) {
|
|
374
|
-
return retryableTurns(closedTurns(await this.readEvents(sessionId, signal)));
|
|
375
|
-
}
|
|
376
|
-
async branchOperation(operation, signal) {
|
|
377
|
-
signal?.throwIfAborted();
|
|
378
|
-
const events = await this.readEvents(operation.sessionId, signal);
|
|
379
|
-
const turns = closedTurns(events);
|
|
380
|
-
const plan = operation.action === "edit" ? editPlan(operation, turns) : operation.action === "retry" ? retryPlan(operation, turns) : rerollPlan(operation, turns);
|
|
381
|
-
const headerConfig = events.findLast((event) => event.type === "request/header")?.data.header.config;
|
|
382
|
-
const seedSuffix = [];
|
|
383
|
-
appendLogSeedEvent(seedSuffix, "session-branch/version", plan.version, true);
|
|
384
|
-
if (plan.manualTurn !== void 0) appendManualTurn(seedSuffix, plan.manualTurn);
|
|
385
|
-
const turnIndex = turns.findIndex((turn) => turn.startSeq === plan.anchorSeq);
|
|
386
|
-
const boundary = plan.rewindBoundary !== void 0 ? plan.rewindBoundary : turnIndex <= 0 ? -1 : turns[turnIndex - 1].endSeq;
|
|
387
|
-
const live = this.ctx.sessions.get(operation.sessionId);
|
|
388
|
-
await this.ctx.sessionBranch.rewind(operation.sessionId, boundary, signal);
|
|
389
|
-
if (seedSuffix.length > 0) {
|
|
390
|
-
if (live !== void 0) {
|
|
391
|
-
appendSeedSuffixLive(live, seedSuffix);
|
|
392
|
-
this.ctx.sessionBranch.syncLiveCursor(operation.sessionId);
|
|
393
|
-
await this.ctx.sessions.flush(live);
|
|
394
|
-
} else {
|
|
395
|
-
const rawKeepLength = boundary + (plan.rewindBoundary === void 0 ? 1 : 0);
|
|
396
|
-
const keepLength = balanceRewindPrefix(events.slice(0, rawKeepLength)).length;
|
|
397
|
-
const renumbered = seedSuffix.map((event, index) => ({
|
|
398
|
-
...event,
|
|
399
|
-
seq: keepLength + index
|
|
400
|
-
}));
|
|
401
|
-
await this.ctx.sessionPersistence.append(operation.sessionId, renumbered);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
const queuedTurns = await this.driveAgent(operation.sessionId, plan.queuedUsers, signal, headerConfig);
|
|
405
|
-
return {
|
|
406
|
-
sessionId: operation.sessionId,
|
|
407
|
-
queuedTurns,
|
|
408
|
-
live: this.ctx.sessions.get(operation.sessionId) !== void 0
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
async readEvents(sessionId, signal) {
|
|
412
|
-
const live = this.ctx.sessions.get(sessionId);
|
|
413
|
-
if (live !== void 0) return live.snapshotEvents();
|
|
414
|
-
return (await this.ctx.sessionBranch.readRawEvents(sessionId, signal)).events;
|
|
415
|
-
}
|
|
416
|
-
async driveAgent(sessionId, queuedUsers, signal, headerConfig) {
|
|
417
|
-
if (queuedUsers.length === 0) return 0;
|
|
418
|
-
signal?.throwIfAborted();
|
|
419
|
-
if (this.ctx.get("agents") === void 0) return 0;
|
|
420
|
-
const provider = headerConfig?.provider ?? "";
|
|
421
|
-
const model = headerConfig?.model ?? "";
|
|
422
|
-
if (provider.length === 0 || model.length === 0) {
|
|
423
|
-
const config = (await this.readEvents(sessionId, signal)).findLast((event) => event.type === "request/header")?.data.header.config;
|
|
424
|
-
const fallbackProvider = config?.provider ?? "";
|
|
425
|
-
const fallbackModel = config?.model ?? "";
|
|
426
|
-
if (fallbackProvider.length === 0 || fallbackModel.length === 0) return 0;
|
|
427
|
-
return this.queueThroughAgent(sessionId, queuedUsers, signal, fallbackProvider, fallbackModel);
|
|
428
|
-
}
|
|
429
|
-
return this.queueThroughAgent(sessionId, queuedUsers, signal, provider, model);
|
|
430
|
-
}
|
|
431
|
-
async queueThroughAgent(sessionId, queuedUsers, signal, provider, model) {
|
|
432
|
-
const agents = this.ctx.get("agents");
|
|
433
|
-
if (agents === void 0) return 0;
|
|
434
|
-
const existing = agents.get(sessionId);
|
|
435
|
-
if (existing !== void 0) {
|
|
436
|
-
for (const message of queuedUsers) existing.followup(message);
|
|
437
|
-
await this.ctx.sessions.flush(existing.session);
|
|
438
|
-
return queuedUsers.length;
|
|
439
|
-
}
|
|
440
|
-
const handle = await agents.resume({
|
|
441
|
-
resumeSessionId: sessionId,
|
|
442
|
-
agentOptions: {
|
|
443
|
-
provider,
|
|
444
|
-
model
|
|
445
|
-
}
|
|
446
|
-
}).catch((error) => {
|
|
447
|
-
this.ctx.logger.warn("session-editor: agent resume failed (%s); version remains durable", String(error));
|
|
448
|
-
});
|
|
449
|
-
if (handle === void 0) return 0;
|
|
450
|
-
for (const message of queuedUsers) handle.agent.followup(message);
|
|
451
|
-
await this.ctx.sessions.flush(handle.agent.session);
|
|
452
|
-
return queuedUsers.length;
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
function objectValue(value) {
|
|
456
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("请求体必须是 JSON 对象。");
|
|
457
|
-
return value;
|
|
458
|
-
}
|
|
459
|
-
function sessionIdOf(value) {
|
|
460
|
-
if (typeof value !== "string" || value.length === 0) throw new TypeError("sessionId 必须是非空字符串。");
|
|
461
|
-
return value;
|
|
462
|
-
}
|
|
463
|
-
function integerOf(value, name) {
|
|
464
|
-
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${name} 必须是非负安全整数。`);
|
|
465
|
-
return value;
|
|
466
|
-
}
|
|
467
|
-
function cascadeOf(value) {
|
|
468
|
-
if (value !== "truncate" && value !== "preserve") throw new TypeError("cascade 必须是 truncate 或 preserve。");
|
|
469
|
-
return value;
|
|
470
|
-
}
|
|
471
|
-
function decodeOperation(value) {
|
|
472
|
-
const record = objectValue(value);
|
|
473
|
-
const sessionId = sessionIdOf(record["sessionId"]);
|
|
474
|
-
switch (record["action"]) {
|
|
475
|
-
case "edit":
|
|
476
|
-
if (typeof record["text"] !== "string") throw new TypeError("text 必须是字符串。");
|
|
477
|
-
return {
|
|
478
|
-
action: "edit",
|
|
479
|
-
sessionId,
|
|
480
|
-
eventSeq: integerOf(record["eventSeq"], "eventSeq"),
|
|
481
|
-
blockIndex: integerOf(record["blockIndex"], "blockIndex"),
|
|
482
|
-
text: record["text"],
|
|
483
|
-
cascade: cascadeOf(record["cascade"])
|
|
484
|
-
};
|
|
485
|
-
case "reroll": return {
|
|
486
|
-
action: "reroll",
|
|
487
|
-
sessionId
|
|
488
|
-
};
|
|
489
|
-
case "retry": return {
|
|
490
|
-
action: "retry",
|
|
491
|
-
sessionId,
|
|
492
|
-
turn: integerOf(record["turn"], "turn"),
|
|
493
|
-
cascade: cascadeOf(record["cascade"])
|
|
494
|
-
};
|
|
495
|
-
case "rewind": return {
|
|
496
|
-
action: "rewind",
|
|
497
|
-
sessionId,
|
|
498
|
-
toBoundary: integerOf(record["toBoundary"], "toBoundary")
|
|
499
|
-
};
|
|
500
|
-
case "fork": return {
|
|
501
|
-
action: "fork",
|
|
502
|
-
sessionId,
|
|
503
|
-
...record["atSeq"] === void 0 ? {} : { atSeq: integerOf(record["atSeq"], "atSeq") },
|
|
504
|
-
...record["childSessionId"] === void 0 ? {} : { childSessionId: sessionIdOf(record["childSessionId"]) }
|
|
505
|
-
};
|
|
506
|
-
default: throw new TypeError("action 必须是 edit、reroll、retry、rewind 或 fork。");
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
function requestJson(request) {
|
|
510
|
-
return new Promise((resolve, reject) => {
|
|
511
|
-
const decoder = new TextDecoder();
|
|
512
|
-
let text = "";
|
|
513
|
-
request.on("data", (chunk) => {
|
|
514
|
-
text += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
|
|
515
|
-
});
|
|
516
|
-
request.on("end", () => {
|
|
517
|
-
try {
|
|
518
|
-
text += decoder.decode();
|
|
519
|
-
resolve(JSON.parse(text));
|
|
520
|
-
} catch (error) {
|
|
521
|
-
reject(error);
|
|
522
|
-
}
|
|
523
|
-
});
|
|
524
|
-
request.on("error", reject);
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
function respondJson(response, status, value) {
|
|
528
|
-
response.writeHead(status, {
|
|
529
|
-
"content-type": "application/json; charset=utf-8",
|
|
530
|
-
"cache-control": "no-store"
|
|
531
|
-
});
|
|
532
|
-
response.end(JSON.stringify(value));
|
|
533
|
-
}
|
|
534
|
-
async function readTimeline(editor, sessionId) {
|
|
535
|
-
return toTimelinePayload(sessionId, await editor.timeline(sessionId), await editor.editableMessages(sessionId), await editor.retryableTurns(sessionId));
|
|
536
|
-
}
|
|
537
|
-
async function runOperation(editor, operation) {
|
|
538
|
-
switch (operation.action) {
|
|
539
|
-
case "edit": {
|
|
540
|
-
const result = await editor.edit(operation);
|
|
541
|
-
return {
|
|
542
|
-
sessionId: result.sessionId,
|
|
543
|
-
queuedTurns: result.queuedTurns,
|
|
544
|
-
...result.live === void 0 ? {} : { live: result.live }
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
case "reroll": {
|
|
548
|
-
const result = await editor.reroll(operation);
|
|
549
|
-
return {
|
|
550
|
-
sessionId: result.sessionId,
|
|
551
|
-
queuedTurns: result.queuedTurns,
|
|
552
|
-
...result.live === void 0 ? {} : { live: result.live }
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
case "retry": {
|
|
556
|
-
const result = await editor.retry(operation);
|
|
557
|
-
return {
|
|
558
|
-
sessionId: result.sessionId,
|
|
559
|
-
queuedTurns: result.queuedTurns,
|
|
560
|
-
...result.live === void 0 ? {} : { live: result.live }
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
case "rewind":
|
|
564
|
-
await editor.rewind(operation.sessionId, operation.toBoundary);
|
|
565
|
-
return {
|
|
566
|
-
sessionId: operation.sessionId,
|
|
567
|
-
queuedTurns: 0
|
|
568
|
-
};
|
|
569
|
-
case "fork": return {
|
|
570
|
-
sessionId: await editor.fork(operation.sessionId, operation.atSeq, operation.childSessionId),
|
|
571
|
-
queuedTurns: 0
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
async function handleRoute(editor, request, response) {
|
|
576
|
-
try {
|
|
577
|
-
if (request.method === "GET") {
|
|
578
|
-
respondJson(response, 200, await readTimeline(editor, sessionIdOf(new URL(request.url ?? "/session-editor", "http://session-editor.local").searchParams.get("sessionId"))));
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
if (request.method === "POST") {
|
|
582
|
-
respondJson(response, 200, await runOperation(editor, decodeOperation(await requestJson(request))));
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
response.writeHead(405);
|
|
586
|
-
response.end();
|
|
587
|
-
} catch (error) {
|
|
588
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
589
|
-
respondJson(response, error instanceof TypeError ? 400 : 409, { error: message });
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function registerHttpRoutes(ctx) {
|
|
593
|
-
const webServer = ctx.get("webServer");
|
|
594
|
-
if (webServer === void 0) return;
|
|
595
|
-
ctx.effect(() => {
|
|
596
|
-
const editor = ctx.sessionEditor;
|
|
597
|
-
return webServer.register({
|
|
598
|
-
kind: "exact",
|
|
599
|
-
path: SESSION_EDITOR_PATH,
|
|
600
|
-
handler: (request, response) => handleRoute(editor, request, response)
|
|
601
|
-
});
|
|
602
|
-
}, "session-editor: HTTP route");
|
|
603
|
-
}
|
|
604
|
-
//#endregion
|
|
1
|
+
import { n as SessionEditor, t as SESSION_BRANCH_VERSION_SCHEMA } from "./src-CXYW0Bc0.mjs";
|
|
2
|
+
import { closedTurns, editableMessages, retryableTurns } from "./plan.mjs";
|
|
605
3
|
export { SESSION_BRANCH_VERSION_SCHEMA, SessionEditor, SessionEditor as default, closedTurns, editableMessages, retryableTurns };
|
|
606
|
-
|
|
607
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/invariant.d.mts
CHANGED
|
@@ -4,5 +4,4 @@ declare const name = "ui-conversation-message-actions-invariant";
|
|
|
4
4
|
declare const inject: string[];
|
|
5
5
|
declare const apply: (ctx: Context) => Promise<() => void>;
|
|
6
6
|
//#endregion
|
|
7
|
-
export { apply, inject, name };
|
|
8
|
-
//# sourceMappingURL=invariant.d.mts.map
|
|
7
|
+
export { apply, inject, name };
|