@iota-uz/sdk 0.4.17 → 0.4.19

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.
@@ -1518,6 +1518,10 @@ var ChatMachine = class {
1518
1518
  }
1519
1519
  /** Sets turns from fetch, preserving pending user-only turns if server hasn't caught up. */
1520
1520
  _setTurnsFromFetch(fetchedTurns) {
1521
+ if (!Array.isArray(fetchedTurns)) {
1522
+ console.warn("[ChatMachine] Ignoring malformed turns payload from fetchSession");
1523
+ return;
1524
+ }
1521
1525
  const prev = this.state.messaging.turns;
1522
1526
  const hasPendingUserOnly = prev.length > 0 && !prev[prev.length - 1].assistantTurn;
1523
1527
  if (hasPendingUserOnly && (!fetchedTurns || fetchedTurns.length === 0)) {
@@ -1907,6 +1911,7 @@ var ChatMachine = class {
1907
1911
  const curSessionId = this.state.session.currentSessionId;
1908
1912
  const curPendingQuestion = this.state.messaging.pendingQuestion;
1909
1913
  if (!curSessionId || !curPendingQuestion) return;
1914
+ const previousTurns = this.state.messaging.turns;
1910
1915
  this._updateMessaging({ loading: true });
1911
1916
  this._updateSession({ error: null, errorRetryable: false });
1912
1917
  const previousPendingQuestion = curPendingQuestion;
@@ -1924,19 +1929,28 @@ var ChatMachine = class {
1924
1929
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
1925
1930
  if (this.disposed) return;
1926
1931
  if (fetchResult) {
1927
- this._updateMessaging({
1928
- turns: fetchResult.turns,
1929
- pendingQuestion: fetchResult.pendingQuestion || null
1930
- });
1932
+ this._updateSession({ session: fetchResult.session });
1933
+ this._updateMessaging({ pendingQuestion: fetchResult.pendingQuestion || null });
1934
+ const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
1935
+ if (hasMalformedRefresh) {
1936
+ console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
1937
+ sessionId: curSessionId,
1938
+ previousTurnCount: previousTurns.length
1939
+ });
1940
+ this._updateSession({
1941
+ error: "Failed to fully refresh session. Showing last known messages.",
1942
+ errorRetryable: true
1943
+ });
1944
+ } else {
1945
+ this._setTurnsFromFetch(fetchResult.turns);
1946
+ }
1931
1947
  } else {
1932
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1933
- this._updateSession({ error: "Failed to load updated session", errorRetryable: false });
1948
+ this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
1934
1949
  }
1935
1950
  } catch (fetchErr) {
1936
1951
  if (this.disposed) return;
1937
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1938
1952
  const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
1939
- this._updateSession({ error: normalized.userMessage, errorRetryable: normalized.retryable });
1953
+ this._updateSession({ error: normalized.userMessage, errorRetryable: true });
1940
1954
  }
1941
1955
  }
1942
1956
  } else {
@@ -3296,7 +3310,7 @@ function InlineQuestionForm({ pendingQuestion }) {
3296
3310
  const [currentStep, setCurrentStep] = React.useState(0);
3297
3311
  const [answers, setAnswers] = React.useState({});
3298
3312
  const [otherTexts, setOtherTexts] = React.useState({});
3299
- const questions = pendingQuestion.questions;
3313
+ const questions = Array.isArray(pendingQuestion.questions) ? pendingQuestion.questions : [];
3300
3314
  const currentQuestion = questions[currentStep];
3301
3315
  const isLastStep = currentStep === questions.length - 1;
3302
3316
  const isFirstStep = currentStep === 0;
@@ -3390,9 +3404,31 @@ function InlineQuestionForm({ pendingQuestion }) {
3390
3404
  e.preventDefault();
3391
3405
  handleNext();
3392
3406
  };
3393
- if (!currentQuestion) return null;
3407
+ if (!currentQuestion) {
3408
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "animate-slide-up rounded-2xl border border-amber-200 dark:border-amber-700/50 bg-gradient-to-b from-amber-50/70 to-white dark:from-amber-950/20 dark:to-gray-900/80 shadow-sm overflow-hidden p-4", children: [
3409
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-800 dark:text-gray-200", children: t("BiChat.Error.SomethingWentWrong") }),
3410
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Error.UnexpectedError") }),
3411
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
3412
+ "button",
3413
+ {
3414
+ type: "button",
3415
+ onClick: handleRejectPendingQuestion,
3416
+ disabled: loading,
3417
+ className: "cursor-pointer inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 disabled:opacity-40",
3418
+ children: [
3419
+ /* @__PURE__ */ jsxRuntime.jsx(react.X, { size: 14, weight: "bold" }),
3420
+ t("BiChat.InlineQuestion.Dismiss")
3421
+ ]
3422
+ }
3423
+ ) })
3424
+ ] });
3425
+ }
3394
3426
  const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
