@arbidocs/sdk 0.3.10 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
2
+ import path2 from 'path';
3
3
  import os from 'os';
4
4
  import { createArbiClient, base64ToBytes, deriveEncryptionKeypairFromSigning, sealedBoxDecrypt, createWorkspaceKeyHeader, buildWebSocketUrl, createAuthMessage, parseServerMessage, isMessageType } from '@arbidocs/client';
5
5
 
@@ -36,14 +36,64 @@ function requireOk(result, message) {
36
36
  }
37
37
  }
38
38
  function getErrorMessage(err) {
39
- return err instanceof Error ? err.message : String(err);
39
+ if (!(err instanceof Error)) return String(err);
40
+ const rootCause = getDeepestCause(err);
41
+ if (rootCause !== err) {
42
+ const rootMsg = rootCause.message || String(rootCause);
43
+ if (err.message && !err.message.includes(rootMsg)) {
44
+ return `${err.message}: ${rootMsg}`;
45
+ }
46
+ }
47
+ return err.message;
48
+ }
49
+ function getDeepestCause(err) {
50
+ let current = err;
51
+ const seen = /* @__PURE__ */ new Set();
52
+ while (current.cause instanceof Error && !seen.has(current.cause)) {
53
+ seen.add(current);
54
+ current = current.cause;
55
+ }
56
+ return current;
57
+ }
58
+ function getErrorCode(err) {
59
+ if (!(err instanceof Error)) return void 0;
60
+ let current = err;
61
+ const seen = /* @__PURE__ */ new Set();
62
+ while (current && !seen.has(current)) {
63
+ seen.add(current);
64
+ const code = current.code;
65
+ if (typeof code === "string") return code;
66
+ current = current.cause instanceof Error ? current.cause : void 0;
67
+ }
68
+ return void 0;
40
69
  }
41
70
 
42
71
  // src/fetch.ts
