@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.
@@ -2,7 +2,7 @@ import React, { createContext, lazy, memo, forwardRef, useState, useRef, useMemo
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import ReactApexChart from 'react-apexcharts';
4
4
  import ApexCharts from 'apexcharts';
5
- import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ArrowCounterClockwise, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, MagnifyingGlass, WarningCircle, CaretDown, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, ArrowsCounterClockwise, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
5
+ import { X, Bug, ArrowUp, ArrowDown, Stack, Paperclip, PaperPlaneRight, CircleNotch, ArrowUUpLeft, PencilSimple, Check, Bookmark, ArrowsClockwise, Archive, Trash, DotsThree, Warning, ArrowClockwise, Image, ArrowCounterClockwise, ImageBroken, CaretLeft, CaretRight, Info, CheckCircle, XCircle, Spinner, MagnifyingGlass, WarningCircle, CaretDown, Copy, FilePdf, FileXls, FileCsv, FileDoc, FileCode, FileText, File as File$1, ChartBar, DownloadSimple, Download, ChatCircleDots, PencilSimpleLine, ArrowLeft, PaperPlaneTilt, ArrowRight, Timer, Lightning, Database, Wrench, ClockCounterClockwise, Lightbulb, Package, Plus, ArrowsCounterClockwise, Gear, Users, List, CaretLineLeft, CaretLineRight, Code, ArrowSquareOut, SpinnerGap, FloppyDisk, Sidebar } from '@phosphor-icons/react';
6
6
  import { Prism } from 'react-syntax-highlighter';
7
7
  import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
8
8
  import ReactMarkdown from 'react-markdown';
@@ -1508,6 +1508,10 @@ var ChatMachine = class {
1508
1508
  }
1509
1509
  /** Sets turns from fetch, preserving pending user-only turns if server hasn't caught up. */
1510
1510
  _setTurnsFromFetch(fetchedTurns) {
1511
+ if (!Array.isArray(fetchedTurns)) {
1512
+ console.warn("[ChatMachine] Ignoring malformed turns payload from fetchSession");
1513
+ return;
1514
+ }
1511
1515
  const prev = this.state.messaging.turns;
1512
1516
  const hasPendingUserOnly = prev.length > 0 && !prev[prev.length - 1].assistantTurn;
1513
1517
  if (hasPendingUserOnly && (!fetchedTurns || fetchedTurns.length === 0)) {
@@ -1897,6 +1901,7 @@ var ChatMachine = class {
1897
1901
  const curSessionId = this.state.session.currentSessionId;
1898
1902
  const curPendingQuestion = this.state.messaging.pendingQuestion;
1899
1903
  if (!curSessionId || !curPendingQuestion) return;
1904
+ const previousTurns = this.state.messaging.turns;
1900
1905
  this._updateMessaging({ loading: true });
1901
1906
  this._updateSession({ error: null, errorRetryable: false });
1902
1907
  const previousPendingQuestion = curPendingQuestion;
@@ -1914,19 +1919,28 @@ var ChatMachine = class {
1914
1919
  const fetchResult = await this.dataSource.fetchSession(curSessionId);
1915
1920
  if (this.disposed) return;
1916
1921
  if (fetchResult) {
1917
- this._updateMessaging({
1918
- turns: fetchResult.turns,
1919
- pendingQuestion: fetchResult.pendingQuestion || null
1920
- });
1922
+ this._updateSession({ session: fetchResult.session });
1923
+ this._updateMessaging({ pendingQuestion: fetchResult.pendingQuestion || null });
1924
+ const hasMalformedRefresh = previousTurns.length > 0 && Array.isArray(fetchResult.turns) && fetchResult.turns.length === 0;
1925
+ if (hasMalformedRefresh) {
1926
+ console.warn("[ChatMachine] Preserving previous turns due to empty post-HITL refetch payload", {
1927
+ sessionId: curSessionId,
1928
+ previousTurnCount: previousTurns.length
1929
+ });
1930
+ this._updateSession({
1931
+ error: "Failed to fully refresh session. Showing last known messages.",
1932
+ errorRetryable: true
1933
+ });
1934
+ } else {
1935
+ this._setTurnsFromFetch(fetchResult.turns);
1936
+ }
1921
1937
  } else {
1922
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1923
- this._updateSession({ error: "Failed to load updated session", errorRetryable: false });
1938
+ this._updateSession({ error: "Failed to load updated session", errorRetryable: true });
1924
1939
  }
1925
1940
  } catch (fetchErr) {
1926
1941
  if (this.disposed) return;
1927
- this._updateMessaging({ pendingQuestion: previousPendingQuestion });
1928
1942
  const normalized = normalizeRPCError(fetchErr, "Failed to load updated session");
1929
- this._updateSession({ error: normalized.userMessage, errorRetryable: normalized.retryable });
1943
+ this._updateSession({ error: normalized.userMessage, errorRetryable: true });
1930
1944
  }
1931
1945
  }
1932
1946
  } else {
@@ -2448,7 +2462,7 @@ function getFileVisual(mimeType, filename) {
2448
2462
  };
2449
2463
  }
2450
2464
  return {
2451
- icon: File,
2465
+ icon: File$1,
2452
2466
  iconColor: "text-gray-400 dark:text-gray-500",
2453
2467
  bgColor: "bg-gray-100 dark:bg-gray-800",
2454
2468
  label: (mime.split("/")[1] || "FILE").toUpperCase().slice(0, 4)
@@ -3286,7 +3300,7 @@ function InlineQuestionForm({ pendingQuestion }) {
3286
3300
  const [currentStep, setCurrentStep] = useState(0);
3287
3301
  const [answers, setAnswers] = useState({});
3288
3302
  const [otherTexts, setOtherTexts] = useState({});
3289
- const questions = pendingQuestion.questions;
3303
+ const questions = Array.isArray(pendingQuestion.questions) ? pendingQuestion.questions : [];
3290
3304
  const currentQuestion = questions[currentStep];
3291
3305
  const isLastStep = currentStep === questions.length - 1;
3292
3306
  const isFirstStep = currentStep === 0;
@@ -3380,9 +3394,31 @@ function InlineQuestionForm({ pendingQuestion }) {
3380
3394
  e.preventDefault();
3381
3395
  handleNext();
3382
3396
  };
3383
- if (!currentQuestion) return null;
3397
+ if (!currentQuestion) {
3398
+ return /* @__PURE__ */ 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: [
3399
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-800 dark:text-gray-200", children: t("BiChat.Error.SomethingWentWrong") }),
3400
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: t("BiChat.Error.UnexpectedError") }),
3401
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsxs(
3402
+ "button",
3403
+ {
3404
+ type: "button",
3405
+ onClick: handleRejectPendingQuestion,
3406
+ disabled: loading,
3407
+ 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",
3408
+ children: [
3409
+ /* @__PURE__ */ jsx(X, { size: 14, weight: "bold" }),
3410
+ t("BiChat.InlineQuestion.Dismiss")
3411
+ ]
3412
+ }
3413
+ ) })
3414
+ ] });
3415
+ }
3384
3416
  const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