3395
- const options = currentQuestion.options || [];
3427
+ const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
3428
+ id: option.id || `${currentQuestion.id}-option-${index}`,
3429
+ label: option.label,
3430
+ value: option.value || option.label
3431
+ }));
3396
3432
  const isOtherSelected = currentAnswer?.customText !== void 0;
3397
3433
  const canProceed = isCurrentAnswerValid();
3398
3434
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-slide-up rounded-2xl border border-gray-200 dark:border-gray-700/50 bg-gradient-to-b from-primary-50/80 to-white dark:from-primary-950/30 dark:to-gray-900/80 shadow-sm overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, children: [
@@ -4306,7 +4342,8 @@ function TurnBubble({
4306
4342
  userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
4307
4343
  assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
4308
4344
  };
4309
- const isSystemSummaryTurn = turn.userTurn.content.trim() === "" && turn.assistantTurn?.role === "system";
4345
+ const userContent = typeof turn.userTurn?.content === "string" ? turn.userTurn.content : "";
4346
+ const isSystemSummaryTurn = userContent.trim() === "" && turn.assistantTurn?.role === "system";
4310
4347
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
4311
4348
  !isSystemSummaryTurn && /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsxRuntime.jsx(
4312
4349
  UserTurnView,
@@ -8084,12 +8121,63 @@ function DefaultErrorContent({
8084
8121
  ] })
8085
8122
  ] });
8086
8123
  }