43
- var STATUS_MESSAGES = {
72
+ var STATUS_FALLBACKS = {
44
73
  401: "Token has expired. Please run: arbi login",
74
+ 403: "Access denied. You may not have permission for this workspace.",
75
+ 404: "Resource not found.",
45
76
  503: "The selected LLM is not responding. Please retry or select another model."
46
77
  };
78
+ async function extractErrorMessage(res) {
79
+ let bodyDetail;
80
+ try {
81
+ const text = await res.text();
82
+ if (text) {
83
+ try {
84
+ const json = JSON.parse(text);
85
+ bodyDetail = json.detail || json.message || json.error || void 0;
86
+ } catch {
87
+ if (text.length < 200) bodyDetail = text;
88
+ }
89
+ }
90
+ } catch {
91
+ }
92
+ if (bodyDetail) return `Request failed (${res.status}): ${bodyDetail}`;
93
+ const fallback = STATUS_FALLBACKS[res.status];
94
+ if (fallback) return fallback;
95
+ return `Request failed: ${res.status} ${res.statusText}`;
96
+ }
47
97
  async function authenticatedFetch(options) {
48
98
  const { baseUrl, accessToken, workspaceKeyHeader, path: path3, method, body, headers } = options;
49
99
  const res = await fetch(`${baseUrl}${path3}`, {
@@ -56,16 +106,15 @@ async function authenticatedFetch(options) {
56
106
  body
57
107
  });
58
108
  if (!res.ok) {
59
- const knownMessage = STATUS_MESSAGES[res.status];
60
- if (knownMessage) throw new Error(knownMessage);
61
- const text = await res.text().catch(() => "");
62
- throw new Error(`Request failed: ${res.status} ${text}`);
109
+ const message = await extractErrorMessage(res);
110
+ throw new ArbiApiError(message, { status: res.status, statusText: res.statusText });
63
111
  }
64
112
  return res;
65
113
  }
66
114
  var DEFAULT_SESSION = {
67
115
  lastMessageExtId: null,
68
- conversationExtId: null
116
+ conversationExtId: null,
117
+ workspaceId: null
69
118
  };
70
119
  var FileConfigStore = class {
71
120
  configDir;
@@ -73,10 +122,10 @@ var FileConfigStore = class {
73
122
  credentialsFile;
74
123
  sessionFile;
75
124
  constructor(configDir) {
76
- this.configDir = configDir ?? process.env.ARBI_CONFIG_DIR ?? path.join(os.homedir(), ".arbi");
77
- this.configFile = path.join(this.configDir, "config.json");
78
- this.credentialsFile = path.join(this.configDir, "credentials.json");
79
- this.sessionFile = path.join(this.configDir, "session.json");
125
+ this.configDir = configDir ?? process.env.ARBI_CONFIG_DIR ?? path2.join(os.homedir(), ".arbi");
126
+ this.configFile = path2.join(this.configDir, "config.json");
127
+ this.credentialsFile = path2.join(this.configDir, "credentials.json");
128
+ this.sessionFile = path2.join(this.configDir, "session.json");
80
129
  }
81
130
  ensureConfigDir() {
82
131
  if (!fs.existsSync(this.configDir)) {
@@ -147,6 +196,77 @@ var FileConfigStore = class {
147
196
  clearChatSession() {
148
197
  this.saveChatSession({ ...DEFAULT_SESSION });
149
198
  }
199
+ /**
200
+ * Try to resolve config from multiple sources, in priority order:
201
+ *
202
+ * 1. Existing `~/.arbi/config.json` (highest priority)
203
+ * 2. `ARBI_SERVER_URL` environment variable
204
+ * 3. `.env` file in `searchDir` → read `VITE_DEPLOYMENT_DOMAIN`
205
+ * 4. `public/config.json` in `searchDir` → read `deployment.domain`
206
+ * 5. Default to `https://localhost`
207
+ *
208
+ * Returns `{ config, source }` where source describes where the config came from.
209
+ * Saves auto-detected config to disk so subsequent calls use the fast path.
210
+ */
211
+ resolveConfigWithFallbacks(searchDir) {
212
+ const existing = this.getConfig();
213
+ if (existing?.baseUrl) {
214
+ return { config: existing, source: "config" };
215
+ }
216
+ const envUrl = process.env.ARBI_SERVER_URL;
217
+ if (envUrl) {
218
+ const domain = new URL(envUrl).hostname;
219
+ const config2 = { baseUrl: envUrl.replace(/\/$/, ""), deploymentDomain: domain };
220
+ this.saveConfig(config2);
221
+ return { config: config2, source: "ARBI_SERVER_URL env var" };
222
+ }
223
+ const dir = searchDir || process.cwd();
224
+ const dotenvConfig = this.readDotEnvDomain(path2.join(dir, ".env"));
225
+ if (dotenvConfig) {
226
+ this.saveConfig(dotenvConfig);
227
+ return { config: dotenvConfig, source: ".env file" };
228
+ }
229
+ const publicConfig = this.readPublicConfigDomain(path2.join(dir, "public", "config.json"));
230
+ if (publicConfig) {
231
+ this.saveConfig(publicConfig);
232
+ return { config: publicConfig, source: "public/config.json" };
233
+ }
234
+ const config = { baseUrl: "https://localhost", deploymentDomain: "localhost" };
235
+ this.saveConfig(config);
236
+ return { config, source: "default (https://localhost)" };
237
+ }
238
+ /**
239
+ * Read VITE_DEPLOYMENT_DOMAIN from a .env file.
240
+ */
241
+ readDotEnvDomain(filePath) {
242
+ try {
243
+ const content = fs.readFileSync(filePath, "utf-8");
244
+ const match = content.match(/^VITE_DEPLOYMENT_DOMAIN\s*=\s*(.+)$/m);
245
+ if (match) {
246
+ const domain = match[1].trim().replace(/^["']|["']$/g, "");
247
+ if (domain) {
248
+ return { baseUrl: `https://${domain}`, deploymentDomain: domain };
249
+ }
250
+ }
251
+ } catch {
252
+ }
253
+ return null;
254
+ }
255
+ /**
256
+ * Read deployment.domain from a public/config.json file.
257
+ */
258
+ readPublicConfigDomain(filePath) {
259
+ try {
260
+ const content = fs.readFileSync(filePath, "utf-8");
261
+ const json = JSON.parse(content);
262
+ const domain = json?.deployment?.domain;
263
+ if (domain) {
264
+ return { baseUrl: `https://${domain}`, deploymentDomain: domain };
265
+ }
266
+ } catch {
267
+ }
268
+ return null;
269
+ }
150
270
  };
151
271
  function formatWorkspaceChoices(wsList) {
152
272
  return wsList.map((ws) => {
@@ -172,7 +292,11 @@ async function createAuthenticatedClient(config, creds, store) {
172
292
  });
173
293
  store.saveCredentials({
174
294
  ...creds,
175
- serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey)
295
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey),
296
+ accessToken: void 0,
297
+ workspaceKeyHeader: void 0,
298
+ workspaceId: void 0,
299
+ tokenTimestamp: void 0
176
300
  });
177
301
  return { arbi, loginResult };
178
302
  }
@@ -187,7 +311,12 @@ async function performPasswordLogin(config, email, password, store) {
187
311
  store.saveCredentials({
188
312
  email,
189
313
  signingPrivateKeyBase64: arbi.crypto.bytesToBase64(loginResult.signingPrivateKey),
190
- serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey)
314
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey),
315
+ // Clear any cached workspace tokens — new session key invalidates them
316
+ accessToken: void 0,
317
+ workspaceKeyHeader: void 0,
318
+ workspaceId: void 0,
319
+ tokenTimestamp: void 0
191
320
  });
192
321
  return { arbi, loginResult, config };
193
322
  }
@@ -250,6 +379,10 @@ async function resolveAuth(store) {
250
379
  const { arbi, loginResult } = await createAuthenticatedClient(config, creds, store);
251
380
  return { arbi, loginResult, config };
252
381
  }
382
+ var TOKEN_MAX_AGE_MS = 50 * 60 * 1e3;
383
+ function isCachedTokenValid(creds, workspaceId) {
384
+ return !!(creds.accessToken && creds.workspaceKeyHeader && creds.workspaceId === workspaceId && creds.tokenTimestamp && Date.now() - new Date(creds.tokenTimestamp).getTime() < TOKEN_MAX_AGE_MS);
385
+ }
253
386
  async function resolveWorkspace(store, workspaceOpt) {
254
387
  const config = store.requireConfig();
255
388
  const creds = store.requireCredentials();
@@ -257,6 +390,32 @@ async function resolveWorkspace(store, workspaceOpt) {
257
390
  if (!workspaceId) {
258
391
  throw new ArbiError("No workspace selected. Run: arbi workspace select <id>");
259
392
  }
393
+ if (isCachedTokenValid(creds, workspaceId)) {
394
+ const arbi2 = createArbiClient({
395
+ baseUrl: config.baseUrl,
396
+ deploymentDomain: config.deploymentDomain,
397
+ credentials: "omit"
398
+ });
399
+ await arbi2.crypto.initSodium();
400
+ arbi2.session.setSelectedWorkspace(workspaceId);
401
+ arbi2.session.setAccessToken(creds.accessToken);
402
+ arbi2.session.setCachedWorkspaceHeader(workspaceId, creds.workspaceKeyHeader);
403
+ const signingPrivateKey = base64ToBytes(creds.signingPrivateKeyBase64);
404
+ const serverSessionKey = base64ToBytes(creds.serverSessionKeyBase64);
405
+ const loginResult2 = {
406
+ accessToken: creds.accessToken,
407
+ signingPrivateKey,
408
+ serverSessionKey
409
+ };
410
+ return {
411
+ arbi: arbi2,
412
+ loginResult: loginResult2,
413
+ config,
414
+ workspaceId,
415
+ accessToken: creds.accessToken,
416
+ workspaceKeyHeader: creds.workspaceKeyHeader
417
+ };
418
+ }
260
419
  const { arbi, loginResult } = await createAuthenticatedClient(config, creds, store);
261
420
  await selectWorkspaceById(
262
421
  arbi,
@@ -269,10 +428,57 @@ async function resolveWorkspace(store, workspaceOpt) {
269
428
  if (!accessToken || !workspaceKeyHeader) {
270
429
  throw new ArbiError("Authentication error \u2014 missing token or workspace key");
271
430
  }
431
+ store.saveCredentials({
432
+ ...store.requireCredentials(),
433
+ accessToken,
434
+ workspaceKeyHeader,
435
+ workspaceId,
436
+ tokenTimestamp: (/* @__PURE__ */ new Date()).toISOString()
437
+ });
272
438
  return { arbi, loginResult, config, workspaceId, accessToken, workspaceKeyHeader };
273
439
  }
274
440
 
275
441
  // src/sse.ts
442
+ var TOOL_LABELS = {
443
+ search_documents: "Searching documents",
444
+ get_document_passages: "Reading document",
445
+ get_table_of_contents: "Getting table of contents",
446
+ view_document_pages: "Viewing document pages",
447
+ get_full_document: "Reading full document",
448
+ web_search: "Searching the web",
449
+ read_url: "Reading web pages",
450
+ ask_user: "Asking user",
451
+ compaction: "Compacting conversation",
452
+ personal_agent: "Running agent",
453
+ create_artifact: "Creating artifact",
454
+ create_plan: "Creating plan",
455
+ save_skill: "Saving skill",
456
+ run_code: "Running code"
457
+ };
458
+ var LIFECYCLE_LABELS = {
459
+ evaluation: "Evaluating results",
460
+ answering: "Writing answer",
461
+ reviewing: "Reviewing answer",
462
+ planning: "Planning",
463
+ tool_progress: "Working"
464
+ };
465
+ function formatAgentStepLabel(step) {
466
+ if (step.focus) return step.focus;
467
+ const detail = step.detail;
468
+ if (step.step === "tool_progress" && detail && detail.length > 0) {
469
+ const toolName = detail[0].tool;
470
+ const label = toolName && TOOL_LABELS[toolName] || LIFECYCLE_LABELS.tool_progress;
471
+ const message = detail[0].message;
472
+ return message ? `${label}: ${message}` : label;
473
+ }
474
+ if (step.step) {
475
+ return LIFECYCLE_LABELS[step.step] || step.step;
476
+ }
477
+ if (detail && detail.length > 0 && detail[0].tool) {
478
+ return TOOL_LABELS[detail[0].tool] || detail[0].tool;
479
+ }
480
+ return "";
481
+ }
276
482
  function parseSSEEvents(chunk, buffer) {
277
483
  const combined = buffer + chunk;
278
484
  const events = [];
@@ -343,6 +549,11 @@ async function streamSSE(response, callbacks = {}) {
343
549
  usage = data.response.usage;
344
550
  callbacks.onUsage?.(data.response.usage);
345
551
  }
552
+ if (data.response?.metadata) {
553
+ const meta = data.response.metadata;
554
+ metadata = meta;
555
+ callbacks.onMetadata?.(meta);
556
+ }
346
557
  if (data.t != null) callbacks.onElapsedTime?.(data.t);
347
558
  callbacks.onComplete?.();
348
559
  },
@@ -355,8 +566,8 @@ async function streamSSE(response, callbacks = {}) {
355
566
  // ARBI-specific events (dot-prefixed from server)
356
567
  "arbi.agent_step": (raw) => {
357
568
  const data = JSON.parse(raw);
358
- const focus = data.focus || data.step || "";
359
- if (focus) agentSteps.push(focus);
569
+ const label = formatAgentStepLabel(data);
570
+ if (label) agentSteps.push(label);
360
571
  callbacks.onAgentStep?.(data);
361
572
  if (data.t != null) callbacks.onElapsedTime?.(data.t);
362
573
  },
@@ -634,16 +845,34 @@ function formatUserName(user) {
634
845
  // src/operations/documents.ts
635
846
  var documents_exports = {};
636
847
  __export(documents_exports, {
848
+ SUPPORTED_EXTENSIONS: () => SUPPORTED_EXTENSIONS,
637
849
  deleteDocuments: () => deleteDocuments,
638
850
  downloadDocument: () => downloadDocument,
639
851
  getDocuments: () => getDocuments,
640
852
  getParsedContent: () => getParsedContent,
641
853
  listDocuments: () => listDocuments,
854
+ sanitizeFolderPath: () => sanitizeFolderPath,
642
855
  updateDocuments: () => updateDocuments,
643
856
  uploadFile: () => uploadFile,
644
- uploadLocalFile: () => uploadLocalFile,
857
+ uploadFiles: () => uploadFiles,
645
858
  uploadUrl: () => uploadUrl
646
859
  });
860
+ var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
861
+ ".pdf",
862
+ ".txt",
863
+ ".md",
864
+ ".html",
865
+ ".doc",
866
+ ".docx",
867
+ ".rtf",
868
+ ".ppt",
869
+ ".pptx",
870
+ ".xls",
871
+ ".xlsx"
872
+ ]);
873
+ function sanitizeFolderPath(folderPath) {
874
+ return folderPath.replace(/[^a-zA-Z0-9_\-/]/g, "_").replace(/_{2,}/g, "_");
875
+ }
647
876
  async function listDocuments(arbi) {
648
877
  return requireData(await arbi.fetch.GET("/v1/document/list"), "Failed to fetch documents");
649
878
  }
@@ -682,22 +911,31 @@ async function getParsedContent(auth, docId, stage) {
682
911
  });
683
912
  return res.json();
684
913
  }
685
- async function uploadFile(auth, workspaceId, fileData, fileName) {
914
+ async function uploadFile(auth, workspaceId, fileData, fileName, options) {
686
915
  const formData = new FormData();
687
916
  formData.append("files", fileData, fileName);
917
+ const params = new URLSearchParams({ workspace_ext_id: workspaceId });
918
+ if (options?.folder) params.set("folder", sanitizeFolderPath(options.folder));
688
919
  const res = await authenticatedFetch({
689
920
  ...auth,
690
- path: `/v1/document/upload?workspace_ext_id=${workspaceId}`,
921
+ path: `/v1/document/upload?${params.toString()}`,
691
922
  method: "POST",
692
923
  body: formData
693
924
  });
694
925
  return res.json();
695
926
  }
696
- async function uploadLocalFile(auth, workspaceId, filePath) {
697
- const fileBuffer = fs.readFileSync(filePath);
698
- const fileName = path.basename(filePath);
699
- const result = await uploadFile(auth, workspaceId, new Blob([fileBuffer]), fileName);
700
- return { ...result, fileName };
927
+ async function uploadFiles(auth, workspaceId, files, options) {
928
+ const formData = new FormData();
929
+ for (const f of files) formData.append("files", f.data, f.name);
930
+ const params = new URLSearchParams({ workspace_ext_id: workspaceId });
931
+ if (options?.folder) params.set("folder", sanitizeFolderPath(options.folder));
932
+ const res = await authenticatedFetch({
933
+ ...auth,
934
+ path: `/v1/document/upload?${params.toString()}`,
935
+ method: "POST",
936
+ body: formData
937
+ });
938
+ return res.json();
701
939
  }
702
940
  async function downloadDocument(auth, docId) {
703
941
  return authenticatedFetch({
@@ -897,6 +1135,8 @@ async function retrieve(options) {
897
1135
  input: query,
898
1136
  workspace_ext_id: workspaceId,
899
1137
  stream: false,
1138
+ background: false,
1139
+ store: true,
900
1140
  tools,
901
1141
  ...model ? { model } : {}
902
1142
  };
@@ -912,6 +1152,8 @@ async function queryAssistant(options) {
912
1152
  input: question,
913
1153
  workspace_ext_id: workspaceId,
914
1154
  stream: true,
1155
+ background: false,
1156
+ store: true,
915
1157
  tools: {
916
1158
  retrieval_chunk: buildRetrievalChunkTool(docIds),
917
1159
  retrieval_full_context: buildRetrievalFullContextTool([])
@@ -1371,11 +1613,12 @@ var Arbi = class {
1371
1613
  workspaceId ?? this.requireWorkspace(),
1372
1614
  shared
1373
1615
  ),
1374
- uploadFile: (fileData, fileName, workspaceId) => uploadFile(
1616
+ uploadFile: (fileData, fileName, options) => uploadFile(
1375
1617
  this.getAuthHeaders(),
1376
- workspaceId ?? this.requireWorkspace(),
1618
+ options?.workspaceId ?? this.requireWorkspace(),
1377
1619
  fileData,
1378
- fileName
1620
+ fileName,
1621
+ options?.folder ? { folder: options.folder } : void 0
1379
1622
  ),
1380
1623
  download: (docId) => downloadDocument(this.getAuthHeaders(), docId),
1381
1624
  getParsedContent: (docId, stage) => getParsedContent(this.getAuthHeaders(), docId, stage)
@@ -1517,6 +1760,123 @@ var Arbi = class {
1517
1760
  }
1518
1761
  };
1519
1762
 
1520
- export { Arbi, ArbiApiError, ArbiError, FileConfigStore, agentconfig_exports as agentconfig, assistant_exports as assistant, authenticatedFetch, buildRetrievalChunkTool, buildRetrievalFullContextTool, buildRetrievalTocTool, connectWebSocket, connectWithReconnect, consumeSSEStream, contacts_exports as contacts, conversations_exports as conversations, createAuthenticatedClient, createDocumentWaiter, dm_exports as dm, doctags_exports as doctags, documents_exports as documents, files_exports as files, formatFileSize, formatUserName, formatWorkspaceChoices, formatWsMessage, generateEncryptedWorkspaceKey, getErrorMessage, health_exports as health, parseSSEEvents, performPasswordLogin, requireData, requireOk, resolveAuth, resolveWorkspace, selectWorkspace, selectWorkspaceById, settings_exports as settings, streamSSE, tags_exports as tags, workspaces_exports as workspaces };
1763
+ // src/operations/documents-node.ts
1764
+ var documents_node_exports = {};
1765
+ __export(documents_node_exports, {
1766
+ uploadDirectory: () => uploadDirectory,
1767
+ uploadLocalFile: () => uploadLocalFile,
1768
+ uploadZip: () => uploadZip
1769
+ });
1770
+ function collectFiles(dirPath, baseDir) {
1771
+ const root = baseDir ?? dirPath;
1772
+ const results = [];
1773
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
1774
+ const fullPath = path2.join(dirPath, entry.name);
1775
+ if (entry.isDirectory()) {
1776
+ results.push(...collectFiles(fullPath, root));
1777
+ } else if (entry.isFile()) {
1778
+ const ext = path2.extname(entry.name).toLowerCase();
1779
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
1780
+ results.push({ absolutePath: fullPath, relativePath: path2.relative(root, fullPath) });
1781
+ }
1782
+ }
1783
+ }
1784
+ return results;
1785
+ }
1786
+ async function uploadLocalFile(auth, workspaceId, filePath, options) {
1787
+ const fileBuffer = fs.readFileSync(filePath);
1788
+ const fileName = path2.basename(filePath);
1789
+ const result = await uploadFile(auth, workspaceId, new Blob([fileBuffer]), fileName, options);
1790
+ return { ...result, fileName };
1791
+ }
1792
+ async function uploadDirectory(auth, workspaceId, dirPath) {
1793
+ const resolvedDir = path2.resolve(dirPath);
1794
+ const dirName = path2.basename(resolvedDir);
1795
+ const entries = collectFiles(resolvedDir);
1796
+ if (entries.length === 0) {
1797
+ return { doc_ext_ids: [], duplicates: [], folders: /* @__PURE__ */ new Map() };
1798
+ }
1799
+ const groups = /* @__PURE__ */ new Map();
1800
+ for (const entry of entries) {
1801
+ const relDir = path2.dirname(entry.relativePath);
1802
+ const folder = relDir === "." ? dirName : `${dirName}/${relDir}`;
1803
+ if (!groups.has(folder)) groups.set(folder, []);
1804
+ groups.get(folder).push({ absolutePath: entry.absolutePath, name: path2.basename(entry.relativePath) });
1805
+ }
1806
+ const allDocIds = [];
1807
+ const allDuplicates = [];
1808
+ const folders = /* @__PURE__ */ new Map();
1809
+ for (const [folder, files] of groups) {
1810
+ const blobs = files.map((f) => ({
1811
+ data: new Blob([fs.readFileSync(f.absolutePath)]),
1812
+ name: f.name
1813
+ }));
1814
+ const result = await uploadFiles(auth, workspaceId, blobs, { folder });
1815
+ const dups = result.duplicates ?? [];
1816
+ allDocIds.push(...result.doc_ext_ids);
1817
+ allDuplicates.push(...dups);
1818
+ folders.set(folder, {
1819
+ fileCount: files.length,
1820
+ doc_ext_ids: result.doc_ext_ids,
1821
+ duplicates: dups
1822
+ });
1823
+ }
1824
+ return { doc_ext_ids: allDocIds, duplicates: allDuplicates, folders };
1825
+ }
1826
+ async function uploadZip(auth, workspaceId, zipPath) {
1827
+ const JSZip = (await import('jszip')).default;
1828
+ const zipBuffer = fs.readFileSync(zipPath);
1829
+ const zip = await JSZip.loadAsync(zipBuffer);
1830
+ const zipBaseName = path2.basename(zipPath).replace(/\.zip$/i, "");
1831
+ const entries = [];
1832
+ zip.forEach((relativePath, zipEntry) => {
1833
+ if (zipEntry.dir) return;
1834
+ const ext = path2.extname(relativePath).toLowerCase();
1835
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
1836
+ entries.push({ zipEntryPath: relativePath, zipEntry });
1837
+ }
1838
+ });
1839
+ if (entries.length === 0) {
1840
+ return { doc_ext_ids: [], duplicates: [], folders: /* @__PURE__ */ new Map() };
1841
+ }
1842
+ const firstParts = new Set(entries.map((e) => e.zipEntryPath.split("/")[0]));
1843
+ const hasSingleRoot = firstParts.size === 1 && entries.every((e) => e.zipEntryPath.includes("/"));
1844
+ const groups = /* @__PURE__ */ new Map();
1845
+ for (const entry of entries) {
1846
+ const data = await entry.zipEntry.async("uint8array");
1847
+ const fileName = path2.basename(entry.zipEntryPath);
1848
+ const relDir = path2.dirname(entry.zipEntryPath);
1849
+ let folder;
1850
+ if (hasSingleRoot) {
1851
+ folder = relDir === "." ? zipBaseName : relDir;
1852
+ } else {
1853
+ folder = relDir === "." ? zipBaseName : `${zipBaseName}/${relDir}`;
1854
+ }
1855
+ folder = sanitizeFolderPath(folder);
1856
+ if (!groups.has(folder)) groups.set(folder, []);
1857
+ groups.get(folder).push({ name: fileName, data });
1858
+ }
1859
+ const allDocIds = [];
1860
+ const allDuplicates = [];
1861
+ const folders = /* @__PURE__ */ new Map();
1862
+ for (const [folder, files] of groups) {
1863
+ const blobs = files.map((f) => ({
1864
+ data: new Blob([f.data]),
1865
+ name: f.name
1866
+ }));
1867
+ const result = await uploadFiles(auth, workspaceId, blobs, { folder });
1868
+ const dups = result.duplicates ?? [];
1869
+ allDocIds.push(...result.doc_ext_ids);
1870
+ allDuplicates.push(...dups);
1871
+ folders.set(folder, {
1872
+ fileCount: files.length,
1873
+ doc_ext_ids: result.doc_ext_ids,
1874
+ duplicates: dups
1875
+ });
1876
+ }
1877
+ return { doc_ext_ids: allDocIds, duplicates: allDuplicates, folders };
1878
+ }
1879
+
1880
+ export { Arbi, ArbiApiError, ArbiError, FileConfigStore, LIFECYCLE_LABELS, TOOL_LABELS, agentconfig_exports as agentconfig, assistant_exports as assistant, authenticatedFetch, buildRetrievalChunkTool, buildRetrievalFullContextTool, buildRetrievalTocTool, connectWebSocket, connectWithReconnect, consumeSSEStream, contacts_exports as contacts, conversations_exports as conversations, createAuthenticatedClient, createDocumentWaiter, dm_exports as dm, doctags_exports as doctags, documents_exports as documents, documents_node_exports as documentsNode, files_exports as files, formatAgentStepLabel, formatFileSize, formatUserName, formatWorkspaceChoices, formatWsMessage, generateEncryptedWorkspaceKey, getErrorCode, getErrorMessage, health_exports as health, parseSSEEvents, performPasswordLogin, requireData, requireOk, resolveAuth, resolveWorkspace, selectWorkspace, selectWorkspaceById, settings_exports as settings, streamSSE, tags_exports as tags, workspaces_exports as workspaces };
1521
1881
  //# sourceMappingURL=index.js.map
1522
1882
  //# sourceMappingURL=index.js.map