@trustgraph/client 1.7.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -10,7 +10,8 @@ const TG_QUERY = TG + "query";
10
10
  const TG_EDGE_COUNT = TG + "edgeCount";
11
11
  const TG_SELECTED_EDGE = TG + "selectedEdge";
12
12
  const TG_EDGE = TG + "edge";
13
- const TG_REASONING = TG + "reasoning";
13
+ const TG_CONCEPT = TG + "concept";
14
+ const TG_SCORE = TG + "score";
14
15
  const TG_CONTENT = TG + "content";
15
16
  const TG_REIFIES = TG + "reifies";
16
17
  const TG_DOCUMENT = TG + "document";
@@ -76,18 +77,17 @@ class ServiceCallMulti {
76
77
  }
77
78
  }
78
79
  /**
79
- * Called when socket connects - immediately retry if we were waiting
80
+ * Called when socket reconnects. Streaming requests cannot be resumed
81
+ * after a disconnect — the backend state is gone — so fail immediately.
80
82
  */
81
83
  retryNow() {
82
84
  if (this.complete)
83
85
  return;
84
- // Clear any pending backoff timer
85
86
  clearTimeout(this.timeoutId);
86
87
  this.timeoutId = undefined;
87
- // Restore retry count since we didn't actually fail
88
- this.retries++;
89
- // Attempt immediately
90
- this.attempt();
88
+ this.complete = true;
89
+ delete this.socket.inflight[this.mid];
90
+ this.error("Connection lost during streaming request");
91
91
  }
