@astralform/js 4.3.0 → 4.4.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/dist/index.cjs CHANGED
@@ -1820,11 +1820,16 @@ var ChatSession = class {
1820
1820
  * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
1821
1821
  * so leading with the prompt keeps the turn in order.
1822
1822
  */
1823
- replayTurn(id, events, userMessageContent) {
1823
+ replayTurn(id, events, userMessageContent, userMessageId, isSteer = false) {
1824
1824
  this.conversationId = id;
1825
1825
  this.resetStreamingState();
1826
1826
  if (userMessageContent) {
1827
- this.emit({ type: "user_message", content: userMessageContent });
1827
+ this.emit({
1828
+ type: "user_message",
1829
+ content: userMessageContent,
1830
+ ...userMessageId ? { id: userMessageId } : {},
1831
+ ...isSteer ? { steer: true } : {}
1832
+ });
1828
1833
  }
1829
1834
  for (const ev of events) {
1830
1835
  const type = ev.data.type || ev.event;
@@ -1937,6 +1942,64 @@ var ChatSession = class {
1937
1942
  }
1938
1943
  };
1939
1944
 
1945
+ // src/restore-plan.ts
1946
+ function planRestore(args) {
1947
+ const { completedJobs, userMessages } = args;
1948
+ const byId = /* @__PURE__ */ new Map();
1949
+ userMessages.forEach((m, i) => {
1950
+ if (m.id) byId.set(m.id, i);
1951
+ });
1952
+ const linkOf = (j) => j.message_id ? byId.get(j.message_id) : void 0;
1953
+ const positional = (jobs, msgs) => jobs.map((job, i) => ({
1954
+ kind: "turn",
1955
+ jobId: job.job_id,
1956
+ content: msgs[i]?.content,
1957
+ messageId: msgs[i]?.id
1958
+ }));
1959
+ const firstLinked = completedJobs.findIndex((j) => linkOf(j) !== void 0);
1960
+ if (firstLinked === -1) {
1961
+ return positional(completedJobs, userMessages);
1962
+ }
1963
+ const cutover = linkOf(completedJobs[firstLinked]);
1964
+ const steps = positional(
1965
+ completedJobs.slice(0, firstLinked),
1966
+ userMessages.slice(0, cutover)
1967
+ );
1968
+ let cursor = cutover;
1969
+ const isSteer = (m) => !!m?.id && !completedJobs.some((j) => j.message_id === m.id);
1970
+ const drainTo = (stopAt) => {
1971
+ while (cursor < stopAt) {
1972
+ const m = userMessages[cursor++];
1973
+ if (isSteer(m)) {
1974
+ steps.push({ kind: "steer", content: m.content, messageId: m.id });
1975
+ }
1976
+ }
1977
+ cursor = stopAt + 1;
1978
+ };
1979
+ for (const job of completedJobs.slice(firstLinked)) {
1980
+ const at = linkOf(job);
1981
+ if (at !== void 0) {
1982
+ drainTo(at);
1983
+ const prompt = userMessages[at];
1984
+ steps.push({
1985
+ kind: "turn",
1986
+ jobId: job.job_id,
1987
+ content: prompt.content,
1988
+ messageId: prompt.id
1989
+ });
1990
+ continue;
1991
+ }
1992
+ steps.push({ kind: "turn", jobId: job.job_id });
1993
+ }
1994
+ for (let i = cursor; i < userMessages.length; i++) {
1995
+ const m = userMessages[i];
1996
+ if (isSteer(m)) {
1997
+ steps.push({ kind: "steer", content: m.content, messageId: m.id });
1998
+ }
1999
+ }
2000
+ return steps;
2001
+ }
2002
+
1940
2003
  // src/types.ts
1941
2004
  var ChatEventType = {
1942
2005
  // Connection lifecycle (SDK-local, not wire)
@@ -2205,16 +2268,40 @@ var StreamManager = class {
2205
2268
  const userMessages = this.session.messages.filter(
2206
2269
  (m) => m.role === "user"
2207
2270
  );
2271
+ const plan = planRestore({
2272
+ completedJobs: completedJobs.map((j) => ({
2273
+ job_id: j.job_id,
2274
+ message_id: j.message_id
2275
+ })),
2276
+ userMessages: userMessages.map((m) => ({
2277
+ id: m.id,
2278
+ content: m.content
2279
+ }))
2280
+ });
2208
2281
  const eventLists = await Promise.all(
2209
2282
  completedJobs.map(
2210
2283
  (job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
2211
2284
  )
2212
2285
  );
2213
- for (let i = 0; i < completedJobs.length; i++) {
2286
+ const eventsByJobId = new Map(
2287
+ completedJobs.map((job, i) => [job.job_id, eventLists[i] ?? []])
2288
+ );
2289
+ for (const step of plan) {
2290
+ if (step.kind === "steer") {
2291
+ this.session.replayTurn(
2292
+ conversationId,
2293
+ [],
2294
+ step.content,
2295
+ step.messageId,
2296
+ true
2297
+ );
2298
+ continue;
2299
+ }
2214
2300
  this.session.replayTurn(
2215
2301
  conversationId,
2216
- eventLists[i] ?? [],
2217
- userMessages[i]?.content
2302
+ eventsByJobId.get(step.jobId) ?? [],
2303
+ step.content,
2304
+ step.messageId
2218
2305
  );
2219
2306
  }
2220
2307
  if (completedJobs.length > 0) {