3385
- const options = currentQuestion.options || [];
3417
+ const options = (currentQuestion.options || []).filter((option) => Boolean(option && typeof option.label === "string")).map((option, index) => ({
3418
+ id: option.id || `${currentQuestion.id}-option-${index}`,
3419
+ label: option.label,
3420
+ value: option.value || option.label
3421
+ }));
3386
3422
  const isOtherSelected = currentAnswer?.customText !== void 0;
3387
3423
  const canProceed = isCurrentAnswerValid();
3388
3424
  return /* @__PURE__ */ 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__ */ jsxs("form", { onSubmit: handleSubmit, children: [
@@ -4296,7 +4332,8 @@ function TurnBubble({
4296
4332
  userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
4297
4333
  assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
4298
4334
  };
4299
- const isSystemSummaryTurn = turn.userTurn.content.trim() === "" && turn.assistantTurn?.role === "system";
4335
+ const userContent = typeof turn.userTurn?.content === "string" ? turn.userTurn.content : "";
4336
+ const isSystemSummaryTurn = userContent.trim() === "" && turn.assistantTurn?.role === "system";
4300
4337
  return /* @__PURE__ */ jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
4301
4338
  !isSystemSummaryTurn && /* @__PURE__ */ jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsx(
4302
4339
  UserTurnView,
@@ -8074,12 +8111,63 @@ function DefaultErrorContent({
8074
8111
  ] })
8075
8112
  ] });
8076
8113
  }
