@astralbeam/sdk 0.15.2 → 0.16.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.
@@ -1,4 +1,4 @@
1
- import { A as resolveApiUrl, O as astralBeamChatFetch, c as getCurrentUser, i as getChatConfig, k as isAstralBeamApiError, v as getRunChatUrl } from "./api-r11gRy91.js";
1
+ import { $ as listChatMessages, C as getCurrentUser, U as getResolveChatToolResultUrl, W as getRunChatUrl, _ as getChatConfig, dt as updateChatThread, f as createChatThread, g as getChatAttachment, gt as resolveApiUrl, h as deleteChatThread, ht as isAstralBeamApiError, mt as astralBeamChatFetch, tt as listChatThreads } from "./api-DzeMhfHU.js";
2
2
  import { d as JWTInvalid, h as strictDecoder, r as isObject, s as decode } from "./type_checks-C-WTHaWs.js";
3
3
  //#region \0rolldown/runtime.js
4
4
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
@@ -26,7 +26,7 @@ function decodeJwt(jwt) {
26
26
  return result;
27
27
  }
28
28
  //#endregion
29
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/response-stream.js
29
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/response-stream.js
30
30
  var UnsupportedResponseStreamError = class extends Error {
31
31
  missingFeature;
32
32
  constructor(missingFeature) {
@@ -48,14 +48,14 @@ function createResponseStreamTextDecoder() {
48
48
  return new globalThis.TextDecoder();
49
49
  }
50
50
  //#endregion
51
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/sse-utils.js
51
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/sse-utils.js
52
52
  function parseSseDataLine(line) {
53
53
  if (!line.startsWith("data:")) return line;
54
54
  const data = line.slice(5);
55
55
  return data.startsWith(" ") ? data.slice(1) : data;
56
56
  }
57
57
  //#endregion
58
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/message-date-normalizer.js
58
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/message-date-normalizer.js
59
59
  function validDate(value) {
60
60
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? void 0 : value;
61
61
  if (typeof value !== "string") return void 0;
@@ -87,7 +87,7 @@ function normalizeMessagesDates(messages) {
87
87
  return messages.map(normalizeMessageDates);
88
88
  }
89
89
  //#endregion
90
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/ag-ui-usage.js
90
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/ag-ui-usage.js
91
91
  function definedDetails(value) {
92
92
  return Object.keys(value).length > 0 ? value : void 0;
93
93
  }
@@ -132,7 +132,7 @@ function fromSpecTokenUsage(usage, leftover) {
132
132
  };
133
133
  }
134
134
  //#endregion
135
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/merge-metadata.js
135
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/merge-metadata.js
136
136
  function isPlainRecord$1(value) {
137
137
  return value != null && typeof value === "object" && !Array.isArray(value);
138
138
  }
@@ -211,7 +211,19 @@ let EventType = /* @__PURE__ */ function(EventType) {
211
211
  return EventType;
212
212
  }({});
213
213
  //#endregion
214
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/interrupt-serialization.js
214
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/reasoning-encrypted-value.js
215
+ /**
216
+ * Id prefix for the reasoning message of a redacted thinking block (Anthropic
217
+ * `redacted_thinking`). AG-UI's `encryptedValue` does not say what kind of
218
+ * bytes it holds (ag-ui-protocol/ag-ui#2884), so the id that `entityId` points
219
+ * to says it. AG-UI clients keep message ids, but they drop event metadata.
220
+ */
221
+ var REDACTED_THINKING_ID_PREFIX = "redacted_thinking-";
222
+ function isRedactedThinkingId(id) {
223
+ return typeof id === "string" && id.startsWith("redacted_thinking-");
224
+ }
225
+ //#endregion
226
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/interrupt-serialization.js
215
227
  var SHA256_K = new Uint32Array([
216
228
  1116352408,
217
229
  1899447441,
@@ -386,7 +398,7 @@ function cloneAndDeepFreezeJson(value) {
386
398
  return clone;
387
399
  }
388
400
  //#endregion
389
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/interrupts.js
401
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/interrupts.js
390
402
  function canonicalizeInterruptResolutions(resolutions) {
391
403
  const frozen = cloneAndDeepFreezeJson([...resolutions].sort((left, right) => left.interruptId.localeCompare(right.interruptId)));
392
404
  const canonicalResolutions = canonicalInterruptJson(frozen);
@@ -397,7 +409,7 @@ function canonicalizeInterruptResolutions(resolutions) {
397
409
  });
398
410
  }
399
411
  //#endregion
400
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/schema-converter.js
412
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/schema-converter.js
401
413
  /**
402
414
  * Build a JSONSchema object from any plain key/value source. The `JSONSchema`
403
415
  * interface's `[key: string]: any` index signature makes every property
@@ -550,16 +562,17 @@ function makeStructuredOutputCompatible(schema, originalRequired = []) {
550
562
  *
551
563
  * - Standard JSON Schemas are rebuilt structurally (dropping `$schema`, which
552
564
  * LLM providers ignore) and given the explicit `type`/`properties`/`required`
553
- * defaults object shapes need downstream.
565
+ * defaults object shapes need downstream. `io` picks the `input` (default)
566
+ * or `output` view.
554
567
  * - Plain `JSONSchema` inputs are rebuilt into the typed view; non-object inputs
555
568
  * are surfaced untouched (they can't be widened).
556
569
  * - Standard Schema validators lacking a `~standard.jsonSchema` converter throw
557
570
  * with actionable guidance, rather than shipping `{ '~standard': … }` to the
558
571
  * provider and producing an opaque downstream error.
559
572
  */
560
- function toTypedJsonSchema(schema) {
573
+ function toTypedJsonSchema(schema, io = "input") {
561
574
  if (isStandardJSONSchema(schema)) {
562
- const result = toJsonSchema$2(schema["~standard"].jsonSchema.input({ target: "draft-07" }));
575
+ const result = toJsonSchema$2(schema["~standard"].jsonSchema[io]({ target: "draft-07" }));
563
576
  if ("properties" in result && !result.type) result.type = "object";
564
577
  if (result.type === "object" && !("properties" in result)) result.properties = {};
565
578
  if (result.type === "object" && !("required" in result)) result.required = [];
@@ -631,9 +644,9 @@ function toTypedJsonSchema(schema) {
631
644
  */
632
645
  function convertSchemaToJsonSchema(schema, options = {}) {
633
646
  if (!schema) return void 0;
634
- const { forStructuredOutput = false } = options;
647
+ const { forStructuredOutput = false, io } = options;
635
648
  if (!forStructuredOutput && !isStandardJSONSchema(schema) && !isStandardSchema(schema)) return schema;
636
- const base = toTypedJsonSchema(schema);
649
+ const base = toTypedJsonSchema(schema, io);
637
650
  if (!base || typeof base !== "object") return base;
638
651
  if (!forStructuredOutput) return base;
639
652
  return makeStructuredOutputCompatible(base, base.required || []).schema;
@@ -664,7 +677,7 @@ async function validateWithStandardSchema(schema, data) {
664
677
  };
665
678
  }
666
679
  //#endregion
667
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/approval-schema.js
680
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/approval-schema.js
668
681
  var jsonSchemaKeywords = /* @__PURE__ */ new Set([
669
682
  "$schema",
670
683
  "$id",
@@ -776,7 +789,7 @@ function hashSchemaInput(schema) {
776
789
  return digestInterruptJson(canonicalInterruptJson(schemaToWire(schema).jsonSchema ?? { standardValidator: "unserialized" }));
777
790
  }
778
791
  //#endregion
779
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/interrupt-resume.js
792
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/interrupt-resume.js
780
793
  /**
781
794
  * The `Interrupt.metadata` key under which this package's resume binding
782
795
  * travels.
@@ -897,7 +910,7 @@ function readInterruptBinding(descriptor) {
897
910
  };
898
911
  }
899
912
  //#endregion
900
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/interrupt-definition.js
913
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/interrupt-definition.js
901
914
  var INTERRUPT_PAYLOAD_METADATA_KEY = "tanstack:interruptPayload";
902
915
  function schemaJson(schema, name) {
903
916
  if (!isStandardJSONSchema(schema)) throw new TypeError(`${name} must be a Standard Schema with a JSON Schema converter.`);
@@ -922,7 +935,7 @@ function hashInterruptDefinitionSchema(schema) {
922
935
  return digestInterruptJson(schemaJson(schema, "Interrupt schema").canonicalJson);
923
936
  }
924
937
  //#endregion
925
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/generic-interrupt-continuation.js
938
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/generic-interrupt-continuation.js
926
939
  /**
927
940
  * `ResumeEntry.metadata` key for a first-party generic request.
928
941
  *
@@ -961,7 +974,7 @@ function genericInterruptContinuationFromDescriptor(interrupt) {
961
974
  };
962
975
  }
963
976
  //#endregion
964
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/provider-executed.js
977
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/provider-executed.js
965
978
  /**
966
979
  * Narrow a tool call's opaque `metadata` to the provider-executed convention.
967
980
  * Returns the typed metadata when the call is provider-executed, else `null`.
@@ -991,7 +1004,7 @@ function isAssistantSegmentOf(id, parentId) {
991
1004
  return id?.startsWith(`${parentId}-segment-`) === true;
992
1005
  }
993
1006
  //#endregion
994
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/tool-result.js
1007
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/tool-result.js
995
1008
  var CONTENT_PART_TYPES = /* @__PURE__ */ new Set([
996
1009
  "text",
997
1010
  "image",
@@ -1052,7 +1065,7 @@ function normalizeToolResult(result) {
1052
1065
  return JSON.stringify(result);
1053
1066
  }
1054
1067
  //#endregion
1055
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/subagent-wire.js
1068
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/subagent-wire.js
1056
1069
  function isRecord$2(value) {
1057
1070
  return typeof value === "object" && value !== null && !Array.isArray(value);
1058
1071
  }
@@ -1156,7 +1169,7 @@ function splitSubagentWire(messages) {
1156
1169
  };
1157
1170
  }
1158
1171
  //#endregion
1159
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/messages.js
1172
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/messages.js
1160
1173
  function isRecord$1(value) {
1161
1174
  return typeof value === "object" && value !== null && !Array.isArray(value);
1162
1175
  }
@@ -1184,6 +1197,10 @@ function encryptedValueFrom(value) {
1184
1197
  }
1185
1198
  return nonEmptyString(tanstackMetadata(value)?.signature);
1186
1199
  }
1200
+ /** `{ redacted: true }` when a reasoning message's id marks a redacted block. */
1201
+ function redactedFrom(value) {
1202
+ return "id" in value && isRedactedThinkingId(value.id) ? { redacted: true } : {};
1203
+ }
1187
1204
  /**
1188
1205
  * Collapse an array of ContentParts into the most compact ModelMessage content:
1189
1206
  * - Empty array → null
@@ -1456,7 +1473,8 @@ function buildAssistantMessages(uiMessage) {
1456
1473
  if (current.toolCalls.some(isProviderExecutedToolCall)) flushSegment();
1457
1474
  pendingThinking.push({
1458
1475
  content: part.content,
1459
- ...part.signature && { signature: part.signature }
1476
+ ...part.signature && { signature: part.signature },
1477
+ ...part.redacted && { redacted: true }
1460
1478
  });
1461
1479
  }
1462
1480
  break;
@@ -1538,7 +1556,8 @@ function modelMessageToUIMessage(modelMessage, id) {
1538
1556
  parts.push({
1539
1557
  type: "thinking",
1540
1558
  content: thinking.content,
1541
- ...thinking.signature && { signature: thinking.signature }
1559
+ ...thinking.signature && { signature: thinking.signature },
1560
+ ...thinking.redacted && { redacted: true }
1542
1561
  });
1543
1562
  }
1544
1563
  const structuredOutput = modelMessage.structuredOutput ?? snapshotStructuredOutput(tanstackMetadata(modelMessage)?.structuredOutput);
@@ -1683,7 +1702,8 @@ function aguiSnapshotMessageToUIMessage(message) {
1683
1702
  parts: content || signature !== void 0 ? [{
1684
1703
  type: "thinking",
1685
1704
  content,
1686
- ...signature !== void 0 ? { signature } : {}
1705
+ ...signature !== void 0 ? { signature } : {},
1706
+ ...redactedFrom(message)
1687
1707
  }] : []
1688
1708
  });
1689
1709
  }
@@ -1798,7 +1818,7 @@ function generateMessageId() {
1798
1818
  return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
1799
1819
  }
1800
1820
  //#endregion
1801
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/ag-ui-wire.js
1821
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/ag-ui-wire.js
1802
1822
  function isRecord(value) {
1803
1823
  return typeof value === "object" && value !== null && !Array.isArray(value);
1804
1824
  }
@@ -2081,7 +2101,8 @@ function collectToolCalls(parts) {
2081
2101
  return calls.length > 0 ? calls : void 0;
2082
2102
  }
2083
2103
  function deriveReasoningId(messageId, part) {
2084
- return `${messageId}-reasoning-${part.id ?? hashContent(part.content)}`;
2104
+ const id = `${messageId}-reasoning-${part.id ?? hashContent(part.content)}`;
2105
+ return part.type === "thinking" && part.redacted ? `${REDACTED_THINKING_ID_PREFIX}${id}` : id;
2085
2106
  }
2086
2107
  function deriveToolMessageId(toolCallId) {
2087
2108
  return `tool-${toolCallId}`;
@@ -2111,7 +2132,7 @@ function hashContent(s) {
2111
2132
  return Math.abs(h).toString(36);
2112
2133
  }
2113
2134
  //#endregion
2114
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/restore-inbound-chunk.js
2135
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/restore-inbound-chunk.js
2115
2136
  /**
2116
2137
  * Rebuild TanStack `TokenUsage` (`promptTokens`) from spec `usage[]` plus
2117
2138
  * leftover fields in `metadata.tanstack.usage`. Restore in-process aliases
@@ -2148,7 +2169,7 @@ function restoreInboundChunk(chunk) {
2148
2169
  return next;
2149
2170
  }
2150
2171
  //#endregion
2151
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/tool-definition.js
2172
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/tools/tool-definition.js
2152
2173
  /**
2153
2174
  * Create an isomorphic tool definition that can be used directly or instantiated for server/client
2154
2175
  *
@@ -2246,7 +2267,7 @@ function toolDefinition(config) {
2246
2267
  };
2247
2268
  }
2248
2269
  //#endregion
2249
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/chunk-ids.js
2270
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/chunk-ids.js
2250
2271
  function stringField(value) {
2251
2272
  return typeof value === "string" && value !== "" ? value : void 0;
2252
2273
  }
@@ -2349,7 +2370,7 @@ var require_options = /* @__PURE__ */ __commonJSMin(((exports) => {
2349
2370
  exports.default = exports.Allow;
2350
2371
  }));
2351
2372
  //#endregion
2352
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/json-parser.js
2373
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/json-parser.js
2353
2374
  var import_dist = (/* @__PURE__ */ __commonJSMin(((exports) => {
2354
2375
  var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
2355
2376
  if (k2 === void 0) k2 = k;
@@ -2571,7 +2592,7 @@ function parsePartialJSON(jsonString) {
2571
2592
  return defaultJSONParser.parse(jsonString);
2572
2593
  }
2573
2594
  //#endregion
2574
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/strategies.js
2595
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/strategies.js
2575
2596
  /**
2576
2597
  * Immediate Strategy - emit on every chunk (default behavior)
2577
2598
  */
@@ -2581,7 +2602,7 @@ var ImmediateStrategy = class {
2581
2602
  }
2582
2603
  };
2583
2604
  //#endregion
2584
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/utilities/errors.js
2605
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/utilities/errors.js
2585
2606
  /**
2586
2607
  * Convert an AG-UI RUN_ERROR event to the Error shape exposed to consumers.
2587
2608
  * Preserves the provider code and sanitized raw event when available, while
@@ -2595,7 +2616,7 @@ function runErrorEventToError(chunk) {
2595
2616
  return error;
2596
2617
  }
2597
2618
  //#endregion
2598
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/message-updaters.js
2619
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/message-updaters.js
2599
2620
  /**
2600
2621
  * Message Updaters (Internal)
2601
2622
  *
@@ -2880,11 +2901,13 @@ function updateThinkingPart(messages, messageId, stepId, content, signature) {
2880
2901
  if (candidate?.type === "thinking") adopted = candidate;
2881
2902
  }
2882
2903
  const nextSignature = signature ?? adopted?.signature;
2904
+ const redacted = isRedactedThinkingId(stepId) || adopted?.redacted === true;
2883
2905
  const thinkingPart = {
2884
2906
  type: "thinking",
2885
2907
  content,
2886
2908
  stepId,
2887
- ...nextSignature && { signature: nextSignature }
2909
+ ...nextSignature && { signature: nextSignature },
2910
+ ...redacted && { redacted: true }
2888
2911
  };
2889
2912
  if (thinkingPartIndex >= 0) parts[thinkingPartIndex] = thinkingPart;
2890
2913
  else parts.push(thinkingPart);
@@ -2895,7 +2918,7 @@ function updateThinkingPart(messages, messageId, stepId, content, signature) {
2895
2918
  });
2896
2919
  }
2897
2920
  //#endregion
2898
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/processor.js
2921
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/activities/chat/stream/processor.js
2899
2922
  /**
2900
2923
  * Unified Stream Processor
2901
2924
  *
@@ -4679,14 +4702,14 @@ function createReplayStream(recording) {
4679
4702
  } };
4680
4703
  }
4681
4704
  //#endregion
4682
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/byok/providers.js
4705
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/byok/providers.js
4683
4706
  /** `[a-z][a-z0-9-]{0,63}` — lowercase, no underscores, max 64 chars. */
4684
4707
  var BYOK_PROVIDER_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
4685
4708
  function isProviderId(value) {
4686
4709
  return typeof value === "string" && BYOK_PROVIDER_ID_PATTERN.test(value);
4687
4710
  }
4688
4711
  //#endregion
4689
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/byok/missing.js
4712
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/byok/missing.js
4690
4713
  function isByokMissingBody(value) {
4691
4714
  if (typeof value !== "object" || value === null) return false;
4692
4715
  if (!("error" in value)) return false;
@@ -4699,7 +4722,7 @@ function isByokMissingBody(value) {
4699
4722
  return true;
4700
4723
  }
4701
4724
  //#endregion
4702
- //#region node_modules/.deno/@tanstack+ai@0.63.0/node_modules/@tanstack/ai/dist/esm/byok/errors.js
4725
+ //#region node_modules/.deno/@tanstack+ai@0.64.0/node_modules/@tanstack/ai/dist/esm/byok/errors.js
4703
4726
  var ByokMissingError = class extends Error {
4704
4727
  provider;
4705
4728
  constructor(provider) {
@@ -4725,7 +4748,7 @@ var ByokUnresolvedProviderError = class extends Error {
4725
4748
  }
4726
4749
  };
4727
4750
  //#endregion
4728
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/connection-adapters.js
4751
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/connection-adapters.js
4729
4752
  /**
4730
4753
  * Associates connect-wrapped chunks with the run they were produced under.
4731
4754
  * Content events (TEXT_MESSAGE_CONTENT, TOOL_CALL_*, …) carry no `runId` of
@@ -5420,7 +5443,7 @@ async function* abortableIterable(iterable, signal) {
5420
5443
  }
5421
5444
  }
5422
5445
  //#endregion
5423
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/interrupt-manager.js
5446
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/interrupt-manager.js
5424
5447
  function isClientOwnedInterrupt(item) {
5425
5448
  return item.resumable;
5426
5449
  }
@@ -6362,7 +6385,7 @@ var InterruptManager = class {
6362
6385
  }
6363
6386
  };
6364
6387
  //#endregion
6365
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/byok/client.js
6388
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/byok/client.js
6366
6389
  var registeredProviderIds = /* @__PURE__ */ new WeakMap();
6367
6390
  /**
6368
6391
  * Provider to stamp when the caller did not pass a slug.
@@ -6375,7 +6398,7 @@ function byokFallbackProviderId(byok) {
6375
6398
  if (registered.length === 1) return registered[0];
6376
6399
  }
6377
6400
  //#endregion
6378
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/byok/resolve.js
6401
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/byok/resolve.js
6379
6402
  function resolveByokProviderId(byokProvider, ...candidates) {
6380
6403
  const fromFn = byokProvider?.();
6381
6404
  if (isProviderId(fromFn)) return fromFn;
@@ -6391,7 +6414,7 @@ async function prepareResolvedByokHeaders(byok, provider) {
6391
6414
  return byok.headers(provider);
6392
6415
  }
6393
6416
  //#endregion
6394
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/events.js
6417
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/events.js
6395
6418
  /**
6396
6419
  * Abstract base class for ChatClient event emission
6397
6420
  */
@@ -6565,7 +6588,7 @@ var ChatClientEventEmitter = class {
6565
6588
  }
6566
6589
  };
6567
6590
  //#endregion
6568
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/devtools-noop.js
6591
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/devtools-noop.js
6569
6592
  var NoOpChatClientEventEmitter = class extends ChatClientEventEmitter {
6570
6593
  emitEvent() {}
6571
6594
  };
@@ -6610,7 +6633,7 @@ var NoOpChatDevtoolsBridge = class {
6610
6633
  };
6611
6634
  var createNoOpChatDevtoolsBridge = (options) => new NoOpChatDevtoolsBridge(options);
6612
6635
  //#endregion
6613
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/client-persistor.js
6636
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/client-persistor.js
6614
6637
  /** Normalize a raw `getItem` result (legacy bare array or combined record). */
6615
6638
  function normalizePersistedState(raw) {
6616
6639
  if (Array.isArray(raw)) return { messages: raw };
@@ -6874,7 +6897,7 @@ var ChatPersistor = class {
6874
6897
  }
6875
6898
  };
6876
6899
  //#endregion
6877
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/cleared-stream-tracker.js
6900
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/cleared-stream-tracker.js
6878
6901
  function getChunkToolCallId(chunk) {
6879
6902
  return "toolCallId" in chunk && typeof chunk.toolCallId === "string" ? chunk.toolCallId : void 0;
6880
6903
  }
@@ -6967,7 +6990,7 @@ var ClearedStreamTracker = class {
6967
6990
  }
6968
6991
  };
6969
6992
  //#endregion
6970
- //#region node_modules/.deno/@tanstack+ai-client@0.36.0/node_modules/@tanstack/ai-client/dist/esm/chat-client.js
6993
+ //#region node_modules/.deno/@tanstack+ai-client@0.36.1/node_modules/@tanstack/ai-client/dist/esm/chat-client.js
6971
6994
  var STREAM_PROCESSING_BUDGET_MS = 8;
6972
6995
  function yieldToHost() {
6973
6996
  const { scheduler } = globalThis;
@@ -9043,16 +9066,11 @@ function toJsonSchema(parameters) {
9043
9066
  properties: {}
9044
9067
  };
9045
9068
  if (!("~standard" in parameters)) return parameters;
9046
- try {
9047
- return convertSchemaToJsonSchema(parameters);
9048
- } catch {
9049
- return { type: "object" };
9050
- }
9069
+ return "jsonSchema" in parameters["~standard"] ? convertSchemaToJsonSchema(parameters) : { type: "object" };
9051
9070
  }
9052
9071
  async function validateParameters(parameters, input) {
9053
- if (!parameters || !("~standard" in parameters)) return input;
9054
- const result = await parameters["~standard"].validate(input);
9055
- return result.issues ? null : result.value ?? {};
9072
+ const result = await validateWithStandardSchema(parameters, input);
9073
+ return result.success ? result.data ?? {} : null;
9056
9074
  }
9057
9075
  //#endregion
9058
9076
  //#region src/core/agent-tools.ts
@@ -9558,9 +9576,116 @@ function describeSandboxCommandRun(run) {
9558
9576
  return parts.join(" · ");
9559
9577
  }
9560
9578
  //#endregion
9579
+ //#region src/core/threads.ts
9580
+ function newUuid() {
9581
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
9582
+ bytes[6] = bytes[6] & 15 | 64;
9583
+ bytes[8] = bytes[8] & 63 | 128;
9584
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
9585
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
9586
+ }
9587
+ function threadFromRecord(record, hasMessages = true) {
9588
+ return {
9589
+ id: record.id,
9590
+ title: record.title,
9591
+ agentId: record.agent_id,
9592
+ version: record.version,
9593
+ role: record.role,
9594
+ writerActive: record.writer_active,
9595
+ hasMessages,
9596
+ updatedAt: record.updated_at
9597
+ };
9598
+ }
9599
+ function savedToolCallId(messageId, partId, targetId) {
9600
+ return `saved:${messageId}:${partId}${targetId ? `:${targetId}` : ""}`;
9601
+ }
9602
+ async function loadThreadMessages(id, options, pageAfter, liveToolMessageIds = /* @__PURE__ */ new Map()) {
9603
+ const page = await listChatMessages(id, {
9604
+ page_size: 100,
9605
+ ...pageAfter ? { page_after: pageAfter } : {}
9606
+ }, options);
9607
+ const pendingInteractions = page.pending_interactions.map((pending) => ({
9608
+ sourceMessageId: pending.source_message_id,
9609
+ sourcePartId: pending.source_part_id,
9610
+ responseTargetId: pending.response_target_id,
9611
+ toolCallId: liveToolMessageIds.get(pending.tool_call_id) === pending.source_message_id ? pending.tool_call_id : savedToolCallId(pending.source_message_id, pending.source_part_id, pending.response_target_id),
9612
+ targetTenantUserId: pending.target_tenant_user_id,
9613
+ targetClientId: pending.target_client_id,
9614
+ executionLocation: pending.execution_location
9615
+ }));
9616
+ return {
9617
+ ...page,
9618
+ pendingInteractions
9619
+ };
9620
+ }
9621
+ function projectThreadMessages(records, pendingInteractions = [], liveToolMessageIds = /* @__PURE__ */ new Map()) {
9622
+ const messages = [];
9623
+ const calls = /* @__PURE__ */ new Map();
9624
+ for (const message of records) {
9625
+ if (message.role === "tool") {
9626
+ for (const part of message.parts) {
9627
+ const call = calls.get(savedToolCallId(message.source_assistant_message_id, message.source_tool_part_id, message.response_target_id));
9628
+ if (!call) continue;
9629
+ call.output = part.output ?? null;
9630
+ Object.assign(call, { resultOutcome: part.outcome });
9631
+ call.state = part.outcome === "succeeded" ? "complete" : "error";
9632
+ }
9633
+ continue;
9634
+ }
9635
+ const parts = message.parts.flatMap((part) => {
9636
+ const source = part.source;
9637
+ if ((part.type === "image" || part.type === "document") && source && typeof source === "object" && !Array.isArray(source) && "type" in source && source.type === "attachment") return [{
9638
+ ...part,
9639
+ source: {
9640
+ ...source,
9641
+ type: "url",
9642
+ value: ""
9643
+ },
9644
+ savedAttachmentId: part.id
9645
+ }];
9646
+ if (part.type !== "tool-call") return [part];
9647
+ const toolCallId = String(part.toolCallId);
9648
+ const targets = Array.isArray(part.targets) ? part.targets.flatMap((target) => target && typeof target === "object" && !Array.isArray(target) && "id" in target && typeof target.id === "string" ? [target.id] : []) : pendingInteractions.filter((pending) => pending.sourceMessageId === message.id && pending.sourcePartId === part.id).map((pending) => pending.responseTargetId);
9649
+ return (targets.length ? targets : [void 0]).map((responseTargetId) => {
9650
+ const savedId = savedToolCallId(message.id, String(part.id), responseTargetId);
9651
+ const call = {
9652
+ ...part,
9653
+ id: liveToolMessageIds.get(toolCallId) === message.id ? toolCallId : savedId,
9654
+ upstreamToolCallId: toolCallId,
9655
+ applicationPartId: part.id,
9656
+ sourceMessageId: message.id,
9657
+ responseTargetId
9658
+ };
9659
+ calls.set(savedId, call);
9660
+ return call;
9661
+ });
9662
+ });
9663
+ messages.push({
9664
+ id: message.id,
9665
+ role: message.role,
9666
+ parts,
9667
+ createdAt: new Date(message.created_at),
9668
+ metadata: { astralbeam: {
9669
+ state: message.state,
9670
+ authorTenantUserId: message.author_tenant_user_id
9671
+ } }
9672
+ });
9673
+ }
9674
+ return messages;
9675
+ }
9676
+ /** Cache selection within this tab. Stored IDs are reauthorized before loading history. */
9677
+ function selectedThread(key, id) {
9678
+ try {
9679
+ if (id === void 0) return globalThis.sessionStorage?.getItem(key) ?? void 0;
9680
+ if (id === null) globalThis.sessionStorage?.removeItem(key);
9681
+ else globalThis.sessionStorage?.setItem(key, id);
9682
+ } catch {}
9683
+ }
9684
+ //#endregion
9561
9685
  //#region src/core/session.ts
9562
9686
  const CORE_OPTION_KEYS = Object.keys({
9563
9687
  agentId: true,
9688
+ threadId: true,
9564
9689
  apiUrl: true,
9565
9690
  fetchAstralBeamToken: true,
9566
9691
  tools: true,
@@ -9592,7 +9717,15 @@ function createAstralBeamChat(options, deferStart = false) {
9592
9717
  sandbox: {
9593
9718
  files: [],
9594
9719
  commands: []
9595
- }
9720
+ },
9721
+ thread: void 0,
9722
+ threadLoading: false,
9723
+ threadLoadFailed: false,
9724
+ messagesCursor: void 0,
9725
+ olderMessagesLoading: false,
9726
+ unsentMessage: void 0,
9727
+ activeToolCallIds: [],
9728
+ pendingInteractions: []
9596
9729
  };
9597
9730
  const update = (next) => {
9598
9731
  state = {
@@ -9610,24 +9743,89 @@ function createAstralBeamChat(options, deferStart = false) {
9610
9743
  let unsubscribeAuthentication;
9611
9744
  let identity;
9612
9745
  let started = false;
9746
+ let selectionGeneration = 0;
9747
+ let navigationGeneration = 0;
9748
+ let historyGeneration = 0;
9749
+ let hydration = Promise.resolve();
9750
+ let requestController = new AbortController();
9751
+ let threadKey;
9752
+ let startWithNewThread = false;
9753
+ let threadRecords = [];
9754
+ const clientId = newUuid();
9755
+ let pendingSend;
9756
+ let sending = false;
9757
+ const pendingSends = /* @__PURE__ */ new Map();
9758
+ const acceptPendingSend = () => {
9759
+ if (!pendingSend || pendingSend.accepted) return;
9760
+ pendingSend.accepted = true;
9761
+ if (state.thread) update({ thread: {
9762
+ ...state.thread,
9763
+ hasMessages: true
9764
+ } });
9765
+ pendingSend.callbacks?.onAccepted?.();
9766
+ };
9767
+ const liveToolCalls = /* @__PURE__ */ new Set();
9768
+ const liveToolMessageIds = /* @__PURE__ */ new Map();
9769
+ const historicalToolIds = /* @__PURE__ */ new Map();
9770
+ let toolResults = /* @__PURE__ */ new Map();
9771
+ const threadToolResults = /* @__PURE__ */ new Map();
9772
+ const toolResultKey = (id) => {
9773
+ const messageId = liveToolMessageIds.get(id);
9774
+ return messageId ? `live:${messageId}:${id}` : id;
9775
+ };
9776
+ const retainToolResults = (history) => {
9777
+ const retained = /* @__PURE__ */ new Map();
9778
+ for (const pending of history.pending_interactions) {
9779
+ const nativeKey = `live:${pending.source_message_id}:${pending.tool_call_id}`;
9780
+ const savedKey = savedToolCallId(pending.source_message_id, pending.source_part_id, pending.response_target_id);
9781
+ const result = toolResults.get(nativeKey) ?? toolResults.get(savedKey);
9782
+ if (!result) continue;
9783
+ retained.set(savedKey, result);
9784
+ if (liveToolMessageIds.get(pending.tool_call_id) === pending.source_message_id) retained.set(nativeKey, result);
9785
+ }
9786
+ toolResults.clear();
9787
+ for (const [id, result] of retained) toolResults.set(id, result);
9788
+ };
9789
+ const reportError = (error) => {
9790
+ if (error instanceof Error && error.name === "AbortError") return;
9791
+ const failure = error instanceof Error ? error : new Error(String(error));
9792
+ update({
9793
+ error: failure,
9794
+ status: "error"
9795
+ });
9796
+ live.streamCallbacks?.onError?.(failure);
9797
+ };
9798
+ const requestOptions = async () => {
9799
+ const generation = selectionGeneration;
9800
+ const token = await getValidChatAuthToken(authentication);
9801
+ if (generation !== selectionGeneration) throw new DOMException("Conversation changed", "AbortError");
9802
+ return {
9803
+ apiUrl: authentication.apiUrl,
9804
+ astralBeamToken: token,
9805
+ cache: "no-store",
9806
+ signal: AbortSignal.any([requestController.signal, authentication.session.abortController.signal]),
9807
+ fetchClient: (input, init) => fetchAuthenticatedChat({
9808
+ ...authentication,
9809
+ input,
9810
+ init
9811
+ })
9812
+ };
9813
+ };
9613
9814
  let capabilitiesGeneration = 0;
9614
9815
  let capabilityIdentity;
9615
9816
  const resolveCapabilities = async () => {
9616
9817
  const generation = ++capabilitiesGeneration;
9818
+ const selection = selectionGeneration;
9819
+ if (state.thread?.agentId === null) {
9820
+ update({ capabilities: { attachments: false } });
9821
+ return;
9822
+ }
9823
+ const agentId = state.thread ? state.thread.agentId : live.agentId;
9617
9824
  try {
9618
- const token = await getValidChatAuthToken(authentication);
9619
- if (generation !== capabilitiesGeneration) return;
9620
- const body = await getChatConfig(live.agentId ? { agentId: live.agentId } : {}, {
9621
- apiUrl: authentication.apiUrl,
9622
- astralBeamToken: token,
9623
- signal: authentication.session.abortController.signal,
9624
- fetchClient: (input, init) => fetchAuthenticatedChat({
9625
- ...authentication,
9626
- input,
9627
- init
9628
- })
9629
- });
9630
- if (generation !== capabilitiesGeneration) return;
9825
+ const auth = await requestOptions();
9826
+ if (generation !== capabilitiesGeneration || selection !== selectionGeneration) return;
9827
+ const body = await getChatConfig(agentId ? { agentId } : {}, auth);
9828
+ if (generation !== capabilitiesGeneration || selection !== selectionGeneration) return;
9631
9829
  const attachments = body.capabilities?.attachments !== false;
9632
9830
  update({ capabilities: { attachments } });
9633
9831
  debug?.("mount", "agent capabilities resolved", { attachments });
@@ -9636,23 +9834,38 @@ function createAstralBeamChat(options, deferStart = false) {
9636
9834
  }
9637
9835
  };
9638
9836
  const renderCleanups = /* @__PURE__ */ new Map();
9837
+ const restoredWidgets = /* @__PURE__ */ new Set();
9838
+ const restoringWidgets = /* @__PURE__ */ new Set();
9639
9839
  const disposeRenders = () => {
9640
9840
  for (const cleanup of renderCleanups.values()) cleanup();
9641
9841
  renderCleanups.clear();
9842
+ restoredWidgets.clear();
9843
+ restoringWidgets.clear();
9642
9844
  };
9643
9845
  const renderWidget = async (input, toolCallId) => {
9846
+ const generation = selectionGeneration;
9644
9847
  const widgets = live.widgets ?? {};
9645
9848
  if (!Object.hasOwn(widgets, input.widget)) throw new Error(`Unknown widget "${input.widget}"`);
9646
9849
  const declaration = widgets[input.widget];
9647
9850
  const validated = await validateParameters(declaration?.parameters, input.props ?? {});
9851
+ if (generation !== selectionGeneration || state.thread?.role === "viewer") return {
9852
+ widget: input.widget,
9853
+ rendered: false
9854
+ };
9855
+ if (declaration !== live.widgets?.[input.widget]) return renderWidget(input, toolCallId);
9648
9856
  if (validated == null) throw new Error(`Props for widget "${input.widget}" failed validation`);
9857
+ const onRenderWidget = live.onRenderWidget;
9858
+ if (!onRenderWidget) return {
9859
+ widget: input.widget,
9860
+ rendered: false
9861
+ };
9649
9862
  renderCleanups.get(toolCallId)?.();
9650
9863
  renderCleanups.delete(toolCallId);
9651
9864
  let registered;
9652
9865
  const release = () => {
9653
9866
  if (renderCleanups.get(toolCallId) === registered) renderCleanups.delete(toolCallId);
9654
9867
  };
9655
- const cleanup = live.onRenderWidget?.({
9868
+ const cleanup = onRenderWidget({
9656
9869
  widget: input.widget,
9657
9870
  props: validated,
9658
9871
  toolCallId,
@@ -9662,12 +9875,68 @@ function createAstralBeamChat(options, deferStart = false) {
9662
9875
  registered = cleanup;
9663
9876
  renderCleanups.set(toolCallId, cleanup);
9664
9877
  }
9878
+ restoredWidgets.add(toolCallId);
9665
9879
  return {
9666
9880
  widget: input.widget,
9667
- rendered: live.onRenderWidget !== void 0
9881
+ rendered: true
9668
9882
  };
9669
9883
  };
9670
- const buildTools = () => buildAgentTools(live.widgets ?? {}, live.tools ?? {}, renderWidget, debug);
9884
+ const restoreCompletedWidgets = (messages) => {
9885
+ if (state.thread?.role === "viewer") return;
9886
+ const renderIds = /* @__PURE__ */ new Map();
9887
+ for (const message of state.messages) for (const part of message.parts) {
9888
+ if (part.type !== "tool-call" || part.name !== "render_widget") continue;
9889
+ const stored = part;
9890
+ if (!stored.widgetRenderId) continue;
9891
+ renderIds.set(savedToolCallId(message.id, part.id), stored.widgetRenderId);
9892
+ if (stored.applicationPartId) renderIds.set(savedToolCallId(message.id, stored.applicationPartId, stored.responseTargetId), stored.widgetRenderId);
9893
+ }
9894
+ for (const message of messages) for (const part of message.parts) {
9895
+ if (part.type !== "tool-call" || part.name !== "render_widget" || part.state !== "complete" || !part.input) continue;
9896
+ const stored = part;
9897
+ const upstream = stored.upstreamToolCallId;
9898
+ const renderId = stored.widgetRenderId ?? renderIds.get(savedToolCallId(message.id, part.id)) ?? (stored.applicationPartId ? renderIds.get(savedToolCallId(message.id, stored.applicationPartId, stored.responseTargetId)) : void 0) ?? (upstream && liveToolMessageIds.get(upstream) === message.id && restoredWidgets.has(upstream) ? upstream : part.id);
9899
+ stored.widgetRenderId = renderId;
9900
+ if (restoredWidgets.has(renderId) || restoringWidgets.has(renderId)) continue;
9901
+ restoringWidgets.add(renderId);
9902
+ const generation = selectionGeneration;
9903
+ const { widgets, onRenderWidget } = live;
9904
+ renderWidget(part.input, renderId).catch((error) => debug?.("error", "Saved widget could not be rendered", error)).finally(() => {
9905
+ if (generation !== selectionGeneration) return;
9906
+ restoringWidgets.delete(renderId);
9907
+ if (widgets !== live.widgets || onRenderWidget !== live.onRenderWidget) restoreCompletedWidgets(state.messages);
9908
+ });
9909
+ }
9910
+ };
9911
+ const buildTools = () => buildAgentTools(live.widgets ?? {}, live.tools ?? {}, renderWidget, debug).map((tool) => {
9912
+ if (!tool.execute) return tool;
9913
+ const execute = tool.execute;
9914
+ return {
9915
+ ...tool,
9916
+ execute: async (input, context) => {
9917
+ const toolCallId = context?.toolCallId;
9918
+ if (!toolCallId || !liveToolCalls.has(toolCallId)) throw new Error("A saved tool call cannot execute again. Send a new message to request it.");
9919
+ const results = toolResults;
9920
+ const key = toolResultKey(toolCallId);
9921
+ const resultIdentity = identity;
9922
+ const apiUrl = authentication.apiUrl;
9923
+ try {
9924
+ const output = await execute(input, context);
9925
+ if (identity === resultIdentity && authentication.apiUrl === apiUrl) results.set(key, {
9926
+ outcome: "succeeded",
9927
+ output: output ?? null
9928
+ });
9929
+ return output;
9930
+ } catch (error) {
9931
+ if (identity === resultIdentity && authentication.apiUrl === apiUrl) results.set(key, {
9932
+ outcome: "unknown",
9933
+ output: { error: error instanceof Error ? error.message : String(error) }
9934
+ });
9935
+ throw error;
9936
+ }
9937
+ }
9938
+ };
9939
+ });
9671
9940
  const declareTools = () => {
9672
9941
  const tools = buildTools();
9673
9942
  const agentTools = tools.map((tool) => {
@@ -9680,40 +9949,200 @@ function createAstralBeamChat(options, deferStart = false) {
9680
9949
  if (!sameAgentTools(state.agentTools, agentTools)) update({ agentTools });
9681
9950
  return tools;
9682
9951
  };
9683
- const forwardedProps = () => ({
9684
- ...live.agentId ? { agentId: live.agentId } : {},
9685
- ...live.debug ? { debug: true } : {}
9686
- });
9952
+ const forwardedProps = () => live.debug ? { debug: true } : {};
9953
+ const threadRequest = async (init, auth) => {
9954
+ const thread = state.thread;
9955
+ if (!thread || typeof init.body !== "string") throw new Error("Open a conversation before sending.");
9956
+ const generation = selectionGeneration;
9957
+ const body = JSON.parse(init.body);
9958
+ let path = getRunChatUrl();
9959
+ let payload;
9960
+ const submission = body.messages.at(-1)?.role === "user" ? pendingSend : void 0;
9961
+ const previouslyAttempted = submission?.tools !== void 0;
9962
+ if (submission) payload = {
9963
+ ...body,
9964
+ threadId: thread.id,
9965
+ resume: void 0,
9966
+ parentRunId: void 0,
9967
+ messages: [body.messages.at(-1)],
9968
+ tools: submission.tools ??= body.tools,
9969
+ forwardedProps: {
9970
+ ...body.forwardedProps,
9971
+ clientId
9972
+ }
9973
+ };
9974
+ else {
9975
+ scopeCompletedToolCalls();
9976
+ const history = await loadThreadMessages(thread.id, auth, void 0, liveToolMessageIds);
9977
+ if (generation !== selectionGeneration) throw new DOMException("Conversation changed", "AbortError");
9978
+ update({ thread: threadFromRecord(history.thread, history.messages.length > 0) });
9979
+ retainToolResults(history);
9980
+ const available = history.pendingInteractions.filter((pending) => toolResults.has(toolResultKey(pending.toolCallId)));
9981
+ const source = available.find((pending) => pending.toolCallId === body.messages.at(-1)?.toolCallId) ?? available[0];
9982
+ const results = history.pendingInteractions.flatMap((pending) => {
9983
+ const submitted = toolResults.get(toolResultKey(pending.toolCallId));
9984
+ return submitted && pending.sourceMessageId === source?.sourceMessageId ? [{
9985
+ source_message_id: pending.sourceMessageId,
9986
+ source_part_id: pending.sourcePartId,
9987
+ response_target_id: pending.responseTargetId,
9988
+ ...submitted
9989
+ }] : [];
9990
+ });
9991
+ if (results.length === 0) return new Response("", { headers: { "Content-Type": "text/event-stream" } });
9992
+ path = getResolveChatToolResultUrl(thread.id);
9993
+ payload = {
9994
+ client_id: clientId,
9995
+ run_id: body.runId,
9996
+ ...body.parentRunId ? { parent_run_id: body.parentRunId } : {},
9997
+ results
9998
+ };
9999
+ }
10000
+ liveToolCalls.clear();
10001
+ update({ activeToolCallIds: [] });
10002
+ const headers = new Headers(init.headers);
10003
+ if (submission) headers.set("Idempotency-Key", submission.key);
10004
+ const response = await astralBeamChatFetch(path, {
10005
+ ...auth,
10006
+ ...init,
10007
+ headers,
10008
+ body: JSON.stringify(payload)
10009
+ }).catch((error) => {
10010
+ if (submission && !previouslyAttempted && isAstralBeamApiError(error) && (error.status === 400 || error.status === 413 || error.status === 429)) delete submission.tools;
10011
+ throw error;
10012
+ });
10013
+ if (generation !== selectionGeneration) throw new DOMException("Conversation changed", "AbortError");
10014
+ if (response.headers.get("Content-Type")?.includes("application/json")) {
10015
+ const receipt = await response.json();
10016
+ if (generation !== selectionGeneration) throw new DOMException("Conversation changed", "AbortError");
10017
+ update({ thread: {
10018
+ ...state.thread,
10019
+ version: receipt.thread_version
10020
+ } });
10021
+ if (submission) acceptPendingSend();
10022
+ return new Response("", { headers: { "Content-Type": "text/event-stream" } });
10023
+ }
10024
+ return response;
10025
+ };
9687
10026
  const connection = fetchServerSentEvents(() => resolveApiUrl(getRunChatUrl(), authentication.apiUrl), async () => {
9688
10027
  const previousIdentity = identity;
9689
10028
  const token = await getValidChatAuthToken(authentication);
9690
10029
  if (previousIdentity !== void 0 && previousIdentity !== identity) throw new DOMException("Identity changed", "AbortError");
9691
- return { fetchClient: (_input, init) => astralBeamChatFetch(getRunChatUrl(), {
9692
- ...init,
9693
- apiUrl: authentication.apiUrl,
9694
- astralBeamToken: token,
9695
- fetchClient: (input, request) => fetchAuthenticatedChat({
9696
- ...authentication,
9697
- input,
9698
- init: request
9699
- })
9700
- }) };
10030
+ return { fetchClient: (_input, init) => {
10031
+ const auth = {
10032
+ apiUrl: authentication.apiUrl,
10033
+ astralBeamToken: token,
10034
+ ...init?.signal ? { signal: init.signal } : {},
10035
+ cache: "no-store",
10036
+ fetchClient: (input, request) => fetchAuthenticatedChat({
10037
+ ...authentication,
10038
+ input,
10039
+ init: request
10040
+ })
10041
+ };
10042
+ return threadRequest(init ?? {}, auth);
10043
+ } };
9701
10044
  });
9702
10045
  const connect = connection.connect;
9703
10046
  connection.connect = async function* (...args) {
9704
10047
  try {
9705
- yield* connect(...args);
10048
+ for await (const chunk of connect(...args)) {
10049
+ if (chunk.type === EventType.TOOL_CALL_START && chunk.parentMessageId) liveToolMessageIds.set(chunk.toolCallId, chunk.parentMessageId);
10050
+ if (chunk.type !== EventType.MESSAGES_SNAPSHOT) {
10051
+ yield chunk;
10052
+ continue;
10053
+ }
10054
+ let aliases;
10055
+ yield {
10056
+ ...chunk,
10057
+ messages: chunk.messages.map((message) => {
10058
+ if (message.role === "assistant") {
10059
+ aliases = historicalToolIds.get(message.id);
10060
+ return aliases && message.toolCalls ? {
10061
+ ...message,
10062
+ toolCalls: message.toolCalls.map((call) => ({
10063
+ ...call,
10064
+ id: aliases?.get(call.id) ?? call.id
10065
+ }))
10066
+ } : message;
10067
+ }
10068
+ if (message.role === "tool") return {
10069
+ ...message,
10070
+ toolCallId: aliases?.get(message.toolCallId) ?? message.toolCallId
10071
+ };
10072
+ aliases = void 0;
10073
+ return message;
10074
+ })
10075
+ };
10076
+ }
9706
10077
  } catch (error) {
9707
10078
  throw error instanceof Error && isAstralBeamApiError(error.cause) ? error.cause : error;
9708
10079
  }
9709
10080
  };
9710
- const createClient = () => {
10081
+ const createClient = (threadId = state.thread?.id) => {
10082
+ const generation = selectionGeneration;
10083
+ const adapter = { ...connection };
10084
+ delete adapter.hydrate;
10085
+ if (threadId) adapter.hydrate = (_id, options) => {
10086
+ const readGeneration = ++historyGeneration;
10087
+ if (!options?.before) update({ threadLoading: true });
10088
+ const read = (async () => {
10089
+ let history = await loadThreadMessages(threadId, await requestOptions(), options?.before, liveToolMessageIds);
10090
+ while (options?.before && history.messages.every((message) => message.role === "tool") && history.page_after) {
10091
+ const earlier = await loadThreadMessages(threadId, await requestOptions(), history.page_after, liveToolMessageIds);
10092
+ history = {
10093
+ ...earlier,
10094
+ messages: [...earlier.messages, ...history.messages]
10095
+ };
10096
+ }
10097
+ if (generation !== selectionGeneration || readGeneration !== historyGeneration) throw new DOMException("Conversation changed", "AbortError");
10098
+ retainToolResults(history);
10099
+ threadRecords = options?.before ? [...history.messages, ...threadRecords] : history.messages;
10100
+ const pageIds = new Set(history.messages.map((message) => message.id));
10101
+ const messages = projectThreadMessages(threadRecords, history.pendingInteractions, liveToolMessageIds).filter((message) => pageIds.has(message.id));
10102
+ if (history.thread.role === "viewer") disposeRenders();
10103
+ const agentChanged = state.thread?.agentId !== history.thread.agent_id;
10104
+ update({
10105
+ thread: threadFromRecord(history.thread, history.messages.length > 0),
10106
+ messagesCursor: history.page_after ?? void 0,
10107
+ pendingInteractions: history.pendingInteractions,
10108
+ threadLoadFailed: false
10109
+ });
10110
+ restoreCompletedWidgets(messages);
10111
+ if (!options?.before && agentChanged) resolveCapabilities();
10112
+ if (!options?.before && messages.length === 0) next.setMessagesManually([]);
10113
+ return {
10114
+ messages,
10115
+ activeRun: null,
10116
+ interrupts: null,
10117
+ page: history.page_after ? {
10118
+ truncated: true,
10119
+ cursor: history.page_after
10120
+ } : { truncated: false }
10121
+ };
10122
+ })();
10123
+ if (!options?.before) {
10124
+ hydration = read;
10125
+ const settled = (failed) => queueMicrotask(() => {
10126
+ if (generation === selectionGeneration && readGeneration === historyGeneration) update({
10127
+ threadLoading: false,
10128
+ threadLoadFailed: failed
10129
+ });
10130
+ });
10131
+ read.then(() => settled(false), () => settled(true));
10132
+ }
10133
+ return read;
10134
+ };
9711
10135
  const next = new ChatClient({
9712
- connection,
10136
+ connection: adapter,
10137
+ threadId: threadId ?? clientId,
10138
+ persistence: true,
10139
+ history: { pageSize: 100 },
10140
+ queue: "drop",
9713
10141
  tools: declareTools(),
9714
10142
  forwardedProps: forwardedProps(),
9715
10143
  onMessagesChange: (messages) => {
9716
- if (client !== next) return;
10144
+ if (client !== next || generation !== selectionGeneration) return;
10145
+ restoreCompletedWidgets(messages);
9717
10146
  update({
9718
10147
  messages,
9719
10148
  sandbox: collectSandboxActivity(messages),
@@ -9721,13 +10150,35 @@ function createAstralBeamChat(options, deferStart = false) {
9721
10150
  });
9722
10151
  },
9723
10152
  onStatusChange: (status) => {
9724
- if (client === next) update({
10153
+ if (client === next && generation === selectionGeneration) update({
9725
10154
  status,
9726
10155
  error: next.getError()
9727
10156
  });
9728
10157
  },
10158
+ onInterruptStateChange: (interrupts) => {
10159
+ if (client === next) debug?.("tool", "interrupt state changed", interrupts);
10160
+ },
9729
10161
  onCustomEvent: (eventType, data) => {
9730
10162
  if (client !== next) return;
10163
+ if (eventType === "astralbeam_thread") {
10164
+ const event = data;
10165
+ if (state.thread?.id === event.threadId) {
10166
+ if (event.acceptedMessageId) acceptPendingSend();
10167
+ if (event.saved) for (const id of event.executableToolCallIds ?? []) {
10168
+ if (!liveToolCalls.has(id)) toolResults.delete(toolResultKey(id));
10169
+ liveToolCalls.add(id);
10170
+ }
10171
+ update({
10172
+ thread: {
10173
+ ...state.thread,
10174
+ version: event.version
10175
+ },
10176
+ activeToolCallIds: [...liveToolCalls],
10177
+ ...event.acceptedMessageId ? { unsentMessage: void 0 } : {}
10178
+ });
10179
+ }
10180
+ return;
10181
+ }
9731
10182
  const value = data?.state;
9732
10183
  if (eventType === "astralbeam.sandbox.status" && (value === "starting" || value === "ready" || value === "error")) {
9733
10184
  debug?.("sandbox", `sandbox ${value}`);
@@ -9736,43 +10187,343 @@ function createAstralBeamChat(options, deferStart = false) {
9736
10187
  }
9737
10188
  debug?.("stream", `custom event "${eventType}"`, data);
9738
10189
  },
9739
- onChunk: (chunk) => live.streamCallbacks?.onChunk?.(chunk),
9740
- onResponse: (response) => live.streamCallbacks?.onResponse?.(response),
9741
- onFinish: (message) => live.streamCallbacks?.onFinish?.(message),
9742
- onError: (error) => live.streamCallbacks?.onError?.(error)
10190
+ onChunk: (chunk) => {
10191
+ if (client !== next) return;
10192
+ live.streamCallbacks?.onChunk?.(chunk);
10193
+ },
10194
+ onResponse: (response) => {
10195
+ if (client === next) live.streamCallbacks?.onResponse?.(response);
10196
+ },
10197
+ onFinish: (message) => {
10198
+ if (client === next) live.streamCallbacks?.onFinish?.(message);
10199
+ },
10200
+ onError: (error) => {
10201
+ if (client === next) live.streamCallbacks?.onError?.(error);
10202
+ }
9743
10203
  });
9744
10204
  return next;
9745
10205
  };
9746
10206
  let client = createClient();
9747
- const replaceClient = () => {
10207
+ const replaceClient = (threadId = state.thread?.id) => {
9748
10208
  client.stop();
9749
10209
  const previous = client;
9750
- client = createClient();
10210
+ previous.detach();
10211
+ liveToolCalls.clear();
10212
+ liveToolMessageIds.clear();
10213
+ update({ activeToolCallIds: [] });
10214
+ client = createClient(threadId);
10215
+ if (started) client.attach();
9751
10216
  previous.dispose();
9752
10217
  };
9753
- const settleDanglingToolCalls = () => {
9754
- for (const message of state.messages) for (const part of message.parts) {
9755
- if (part.type !== "tool-call" || isSettledToolCall(part)) continue;
9756
- if (part.name === "ask_questionnaire") {
9757
- debug?.("questionnaire", "skipping pending questionnaire before send", { id: part.id });
9758
- client.addToolResult({
9759
- toolCallId: part.id,
9760
- tool: part.name,
9761
- output: {
9762
- answers: [],
9763
- skipped: true
9764
- }
9765
- });
9766
- } else {
9767
- debug?.("tool", `settling unimplemented tool call "${part.name}" as error`, { id: part.id });
9768
- client.addToolResult({
9769
- toolCallId: part.id,
9770
- tool: part.name,
9771
- output: null,
9772
- state: "output-error",
9773
- errorText: `The application hosting this chat has no implementation for "${part.name}"`
9774
- });
10218
+ const scopeCompletedToolCalls = () => {
10219
+ historicalToolIds.clear();
10220
+ client.setMessagesManually(state.messages.map((message) => {
10221
+ const aliases = /* @__PURE__ */ new Map();
10222
+ const parts = message.parts.map((part) => {
10223
+ if (part.type !== "tool-call") return part;
10224
+ const stored = part;
10225
+ const upstreamToolCallId = stored.upstreamToolCallId ?? part.id;
10226
+ if (liveToolMessageIds.get(upstreamToolCallId) === message.id && !isSettledToolCall(part)) return part;
10227
+ const id = savedToolCallId(message.id, stored.applicationPartId ?? upstreamToolCallId, stored.responseTargetId);
10228
+ aliases.set(upstreamToolCallId, id);
10229
+ return {
10230
+ ...part,
10231
+ upstreamToolCallId,
10232
+ id,
10233
+ ...part.name === "render_widget" && liveToolMessageIds.get(upstreamToolCallId) === message.id ? { widgetRenderId: stored.widgetRenderId ?? part.id } : {}
10234
+ };
10235
+ });
10236
+ if (aliases.size) historicalToolIds.set(message.id, aliases);
10237
+ return {
10238
+ ...message,
10239
+ parts
10240
+ };
10241
+ }));
10242
+ };
10243
+ const changeSelection = (threadId) => {
10244
+ const previousId = state.thread?.id;
10245
+ if (previousId) {
10246
+ if (pendingSend && !pendingSend.accepted) pendingSends.set(previousId, pendingSend);
10247
+ else pendingSends.delete(previousId);
10248
+ threadToolResults.set(previousId, toolResults);
10249
+ }
10250
+ selectionGeneration++;
10251
+ historyGeneration++;
10252
+ sending = false;
10253
+ requestController.abort();
10254
+ requestController = new AbortController();
10255
+ pendingSend = void 0;
10256
+ toolResults = threadToolResults.get(threadId ?? "") ?? /* @__PURE__ */ new Map();
10257
+ historicalToolIds.clear();
10258
+ threadRecords = [];
10259
+ disposeRenders();
10260
+ update({
10261
+ thread: void 0,
10262
+ messages: [],
10263
+ unsentMessage: void 0,
10264
+ error: void 0,
10265
+ status: "ready",
10266
+ sandbox: {
10267
+ files: [],
10268
+ commands: []
10269
+ },
10270
+ sandboxStatus: void 0,
10271
+ pendingInteractions: [],
10272
+ threadLoadFailed: false,
10273
+ messagesCursor: void 0,
10274
+ olderMessagesLoading: false
10275
+ });
10276
+ replaceClient(threadId);
10277
+ };
10278
+ const openThread = async (id, restoring = false) => {
10279
+ if (!restoring) navigationGeneration++;
10280
+ changeSelection(id);
10281
+ const generation = selectionGeneration;
10282
+ update({ threadLoading: true });
10283
+ try {
10284
+ pendingSend = pendingSends.get(id);
10285
+ update({ unsentMessage: pendingSend?.content });
10286
+ if (!started) client.attach();
10287
+ await hydration;
10288
+ if (generation !== selectionGeneration) return;
10289
+ if (threadKey) selectedThread(threadKey, id);
10290
+ } catch (error) {
10291
+ if (generation === selectionGeneration) {
10292
+ if (restoring && (live.threadId === void 0 || live.threadId === "auto") && isAstralBeamApiError(error) && error.status === 404) {
10293
+ newThread();
10294
+ return;
10295
+ }
10296
+ update({ threadLoadFailed: true });
10297
+ reportError(error);
10298
+ }
10299
+ } finally {
10300
+ if (generation === selectionGeneration) update({ threadLoading: false });
10301
+ }
10302
+ };
10303
+ const newThread = () => {
10304
+ navigationGeneration++;
10305
+ changeSelection();
10306
+ update({ threadLoading: false });
10307
+ if (started) resolveCapabilities();
10308
+ if (threadKey) selectedThread(threadKey, null);
10309
+ else startWithNewThread = true;
10310
+ };
10311
+ const selectConfiguredThread = (restoring = false) => {
10312
+ let selected = live.threadId;
10313
+ if (selected === void 0 || selected === "auto") {
10314
+ if (!threadKey) return;
10315
+ selected = selectedThread(threadKey);
10316
+ }
10317
+ if (selected && selected !== "new") openThread(selected, restoring);
10318
+ else if (!restoring) newThread();
10319
+ };
10320
+ const ensureThread = async () => {
10321
+ if (state.thread) return;
10322
+ const generation = selectionGeneration;
10323
+ const options = await requestOptions();
10324
+ const record = await createChatThread(live.agentId ? { agent_id: live.agentId } : {}, options);
10325
+ if (generation !== selectionGeneration) throw new DOMException("Conversation changed", "AbortError");
10326
+ const thread = threadFromRecord(record, false);
10327
+ update({ thread });
10328
+ replaceClient();
10329
+ await hydration;
10330
+ resolveCapabilities();
10331
+ if (threadKey) selectedThread(threadKey, thread.id);
10332
+ };
10333
+ const refreshThread = async (preserveError = false, resetClient = true) => {
10334
+ if (!state.thread?.id) return;
10335
+ const generation = selectionGeneration;
10336
+ const previousError = state.error;
10337
+ update({
10338
+ threadLoading: true,
10339
+ olderMessagesLoading: false
10340
+ });
10341
+ try {
10342
+ if (resetClient) replaceClient();
10343
+ else {
10344
+ client.detach();
10345
+ client.attach();
10346
+ }
10347
+ await hydration;
10348
+ if (generation !== selectionGeneration) return;
10349
+ if (preserveError && previousError) update({
10350
+ error: previousError,
10351
+ status: "error"
10352
+ });
10353
+ } catch (error) {
10354
+ if (generation === selectionGeneration) {
10355
+ if (isAstralBeamApiError(error) && error.status === 404) {
10356
+ newThread();
10357
+ return;
10358
+ }
10359
+ reportError(error);
10360
+ }
10361
+ } finally {
10362
+ if (generation === selectionGeneration) update({ threadLoading: false });
10363
+ }
10364
+ };
10365
+ const loadOlderMessages = async () => {
10366
+ if (!client.getHasOlderMessages() || state.olderMessagesLoading || state.threadLoading || sending) return;
10367
+ const generation = selectionGeneration;
10368
+ update({ olderMessagesLoading: true });
10369
+ try {
10370
+ await client.loadOlderMessages();
10371
+ } catch (error) {
10372
+ if (generation === selectionGeneration) reportError(error);
10373
+ } finally {
10374
+ if (generation === selectionGeneration) update({ olderMessagesLoading: false });
10375
+ }
10376
+ };
10377
+ const renameThread = async (title) => {
10378
+ const thread = state.thread;
10379
+ if (!thread || thread.role !== "manager") return false;
10380
+ const generation = selectionGeneration;
10381
+ try {
10382
+ const record = await updateChatThread(thread.id, {
10383
+ title,
10384
+ expected_version: thread.version
10385
+ }, await requestOptions());
10386
+ if (generation !== selectionGeneration) return false;
10387
+ const renamed = threadFromRecord(record, thread.hasMessages);
10388
+ update({
10389
+ thread: renamed,
10390
+ error: client.getError(),
10391
+ status: client.getStatus()
10392
+ });
10393
+ return true;
10394
+ } catch (error) {
10395
+ if (generation === selectionGeneration) reportError(error);
10396
+ return false;
10397
+ }
10398
+ };
10399
+ const deleteThread = async (thread = state.thread) => {
10400
+ if (!thread || thread.role !== "manager") return false;
10401
+ const generation = selectionGeneration;
10402
+ try {
10403
+ await deleteChatThread(thread.id, { expected_version: String(thread.version) }, await requestOptions());
10404
+ if (generation === selectionGeneration && state.thread?.id === thread.id) newThread();
10405
+ return true;
10406
+ } catch (error) {
10407
+ if (generation === selectionGeneration) reportError(error);
10408
+ return false;
10409
+ }
10410
+ };
10411
+ const addToolResult = async (result) => {
10412
+ const generation = selectionGeneration;
10413
+ if (sending || client.getIsLoading()) {
10414
+ reportError(/* @__PURE__ */ new Error("Wait for this client's current request to finish before responding."));
10415
+ return;
10416
+ }
10417
+ const current = authenticationState(authentication);
10418
+ const pending = state.pendingInteractions.find((item) => item.toolCallId === result.toolCallId);
10419
+ const closure = !("tool" in result) && pending && (state.thread?.role === "manager" || current.status === "ready" && pending.targetTenantUserId === current.currentUser.user.id);
10420
+ const questionnaire = "tool" in result && result.tool === "ask_questionnaire" && current.status === "ready" && pending?.targetTenantUserId === current.currentUser.user.id;
10421
+ if (state.thread?.role === "viewer" || !liveToolCalls.has(result.toolCallId) && !questionnaire && !closure && !toolResults.has(toolResultKey(result.toolCallId))) {
10422
+ reportError(/* @__PURE__ */ new Error("This tool call belongs to another session and cannot be executed here."));
10423
+ return;
10424
+ }
10425
+ if (closure && !toolResults.has(toolResultKey(result.toolCallId))) toolResults.set(toolResultKey(result.toolCallId), {
10426
+ outcome: "unknown",
10427
+ output: null
10428
+ });
10429
+ else if (!toolResults.has(toolResultKey(result.toolCallId)) && "tool" in result) {
10430
+ const output = result.output;
10431
+ toolResults.set(toolResultKey(result.toolCallId), {
10432
+ outcome: result.state === "output-error" ? "failed" : "succeeded",
10433
+ output: output ?? null
10434
+ });
10435
+ }
10436
+ scopeCompletedToolCalls();
10437
+ if (pending && (!("tool" in result) || result.toolCallId === savedToolCallId(pending.sourceMessageId, pending.sourcePartId, pending.responseTargetId))) {
10438
+ replaceClient();
10439
+ await hydration;
10440
+ if (generation !== selectionGeneration) return;
10441
+ update({ threadLoading: false });
10442
+ await client.append({
10443
+ role: "tool",
10444
+ toolCallId: result.toolCallId,
10445
+ content: JSON.stringify(toolResults.get(toolResultKey(result.toolCallId)))
10446
+ });
10447
+ } else if ("tool" in result) await client.addToolResult(result);
10448
+ if (generation === selectionGeneration) await refreshThread(client.getError() !== void 0, false);
10449
+ };
10450
+ const abandonToolCall = (toolCallId) => addToolResult({ toolCallId });
10451
+ const sendMessage = async (content, callbacks) => {
10452
+ const navigation = navigationGeneration;
10453
+ try {
10454
+ await getValidChatAuthToken(authentication);
10455
+ } catch (error) {
10456
+ if (navigation !== navigationGeneration) return;
10457
+ update({ unsentMessage: content });
10458
+ reportError(error);
10459
+ return;
10460
+ }
10461
+ if (navigation !== navigationGeneration) return;
10462
+ if (sending || state.threadLoading || state.status === "streaming") {
10463
+ reportError(/* @__PURE__ */ new Error("Wait for this client's current request to finish before sending."));
10464
+ return;
10465
+ }
10466
+ if (state.threadLoadFailed) {
10467
+ reportError(/* @__PURE__ */ new Error("Reopen this conversation or start a new one before sending."));
10468
+ return;
10469
+ }
10470
+ const samePendingContent = pendingSend !== void 0 && JSON.stringify(pendingSend.content) === JSON.stringify(content);
10471
+ const uncertainSend = pendingSend?.tools !== void 0 && !pendingSend.accepted ? pendingSend : void 0;
10472
+ const retrying = uncertainSend !== void 0 && samePendingContent;
10473
+ if (state.thread?.agentId === null && !retrying) {
10474
+ reportError(/* @__PURE__ */ new Error("This conversation’s agent is unavailable. Start a new conversation to continue."));
10475
+ return;
10476
+ }
10477
+ if (state.thread?.role === "viewer" && !retrying) {
10478
+ reportError(/* @__PURE__ */ new Error("You have read-only access to this conversation."));
10479
+ return;
10480
+ }
10481
+ if (uncertainSend && !samePendingContent) {
10482
+ update({ unsentMessage: uncertainSend.content });
10483
+ reportError(/* @__PURE__ */ new Error("The previous message’s acceptance is unconfirmed. Retry that message before sending different text, or start a new conversation."));
10484
+ return;
10485
+ }
10486
+ const generation = selectionGeneration;
10487
+ historyGeneration++;
10488
+ update({ olderMessagesLoading: false });
10489
+ sending = true;
10490
+ if (!pendingSend || pendingSend.accepted || !samePendingContent) {
10491
+ pendingSend = {
10492
+ content,
10493
+ accepted: false,
10494
+ key: newUuid(),
10495
+ callbacks
10496
+ };
10497
+ client.stop();
10498
+ liveToolCalls.clear();
10499
+ scopeCompletedToolCalls();
10500
+ liveToolMessageIds.clear();
10501
+ update({ activeToolCallIds: [] });
10502
+ }
10503
+ pendingSend.callbacks ??= callbacks;
10504
+ update({
10505
+ unsentMessage: content,
10506
+ error: void 0,
10507
+ status: "submitted"
10508
+ });
10509
+ try {
10510
+ await ensureThread();
10511
+ if (generation !== selectionGeneration) return;
10512
+ pendingSend.callbacks?.onThreadReady?.(state.thread.id);
10513
+ if (generation !== selectionGeneration) return;
10514
+ scopeCompletedToolCalls();
10515
+ await client.sendMessage(content);
10516
+ if (generation !== selectionGeneration) return;
10517
+ const failed = client.getError() !== void 0;
10518
+ if (pendingSend?.accepted) {
10519
+ pendingSend = void 0;
10520
+ update({ unsentMessage: void 0 });
9775
10521
  }
10522
+ await refreshThread(failed, false);
10523
+ } catch (error) {
10524
+ if (generation === selectionGeneration) reportError(error);
10525
+ } finally {
10526
+ if (generation === selectionGeneration) sending = false;
9776
10527
  }
9777
10528
  };
9778
10529
  const observeAuthentication = () => {
@@ -9780,18 +10531,22 @@ function createAstralBeamChat(options, deferStart = false) {
9780
10531
  if (auth.status === "ready") {
9781
10532
  const nextIdentity = authenticationIdentity(auth.currentUser);
9782
10533
  if (identity !== void 0 && identity !== nextIdentity) {
9783
- replaceClient();
9784
- disposeRenders();
9785
- update({
9786
- messages: [],
9787
- sandbox: {
9788
- files: [],
9789
- commands: []
9790
- },
9791
- sandboxStatus: void 0
9792
- });
10534
+ navigationGeneration++;
10535
+ pendingSends.clear();
10536
+ threadToolResults.clear();
10537
+ toolResults.clear();
10538
+ pendingSend = void 0;
9793
10539
  }
9794
10540
  identity = nextIdentity;
10541
+ const nextThreadKey = `astralbeam:thread:${live.apiUrl ?? "https://astralbeam.ai/api"}:${nextIdentity}`;
10542
+ if (threadKey !== nextThreadKey) {
10543
+ changeSelection();
10544
+ threadKey = nextThreadKey;
10545
+ if (startWithNewThread) {
10546
+ selectedThread(nextThreadKey, null);
10547
+ startWithNewThread = false;
10548
+ } else selectConfiguredThread(true);
10549
+ }
9795
10550
  if (capabilityIdentity !== nextIdentity) {
9796
10551
  capabilityIdentity = nextIdentity;
9797
10552
  resolveCapabilities();
@@ -9802,6 +10557,8 @@ function createAstralBeamChat(options, deferStart = false) {
9802
10557
  const start = () => {
9803
10558
  if (started) return;
9804
10559
  started = true;
10560
+ client.attach();
10561
+ if (requestController.signal.aborted) requestController = new AbortController();
9805
10562
  unsubscribeAuthentication = subscribeAuthentication(authentication, observeAuthentication);
9806
10563
  observeAuthentication();
9807
10564
  stopAuthentication = startAuthentication(authentication);
@@ -9817,6 +10574,8 @@ function createAstralBeamChat(options, deferStart = false) {
9817
10574
  updateOptions: (next) => {
9818
10575
  const agent = live.agentId;
9819
10576
  const apiUrl = live.apiUrl;
10577
+ const selectedId = live.threadId;
10578
+ const { widgets, onRenderWidget } = live;
9820
10579
  live = {
9821
10580
  ...live,
9822
10581
  ...next
@@ -9831,13 +10590,22 @@ function createAstralBeamChat(options, deferStart = false) {
9831
10590
  tools: declareTools(),
9832
10591
  forwardedProps: forwardedProps()
9833
10592
  });
10593
+ if (live.apiUrl !== apiUrl) {
10594
+ navigationGeneration++;
10595
+ pendingSends.clear();
10596
+ threadToolResults.clear();
10597
+ toolResults.clear();
10598
+ pendingSend = void 0;
10599
+ changeSelection();
10600
+ threadKey = void 0;
10601
+ update({ threadLoading: false });
10602
+ observeAuthentication();
10603
+ } else if (live.threadId !== selectedId) selectConfiguredThread();
10604
+ if (widgets !== live.widgets || onRenderWidget !== live.onRenderWidget) restoreCompletedWidgets(state.messages);
9834
10605
  if (started && (live.agentId !== agent || live.apiUrl !== apiUrl)) resolveCapabilities();
9835
10606
  },
9836
- sendMessage: (content) => {
9837
- settleDanglingToolCalls();
9838
- return client.sendMessage(content);
9839
- },
9840
- addToolResult: (result) => client.addToolResult(result),
10607
+ sendMessage,
10608
+ addToolResult,
9841
10609
  stop: () => client.stop(),
9842
10610
  retryAuthentication: () => {
9843
10611
  getValidChatAuthToken({
@@ -9845,25 +10613,56 @@ function createAstralBeamChat(options, deferStart = false) {
9845
10613
  force: true
9846
10614
  }).catch(() => void 0);
9847
10615
  },
9848
- reload: () => client.reload(),
10616
+ reload: async () => {
10617
+ const generation = selectionGeneration;
10618
+ await refreshThread();
10619
+ if (generation !== selectionGeneration || state.error) return;
10620
+ const attempted = /* @__PURE__ */ new Set();
10621
+ while (generation === selectionGeneration) {
10622
+ const pending = state.pendingInteractions.find((item) => !attempted.has(item.sourceMessageId) && toolResults.has(toolResultKey(item.toolCallId)));
10623
+ if (!pending) return;
10624
+ attempted.add(pending.sourceMessageId);
10625
+ await addToolResult({ toolCallId: pending.toolCallId });
10626
+ if (state.error) return;
10627
+ }
10628
+ },
10629
+ searchThreads: async (q = "", cursor, signal) => {
10630
+ const options = await requestOptions();
10631
+ const page = await listChatThreads({
10632
+ q,
10633
+ page_size: 50,
10634
+ ...cursor ? { page_after: cursor } : {}
10635
+ }, {
10636
+ ...options,
10637
+ signal: AbortSignal.any([options.signal, ...signal ? [signal] : []])
10638
+ });
10639
+ return {
10640
+ items: page.items.map((record) => threadFromRecord(record)),
10641
+ page_after: page.page_after
10642
+ };
10643
+ },
10644
+ openThread,
10645
+ renameThread,
10646
+ deleteThread,
10647
+ refreshThread,
10648
+ loadOlderMessages,
10649
+ getAttachment: async (messageId, partId) => {
10650
+ const thread = state.thread;
10651
+ if (!thread) throw new Error("Open a conversation before downloading attachments.");
10652
+ return (await getChatAttachment(thread.id, messageId, partId, await requestOptions())).blob();
10653
+ },
10654
+ abandonToolCall,
9849
10655
  reset: () => {
9850
10656
  debug?.("status", "conversation reset");
9851
- replaceClient();
9852
- disposeRenders();
9853
- update({
9854
- messages: [],
9855
- status: "ready",
9856
- sandbox: {
9857
- files: [],
9858
- commands: []
9859
- },
9860
- sandboxStatus: void 0,
9861
- error: void 0
9862
- });
10657
+ newThread();
9863
10658
  },
9864
10659
  dispose: () => {
10660
+ client.detach();
9865
10661
  disposeRenders();
9866
10662
  client.stop();
10663
+ requestController.abort();
10664
+ selectionGeneration++;
10665
+ threadKey = void 0;
9867
10666
  capabilitiesGeneration++;
9868
10667
  capabilityIdentity = void 0;
9869
10668
  stopAuthentication?.();