@astralform/js 1.0.0 → 1.2.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 +146 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +146 -25
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
package/dist/index.d.cts
CHANGED
|
@@ -641,6 +641,27 @@ interface ToolApprovalRequest {
|
|
|
641
641
|
decision: ToolApprovalDecision;
|
|
642
642
|
scope: ToolApprovalScope;
|
|
643
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* A remembered tool-permission grant belonging to the current end user.
|
|
646
|
+
* Only `conversation`/`always` grants are ever stored (`once` is consumed at
|
|
647
|
+
* approval time and never persisted).
|
|
648
|
+
*/
|
|
649
|
+
interface ToolGrant {
|
|
650
|
+
id: string;
|
|
651
|
+
toolName: string;
|
|
652
|
+
decision: ToolApprovalDecision;
|
|
653
|
+
scope: Exclude<ToolApprovalScope, "once">;
|
|
654
|
+
/** Set for `conversation`-scoped grants; `null` for `always`. */
|
|
655
|
+
conversationId: string | null;
|
|
656
|
+
createdAt: string;
|
|
657
|
+
}
|
|
658
|
+
/** A page of the current end user's own tool grants. */
|
|
659
|
+
interface MyToolGrantsPage {
|
|
660
|
+
grants: ToolGrant[];
|
|
661
|
+
total: number;
|
|
662
|
+
limit: number;
|
|
663
|
+
offset: number;
|
|
664
|
+
}
|
|
644
665
|
interface ToolDefinition {
|
|
645
666
|
name: string;
|
|
646
667
|
description: string;
|
|
@@ -759,6 +780,21 @@ declare class AstralformClient {
|
|
|
759
780
|
getConversationEvents(conversationId: string, jobId?: string): Promise<ConversationEvent[]>;
|
|
760
781
|
submitToolResult(request: ToolResultRequest): Promise<void>;
|
|
761
782
|
submitToolApproval(request: ToolApprovalRequest): Promise<void>;
|
|
783
|
+
/**
|
|
784
|
+
* List the current end user's own remembered tool-permission grants.
|
|
785
|
+
* Only `conversation`/`always` grants exist (`once` is never persisted).
|
|
786
|
+
* Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you
|
|
787
|
+
* page through all of them.
|
|
788
|
+
*/
|
|
789
|
+
getMyToolPermissions(options?: {
|
|
790
|
+
limit?: number;
|
|
791
|
+
offset?: number;
|
|
792
|
+
}): Promise<MyToolGrantsPage>;
|
|
793
|
+
/**
|
|
794
|
+
* Revoke one of the current end user's remembered grants by id. The agent
|
|
795
|
+
* will ask again the next time that tool is used.
|
|
796
|
+
*/
|
|
797
|
+
revokeToolPermission(id: string): Promise<void>;
|
|
762
798
|
private mapAsset;
|
|
763
799
|
uploadFile(conversationId: string, file: Blob, filename?: string): Promise<ConversationAsset>;
|
|
764
800
|
listUploads(conversationId: string): Promise<ConversationAsset[]>;
|
|
@@ -881,6 +917,13 @@ declare class ChatSession {
|
|
|
881
917
|
private processStream;
|
|
882
918
|
/** Last received sequence number for resumable reconnection */
|
|
883
919
|
private lastSeq;
|
|
920
|
+
/**
|
|
921
|
+
* Client-tool call_ids whose result was already submitted this turn. On a
|
|
922
|
+
* reconnect the resumed stream can replay a tool request we already handled;
|
|
923
|
+
* this dedups so each is executed + submitted at most once (but a request we
|
|
924
|
+
* never submitted still runs). Cleared at the start of each turn.
|
|
925
|
+
*/
|
|
926
|
+
private submittedToolCallIds;
|
|
884
927
|
/** Current job ID for cancellation */
|
|
885
928
|
currentJobId: string | null;
|
|
886
929
|
private consumeJobStream;
|
|
@@ -889,6 +932,16 @@ declare class ChatSession {
|
|
|
889
932
|
* minimal session state, and emits typed ChatEvents to consumers.
|
|
890
933
|
*/
|
|
891
934
|
private consumeEventStream;
|
|
935
|
+
/**
|
|
936
|
+
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
937
|
+
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
938
|
+
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
939
|
+
*/
|
|
940
|
+
private pumpStream;
|
|
941
|
+
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
|
942
|
+
private sleepUnlessAborted;
|
|
943
|
+
/** POST a client-tool result, retrying transient failures a few times. */
|
|
944
|
+
private submitToolResultWithRetry;
|
|
892
945
|
private dispatchWireEvent;
|
|
893
946
|
/**
|
|
894
947
|
* State mutations driven by wire events. Kept separate from translation so
|
|
@@ -1122,4 +1175,4 @@ declare function isEmbeddedResource(value: unknown): value is {
|
|
|
1122
1175
|
*/
|
|
1123
1176
|
declare function parseEmbeddedResource(value: unknown): EmbeddedResource | null;
|
|
1124
1177
|
|
|
1125
|
-
export { type ActiveJob, type AgentIdentity, type AgentInfo, 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 ProjectStatus, type ProjectSummary, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, 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 };
|
|
1178
|
+
export { type ActiveJob, type AgentIdentity, type AgentInfo, 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 MyToolGrantsPage, type ProjectStatus, type ProjectSummary, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, 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
|
@@ -641,6 +641,27 @@ interface ToolApprovalRequest {
|
|
|
641
641
|
decision: ToolApprovalDecision;
|
|
642
642
|
scope: ToolApprovalScope;
|
|
643
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* A remembered tool-permission grant belonging to the current end user.
|
|
646
|
+
* Only `conversation`/`always` grants are ever stored (`once` is consumed at
|
|
647
|
+
* approval time and never persisted).
|
|
648
|
+
*/
|
|
649
|
+
interface ToolGrant {
|
|
650
|
+
id: string;
|
|
651
|
+
toolName: string;
|
|
652
|
+
decision: ToolApprovalDecision;
|
|
653
|
+
scope: Exclude<ToolApprovalScope, "once">;
|
|
654
|
+
/** Set for `conversation`-scoped grants; `null` for `always`. */
|
|
655
|
+
conversationId: string | null;
|
|
656
|
+
createdAt: string;
|
|
657
|
+
}
|
|
658
|
+
/** A page of the current end user's own tool grants. */
|
|
659
|
+
interface MyToolGrantsPage {
|
|
660
|
+
grants: ToolGrant[];
|
|
661
|
+
total: number;
|
|
662
|
+
limit: number;
|
|
663
|
+
offset: number;
|
|
664
|
+
}
|
|
644
665
|
interface ToolDefinition {
|
|
645
666
|
name: string;
|
|
646
667
|
description: string;
|
|
@@ -759,6 +780,21 @@ declare class AstralformClient {
|
|
|
759
780
|
getConversationEvents(conversationId: string, jobId?: string): Promise<ConversationEvent[]>;
|
|
760
781
|
submitToolResult(request: ToolResultRequest): Promise<void>;
|
|
761
782
|
submitToolApproval(request: ToolApprovalRequest): Promise<void>;
|
|
783
|
+
/**
|
|
784
|
+
* List the current end user's own remembered tool-permission grants.
|
|
785
|
+
* Only `conversation`/`always` grants exist (`once` is never persisted).
|
|
786
|
+
* Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you
|
|
787
|
+
* page through all of them.
|
|
788
|
+
*/
|
|
789
|
+
getMyToolPermissions(options?: {
|
|
790
|
+
limit?: number;
|
|
791
|
+
offset?: number;
|
|
792
|
+
}): Promise<MyToolGrantsPage>;
|
|
793
|
+
/**
|
|
794
|
+
* Revoke one of the current end user's remembered grants by id. The agent
|
|
795
|
+
* will ask again the next time that tool is used.
|
|
796
|
+
*/
|
|
797
|
+
revokeToolPermission(id: string): Promise<void>;
|
|
762
798
|
private mapAsset;
|
|
763
799
|
uploadFile(conversationId: string, file: Blob, filename?: string): Promise<ConversationAsset>;
|
|
764
800
|
listUploads(conversationId: string): Promise<ConversationAsset[]>;
|
|
@@ -881,6 +917,13 @@ declare class ChatSession {
|
|
|
881
917
|
private processStream;
|
|
882
918
|
/** Last received sequence number for resumable reconnection */
|
|
883
919
|
private lastSeq;
|
|
920
|
+
/**
|
|
921
|
+
* Client-tool call_ids whose result was already submitted this turn. On a
|
|
922
|
+
* reconnect the resumed stream can replay a tool request we already handled;
|
|
923
|
+
* this dedups so each is executed + submitted at most once (but a request we
|
|
924
|
+
* never submitted still runs). Cleared at the start of each turn.
|
|
925
|
+
*/
|
|
926
|
+
private submittedToolCallIds;
|
|
884
927
|
/** Current job ID for cancellation */
|
|
885
928
|
currentJobId: string | null;
|
|
886
929
|
private consumeJobStream;
|
|
@@ -889,6 +932,16 @@ declare class ChatSession {
|
|
|
889
932
|
* minimal session state, and emits typed ChatEvents to consumers.
|
|
890
933
|
*/
|
|
891
934
|
private consumeEventStream;
|
|
935
|
+
/**
|
|
936
|
+
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
937
|
+
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
938
|
+
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
939
|
+
*/
|
|
940
|
+
private pumpStream;
|
|
941
|
+
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
|
942
|
+
private sleepUnlessAborted;
|
|
943
|
+
/** POST a client-tool result, retrying transient failures a few times. */
|
|
944
|
+
private submitToolResultWithRetry;
|
|
892
945
|
private dispatchWireEvent;
|
|
893
946
|
/**
|
|
894
947
|
* State mutations driven by wire events. Kept separate from translation so
|
|
@@ -1122,4 +1175,4 @@ declare function isEmbeddedResource(value: unknown): value is {
|
|
|
1122
1175
|
*/
|
|
1123
1176
|
declare function parseEmbeddedResource(value: unknown): EmbeddedResource | null;
|
|
1124
1177
|
|
|
1125
|
-
export { type ActiveJob, type AgentIdentity, type AgentInfo, 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 ProjectStatus, type ProjectSummary, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, type TeamSummary, type TitleGeneratedPayload, type TodoItem, type TodoUpdatePayload, type ToolApprovalDecision, type ToolApprovalGrantedPayload, type ToolApprovalRequest, type ToolApprovalRequestedPayload, type ToolApprovalScope, type ToolCallRequest, type ToolDefinition, 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 };
|
|
1178
|
+
export { type ActiveJob, type AgentIdentity, type AgentInfo, 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 MyToolGrantsPage, type ProjectStatus, type ProjectSummary, type PromptSuggestionPayload, type ProtocolAdapter, ProtocolRegistry, RateLimitError, type RateLimitErrorDetails, type RawSseEvent, type SendOptions, ServerError, type SkillInfo, StreamAbortedError, type StreamJobSSEOptions, StreamManager, type StreamManagerEvent, type StreamState, type SubagentStartPayload, type SubagentStopPayload, type TaskStatus, 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
|
@@ -508,6 +508,49 @@ var AstralformClient = class {
|
|
|
508
508
|
async submitToolApproval(request) {
|
|
509
509
|
await this.post("/v1/tool-approval", request);
|
|
510
510
|
}
|
|
511
|
+
// --- End-user tool-permission self-service ---
|
|
512
|
+
/**
|
|
513
|
+
* List the current end user's own remembered tool-permission grants.
|
|
514
|
+
* Only `conversation`/`always` grants exist (`once` is never persisted).
|
|
515
|
+
* Paginated via `limit` (default 100, max 200) / `offset`; `total` lets you
|
|
516
|
+
* page through all of them.
|
|
517
|
+
*/
|
|
518
|
+
async getMyToolPermissions(options) {
|
|
519
|
+
const params = new URLSearchParams();
|
|
520
|
+
if (options?.limit != null) {
|
|
521
|
+
const safeLimit = Math.max(
|
|
522
|
+
1,
|
|
523
|
+
Math.min(200, Math.floor(Number(options.limit)))
|
|
524
|
+
);
|
|
525
|
+
params.set("limit", String(safeLimit));
|
|
526
|
+
}
|
|
527
|
+
if (options?.offset != null) {
|
|
528
|
+
const safeOffset = Math.max(0, Math.floor(Number(options.offset)));
|
|
529
|
+
params.set("offset", String(safeOffset));
|
|
530
|
+
}
|
|
531
|
+
const qs = params.toString();
|
|
532
|
+
const raw = await this.get(`/v1/me/tool-permissions${qs ? `?${qs}` : ""}`);
|
|
533
|
+
return {
|
|
534
|
+
grants: raw.grants.map((g) => ({
|
|
535
|
+
id: g.id,
|
|
536
|
+
toolName: g.tool_name,
|
|
537
|
+
decision: g.decision,
|
|
538
|
+
scope: g.scope,
|
|
539
|
+
conversationId: g.conversation_id,
|
|
540
|
+
createdAt: g.created_at
|
|
541
|
+
})),
|
|
542
|
+
total: raw.total,
|
|
543
|
+
limit: raw.limit,
|
|
544
|
+
offset: raw.offset
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Revoke one of the current end user's remembered grants by id. The agent
|
|
549
|
+
* will ask again the next time that tool is used.
|
|
550
|
+
*/
|
|
551
|
+
async revokeToolPermission(id) {
|
|
552
|
+
await this.del(`/v1/me/tool-permissions/${encodeURIComponent(id)}`);
|
|
553
|
+
}
|
|
511
554
|
// --- Conversation Assets ---
|
|
512
555
|
mapAsset(raw) {
|
|
513
556
|
return {
|
|
@@ -1101,6 +1144,11 @@ function translateWireEvent(wire) {
|
|
|
1101
1144
|
}
|
|
1102
1145
|
|
|
1103
1146
|
// src/session.ts
|
|
1147
|
+
var SSE_MAX_RECONNECTS = 6;
|
|
1148
|
+
var TOOL_RESULT_MAX_RETRIES = 3;
|
|
1149
|
+
function sseReconnectDelayMs(attempt) {
|
|
1150
|
+
return Math.min(500 * 2 ** (attempt - 1), 5e3);
|
|
1151
|
+
}
|
|
1104
1152
|
function pathEquals(a, b) {
|
|
1105
1153
|
if (a.length !== b.length) return false;
|
|
1106
1154
|
for (let i = 0; i < a.length; i++) {
|
|
@@ -1137,6 +1185,13 @@ var ChatSession = class {
|
|
|
1137
1185
|
this.abortController = null;
|
|
1138
1186
|
/** Last received sequence number for resumable reconnection */
|
|
1139
1187
|
this.lastSeq = -1;
|
|
1188
|
+
/**
|
|
1189
|
+
* Client-tool call_ids whose result was already submitted this turn. On a
|
|
1190
|
+
* reconnect the resumed stream can replay a tool request we already handled;
|
|
1191
|
+
* this dedups so each is executed + submitted at most once (but a request we
|
|
1192
|
+
* never submitted still runs). Cleared at the start of each turn.
|
|
1193
|
+
*/
|
|
1194
|
+
this.submittedToolCallIds = /* @__PURE__ */ new Set();
|
|
1140
1195
|
/** Current job ID for cancellation */
|
|
1141
1196
|
this.currentJobId = null;
|
|
1142
1197
|
this.client = new AstralformClient(config);
|
|
@@ -1271,13 +1326,9 @@ var ChatSession = class {
|
|
|
1271
1326
|
}
|
|
1272
1327
|
const messageId = job.message_id;
|
|
1273
1328
|
this.lastSeq = -1;
|
|
1274
|
-
|
|
1275
|
-
job.job_id,
|
|
1276
|
-
this.lastSeq,
|
|
1277
|
-
this.abortController?.signal
|
|
1278
|
-
);
|
|
1329
|
+
this.submittedToolCallIds.clear();
|
|
1279
1330
|
await this.consumeEventStream(
|
|
1280
|
-
|
|
1331
|
+
job.job_id,
|
|
1281
1332
|
conversationId,
|
|
1282
1333
|
messageId,
|
|
1283
1334
|
true
|
|
@@ -1288,7 +1339,41 @@ var ChatSession = class {
|
|
|
1288
1339
|
* Shared event consumption loop. Parses each wire event, updates
|
|
1289
1340
|
* minimal session state, and emits typed ChatEvents to consumers.
|
|
1290
1341
|
*/
|
|
1291
|
-
async consumeEventStream(
|
|
1342
|
+
async consumeEventStream(jobId, conversationId, messageId, executeClientTools) {
|
|
1343
|
+
const signal = this.abortController?.signal;
|
|
1344
|
+
for (let attempt = 0; ; attempt++) {
|
|
1345
|
+
const stream = this.client.streamJobEvents(jobId, this.lastSeq, signal);
|
|
1346
|
+
let sawTerminal;
|
|
1347
|
+
try {
|
|
1348
|
+
sawTerminal = await this.pumpStream(
|
|
1349
|
+
stream,
|
|
1350
|
+
conversationId,
|
|
1351
|
+
messageId,
|
|
1352
|
+
executeClientTools
|
|
1353
|
+
);
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
if (signal?.aborted) return;
|
|
1356
|
+
if (err instanceof AuthenticationError || err instanceof RateLimitError) {
|
|
1357
|
+
throw err;
|
|
1358
|
+
}
|
|
1359
|
+
if (attempt >= SSE_MAX_RECONNECTS) throw err;
|
|
1360
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1361
|
+
continue;
|
|
1362
|
+
}
|
|
1363
|
+
if (sawTerminal || signal?.aborted) return;
|
|
1364
|
+
if (attempt >= SSE_MAX_RECONNECTS) {
|
|
1365
|
+
throw new ConnectionError("Lost connection to the response stream.");
|
|
1366
|
+
}
|
|
1367
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Consume a single SSE stream to exhaustion. Returns whether a terminal
|
|
1372
|
+
* event (``message_stop`` / ``error``) was seen, so the caller can decide
|
|
1373
|
+
* whether an ended stream means "turn done" vs "dropped, reconnect".
|
|
1374
|
+
*/
|
|
1375
|
+
async pumpStream(stream, conversationId, messageId, executeClientTools) {
|
|
1376
|
+
let sawTerminal = false;
|
|
1292
1377
|
for await (const raw of stream) {
|
|
1293
1378
|
let parsed;
|
|
1294
1379
|
try {
|
|
@@ -1306,6 +1391,9 @@ var ChatSession = class {
|
|
|
1306
1391
|
} catch {
|
|
1307
1392
|
continue;
|
|
1308
1393
|
}
|
|
1394
|
+
if (parsed.type === "message_stop" || parsed.type === "error") {
|
|
1395
|
+
sawTerminal = true;
|
|
1396
|
+
}
|
|
1309
1397
|
await this.dispatchWireEvent(
|
|
1310
1398
|
parsed,
|
|
1311
1399
|
conversationId,
|
|
@@ -1313,6 +1401,39 @@ var ChatSession = class {
|
|
|
1313
1401
|
executeClientTools
|
|
1314
1402
|
);
|
|
1315
1403
|
}
|
|
1404
|
+
return sawTerminal;
|
|
1405
|
+
}
|
|
1406
|
+
/** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
|
|
1407
|
+
sleepUnlessAborted(ms, signal) {
|
|
1408
|
+
return new Promise((resolve) => {
|
|
1409
|
+
if (signal?.aborted) return resolve();
|
|
1410
|
+
const timer = setTimeout(() => {
|
|
1411
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1412
|
+
resolve();
|
|
1413
|
+
}, ms);
|
|
1414
|
+
const onAbort = () => {
|
|
1415
|
+
clearTimeout(timer);
|
|
1416
|
+
resolve();
|
|
1417
|
+
};
|
|
1418
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
/** POST a client-tool result, retrying transient failures a few times. */
|
|
1422
|
+
async submitToolResultWithRetry(payload) {
|
|
1423
|
+
const signal = this.abortController?.signal;
|
|
1424
|
+
for (let attempt = 0; ; attempt++) {
|
|
1425
|
+
try {
|
|
1426
|
+
await this.client.submitToolResult(payload);
|
|
1427
|
+
return;
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
if (signal?.aborted) throw err;
|
|
1430
|
+
if (err instanceof AuthenticationError || err instanceof RateLimitError) {
|
|
1431
|
+
throw err;
|
|
1432
|
+
}
|
|
1433
|
+
if (attempt >= TOOL_RESULT_MAX_RETRIES) throw err;
|
|
1434
|
+
await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1316
1437
|
}
|
|
1317
1438
|
async dispatchWireEvent(wire, conversationId, messageId, executeClientTools) {
|
|
1318
1439
|
this.applyWireSideEffects(wire, conversationId, messageId);
|
|
@@ -1322,18 +1443,22 @@ var ChatSession = class {
|
|
|
1322
1443
|
}
|
|
1323
1444
|
if (executeClientTools && wire.type === "block_stop" && wire.status === "awaiting_client_result" && wire.final?.call_id) {
|
|
1324
1445
|
const f = wire.final;
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1446
|
+
const callId = f.call_id ?? "";
|
|
1447
|
+
if (callId && !this.submittedToolCallIds.has(callId)) {
|
|
1448
|
+
const request = {
|
|
1449
|
+
callId,
|
|
1450
|
+
toolName: f.tool_name ?? "",
|
|
1451
|
+
arguments: f.input ?? {},
|
|
1452
|
+
isClientTool: true
|
|
1453
|
+
};
|
|
1454
|
+
const results = await this.executeClientTools([request]);
|
|
1455
|
+
await this.submitToolResultWithRetry({
|
|
1456
|
+
conversation_id: conversationId,
|
|
1457
|
+
message_id: messageId,
|
|
1458
|
+
tool_results: results
|
|
1459
|
+
});
|
|
1460
|
+
this.submittedToolCallIds.add(callId);
|
|
1461
|
+
}
|
|
1337
1462
|
}
|
|
1338
1463
|
}
|
|
1339
1464
|
/**
|
|
@@ -1430,16 +1555,12 @@ var ChatSession = class {
|
|
|
1430
1555
|
this.isStreaming = true;
|
|
1431
1556
|
this.currentJobId = jobId;
|
|
1432
1557
|
this.lastSeq = -1;
|
|
1558
|
+
this.submittedToolCallIds.clear();
|
|
1433
1559
|
this.resetStreamingState();
|
|
1434
1560
|
this.abortController = new AbortController();
|
|
1435
1561
|
try {
|
|
1436
|
-
const stream = this.client.streamJobEvents(
|
|
1437
|
-
jobId,
|
|
1438
|
-
this.lastSeq,
|
|
1439
|
-
this.abortController?.signal
|
|
1440
|
-
);
|
|
1441
1562
|
await this.consumeEventStream(
|
|
1442
|
-
|
|
1563
|
+
jobId,
|
|
1443
1564
|
this.conversationId ?? "",
|
|
1444
1565
|
"",
|
|
1445
1566
|
false
|