@astralform/js 4.3.0 → 4.5.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
@@ -538,6 +538,14 @@ var AstralformClient = class {
538
538
  llmProvider: raw.llm_provider,
539
539
  llmModel: raw.llm_model,
540
540
  message: raw.message,
541
+ // Defaults to [] rather than undefined so a caller can iterate without a
542
+ // guard, and so a server that predates the field behaves as "reports
543
+ // nothing" instead of throwing. Entries missing a key are dropped: a
544
+ // capability with no name cannot be matched against and would render as
545
+ // an unlabelled row.
546
+ capabilities: (raw.capabilities ?? []).flatMap(
547
+ (c) => typeof c?.key === "string" && c.key.length > 0 ? [{ key: c.key, enabled: Boolean(c.enabled) }] : []
548
+ ),
541
549
  uiComponents: {
542
550
  enabled: Boolean(ui.enabled),
543
551
  protocol: ui.protocol ?? null,
@@ -1820,11 +1828,16 @@ var ChatSession = class {
1820
1828
  * ``message_start`` in the stream (e.g. ``memory_recall`` from prompt prep),
1821
1829
  * so leading with the prompt keeps the turn in order.
1822
1830
  */
1823
- replayTurn(id, events, userMessageContent) {
1831
+ replayTurn(id, events, userMessageContent, userMessageId, isSteer = false) {
1824
1832
  this.conversationId = id;
1825
1833
  this.resetStreamingState();
1826
1834
  if (userMessageContent) {
1827
- this.emit({ type: "user_message", content: userMessageContent });
1835
+ this.emit({
1836
+ type: "user_message",
1837
+ content: userMessageContent,
1838
+ ...userMessageId ? { id: userMessageId } : {},
1839
+ ...isSteer ? { steer: true } : {}
1840
+ });
1828
1841
  }
1829
1842
  for (const ev of events) {
1830
1843
  const type = ev.data.type || ev.event;
@@ -1937,6 +1950,64 @@ var ChatSession = class {
1937
1950
  }
1938
1951
  };
1939
1952
 
1953
+ // src/restore-plan.ts
1954
+ function planRestore(args) {
1955
+ const { completedJobs, userMessages } = args;
1956
+ const byId = /* @__PURE__ */ new Map();
1957
+ userMessages.forEach((m, i) => {
1958
+ if (m.id) byId.set(m.id, i);
1959
+ });
1960
+ const linkOf = (j) => j.message_id ? byId.get(j.message_id) : void 0;
1961
+ const positional = (jobs, msgs) => jobs.map((job, i) => ({
1962
+ kind: "turn",
1963
+ jobId: job.job_id,
1964
+ content: msgs[i]?.content,
1965
+ messageId: msgs[i]?.id
1966
+ }));
1967
+ const firstLinked = completedJobs.findIndex((j) => linkOf(j) !== void 0);
1968
+ if (firstLinked === -1) {
1969
+ return positional(completedJobs, userMessages);
1970
+ }
1971
+ const cutover = linkOf(completedJobs[firstLinked]);
1972
+ const steps = positional(
1973
+ completedJobs.slice(0, firstLinked),
1974
+ userMessages.slice(0, cutover)
1975
+ );
1976
+ let cursor = cutover;
1977
+ const isSteer = (m) => !!m?.id && !completedJobs.some((j) => j.message_id === m.id);
1978
+ const drainTo = (stopAt) => {
1979
+ while (cursor < stopAt) {
1980
+ const m = userMessages[cursor++];
1981
+ if (isSteer(m)) {
1982
+ steps.push({ kind: "steer", content: m.content, messageId: m.id });
1983
+ }
1984
+ }
1985
+ cursor = stopAt + 1;
1986
+ };
1987
+ for (const job of completedJobs.slice(firstLinked)) {
1988
+ const at = linkOf(job);
1989
+ if (at !== void 0) {
1990
+ drainTo(at);
1991
+ const prompt = userMessages[at];
1992
+ steps.push({
1993
+ kind: "turn",
1994
+ jobId: job.job_id,
1995
+ content: prompt.content,
1996
+ messageId: prompt.id
1997
+ });
1998
+ continue;
1999
+ }
2000
+ steps.push({ kind: "turn", jobId: job.job_id });
2001
+ }
2002
+ for (let i = cursor; i < userMessages.length; i++) {
2003
+ const m = userMessages[i];
2004
+ if (isSteer(m)) {
2005
+ steps.push({ kind: "steer", content: m.content, messageId: m.id });
2006
+ }
2007
+ }
2008
+ return steps;
2009
+ }
2010
+
1940
2011
  // src/types.ts
1941
2012
  var ChatEventType = {
1942
2013
  // Connection lifecycle (SDK-local, not wire)
@@ -2205,16 +2276,40 @@ var StreamManager = class {
2205
2276
  const userMessages = this.session.messages.filter(
2206
2277
  (m) => m.role === "user"
2207
2278
  );
2279
+ const plan = planRestore({
2280
+ completedJobs: completedJobs.map((j) => ({
2281
+ job_id: j.job_id,
2282
+ message_id: j.message_id
2283
+ })),
2284
+ userMessages: userMessages.map((m) => ({
2285
+ id: m.id,
2286
+ content: m.content
2287
+ }))
2288
+ });
2208
2289
  const eventLists = await Promise.all(
2209
2290
  completedJobs.map(
2210
2291
  (job) => this.session.client.getConversationEvents(conversationId, job.job_id).catch(() => [])
2211
2292
  )
2212
2293
  );
2213
- for (let i = 0; i < completedJobs.length; i++) {
2294
+ const eventsByJobId = new Map(
2295
+ completedJobs.map((job, i) => [job.job_id, eventLists[i] ?? []])
2296
+ );
2297
+ for (const step of plan) {
2298
+ if (step.kind === "steer") {
2299
+ this.session.replayTurn(
2300
+ conversationId,
2301
+ [],
2302
+ step.content,
2303
+ step.messageId,
2304
+ true
2305
+ );
2306
+ continue;
2307
+ }
2214
2308
  this.session.replayTurn(
2215
2309
  conversationId,
2216
- eventLists[i] ?? [],
2217
- userMessages[i]?.content
2310
+ eventsByJobId.get(step.jobId) ?? [],
2311
+ step.content,
2312
+ step.messageId
2218
2313
  );
2219
2314
  }
2220
2315
  if (completedJobs.length > 0) {