@astralform/js 4.9.1 → 5.1.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.d.cts CHANGED
@@ -792,6 +792,24 @@ interface ConversationEvent {
792
792
  data: Record<string, unknown>;
793
793
  }
794
794
  interface SendOptions$1 extends ModelChoiceOptions {
795
+ /**
796
+ * Which conversation this turn belongs to. Defaults to the session's current
797
+ * one.
798
+ *
799
+ * ⚠️ BEHAVIOR CHANGE: passing this now RELOCATES the session. A value
800
+ * different from the current one sets `session.conversationId`, DISCARDS
801
+ * `session.messages` (the old conversation's list is not that conversation's
802
+ * history), and invalidates any `loadConversation` still in flight.
803
+ * Previously it addressed a single turn elsewhere and left the session where
804
+ * it was. After a successful send the list holds only that turn, so load the
805
+ * conversation if you need its history.
806
+ *
807
+ * The change is deliberate: `onSessionEvent` tags every emitted event with
808
+ * `session.conversationId`, so a turn sent elsewhere without moving the
809
+ * pointer streamed its events back under the wrong conversation. Addressing
810
+ * one turn away from the session is not something the session can honestly
811
+ * represent while it has a single pointer, so the send now moves it.
812
+ */
795
813
  conversationId?: string;
796
814
  enabledClientTools?: string[];
797
815
  uploadIds?: string[];
@@ -865,6 +883,20 @@ interface ConversationAsset {
865
883
  * failed or the asset has no stored object.
866
884
  */
867
885
  url?: string;
886
+ /**
887
+ * The asset's PERMANENT address. Authorization is resolved per request
888
+ * against the caller's live session, so unlike `url` it never expires — but
889
+ * it needs an `Authorization` header, which a browser will not attach to an
890
+ * `<img src>`. Store and link this one; render from `url`.
891
+ */
892
+ contentUrl?: string;
893
+ /**
894
+ * A still to show for an asset the browser cannot draw from `url` alone.
895
+ * Present only for video: an `<img src>` pointed at an mp4 renders nothing,
896
+ * so a video row would otherwise have no thumbnail. Signed and expiring
897
+ * exactly like `url` — display, not identity, so never store it.
898
+ */
899
+ posterUrl?: string;
868
900
  createdAt: string;
869
901
  }
870
902
 
@@ -1115,6 +1147,17 @@ declare class ChatSession {
1115
1147
  /** True while ``loadMoreConversations`` is in flight. */
1116
1148
  isLoadingConversations: boolean;
1117
1149
  messages: Message[];
1150
+ /**
1151
+ * Which conversation ``messages`` currently holds.
1152
+ *
1153
+ * Distinct from ``conversationId``, and the distinction is the point:
1154
+ * ``loadConversation`` moves the POINTER synchronously and installs the LIST
1155
+ * an await later, so for the whole duration of every load the two disagree.
1156
+ * Anything pairing a message with a conversation — ``regenerate`` above all —
1157
+ * has to read this one, or it will pair the previous conversation's last
1158
+ * message with the new conversation's id.
1159
+ */
1160
+ messagesConversationId: string | null;
1118
1161
  isStreaming: boolean;
1119
1162
  agentStatus: AgentStatus | null;
1120
1163
  agents: AgentInfo[];
@@ -1147,6 +1190,49 @@ declare class ChatSession {
1147
1190
  * is discarded instead.
1148
1191
  */
1149
1192
  private conversationsGeneration;
1193
+ /**
1194
+ * Bumped by every ``loadConversation`` call, so an out-of-order fetch can
1195
+ * tell it is no longer the newest one and drop its result. Separate from
1196
+ * ``conversationsGeneration``, which guards the conversation LIST.
1197
+ */
1198
+ private loadGeneration;
1199
+ /**
1200
+ * Ids of locally-created user messages the server has not acknowledged yet.
1201
+ *
1202
+ * Arrival time cannot answer "could the reply have included this?" on its
1203
+ * own. Every `loadConversation` in `StreamManager` sits behind the
1204
+ * active-job probe, so the ORDINARY ordering is that a send lands BEFORE the
1205
+ * load starts — and an arrival-time rule drops exactly those, losing the
1206
+ * prompt the user just sent while its stream is still running. Membership
1207
+ * here is set by `send` and cleared when a server row turns up carrying the
1208
+ * same turn, so the keep-decision no longer depends on which side of the
1209
+ * fetch the push landed on.
1210
+ *
1211
+ * The value is `serverRowsKnown` as of the `message_stop` that proved the
1212
+ * row committed, or 0 until then — including after the job response, which
1213
+ * hands back the id but starts the loop as a background task and so proves
1214
+ * nothing about the row. That stamp is what separates "the snapshot predates
1215
+ * the row" from "the server does not have this row" — see
1216
+ * `loadConversation`.
1217
+ *
1218
+ * It is an ANNOTATION ON `this.messages`: reconciliation only ever consults
1219
+ * entries of that array, so an id whose message has left it is dead weight.
1220
+ * `setMessages` is the single place the array is replaced, and it prunes.
1221
+ */
1222
+ private pendingUserMessages;
1223
+ /**
1224
+ * Bumped once per completed turn, at `message_stop`, so a fetch can record
1225
+ * what was proven when it was ISSUED. A row proven committed before the
1226
+ * fetch went out must appear in its snapshot; one proven after may
1227
+ * legitimately be missing.
1228
+ */
1229
+ private serverRowsKnown;
1230
+ /**
1231
+ * Replace the message list, keeping `pendingUserMessages` an annotation on
1232
+ * it. Every removal from the array goes through here — `push` is the only
1233
+ * other mutation and it cannot orphan an id.
1234
+ */
1235
+ private setMessages;
1150
1236
  private accumulatedText;
1151
1237
  private currentTextPath;
1152
1238
  private handlers;
@@ -1205,9 +1291,15 @@ declare class ChatSession {
1205
1291
  * the pure wire → ChatEvent mapping can live in translate.ts and be reused
1206
1292
  * by the replay path.
1207
1293
  *
1208
- * ``messageId`` is the server-assigned assistant message id for the current
1209
- * turn; empty in the reconnect and conversation-switch replay paths where
1210
- * messages have already been loaded from REST and shouldn't be re-pushed.
1294
+ * ``promptMessageId`` is the id of the USER turn that started this job —
1295
+ * `POST /v1/jobs` returns it and the backend tags the prompt with it
1296
+ * (`HumanMessage(content=..., id=message_id)`), which is what lets a restore
1297
+ * pair a job with its prompt by id instead of by position. It was previously
1298
+ * documented here as the ASSISTANT's id and used as one; there is no
1299
+ * server-assigned assistant id on the wire, so that row gets a local one.
1300
+ * Empty in the reconnect and conversation-switch replay paths, where the
1301
+ * messages have already been loaded from REST and must not be re-pushed —
1302
+ * so it doubles as the "is this a live send?" gate.
1211
1303
  */
1212
1304
  private applyWireSideEffects;
1213
1305
  private executeClientTools;
@@ -1223,8 +1315,26 @@ declare class ChatSession {
1223
1315
  reconnectToJob(jobId: string): Promise<void>;
1224
1316
  /** Detach from the SSE stream without cancelling the job. */
1225
1317
  detach(): void;
1226
- /** Stop the job and disconnect (explicit user action). */
1318
+ /**
1319
+ * Cancel the running turn: stop the job server-side and tear the stream
1320
+ * down, WITHOUT ending the session. A turn-level cancel is not a
1321
+ * session-level teardown, so unlike `disconnect()` this leaves the protocol
1322
+ * registry alone — the SDK never auto-registers adapters, so clearing them
1323
+ * on a Stop press would silently kill embedded-resource rendering for the
1324
+ * rest of the session with nothing to re-register it.
1325
+ */
1326
+ cancelTurn(): void;
1327
+ /** Stop the job and end the session's activity (explicit user action). */
1227
1328
  disconnect(): void;
1329
+ /**
1330
+ * A pointer move happened above this layer, so any `loadConversation` in
1331
+ * flight must lose to it. `StreamManager` owns a pointer of its own and
1332
+ * moves it before this one; without this the two halves would gate on
1333
+ * counters that bump at different instants — `generation` synchronously in
1334
+ * `setActiveConversation`, `loadGeneration` only once the switch's own load
1335
+ * actually runs, which is behind the active-job probe.
1336
+ */
1337
+ invalidateLoadsInFlight(): void;
1228
1338
  createNewConversation(): Promise<string>;
1229
1339
  /**
1230
1340
  * Replay one completed turn's already-fetched events, synchronously.
@@ -1401,6 +1511,12 @@ declare class StreamManager {
1401
1511
  private _backgroundJobs;
1402
1512
  private handlers;
1403
1513
  private unsub;
1514
+ /**
1515
+ * Bumped every time the active conversation moves. An async sequence that
1516
+ * captures it can then tell, at each await boundary, whether it is still the
1517
+ * one the user is waiting on — see ``restore``.
1518
+ */
1519
+ private generation;
1404
1520
  constructor(session: ChatSession);
1405
1521
  get state(): StreamState;
1406
1522
  get activeConversationId(): string | null;
@@ -1432,6 +1548,15 @@ declare class StreamManager {
1432
1548
  switchTo(conversationId: string, opts?: {
1433
1549
  skipHistoryReplay?: boolean;
1434
1550
  }): Promise<void>;
1551
+ /**
1552
+ * Create a conversation and make it active.
1553
+ *
1554
+ * The returned id is NOT guaranteed to be the active conversation: if a
1555
+ * switch lands inside the storage round-trip, this declines the pointer move
1556
+ * so the newer one wins, and no `conversationChanged` fires for the new id.
1557
+ * A caller that routes on the return value should `switchTo(id)` rather than
1558
+ * assume it is current — that call is not a no-op in the declined case.
1559
+ */
1435
1560
  createConversation(): Promise<string>;
1436
1561
  /**
1437
1562
  * Rename a conversation. Purely a relabel — no active-conversation or
@@ -1441,6 +1566,31 @@ declare class StreamManager {
1441
1566
  deleteConversation(id: string): Promise<void>;
1442
1567
  stop(): void;
1443
1568
  destroy(): void;
1569
+ /**
1570
+ * Park a streaming turn as a background job and detach from its SSE stream.
1571
+ *
1572
+ * Every method that relocates the active conversation has to do this before
1573
+ * announcing a new state. Announcing `idle` while `session.isStreaming` is
1574
+ * still true is worse than announcing nothing: `manager.send` no longer bails
1575
+ * on the streaming state, calls `session.send`, and THAT bails on its own
1576
+ * `isStreaming` — so the message is never posted, no error is emitted, and
1577
+ * the composer looks ready the whole time.
1578
+ */
1579
+ private detachStreamingTurn;
1580
+ /**
1581
+ * Announce `idle` unless a turn is actually streaming.
1582
+ *
1583
+ * A `send` can land inside any of the switch paths — the fast path most
1584
+ * easily, since it deliberately stays out of `restoring` and so leaves the
1585
+ * composer live for the whole probe. `send` sets `streaming` and does not
1586
+ * bump the generation, so the path resumes, passes its supersession check,
1587
+ * and would announce a ready composer over a running stream. From there
1588
+ * `finalizeStream` and the `message_stop` branch both no-op (they only act
1589
+ * on `streaming`), so it stays `idle` for the whole turn — and the next send
1590
+ * reaches `session.send`, which bails on its own `isStreaming`: message
1591
+ * never posted, no error, composer ready throughout.
1592
+ */
1593
+ private settleIdle;
1444
1594
  private finalizeStream;
1445
1595
  private restore;
1446
1596
  private setActiveConversation;
package/dist/index.d.ts CHANGED
@@ -792,6 +792,24 @@ interface ConversationEvent {
792
792
  data: Record<string, unknown>;
793
793
  }
794
794
  interface SendOptions$1 extends ModelChoiceOptions {
795
+ /**
796
+ * Which conversation this turn belongs to. Defaults to the session's current
797
+ * one.
798
+ *
799
+ * ⚠️ BEHAVIOR CHANGE: passing this now RELOCATES the session. A value
800
+ * different from the current one sets `session.conversationId`, DISCARDS
801
+ * `session.messages` (the old conversation's list is not that conversation's
802
+ * history), and invalidates any `loadConversation` still in flight.
803
+ * Previously it addressed a single turn elsewhere and left the session where
804
+ * it was. After a successful send the list holds only that turn, so load the
805
+ * conversation if you need its history.
806
+ *
807
+ * The change is deliberate: `onSessionEvent` tags every emitted event with
808
+ * `session.conversationId`, so a turn sent elsewhere without moving the
809
+ * pointer streamed its events back under the wrong conversation. Addressing
810
+ * one turn away from the session is not something the session can honestly
811
+ * represent while it has a single pointer, so the send now moves it.
812
+ */
795
813
  conversationId?: string;
796
814
  enabledClientTools?: string[];
797
815
  uploadIds?: string[];
@@ -865,6 +883,20 @@ interface ConversationAsset {
865
883
  * failed or the asset has no stored object.
866
884
  */
867
885
  url?: string;
886
+ /**
887
+ * The asset's PERMANENT address. Authorization is resolved per request
888
+ * against the caller's live session, so unlike `url` it never expires — but
889
+ * it needs an `Authorization` header, which a browser will not attach to an
890
+ * `<img src>`. Store and link this one; render from `url`.
891
+ */
892
+ contentUrl?: string;
893
+ /**
894
+ * A still to show for an asset the browser cannot draw from `url` alone.
895
+ * Present only for video: an `<img src>` pointed at an mp4 renders nothing,
896
+ * so a video row would otherwise have no thumbnail. Signed and expiring
897
+ * exactly like `url` — display, not identity, so never store it.
898
+ */
899
+ posterUrl?: string;
868
900
  createdAt: string;
869
901
  }
870
902
 
@@ -1115,6 +1147,17 @@ declare class ChatSession {
1115
1147
  /** True while ``loadMoreConversations`` is in flight. */
1116
1148
  isLoadingConversations: boolean;
1117
1149
  messages: Message[];
1150
+ /**
1151
+ * Which conversation ``messages`` currently holds.
1152
+ *
1153
+ * Distinct from ``conversationId``, and the distinction is the point:
1154
+ * ``loadConversation`` moves the POINTER synchronously and installs the LIST
1155
+ * an await later, so for the whole duration of every load the two disagree.
1156
+ * Anything pairing a message with a conversation — ``regenerate`` above all —
1157
+ * has to read this one, or it will pair the previous conversation's last
1158
+ * message with the new conversation's id.
1159
+ */
1160
+ messagesConversationId: string | null;
1118
1161
  isStreaming: boolean;
1119
1162
  agentStatus: AgentStatus | null;
1120
1163
  agents: AgentInfo[];
@@ -1147,6 +1190,49 @@ declare class ChatSession {
1147
1190
  * is discarded instead.
1148
1191
  */
1149
1192
  private conversationsGeneration;
1193
+ /**
1194
+ * Bumped by every ``loadConversation`` call, so an out-of-order fetch can
1195
+ * tell it is no longer the newest one and drop its result. Separate from
1196
+ * ``conversationsGeneration``, which guards the conversation LIST.
1197
+ */
1198
+ private loadGeneration;
1199
+ /**
1200
+ * Ids of locally-created user messages the server has not acknowledged yet.
1201
+ *
1202
+ * Arrival time cannot answer "could the reply have included this?" on its
1203
+ * own. Every `loadConversation` in `StreamManager` sits behind the
1204
+ * active-job probe, so the ORDINARY ordering is that a send lands BEFORE the
1205
+ * load starts — and an arrival-time rule drops exactly those, losing the
1206
+ * prompt the user just sent while its stream is still running. Membership
1207
+ * here is set by `send` and cleared when a server row turns up carrying the
1208
+ * same turn, so the keep-decision no longer depends on which side of the
1209
+ * fetch the push landed on.
1210
+ *
1211
+ * The value is `serverRowsKnown` as of the `message_stop` that proved the
1212
+ * row committed, or 0 until then — including after the job response, which
1213
+ * hands back the id but starts the loop as a background task and so proves
1214
+ * nothing about the row. That stamp is what separates "the snapshot predates
1215
+ * the row" from "the server does not have this row" — see
1216
+ * `loadConversation`.
1217
+ *
1218
+ * It is an ANNOTATION ON `this.messages`: reconciliation only ever consults
1219
+ * entries of that array, so an id whose message has left it is dead weight.
1220
+ * `setMessages` is the single place the array is replaced, and it prunes.
1221
+ */
1222
+ private pendingUserMessages;
1223
+ /**
1224
+ * Bumped once per completed turn, at `message_stop`, so a fetch can record
1225
+ * what was proven when it was ISSUED. A row proven committed before the
1226
+ * fetch went out must appear in its snapshot; one proven after may
1227
+ * legitimately be missing.
1228
+ */
1229
+ private serverRowsKnown;
1230
+ /**
1231
+ * Replace the message list, keeping `pendingUserMessages` an annotation on
1232
+ * it. Every removal from the array goes through here — `push` is the only
1233
+ * other mutation and it cannot orphan an id.
1234
+ */
1235
+ private setMessages;
1150
1236
  private accumulatedText;
1151
1237
  private currentTextPath;
1152
1238
  private handlers;
@@ -1205,9 +1291,15 @@ declare class ChatSession {
1205
1291
  * the pure wire → ChatEvent mapping can live in translate.ts and be reused
1206
1292
  * by the replay path.
1207
1293
  *
1208
- * ``messageId`` is the server-assigned assistant message id for the current
1209
- * turn; empty in the reconnect and conversation-switch replay paths where
1210
- * messages have already been loaded from REST and shouldn't be re-pushed.
1294
+ * ``promptMessageId`` is the id of the USER turn that started this job —
1295
+ * `POST /v1/jobs` returns it and the backend tags the prompt with it
1296
+ * (`HumanMessage(content=..., id=message_id)`), which is what lets a restore
1297
+ * pair a job with its prompt by id instead of by position. It was previously
1298
+ * documented here as the ASSISTANT's id and used as one; there is no
1299
+ * server-assigned assistant id on the wire, so that row gets a local one.
1300
+ * Empty in the reconnect and conversation-switch replay paths, where the
1301
+ * messages have already been loaded from REST and must not be re-pushed —
1302
+ * so it doubles as the "is this a live send?" gate.
1211
1303
  */
1212
1304
  private applyWireSideEffects;
1213
1305
  private executeClientTools;
@@ -1223,8 +1315,26 @@ declare class ChatSession {
1223
1315
  reconnectToJob(jobId: string): Promise<void>;
1224
1316
  /** Detach from the SSE stream without cancelling the job. */
1225
1317
  detach(): void;
1226
- /** Stop the job and disconnect (explicit user action). */
1318
+ /**
1319
+ * Cancel the running turn: stop the job server-side and tear the stream
1320
+ * down, WITHOUT ending the session. A turn-level cancel is not a
1321
+ * session-level teardown, so unlike `disconnect()` this leaves the protocol
1322
+ * registry alone — the SDK never auto-registers adapters, so clearing them
1323
+ * on a Stop press would silently kill embedded-resource rendering for the
1324
+ * rest of the session with nothing to re-register it.
1325
+ */
1326
+ cancelTurn(): void;
1327
+ /** Stop the job and end the session's activity (explicit user action). */
1227
1328
  disconnect(): void;
1329
+ /**
1330
+ * A pointer move happened above this layer, so any `loadConversation` in
1331
+ * flight must lose to it. `StreamManager` owns a pointer of its own and
1332
+ * moves it before this one; without this the two halves would gate on
1333
+ * counters that bump at different instants — `generation` synchronously in
1334
+ * `setActiveConversation`, `loadGeneration` only once the switch's own load
1335
+ * actually runs, which is behind the active-job probe.
1336
+ */
1337
+ invalidateLoadsInFlight(): void;
1228
1338
  createNewConversation(): Promise<string>;
1229
1339
  /**
1230
1340
  * Replay one completed turn's already-fetched events, synchronously.
@@ -1401,6 +1511,12 @@ declare class StreamManager {
1401
1511
  private _backgroundJobs;
1402
1512
  private handlers;
1403
1513
  private unsub;
1514
+ /**
1515
+ * Bumped every time the active conversation moves. An async sequence that
1516
+ * captures it can then tell, at each await boundary, whether it is still the
1517
+ * one the user is waiting on — see ``restore``.
1518
+ */
1519
+ private generation;
1404
1520
  constructor(session: ChatSession);
1405
1521
  get state(): StreamState;
1406
1522
  get activeConversationId(): string | null;
@@ -1432,6 +1548,15 @@ declare class StreamManager {
1432
1548
  switchTo(conversationId: string, opts?: {
1433
1549
  skipHistoryReplay?: boolean;
1434
1550
  }): Promise<void>;
1551
+ /**
1552
+ * Create a conversation and make it active.
1553
+ *
1554
+ * The returned id is NOT guaranteed to be the active conversation: if a
1555
+ * switch lands inside the storage round-trip, this declines the pointer move
1556
+ * so the newer one wins, and no `conversationChanged` fires for the new id.
1557
+ * A caller that routes on the return value should `switchTo(id)` rather than
1558
+ * assume it is current — that call is not a no-op in the declined case.
1559
+ */
1435
1560
  createConversation(): Promise<string>;
1436
1561
  /**
1437
1562
  * Rename a conversation. Purely a relabel — no active-conversation or
@@ -1441,6 +1566,31 @@ declare class StreamManager {
1441
1566
  deleteConversation(id: string): Promise<void>;
1442
1567
  stop(): void;
1443
1568
  destroy(): void;
1569
+ /**
1570
+ * Park a streaming turn as a background job and detach from its SSE stream.
1571
+ *
1572
+ * Every method that relocates the active conversation has to do this before
1573
+ * announcing a new state. Announcing `idle` while `session.isStreaming` is
1574
+ * still true is worse than announcing nothing: `manager.send` no longer bails
1575
+ * on the streaming state, calls `session.send`, and THAT bails on its own
1576
+ * `isStreaming` — so the message is never posted, no error is emitted, and
1577
+ * the composer looks ready the whole time.
1578
+ */
1579
+ private detachStreamingTurn;
1580
+ /**
1581
+ * Announce `idle` unless a turn is actually streaming.
1582
+ *
1583
+ * A `send` can land inside any of the switch paths — the fast path most
1584
+ * easily, since it deliberately stays out of `restoring` and so leaves the
1585
+ * composer live for the whole probe. `send` sets `streaming` and does not
1586
+ * bump the generation, so the path resumes, passes its supersession check,
1587
+ * and would announce a ready composer over a running stream. From there
1588
+ * `finalizeStream` and the `message_stop` branch both no-op (they only act
1589
+ * on `streaming`), so it stays `idle` for the whole turn — and the next send
1590
+ * reaches `session.send`, which bails on its own `isStreaming`: message
1591
+ * never posted, no error, composer ready throughout.
1592
+ */
1593
+ private settleIdle;
1444
1594
  private finalizeStream;
1445
1595
  private restore;
1446
1596
  private setActiveConversation;