8114
+ function StaticEmergencyErrorContent({
8115
+ error,
8116
+ onReset
8117
+ }) {
8118
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col items-center justify-center p-8 text-center min-h-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col items-center", children: [
8119
+ /* @__PURE__ */ jsxs("div", { className: "relative mb-5", children: [
8120
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full bg-red-100 scale-150 blur-md" }),
8121
+ /* @__PURE__ */ 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__ */ jsx(WarningCircle, { size: 28, className: "text-red-500", weight: "fill" }) })
8122
+ ] }),
8123
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900 mb-1.5", children: "Something went wrong" }),
8124
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-5 max-w-md leading-relaxed", children: error?.message || "An unexpected UI error occurred." }),
8125
+ onReset && /* @__PURE__ */ jsxs(
8126
+ "button",
8127
+ {
8128
+ type: "button",
8129
+ onClick: onReset,
8130
+ 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",
8131
+ children: [
8132
+ /* @__PURE__ */ jsx(ArrowClockwise, { size: 16, weight: "bold" }),
8133
+ "Try again"
8134
+ ]
8135
+ }
8136
+ )
8137
+ ] }) });
8138
+ }
8139
+ var FallbackGuard = class extends Component {
8140
+ constructor(props) {
8141
+ super(props);
8142
+ this.state = { fallbackFailed: false };
8143
+ }
8144
+ static getDerivedStateFromError() {
8145
+ return { fallbackFailed: true };
8146
+ }
8147
+ componentDidCatch(error, errorInfo) {
8148
+ this.props.onFallbackError?.(error, errorInfo);
8149
+ }
8150
+ render() {
8151
+ if (this.state.fallbackFailed) {
8152
+ return /* @__PURE__ */ jsx(StaticEmergencyErrorContent, { error: this.props.primaryError, onReset: this.props.onReset });
8153
+ }
8154
+ return this.props.renderFallback();
8155
+ }
8156
+ };
8077
8157
  var ErrorBoundary = class extends Component {
8078
8158
  constructor(props) {
8079
8159
  super(props);
8080
8160
  this.handleReset = () => {
8081
8161
  this.setState({ hasError: false, error: null });
8082
8162
  };
8163
+ this.handleFallbackError = (error, errorInfo) => {
8164
+ console.error("React Error Boundary fallback crashed:", {
8165
+ primaryError: this.state.error,
8166
+ fallbackError: error,
8167
+ errorInfo
8168
+ });
8169
+ this.props.onError?.(error, errorInfo);
8170
+ };
8083
8171
  this.state = { hasError: false, error: null };
8084
8172
  }
8085
8173
  static getDerivedStateFromError(error) {
@@ -8091,13 +8179,24 @@ var ErrorBoundary = class extends Component {
8091
8179
  }
8092
8180
  render() {
8093
8181
  if (this.state.hasError) {
8094
- if (this.props.fallback) {
8095
- if (typeof this.props.fallback === "function") {
8096
- return this.props.fallback(this.state.error, this.handleReset);
8097
- }
8098
- return this.props.fallback;
8099
- }
8100
- return /* @__PURE__ */ jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8182
+ return /* @__PURE__ */ jsx(
8183
+ FallbackGuard,
8184
+ {
8185
+ primaryError: this.state.error,
8186
+ onReset: this.handleReset,
8187
+ onFallbackError: this.handleFallbackError,
8188
+ renderFallback: () => {
8189
+ if (this.props.fallback) {
8190
+ if (typeof this.props.fallback === "function") {
8191
+ return this.props.fallback(this.state.error, this.handleReset);
8192
+ }
8193
+ return this.props.fallback;
8194
+ }
8195
+ return /* @__PURE__ */ jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
8196
+ }
8197
+ },
8198
+ `${this.state.error?.name ?? "Error"}:${this.state.error?.message ?? ""}`
8199
+ );
8101
8200
  }
8102
8201
  return this.props.children;
8103
8202
  }
@@ -12056,6 +12155,7 @@ function toSessionArtifact(artifact) {
12056
12155
  id: artifact.id,
12057
12156
  sessionId: artifact.sessionId,
12058
12157
  messageId: artifact.messageId,
12158
+ uploadId: artifact.uploadId,
12059
12159
  type: artifact.type,
12060
12160
  name: artifact.name,
12061
12161
  description: artifact.description,
@@ -12066,21 +12166,272 @@ function toSessionArtifact(artifact) {
12066
12166
  createdAt: artifact.createdAt
12067
12167
  };
12068
12168
  }
12069
- function toPendingQuestion(rpc) {
12070
- if (!rpc) return null;
12071
- const questions = (rpc.questions || []).map((q) => ({
12072
- id: q.id,
12073
- text: q.text,
12074
- type: q.type,
12075
- options: (q.options || []).map((o) => ({
12076
- id: o.id,
12077
- label: o.label,
12078
- value: o.label
12079
- }))
12080
- }));
12169
+ function warnMalformedSessionPayload(message, details) {
12170
+ console.warn(`[BiChat] ${message}`, details || {});
12171
+ }
12172
+ function readString(value, fallback = "") {
12173
+ return typeof value === "string" ? value : fallback;
12174
+ }
12175
+ function readNonEmptyString(value) {
12176
+ if (typeof value !== "string") return null;
12177
+ const trimmed = value.trim();
12178
+ return trimmed.length > 0 ? trimmed : null;
12179
+ }
12180
+ function readFiniteNumber(value, fallback = 0) {
12181
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
12182
+ }
12183
+ function readOptionalFiniteNumber(value) {
12184
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
12185
+ }
12186
+ function normalizeQuestionType(rawType) {
12187
+ const normalized = readString(rawType).trim().toUpperCase().replace(/[\s-]+/g, "_");
12188
+ return normalized === "MULTIPLE_CHOICE" ? "MULTIPLE_CHOICE" : "SINGLE_CHOICE";
12189
+ }
12190
+ function normalizeMessageRole(rawRole) {
12191
+ const normalized = readString(rawRole).trim().toLowerCase();
12192
+ if (normalized === "user" /* User */) return "user" /* User */;
12193
+ if (normalized === "system" /* System */) return "system" /* System */;
12194
+ if (normalized === "tool" /* Tool */) return "tool" /* Tool */;
12195
+ return "assistant" /* Assistant */;
12196
+ }
12197
+ function sanitizeAttachment(rawAttachment, turnId, index) {
12198
+ if (!isRecord(rawAttachment)) {
12199
+ warnMalformedSessionPayload("Dropped malformed attachment entry", { turnId, index });
12200
+ return null;
12201
+ }
12202
+ const filename = readString(rawAttachment.filename, "attachment");
12203
+ const mimeType = readString(rawAttachment.mimeType, "application/octet-stream");
12204
+ const id = readNonEmptyString(rawAttachment.id) || void 0;
12205
+ const clientKey = readNonEmptyString(rawAttachment.clientKey) || id || `${turnId}-attachment-${index}`;
12206
+ return {
12207
+ id,
12208
+ clientKey,
12209
+ filename,
12210
+ mimeType,
12211
+ sizeBytes: readFiniteNumber(rawAttachment.sizeBytes),
12212
+ uploadId: readOptionalFiniteNumber(rawAttachment.uploadId),
12213
+ base64Data: readNonEmptyString(rawAttachment.base64Data) || void 0,
12214
+ url: readNonEmptyString(rawAttachment.url) || void 0,
12215
+ preview: readNonEmptyString(rawAttachment.preview) || void 0
12216
+ };
12217
+ }
12218
+ function sanitizeUserAttachments(rawAttachments, turnId) {
12219
+ if (!Array.isArray(rawAttachments)) return [];
12220
+ const result = [];
12221
+ for (let i = 0; i < rawAttachments.length; i++) {
12222
+ const sanitized = sanitizeAttachment(rawAttachments[i], turnId, i);
12223
+ if (sanitized) result.push(sanitized);
12224
+ }
12225
+ return result;
12226
+ }
12227
+ function sanitizeAssistantArtifacts(rawArtifacts, turnId) {
12228
+ if (!Array.isArray(rawArtifacts)) return [];
12229
+ const artifacts = [];
12230
+ for (let i = 0; i < rawArtifacts.length; i++) {
12231
+ const raw = rawArtifacts[i];
12232
+ if (!isRecord(raw)) {
12233
+ warnMalformedSessionPayload("Dropped malformed assistant artifact", { turnId, index: i });
12234
+ continue;
12235
+ }
12236
+ const type = readString(raw.type).toLowerCase();
12237
+ if (type !== "excel" && type !== "pdf") {
12238
+ continue;
12239
+ }
12240
+ const url = readNonEmptyString(raw.url);
12241
+ if (!url) {
12242
+ warnMalformedSessionPayload("Dropped assistant artifact without url", { turnId, index: i });
12243
+ continue;
12244
+ }
12245
+ artifacts.push({
12246
+ type,
12247
+ filename: readString(raw.filename, "download"),
12248
+ url,
12249
+ sizeReadable: readNonEmptyString(raw.sizeReadable) || void 0,
12250
+ rowCount: typeof raw.rowCount === "number" && Number.isFinite(raw.rowCount) ? raw.rowCount : void 0,
12251
+ description: readNonEmptyString(raw.description) || void 0
12252
+ });
12253
+ }
12254
+ return artifacts;
12255
+ }
12256
+ function sanitizeAssistantTurn(rawAssistantTurn, fallbackCreatedAt, turnId) {
12257
+ if (rawAssistantTurn == null) return void 0;
12258
+ if (!isRecord(rawAssistantTurn)) {
12259
+ warnMalformedSessionPayload("Dropped malformed assistant turn payload", { turnId });
12260
+ return void 0;
12261
+ }
12262
+ const assistantID = readNonEmptyString(rawAssistantTurn.id);
12263
+ if (!assistantID) {
12264
+ warnMalformedSessionPayload("Dropped assistant turn without id", { turnId });
12265
+ return void 0;
12266
+ }
12267
+ const citations = Array.isArray(rawAssistantTurn.citations) ? rawAssistantTurn.citations.filter((item) => isRecord(item)).map((item, index) => ({
12268
+ id: readString(item.id, `${assistantID}-citation-${index}`),
12269
+ type: readString(item.type),
12270
+ title: readString(item.title),
12271
+ url: readString(item.url),
12272
+ startIndex: readFiniteNumber(item.startIndex),
12273
+ endIndex: readFiniteNumber(item.endIndex),
12274
+ excerpt: readNonEmptyString(item.excerpt) || void 0
12275
+ })) : [];
12276
+ const toolCalls = Array.isArray(rawAssistantTurn.toolCalls) ? rawAssistantTurn.toolCalls.filter((item) => isRecord(item)).map((item, index) => ({
12277
+ id: readString(item.id, `${assistantID}-tool-${index}`),
12278
+ name: readString(item.name),
12279
+ arguments: readString(item.arguments),
12280
+ result: readNonEmptyString(item.result) || void 0,
12281
+ error: readNonEmptyString(item.error) || void 0,
12282
+ durationMs: readFiniteNumber(item.durationMs)
12283
+ })) : [];
12284
+ const codeOutputs = Array.isArray(rawAssistantTurn.codeOutputs) ? rawAssistantTurn.codeOutputs.filter((item) => isRecord(item)).map((item) => ({
12285
+ type: (() => {
12286
+ const normalizedType = readString(item.type, "text").toLowerCase();
12287
+ if (normalizedType === "image" || normalizedType === "error") return normalizedType;
12288
+ return "text";
12289
+ })(),
12290
+ content: readString(item.content),
12291
+ filename: readNonEmptyString(item.filename) || void 0,
12292
+ mimeType: readNonEmptyString(item.mimeType) || void 0,
12293
+ sizeBytes: readOptionalFiniteNumber(item.sizeBytes)
12294
+ })) : [];
12295
+ const debugTrace = isRecord(rawAssistantTurn.debug) ? {
12296
+ generationMs: readOptionalFiniteNumber(rawAssistantTurn.debug.generationMs),
12297
+ usage: isRecord(rawAssistantTurn.debug.usage) ? {
12298
+ promptTokens: readFiniteNumber(rawAssistantTurn.debug.usage.promptTokens),
12299
+ completionTokens: readFiniteNumber(rawAssistantTurn.debug.usage.completionTokens),
12300
+ totalTokens: readFiniteNumber(rawAssistantTurn.debug.usage.totalTokens),
12301
+ cachedTokens: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cachedTokens),
12302
+ cost: readOptionalFiniteNumber(rawAssistantTurn.debug.usage.cost)
12303
+ } : void 0,
12304
+ tools: Array.isArray(rawAssistantTurn.debug.tools) ? rawAssistantTurn.debug.tools.filter((tool) => isRecord(tool)).map((tool) => ({
12305
+ callId: readNonEmptyString(tool.callId) || void 0,
12306
+ name: readString(tool.name),
12307
+ arguments: readNonEmptyString(tool.arguments) || void 0,
12308
+ result: readNonEmptyString(tool.result) || void 0,
12309
+ error: readNonEmptyString(tool.error) || void 0,
12310
+ durationMs: readOptionalFiniteNumber(tool.durationMs)
12311
+ })) : []
12312
+ } : void 0;
12313
+ return {
12314
+ id: assistantID,
12315
+ role: normalizeMessageRole(rawAssistantTurn.role),
12316
+ content: readString(rawAssistantTurn.content),
12317
+ explanation: readNonEmptyString(rawAssistantTurn.explanation) || void 0,
12318
+ citations,
12319
+ toolCalls,
12320
+ chartData: void 0,
12321
+ artifacts: sanitizeAssistantArtifacts(rawAssistantTurn.artifacts, turnId),
12322
+ codeOutputs,
12323
+ debug: debugTrace,
12324
+ createdAt: readString(rawAssistantTurn.createdAt, fallbackCreatedAt)
12325
+ };
12326
+ }
12327
+ function sanitizeConversationTurn(rawTurn, index, fallbackSessionID) {
12328
+ if (!isRecord(rawTurn)) {
12329
+ warnMalformedSessionPayload("Dropped malformed turn payload (not an object)", { index });
12330
+ return null;
12331
+ }
12332
+ if (!isRecord(rawTurn.userTurn)) {
12333
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn)", { index });
12334
+ return null;
12335
+ }
12336
+ const userTurnID = readNonEmptyString(rawTurn.userTurn.id);
12337
+ if (!userTurnID) {
12338
+ warnMalformedSessionPayload("Dropped malformed turn payload (missing user turn id)", { index });
12339
+ return null;
12340
+ }
12341
+ const turnID = readString(rawTurn.id, userTurnID);
12342
+ const createdAt = readString(
12343
+ rawTurn.createdAt,
12344
+ readString(rawTurn.userTurn.createdAt, (/* @__PURE__ */ new Date()).toISOString())
12345
+ );
12081
12346
  return {
12082
- id: rpc.checkpointId,
12083
- turnId: rpc.turnId || "",
12347
+ id: turnID,
12348
+ sessionId: readString(rawTurn.sessionId, fallbackSessionID),
12349
+ userTurn: {
12350
+ id: userTurnID,
12351
+ content: readString(rawTurn.userTurn.content),
12352
+ attachments: sanitizeUserAttachments(rawTurn.userTurn.attachments, turnID),
12353
+ createdAt: readString(rawTurn.userTurn.createdAt, createdAt)
12354
+ },
12355
+ assistantTurn: sanitizeAssistantTurn(rawTurn.assistantTurn, createdAt, turnID),
12356
+ createdAt
12357
+ };
12358
+ }
12359
+ function sanitizeConversationTurns(rawTurns, sessionID) {
12360
+ if (!Array.isArray(rawTurns)) {
12361
+ warnMalformedSessionPayload("Session payload contained non-array turns field", { sessionID });
12362
+ return [];
12363
+ }
12364
+ const turns = [];
12365
+ let dropped = 0;
12366
+ for (let i = 0; i < rawTurns.length; i++) {
12367
+ const sanitizedTurn = sanitizeConversationTurn(rawTurns[i], i, sessionID);
12368
+ if (sanitizedTurn) {
12369
+ turns.push(sanitizedTurn);
12370
+ } else {
12371
+ dropped++;
12372
+ }
12373
+ }
12374
+ if (dropped > 0) {
12375
+ warnMalformedSessionPayload("Dropped malformed turns from session payload", {
12376
+ sessionID,
12377
+ dropped,
12378
+ total: rawTurns.length
12379
+ });
12380
+ }
12381
+ return turns;
12382
+ }
12383
+ function sanitizePendingQuestion(rawPendingQuestion, sessionID) {
12384
+ if (!rawPendingQuestion) return null;
12385
+ const checkpointID = readNonEmptyString(rawPendingQuestion.checkpointId);
12386
+ if (!checkpointID) {
12387
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion without checkpointId", { sessionID });
12388
+ return null;
12389
+ }
12390
+ if (!Array.isArray(rawPendingQuestion.questions)) {
12391
+ warnMalformedSessionPayload("Pending question had non-array questions payload", {
12392
+ sessionID,
12393
+ checkpointID
12394
+ });
12395
+ }
12396
+ const questions = Array.isArray(rawPendingQuestion.questions) ? rawPendingQuestion.questions.filter((question) => {
12397
+ if (!question || !isRecord(question)) {
12398
+ warnMalformedSessionPayload("Dropped malformed question from pendingQuestion", {
12399
+ sessionID,
12400
+ checkpointID
12401
+ });
12402
+ return false;
12403
+ }
12404
+ return true;
12405
+ }).map((question, index) => {
12406
+ const questionID = readString(question.id, `${checkpointID}-q-${index}`);
12407
+ const options = Array.isArray(question.options) ? question.options.filter((option) => {
12408
+ if (!option || !isRecord(option)) {
12409
+ warnMalformedSessionPayload("Dropped malformed pendingQuestion option", {
12410
+ sessionID,
12411
+ checkpointID,
12412
+ questionID
12413
+ });
12414
+ return false;
12415
+ }
12416
+ return true;
12417
+ }).map((option, optionIndex) => {
12418
+ const label = readString(option.label);
12419
+ return {
12420
+ id: readString(option.id, `${questionID}-opt-${optionIndex}`),
12421
+ label,
12422
+ value: label
12423
+ };
12424
+ }) : [];
12425
+ return {
12426
+ id: questionID,
12427
+ text: readString(question.text),
12428
+ type: normalizeQuestionType(question.type),
12429
+ options
12430
+ };
12431
+ }) : [];
12432
+ return {
12433
+ id: checkpointID,
12434
+ turnId: readString(rawPendingQuestion.turnId),
12084
12435
  questions,
12085
12436
  status: "PENDING"
12086
12437
  };
@@ -12276,6 +12627,7 @@ var HttpDataSource = class {
12276
12627
  this.abortController = null;
12277
12628
  this.config = {
12278
12629
  streamEndpoint: "/stream",
12630
+ uploadEndpoint: "/api/uploads",
12279
12631
  timeout: 12e4,
12280
12632
  ...config
12281
12633
  };
@@ -12311,6 +12663,83 @@ var HttpDataSource = class {
12311
12663
  }
12312
12664
  return headers;
12313
12665
  }
12666
+ createUploadHeaders(additionalHeaders) {
12667
+ const headers = new Headers({
12668
+ ...this.config.headers,
12669
+ ...additionalHeaders
12670
+ });
12671
+ const csrfToken = this.getCSRFToken();
12672
+ if (csrfToken) {
12673
+ headers.set("X-CSRF-Token", csrfToken);
12674
+ }
12675
+ headers.delete("Content-Type");
12676
+ return headers;
12677
+ }
12678
+ async uploadFile(file) {
12679
+ const formData = new FormData();
12680
+ formData.append("file", file);
12681
+ const response = await fetch(`${this.config.baseUrl}${this.config.uploadEndpoint}`, {
12682
+ method: "POST",
12683
+ headers: this.createUploadHeaders(),
12684
+ body: formData
12685
+ });
12686
+ let payload = null;
12687
+ try {
12688
+ payload = await response.json();
12689
+ } catch {
12690
+ payload = null;
12691
+ }
12692
+ if (!response.ok) {
12693
+ const errorMessage = isRecord(payload) && typeof payload.error === "string" ? payload.error : `Upload failed: HTTP ${response.status}`;
12694
+ throw new Error(errorMessage);
12695
+ }
12696
+ if (!isRecord(payload) || typeof payload.id !== "number" || payload.id <= 0) {
12697
+ throw new Error("Upload failed: invalid response payload");
12698
+ }
12699
+ return {
12700
+ id: payload.id,
12701
+ url: typeof payload.url === "string" ? payload.url : "",
12702
+ path: typeof payload.path === "string" ? payload.path : "",
12703
+ name: typeof payload.name === "string" ? payload.name : file.name,
12704
+ mimetype: typeof payload.mimetype === "string" ? payload.mimetype : file.type,
12705
+ size: typeof payload.size === "number" && Number.isFinite(payload.size) ? payload.size : file.size
12706
+ };
12707
+ }
12708
+ async attachmentToFile(attachment) {
12709
+ if (attachment.base64Data && attachment.base64Data.trim().length > 0) {
12710
+ const base64Data = attachment.base64Data.trim();
12711
+ const dataUrl = base64Data.startsWith("data:") ? base64Data : `data:${attachment.mimeType || "application/octet-stream"};base64,${base64Data}`;
12712
+ const blob = await fetch(dataUrl).then((response) => response.blob());
12713
+ return new File([blob], attachment.filename, {
12714
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12715
+ });
12716
+ }
12717
+ if (attachment.url) {
12718
+ const response = await fetch(attachment.url);
12719
+ if (!response.ok) {
12720
+ throw new Error(`Failed to read attachment source: HTTP ${response.status}`);
12721
+ }
12722
+ const blob = await response.blob();
12723
+ return new File([blob], attachment.filename, {
12724
+ type: attachment.mimeType || blob.type || "application/octet-stream"
12725
+ });
12726
+ }
12727
+ throw new Error(`Attachment "${attachment.filename}" has no uploadable data`);
12728
+ }
12729
+ async ensureAttachmentUpload(attachment) {
12730
+ if (typeof attachment.uploadId === "number" && attachment.uploadId > 0) {
12731
+ return {
12732
+ id: attachment.uploadId,
12733
+ url: attachment.url || "",
12734
+ path: "",
12735
+ name: attachment.filename,
12736
+ mimetype: attachment.mimeType,
12737
+ size: attachment.sizeBytes
12738
+ };
12739
+ }
12740
+ const file = await this.attachmentToFile(attachment);
12741
+ return this.uploadFile(file);
12742
+ }
12314
12743
  async callRPC(method, params) {
12315
12744
  return this.rpc.callTyped(method, params);
12316
12745
  }
@@ -12333,11 +12762,22 @@ var HttpDataSource = class {
12333
12762
  return { artifacts: [], hasMore: false, nextOffset: 0 };
12334
12763
  })
12335
12764
  ]);
