@raisindb/client 0.5.9 → 0.5.11
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/README.md +32 -0
- package/dist/{chunk-IFV4ECHC.mjs → chunk-ASOWGNQZ.mjs} +3 -3
- package/dist/{chunk-ZUUSBLVP.mjs → chunk-FYWYSNQG.mjs} +3 -3
- package/dist/{chunk-765DLJMZ.mjs → chunk-NOPZAMIV.mjs} +31 -2
- package/dist/chunk-NOPZAMIV.mjs.map +1 -0
- package/dist/{chunk-AULHRX5C.mjs → chunk-TPZNJNZR.mjs} +2 -2
- package/dist/{chunk-VMUT6PIO.mjs → chunk-ZCRYQCXS.mjs} +2 -2
- package/dist/{conversation-list-store-CHaqGtSi.d.mts → conversation-list-store-BNYosL8k.d.mts} +221 -3
- package/dist/{conversation-list-store-CHaqGtSi.d.ts → conversation-list-store-BNYosL8k.d.ts} +221 -3
- package/dist/{index-DiL2gplR.d.ts → index-GaJToZKx.d.ts} +1 -1
- package/dist/{index-AobOnS4A.d.mts → index-UlXuwMoL.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +405 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -10
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +30 -1
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +3 -3
- package/dist/{svelte-sql-FQqis53r.d.ts → svelte-sql-bROcapdk.d.ts} +1 -1
- package/dist/{svelte-sql-BUrNcoHR.d.mts → svelte-sql-yLeGoj89.d.mts} +1 -1
- package/dist/svelte.d.mts +2 -2
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +30 -1
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +3 -3
- package/dist/vue.d.mts +2 -2
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +30 -1
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-765DLJMZ.mjs.map +0 -1
- /package/dist/{chunk-IFV4ECHC.mjs.map → chunk-ASOWGNQZ.mjs.map} +0 -0
- /package/dist/{chunk-ZUUSBLVP.mjs.map → chunk-FYWYSNQG.mjs.map} +0 -0
- /package/dist/{chunk-AULHRX5C.mjs.map → chunk-TPZNJNZR.mjs.map} +0 -0
- /package/dist/{chunk-VMUT6PIO.mjs.map → chunk-ZCRYQCXS.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ __export(src_exports, {
|
|
|
99
99
|
UploadErrorCode: () => UploadErrorCode,
|
|
100
100
|
UploadManager: () => UploadManager,
|
|
101
101
|
Uploader: () => Uploader,
|
|
102
|
+
VisitorChatError: () => VisitorChatError,
|
|
102
103
|
WorkspaceClient: () => WorkspaceClient,
|
|
103
104
|
WorkspaceManager: () => WorkspaceManager,
|
|
104
105
|
calculateChunkCount: () => calculateChunkCount,
|
|
@@ -120,6 +121,7 @@ __export(src_exports, {
|
|
|
120
121
|
createSqlHandler: () => createSqlHandler,
|
|
121
122
|
createSubscriptionAdapter: () => createSubscriptionAdapter,
|
|
122
123
|
decodeMessage: () => decodeMessage,
|
|
124
|
+
defaultVisitorSessionStore: () => defaultVisitorSessionStore,
|
|
123
125
|
encodeMessage: () => encodeMessage,
|
|
124
126
|
getChunkRange: () => getChunkRange,
|
|
125
127
|
getContentType: () => getContentType,
|
|
@@ -872,6 +874,7 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
872
874
|
RequestType2["NodeUpdate"] = "node_update";
|
|
873
875
|
RequestType2["NodeDelete"] = "node_delete";
|
|
874
876
|
RequestType2["NodeGet"] = "node_get";
|
|
877
|
+
RequestType2["NodeGetByLocalizedPath"] = "node_get_by_localized_path";
|
|
875
878
|
RequestType2["NodeQuery"] = "node_query";
|
|
876
879
|
RequestType2["NodeQueryByPath"] = "node_query_by_path";
|
|
877
880
|
RequestType2["NodeQueryByProperty"] = "node_query_by_property";
|
|
@@ -973,6 +976,8 @@ var RequestType = /* @__PURE__ */ ((RequestType2) => {
|
|
|
973
976
|
RequestType2["ScheduledInvocationCancel"] = "scheduled_invocation_cancel";
|
|
974
977
|
RequestType2["ScheduledInvocationList"] = "scheduled_invocation_list";
|
|
975
978
|
RequestType2["ScheduledInvocationGet"] = "scheduled_invocation_get";
|
|
979
|
+
RequestType2["VisitorChatStart"] = "visitor_chat_start";
|
|
980
|
+
RequestType2["VisitorChatSend"] = "visitor_chat_send";
|
|
976
981
|
return RequestType2;
|
|
977
982
|
})(RequestType || {});
|
|
978
983
|
var ResponseStatus = /* @__PURE__ */ ((ResponseStatus2) => {
|
|
@@ -1256,7 +1261,8 @@ var EventHandler = class {
|
|
|
1256
1261
|
this.filterSubscriptions.set(hash, {
|
|
1257
1262
|
serverId: subscriptionId,
|
|
1258
1263
|
filters: {},
|
|
1259
|
-
callbacks: /* @__PURE__ */ new Set([callback])
|
|
1264
|
+
callbacks: /* @__PURE__ */ new Set([callback]),
|
|
1265
|
+
synthetic: true
|
|
1260
1266
|
});
|
|
1261
1267
|
this.subscriptions.set(subscriptionId, hash);
|
|
1262
1268
|
this.callbackToHash.set(callback, hash);
|
|
@@ -1291,11 +1297,12 @@ var EventHandler = class {
|
|
|
1291
1297
|
async restoreSubscriptions(options) {
|
|
1292
1298
|
if (!options?.retry) {
|
|
1293
1299
|
for (const entry of this.filterSubscriptions.values()) {
|
|
1300
|
+
if (entry.synthetic) continue;
|
|
1294
1301
|
this.subscriptions.delete(entry.serverId);
|
|
1295
1302
|
entry.restorePending = true;
|
|
1296
1303
|
}
|
|
1297
1304
|
}
|
|
1298
|
-
const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending);
|
|
1305
|
+
const entriesToRestore = Array.from(this.filterSubscriptions.entries()).filter(([, entry]) => entry.restorePending && !entry.synthetic);
|
|
1299
1306
|
let failureCount = 0;
|
|
1300
1307
|
let lastError;
|
|
1301
1308
|
for (const [hash, entry] of entriesToRestore) {
|
|
@@ -1528,6 +1535,27 @@ var NodeOperations = class {
|
|
|
1528
1535
|
const result = await this.sendRequest(payload, "node_history");
|
|
1529
1536
|
return result ?? [];
|
|
1530
1537
|
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Get a node by its localized URL path (plan Phase 12).
|
|
1540
|
+
*
|
|
1541
|
+
* @param locale - Locale code (`fr`, `fr-CA`)
|
|
1542
|
+
* @param path - The node's path in that locale (`/produits/chaise`)
|
|
1543
|
+
* @returns The translated node with `canonical_path`,
|
|
1544
|
+
* `canonical_localized_path`, hreflang `alternates` and a `redirect`
|
|
1545
|
+
* hint, or `null` when nothing resolves (missing, hidden in the locale
|
|
1546
|
+
* or not readable — the same answer)
|
|
1547
|
+
*
|
|
1548
|
+
* @example
|
|
1549
|
+
* ```typescript
|
|
1550
|
+
* const hit = await ws.nodes().getByLocalizedPath('fr', '/produits/chaise');
|
|
1551
|
+
* if (hit?.redirect) redirect301(`/fr${hit.canonical_localized_path}`);
|
|
1552
|
+
* ```
|
|
1553
|
+
*/
|
|
1554
|
+
async getByLocalizedPath(locale, path) {
|
|
1555
|
+
const payload = { locale, path };
|
|
1556
|
+
const result = await this.sendRequest(payload, "node_get_by_localized_path");
|
|
1557
|
+
return result ?? null;
|
|
1558
|
+
}
|
|
1531
1559
|
/**
|
|
1532
1560
|
* List a node's revision history by path (newest first).
|
|
1533
1561
|
*
|
|
@@ -4487,7 +4515,8 @@ function normalizeHomePath(home) {
|
|
|
4487
4515
|
// src/conversations.ts
|
|
4488
4516
|
var DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS = 12e4;
|
|
4489
4517
|
var ConversationManager = class {
|
|
4490
|
-
constructor(baseUrl, repository, authManager, options = {}, executeSql) {
|
|
4518
|
+
constructor(baseUrl, repository, authManager, options = {}, executeSql, visitor) {
|
|
4519
|
+
this.visitor = visitor;
|
|
4491
4520
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
4492
4521
|
this.repository = repository;
|
|
4493
4522
|
this.authManager = authManager;
|
|
@@ -4608,6 +4637,45 @@ var ConversationManager = class {
|
|
|
4608
4637
|
]
|
|
4609
4638
|
};
|
|
4610
4639
|
}
|
|
4640
|
+
/**
|
|
4641
|
+
* Start (or resume) an ANONYMOUS conversation with an agent — a public
|
|
4642
|
+
* website's chat, without login.
|
|
4643
|
+
*
|
|
4644
|
+
* Works on an anonymous connection only, and only with an agent that allows
|
|
4645
|
+
* anonymous visitors (`anonymous.enabled: true` on its raisin:AIAgent). The
|
|
4646
|
+
* server keeps the conversation in an ephemeral visitor session that only
|
|
4647
|
+
* this browser session can read; the session secret is kept in
|
|
4648
|
+
* sessionStorage (override with `options.store`), so a reload continues the
|
|
4649
|
+
* same conversation. Then use `sendMessage`, `getMessages`, `subscribe` and
|
|
4650
|
+
* `ConversationStore` / `useConversation` exactly as for a signed-in user.
|
|
4651
|
+
*
|
|
4652
|
+
* @example
|
|
4653
|
+
* ```typescript
|
|
4654
|
+
* const convo = await db.conversations.startAnonymous('/agents/website-assistant');
|
|
4655
|
+
* for await (const ev of db.conversations.sendMessage(convo.conversationPath, 'Hello')) {
|
|
4656
|
+
* if (ev.type === 'text_chunk') append(ev.text);
|
|
4657
|
+
* if (ev.type === 'failed') showLimit(ev.code); // e.g. RATE_LIMITED, LIMIT_REACHED
|
|
4658
|
+
* }
|
|
4659
|
+
* ```
|
|
4660
|
+
*/
|
|
4661
|
+
async startAnonymous(agent, options) {
|
|
4662
|
+
if (!this.visitor) {
|
|
4663
|
+
throw new Error("Anonymous chat needs a WebSocket database: use client.database(repo).");
|
|
4664
|
+
}
|
|
4665
|
+
return this.visitor.start(this.normalizeAgentPath(agent), options);
|
|
4666
|
+
}
|
|
4667
|
+
/** Forget the stored anonymous session for `agent` (the next start begins a new one). */
|
|
4668
|
+
forgetAnonymous(agent, options) {
|
|
4669
|
+
this.visitor?.forget(this.normalizeAgentPath(agent), options);
|
|
4670
|
+
}
|
|
4671
|
+
/** Whether this tab remembers an anonymous conversation with `agent`. */
|
|
4672
|
+
hasStoredAnonymous(agent, options) {
|
|
4673
|
+
return this.visitor?.remembers(this.normalizeAgentPath(agent), options) ?? false;
|
|
4674
|
+
}
|
|
4675
|
+
/** Whether `conversationPath` is an anonymous conversation of this client. */
|
|
4676
|
+
isAnonymous(conversationPath) {
|
|
4677
|
+
return this.visitor?.isAnonymous(conversationPath) ?? false;
|
|
4678
|
+
}
|
|
4611
4679
|
/**
|
|
4612
4680
|
* Open an existing conversation by path.
|
|
4613
4681
|
* Returns null if not found.
|
|
@@ -4653,6 +4721,7 @@ var ConversationManager = class {
|
|
|
4653
4721
|
* Mark a conversation as read by setting unread_count to 0.
|
|
4654
4722
|
*/
|
|
4655
4723
|
async markAsRead(conversationPath) {
|
|
4724
|
+
if (this.isAnonymous(conversationPath)) return;
|
|
4656
4725
|
const workspace = "raisin:access_control";
|
|
4657
4726
|
await this.sqlQuery(
|
|
4658
4727
|
`UPDATE '${workspace}' SET properties = jsonb_set(properties, '{unread_count}', '0'::jsonb) WHERE path = $1 AND node_type = 'raisin:Conversation'`,
|
|
@@ -4663,6 +4732,7 @@ var ConversationManager = class {
|
|
|
4663
4732
|
* Mark a single message as read by the current user.
|
|
4664
4733
|
*/
|
|
4665
4734
|
async markMessageAsRead(messagePath) {
|
|
4735
|
+
if (messagePath.startsWith("/visitors/")) return;
|
|
4666
4736
|
const workspace = "raisin:access_control";
|
|
4667
4737
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4668
4738
|
await this.sqlQuery(
|
|
@@ -4687,6 +4757,18 @@ var ConversationManager = class {
|
|
|
4687
4757
|
*/
|
|
4688
4758
|
async *sendMessage(conversationPath, content, options) {
|
|
4689
4759
|
const shouldStream = options?.stream !== false;
|
|
4760
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4761
|
+
if (!shouldStream) {
|
|
4762
|
+
await this.visitor.post(conversationPath, content);
|
|
4763
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4764
|
+
return;
|
|
4765
|
+
}
|
|
4766
|
+
yield* this.visitor.stream(conversationPath, content, {
|
|
4767
|
+
signal: options?.signal,
|
|
4768
|
+
inactivityTimeoutMs: options?.inactivityTimeoutMs ?? DEFAULT_SEND_MESSAGE_INACTIVITY_TIMEOUT_MS
|
|
4769
|
+
});
|
|
4770
|
+
return;
|
|
4771
|
+
}
|
|
4690
4772
|
logger.debug("[ConversationManager] Starting SSE stream for message send");
|
|
4691
4773
|
if (!shouldStream) {
|
|
4692
4774
|
await this.createUserMessage(conversationPath, content);
|
|
@@ -4774,6 +4856,10 @@ var ConversationManager = class {
|
|
|
4774
4856
|
*/
|
|
4775
4857
|
async createUserMessage(conversationPath, content) {
|
|
4776
4858
|
logger.debug("[ConversationManager] Creating user message", { path: conversationPath });
|
|
4859
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4860
|
+
await this.visitor.post(conversationPath, content);
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4777
4863
|
const workspace = "raisin:access_control";
|
|
4778
4864
|
const properties = await this.loadConversationProperties(conversationPath, workspace);
|
|
4779
4865
|
const flowInstanceId = typeof properties?.flow_instance_id === "string" ? properties.flow_instance_id.trim() : "";
|
|
@@ -4825,6 +4911,12 @@ var ConversationManager = class {
|
|
|
4825
4911
|
* initiated messages, etc.). Auto-reconnects on disconnect.
|
|
4826
4912
|
*/
|
|
4827
4913
|
subscribe(conversationPath, onEvent, options) {
|
|
4914
|
+
if (this.visitor?.isAnonymous(conversationPath)) {
|
|
4915
|
+
const stop = this.visitor.listen(conversationPath, onEvent);
|
|
4916
|
+
options?.signal?.addEventListener("abort", stop, { once: true });
|
|
4917
|
+
return { unsubscribe: stop, waitUntilConnected: async () => {
|
|
4918
|
+
} };
|
|
4919
|
+
}
|
|
4828
4920
|
logger.info("[ConversationManager] Subscribing to SSE", { path: conversationPath });
|
|
4829
4921
|
const sseUrl = `${this.baseUrl}/api/conversations/${this.repository}/events`;
|
|
4830
4922
|
const headers = {};
|
|
@@ -5264,6 +5356,272 @@ var ConversationManager = class {
|
|
|
5264
5356
|
}
|
|
5265
5357
|
};
|
|
5266
5358
|
|
|
5359
|
+
// src/visitor-chat.ts
|
|
5360
|
+
function defaultVisitorSessionStore() {
|
|
5361
|
+
const memory = /* @__PURE__ */ new Map();
|
|
5362
|
+
const storage = () => {
|
|
5363
|
+
try {
|
|
5364
|
+
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
|
|
5365
|
+
} catch {
|
|
5366
|
+
return null;
|
|
5367
|
+
}
|
|
5368
|
+
};
|
|
5369
|
+
return {
|
|
5370
|
+
get(key) {
|
|
5371
|
+
try {
|
|
5372
|
+
return storage()?.getItem(key) ?? memory.get(key) ?? null;
|
|
5373
|
+
} catch {
|
|
5374
|
+
return memory.get(key) ?? null;
|
|
5375
|
+
}
|
|
5376
|
+
},
|
|
5377
|
+
set(key, value) {
|
|
5378
|
+
memory.set(key, value);
|
|
5379
|
+
try {
|
|
5380
|
+
storage()?.setItem(key, value);
|
|
5381
|
+
} catch {
|
|
5382
|
+
}
|
|
5383
|
+
},
|
|
5384
|
+
remove(key) {
|
|
5385
|
+
memory.delete(key);
|
|
5386
|
+
try {
|
|
5387
|
+
storage()?.removeItem(key);
|
|
5388
|
+
} catch {
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
};
|
|
5392
|
+
}
|
|
5393
|
+
var VisitorChatError = class extends Error {
|
|
5394
|
+
constructor(message, code) {
|
|
5395
|
+
super(message);
|
|
5396
|
+
this.name = "VisitorChatError";
|
|
5397
|
+
this.code = code;
|
|
5398
|
+
}
|
|
5399
|
+
};
|
|
5400
|
+
var REBIND_CODES = /* @__PURE__ */ new Set(["NO_SESSION", "UNKNOWN_CONVERSATION"]);
|
|
5401
|
+
function asError(err) {
|
|
5402
|
+
const e = err;
|
|
5403
|
+
return new VisitorChatError(e?.message ?? String(err), e?.code ?? "UNAVAILABLE");
|
|
5404
|
+
}
|
|
5405
|
+
var VisitorChat = class {
|
|
5406
|
+
constructor(repository, send, events, onReconnected) {
|
|
5407
|
+
this.repository = repository;
|
|
5408
|
+
this.send = send;
|
|
5409
|
+
this.events = events;
|
|
5410
|
+
this.byPath = /* @__PURE__ */ new Map();
|
|
5411
|
+
this.token = null;
|
|
5412
|
+
onReconnected?.(() => {
|
|
5413
|
+
void this.rebindAll();
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
/** Whether `conversationPath` is a visitor conversation started here. */
|
|
5417
|
+
isAnonymous(conversationPath) {
|
|
5418
|
+
return this.byPath.has(conversationPath);
|
|
5419
|
+
}
|
|
5420
|
+
storeOf(options) {
|
|
5421
|
+
if (options.store === false) return null;
|
|
5422
|
+
return options.store ?? defaultVisitorSessionStore();
|
|
5423
|
+
}
|
|
5424
|
+
keyOf(agent, options) {
|
|
5425
|
+
return options.storageKey ?? `raisindb:visitor:${this.repository}:${agent}`;
|
|
5426
|
+
}
|
|
5427
|
+
/** Start (or resume) a visitor conversation with `agent` (`/agents/<name>`). */
|
|
5428
|
+
async start(agent, options = {}) {
|
|
5429
|
+
const store = this.storeOf(options);
|
|
5430
|
+
const key = this.keyOf(agent, options);
|
|
5431
|
+
const sessionToken = options.sessionToken ?? this.token ?? store?.get(`${key}:session`) ?? void 0;
|
|
5432
|
+
const conversationId = options.newConversation ? void 0 : options.conversationId ?? store?.get(`${key}:conversation`) ?? void 0;
|
|
5433
|
+
let res;
|
|
5434
|
+
try {
|
|
5435
|
+
res = await this.send(
|
|
5436
|
+
{ agent, session_token: sessionToken, conversation_id: conversationId },
|
|
5437
|
+
"visitor_chat_start"
|
|
5438
|
+
);
|
|
5439
|
+
} catch (err) {
|
|
5440
|
+
const e = asError(err);
|
|
5441
|
+
if ((conversationId || sessionToken) && !options.conversationId && !options.sessionToken && ["UNKNOWN_CONVERSATION", "INVALID_CONVERSATION", "SESSION_EXPIRED", "SESSION_MISMATCH"].includes(e.code)) {
|
|
5442
|
+
store?.remove(`${key}:conversation`);
|
|
5443
|
+
if (e.code !== "UNKNOWN_CONVERSATION" && e.code !== "INVALID_CONVERSATION") {
|
|
5444
|
+
store?.remove(`${key}:session`);
|
|
5445
|
+
this.token = null;
|
|
5446
|
+
}
|
|
5447
|
+
return this.start(agent, { ...options, newConversation: true });
|
|
5448
|
+
}
|
|
5449
|
+
throw e;
|
|
5450
|
+
}
|
|
5451
|
+
const token = res.session_token ?? sessionToken ?? this.token;
|
|
5452
|
+
if (!token) throw new VisitorChatError("the server returned no session", "UNAVAILABLE");
|
|
5453
|
+
this.token = token;
|
|
5454
|
+
store?.set(`${key}:session`, token);
|
|
5455
|
+
store?.set(`${key}:conversation`, res.conversation_id);
|
|
5456
|
+
const path = res.conversation_path;
|
|
5457
|
+
let entry = this.byPath.get(path);
|
|
5458
|
+
if (!entry) {
|
|
5459
|
+
entry = {
|
|
5460
|
+
agent: res.agent,
|
|
5461
|
+
conversationId: res.conversation_id,
|
|
5462
|
+
subscriptionId: res.subscription_id,
|
|
5463
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
5464
|
+
options
|
|
5465
|
+
};
|
|
5466
|
+
this.byPath.set(path, entry);
|
|
5467
|
+
}
|
|
5468
|
+
this.route(entry);
|
|
5469
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5470
|
+
return {
|
|
5471
|
+
id: res.conversation_id,
|
|
5472
|
+
type: "ai_chat",
|
|
5473
|
+
agentRef: res.agent,
|
|
5474
|
+
conversationPath: path,
|
|
5475
|
+
conversationWorkspace: res.conversation_workspace,
|
|
5476
|
+
anonymous: true,
|
|
5477
|
+
conversationId: res.conversation_id,
|
|
5478
|
+
sessionToken: token,
|
|
5479
|
+
limits: res.limits,
|
|
5480
|
+
initialEvents: [{ type: "waiting", timestamp: now }]
|
|
5481
|
+
};
|
|
5482
|
+
}
|
|
5483
|
+
/** Route the server's forwarded events for this conversation to its listeners. */
|
|
5484
|
+
route(entry) {
|
|
5485
|
+
this.events.removeFlowEventListener(entry.subscriptionId);
|
|
5486
|
+
this.events.addFlowEventListener(entry.subscriptionId, (msg) => {
|
|
5487
|
+
const event = msg.payload;
|
|
5488
|
+
if (!event || typeof event.type !== "string") return;
|
|
5489
|
+
for (const listener of entry.listeners) {
|
|
5490
|
+
try {
|
|
5491
|
+
listener(event);
|
|
5492
|
+
} catch (err) {
|
|
5493
|
+
logger.error("[VisitorChat] listener failed", err);
|
|
5494
|
+
}
|
|
5495
|
+
}
|
|
5496
|
+
});
|
|
5497
|
+
}
|
|
5498
|
+
/** Re-prove the session on a new connection, for every conversation started here. */
|
|
5499
|
+
async rebindAll() {
|
|
5500
|
+
for (const entry of this.byPath.values()) {
|
|
5501
|
+
try {
|
|
5502
|
+
await this.send(
|
|
5503
|
+
{ agent: entry.agent, session_token: this.token, conversation_id: entry.conversationId },
|
|
5504
|
+
"visitor_chat_start"
|
|
5505
|
+
);
|
|
5506
|
+
this.route(entry);
|
|
5507
|
+
} catch (err) {
|
|
5508
|
+
logger.warn("[VisitorChat] could not resume a conversation after reconnect", err);
|
|
5509
|
+
}
|
|
5510
|
+
}
|
|
5511
|
+
}
|
|
5512
|
+
/** Listen to a visitor conversation's events. Returns an unsubscribe function. */
|
|
5513
|
+
listen(conversationPath, listener) {
|
|
5514
|
+
const entry = this.require(conversationPath);
|
|
5515
|
+
entry.listeners.add(listener);
|
|
5516
|
+
return () => {
|
|
5517
|
+
entry.listeners.delete(listener);
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
require(conversationPath) {
|
|
5521
|
+
const entry = this.byPath.get(conversationPath);
|
|
5522
|
+
if (!entry) {
|
|
5523
|
+
throw new VisitorChatError(
|
|
5524
|
+
"not a visitor conversation of this client; call startAnonymous first",
|
|
5525
|
+
"UNKNOWN_CONVERSATION"
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
return entry;
|
|
5529
|
+
}
|
|
5530
|
+
/** Make sure this connection is bound (after a reconnect nothing else may read the home). */
|
|
5531
|
+
async ensureBound(conversationPath) {
|
|
5532
|
+
const entry = this.require(conversationPath);
|
|
5533
|
+
await this.send(
|
|
5534
|
+
{ agent: entry.agent, session_token: this.token, conversation_id: entry.conversationId },
|
|
5535
|
+
"visitor_chat_start"
|
|
5536
|
+
).catch((err) => {
|
|
5537
|
+
throw asError(err);
|
|
5538
|
+
});
|
|
5539
|
+
}
|
|
5540
|
+
/** Send one visitor message. Events arrive through {@link listen}. */
|
|
5541
|
+
async post(conversationPath, text) {
|
|
5542
|
+
const entry = this.require(conversationPath);
|
|
5543
|
+
const once = async () => await this.send(
|
|
5544
|
+
{ conversation_id: entry.conversationId, text },
|
|
5545
|
+
"visitor_chat_send"
|
|
5546
|
+
);
|
|
5547
|
+
try {
|
|
5548
|
+
const res = await once();
|
|
5549
|
+
return { messageId: res.message_id, messagesLeft: res.messages_left };
|
|
5550
|
+
} catch (err) {
|
|
5551
|
+
const e = asError(err);
|
|
5552
|
+
if (!REBIND_CODES.has(e.code)) throw e;
|
|
5553
|
+
await this.ensureBound(conversationPath);
|
|
5554
|
+
this.route(entry);
|
|
5555
|
+
const res = await once().catch((again) => {
|
|
5556
|
+
throw asError(again);
|
|
5557
|
+
});
|
|
5558
|
+
return { messageId: res.message_id, messagesLeft: res.messages_left };
|
|
5559
|
+
}
|
|
5560
|
+
}
|
|
5561
|
+
/**
|
|
5562
|
+
* Send one visitor message and stream the turn: `text_chunk`,
|
|
5563
|
+
* `tool_call_started`, `tool_call_completed`, … until `done`. A refusal
|
|
5564
|
+
* ends the stream with `failed` (its `code` says why). No event for
|
|
5565
|
+
* `inactivityTimeoutMs` ends it with a synthetic `waiting`.
|
|
5566
|
+
*/
|
|
5567
|
+
async *stream(conversationPath, text, options = {}) {
|
|
5568
|
+
const queue = [];
|
|
5569
|
+
let wake = null;
|
|
5570
|
+
const push = (event) => {
|
|
5571
|
+
queue.push(event);
|
|
5572
|
+
wake?.();
|
|
5573
|
+
};
|
|
5574
|
+
const stop = this.listen(conversationPath, push);
|
|
5575
|
+
const onAbort = () => push({ type: "failed", error: "aborted", code: "ABORTED", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5576
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
5577
|
+
const timeout = options.inactivityTimeoutMs ?? 12e4;
|
|
5578
|
+
try {
|
|
5579
|
+
try {
|
|
5580
|
+
await this.post(conversationPath, text);
|
|
5581
|
+
} catch (err) {
|
|
5582
|
+
const e = asError(err);
|
|
5583
|
+
yield { type: "failed", error: e.message, code: e.code, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5584
|
+
return;
|
|
5585
|
+
}
|
|
5586
|
+
for (; ; ) {
|
|
5587
|
+
if (!queue.length) {
|
|
5588
|
+
const idle = await new Promise((resolve) => {
|
|
5589
|
+
const timer = timeout > 0 ? setTimeout(() => resolve(true), timeout) : null;
|
|
5590
|
+
wake = () => {
|
|
5591
|
+
if (timer) clearTimeout(timer);
|
|
5592
|
+
resolve(false);
|
|
5593
|
+
};
|
|
5594
|
+
});
|
|
5595
|
+
wake = null;
|
|
5596
|
+
if (idle && !queue.length) {
|
|
5597
|
+
yield { type: "waiting", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5598
|
+
return;
|
|
5599
|
+
}
|
|
5600
|
+
}
|
|
5601
|
+
const event = queue.shift();
|
|
5602
|
+
yield event;
|
|
5603
|
+
if (event.type === "done" || event.type === "failed" || event.type === "waiting") return;
|
|
5604
|
+
}
|
|
5605
|
+
} finally {
|
|
5606
|
+
stop();
|
|
5607
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
/** Whether the store remembers a conversation with `agent`. */
|
|
5611
|
+
remembers(agent, options = {}) {
|
|
5612
|
+
const store = this.storeOf(options);
|
|
5613
|
+
return !!store?.get(`${this.keyOf(agent, options)}:conversation`);
|
|
5614
|
+
}
|
|
5615
|
+
/** Forget the stored session (e.g. a "new chat" button that should start a new session). */
|
|
5616
|
+
forget(agent, options = {}) {
|
|
5617
|
+
const store = this.storeOf(options);
|
|
5618
|
+
const key = this.keyOf(agent, options);
|
|
5619
|
+
store?.remove(`${key}:session`);
|
|
5620
|
+
store?.remove(`${key}:conversation`);
|
|
5621
|
+
this.token = null;
|
|
5622
|
+
}
|
|
5623
|
+
};
|
|
5624
|
+
|
|
5267
5625
|
// src/flow-client.ts
|
|
5268
5626
|
var FlowClient = class _FlowClient {
|
|
5269
5627
|
/**
|
|
@@ -6156,7 +6514,13 @@ var Database = class _Database {
|
|
|
6156
6514
|
this.repository,
|
|
6157
6515
|
this.httpOptions.authManager,
|
|
6158
6516
|
{},
|
|
6159
|
-
(query, params) => this.executeSql(query, params)
|
|
6517
|
+
(query, params) => this.executeSql(query, params),
|
|
6518
|
+
new VisitorChat(
|
|
6519
|
+
this.repository,
|
|
6520
|
+
(payload, requestType) => this.sendRequest(payload, requestType),
|
|
6521
|
+
this.eventHandler,
|
|
6522
|
+
this.httpOptions.onReconnected
|
|
6523
|
+
)
|
|
6160
6524
|
);
|
|
6161
6525
|
}
|
|
6162
6526
|
return this._conversationManager;
|
|
@@ -10023,7 +10387,11 @@ var _RaisinClient = class _RaisinClient extends import_events4.EventEmitter {
|
|
|
10023
10387
|
void 0,
|
|
10024
10388
|
() => this.getUploadManager(),
|
|
10025
10389
|
(options) => this.signAssetUrl(options),
|
|
10026
|
-
{
|
|
10390
|
+
{
|
|
10391
|
+
httpBaseUrl: this._httpBaseUrl,
|
|
10392
|
+
authManager: this.authManager,
|
|
10393
|
+
onReconnected: (callback) => this.onReconnected(callback)
|
|
10394
|
+
}
|
|
10027
10395
|
);
|
|
10028
10396
|
}
|
|
10029
10397
|
/**
|
|
@@ -11403,6 +11771,34 @@ var ConversationStore = class {
|
|
|
11403
11771
|
this._watchdogIntervalMs = options.watchdogIntervalMs ?? 3e4;
|
|
11404
11772
|
if (this._conversationPath) {
|
|
11405
11773
|
this.ensureSubscription();
|
|
11774
|
+
} else if (this.createOptions?.anonymous) {
|
|
11775
|
+
void this.resumeAnonymous();
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11778
|
+
anonymousOptions() {
|
|
11779
|
+
const a = this.createOptions?.anonymous;
|
|
11780
|
+
if (!a) return null;
|
|
11781
|
+
return a === true ? {} : a;
|
|
11782
|
+
}
|
|
11783
|
+
/**
|
|
11784
|
+
* Resume the visitor conversation this tab already has (the session secret
|
|
11785
|
+
* and conversation id live in sessionStorage), and load its history. A tab
|
|
11786
|
+
* without one starts nothing until the first message.
|
|
11787
|
+
*/
|
|
11788
|
+
async resumeAnonymous() {
|
|
11789
|
+
const opts = this.anonymousOptions();
|
|
11790
|
+
if (!opts || !this.createOptions || this._conversationPath) return;
|
|
11791
|
+
const agent = this.createOptions.participant;
|
|
11792
|
+
try {
|
|
11793
|
+
if (!this.manager.hasStoredAnonymous(agent, opts)) return;
|
|
11794
|
+
const convo = await this.manager.startAnonymous(agent, opts);
|
|
11795
|
+
if (this._conversationPath) return;
|
|
11796
|
+
this._conversationPath = convo.conversationPath;
|
|
11797
|
+
this._conversationType = convo.type;
|
|
11798
|
+
this.ensureSubscription();
|
|
11799
|
+
await this.loadMessages();
|
|
11800
|
+
} catch (err) {
|
|
11801
|
+
logger.debug("[ConversationStore] No visitor conversation to resume", err);
|
|
11406
11802
|
}
|
|
11407
11803
|
}
|
|
11408
11804
|
// ==========================================================================
|
|
@@ -11457,7 +11853,8 @@ var ConversationStore = class {
|
|
|
11457
11853
|
throw new Error("No conversationPath and no createOptions provided");
|
|
11458
11854
|
}
|
|
11459
11855
|
try {
|
|
11460
|
-
const
|
|
11856
|
+
const anonymous = this.anonymousOptions();
|
|
11857
|
+
const convo = anonymous ? await this.manager.startAnonymous(this.createOptions.participant, anonymous) : await this.manager.create({
|
|
11461
11858
|
participant: this.createOptions.participant,
|
|
11462
11859
|
input: this.createOptions.input
|
|
11463
11860
|
});
|
|
@@ -13345,6 +13742,7 @@ function createRaisinVue(vue) {
|
|
|
13345
13742
|
UploadErrorCode,
|
|
13346
13743
|
UploadManager,
|
|
13347
13744
|
Uploader,
|
|
13745
|
+
VisitorChatError,
|
|
13348
13746
|
WorkspaceClient,
|
|
13349
13747
|
WorkspaceManager,
|
|
13350
13748
|
calculateChunkCount,
|
|
@@ -13366,6 +13764,7 @@ function createRaisinVue(vue) {
|
|
|
13366
13764
|
createSqlHandler,
|
|
13367
13765
|
createSubscriptionAdapter,
|
|
13368
13766
|
decodeMessage,
|
|
13767
|
+
defaultVisitorSessionStore,
|
|
13369
13768
|
encodeMessage,
|
|
13370
13769
|
getChunkRange,
|
|
13371
13770
|
getContentType,
|