8124
+ function StaticEmergencyErrorContent({
8125
+ error,
8126
+ onReset
8127
+ }) {
8128
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center justify-center p-8 text-center min-h-[200px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col items-center", children: [
8129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mb-5", children: [
8130
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-full bg-red-100 scale-150 blur-md" }),
8131
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative flex items-center justify-center w-14 h-14 rounded-full bg-red-50 border border-red-200/60", children: /* @__PURE__ */ jsxRuntime.jsx(react.WarningCircle, { size: 28, className: "text-red-500", weight: "fill" }) })
8132
+ ] }),
8133
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-1.5", children: "Something went wrong" }),
8134
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mb-5 max-w-md leading-relaxed", children: error?.message || "An unexpected UI error occurred." }),
8135
+ onReset && /* @__PURE__ */ jsxRuntime.jsxs(
8136
+ "button",
8137
+ {
8138
+ type: "button",
8139
+ onClick: onReset,
8140
+ className: "flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 active:bg-red-800 text-white rounded-lg transition-colors shadow-sm text-sm font-medium",
8141
+ children: [
8142
+ /* @__PURE__ */ jsxRuntime.jsx(react.ArrowClockwise, { size: 16, weight: "bold" }),
8143
+ "Try again"
8144
+ ]
8145
+ }
8146
+ )
8147
+ ] }) });
8148
+ }
8149
+ var FallbackGuard = class extends React.Component {
8150
+ constructor(props) {
8151
+ super(props);
8152
+ this.state = { fallbackFailed: false };
8153
+ }
8154
+ static getDerivedStateFromError() {
8155
+ return { fallbackFailed: true };
8156
+ }
8157
+ componentDidCatch(error, errorInfo) {
8158
+ this.props.onFallbackError?.(error, errorInfo);
8159
+ }
8160
+ render() {
8161
+ if (this.state.fallbackFailed) {
8162
+ return /* @__PURE__ */ jsxRuntime.jsx(StaticEmergencyErrorContent, { error: this.props.primaryError, onReset: this.props.onReset });
8163
+ }
8164
+ return this.props.renderFallback();
8165
+ }
8166
+ };
8087
8167
  var ErrorBoundary = class extends React.Component {
8088
8168
  constructor(props) {
8089
8169
  super(props);
8090
8170
  this.handleReset = () => {
8091
8171
  this.setState({ hasError: false, error: null });
8092
8172
  };
8173
+ this.handleFallbackError = (error, errorInfo) => {
8174
+ console.error("React Error Boundary fallback crashed:", {
8175
+ primaryError: this.state.error,
8176
+ fallbackError: error,
8177
+ errorInfo
8178
+ });
8179
+ this.props.onError?.(error, errorInfo);
8180
+ };
8093
8181
  this.state = { hasError: false, error: null };
8094
8182
  }
8095
8183
  static getDerivedStateFromError(error) {
@@ -8101,13 +8189,24 @@ var ErrorBoundary = class extends React.Component {
8101
8189
  }
8102
8190
  render() {
8103
8191
  if (this.state.hasError) {
8104
- if (this.props.fallback) {
8105
- if (typeof this.props.fallback === "function") {
8106
- return this.props.fallback(this.state.error, this.handleReset);
8107
- }
8108
- return this.props.fallback;
8109
- }
8110
- return /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8192
+ return /* @__PURE__ */ jsxRuntime.jsx(
8193
+ FallbackGuard,
8194
+ {
8195
+ primaryError: this.state.error,
8196
+ onReset: this.handleReset,
8197
+ onFallbackError: this.handleFallbackError,
8198
+ renderFallback: () => {
8199
+ if (this.props.fallback) {
8200
+ if (typeof this.props.fallback === "function") {
8201
+ return this.props.fallback(this.state.error, this.handleReset);
8202
+ }
8203
+ return this.props.fallback;
8204
+ }
8205
+ return /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8206
+ }
8207
+ },
8208
+ `${this.state.error?.name ?? "Error"}:${this.state.error?.message ?? ""}`
8209
+ );
8111
8210
  }
8112
8211
  return this.props.children;
8113
8212
  }
@@ -12066,6 +12165,7 @@ function toSessionArtifact(artifact) {
12066
12165
  id: artifact.id,
12067
12166
  sessionId: artifact.sessionId,
12068
12167
  messageId: artifact.messageId,
12168
+ uploadId: artifact.uploadId,
12069
12169
  type: artifact.type,
12070
12170
  name: artifact.name,
12071
12171
  description: artifact.description,
@@ -12076,21 +12176,272 @@ function toSessionArtifact(artifact) {
12076
12176
  createdAt: artifact.createdAt
12077
12177
  };
12078
12178
  }
12079
- function toPendingQuestion(rpc) {
12080
- if (!rpc) return null;
12081
- const questions = (rpc.questions || []).map((q) => ({
12082
- id: q.id,
12083
- text: q.text,
12084
- type: q.type,
12085
- options: (q.options || []).map((o) => ({
12086
- id: o.id,
12087
- label: o.label,
12088
- value: o.label
12089
- }))
12090
- }));
12179
+ function warnMalformedSessionPayload(message, details) {
12180
+ console.warn(`[BiChat] ${message}`, details || {});
12181
+ }
12182
+ function readString(value, fallback = "") {
12183
+ return typeof value === "string" ? value : fallback;
12184
+ }
12185
+ function readNonEmptyString(value) {
12186
+ if (typeof value !== "string") return null;
12187
+ const trimmed = value.trim();
12188
+ return trimmed.length > 0 ? trimmed : null;
12189
+ }
12190
+ function readFiniteNumber(value, fallback = 0) {
12191
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
12192
+ }
12193
+ function readOptionalFiniteNumber(value) {
12194
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12195
+ }
12196
+ function normalizeQuestionType(rawType) {
12197
+ const normalized = readString(rawType).trim().toUpperCase().replace(/[\s-]+/g, "_");
12198
+ return normalized === "MULTIPLE_CHOICE" ? "MULTIPLE_CHOICE" : "SINGLE_CHOICE";
12199
+ }
12200
+ function normalizeMessageRole(rawRole) {
12201
+ const normalized = readString(rawRole).trim().toLowerCase();
12202
+ if (normalized === "user" /* User */) return "user" /* User */;
12203
+ if (normalized === "system" /* System */) return "system" /* System */;
12204
+ if (normalized === "tool" /* Tool */) return "tool" /* Tool */;
12205
+ return "assistant" /* Assistant */;
12206
+ }
12207
+ function sanitizeAttachment(rawAttachment, turnId, index) {
12208
+ if (!isRecord(rawAttachment)) {
12209
+ warnMalformedSessionPayload("Dropped malformed attachment entry", { turnId, index });
12210
+ return null;
12211
+ }
12212
+ const filename = readString(rawAttachment.filename, "attachment");
12213
+ const mimeType = readString(rawAttachment.mimeType, "application/octet-stream");
12214
+ const id = readNonEmptyString(rawAttachment.id) || void 0;
12215
+ const clientKey = readNonEmptyString(rawAttachment.clientKey) || id || `${turnId}-attachment-${index}`;
12216
+ return {
12217
+ id,
12218
+ clientKey,
12219
+ filename,
12220
+ mimeType,
12221
+ sizeBytes: readFiniteNumber(rawAttachment.sizeBytes),
12222
+ uploadId: readOptionalFiniteNumber(rawAttachment.uploadId),
12223
+ base64Data: readNonEmptyString(rawAttachment.base64Data) || void 0,
12224
+ url: readNonEmptyString(rawAttachment.url) || void 0,
12225
+ preview: readNonEmptyString(rawAttachment.preview) || void 0
12226
+ };
12227
+ }
12228
+ function sanitizeUserAttachments(rawAttachments, turnId) {
12229
+ if (!Array.isArray(rawAttachments)) return [];
12230
+ const result = [];
12231
+ for (let i = 0; i < rawAttachments.length; i++) {
12232
+ const sanitized = sanitizeAttachment(rawAttachments[i], turnId, i);
12233
+ if (sanitized) result.push(sanitized);
12234
+ }
12235
+ return result;
12236
+ }
12237
+ function sanitizeAssistantArtifacts(rawArtifacts, turnId) {
12238
+ if (!Array.isArray(rawArtifacts)) return [];
12239
+ const artifacts = [];
12240
+ for (let i = 0; i < rawArtifacts.length; i++) {
12241
+ const raw = rawArtifacts[i];
12242
+ if (!isRecord(raw)) {
12243
+ warnMalformedSessionPayload("Dropped malformed assistant artifact", { turnId, index: i });
12244
+ continue;
12245
+ }
12246
+ const type = readString(raw.type).toLowerCase();
12247
+ if (type !== "excel" && type !== "pdf") {
12248
+ continue;
12249
+ }
12250
+ const url = readNonEmptyString(raw.url);
12251
+ if (!url) {
12252
+ warnMalformedSessionPayload("Dropped assistant artifact without url", { turnId, index: i });
12253
+ continue;
12254
+ }
12255
+ artifacts.push({
12256
+ type,
12257
+ filename: readString(raw.filename, "download"),
12258
+ url,
12259
+ sizeReadable: readNonEmptyString(raw.sizeReadable) || void 0,
12260
+ rowCount: typeof raw.rowCount === "number" && Number.isFinite(raw.rowCount) ? raw.rowCount : void 0,
12261
+ description: readNonEmptyString(raw.description) || void 0
12262
+ });
12263
+ }
12264
+ return artifacts;
12265
+ }
12266
+ function sanitizeAssistantTurn(rawAssistantTurn, fallbackCreatedAt, turnId) {
12267
+ if (rawAssistantTurn == null) return void 0;
12268
+ if (!isRecord(rawAssistantTurn)) {
12269
+ warnMalformedSessionPayload("Dropped malformed assistant turn payload", { turnId });
12270
+ return void 0;
12271
+ }
12272
+ const assistantID = readNonEmptyString(rawAssistantTurn.id);
12273
+ if (!assistantID) {
12274
+ warnMalformedSessionPayload("Dropped assistant turn without id", { turnId });
12275
+ return void 0;
12276
+ }
12277
+ const citations = Array.isArray(rawAssistantTurn.citations) ? rawAssistantTurn.citations.filter((item) => isRecord(item)).map((item, index) => ({
12278
+ id: readString(item.id, `${assistantID}-citation-${index}`),
12279
+ type: readString(item.type),
12280
+ title: readString(item.title),
12281
+ url: readString(item.url),
12282
+ startIndex: readFiniteNumber(item.startIndex),
12283
+ endIndex: readFiniteNumber(item.endIndex),
12284
+ excerpt: readNonEmptyString(item.excerpt) || void 0
12285
+ })) : [];
12286
+ const toolCalls = Array.isArray(rawAssistantTurn.toolCalls) ? rawAssistantTurn.toolCalls.filter((item) => isRecord(item)).map((item, index) => ({
12287
+ id: readString(item.id, `${assistantID}-tool-${index}`),
12288
+ name: readString(item.name),
12289
+ arguments: readString(item.arguments),
12290
+ result: readNonEmptyString(item.result) || void 0,
12291
+ error: readNonEmptyString(item.error) || void 0,
12292
+ durationMs: readFiniteNumber(item.durationMs)
12293
+ })) : [];
12294
+ const codeOutputs = Array.isArray(rawAssistantTurn.codeOutputs) ? rawAssistantTurn.codeOutputs.filter((item) => isRecord(item)).map((item) => ({
12295
+ type: (() => {
12296
+ const normalizedType = readString(item.type, "text").toLowerCase();
12297
+ if (normalizedType === "image" || normalizedType === "error") return normalizedType;
12298
+ return "text";
12299
+ })(),
12300
+ content: readString(item.content),
12301
+ filename: readNonEmptyString(item.filename) || void 0,
12302
+ mimeType: readNonEmptyString(item.mimeType) || void 0,
12303
+ sizeBytes: readOptionalFiniteNumber(item.sizeBytes)
12304
+ })) : [];
12305
+ const debugTrace = isRecord(rawAssistantTurn.debug) ? {
12306
+ generationMs: readOptionalFiniteNumber(rawAssistantTurn.debug.generationMs),
12307
+ usage: isRecord(rawAssistantTurn.debug.usage) ? {
12308
+ promptTokens: readFiniteNumber(rawAssistantTurn.debug.usage.promptTokens),
12309
+ completionTokens: readFiniteNumber(rawAssistantTurn.debug.usage.completionTokens),
12310
+ totalTokens: readFiniteNumber(rawAssistantTurn.debug.usage.totalTokens),
12311
+ cachedTokens: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cachedTokens),
12312
+ cost: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cost)
12313
+ } : void 0,
12314
+ tools: Array.isArray(rawAssistantTurn.debug.tools) ? rawAssistantTurn.debug.tools.filter((tool) => isRecord(tool)).map((tool) => ({
12315
+ callId: readNonEmptyString(tool.callId) || void 0,
12316
+ name: readString(tool.name),
12317
+ arguments: readNonEmptyString(tool.arguments) || void 0,
12318
+ result: readNonEmptyString(tool.result) || void 0,
12319
+ error: readNonEmptyString(tool.error) || void 0,
12320
+ durationMs: readOptionalFiniteNumber(tool.durationMs)
12321
+ })) : []
12322
+ } : void 0;
12323
+ return {
12324
+ id: assistantID,
12325
+ role: normalizeMessageRole(rawAssistantTurn.role),
12326
+ content: readString(rawAssistantTurn.content),
12327
+ explanation: readNonEmptyString(rawAssistantTurn.explanation) || void 0,
12328
+ citations,
12329
+ toolCalls,
12330
+ chartData: void 0,
12331
+ artifacts: sanitizeAssistantArtifacts(rawAssistantTurn.artifacts, turnId),
12332
+ codeOutputs,
12333
+ debug: debugTrace,
12334
+ createdAt: readString(rawAssistantTurn.createdAt, fallbackCreatedAt)
12335
+ };
12336
+ }
12337
+ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
12338
+ if (!isRecord(rawTurn)) {
12339
+ warnMalformedSessionPayload("Dropped malformed turn payload (not an object)", { index });
12340
+ return null;
12341
+ }
12342
+ if (!isRecord(rawTurn.userTurn)) {
12343
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn)", { index });
12344
+ return null;
12345
+ }
12346
+ const userTurnID = readNonEmptyString(rawTurn.userTurn.id);
12347
+ if (!userTurnID) {
12348
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn id)", { index });
12349
+ return null;
12350
+ }
12351
+ const turnID = readString(rawTurn.id, userTurnID);
12352
+ const createdAt = readString(
12353
+ rawTurn.createdAt,
12354
+ readString(rawTurn.userTurn.createdAt, (/* @__PURE__ */ new Date()).toISOString())
12355
+ );
12091
12356
  return {
12092
- id: rpc.checkpointId,
12093
- turnId: rpc.turnId || "",
12357
+ id: turnID,
12358
+ sessionId: readString(rawTurn.sessionId, fallbackSessionID),
12359
+ userTurn: {
12360
+ id: userTurnID,
12361
+ content: readString(rawTurn.userTurn.content),
12362
+ attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
12363
+ createdAt: readString(rawTurn.userTurn.createdAt, createdAt)
12364
+ },
12365
+ assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
12366
+ createdAt
12367
+ };
12368
+ }
12369
+ function sanitizeConversationTurns(rawTurns, sessionID) {
12370
+ if (!Array.isArray(rawTurns)) {
12371
+ warnMalformedSessionPayload("Session payload contained non-array turns field", { sessionID });
12372
+ return [];
12373
+ }
12374
+ const turns = [];
12375
+ let dropped = 0;
12376
+ for (let i = 0; i < rawTurns.length; i++) {
12377
+ const sanitizedTurn = sanitizeConversationTurn(rawTurns[i], i, sessionID);
12378
+ if (sanitizedTurn) {
12379
+ turns.push(sanitizedTurn);
12380
+ } else {
12381
+ dropped++;
12382
+ }
12383
+ }
12384
+ if (dropped > 0) {
12385
+ warnMalformedSessionPayload("Dropped malformed turns from session payload", {
12386
+ sessionID,
12387
+ dropped,
12388
+ total: rawTurns.length
12389
+ });
12390
+ }
12391
+ return turns;
12392
+ }
12393
+ function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
12394
+ if (!rawPendingQuestion) return null;
12395
+ const checkpointID = readNonEmptyString(rawPendingQuestion.checkpointId);
12396
+ if (!checkpointID) {
12397
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion without checkpointId", { sessionID });
12398
+ return null;
12399
+ }
12400
+ if (!Array.isArray(rawPendingQuestion.questions)) {
12401
+ warnMalformedSessionPayload("Pending question had non-array questions payload", {
12402
+ sessionID,
12403
+ checkpointID
12404
+ });
12405
+ }
12406
+ const questions = Array.isArray(rawPendingQuestion.questions) ? rawPendingQuestion.questions.filter((question) => {
12407
+ if (!question || !isRecord(question)) {
12408
+ warnMalformedSessionPayload("Dropped malformed question from pendingQuestion", {
12409
+ sessionID,
12410
+ checkpointID
12411
+ });
12412
+ return false;
12413
+ }
12414
+ return true;
12415
+ }).map((question, index) => {
12416
+ const questionID = readString(question.id, `${checkpointID}-q-${index}`);
12417
+ const options = Array.isArray(question.options) ? question.options.filter((option) => {
12418
+ if (!option || !isRecord(option)) {
12419
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion option", {
12420
+ sessionID,
12421
+ checkpointID,
12422
+ questionID
12423
+ });
12424
+ return false;
12425
+ }
12426
+ return true;
12427
+ }).map((option, optionIndex) => {
12428
+ const label = readString(option.label);
12429
+ return {
12430
+ id: readString(option.id, `${questionID}-opt-${optionIndex}`),
12431
+ label,
12432
+ value: label
12433
+ };
12434
+ }) : [];
12435
+ return {
12436
+ id: questionID,
12437
+ text: readString(question.text),
12438
+ type: normalizeQuestionType(question.type),
12439
+ options
12440
+ };
12441
+ }) : [];
12442
+ return {
12443
+ id: checkpointID,
12444
+ turnId: readString(rawPendingQuestion.turnId),
12094
12445
  questions,
12095
12446
  status: "PENDING"
12096
12447
  };
