@arbidocs/sdk 0.3.16 → 0.3.18

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.cjs CHANGED
@@ -129,11 +129,13 @@ var FileConfigStore = class {
129
129
  configFile;
130
130
  credentialsFile;
131
131
  sessionFile;
132
+ metadataFile;
132
133
  constructor(configDir) {
133
134
  this.configDir = configDir ?? process.env.ARBI_CONFIG_DIR ?? path2__default.default.join(os__default.default.homedir(), ".arbi");
134
135
  this.configFile = path2__default.default.join(this.configDir, "config.json");
135
136
  this.credentialsFile = path2__default.default.join(this.configDir, "credentials.json");
136
137
  this.sessionFile = path2__default.default.join(this.configDir, "session.json");
138
+ this.metadataFile = path2__default.default.join(this.configDir, "last-metadata.json");
137
139
  }
138
140
  ensureConfigDir() {
139
141
  if (!fs__default.default.existsSync(this.configDir)) {
@@ -204,6 +206,13 @@ var FileConfigStore = class {
204
206
  clearChatSession() {
205
207
  this.saveChatSession({ ...DEFAULT_SESSION });
206
208
  }
209
+ // ── Last metadata (for citation browsing) ────────────────────────────────
210
+ saveLastMetadata(metadata) {
211
+ this.writeSecureFile(this.metadataFile, metadata);
212
+ }
213
+ loadLastMetadata() {
214
+ return this.readJsonFile(this.metadataFile);
215
+ }
207
216
  /**
208
217
  * Try to resolve config from multiple sources, in priority order:
209
218
  *
@@ -276,6 +285,107 @@ var FileConfigStore = class {
276
285
  return null;
277
286
  }
278
287
  };
288
+
289
+ // src/device-flow.ts
290
+ var device_flow_exports = {};
291
+ __export(device_flow_exports, {
292
+ DeviceFlowAccessDenied: () => DeviceFlowAccessDenied,
293
+ DeviceFlowError: () => DeviceFlowError,
294
+ DeviceFlowExpired: () => DeviceFlowExpired,
295
+ fetchSsoConfig: () => fetchSsoConfig,
296
+ pollForToken: () => pollForToken,
297
+ requestDeviceCode: () => requestDeviceCode
298
+ });
299
+ var DeviceFlowError = class extends Error {
300
+ constructor(message) {
301
+ super(message);
302
+ this.name = "DeviceFlowError";
303
+ }
304
+ };
305
+ var DeviceFlowExpired = class extends DeviceFlowError {
306
+ constructor() {
307
+ super("Device code expired \u2014 please try again");
308
+ this.name = "DeviceFlowExpired";
309
+ }
310
+ };
311
+ var DeviceFlowAccessDenied = class extends DeviceFlowError {
312
+ constructor() {
313
+ super("Authorization was denied by the user");
314
+ this.name = "DeviceFlowAccessDenied";
315
+ }
316
+ };
317
+ async function fetchSsoConfig(baseUrl) {
318
+ const arbi = client.createArbiClient({ baseUrl, deploymentDomain: "", credentials: "omit" });
319
+ const { data, error } = await arbi.fetch.GET("/v1/user/sso-config");
320
+ if (error || !data) {
321
+ throw new DeviceFlowError(`Failed to fetch SSO config`);
322
+ }
323
+ return {
324
+ ssoEnabled: data.sso_enabled,
325
+ domain: data.domain,
326
+ clientId: data.cli_client_id || data.client_id,
327
+ audience: data.audience
328
+ };
329
+ }
330
+ async function requestDeviceCode(domain, clientId, audience, scope = "openid email profile") {
331
+ const body = new URLSearchParams({
332
+ client_id: clientId,
333
+ scope,
334
+ ...audience ? { audience } : {}
335
+ });
336
+ const res = await fetch(`https://${domain}/oauth/device/code`, {
337
+ method: "POST",
338
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
339
+ body
340
+ });
341
+ if (!res.ok) {
342
+ const text = await res.text();
343
+ throw new DeviceFlowError(`Device code request failed: ${res.status} ${text}`);
344
+ }
345
+ return await res.json();
346
+ }
347
+ async function pollForToken(domain, clientId, deviceCode, interval, expiresIn, onPoll) {
348
+ const deadline = Date.now() + expiresIn * 1e3;
349
+ let pollInterval = interval * 1e3;
350
+ while (Date.now() < deadline) {
351
+ await sleep(pollInterval);
352
+ onPoll?.(Date.now());
353
+ const res = await fetch(`https://${domain}/oauth/token`, {
354
+ method: "POST",
355
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
356
+ body: new URLSearchParams({
357
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
358
+ client_id: clientId,
359
+ device_code: deviceCode
360
+ })
361
+ });
362
+ if (res.ok) {
363
+ const data = await res.json();
364
+ return data.access_token;
365
+ }
366
+ const errBody = await res.json().catch(() => ({ error: "unknown" }));
367
+ if (errBody.error === "authorization_pending") {
368
+ continue;
369
+ } else if (errBody.error === "slow_down") {
370
+ pollInterval += 5e3;
371
+ continue;
372
+ } else if (errBody.error === "expired_token") {
373
+ throw new DeviceFlowExpired();
374
+ } else if (errBody.error === "access_denied") {
375
+ throw new DeviceFlowAccessDenied();
376
+ } else {
377
+ throw new DeviceFlowError(
378
+ `Token polling error: ${errBody.error} \u2014 ${errBody.error_description ?? ""}`
379
+ );
380
+ }
381
+ }
382
+ throw new DeviceFlowExpired();
383
+ }
384
+ function sleep(ms) {
385
+ return new Promise((resolve) => setTimeout(resolve, ms));
386
+ }
387
+
388
+ // src/auth.ts
279
389
  function formatWorkspaceChoices(wsList) {
280
390
  return wsList.map((ws) => {
281
391
  const totalDocs = ws.shared_document_count + ws.private_document_count;
@@ -296,7 +406,8 @@ async function createAuthenticatedClient(config, creds, store) {
296
406
  const signingPrivateKey = client.base64ToBytes(creds.signingPrivateKeyBase64);
297
407
  const loginResult = await arbi.auth.loginWithKey({
298
408
  email: creds.email,
299
- signingPrivateKey
409
+ signingPrivateKey,
410
+ ssoToken: creds.ssoToken
300
411
  });
301
412
  store.saveCredentials({
302
413
  ...creds,
@@ -328,6 +439,40 @@ async function performPasswordLogin(config, email, password, store) {
328
439
  });
329
440
  return { arbi, loginResult, config };
330
441
  }
442
+ async function performSsoDeviceFlowLogin(config, email, password, store, callbacks) {
443
+ const ssoConfig = await fetchSsoConfig(config.baseUrl);
444
+ if (!ssoConfig.ssoEnabled) {
445
+ throw new ArbiError("SSO is not enabled on this deployment");
446
+ }
447
+ const dc = await requestDeviceCode(ssoConfig.domain, ssoConfig.clientId, ssoConfig.audience);
448
+ callbacks?.onUserCode?.(dc.user_code, dc.verification_uri_complete);
449
+ const ssoToken = await pollForToken(
450
+ ssoConfig.domain,
451
+ ssoConfig.clientId,
452
+ dc.device_code,
453
+ dc.interval,
454
+ dc.expires_in,
455
+ callbacks?.onPoll
456
+ );
457
+ const arbi = client.createArbiClient({
458
+ baseUrl: config.baseUrl,
459
+ deploymentDomain: config.deploymentDomain,
460
+ credentials: "omit"
461
+ });
462
+ await arbi.crypto.initSodium();
463
+ const loginResult = await arbi.auth.login({ email, password, ssoToken });
464
+ store.saveCredentials({
465
+ email,
466
+ signingPrivateKeyBase64: arbi.crypto.bytesToBase64(loginResult.signingPrivateKey),
467
+ serverSessionKeyBase64: arbi.crypto.bytesToBase64(loginResult.serverSessionKey),
468
+ ssoToken,
469
+ accessToken: void 0,
470
+ workspaceKeyHeader: void 0,
471
+ workspaceId: void 0,
472
+ tokenTimestamp: void 0
473
+ });
474
+ return { arbi, loginResult, config };
475
+ }
331
476
  async function selectWorkspace(arbi, workspaceId, wrappedKey, serverSessionKey, signingPrivateKeyBase64) {
332
477
  const signingPrivateKey = client.base64ToBytes(signingPrivateKeyBase64);
333
478
  const ed25519PublicKey = signingPrivateKey.slice(32, 64);
@@ -652,6 +797,27 @@ async function streamSSE(response, callbacks = {}) {
652
797
  context
653
798
  };
654
799
  }
800
+ function formatStreamSummary(result, elapsedTime) {
801
+ const parts = [];
802
+ if (result.agentSteps.length > 0) {
803
+ let stepLabel = `${result.agentSteps.length} step${result.agentSteps.length === 1 ? "" : "s"}`;
804
+ if (result.toolCallCount > 0) {
805
+ stepLabel += ` (${result.toolCallCount} tool call${result.toolCallCount === 1 ? "" : "s"})`;
806
+ }
807
+ parts.push(stepLabel);
808
+ }
809
+ if (result.usage) {
810
+ parts.push(`${result.usage.total_tokens.toLocaleString()} tokens`);
811
+ }
812
+ if (result.context && result.context.context_window > 0) {
813
+ const used = result.context.all_llm_calls?.last_input_tokens ?? result.context.total_input;
814
+ parts.push(`${used.toLocaleString()}/${result.context.context_window.toLocaleString()} context`);
815
+ }
816
+ if (elapsedTime != null) {
817
+ parts.push(`${elapsedTime.toFixed(1)}s`);
818
+ }
819
+ return parts.length > 0 ? parts.join(" \xB7 ") : "";
820
+ }
655
821
  var consumeSSEStream = streamSSE;
656
822
  var AUTH_TIMEOUT_MS = 1e4;
657
823
  var MAX_BACKOFF_MS = 3e4;
@@ -876,6 +1042,66 @@ function formatUserName(user) {
876
1042
  return [user.given_name, user.family_name].filter(Boolean).join(" ");
877
1043
  }
878
1044
 
1045
+ // src/citations.ts
1046
+ function resolveCitations(metadata) {
1047
+ if (!metadata?.tools) return [];
1048
+ const tools = metadata.tools;
1049
+ const modelCitations = tools.model_citations;
1050
+ const citationMap = modelCitations?.tool_responses;
1051
+ if (!citationMap || Object.keys(citationMap).length === 0) return [];
1052
+ const chunkLookup = buildChunkLookup(tools);
1053
+ const resolved = [];
1054
+ for (const [citationNum, citationData] of Object.entries(citationMap)) {
1055
+ const chunks = [];
1056
+ for (const chunkId of citationData.chunk_ids ?? []) {
1057
+ const chunk = chunkLookup.get(chunkId);
1058
+ if (chunk) chunks.push(chunk);
1059
+ }
1060
+ resolved.push({ citationNum, citationData, chunks });
1061
+ }
1062
+ resolved.sort((a, b) => Number(a.citationNum) - Number(b.citationNum));
1063
+ return resolved;
1064
+ }
1065
+ function summarizeCitations(resolved) {
1066
+ return resolved.map((r) => {
1067
+ const firstChunk = r.chunks[0];
1068
+ return {
1069
+ citationNum: r.citationNum,
1070
+ statement: r.citationData.statement ?? "",
1071
+ docTitle: firstChunk?.metadata?.doc_title ?? "Unknown document",
1072
+ pageNumber: firstChunk?.metadata?.page_number ?? null,
1073
+ chunkCount: r.chunks.length
1074
+ };
1075
+ });
1076
+ }
1077
+ function countCitations(metadata) {
1078
+ if (!metadata?.tools) return 0;
1079
+ const tools = metadata.tools;
1080
+ const modelCitations = tools.model_citations;
1081
+ const responses = modelCitations?.tool_responses;
1082
+ return responses ? Object.keys(responses).length : 0;
1083
+ }
1084
+ function stripCitationMarkdown(text) {
1085
+ return text.replace(/\[([^\]]+)\]\(#cite-(\d+)\)/g, "$1[$2]");
1086
+ }
1087
+ function buildChunkLookup(tools) {
1088
+ const lookup = /* @__PURE__ */ new Map();
1089
+ for (const toolName of ["retrieval_chunk", "retrieval_full_context"]) {
1090
+ const tool = tools[toolName];
1091
+ if (!tool?.tool_responses) continue;
1092
+ for (const chunks of Object.values(tool.tool_responses)) {
1093
+ if (!Array.isArray(chunks)) continue;
1094
+ for (const chunk of chunks) {
1095
+ const id = chunk.metadata?.chunk_ext_id;
1096
+ if (id && !lookup.has(id)) {
1097
+ lookup.set(id, chunk);
1098
+ }
1099
+ }
1100
+ }
1101
+ }
1102
+ return lookup;
1103
+ }
1104
+
879
1105
  // src/operations/documents.ts
880
1106
  var documents_exports = {};
881
1107
  __export(documents_exports, {
@@ -1573,6 +1799,19 @@ var Arbi = class {
1573
1799
  lr.serverSessionKey,
1574
1800
  signingPrivateKeyBase64
1575
1801
  );
1802
+ const workspaceKeyHeader = client.session.getWorkspaceKeyHeader();
1803
+ if (workspaceKeyHeader) {
1804
+ const { data: openResult, error: openError } = await client.fetch.POST(
1805
+ "/v1/workspace/{workspace_ext_id}/open",
1806
+ {
1807
+ params: { path: { workspace_ext_id: ws.external_id } },
1808
+ body: { workspace_key: workspaceKeyHeader }
1809
+ }
1810
+ );
1811
+ if (!openError && openResult?.access_token) {
1812
+ client.session.setAccessToken(openResult.access_token);
1813
+ }
1814
+ }
1576
1815
  this.currentWorkspaceId = ws.external_id;
1577
1816
  }
1578
1817
  /** Log out and clear internal state. */
@@ -1991,8 +2230,10 @@ exports.connectWithReconnect = connectWithReconnect;
1991
2230
  exports.consumeSSEStream = consumeSSEStream;
1992
2231
  exports.contacts = contacts_exports;
1993
2232
  exports.conversations = conversations_exports;
2233
+ exports.countCitations = countCitations;
1994
2234
  exports.createAuthenticatedClient = createAuthenticatedClient;
1995
2235
  exports.createDocumentWaiter = createDocumentWaiter;
2236
+ exports.deviceFlow = device_flow_exports;
1996
2237
  exports.dm = dm_exports;
1997
2238
  exports.doctags = doctags_exports;
1998
2239
  exports.documents = documents_exports;
@@ -2000,6 +2241,7 @@ exports.documentsNode = documents_node_exports;
2000
2241
  exports.files = files_exports;
2001
2242
  exports.formatAgentStepLabel = formatAgentStepLabel;
2002
2243
  exports.formatFileSize = formatFileSize;
2244
+ exports.formatStreamSummary = formatStreamSummary;
2003
2245
  exports.formatUserName = formatUserName;
2004
2246
  exports.formatWorkspaceChoices = formatWorkspaceChoices;
2005
2247
  exports.formatWsMessage = formatWsMessage;
@@ -2009,15 +2251,19 @@ exports.getErrorMessage = getErrorMessage;
2009
2251
  exports.health = health_exports;
2010
2252
  exports.parseSSEEvents = parseSSEEvents;
2011
2253
  exports.performPasswordLogin = performPasswordLogin;
2254
+ exports.performSsoDeviceFlowLogin = performSsoDeviceFlowLogin;
2012
2255
  exports.requireData = requireData;
2013
2256
  exports.requireOk = requireOk;
2014
2257
  exports.resolveAuth = resolveAuth;
2258
+ exports.resolveCitations = resolveCitations;
2015
2259
  exports.resolveWorkspace = resolveWorkspace;
2016
2260
  exports.responses = responses_exports;
2017
2261
  exports.selectWorkspace = selectWorkspace;
2018
2262
  exports.selectWorkspaceById = selectWorkspaceById;
2019
2263
  exports.settings = settings_exports;
2020
2264
  exports.streamSSE = streamSSE;
2265
+ exports.stripCitationMarkdown = stripCitationMarkdown;
2266
+ exports.summarizeCitations = summarizeCitations;
2021
2267
  exports.tags = tags_exports;
2022
2268
  exports.workspaces = workspaces_exports;
2023
2269
  //# sourceMappingURL=index.cjs.map