@astralform/js 4.1.0 → 4.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/README.md +6 -0
- package/dist/index.cjs +156 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -2
- package/dist/index.d.ts +110 -2
- package/dist/index.js +155 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -120,6 +120,19 @@ interface AstralformBaseConfig {
|
|
|
120
120
|
baseURL?: string;
|
|
121
121
|
/** Supply a custom fetch (SSR, testing, custom interceptors). */
|
|
122
122
|
fetch?: typeof globalThis.fetch;
|
|
123
|
+
/**
|
|
124
|
+
* Abort a REST request — connect, headers, AND body read — after this many
|
|
125
|
+
* milliseconds. Defaults to 30_000. Does not apply to `uploadFile` (large
|
|
126
|
+
* files on slow uplinks) or to SSE streaming, which is long-lived by design
|
|
127
|
+
* and carries its own `AbortSignal`.
|
|
128
|
+
*
|
|
129
|
+
* Note that unlike axios and friends, `0` does NOT mean "no timeout": any
|
|
130
|
+
* non-positive or non-finite value falls back to the default. REST calls
|
|
131
|
+
* cannot opt out of the deadline — an unbounded REST request is the bug
|
|
132
|
+
* this exists to prevent, and it fails silently (a stalled body strands the
|
|
133
|
+
* caller forever with nothing to react to).
|
|
134
|
+
*/
|
|
135
|
+
timeoutMs?: number;
|
|
123
136
|
}
|
|
124
137
|
interface AstralformApiKeyConfig extends AstralformBaseConfig {
|
|
125
138
|
/** Agent API key (`sk_live_...` or `sk_test_...`). */
|
|
@@ -773,6 +786,7 @@ interface ConversationAsset {
|
|
|
773
786
|
declare class AstralformClient {
|
|
774
787
|
private readonly baseURL;
|
|
775
788
|
private readonly fetchFn;
|
|
789
|
+
private readonly timeoutMs;
|
|
776
790
|
/**
|
|
777
791
|
* Auth state is mutable so callers can rotate access tokens or switch
|
|
778
792
|
* agent context without re-instantiating the client. API-key mode is
|
|
@@ -782,7 +796,7 @@ declare class AstralformClient {
|
|
|
782
796
|
constructor(config: AstralformConfig);
|
|
783
797
|
/**
|
|
784
798
|
* Replace the current OIDC access token without reconstructing the client.
|
|
785
|
-
* Use after refreshing via the host's token manager
|
|
799
|
+
* Use after refreshing via the host app's own token manager.
|
|
786
800
|
* Throws if the client was created in API-key mode.
|
|
787
801
|
*/
|
|
788
802
|
updateAccessToken(accessToken: string): void;
|
|
@@ -819,7 +833,23 @@ declare class AstralformClient {
|
|
|
819
833
|
*/
|
|
820
834
|
private get authHeaders();
|
|
821
835
|
private get headers();
|
|
836
|
+
/**
|
|
837
|
+
* Run one REST exchange under a single deadline covering connect, headers,
|
|
838
|
+
* AND the body read. The body read is the part that matters: `json()` used
|
|
839
|
+
* to sit outside every guard, so a response whose headers arrived but whose
|
|
840
|
+
* body stalled hung forever — silently stranding callers that await it
|
|
841
|
+
* (a stalled `getMessages` used to leave `StreamManager.restore()` parked
|
|
842
|
+
* before it ever fetched the events it renders from).
|
|
843
|
+
*
|
|
844
|
+
* The controller is created per request and is deliberately NOT the
|
|
845
|
+
* session's — that one means "the user cancelled this turn" and is null
|
|
846
|
+
* outside a live turn. Aborting frees the socket; the race guarantees a
|
|
847
|
+
* rejection even when an injected `fetch` ignores the signal.
|
|
848
|
+
*/
|
|
849
|
+
private withDeadline;
|
|
822
850
|
private request;
|
|
851
|
+
/** Fetch + status handling. Always called inside `withDeadline`. */
|
|
852
|
+
private send;
|
|
823
853
|
get<T>(path: string): Promise<T>;
|
|
824
854
|
post<T>(path: string, body: unknown): Promise<T>;
|
|
825
855
|
private del;
|
|
@@ -950,6 +980,13 @@ declare class ToolRegistry {
|
|
|
950
980
|
}
|
|
951
981
|
|
|
952
982
|
type ChatEventHandler = (event: ChatEvent) => void;
|
|
983
|
+
/**
|
|
984
|
+
* Conversations fetched per page, by ``connect`` and ``loadMoreConversations``
|
|
985
|
+
* alike. The two must use the same size: the offset is derived from how many
|
|
986
|
+
* rows the server has returned so far, so a first page of a different size
|
|
987
|
+
* would leave the second page's offset pointing at the wrong row.
|
|
988
|
+
*/
|
|
989
|
+
declare const CONVERSATION_PAGE_SIZE = 50;
|
|
953
990
|
/**
|
|
954
991
|
* ChatSession — translates the backend wire protocol into typed ChatEvents
|
|
955
992
|
* for consumers. Owns HTTP + SSE plumbing, conversation state, and the
|
|
@@ -970,6 +1007,17 @@ declare class ChatSession {
|
|
|
970
1007
|
readonly protocols: ProtocolRegistry<ProtocolAdapter>;
|
|
971
1008
|
conversationId: string | null;
|
|
972
1009
|
conversations: Conversation[];
|
|
1010
|
+
/**
|
|
1011
|
+
* Whether another page of conversations may exist on the server.
|
|
1012
|
+
*
|
|
1013
|
+
* Inferred from the last page being full, since the list endpoint returns a
|
|
1014
|
+
* bare array with no total. A total that happens to be an exact multiple of
|
|
1015
|
+
* the page size therefore costs one extra empty request before this flips —
|
|
1016
|
+
* cheaper than adding a count query to every list call.
|
|
1017
|
+
*/
|
|
1018
|
+
hasMoreConversations: boolean;
|
|
1019
|
+
/** True while ``loadMoreConversations`` is in flight. */
|
|
1020
|
+
isLoadingConversations: boolean;
|
|
973
1021
|
messages: Message[];
|
|
974
1022
|
isStreaming: boolean;
|
|
975
1023
|
agentStatus: AgentStatus | null;
|
|
@@ -977,6 +1025,32 @@ declare class ChatSession {
|
|
|
977
1025
|
skills: SkillInfo[];
|
|
978
1026
|
enabledClientTools: Set<string>;
|
|
979
1027
|
modelDisplayName: string | null;
|
|
1028
|
+
/**
|
|
1029
|
+
* Ids of conversations the SERVER has handed us, which is the paging offset.
|
|
1030
|
+
*
|
|
1031
|
+
* Deliberately not ``conversations.length``. That array also holds
|
|
1032
|
+
* conversations created locally and unshifted on top (``createNewConversation``,
|
|
1033
|
+
* and the auto-created conversation in ``consumeJobStream``), so using its
|
|
1034
|
+
* length as the offset would over-count and silently skip a row of real
|
|
1035
|
+
* history on the next page. Tracking ids rather than a counter also makes
|
|
1036
|
+
* deletion self-correcting: removing a server-sourced conversation shifts
|
|
1037
|
+
* every later page up by one, and dropping its id from this set is exactly
|
|
1038
|
+
* that shift — while deleting a purely local one correctly changes nothing.
|
|
1039
|
+
*/
|
|
1040
|
+
private serverConversationIds;
|
|
1041
|
+
/**
|
|
1042
|
+
* Bumped every time ``connect()`` re-seeds the conversation list.
|
|
1043
|
+
*
|
|
1044
|
+
* A ``loadMoreConversations`` request issued before a re-seed describes the
|
|
1045
|
+
* OLD paging state, so applying its response afterwards both appends the
|
|
1046
|
+
* wrong rows and corrupts the offset. Concretely: with 100 rows held, an
|
|
1047
|
+
* offset-100 response landing after a reconnect has reset to rows 0-49 would
|
|
1048
|
+
* append rows 100-149 — a 50-row hole — and leave the id set at 100, so every
|
|
1049
|
+
* later page re-requests offset 100 and never advances again. The generation
|
|
1050
|
+
* is captured before the await and rechecked after, so a superseded response
|
|
1051
|
+
* is discarded instead.
|
|
1052
|
+
*/
|
|
1053
|
+
private conversationsGeneration;
|
|
980
1054
|
private accumulatedText;
|
|
981
1055
|
private currentTextPath;
|
|
982
1056
|
private handlers;
|
|
@@ -1078,6 +1152,40 @@ declare class ChatSession {
|
|
|
1078
1152
|
* job's events.
|
|
1079
1153
|
*/
|
|
1080
1154
|
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1155
|
+
/**
|
|
1156
|
+
* Append the next page of conversation history to ``conversations``.
|
|
1157
|
+
*
|
|
1158
|
+
* The list is ordered ``updated_at DESC`` and paged by offset, so a
|
|
1159
|
+
* conversation bumped to the top mid-scroll can surface again in a later
|
|
1160
|
+
* page; ids already held are dropped rather than duplicated. Returns only
|
|
1161
|
+
* the conversations actually appended, which may be empty even on a full
|
|
1162
|
+
* page. Rejects on network failure with ``hasMoreConversations`` still true,
|
|
1163
|
+
* so the caller can retry.
|
|
1164
|
+
*
|
|
1165
|
+
* KNOWN LIMITATION — offset paging is only stable while the prefix already
|
|
1166
|
+
* consumed stays put. The offset tracking here corrects for perturbations
|
|
1167
|
+
* THIS session causes (local unshifts, ``deleteConversation``), but not for
|
|
1168
|
+
* ones it never sees:
|
|
1169
|
+
*
|
|
1170
|
+
* - a conversation this session hasn't loaded yet is bumped to the top (a
|
|
1171
|
+
* headless routine or another device posting to it), pushing the whole
|
|
1172
|
+
* list down — it lands inside the consumed prefix, which no later offset
|
|
1173
|
+
* revisits;
|
|
1174
|
+
* - a conversation is deleted from another tab/device, shrinking the list so
|
|
1175
|
+
* the next offset lands one row too far in.
|
|
1176
|
+
*
|
|
1177
|
+
* Each perturbation costs at most one conversation off the sidebar, and only
|
|
1178
|
+
* until the next ``connect()`` — that re-seeds page 1 and resets the paging
|
|
1179
|
+
* state, so a reload or reconnect always recovers it. Nothing is lost
|
|
1180
|
+
* server-side. Both cases are pinned by tests in
|
|
1181
|
+
* ``tests/conversation-paging.test.ts``.
|
|
1182
|
+
*
|
|
1183
|
+
* Closing the gap properly needs a stable server cursor (keyset paging on
|
|
1184
|
+
* ``(updated_at, id)``) rather than a raw offset, which is a backend change —
|
|
1185
|
+
* tracking ids client-side cannot discover a row that moved into a region
|
|
1186
|
+
* already scanned.
|
|
1187
|
+
*/
|
|
1188
|
+
loadMoreConversations(): Promise<Conversation[]>;
|
|
1081
1189
|
deleteConversation(id: string): Promise<void>;
|
|
1082
1190
|
toggleClientTool(name: string): boolean;
|
|
1083
1191
|
}
|
|
@@ -1310,4 +1418,4 @@ declare function isEmbeddedResource(value: unknown): value is {
|
|
|
1310
1418
|
*/
|
|
1311
1419
|
declare function parseEmbeddedResource(value: unknown): EmbeddedResource | null;
|
|
1312
1420
|
|
|
1313
|
-
export { type ActiveJob, type AgentIdentity, type AgentInfo, type AgentStatus, type AssetCreatedPayload, type AstralformApiKeyConfig, AstralformClient, type AstralformConfig, AstralformError, type AstralformUserTokenConfig, type AttachmentStagedPayload, AuthenticationError, type BlockDeltaPayload, type ChatEvent, ChatEventType, type ChatEventTypeValue, ChatSession, type ChatStorage, type ChatStreamEvent, type ChatStreamRequest, ConnectionError, type ContextUpdatePayload, type ContextWarningPayload, type Conversation, type ConversationAsset, type ConversationEvent, type DesktopStreamPayload, type EmbeddedResource, type FeedbackRequest, type FeedbackResponse, InMemoryStorage, type JobCreateResponse, type JobStatus, type JobSummary, LLMNotConfiguredError, type MemoryRecallPayload, type MemoryRecord, type MemoryUpdatePayload, type Message, type ModelChoiceOptions, type ModelOption, type MyToolGrantsPage, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type ReasoningEffort, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamAgentSummary, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, type ToolGrant, type ToolHandler, type ToolHarnessWarningPayload, type ToolPermissionDeniedPayload, ToolRegistry, type ToolResult, type ToolResultRequest, type TurnUsage, type UIComponentsConfig, type UserUnavailablePayload, type WireBlockDelta, type WireBlockDeltaPayload, type WireBlockKind, type WireBlockStart, type WireBlockStatus, type WireBlockStop, type WireCustomEvent, type WireErrorEvent, type WireEvent, type WireInputArgDelta, type WireInputDelta, type WireKeepalive, type WireMessageStart, type WireMessageStop, type WireOutputDelta, type WireRetryEvent, type WireSignatureDelta, type WireStallWarning, type WireStatusDelta, type WireStopReason, type WireTextDelta, type WireThinkingDelta, type WorkspaceReadyPayload, generateId, isEmbeddedResource, mapSseToChat, parseEmbeddedResource, replayEvents, streamJobSSE, translateDelta };
|
|
1421
|
+
export { type ActiveJob, type AgentIdentity, type AgentInfo, type AgentStatus, type AssetCreatedPayload, type AstralformApiKeyConfig, AstralformClient, type AstralformConfig, AstralformError, type AstralformUserTokenConfig, type AttachmentStagedPayload, AuthenticationError, type BlockDeltaPayload, CONVERSATION_PAGE_SIZE, type ChatEvent, ChatEventType, type ChatEventTypeValue, ChatSession, type ChatStorage, type ChatStreamEvent, type ChatStreamRequest, ConnectionError, type ContextUpdatePayload, type ContextWarningPayload, type Conversation, type ConversationAsset, type ConversationEvent, type DesktopStreamPayload, type EmbeddedResource, type FeedbackRequest, type FeedbackResponse, InMemoryStorage, type JobCreateResponse, type JobStatus, type JobSummary, LLMNotConfiguredError, type MemoryRecallPayload, type MemoryRecord, type MemoryUpdatePayload, type Message, type ModelChoiceOptions, type ModelOption, type MyToolGrantsPage, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type ReasoningEffort, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamAgentSummary, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, type ToolGrant, type ToolHandler, type ToolHarnessWarningPayload, type ToolPermissionDeniedPayload, ToolRegistry, type ToolResult, type ToolResultRequest, type TurnUsage, type UIComponentsConfig, type UserUnavailablePayload, type WireBlockDelta, type WireBlockDeltaPayload, type WireBlockKind, type WireBlockStart, type WireBlockStatus, type WireBlockStop, type WireCustomEvent, type WireErrorEvent, type WireEvent, type WireInputArgDelta, type WireInputDelta, type WireKeepalive, type WireMessageStart, type WireMessageStop, type WireOutputDelta, type WireRetryEvent, type WireSignatureDelta, type WireStallWarning, type WireStatusDelta, type WireStopReason, type WireTextDelta, type WireThinkingDelta, type WorkspaceReadyPayload, generateId, isEmbeddedResource, mapSseToChat, parseEmbeddedResource, replayEvents, streamJobSSE, translateDelta };
|
package/dist/index.d.ts
CHANGED
|
@@ -120,6 +120,19 @@ interface AstralformBaseConfig {
|
|
|
120
120
|
baseURL?: string;
|
|
121
121
|
/** Supply a custom fetch (SSR, testing, custom interceptors). */
|
|
122
122
|
fetch?: typeof globalThis.fetch;
|
|
123
|
+
/**
|
|
124
|
+
* Abort a REST request — connect, headers, AND body read — after this many
|
|
125
|
+
* milliseconds. Defaults to 30_000. Does not apply to `uploadFile` (large
|
|
126
|
+
* files on slow uplinks) or to SSE streaming, which is long-lived by design
|
|
127
|
+
* and carries its own `AbortSignal`.
|
|
128
|
+
*
|
|
129
|
+
* Note that unlike axios and friends, `0` does NOT mean "no timeout": any
|
|
130
|
+
* non-positive or non-finite value falls back to the default. REST calls
|
|
131
|
+
* cannot opt out of the deadline — an unbounded REST request is the bug
|
|
132
|
+
* this exists to prevent, and it fails silently (a stalled body strands the
|
|
133
|
+
* caller forever with nothing to react to).
|
|
134
|
+
*/
|
|
135
|
+
timeoutMs?: number;
|
|
123
136
|
}
|
|
124
137
|
interface AstralformApiKeyConfig extends AstralformBaseConfig {
|
|
125
138
|
/** Agent API key (`sk_live_...` or `sk_test_...`). */
|
|
@@ -773,6 +786,7 @@ interface ConversationAsset {
|
|
|
773
786
|
declare class AstralformClient {
|
|
774
787
|
private readonly baseURL;
|
|
775
788
|
private readonly fetchFn;
|
|
789
|
+
private readonly timeoutMs;
|
|
776
790
|
/**
|
|
777
791
|
* Auth state is mutable so callers can rotate access tokens or switch
|
|
778
792
|
* agent context without re-instantiating the client. API-key mode is
|
|
@@ -782,7 +796,7 @@ declare class AstralformClient {
|
|
|
782
796
|
constructor(config: AstralformConfig);
|
|
783
797
|
/**
|
|
784
798
|
* Replace the current OIDC access token without reconstructing the client.
|
|
785
|
-
* Use after refreshing via the host's token manager
|
|
799
|
+
* Use after refreshing via the host app's own token manager.
|
|
786
800
|
* Throws if the client was created in API-key mode.
|
|
787
801
|
*/
|
|
788
802
|
updateAccessToken(accessToken: string): void;
|
|
@@ -819,7 +833,23 @@ declare class AstralformClient {
|
|
|
819
833
|
*/
|
|
820
834
|
private get authHeaders();
|
|
821
835
|
private get headers();
|
|
836
|
+
/**
|
|
837
|
+
* Run one REST exchange under a single deadline covering connect, headers,
|
|
838
|
+
* AND the body read. The body read is the part that matters: `json()` used
|
|
839
|
+
* to sit outside every guard, so a response whose headers arrived but whose
|
|
840
|
+
* body stalled hung forever — silently stranding callers that await it
|
|
841
|
+
* (a stalled `getMessages` used to leave `StreamManager.restore()` parked
|
|
842
|
+
* before it ever fetched the events it renders from).
|
|
843
|
+
*
|
|
844
|
+
* The controller is created per request and is deliberately NOT the
|
|
845
|
+
* session's — that one means "the user cancelled this turn" and is null
|
|
846
|
+
* outside a live turn. Aborting frees the socket; the race guarantees a
|
|
847
|
+
* rejection even when an injected `fetch` ignores the signal.
|
|
848
|
+
*/
|
|
849
|
+
private withDeadline;
|
|
822
850
|
private request;
|
|
851
|
+
/** Fetch + status handling. Always called inside `withDeadline`. */
|
|
852
|
+
private send;
|
|
823
853
|
get<T>(path: string): Promise<T>;
|
|
824
854
|
post<T>(path: string, body: unknown): Promise<T>;
|
|
825
855
|
private del;
|
|
@@ -950,6 +980,13 @@ declare class ToolRegistry {
|
|
|
950
980
|
}
|
|
951
981
|
|
|
952
982
|
type ChatEventHandler = (event: ChatEvent) => void;
|
|
983
|
+
/**
|
|
984
|
+
* Conversations fetched per page, by ``connect`` and ``loadMoreConversations``
|
|
985
|
+
* alike. The two must use the same size: the offset is derived from how many
|
|
986
|
+
* rows the server has returned so far, so a first page of a different size
|
|
987
|
+
* would leave the second page's offset pointing at the wrong row.
|
|
988
|
+
*/
|
|
989
|
+
declare const CONVERSATION_PAGE_SIZE = 50;
|
|
953
990
|
/**
|
|
954
991
|
* ChatSession — translates the backend wire protocol into typed ChatEvents
|
|
955
992
|
* for consumers. Owns HTTP + SSE plumbing, conversation state, and the
|
|
@@ -970,6 +1007,17 @@ declare class ChatSession {
|
|
|
970
1007
|
readonly protocols: ProtocolRegistry<ProtocolAdapter>;
|
|
971
1008
|
conversationId: string | null;
|
|
972
1009
|
conversations: Conversation[];
|
|
1010
|
+
/**
|
|
1011
|
+
* Whether another page of conversations may exist on the server.
|
|
1012
|
+
*
|
|
1013
|
+
* Inferred from the last page being full, since the list endpoint returns a
|
|
1014
|
+
* bare array with no total. A total that happens to be an exact multiple of
|
|
1015
|
+
* the page size therefore costs one extra empty request before this flips —
|
|
1016
|
+
* cheaper than adding a count query to every list call.
|
|
1017
|
+
*/
|
|
1018
|
+
hasMoreConversations: boolean;
|
|
1019
|
+
/** True while ``loadMoreConversations`` is in flight. */
|
|
1020
|
+
isLoadingConversations: boolean;
|
|
973
1021
|
messages: Message[];
|
|
974
1022
|
isStreaming: boolean;
|
|
975
1023
|
agentStatus: AgentStatus | null;
|
|
@@ -977,6 +1025,32 @@ declare class ChatSession {
|
|
|
977
1025
|
skills: SkillInfo[];
|
|
978
1026
|
enabledClientTools: Set<string>;
|
|
979
1027
|
modelDisplayName: string | null;
|
|
1028
|
+
/**
|
|
1029
|
+
* Ids of conversations the SERVER has handed us, which is the paging offset.
|
|
1030
|
+
*
|
|
1031
|
+
* Deliberately not ``conversations.length``. That array also holds
|
|
1032
|
+
* conversations created locally and unshifted on top (``createNewConversation``,
|
|
1033
|
+
* and the auto-created conversation in ``consumeJobStream``), so using its
|
|
1034
|
+
* length as the offset would over-count and silently skip a row of real
|
|
1035
|
+
* history on the next page. Tracking ids rather than a counter also makes
|
|
1036
|
+
* deletion self-correcting: removing a server-sourced conversation shifts
|
|
1037
|
+
* every later page up by one, and dropping its id from this set is exactly
|
|
1038
|
+
* that shift — while deleting a purely local one correctly changes nothing.
|
|
1039
|
+
*/
|
|
1040
|
+
private serverConversationIds;
|
|
1041
|
+
/**
|
|
1042
|
+
* Bumped every time ``connect()`` re-seeds the conversation list.
|
|
1043
|
+
*
|
|
1044
|
+
* A ``loadMoreConversations`` request issued before a re-seed describes the
|
|
1045
|
+
* OLD paging state, so applying its response afterwards both appends the
|
|
1046
|
+
* wrong rows and corrupts the offset. Concretely: with 100 rows held, an
|
|
1047
|
+
* offset-100 response landing after a reconnect has reset to rows 0-49 would
|
|
1048
|
+
* append rows 100-149 — a 50-row hole — and leave the id set at 100, so every
|
|
1049
|
+
* later page re-requests offset 100 and never advances again. The generation
|
|
1050
|
+
* is captured before the await and rechecked after, so a superseded response
|
|
1051
|
+
* is discarded instead.
|
|
1052
|
+
*/
|
|
1053
|
+
private conversationsGeneration;
|
|
980
1054
|
private accumulatedText;
|
|
981
1055
|
private currentTextPath;
|
|
982
1056
|
private handlers;
|
|
@@ -1078,6 +1152,40 @@ declare class ChatSession {
|
|
|
1078
1152
|
* job's events.
|
|
1079
1153
|
*/
|
|
1080
1154
|
switchConversation(id: string, jobId?: string): Promise<void>;
|
|
1155
|
+
/**
|
|
1156
|
+
* Append the next page of conversation history to ``conversations``.
|
|
1157
|
+
*
|
|
1158
|
+
* The list is ordered ``updated_at DESC`` and paged by offset, so a
|
|
1159
|
+
* conversation bumped to the top mid-scroll can surface again in a later
|
|
1160
|
+
* page; ids already held are dropped rather than duplicated. Returns only
|
|
1161
|
+
* the conversations actually appended, which may be empty even on a full
|
|
1162
|
+
* page. Rejects on network failure with ``hasMoreConversations`` still true,
|
|
1163
|
+
* so the caller can retry.
|
|
1164
|
+
*
|
|
1165
|
+
* KNOWN LIMITATION — offset paging is only stable while the prefix already
|
|
1166
|
+
* consumed stays put. The offset tracking here corrects for perturbations
|
|
1167
|
+
* THIS session causes (local unshifts, ``deleteConversation``), but not for
|
|
1168
|
+
* ones it never sees:
|
|
1169
|
+
*
|
|
1170
|
+
* - a conversation this session hasn't loaded yet is bumped to the top (a
|
|
1171
|
+
* headless routine or another device posting to it), pushing the whole
|
|
1172
|
+
* list down — it lands inside the consumed prefix, which no later offset
|
|
1173
|
+
* revisits;
|
|
1174
|
+
* - a conversation is deleted from another tab/device, shrinking the list so
|
|
1175
|
+
* the next offset lands one row too far in.
|
|
1176
|
+
*
|
|
1177
|
+
* Each perturbation costs at most one conversation off the sidebar, and only
|
|
1178
|
+
* until the next ``connect()`` — that re-seeds page 1 and resets the paging
|
|
1179
|
+
* state, so a reload or reconnect always recovers it. Nothing is lost
|
|
1180
|
+
* server-side. Both cases are pinned by tests in
|
|
1181
|
+
* ``tests/conversation-paging.test.ts``.
|
|
1182
|
+
*
|
|
1183
|
+
* Closing the gap properly needs a stable server cursor (keyset paging on
|
|
1184
|
+
* ``(updated_at, id)``) rather than a raw offset, which is a backend change —
|
|
1185
|
+
* tracking ids client-side cannot discover a row that moved into a region
|
|
1186
|
+
* already scanned.
|
|
1187
|
+
*/
|
|
1188
|
+
loadMoreConversations(): Promise<Conversation[]>;
|
|
1081
1189
|
deleteConversation(id: string): Promise<void>;
|
|
1082
1190
|
toggleClientTool(name: string): boolean;
|
|
1083
1191
|
}
|
|
@@ -1310,4 +1418,4 @@ declare function isEmbeddedResource(value: unknown): value is {
|
|
|
1310
1418
|
*/
|
|
1311
1419
|
declare function parseEmbeddedResource(value: unknown): EmbeddedResource | null;
|
|
1312
1420
|
|
|
1313
|
-
export { type ActiveJob, type AgentIdentity, type AgentInfo, type AgentStatus, type AssetCreatedPayload, type AstralformApiKeyConfig, AstralformClient, type AstralformConfig, AstralformError, type AstralformUserTokenConfig, type AttachmentStagedPayload, AuthenticationError, type BlockDeltaPayload, type ChatEvent, ChatEventType, type ChatEventTypeValue, ChatSession, type ChatStorage, type ChatStreamEvent, type ChatStreamRequest, ConnectionError, type ContextUpdatePayload, type ContextWarningPayload, type Conversation, type ConversationAsset, type ConversationEvent, type DesktopStreamPayload, type EmbeddedResource, type FeedbackRequest, type FeedbackResponse, InMemoryStorage, type JobCreateResponse, type JobStatus, type JobSummary, LLMNotConfiguredError, type MemoryRecallPayload, type MemoryRecord, type MemoryUpdatePayload, type Message, type ModelChoiceOptions, type ModelOption, type MyToolGrantsPage, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type ReasoningEffort, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamAgentSummary, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, type ToolGrant, type ToolHandler, type ToolHarnessWarningPayload, type ToolPermissionDeniedPayload, ToolRegistry, type ToolResult, type ToolResultRequest, type TurnUsage, type UIComponentsConfig, type UserUnavailablePayload, type WireBlockDelta, type WireBlockDeltaPayload, type WireBlockKind, type WireBlockStart, type WireBlockStatus, type WireBlockStop, type WireCustomEvent, type WireErrorEvent, type WireEvent, type WireInputArgDelta, type WireInputDelta, type WireKeepalive, type WireMessageStart, type WireMessageStop, type WireOutputDelta, type WireRetryEvent, type WireSignatureDelta, type WireStallWarning, type WireStatusDelta, type WireStopReason, type WireTextDelta, type WireThinkingDelta, type WorkspaceReadyPayload, generateId, isEmbeddedResource, mapSseToChat, parseEmbeddedResource, replayEvents, streamJobSSE, translateDelta };
|
|
1421
|
+
export { type ActiveJob, type AgentIdentity, type AgentInfo, type AgentStatus, type AssetCreatedPayload, type AstralformApiKeyConfig, AstralformClient, type AstralformConfig, AstralformError, type AstralformUserTokenConfig, type AttachmentStagedPayload, AuthenticationError, type BlockDeltaPayload, CONVERSATION_PAGE_SIZE, type ChatEvent, ChatEventType, type ChatEventTypeValue, ChatSession, type ChatStorage, type ChatStreamEvent, type ChatStreamRequest, ConnectionError, type ContextUpdatePayload, type ContextWarningPayload, type Conversation, type ConversationAsset, type ConversationEvent, type DesktopStreamPayload, type EmbeddedResource, type FeedbackRequest, type FeedbackResponse, InMemoryStorage, type JobCreateResponse, type JobStatus, type JobSummary, LLMNotConfiguredError, type MemoryRecallPayload, type MemoryRecord, type MemoryUpdatePayload, type Message, type ModelChoiceOptions, type ModelOption, type MyToolGrantsPage, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type ReasoningEffort, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamAgentSummary, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, type ToolGrant, type ToolHandler, type ToolHarnessWarningPayload, type ToolPermissionDeniedPayload, ToolRegistry, type ToolResult, type ToolResultRequest, type TurnUsage, type UIComponentsConfig, type UserUnavailablePayload, type WireBlockDelta, type WireBlockDeltaPayload, type WireBlockKind, type WireBlockStart, type WireBlockStatus, type WireBlockStop, type WireCustomEvent, type WireErrorEvent, type WireEvent, type WireInputArgDelta, type WireInputDelta, type WireKeepalive, type WireMessageStart, type WireMessageStop, type WireOutputDelta, type WireRetryEvent, type WireSignatureDelta, type WireStallWarning, type WireStatusDelta, type WireStopReason, type WireTextDelta, type WireThinkingDelta, type WorkspaceReadyPayload, generateId, isEmbeddedResource, mapSseToChat, parseEmbeddedResource, replayEvents, streamJobSSE, translateDelta };
|
package/dist/index.js
CHANGED
|
@@ -252,6 +252,7 @@ async function* streamJobSSE(options) {
|
|
|
252
252
|
|
|
253
253
|
// src/client.ts
|
|
254
254
|
var DEFAULT_BASE_URL = "https://api.astralform.ai";
|
|
255
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
255
256
|
function validateBaseURL(url) {
|
|
256
257
|
const cleaned = url.replace(/\/+$/, "");
|
|
257
258
|
try {
|
|
@@ -302,10 +303,11 @@ var AstralformClient = class {
|
|
|
302
303
|
}
|
|
303
304
|
this.baseURL = validateBaseURL(config.baseURL ?? DEFAULT_BASE_URL);
|
|
304
305
|
this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
306
|
+
this.timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
305
307
|
}
|
|
306
308
|
/**
|
|
307
309
|
* Replace the current OIDC access token without reconstructing the client.
|
|
308
|
-
* Use after refreshing via the host's token manager
|
|
310
|
+
* Use after refreshing via the host app's own token manager.
|
|
309
311
|
* Throws if the client was created in API-key mode.
|
|
310
312
|
*/
|
|
311
313
|
updateAccessToken(accessToken) {
|
|
@@ -392,11 +394,48 @@ var AstralformClient = class {
|
|
|
392
394
|
"Content-Type": "application/json"
|
|
393
395
|
};
|
|
394
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Run one REST exchange under a single deadline covering connect, headers,
|
|
399
|
+
* AND the body read. The body read is the part that matters: `json()` used
|
|
400
|
+
* to sit outside every guard, so a response whose headers arrived but whose
|
|
401
|
+
* body stalled hung forever — silently stranding callers that await it
|
|
402
|
+
* (a stalled `getMessages` used to leave `StreamManager.restore()` parked
|
|
403
|
+
* before it ever fetched the events it renders from).
|
|
404
|
+
*
|
|
405
|
+
* The controller is created per request and is deliberately NOT the
|
|
406
|
+
* session's — that one means "the user cancelled this turn" and is null
|
|
407
|
+
* outside a live turn. Aborting frees the socket; the race guarantees a
|
|
408
|
+
* rejection even when an injected `fetch` ignores the signal.
|
|
409
|
+
*/
|
|
410
|
+
async withDeadline(run) {
|
|
411
|
+
const controller = new AbortController();
|
|
412
|
+
const timedOut = () => new ConnectionError(`Request timed out after ${this.timeoutMs}ms`);
|
|
413
|
+
let timer;
|
|
414
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
415
|
+
timer = setTimeout(() => {
|
|
416
|
+
controller.abort();
|
|
417
|
+
reject(timedOut());
|
|
418
|
+
}, this.timeoutMs);
|
|
419
|
+
});
|
|
420
|
+
try {
|
|
421
|
+
return await Promise.race([run(controller.signal), deadline]);
|
|
422
|
+
} catch (err) {
|
|
423
|
+
if (controller.signal.aborted) throw timedOut();
|
|
424
|
+
throw err;
|
|
425
|
+
} finally {
|
|
426
|
+
clearTimeout(timer);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
395
429
|
async request(method, path, body) {
|
|
430
|
+
return this.withDeadline((signal) => this.send(method, path, body, signal));
|
|
431
|
+
}
|
|
432
|
+
/** Fetch + status handling. Always called inside `withDeadline`. */
|
|
433
|
+
async send(method, path, body, signal) {
|
|
396
434
|
const response = await this.fetchFn(`${this.baseURL}${path}`, {
|
|
397
435
|
method,
|
|
398
436
|
headers: this.headers,
|
|
399
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
437
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
438
|
+
signal
|
|
400
439
|
}).catch((err) => {
|
|
401
440
|
throw new ConnectionError(
|
|
402
441
|
err instanceof Error ? err.message : "Failed to connect"
|
|
@@ -405,13 +444,21 @@ var AstralformClient = class {
|
|
|
405
444
|
await this.handleError(response);
|
|
406
445
|
return response;
|
|
407
446
|
}
|
|
447
|
+
// DO NOT refactor these back into `request()` + `.json()`. Parsing the body
|
|
448
|
+
// INSIDE the raced callback is the entire fix: `json()` outside the deadline
|
|
449
|
+
// is the original bug (headers arrive, body stalls, caller hangs forever).
|
|
450
|
+
// `request()` survives for `del()`, which never reads the body.
|
|
408
451
|
async get(path) {
|
|
409
|
-
|
|
410
|
-
|
|
452
|
+
return this.withDeadline(async (signal) => {
|
|
453
|
+
const response = await this.send("GET", path, void 0, signal);
|
|
454
|
+
return await response.json();
|
|
455
|
+
});
|
|
411
456
|
}
|
|
412
457
|
async post(path, body) {
|
|
413
|
-
|
|
414
|
-
|
|
458
|
+
return this.withDeadline(async (signal) => {
|
|
459
|
+
const response = await this.send("POST", path, body, signal);
|
|
460
|
+
return await response.json();
|
|
461
|
+
});
|
|
415
462
|
}
|
|
416
463
|
async del(path) {
|
|
417
464
|
await this.request("DELETE", path);
|
|
@@ -1185,6 +1232,7 @@ function translateWireEvent(wire) {
|
|
|
1185
1232
|
// src/session.ts
|
|
1186
1233
|
var SSE_MAX_RECONNECTS = 6;
|
|
1187
1234
|
var TOOL_RESULT_MAX_RETRIES = 3;
|
|
1235
|
+
var CONVERSATION_PAGE_SIZE = 50;
|
|
1188
1236
|
function sseReconnectDelayMs(attempt) {
|
|
1189
1237
|
return Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
1190
1238
|
}
|
|
@@ -1208,6 +1256,17 @@ var ChatSession = class {
|
|
|
1208
1256
|
// State
|
|
1209
1257
|
this.conversationId = null;
|
|
1210
1258
|
this.conversations = [];
|
|
1259
|
+
/**
|
|
1260
|
+
* Whether another page of conversations may exist on the server.
|
|
1261
|
+
*
|
|
1262
|
+
* Inferred from the last page being full, since the list endpoint returns a
|
|
1263
|
+
* bare array with no total. A total that happens to be an exact multiple of
|
|
1264
|
+
* the page size therefore costs one extra empty request before this flips —
|
|
1265
|
+
* cheaper than adding a count query to every list call.
|
|
1266
|
+
*/
|
|
1267
|
+
this.hasMoreConversations = false;
|
|
1268
|
+
/** True while ``loadMoreConversations`` is in flight. */
|
|
1269
|
+
this.isLoadingConversations = false;
|
|
1211
1270
|
this.messages = [];
|
|
1212
1271
|
this.isStreaming = false;
|
|
1213
1272
|
this.agentStatus = null;
|
|
@@ -1215,6 +1274,32 @@ var ChatSession = class {
|
|
|
1215
1274
|
this.skills = [];
|
|
1216
1275
|
this.enabledClientTools = /* @__PURE__ */ new Set();
|
|
1217
1276
|
this.modelDisplayName = null;
|
|
1277
|
+
/**
|
|
1278
|
+
* Ids of conversations the SERVER has handed us, which is the paging offset.
|
|
1279
|
+
*
|
|
1280
|
+
* Deliberately not ``conversations.length``. That array also holds
|
|
1281
|
+
* conversations created locally and unshifted on top (``createNewConversation``,
|
|
1282
|
+
* and the auto-created conversation in ``consumeJobStream``), so using its
|
|
1283
|
+
* length as the offset would over-count and silently skip a row of real
|
|
1284
|
+
* history on the next page. Tracking ids rather than a counter also makes
|
|
1285
|
+
* deletion self-correcting: removing a server-sourced conversation shifts
|
|
1286
|
+
* every later page up by one, and dropping its id from this set is exactly
|
|
1287
|
+
* that shift — while deleting a purely local one correctly changes nothing.
|
|
1288
|
+
*/
|
|
1289
|
+
this.serverConversationIds = /* @__PURE__ */ new Set();
|
|
1290
|
+
/**
|
|
1291
|
+
* Bumped every time ``connect()`` re-seeds the conversation list.
|
|
1292
|
+
*
|
|
1293
|
+
* A ``loadMoreConversations`` request issued before a re-seed describes the
|
|
1294
|
+
* OLD paging state, so applying its response afterwards both appends the
|
|
1295
|
+
* wrong rows and corrupts the offset. Concretely: with 100 rows held, an
|
|
1296
|
+
* offset-100 response landing after a reconnect has reset to rows 0-49 would
|
|
1297
|
+
* append rows 100-149 — a 50-row hole — and leave the id set at 100, so every
|
|
1298
|
+
* later page re-requests offset 100 and never advances again. The generation
|
|
1299
|
+
* is captured before the await and rechecked after, so a superseded response
|
|
1300
|
+
* is discarded instead.
|
|
1301
|
+
*/
|
|
1302
|
+
this.conversationsGeneration = 0;
|
|
1218
1303
|
// Minimal in-session accumulation for the assistant message record.
|
|
1219
1304
|
// Only top-level ``text`` blocks contribute; subagent / tool output
|
|
1220
1305
|
// is tracked by the consumer's own block store.
|
|
@@ -1254,7 +1339,7 @@ var ChatSession = class {
|
|
|
1254
1339
|
async connect() {
|
|
1255
1340
|
const [status, conversations, agents, skills] = await Promise.allSettled([
|
|
1256
1341
|
this.client.getAgentStatus(),
|
|
1257
|
-
this.client.getConversations(),
|
|
1342
|
+
this.client.getConversations(CONVERSATION_PAGE_SIZE),
|
|
1258
1343
|
this.client.getAgents().catch(() => []),
|
|
1259
1344
|
this.client.getSkills().catch(() => [])
|
|
1260
1345
|
]);
|
|
@@ -1262,7 +1347,12 @@ var ChatSession = class {
|
|
|
1262
1347
|
this.agentStatus = status.value;
|
|
1263
1348
|
}
|
|
1264
1349
|
if (conversations.status === "fulfilled") {
|
|
1350
|
+
this.conversationsGeneration++;
|
|
1265
1351
|
this.conversations = conversations.value;
|
|
1352
|
+
this.serverConversationIds = new Set(
|
|
1353
|
+
conversations.value.map((c) => c.id)
|
|
1354
|
+
);
|
|
1355
|
+
this.hasMoreConversations = conversations.value.length === CONVERSATION_PAGE_SIZE;
|
|
1266
1356
|
}
|
|
1267
1357
|
if (agents.status === "fulfilled") {
|
|
1268
1358
|
this.agents = agents.value;
|
|
@@ -1721,12 +1811,69 @@ var ChatSession = class {
|
|
|
1721
1811
|
eventsResult.status === "fulfilled" ? eventsResult.value : []
|
|
1722
1812
|
);
|
|
1723
1813
|
}
|
|
1814
|
+
/**
|
|
1815
|
+
* Append the next page of conversation history to ``conversations``.
|
|
1816
|
+
*
|
|
1817
|
+
* The list is ordered ``updated_at DESC`` and paged by offset, so a
|
|
1818
|
+
* conversation bumped to the top mid-scroll can surface again in a later
|
|
1819
|
+
* page; ids already held are dropped rather than duplicated. Returns only
|
|
1820
|
+
* the conversations actually appended, which may be empty even on a full
|
|
1821
|
+
* page. Rejects on network failure with ``hasMoreConversations`` still true,
|
|
1822
|
+
* so the caller can retry.
|
|
1823
|
+
*
|
|
1824
|
+
* KNOWN LIMITATION — offset paging is only stable while the prefix already
|
|
1825
|
+
* consumed stays put. The offset tracking here corrects for perturbations
|
|
1826
|
+
* THIS session causes (local unshifts, ``deleteConversation``), but not for
|
|
1827
|
+
* ones it never sees:
|
|
1828
|
+
*
|
|
1829
|
+
* - a conversation this session hasn't loaded yet is bumped to the top (a
|
|
1830
|
+
* headless routine or another device posting to it), pushing the whole
|
|
1831
|
+
* list down — it lands inside the consumed prefix, which no later offset
|
|
1832
|
+
* revisits;
|
|
1833
|
+
* - a conversation is deleted from another tab/device, shrinking the list so
|
|
1834
|
+
* the next offset lands one row too far in.
|
|
1835
|
+
*
|
|
1836
|
+
* Each perturbation costs at most one conversation off the sidebar, and only
|
|
1837
|
+
* until the next ``connect()`` — that re-seeds page 1 and resets the paging
|
|
1838
|
+
* state, so a reload or reconnect always recovers it. Nothing is lost
|
|
1839
|
+
* server-side. Both cases are pinned by tests in
|
|
1840
|
+
* ``tests/conversation-paging.test.ts``.
|
|
1841
|
+
*
|
|
1842
|
+
* Closing the gap properly needs a stable server cursor (keyset paging on
|
|
1843
|
+
* ``(updated_at, id)``) rather than a raw offset, which is a backend change —
|
|
1844
|
+
* tracking ids client-side cannot discover a row that moved into a region
|
|
1845
|
+
* already scanned.
|
|
1846
|
+
*/
|
|
1847
|
+
async loadMoreConversations() {
|
|
1848
|
+
if (this.isLoadingConversations || !this.hasMoreConversations) return [];
|
|
1849
|
+
this.isLoadingConversations = true;
|
|
1850
|
+
const generation = this.conversationsGeneration;
|
|
1851
|
+
try {
|
|
1852
|
+
const page = await this.client.getConversations(
|
|
1853
|
+
CONVERSATION_PAGE_SIZE,
|
|
1854
|
+
this.serverConversationIds.size
|
|
1855
|
+
);
|
|
1856
|
+
if (generation !== this.conversationsGeneration) return [];
|
|
1857
|
+
this.hasMoreConversations = page.length === CONVERSATION_PAGE_SIZE;
|
|
1858
|
+
const fresh = page.filter((c) => !this.serverConversationIds.has(c.id));
|
|
1859
|
+
for (const c of page) this.serverConversationIds.add(c.id);
|
|
1860
|
+
const known = new Set(this.conversations.map((c) => c.id));
|
|
1861
|
+
const appended = fresh.filter((c) => !known.has(c.id));
|
|
1862
|
+
this.conversations.push(...appended);
|
|
1863
|
+
return appended;
|
|
1864
|
+
} finally {
|
|
1865
|
+
this.isLoadingConversations = false;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1724
1868
|
async deleteConversation(id) {
|
|
1725
1869
|
try {
|
|
1726
1870
|
await this.client.deleteConversation(id);
|
|
1727
1871
|
} catch {
|
|
1728
1872
|
}
|
|
1729
1873
|
await this.storage.deleteConversation(id);
|
|
1874
|
+
if (this.serverConversationIds.delete(id)) {
|
|
1875
|
+
this.conversationsGeneration++;
|
|
1876
|
+
}
|
|
1730
1877
|
this.conversations = this.conversations.filter((c) => c.id !== id);
|
|
1731
1878
|
if (this.conversationId === id) {
|
|
1732
1879
|
this.conversationId = null;
|
|
@@ -2111,6 +2258,7 @@ export {
|
|
|
2111
2258
|
AstralformClient,
|
|
2112
2259
|
AstralformError,
|
|
2113
2260
|
AuthenticationError,
|
|
2261
|
+
CONVERSATION_PAGE_SIZE,
|
|
2114
2262
|
ChatEventType,
|
|
2115
2263
|
ChatSession,
|
|
2116
2264
|
ConnectionError,
|