12336
- const turns = attachArtifactsToTurns(normalizeTurns(data.turns), artifactsData.artifacts || []);
12765
+ const sanitizedTurns = sanitizeConversationTurns(data.turns, id);
12766
+ const turns = attachArtifactsToTurns(
12767
+ normalizeTurns(sanitizedTurns),
12768
+ artifactsData.artifacts || []
12769
+ );
12770
+ const pendingQuestion = sanitizePendingQuestion(data.pendingQuestion, id);
12771
+ if (data.pendingQuestion && pendingQuestion && pendingQuestion.questions.length === 0) {
12772
+ warnMalformedSessionPayload("Pending question normalized to zero renderable questions", {
12773
+ sessionID: id,
12774
+ checkpointID: pendingQuestion.id
12775
+ });
12776
+ }
12337
12777
  return {
12338
12778
  session: toSession(data.session),
12339
12779
  turns,
12340
- pendingQuestion: toPendingQuestion(data.pendingQuestion)
12780
+ pendingQuestion
12341
12781
  };
12342
12782
  } catch (err) {
12343
12783
  if (isSessionNotFoundError(err)) {
@@ -12369,27 +12809,12 @@ var HttpDataSource = class {
12369
12809
  return { artifacts: [] };
12370
12810
  }
12371
12811
  validateFileCount(0, files.length, 10);
12372
- const attachments = [];
12373
- for (const file of files) {
12374
- validateAttachmentFile(file);
12375
- const base64Data = await convertToBase64(file);
12376
- attachments.push({
12377
- clientKey: crypto.randomUUID(),
12378
- filename: file.name,
12379
- mimeType: file.type,
12380
- sizeBytes: file.size,
12381
- base64Data
12382
- });
12383
- }
12812
+ files.forEach((file) => validateAttachmentFile(file));
12813
+ const uploads = await Promise.all(files.map((file) => this.uploadFile(file)));
12384
12814
  const data = await this.callRPC("bichat.session.uploadArtifacts", {
12385
12815
  sessionId,
12386
- attachments: attachments.map((a) => ({
12387
- id: "",
12388
- // Backend will assign ID
12389
- filename: a.filename,
12390
- mimeType: a.mimeType,
12391
- sizeBytes: a.sizeBytes,
12392
- base64Data: a.base64Data
12816
+ attachments: uploads.map((upload) => ({
12817
+ uploadId: upload.id
12393
12818
  }))
12394
12819
  });
12395
12820
  return {
@@ -12420,22 +12845,21 @@ var HttpDataSource = class {
12420
12845
  signal.addEventListener("abort", onExternalAbort);
12421
12846
  }
12422
12847
  const url = `${this.config.baseUrl}${this.config.streamEndpoint}`;
12423
- const payload = {
12424
- sessionId,
12425
- content,
12426
- debugMode: options?.debugMode ?? false,
12427
- replaceFromMessageId: options?.replaceFromMessageID,
12428
- attachments: attachments.map((a) => ({
12429
- filename: a.filename,
12430
- mimeType: a.mimeType,
12431
- sizeBytes: a.sizeBytes,
12432
- base64Data: a.base64Data,
12433
- url: a.url
12434
- }))
12435
- };
12436
12848
  let connectionTimeoutID;
12437
12849
  let connectionTimedOut = false;
12438
12850
  try {
12851
+ const uploads = await Promise.all(
12852
+ attachments.map((attachment) => this.ensureAttachmentUpload(attachment))
12853
+ );
12854
+ const payload = {
12855
+ sessionId,
12856
+ content,
12857
+ debugMode: options?.debugMode ?? false,
12858
+ replaceFromMessageId: options?.replaceFromMessageID,
12859
+ attachments: uploads.map((upload) => ({
12860
+ uploadId: upload.id
12861
+ }))
12862
+ };
12439
12863
  const timeoutMs = this.config.timeout ?? 0;
12440
12864
  if (timeoutMs > 0) {
12441
12865
  connectionTimeoutID = setTimeout(() => {