@@ -12286,6 +12637,7 @@ var HttpDataSource = class {
12286
12637
  this.abortController = null;
12287
12638
  this.config = {
12288
12639
  streamEndpoint: "/stream",
12640
+ uploadEndpoint: "/api/uploads",
12289
12641
  timeout: 12e4,
12290
12642
  ...config
12291
12643
  };
@@ -12321,6 +12673,83 @@ var HttpDataSource = class {
12321
12673
  }
12322
12674
  return headers;
12323
12675
  }
12676
+ createUploadHeaders(additionalHeaders) {
12677
+ const headers = new Headers({
12678
+ ...this.config.headers,
12679
+ ...additionalHeaders
12680
+ });
12681
+ const csrfToken = this.getCSRFToken();
12682
+ if (csrfToken) {
12683
+ headers.set("X-CSRF-Token", csrfToken);
12684
+ }
12685
+ headers.delete("Content-Type");
12686
+ return headers;
12687
+ }
12688
+ async uploadFile(file) {
12689
+ const formData = new FormData();
12690
+ formData.append("file", file);
12691
+ const response = await fetch(`${this.config.baseUrl}${this.config.uploadEndpoint}`, {
12692
+ method: "POST",
12693
+ headers: this.createUploadHeaders(),
12694
+ body: formData
12695
+ });
12696
+ let payload = null;
12697
+ try {
12698
+ payload = await response.json();
12699
+ } catch {
12700
+ payload = null;
12701
+ }
12702
+ if (!response.ok) {
12703
+ const errorMessage = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Upload failed: HTTP ${response.status}`;
12704
+ throw new Error(errorMessage);
12705
+ }
12706
+ if (!isRecord(payload) || typeof payload.id !== "number" || payload.id <= 0) {
12707
+ throw new Error("Upload failed: invalid response payload");
12708
+ }
12709
+ return {
12710
+ id: payload.id,
12711
+ url: typeof payload.url === "string" ? payload.url : "",
12712
+ path: typeof payload.path === "string" ? payload.path : "",
12713
+ name: typeof payload.name === "string" ? payload.name : file.name,
12714
+ mimetype: typeof payload.mimetype === "string" ? payload.mimetype : file.type,
12715
+ size: typeof payload.size === "number" && Number.isFinite(payload.size) ? payload.size : file.size
12716
+ };
12717
+ }
12718
+ async attachmentToFile(attachment) {
12719
+ if (attachment.base64Data && attachment.base64Data.trim().length > 0) {
12720
+ const base64Data = attachment.base64Data.trim();
12721
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:${attachment.mimeType || "application/octet-stream"};base64,${base64Data}`;
12722
+ const blob = await fetch(dataUrl).then((response) => response.blob());
12723
+ return new File([blob], attachment.filename, {
12724
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12725
+ });
12726
+ }
12727
+ if (attachment.url) {
12728
+ const response = await fetch(attachment.url);
12729
+ if (!response.ok) {
12730
+ throw new Error(`Failed to read attachment source: HTTP ${response.status}`);
12731
+ }
12732
+ const blob = await response.blob();
12733
+ return new File([blob], attachment.filename, {
12734
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12735
+ });
12736
+ }
12737
+ throw new Error(`Attachment "${attachment.filename}" has no uploadable data`);
12738
+ }
12739
+ async ensureAttachmentUpload(attachment) {
12740
+ if (typeof attachment.uploadId === "number" && attachment.uploadId > 0) {
12741
+ return {
12742
+ id: attachment.uploadId,
12743
+ url: attachment.url || "",
12744
+ path: "",
12745
+ name: attachment.filename,
12746
+ mimetype: attachment.mimeType,
12747
+ size: attachment.sizeBytes
12748
+ };
12749
+ }
12750
+ const file = await this.attachmentToFile(attachment);
12751
+ return this.uploadFile(file);
12752
+ }
12324
12753
  async callRPC(method, params) {
12325
12754
  return this.rpc.callTyped(method, params);
12326
12755
  }
