@astralform/js 4.8.0 → 4.9.1

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
@@ -297,6 +297,7 @@ interface WireMessageStop extends WireEnvelope {
297
297
  input_tokens?: number;
298
298
  output_tokens?: number;
299
299
  cached_tokens?: number;
300
+ cache_creation_tokens?: number;
300
301
  };
301
302
  ttfb_ms?: number | null;
302
303
  total_ms: number;
@@ -344,6 +345,8 @@ interface TurnUsage {
344
345
  inputTokens: number;
345
346
  outputTokens: number;
346
347
  cachedTokens: number;
348
+ /** Tokens written to the model's prompt cache (wire: `cache_creation_tokens`). */
349
+ cacheCreationTokens: number;
347
350
  }
348
351
  type BlockDeltaPayload = {
349
352
  channel: "text";
@@ -629,6 +632,8 @@ interface TeamAgentSummary {
629
632
  teamId: string;
630
633
  createdAt: string;
631
634
  updatedAt: string;
635
+ /** Agent avatar URL, when the team has set one (wire: `avatar_url`). */
636
+ avatarUrl?: string | null;
632
637
  }
633
638
  interface SkillInfo {
634
639
  name: string;
@@ -700,6 +705,7 @@ interface ChatStreamRequest {
700
705
  agent_name?: string;
701
706
  plan_mode?: boolean;
702
707
  image_mode?: boolean;
708
+ video_mode?: boolean;
703
709
  /**
704
710
  * Start a durable long-horizon goal for this run (goal mode). The backend mints
705
711
  * an agent_goal from this text and drives the run under a budget until the
@@ -805,6 +811,22 @@ interface SendOptions$1 extends ModelChoiceOptions {
805
811
  * The `image_mode` field itself is accepted from Astralform ≥ 0.59.0.
806
812
  */
807
813
  imageMode?: boolean;
814
+ /**
815
+ * Put this turn in video mode, attaching the video-generation tool.
816
+ *
817
+ * Off by default and per-message, for a sharper reason than images: a clip
818
+ * occupies one shared GPU for minutes, during which image generation on the
819
+ * same host cannot run at all. The tool is not attached unless this is set.
820
+ *
821
+ * Mutually exclusive with `imageMode` at the composer level — which is why a
822
+ * video turn also gets the image tool server-side: the user cannot select
823
+ * both, so the agent must be able to produce its own first frame.
824
+ *
825
+ * `generate_video` animates an EXISTING image; it cannot start from text, and
826
+ * the clip is silent. Gate the affordance on `AgentStatus.capabilities`
827
+ * (`video`), the same way image mode does.
828
+ */
829
+ videoMode?: boolean;
808
830
  /**
809
831
  * Start a durable long-horizon goal for this run (goal mode). The text becomes
810
832
  * the goal's objective; the backend keeps the agent working under a budget until
@@ -1158,6 +1180,11 @@ declare class ChatSession {
1158
1180
  * Consume a single SSE stream to exhaustion. Returns whether a terminal
1159
1181
  * event (``message_stop`` / ``error``) was seen, so the caller can decide
1160
1182
  * whether an ended stream means "turn done" vs "dropped, reconnect".
1183
+ *
1184
+ * ``onStall`` aborts the per-attempt connection: if no event arrives within
1185
+ * SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
1186
+ * zombie — ``reader.read()`` will never settle — so we kill the fetch and
1187
+ * throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
1161
1188
  */
1162
1189
  private pumpStream;
1163
1190
  /** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
@@ -1334,6 +1361,13 @@ interface SendOptions extends ModelChoiceOptions {
1334
1361
  * provider. Gate the affordance on `AgentStatus.capabilities`.
1335
1362
  */
1336
1363
  imageMode?: boolean;
1364
+ /**
1365
+ * Attach the video-generation tool to this turn. Mutually exclusive with
1366
+ * `imageMode` at the composer level. See `SendOptions.videoMode` in types.ts
1367
+ * for the full rule — a clip animates an existing image, is silent, and holds
1368
+ * one shared GPU for minutes, so it is per-message and off by default.
1369
+ */
1370
+ videoMode?: boolean;
1337
1371
  /**
1338
1372
  * Start a durable long-horizon goal for this run (goal mode) — the text is the
1339
1373
  * goal objective the backend drives to completion. Omit for a normal turn.
package/dist/index.d.ts CHANGED
@@ -297,6 +297,7 @@ interface WireMessageStop extends WireEnvelope {
297
297
  input_tokens?: number;
298
298
  output_tokens?: number;
299
299
  cached_tokens?: number;
300
+ cache_creation_tokens?: number;
300
301
  };
301
302
  ttfb_ms?: number | null;
302
303
  total_ms: number;
@@ -344,6 +345,8 @@ interface TurnUsage {
344
345
  inputTokens: number;
345
346
  outputTokens: number;
346
347
  cachedTokens: number;
348
+ /** Tokens written to the model's prompt cache (wire: `cache_creation_tokens`). */
349
+ cacheCreationTokens: number;
347
350
  }
348
351
  type BlockDeltaPayload = {
349
352
  channel: "text";
@@ -629,6 +632,8 @@ interface TeamAgentSummary {
629
632
  teamId: string;
630
633
  createdAt: string;
631
634
  updatedAt: string;
635
+ /** Agent avatar URL, when the team has set one (wire: `avatar_url`). */
636
+ avatarUrl?: string | null;
632
637
  }
633
638
  interface SkillInfo {
634
639
  name: string;
@@ -700,6 +705,7 @@ interface ChatStreamRequest {
700
705
  agent_name?: string;
701
706
  plan_mode?: boolean;
702
707
  image_mode?: boolean;
708
+ video_mode?: boolean;
703
709
  /**
704
710
  * Start a durable long-horizon goal for this run (goal mode). The backend mints
705
711
  * an agent_goal from this text and drives the run under a budget until the
@@ -805,6 +811,22 @@ interface SendOptions$1 extends ModelChoiceOptions {
805
811
  * The `image_mode` field itself is accepted from Astralform ≥ 0.59.0.
806
812
  */
807
813
  imageMode?: boolean;
814
+ /**
815
+ * Put this turn in video mode, attaching the video-generation tool.
816
+ *
817
+ * Off by default and per-message, for a sharper reason than images: a clip
818
+ * occupies one shared GPU for minutes, during which image generation on the
819
+ * same host cannot run at all. The tool is not attached unless this is set.
820
+ *
821
+ * Mutually exclusive with `imageMode` at the composer level — which is why a
822
+ * video turn also gets the image tool server-side: the user cannot select
823
+ * both, so the agent must be able to produce its own first frame.
824
+ *
825
+ * `generate_video` animates an EXISTING image; it cannot start from text, and
826
+ * the clip is silent. Gate the affordance on `AgentStatus.capabilities`
827
+ * (`video`), the same way image mode does.
828
+ */
829
+ videoMode?: boolean;
808
830
  /**
809
831
  * Start a durable long-horizon goal for this run (goal mode). The text becomes
810
832
  * the goal's objective; the backend keeps the agent working under a budget until
@@ -1158,6 +1180,11 @@ declare class ChatSession {
1158
1180
  * Consume a single SSE stream to exhaustion. Returns whether a terminal
1159
1181
  * event (``message_stop`` / ``error``) was seen, so the caller can decide
1160
1182
  * whether an ended stream means "turn done" vs "dropped, reconnect".
1183
+ *
1184
+ * ``onStall`` aborts the per-attempt connection: if no event arrives within
1185
+ * SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
1186
+ * zombie — ``reader.read()`` will never settle — so we kill the fetch and
1187
+ * throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
1161
1188
  */
1162
1189
  private pumpStream;
1163
1190
  /** Sleep for ``ms``, resolving early if the turn is aborted mid-backoff. */
@@ -1334,6 +1361,13 @@ interface SendOptions extends ModelChoiceOptions {
1334
1361
  * provider. Gate the affordance on `AgentStatus.capabilities`.
1335
1362
  */
1336
1363
  imageMode?: boolean;
1364
+ /**
1365
+ * Attach the video-generation tool to this turn. Mutually exclusive with
1366
+ * `imageMode` at the composer level. See `SendOptions.videoMode` in types.ts
1367
+ * for the full rule — a clip animates an existing image, is silent, and holds
1368
+ * one shared GPU for minutes, so it is per-message and off by default.
1369
+ */
1370
+ videoMode?: boolean;
1337
1371
  /**
1338
1372
  * Start a durable long-horizon goal for this run (goal mode) — the text is the
1339
1373
  * goal objective the backend drives to completion. Omit for a normal turn.
package/dist/index.js CHANGED
@@ -737,7 +737,8 @@ var AstralformClient = class {
737
737
  name: a.name,
738
738
  teamId: a.team_id,
739
739
  createdAt: a.created_at,
740
- updatedAt: a.updated_at
740
+ updatedAt: a.updated_at,
741
+ avatarUrl: a.avatar_url ?? null
741
742
  }));
742
743
  }
743
744
  // --- Jobs API ---
@@ -1019,6 +1020,19 @@ function translateAgentIdentity(raw) {
1019
1020
  description: raw.description ?? null
1020
1021
  };
1021
1022
  }
1023
+ function translateTodoItem(raw) {
1024
+ return {
1025
+ id: raw.id ?? 0,
1026
+ subject: raw.subject ?? "",
1027
+ status: raw.status ?? "pending",
1028
+ description: raw.description ?? null,
1029
+ activeForm: raw.active_form ?? null,
1030
+ owner: raw.owner ?? null,
1031
+ blockedBy: raw.blocked_by ?? null,
1032
+ blocks: raw.blocks ?? null,
1033
+ priority: raw.priority ?? null
1034
+ };
1035
+ }
1022
1036
  function translateCustomEvent(name, data) {
1023
1037
  switch (name) {
1024
1038
  case "user_message":
@@ -1035,7 +1049,9 @@ function translateCustomEvent(name, data) {
1035
1049
  case "todo_update":
1036
1050
  return {
1037
1051
  type: "todo_update",
1038
- todos: data.todos ?? []
1052
+ todos: (data.todos ?? []).map(
1053
+ (t) => translateTodoItem(t)
1054
+ )
1039
1055
  };
1040
1056
  case "plan_update":
1041
1057
  return {
@@ -1228,7 +1244,8 @@ function translateWireEvent(wire) {
1228
1244
  usage: {
1229
1245
  inputTokens: wire.usage.input_tokens ?? 0,
1230
1246
  outputTokens: wire.usage.output_tokens ?? 0,
1231
- cachedTokens: wire.usage.cached_tokens ?? 0
1247
+ cachedTokens: wire.usage.cached_tokens ?? 0,
1248
+ cacheCreationTokens: wire.usage.cache_creation_tokens ?? 0
1232
1249
  },
1233
1250
  ttfbMs: wire.ttfb_ms,
1234
1251
  totalMs: wire.total_ms,
@@ -1274,6 +1291,7 @@ function translateWireEvent(wire) {
1274
1291
 
1275
1292
  // src/session.ts
1276
1293
  var SSE_MAX_RECONNECTS = 6;
1294
+ var SSE_STALL_TIMEOUT_MS = 45e3;
1277
1295
  var TOOL_RESULT_MAX_RETRIES = 3;
1278
1296
  var CONVERSATION_PAGE_SIZE = 50;
1279
1297
  function sseReconnectDelayMs(attempt) {
@@ -1436,6 +1454,7 @@ var ChatSession = class {
1436
1454
  agent_name: options?.agentName,
1437
1455
  plan_mode: options?.planMode,
1438
1456
  image_mode: options?.imageMode,
1457
+ video_mode: options?.videoMode,
1439
1458
  goal: options?.goal,
1440
1459
  // Per-request model choice (client-side model selection).
1441
1460
  provider: options?.provider,
@@ -1524,14 +1543,23 @@ var ChatSession = class {
1524
1543
  async consumeEventStream(jobId, conversationId, messageId, executeClientTools) {
1525
1544
  const signal = this.abortController?.signal;
1526
1545
  for (let attempt = 0; ; attempt++) {
1527
- const stream = this.client.streamJobEvents(jobId, this.lastSeq, signal);
1546
+ if (signal?.aborted) return;
1547
+ const attemptController = new AbortController();
1548
+ const linkAbort = () => attemptController.abort();
1549
+ signal?.addEventListener("abort", linkAbort);
1528
1550
  let sawTerminal;
1529
1551
  try {
1552
+ const stream = this.client.streamJobEvents(
1553
+ jobId,
1554
+ this.lastSeq,
1555
+ attemptController.signal
1556
+ );
1530
1557
  sawTerminal = await this.pumpStream(
1531
1558
  stream,
1532
1559
  conversationId,
1533
1560
  messageId,
1534
- executeClientTools
1561
+ executeClientTools,
1562
+ () => attemptController.abort()
1535
1563
  );
1536
1564
  } catch (err) {
1537
1565
  if (signal?.aborted) return;
@@ -1541,6 +1569,8 @@ var ChatSession = class {
1541
1569
  if (attempt >= SSE_MAX_RECONNECTS) throw err;
1542
1570
  await this.sleepUnlessAborted(sseReconnectDelayMs(attempt + 1), signal);
1543
1571
  continue;
1572
+ } finally {
1573
+ signal?.removeEventListener("abort", linkAbort);
1544
1574
  }
1545
1575
  if (sawTerminal || signal?.aborted) return;
1546
1576
  if (attempt >= SSE_MAX_RECONNECTS) {
@@ -1553,35 +1583,66 @@ var ChatSession = class {
1553
1583
  * Consume a single SSE stream to exhaustion. Returns whether a terminal
1554
1584
  * event (``message_stop`` / ``error``) was seen, so the caller can decide
1555
1585
  * whether an ended stream means "turn done" vs "dropped, reconnect".
1586
+ *
1587
+ * ``onStall`` aborts the per-attempt connection: if no event arrives within
1588
+ * SSE_STALL_TIMEOUT_MS (backend keepalives land every 15s), the stream is a
1589
+ * zombie — ``reader.read()`` will never settle — so we kill the fetch and
1590
+ * throw a ConnectionError, feeding the caller's reconnect-from-lastSeq loop.
1556
1591
  */
1557
- async pumpStream(stream, conversationId, messageId, executeClientTools) {
1592
+ async pumpStream(stream, conversationId, messageId, executeClientTools, onStall) {
1558
1593
  let sawTerminal = false;
1559
- for await (const raw of stream) {
1560
- let parsed;
1561
- try {
1562
- const data = JSON.parse(raw.data);
1563
- if (typeof data !== "object" || data === null || typeof data.type !== "string") {
1564
- if (typeof data?.seq === "number") {
1594
+ const iterator = stream[Symbol.asyncIterator]();
1595
+ try {
1596
+ while (true) {
1597
+ const next = iterator.next();
1598
+ let stallTimer;
1599
+ const stall = new Promise((_, reject) => {
1600
+ stallTimer = setTimeout(() => {
1601
+ reject(
1602
+ new ConnectionError(
1603
+ `Stream stalled: no events for ${SSE_STALL_TIMEOUT_MS}ms`
1604
+ )
1605
+ );
1606
+ onStall?.();
1607
+ }, SSE_STALL_TIMEOUT_MS);
1608
+ });
1609
+ let result;
1610
+ try {
1611
+ result = await Promise.race([next, stall]);
1612
+ } finally {
1613
+ clearTimeout(stallTimer);
1614
+ }
1615
+ if (result.done) break;
1616
+ const raw = result.value;
1617
+ let parsed;
1618
+ try {
1619
+ const data = JSON.parse(raw.data);
1620
+ if (typeof data !== "object" || data === null || typeof data.type !== "string") {
1621
+ if (typeof data?.seq === "number") {
1622
+ this.lastSeq = data.seq;
1623
+ }
1624
+ continue;
1625
+ }
1626
+ parsed = data;
1627
+ if (typeof data.seq === "number") {
1565
1628
  this.lastSeq = data.seq;
1566
1629
  }
1630
+ } catch {
1567
1631
  continue;
1568
1632
  }
1569
- parsed = data;
1570
- if (typeof data.seq === "number") {
1571
- this.lastSeq = data.seq;
1633
+ if (parsed.type === "message_stop" || parsed.type === "error") {
1634
+ sawTerminal = true;
1572
1635
  }
1573
- } catch {
1574
- continue;
1575
- }
1576
- if (parsed.type === "message_stop" || parsed.type === "error") {
1577
- sawTerminal = true;
1636
+ await this.dispatchWireEvent(
1637
+ parsed,
1638
+ conversationId,
1639
+ messageId,
1640
+ executeClientTools
1641
+ );
1578
1642
  }
1579
- await this.dispatchWireEvent(
1580
- parsed,
1581
- conversationId,
1582
- messageId,
1583
- executeClientTools
1584
- );
1643
+ } finally {
1644
+ void iterator.return?.(void 0).catch(() => {
1645
+ });
1585
1646
  }
1586
1647
  return sawTerminal;
1587
1648
  }
@@ -2143,6 +2204,7 @@ var StreamManager = class {
2143
2204
  uploadIds: options?.uploadIds,
2144
2205
  planMode: options?.planMode,
2145
2206
  imageMode: options?.imageMode,
2207
+ videoMode: options?.videoMode,
2146
2208
  goal: options?.goal,
2147
2209
  provider: options?.provider,
2148
2210
  model: options?.model,