92
92
  onTimeout() {
93
93
  if (this.complete == true)
@@ -108,8 +108,11 @@ class ServiceCallMulti {
108
108
  this.error("Ran out of retries");
109
109
  return; // Exit early - no more attempts
110
110
  }
111
- // Check if WebSocket connection is available and ready
112
- if (this.socket.ws && this.socket.ws.readyState === WebSocket.OPEN) {
111
+ // Check if WebSocket connection is available, open, and authenticated.
112
+ const authReady = this.socket.isAuthReady?.() ?? true;
113
+ if (this.socket.ws &&
114
+ this.socket.ws.readyState === WebSocket.OPEN &&
115
+ authReady) {
113
116
  try {
114
117
  this.socket.ws.send(JSON.stringify(this.msg));
115
118
  this.timeoutId = setTimeout(this.onTimeout.bind(this), this.timeout);
@@ -291,8 +294,14 @@ class ServiceCall {
291
294
  this.error("Ran out of retries");
292
295
  return; // Exit early - no more attempts
293
296
  }
294
- // Check if WebSocket connection is available and ready
295
- if (this.socket.ws && this.socket.ws.readyState === WebSocket.OPEN) {
297
+ // Check if WebSocket connection is available, open, and authenticated.
298
+ // The IAM gateway rejects all non-auth frames until the first-frame
299
+ // auth handshake has completed, so sends gated only on readyState
300
+ // would race the auth response.
301
+ const authReady = this.socket.isAuthReady?.() ?? true;
302
+ if (this.socket.ws &&
303
+ this.socket.ws.readyState === WebSocket.OPEN &&
304
+ authReady) {
296
305
  try {
297
306
  // Attempt to send the message as JSON
298
307
  this.socket.ws.send(JSON.stringify(this.msg));
@@ -321,12 +330,23 @@ class ServiceCall {
321
330
  // Configuration constants
322
331
  const SOCKET_RECONNECTION_TIMEOUT = 2000; // 2 seconds between reconnection
323
332
  // attempts
324
- const SOCKET_URL = "/api/socket"; // WebSocket endpoint path
333
+ const SOCKET_URL = "/api/v1/socket"; // WebSocket endpoint path
325
334
  /**
326
335
  * Generates a random message ID using cryptographically secure random values
327
336
  * @param length - Number of random characters to generate
328
337
  * @returns Random string of specified length
329
338
  */
339
+ // Normalise the top-level `error` field from a WebSocket response frame.
340
+ // The gateway may send it as a plain string *or* as an object with a
341
+ // `message` field; callers always want a string.
342
+ function errorToString(err) {
343
+ if (typeof err === "string")
344
+ return err;
345
+ if (err && typeof err === "object" && "message" in err) {
346
+ return String(err.message);
347
+ }
348
+ return String(err);
349
+ }
330
350
  function makeid(length) {
331
351
  const array = new Uint32Array(length);
332
352
  crypto.getRandomValues(array);
@@ -334,23 +354,46 @@ function makeid(length) {
334
354
  return array.reduce((acc, current) => acc + characters[current % characters.length], "");
335
355
  }
336
356
  class BaseApi {
337
- constructor(user, token, socketUrl) {
357
+ constructor(token, socketUrl) {
358
+ // Legacy honour-system field. The gateway now derives identity from the
359
+ // authenticated token; this is retained as an empty string so the
360
+ // existing request-builder code paths that read `api.user` keep
361
+ // compiling. It will be removed when the request payloads are cleaned
362
+ // up.
363
+ this.user = "";
364
+ // Active workspace for outbound requests. Sent as the envelope
365
+ // `workspace` field (sibling of `flow`); the gateway reconciles it
366
+ // against the authenticated identity and, when empty, defaults to
367
+ // the token's bound workspace. Kept in sync with the active
368
+ // workspace by the state layer.
369
+ this.workspace = "";
338
370
  this.inflight = {}; // Track active requests by
339
371
  // message ID
340
372
  this.reconnectAttempts = 0; // Track reconnection attempts
341
373
  this.maxReconnectAttempts = 10; // Maximum reconnection attempts
342
374
  this.reconnectionState = "idle"; // Connection state
375
+ // Auth-handshake state for the current socket. Reset on each open.
376
+ this.authState = "pending";
343
377
  // Connection state tracking for UI
344
378
  this.connectionStateListeners = [];
345
379
  this.tag = makeid(16); // Generate unique client tag
346
380
  this.id = 1; // Start message ID counter
347
- this.token = token; // Store authentication token
348
- this.user = user; // Store user identifier
381
+ this.token = token;
349
382
  this.socketUrl = socketUrl || SOCKET_URL; // Use provided URL or default
350
- console.log("SOCKET: opening socket...", token ? "with auth" : "without auth", "user:", user);
383
+ console.log("SOCKET: opening socket...");
351
384
  this.openSocket(); // Establish WebSocket connection
352
385
  console.log("SOCKET: socket opened");
353
386
  }
387
+ /**
388
+ * True once the WebSocket is OPEN and the first-frame auth handshake
389
+ * has been accepted by the gateway. ServiceCall consults this before
390
+ * sending request frames.
391
+ */
392
+ isAuthReady() {
393
+ return (!!this.ws &&
394
+ this.ws.readyState === WebSocket.OPEN &&
395
+ this.authState === "ok");
396
+ }
354
397
  /**
355
398
  * Subscribe to connection state changes for UI updates
356
399
  */
@@ -371,7 +414,8 @@ class BaseApi {
371
414
  */
372
415
  getConnectionState() {
373
416
  const hasApiKey = !!this.token;
374
- // Determine status based on WebSocket state and reconnection state
417
+ // Determine status based on WebSocket state, auth handshake state, and
418
+ // reconnection state.
375
419
  let status;
376
420
  if (!this.ws || this.ws.readyState === WebSocket.CLOSED) {
377
421
  if (this.reconnectionState === "failed") {
@@ -388,7 +432,12 @@ class BaseApi {
388
432
  status = "connecting";
389
433
  }
390
434
  else if (this.ws.readyState === WebSocket.OPEN) {
391
- status = hasApiKey ? "authenticated" : "unauthenticated";
435
+ if (this.authState === "ok")
436
+ status = "authenticated";
437
+ else if (this.authState === "failed")
438
+ status = "auth-failed";
439
+ else
440
+ status = "authenticating";
392
441
  }
393
442
  else {
394
443
  status = "connecting";
@@ -437,13 +486,12 @@ class BaseApi {
437
486
  this.ws.removeEventListener("error", this.onError);
438
487
  this.ws = undefined;
439
488
  }
489
+ // Reset the auth handshake state for the new connection. The token
490
+ // is sent as the first frame after onOpen, never on the URL.
491
+ this.authState = "pending";
440
492
  try {
441
- // Build WebSocket URL with optional token parameter
442
- const wsUrl = this.token
443
- ? `${this.socketUrl}?token=${this.token}`
444
- : this.socketUrl;
445
- console.log("SOCKET: connecting to", wsUrl.replace(/token=[^&]*/, "token=***"));
446
- this.ws = new WebSocket(wsUrl);
493
+ console.log("SOCKET: connecting to", this.socketUrl);
494
+ this.ws = new WebSocket(this.socketUrl);
447
495
  }
448
496
  catch (e) {
449
497
  console.error("[socket creation error]", e);
@@ -467,6 +515,34 @@ class BaseApi {
467
515
  return;
468
516
  try {
469
517
  const obj = JSON.parse(message.data);
518
+ // Auth handshake frames are addressed by `type`, not by request id.
519
+ if (obj.type === "auth-ok") {
520
+ console.log("[socket] auth-ok", obj.default_workspace ?? "");
521
+ this.authState = "ok";
522
+ this.lastError = undefined;
523
+ this.notifyStateChange();
524
+ // Auth complete — release any requests that were waiting for the
525
+ // socket to become usable.
526
+ for (const mid in this.inflight) {
527
+ this.inflight[mid].retryNow();
528
+ }
529
+ return;
530
+ }
531
+ if (obj.type === "auth-failed") {
532
+ console.warn("[socket] auth-failed", obj.error);
533
+ this.authState = "failed";
534
+ this.lastError = obj.error || "auth failure";
535
+ this.notifyStateChange();
536
+ // Per the IAM spec the server keeps the socket open so the client
537
+ // can re-authenticate without reconnecting. Don't auto-close.
538
+ // Surface the failure to any inflight requests so they don't sit
539
+ // forever — callers should clear the token and reauth.
540
+ for (const mid in this.inflight) {
541
+ this.inflight[mid].error(new Error("auth failure"));
542
+ }
543
+ this.inflight = {};
544
+ return;
545
+ }
470
546
  // Skip messages without ID (can't route them)
471
547
  if (!obj.id)
472
548
  return;
@@ -499,12 +575,19 @@ class BaseApi {
499
575
  clearTimeout(this.reconnectTimer);
500
576
  this.reconnectTimer = undefined;
501
577
  }
502
- // Notify UI of successful connection
503
- this.notifyStateChange();
504
- // Immediately retry any pending requests that were waiting for connection
505
- for (const mid in this.inflight) {
506
- this.inflight[mid].retryNow();
578
+ // Send the auth frame as the first message. The server rejects all
579
+ // non-auth messages until it sends back auth-ok, so we hold off
580
+ // releasing inflight requests here that happens in onMessage when
581
+ // auth-ok arrives.
582
+ this.authState = "pending";
583
+ try {
584
+ this.ws?.send(JSON.stringify({ type: "auth", token: this.token }));
507
585
  }
586
+ catch (e) {
587
+ console.error("[socket] failed to send auth frame", e);
588
+ }
589
+ // Notify UI that we're now in the authenticating phase.
590
+ this.notifyStateChange();
508
591
  }
509
592
  // Handle socket errors
510
593
  onError(event) {
@@ -607,7 +690,7 @@ class BaseApi {
607
690
  const mid = this.getNextId();
608
691
  // Set default values
609
692
  if (timeout == undefined)
610
- timeout = 10000;
693
+ timeout = 30000;
611
694
  if (retries == undefined)
612
695
  retries = 3;
613
696
  // Construct the request message
@@ -619,6 +702,10 @@ class BaseApi {
619
702
  // Add flow identifier if provided
620
703
  if (flow)
621
704
  msg.flow = flow;
705
+ // Stamp the active workspace onto the envelope. When empty the
706
+ // gateway falls back to the token's bound workspace.
707
+ if (this.workspace)
708
+ msg.workspace = this.workspace;
622
709
  // Return a Promise that will be resolved/rejected by the ServiceCall
623
710
  return new Promise((resolve, reject) => {
624
711
  const call = new ServiceCall(mid, msg, resolve, reject, timeout, retries, this);
@@ -638,7 +725,7 @@ class BaseApi {
638
725
  const mid = this.getNextId();
639
726
  // Set defaults
640
727
  if (timeout == undefined)
641
- timeout = 10000;
728
+ timeout = 30000;
642
729
  if (retries == undefined)
643
730
  retries = 3;
644
731
  // Construct request message
@@ -649,6 +736,8 @@ class BaseApi {
649
736
  };
650
737
  if (flow)
651
738
  msg.flow = flow;
739
+ if (this.workspace)
740
+ msg.workspace = this.workspace;
652
741
  return new Promise((resolve, reject) => {
653
742
  const call = new ServiceCallMulti(mid, msg, resolve, reject, timeout, retries, this, // eslint-disable-line @typescript-eslint/no-explicit-any
654
743
  receiver);
@@ -685,6 +774,166 @@ class BaseApi {
685
774
  collectionManagement() {
686
775
  return new CollectionManagementApi(this);
687
776
  }
777
+ iam() {
778
+ return new IamApi(this);
779
+ }
780
+ }
781
+ /**
782
+ * IamApi - Identity, user, workspace, and API key management over the
783
+ * authenticated socket. IAM is the one surface that lives outside
784
+ * workspace tenancy; these calls run as the `iam` service and the
785
+ * gateway injects the caller's identity from the connection's token.
786
+ */
787
+ class IamApi {
788
+ constructor(api) {
789
+ this.api = api;
790
+ }
791
+ call(req) {
792
+ return this.api.makeRequest("iam", req);
793
+ }
794
+ // --- Identity ---
795
+ whoami() {
796
+ return this.call({
797
+ operation: "whoami",
798
+ }).then((r) => {
799
+ const user = r.user ?? {};
800
+ return {
801
+ id: String(user.id ?? ""),
802
+ username: String(user.username ?? ""),
803
+ name: String(user.name ?? ""),
804
+ email: String(user.email ?? ""),
805
+ default_workspace: String(user.default_workspace ?? ""),
806
+ roles: Array.isArray(user.roles) ? user.roles : [],
807
+ enabled: !!user.enabled,
808
+ };
809
+ });
810
+ }
811
+ // --- Users ---
812
+ listUsers(workspace) {
813
+ return this.call({
814
+ operation: "list-users",
815
+ ...(workspace ? { workspace } : {}),
816
+ }).then((r) => r.users ?? []);
817
+ }
818
+ getUser(userId) {
819
+ return this.call({
820
+ operation: "get-user",
821
+ user_id: userId,
822
+ }).then((r) => r.user);
823
+ }
824
+ createUser(params) {
825
+ return this.call({
826
+ operation: "create-user",
827
+ user: {
828
+ username: params.username,
829
+ password: params.password,
830
+ name: params.name ?? "",
831
+ email: params.email ?? "",
832
+ enabled: true,
833
+ must_change_password: params.must_change_password ?? false,
834
+ roles: [],
835
+ },
836
+ ...(params.workspace ? { workspace: params.workspace } : {}),
837
+ }).then((r) => r.user);
838
+ }
839
+ updateUser(userId, fields) {
840
+ return this.call({
841
+ operation: "update-user",
842
+ user_id: userId,
843
+ user: fields,
844
+ }).then((r) => r.user);
845
+ }
846
+ enableUser(userId) {
847
+ return this.call({
848
+ operation: "enable-user",
849
+ user_id: userId,
850
+ }).then(() => { });
851
+ }
852
+ disableUser(userId) {
853
+ return this.call({
854
+ operation: "disable-user",
855
+ user_id: userId,
856
+ }).then(() => { });
857
+ }
858
+ deleteUser(userId) {
859
+ return this.call({
860
+ operation: "delete-user",
861
+ user_id: userId,
862
+ }).then(() => { });
863
+ }
864
+ resetPassword(userId) {
865
+ return this.call({
866
+ operation: "reset-password",
867
+ user_id: userId,
868
+ }).then((r) => r.temporary_password);
869
+ }
870
+ changePassword(userId, currentPassword, newPassword) {
871
+ return this.call({
872
+ operation: "change-password",
873
+ user_id: userId,
874
+ password: currentPassword,
875
+ new_password: newPassword,
876
+ }).then(() => { });
877
+ }
878
+ // --- Workspaces ---
879
+ listMyWorkspaces() {
880
+ return this.call({
881
+ operation: "list-my-workspaces",
882
+ }).then((r) => (r.workspaces ?? []).map((w) => ({
883
+ id: String(w.id ?? ""),
884
+ name: String(w.name ?? ""),
885
+ enabled: !!w.enabled,
886
+ created: String(w.created ?? ""),
887
+ })));
888
+ }
889
+ listWorkspaces() {
890
+ return this.call({
891
+ operation: "list-workspaces",
892
+ }).then((r) => r.workspaces ?? []);
893
+ }
894
+ getWorkspace(id) {
895
+ return this.call({
896
+ operation: "get-workspace",
897
+ workspace_record: { id },
898
+ }).then((r) => r.workspace);
899
+ }
900
+ createWorkspace(id, name) {
901
+ return this.call({
902
+ operation: "create-workspace",
903
+ workspace_record: { id, name: name ?? id, enabled: true },
904
+ }).then((r) => r.workspace);
905
+ }
906
+ updateWorkspace(id, fields) {
907
+ return this.call({
908
+ operation: "update-workspace",
909
+ workspace_record: { id, ...fields },
910
+ }).then((r) => r.workspace);
911
+ }
912
+ disableWorkspace(id) {
913
+ return this.call({
914
+ operation: "disable-workspace",
915
+ workspace_record: { id },
916
+ }).then(() => { });
917
+ }
918
+ // --- API Keys ---
919
+ listApiKeys(userId) {
920
+ return this.call({
921
+ operation: "list-api-keys",
922
+ user_id: userId,
923
+ }).then((r) => r.api_keys ?? []);
924
+ }
925
+ createApiKey(userId, name, expires) {
926
+ return this.call({
927
+ operation: "create-api-key",
928
+ key: { user_id: userId, name, expires: expires ?? "" },
929
+ }).then((r) => ({ plaintext: r.api_key_plaintext, key: r.api_key }));
930
+ }
931
+ revokeApiKey(keyId) {
932
+ return this.call({
933
+ operation: "revoke-api-key",
934
+ key_id: keyId,
935
+ }).then(() => { });
936
+ }
688
937
  }
689
938
  /**
690
939
  * LibrarianApi - Manages document storage and retrieval
@@ -923,7 +1172,7 @@ class LibrarianApi {
923
1172
  const msg = message;
924
1173
  // Check for top-level error
925
1174
  if (msg.error) {
926
- onError(msg.error);
1175
+ onError(errorToString(msg.error));
927
1176
  return true;
928
1177
  }
929
1178
  const resp = msg.response;
@@ -1009,17 +1258,30 @@ class FlowsApi {
1009
1258
  deleteConfig(keys) {
1010
1259
  return this.api.makeRequest("config", {
1011
1260
  operation: "delete",
1012
- keys: keys,
1261
+ keys: [keys],
1013
1262
  }, 30000);
1014
1263
  }
1264
+ /**
1265
+ * Lists available configuration keys for a given type
1266
+ */
1267
+ list(type) {
1268
+ return this.api
1269
+ .makeRequest("config", {
1270
+ operation: "list",
1271
+ type: type,
1272
+ }, 60000)
1273
+ .then((r) => r);
1274
+ }
1015
1275
  // Prompt management - specialized config operations for AI prompts
1016
1276
  /**
1017
- * Retrieves list of available prompt templates
1277
+ * Retrieves list of available prompt template IDs
1018
1278
  */
1019
1279
  getPrompts() {
1020
- return this.getConfigAll().then((r) => {
1021
- const config = r;
1022
- return JSON.parse(config.config.prompt["template-index"]);
1280
+ return this.list("prompt").then((r) => {
1281
+ const keys = r?.directory || [];
1282
+ return keys
1283
+ .filter((k) => k.startsWith("template."))
1284
+ .map((k) => k.slice("template.".length));
1023
1285
  });
1024
1286
  }
1025
1287
  /**
@@ -1176,17 +1438,19 @@ class FlowApi {
1176
1438
  const msg = message;
1177
1439
  // Check for top-level error
1178
1440
  if (msg.error) {
1179
- error(msg.error);
1441
+ error(errorToString(msg.error));
1180
1442
  return true;
1181
1443
  }
1182
1444
  const resp = msg.response || {};
1445
+ // Prefer message_type, fall back to chunk_type for older backends
1446
+ const msgType = resp.message_type || resp.chunk_type;
1183
1447
  // Check for errors in response
1184
- if (resp.chunk_type === "error" || resp.error) {
1448
+ if (msgType === "error" || resp.error) {
1185
1449
  error(resp.error?.message || "Unknown agent error");
1186
1450
  return true; // End streaming on error
1187
1451
  }
1188
- // Handle explainability events (agent uses chunk_type="explain")
1189
- if ((resp.chunk_type === "explain" || resp.message_type === "explain") && resp.explain_id && resp.explain_graph) {
1452
+ // Handle explainability events
1453
+ if (msgType === "explain" && resp.explain_id && resp.explain_graph) {
1190
1454
  onExplain?.({
1191
1455
  explainId: resp.explain_id,
1192
1456
  explainGraph: resp.explain_graph,
@@ -1194,7 +1458,7 @@ class FlowApi {
1194
1458
  });
1195
1459
  return false;
1196
1460
  }
1197
- // Handle streaming chunks by chunk_type
1461
+ // Handle streaming chunks by message type
1198
1462
  const content = resp.content || "";
1199
1463
  const messageId = resp.message_id;
1200
1464
  const messageComplete = !!resp.end_of_message;
@@ -1203,7 +1467,7 @@ class FlowApi {
1203
1467
  const metadata = dialogComplete && (resp.in_token || resp.out_token || resp.model)
1204
1468
  ? { in_token: resp.in_token, out_token: resp.out_token, model: resp.model }
1205
1469
  : undefined;
1206
- switch (resp.chunk_type) {
1470
+ switch (msgType) {
1207
1471
  case "thought":
1208
1472
  think(content, messageComplete, messageId, metadata);
1209
1473
  break;
@@ -1227,7 +1491,7 @@ class FlowApi {
1227
1491
  user: this.api.user,
1228
1492
  collection: collection || "default",
1229
1493
  streaming: true, // Always use streaming mode
1230
- }, receiver, 120000, 2, this.flowId)
1494
+ }, receiver, 180000, 1, this.flowId)
1231
1495
  .catch((err) => {
1232
1496
  const errorMessage = err instanceof Error ? err.message : err?.toString() || "Unknown error";
1233
1497
  error(`Agent request failed: ${errorMessage}`);
@@ -1247,7 +1511,7 @@ class FlowApi {
1247
1511
  const msg = message;
1248
1512
  // Check for top-level error
1249
1513
  if (msg.error) {
1250
- onError(msg.error);
1514
+ onError(errorToString(msg.error));
1251
1515
  return true;
1252
1516
  }
1253
1517
  const resp = (msg.response || {});
@@ -1285,7 +1549,7 @@ class FlowApi {
1285
1549
  "max-subgraph-size": options?.maxSubgraphSize,
1286
1550
  "max-path-length": options?.pathLength,
1287
1551
  streaming: true,
1288
- }, recv, 60000, undefined, this.flowId);
1552
+ }, recv, 180000, 1, this.flowId);
1289
1553
  }
1290
1554
  /**
1291
1555
  * Performs Document RAG query with streaming response
@@ -1300,7 +1564,7 @@ class FlowApi {
1300
1564
  const msg = message;
1301
1565
  // Check for top-level error
1302
1566
  if (msg.error) {
1303
- onError(msg.error);
1567
+ onError(errorToString(msg.error));
1304
1568
  return true;
1305
1569
  }
1306
1570
  const resp = (msg.response || {});
@@ -1333,7 +1597,7 @@ class FlowApi {
1333
1597
  collection: collection || "default",
1334
1598
  "doc-limit": docLimit,
1335
1599
  streaming: true,
1336
- }, recv, 60000, undefined, this.flowId);
1600
+ }, recv, 180000, 1, this.flowId);
1337
1601
  }
1338
1602
  /**
1339
1603
  * Performs text completion with streaming response
@@ -1347,7 +1611,7 @@ class FlowApi {
1347
1611
  const msg = message;
1348
1612
  // Check for top-level error
1349
1613
  if (msg.error) {
1350
- onError(msg.error);
1614
+ onError(errorToString(msg.error));
1351
1615
  return true;
1352
1616
  }
1353
1617
  const resp = (msg.response || {});
@@ -1384,7 +1648,7 @@ class FlowApi {
1384
1648
  const msg = message;
1385
1649
  // Check for top-level error
1386
1650
  if (msg.error) {
1387
- onError(msg.error);
1651
+ onError(errorToString(msg.error));
1388
1652
  return true;
1389
1653
  }
1390
1654
  const resp = (msg.response || {});
@@ -1405,7 +1669,7 @@ class FlowApi {
1405
1669
  };
1406
1670
  this.api.makeRequestMulti("prompt", {
1407
1671
  id: id,
1408
- terms: terms,
1672
+ variables: terms,
1409
1673
  streaming: true,
1410
1674
  }, recv, 30000, undefined, this.flowId);
1411
1675
  }
@@ -1450,6 +1714,75 @@ class FlowApi {
1450
1714
  }, 30000, undefined, this.flowId)
1451
1715
  .then((r) => r.response);
1452
1716
  }
1717
+ sparqlQuery(query, collection, limit, batchSize) {
1718
+ const columns = [];
1719
+ const rows = [];
1720
+ let queryType = "select";
1721
+ let askResult;
1722
+ let triples;
1723
+ let sparqlError = null;
1724
+ const termToString = (val) => {
1725
+ if (!val)
1726
+ return "";
1727
+ if (val.t === "i")
1728
+ return val.i;
1729
+ if (val.t === "l")
1730
+ return val.v;
1731
+ return "";
1732
+ };
1733
+ return this.api
1734
+ .makeRequestMulti("sparql", {
1735
+ query,
1736
+ collection: collection || "default",
1737
+ limit: limit ?? 10000,
1738
+ streaming: true,
1739
+ "batch-size": batchSize ?? 50,
1740
+ }, (resp) => {
1741
+ const msg = resp;
1742
+ const batch = msg.response;
1743
+ const isComplete = msg.complete === true;
1744
+ if (!batch)
1745
+ return isComplete;
1746
+ if (batch.error) {
1747
+ sparqlError = typeof batch.error === "string"
1748
+ ? batch.error
1749
+ : batch.error.message || "SPARQL query error";
1750
+ return true;
1751
+ }
1752
+ queryType = batch["query-type"] || queryType;
1753
+ if (queryType === "ask") {
1754
+ askResult = batch["ask-result"];
1755
+ return true;
1756
+ }
1757
+ if (queryType === "construct" || queryType === "describe") {
1758
+ triples = batch.triples || [];
1759
+ return true;
1760
+ }
1761
+ if (batch.variables && columns.length === 0) {
1762
+ columns.push(...batch.variables);
1763
+ }
1764
+ if (batch.bindings) {
1765
+ for (const binding of batch.bindings) {
1766
+ const row = {};
1767
+ for (let i = 0; i < columns.length; i++) {
1768
+ row[columns[i]] = termToString(binding.values[i] ?? null);
1769
+ }
1770
+ rows.push(row);
1771
+ }
1772
+ }
1773
+ return isComplete;
1774
+ }, 60000, undefined, this.flowId)
1775
+ .then(() => {
1776
+ if (sparqlError)
1777
+ throw new Error(sparqlError);
1778
+ return { queryType, columns, rows, askResult, triples };
1779
+ })
1780
+ .catch((err) => {
1781
+ if (err instanceof Error)
1782
+ throw err;
1783
+ throw new Error(typeof err === "string" ? err : JSON.stringify(err));
1784
+ });
1785
+ }
1453
1786
  /**
1454
1787
  * Loads a document into this flow for processing
1455
1788
  */
@@ -1598,17 +1931,19 @@ class ConfigApi {
1598
1931
  deleteConfig(keys) {
1599
1932
  return this.api.makeRequest("config", {
1600
1933
  operation: "delete",
1601
- keys: keys,
1934
+ keys: [keys],
1602
1935
  }, 30000);
1603
1936
  }
1604
1937
  // Specialized prompt management methods
1605
1938
  /**
1606
- * Retrieves available prompt templates
1939
+ * Retrieves available prompt template IDs
1607
1940
  */
1608
1941
  getPrompts() {
1609
- return this.getConfigAll().then((r) => {
1610
- const config = r;
1611
- return JSON.parse(config.config.prompt["template-index"]);
1942
+ return this.list("prompt").then((r) => {
1943
+ const keys = r?.directory || [];
1944
+ return keys
1945
+ .filter((k) => k.startsWith("template."))
1946
+ .map((k) => k.slice("template.".length));
1612
1947
  });
1613
1948
  }
1614
1949
  /**
@@ -1826,21 +2161,82 @@ class CollectionManagementApi {
1826
2161
  }
1827
2162
  }
1828
2163
  /**
1829
- * Factory function to create a new TrustGraph WebSocket connection
1830
- * This is the main entry point for using the TrustGraph API
1831
- * @param user - User identifier for API requests
1832
- * @param token - Optional authentication token for secure connections
1833
- * @param socketUrl - Optional WebSocket URL (defaults to /api/socket for browser, provide full URL for Node.js)
2164
+ * Factory function to create a new TrustGraph WebSocket connection.
2165
+ * The token (JWT or API key) is sent as the first frame after connect;
2166
+ * the gateway derives the user identity and workspace from it.
2167
+ *
2168
+ * @param token - Bearer token (JWT from /auth/login or an API key)
2169
+ * @param socketUrl - Optional WebSocket URL (defaults to /api/v1/socket
2170
+ * for browser, provide full URL for Node.js)
1834
2171
  */
1835
- const createTrustGraphSocket = (user, token, socketUrl) => {
1836
- return new BaseApi(user, token, socketUrl);
2172
+ const createTrustGraphSocket = (token, socketUrl) => {
2173
+ return new BaseApi(token, socketUrl);
1837
2174
  };
1838
2175
 
2176
+ // HTTP auth client for the TrustGraph IAM gateway endpoints.
2177
+ //
2178
+ // These endpoints run before the WebSocket exists, so they use plain
2179
+ // fetch rather than the socket transport.
2180
+ const DEFAULT_BOOTSTRAP_STATUS_URL = "/api/v1/auth/bootstrap-status";
2181
+ const DEFAULT_LOGIN_URL = "/api/v1/auth/login";
2182
+ class AuthError extends Error {
2183
+ constructor(message, status) {
2184
+ super(message);
2185
+ this.name = "AuthError";
2186
+ this.status = status;
2187
+ }
2188
+ }
2189
+ class AuthApi {
2190
+ constructor(options = {}) {
2191
+ this.bootstrapStatusUrl =
2192
+ options.bootstrapStatusUrl ?? DEFAULT_BOOTSTRAP_STATUS_URL;
2193
+ this.loginUrl = options.loginUrl ?? DEFAULT_LOGIN_URL;
2194
+ this.fetchImpl = options.fetchImpl ?? fetch.bind(globalThis);
2195
+ }
2196
+ async bootstrapStatus() {
2197
+ const resp = await this.fetchImpl(this.bootstrapStatusUrl, {
2198
+ method: "POST",
2199
+ headers: { "Content-Type": "application/json" },
2200
+ body: "{}",
2201
+ });
2202
+ if (!resp.ok) {
2203
+ throw new AuthError(`bootstrap-status failed: ${resp.status}`, resp.status);
2204
+ }
2205
+ const body = await resp.json();
2206
+ return { bootstrapAvailable: !!body.bootstrap_available };
2207
+ }
2208
+ async login(username, password, default_workspace) {
2209
+ const payload = { username, password };
2210
+ if (default_workspace)
2211
+ payload.default_workspace = default_workspace;
2212
+ const resp = await this.fetchImpl(this.loginUrl, {
2213
+ method: "POST",
2214
+ headers: { "Content-Type": "application/json" },
2215
+ body: JSON.stringify(payload),
2216
+ });
2217
+ if (resp.status === 401) {
2218
+ throw new AuthError("auth failure", 401);
2219
+ }
2220
+ if (!resp.ok) {
2221
+ throw new AuthError(`login failed: ${resp.status}`, resp.status);
2222
+ }
2223
+ const body = await resp.json();
2224
+ if (!body.jwt) {
2225
+ throw new AuthError("login response missing jwt");
2226
+ }
2227
+ return { jwt: body.jwt, jwtExpires: body.jwt_expires ?? "" };
2228
+ }
2229
+ }
2230
+ const createAuthApi = (options) => new AuthApi(options);
2231
+
2232
+ exports.AuthApi = AuthApi;
2233
+ exports.AuthError = AuthError;
1839
2234
  exports.BaseApi = BaseApi;
1840
2235
  exports.CollectionManagementApi = CollectionManagementApi;
1841
2236
  exports.ConfigApi = ConfigApi;
1842
2237
  exports.FlowApi = FlowApi;
1843
2238
  exports.FlowsApi = FlowsApi;
2239
+ exports.IamApi = IamApi;
1844
2240
  exports.KnowledgeApi = KnowledgeApi;
1845
2241
  exports.LibrarianApi = LibrarianApi;
1846
2242
  exports.PROV = PROV;
@@ -1861,13 +2257,15 @@ exports.SCHEMA_NAME = SCHEMA_NAME;
1861
2257
  exports.SKOS = SKOS;
1862
2258
  exports.SKOS_DEFINITION = SKOS_DEFINITION;
1863
2259
  exports.TG = TG;
2260
+ exports.TG_CONCEPT = TG_CONCEPT;
1864
2261
  exports.TG_CONTENT = TG_CONTENT;
1865
2262
  exports.TG_DOCUMENT = TG_DOCUMENT;
1866
2263
  exports.TG_EDGE = TG_EDGE;
1867
2264
  exports.TG_EDGE_COUNT = TG_EDGE_COUNT;
1868
2265
  exports.TG_QUERY = TG_QUERY;
1869
- exports.TG_REASONING = TG_REASONING;
1870
2266
  exports.TG_REIFIES = TG_REIFIES;
2267
+ exports.TG_SCORE = TG_SCORE;
1871
2268
  exports.TG_SELECTED_EDGE = TG_SELECTED_EDGE;
2269
+ exports.createAuthApi = createAuthApi;
1872
2270
  exports.createTrustGraphSocket = createTrustGraphSocket;
1873
2271
  //# sourceMappingURL=index.cjs.map