@@ -12343,11 +12772,22 @@ var HttpDataSource = class {
12343
12772
  return { artifacts: [], hasMore: false, nextOffset: 0 };
12344
12773
  })
12345
12774
  ]);
12346
- const turns = attachArtifactsToTurns(normalizeTurns(data.turns), artifactsData.artifacts || []);
12775
+ const sanitizedTurns = sanitizeConversationTurns(data.turns, id);
12776
+ const turns = attachArtifactsToTurns(
12777
+ normalizeTurns(sanitizedTurns),
12778
+ artifactsData.artifacts || []
12779
+ );
12780
+ const pendingQuestion = sanitizePendingQuestion(data.pendingQuestion, id);
12781
+ if (data.pendingQuestion && pendingQuestion && pendingQuestion.questions.length === 0) {
12782
+ warnMalformedSessionPayload("Pending question normalized to zero renderable questions", {
12783
+ sessionID: id,
12784
+ checkpointID: pendingQuestion.id
12785
+ });
12786
+ }
12347
12787
  return {
12348
12788
  session: toSession(data.session),
12349
12789
  turns,
12350
- pendingQuestion: toPendingQuestion(data.pendingQuestion)
12790
+ pendingQuestion
12351
12791
  };
12352
12792
  } catch (err) {
12353
12793
  if (isSessionNotFoundError(err)) {
@@ -12379,27 +12819,12 @@ var HttpDataSource = class {
12379
12819
  return { artifacts: [] };
12380
12820
  }
12381
12821
  validateFileCount(0, files.length, 10);
12382
- const attachments = [];
12383
- for (const file of files) {
12384
- validateAttachmentFile(file);
12385
- const base64Data = await convertToBase64(file);
12386
- attachments.push({
12387
- clientKey: crypto.randomUUID(),
12388
- filename: file.name,
12389
- mimeType: file.type,
12390
- sizeBytes: file.size,
12391
- base64Data
12392
- });
12393
- }
12822
+ files.forEach((file) => validateAttachmentFile(file));
12823
+ const uploads = await Promise.all(files.map((file) => this.uploadFile(file)));
12394
12824
  const data = await this.callRPC("bichat.session.uploadArtifacts", {
12395
12825
  sessionId,
12396
- attachments: attachments.map((a) => ({
12397
- id: "",
12398
- // Backend will assign ID
12399
- filename: a.filename,
12400
- mimeType: a.mimeType,
12401
- sizeBytes: a.sizeBytes,
12402
- base64Data: a.base64Data
12826
+ attachments: uploads.map((upload) => ({
12827
+ uploadId: upload.id
12403
12828
  }))
12404
12829
  });
12405
12830
  return {
@@ -12430,22 +12855,21 @@ var HttpDataSource = class {
12430
12855
  signal.addEventListener("abort", onExternalAbort);
12431
12856
  }
12432
12857
  const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
12433
- const payload = {
12434
- sessionId,
12435
- content,
12436
- debugMode: options?.debugMode ?? false,
12437
- replaceFromMessageId: options?.replaceFromMessageID,
12438
- attachments: attachments.map((a) => ({
12439
- filename: a.filename,
12440
- mimeType: a.mimeType,
12441
- sizeBytes: a.sizeBytes,
12442
- base64Data: a.base64Data,
12443
- url: a.url
12444
- }))
12445
- };
12446
12858
  let connectionTimeoutID;
12447
12859
  let connectionTimedOut = false;
12448
12860
  try {
12861
+ const uploads = await Promise.all(
12862
+ attachments.map((attachment) => this.ensureAttachmentUpload(attachment))
12863
+ );
12864
+ const payload = {
12865
+ sessionId,
12866
+ content,
12867
+ debugMode: options?.debugMode ?? false,
12868
+ replaceFromMessageId: options?.replaceFromMessageID,
12869
+ attachments: uploads.map((upload) => ({
12870
+ uploadId: upload.id
12871
+ }))
12872
+ };
12449
12873
  const timeoutMs = this.config.timeout ?? 0;
12450
12874
  if (timeoutMs > 0) {
12451
12875
  connectionTimeoutID = setTimeout(() => {