@vinaverse/client 0.0.0-stage → 0.1.0-alpha.10

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.
Files changed (49) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +202 -2
  3. package/dist/agent/index.cjs +3319 -0
  4. package/dist/agent/index.cjs.map +1 -0
  5. package/dist/agent/index.d.cts +57 -0
  6. package/dist/agent/index.d.ts +57 -0
  7. package/dist/agent/index.js +99 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/backend/index.cjs +268 -0
  10. package/dist/backend/index.cjs.map +1 -0
  11. package/dist/backend/index.d.cts +72 -0
  12. package/dist/backend/index.d.ts +72 -0
  13. package/dist/backend/index.js +250 -0
  14. package/dist/backend/index.js.map +1 -0
  15. package/dist/chunk-46D3FSLU.js +997 -0
  16. package/dist/chunk-46D3FSLU.js.map +1 -0
  17. package/dist/chunk-GWLLUOFY.js +1374 -0
  18. package/dist/chunk-GWLLUOFY.js.map +1 -0
  19. package/dist/chunk-O3NLAZGZ.js +859 -0
  20. package/dist/chunk-O3NLAZGZ.js.map +1 -0
  21. package/dist/chunk-YNUHXJD6.js +9 -0
  22. package/dist/chunk-YNUHXJD6.js.map +1 -0
  23. package/dist/contract-DmQs8amJ.d.cts +6 -0
  24. package/dist/contract-DmQs8amJ.d.ts +6 -0
  25. package/dist/conversation/index.cjs +2510 -0
  26. package/dist/conversation/index.cjs.map +1 -0
  27. package/dist/conversation/index.d.cts +65 -0
  28. package/dist/conversation/index.d.ts +65 -0
  29. package/dist/conversation/index.js +134 -0
  30. package/dist/conversation/index.js.map +1 -0
  31. package/dist/index.cjs +1390 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.cts +11 -0
  34. package/dist/index.d.ts +11 -0
  35. package/dist/index.js +4 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/runtime-CISPp9nJ.d.cts +371 -0
  38. package/dist/runtime-Z1RI10lU.d.ts +371 -0
  39. package/dist/session-CPz4pcPu.d.cts +3651 -0
  40. package/dist/session-CPz4pcPu.d.ts +3651 -0
  41. package/dist/store-BxiicOKG.d.cts +414 -0
  42. package/dist/store-DZka9T2-.d.ts +414 -0
  43. package/dist/testing/index.cjs +274 -0
  44. package/dist/testing/index.cjs.map +1 -0
  45. package/dist/testing/index.d.cts +60 -0
  46. package/dist/testing/index.d.ts +60 -0
  47. package/dist/testing/index.js +259 -0
  48. package/dist/testing/index.js.map +1 -0
  49. package/package.json +151 -3
@@ -0,0 +1,65 @@
1
+ export { C as CONTRACT } from '../contract-DmQs8amJ.cjs';
2
+ import { Q as Question, A as AnswerInput } from '../session-CPz4pcPu.cjs';
3
+ export { a as Card, b as CardGroup, c as CardImage, d as CardKind, C as CardLink, e as ClientErrorCode, H as HeldCard, f as HeldQuestion, P as Proposal, g as ProposalOutcome, T as ToolLine, V as VinaverseClientError, W as Welcome, i as isKnownCard } from '../session-CPz4pcPu.cjs';
4
+ import { C as ConversationStore, a as ConversationOptions, S as SessionRequest, b as ConversationSession } from '../store-BxiicOKG.cjs';
5
+ export { c as CardItem, d as ConversationActivity, e as ConversationBanner, f as ConversationPersistence, g as ConversationStatus, h as ConversationView, F as FrameScheduler, M as MAX_TEXT, i as MessageItem, j as MessageState, k as SessionReason, l as StreamingText, m as SystemEvent, n as SystemItem, T as TimelineItem, o as createConversation } from '../store-BxiicOKG.cjs';
6
+
7
+ type AnswerProblem =
8
+ /** Nothing chosen or typed, and the question is required (or skipped while required). */
9
+ 'required'
10
+ /** Fewer choices than `min`. */
11
+ | 'too_few'
12
+ /** More choices than `max`, or more than one where one is asked. */
13
+ | 'too_many'
14
+ /** A value that is not on the card, `none_of_the_above` with another choice, or a form the kind does not take. */
15
+ | 'not_allowed'
16
+ /** The text, or the "Other" text, is longer than the card allows (in code points). */
17
+ | 'too_long'
18
+ /** A number outside `min`…`max`, or off its `step`. */
19
+ | 'out_of_range'
20
+ /** Not a calendar date, or outside the card's bounds. */
21
+ | 'bad_date';
22
+ /** Unicode code points, as the contract counts every text limit (an emoji outside the BMP is one). */
23
+ declare function codePoints(text: string): number;
24
+ /**
25
+ * The first rule an answer breaks, or null when it can be sent. For `rate_each`, check each row's answer against
26
+ * the question: one column from `options`, or `skipped` when not required. A kind this client does not know is the
27
+ * server's to judge (null).
28
+ */
29
+ declare function answerProblem(question: Question, input: AnswerInput): AnswerProblem | null;
30
+
31
+ /** Whether a prefix may be registered: the contract's prefix pattern, no refused scheme, no control character. */
32
+ declare function isValidLinkPrefix(prefix: string): boolean;
33
+ /**
34
+ * Whether a card's link may be opened: it starts with one of the registered prefixes (invalid ones are ignored) and
35
+ * what follows is a path and an optional query of the allowed characters. Route by the page it names; never open a
36
+ * query value.
37
+ */
38
+ declare function isAllowedLink(link: string, prefixes: readonly string[]): boolean;
39
+
40
+ /** A session request, with the mode it is for. */
41
+ interface ModeSessionRequest extends SessionRequest {
42
+ readonly mode: string;
43
+ }
44
+ interface ConversationsByModeOptions extends Omit<ConversationOptions, 'getSession'> {
45
+ /** Your backend creates the session for the mode's agent (its `subject.type`, its agent), with its service key. */
46
+ readonly getSession: (request: ModeSessionRequest) => Promise<ConversationSession>;
47
+ /** Every mode the app has, so `clear()` forgets each one's kept history, even one not opened since launch. */
48
+ readonly modes?: readonly string[];
49
+ }
50
+ interface ConversationsByMode {
51
+ /** The mode's store, made the first time it is asked for; the same store after that. */
52
+ get(mode: string): ConversationStore;
53
+ /**
54
+ * The mode the person switched to: its store, with every other mode's sent to the background (no pings; closed
55
+ * with `closeInBackground`). The kit then drives the selected one with the app's foreground and background.
56
+ */
57
+ select(mode: string): ConversationStore;
58
+ /** Close every mode's store. */
59
+ dispose(): void;
60
+ /** Close every mode's store and forget what each kept on the device (sign-out). */
61
+ clear(): void;
62
+ }
63
+ declare function createConversationsByMode(options: ConversationsByModeOptions): ConversationsByMode;
64
+
65
+ export { AnswerInput, type AnswerProblem, ConversationOptions, ConversationSession, ConversationStore, type ConversationsByMode, type ConversationsByModeOptions, type ModeSessionRequest, Question, SessionRequest, answerProblem, codePoints, createConversationsByMode, isAllowedLink, isValidLinkPrefix };
@@ -0,0 +1,65 @@
1
+ export { C as CONTRACT } from '../contract-DmQs8amJ.js';
2
+ import { Q as Question, A as AnswerInput } from '../session-CPz4pcPu.js';
3
+ export { a as Card, b as CardGroup, c as CardImage, d as CardKind, C as CardLink, e as ClientErrorCode, H as HeldCard, f as HeldQuestion, P as Proposal, g as ProposalOutcome, T as ToolLine, V as VinaverseClientError, W as Welcome, i as isKnownCard } from '../session-CPz4pcPu.js';
4
+ import { C as ConversationStore, a as ConversationOptions, S as SessionRequest, b as ConversationSession } from '../store-DZka9T2-.js';
5
+ export { c as CardItem, d as ConversationActivity, e as ConversationBanner, f as ConversationPersistence, g as ConversationStatus, h as ConversationView, F as FrameScheduler, M as MAX_TEXT, i as MessageItem, j as MessageState, k as SessionReason, l as StreamingText, m as SystemEvent, n as SystemItem, T as TimelineItem, o as createConversation } from '../store-DZka9T2-.js';
6
+
7
+ type AnswerProblem =
8
+ /** Nothing chosen or typed, and the question is required (or skipped while required). */
9
+ 'required'
10
+ /** Fewer choices than `min`. */
11
+ | 'too_few'
12
+ /** More choices than `max`, or more than one where one is asked. */
13
+ | 'too_many'
14
+ /** A value that is not on the card, `none_of_the_above` with another choice, or a form the kind does not take. */
15
+ | 'not_allowed'
16
+ /** The text, or the "Other" text, is longer than the card allows (in code points). */
17
+ | 'too_long'
18
+ /** A number outside `min`…`max`, or off its `step`. */
19
+ | 'out_of_range'
20
+ /** Not a calendar date, or outside the card's bounds. */
21
+ | 'bad_date';
22
+ /** Unicode code points, as the contract counts every text limit (an emoji outside the BMP is one). */
23
+ declare function codePoints(text: string): number;
24
+ /**
25
+ * The first rule an answer breaks, or null when it can be sent. For `rate_each`, check each row's answer against
26
+ * the question: one column from `options`, or `skipped` when not required. A kind this client does not know is the
27
+ * server's to judge (null).
28
+ */
29
+ declare function answerProblem(question: Question, input: AnswerInput): AnswerProblem | null;
30
+
31
+ /** Whether a prefix may be registered: the contract's prefix pattern, no refused scheme, no control character. */
32
+ declare function isValidLinkPrefix(prefix: string): boolean;
33
+ /**
34
+ * Whether a card's link may be opened: it starts with one of the registered prefixes (invalid ones are ignored) and
35
+ * what follows is a path and an optional query of the allowed characters. Route by the page it names; never open a
36
+ * query value.
37
+ */
38
+ declare function isAllowedLink(link: string, prefixes: readonly string[]): boolean;
39
+
40
+ /** A session request, with the mode it is for. */
41
+ interface ModeSessionRequest extends SessionRequest {
42
+ readonly mode: string;
43
+ }
44
+ interface ConversationsByModeOptions extends Omit<ConversationOptions, 'getSession'> {
45
+ /** Your backend creates the session for the mode's agent (its `subject.type`, its agent), with its service key. */
46
+ readonly getSession: (request: ModeSessionRequest) => Promise<ConversationSession>;
47
+ /** Every mode the app has, so `clear()` forgets each one's kept history, even one not opened since launch. */
48
+ readonly modes?: readonly string[];
49
+ }
50
+ interface ConversationsByMode {
51
+ /** The mode's store, made the first time it is asked for; the same store after that. */
52
+ get(mode: string): ConversationStore;
53
+ /**
54
+ * The mode the person switched to: its store, with every other mode's sent to the background (no pings; closed
55
+ * with `closeInBackground`). The kit then drives the selected one with the app's foreground and background.
56
+ */
57
+ select(mode: string): ConversationStore;
58
+ /** Close every mode's store. */
59
+ dispose(): void;
60
+ /** Close every mode's store and forget what each kept on the device (sign-out). */
61
+ clear(): void;
62
+ }
63
+ declare function createConversationsByMode(options: ConversationsByModeOptions): ConversationsByMode;
64
+
65
+ export { AnswerInput, type AnswerProblem, ConversationOptions, ConversationSession, ConversationStore, type ConversationsByMode, type ConversationsByModeOptions, type ModeSessionRequest, Question, SessionRequest, answerProblem, codePoints, createConversationsByMode, isAllowedLink, isValidLinkPrefix };
@@ -0,0 +1,134 @@
1
+ export { CONTRACT } from '../chunk-YNUHXJD6.js';
2
+ import { createConversation } from '../chunk-46D3FSLU.js';
3
+ export { ConversationStore, MAX_TEXT, createConversation, isAllowedLink, isValidLinkPrefix } from '../chunk-46D3FSLU.js';
4
+ export { VinaverseClientError, isKnownCard } from '../chunk-GWLLUOFY.js';
5
+
6
+ // src/conversation/answers.ts
7
+ var CHOICE = /* @__PURE__ */ new Set([
8
+ "single_choice",
9
+ "multi_choice",
10
+ "image_choice",
11
+ "yes_no",
12
+ "list_choice"
13
+ ]);
14
+ var ONE_CHOICE = /* @__PURE__ */ new Set(["single_choice", "yes_no", "list_choice"]);
15
+ function codePoints(text) {
16
+ let n = 0;
17
+ for (const _ of text) n++;
18
+ return n;
19
+ }
20
+ function answerProblem(question, input) {
21
+ const answer = input;
22
+ if (answer.skipped === true) return question.required ? "required" : null;
23
+ const kind = question.kind;
24
+ if (CHOICE.has(kind)) return choiceProblem(question, answer);
25
+ switch (kind) {
26
+ case "rate_each": {
27
+ const values = answer.values ?? [];
28
+ if (answer.other_text !== void 0) return "not_allowed";
29
+ if (values.length === 0) return "required";
30
+ if (values.length > 1) return "too_many";
31
+ return (question.options ?? []).some((o) => o.value === values[0]) ? null : "not_allowed";
32
+ }
33
+ case "short_text":
34
+ case "long_text": {
35
+ const text = answer.text ?? "";
36
+ if (!text.trim()) return "required";
37
+ return codePoints(text) > (question.text?.max_length ?? 2e3) ? "too_long" : null;
38
+ }
39
+ case "number":
40
+ case "scale": {
41
+ const n = answer.number;
42
+ if (typeof n !== "number" || !Number.isFinite(n)) return "required";
43
+ const range = kind === "number" ? question.number : question.scale;
44
+ if (!range) return null;
45
+ if (n < range.min || n > range.max) return "out_of_range";
46
+ const steps = (n - range.min) / range.step;
47
+ return Math.abs(steps - Math.round(steps)) > 1e-9 ? "out_of_range" : null;
48
+ }
49
+ case "date":
50
+ return dateProblem(question, answer.date);
51
+ case "photo":
52
+ return answer.upload_ref ? null : "required";
53
+ default:
54
+ return null;
55
+ }
56
+ }
57
+ function choiceProblem(question, answer) {
58
+ const values = answer.values ?? [];
59
+ const other = answer.other_text;
60
+ if (other !== void 0) {
61
+ if (!question.allows_other) return "not_allowed";
62
+ if (!other.trim()) return "required";
63
+ if (codePoints(other) > (question.other_max_length ?? 500))
64
+ return "too_long";
65
+ if (values.length > 0 && question.kind !== "single_choice" && question.kind !== "multi_choice")
66
+ return "not_allowed";
67
+ }
68
+ if (values.length === 0) return other !== void 0 ? null : "required";
69
+ if (new Set(values).size !== values.length) return "not_allowed";
70
+ const none = question.none_of_the_above?.value;
71
+ if (none !== void 0 && values.includes(none))
72
+ return values.length === 1 && other === void 0 ? null : "not_allowed";
73
+ const known = new Set((question.options ?? []).map((o) => o.value));
74
+ if (!values.every((v) => known.has(v))) return "not_allowed";
75
+ if (ONE_CHOICE.has(question.kind) && values.length > 1) return "too_many";
76
+ const chosen = values.length + (other !== void 0 ? 1 : 0);
77
+ if (question.min !== void 0 && chosen < question.min) return "too_few";
78
+ if (question.max !== void 0 && chosen > question.max) return "too_many";
79
+ return null;
80
+ }
81
+ function dateProblem(question, date) {
82
+ if (!date) return "required";
83
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
84
+ if (!m) return "bad_date";
85
+ const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
86
+ const at = new Date(Date.UTC(y, mo - 1, d));
87
+ if (at.getUTCFullYear() !== y || at.getUTCMonth() !== mo - 1 || at.getUTCDate() !== d)
88
+ return "bad_date";
89
+ if (question.date?.min !== void 0 && date < question.date.min)
90
+ return "bad_date";
91
+ if (question.date?.max !== void 0 && date > question.date.max)
92
+ return "bad_date";
93
+ return null;
94
+ }
95
+
96
+ // src/conversation/modes.ts
97
+ function createConversationsByMode(options) {
98
+ const { getSession, modes = [], ...shared } = options;
99
+ const base = shared.persistenceKey ?? "vinaverse.conversation";
100
+ const stores = /* @__PURE__ */ new Map();
101
+ const get = (mode) => {
102
+ let store = stores.get(mode);
103
+ if (!store) {
104
+ store = createConversation({
105
+ ...shared,
106
+ persistenceKey: `${base}.${mode}`,
107
+ getSession: (request) => getSession({ ...request, mode })
108
+ });
109
+ stores.set(mode, store);
110
+ }
111
+ return store;
112
+ };
113
+ return {
114
+ get,
115
+ select(mode) {
116
+ for (const [other, store] of stores)
117
+ if (other !== mode) store.setForeground(false);
118
+ return get(mode);
119
+ },
120
+ dispose() {
121
+ for (const store of stores.values()) store.dispose();
122
+ stores.clear();
123
+ },
124
+ clear() {
125
+ for (const mode of /* @__PURE__ */ new Set([...stores.keys(), ...modes]))
126
+ get(mode).clear();
127
+ stores.clear();
128
+ }
129
+ };
130
+ }
131
+
132
+ export { answerProblem, codePoints, createConversationsByMode };
133
+ //# sourceMappingURL=index.js.map
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/conversation/answers.ts","../../src/conversation/modes.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Whether an answer to a question card can be sent: the card's own rules (`Question`, `QuestionAnswer`), checked as\n// the person answers so a UI enables Send only for an answer the server will take. The server still decides: what it\n// rejects arrives as `ui.answer.rejected` and unlocks the question. Shared by every UI kit.\nimport type { AnswerInput, Question } from '../events.js';\n\nexport type AnswerProblem =\n /** Nothing chosen or typed, and the question is required (or skipped while required). */\n | 'required'\n /** Fewer choices than `min`. */\n | 'too_few'\n /** More choices than `max`, or more than one where one is asked. */\n | 'too_many'\n /** A value that is not on the card, `none_of_the_above` with another choice, or a form the kind does not take. */\n | 'not_allowed'\n /** The text, or the \"Other\" text, is longer than the card allows (in code points). */\n | 'too_long'\n /** A number outside `min`…`max`, or off its `step`. */\n | 'out_of_range'\n /** Not a calendar date, or outside the card's bounds. */\n | 'bad_date';\n\nconst CHOICE = new Set([\n 'single_choice',\n 'multi_choice',\n 'image_choice',\n 'yes_no',\n 'list_choice',\n]);\nconst ONE_CHOICE = new Set(['single_choice', 'yes_no', 'list_choice']);\n\n/** Unicode code points, as the contract counts every text limit (an emoji outside the BMP is one). */\nexport function codePoints(text: string): number {\n let n = 0;\n for (const _ of text) n++;\n return n;\n}\n\ntype Answer = {\n values?: readonly string[];\n other_text?: string;\n text?: string;\n number?: number;\n date?: string;\n upload_ref?: string;\n skipped?: boolean;\n};\n\n/**\n * The first rule an answer breaks, or null when it can be sent. For `rate_each`, check each row's answer against\n * the question: one column from `options`, or `skipped` when not required. A kind this client does not know is the\n * server's to judge (null).\n */\nexport function answerProblem(\n question: Question,\n input: AnswerInput,\n): AnswerProblem | null {\n const answer = input as Answer;\n if (answer.skipped === true) return question.required ? 'required' : null;\n const kind = question.kind as string;\n if (CHOICE.has(kind)) return choiceProblem(question, answer);\n switch (kind) {\n case 'rate_each': {\n const values = answer.values ?? [];\n if (answer.other_text !== undefined) return 'not_allowed';\n if (values.length === 0) return 'required';\n if (values.length > 1) return 'too_many';\n return (question.options ?? []).some((o) => o.value === values[0])\n ? null\n : 'not_allowed';\n }\n case 'short_text':\n case 'long_text': {\n const text = answer.text ?? '';\n if (!text.trim()) return 'required';\n return codePoints(text) > (question.text?.max_length ?? 2000)\n ? 'too_long'\n : null;\n }\n case 'number':\n case 'scale': {\n const n = answer.number;\n if (typeof n !== 'number' || !Number.isFinite(n)) return 'required';\n const range = kind === 'number' ? question.number : question.scale;\n if (!range) return null;\n if (n < range.min || n > range.max) return 'out_of_range';\n const steps = (n - range.min) / range.step;\n return Math.abs(steps - Math.round(steps)) > 1e-9 ? 'out_of_range' : null;\n }\n case 'date':\n return dateProblem(question, answer.date);\n case 'photo':\n return answer.upload_ref ? null : 'required';\n default:\n return null;\n }\n}\n\nfunction choiceProblem(\n question: Question,\n answer: Answer,\n): AnswerProblem | null {\n const values = answer.values ?? [];\n const other = answer.other_text;\n if (other !== undefined) {\n if (!question.allows_other) return 'not_allowed';\n if (!other.trim()) return 'required';\n if (codePoints(other) > (question.other_max_length ?? 500))\n return 'too_long';\n if (\n values.length > 0 &&\n question.kind !== 'single_choice' &&\n question.kind !== 'multi_choice'\n )\n return 'not_allowed';\n }\n if (values.length === 0) return other !== undefined ? null : 'required';\n if (new Set(values).size !== values.length) return 'not_allowed';\n const none = question.none_of_the_above?.value;\n if (none !== undefined && values.includes(none))\n return values.length === 1 && other === undefined ? null : 'not_allowed';\n const known = new Set((question.options ?? []).map((o) => o.value));\n if (!values.every((v) => known.has(v))) return 'not_allowed';\n // One option for the single kinds (a single_choice may add its \"Other\" too, as the contract allows); the \"Other\"\n // counts as a choice against min and max.\n if (ONE_CHOICE.has(question.kind) && values.length > 1) return 'too_many';\n const chosen = values.length + (other !== undefined ? 1 : 0);\n if (question.min !== undefined && chosen < question.min) return 'too_few';\n if (question.max !== undefined && chosen > question.max) return 'too_many';\n return null;\n}\n\nfunction dateProblem(\n question: Question,\n date: string | undefined,\n): AnswerProblem | null {\n if (!date) return 'required';\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(date);\n if (!m) return 'bad_date';\n const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];\n const at = new Date(Date.UTC(y, mo - 1, d));\n if (\n at.getUTCFullYear() !== y ||\n at.getUTCMonth() !== mo - 1 ||\n at.getUTCDate() !== d\n )\n return 'bad_date';\n // ISO dates compare as text.\n if (question.date?.min !== undefined && date < question.date.min)\n return 'bad_date';\n if (question.date?.max !== undefined && date > question.date.max)\n return 'bad_date';\n return null;\n}\n","// One conversation store per mode (a customer and a professional, say, each with an agent of its own): each mode keeps\n// its own timeline, its own history on the device (a persistence key of its own) and its own sessions, so switching\n// modes never shows one mode's conversation in the other. The tenant's backend gets the mode with every session\n// request and creates the session for that mode's agent.\nimport {\n type ConversationOptions,\n type ConversationSession,\n type ConversationStore,\n createConversation,\n type SessionRequest,\n} from './store.js';\n\n/** A session request, with the mode it is for. */\nexport interface ModeSessionRequest extends SessionRequest {\n readonly mode: string;\n}\n\nexport interface ConversationsByModeOptions\n extends Omit<ConversationOptions, 'getSession'> {\n /** Your backend creates the session for the mode's agent (its `subject.type`, its agent), with its service key. */\n readonly getSession: (\n request: ModeSessionRequest,\n ) => Promise<ConversationSession>;\n /** Every mode the app has, so `clear()` forgets each one's kept history, even one not opened since launch. */\n readonly modes?: readonly string[];\n}\n\nexport interface ConversationsByMode {\n /** The mode's store, made the first time it is asked for; the same store after that. */\n get(mode: string): ConversationStore;\n /**\n * The mode the person switched to: its store, with every other mode's sent to the background (no pings; closed\n * with `closeInBackground`). The kit then drives the selected one with the app's foreground and background.\n */\n select(mode: string): ConversationStore;\n /** Close every mode's store. */\n dispose(): void;\n /** Close every mode's store and forget what each kept on the device (sign-out). */\n clear(): void;\n}\n\nexport function createConversationsByMode(\n options: ConversationsByModeOptions,\n): ConversationsByMode {\n const { getSession, modes = [], ...shared } = options;\n const base = shared.persistenceKey ?? 'vinaverse.conversation';\n const stores = new Map<string, ConversationStore>();\n const get = (mode: string): ConversationStore => {\n let store = stores.get(mode);\n if (!store) {\n store = createConversation({\n ...shared,\n persistenceKey: `${base}.${mode}`,\n getSession: (request) => getSession({ ...request, mode }),\n });\n stores.set(mode, store);\n }\n return store;\n };\n return {\n get,\n select(mode) {\n for (const [other, store] of stores)\n if (other !== mode) store.setForeground(false);\n return get(mode);\n },\n dispose() {\n for (const store of stores.values()) store.dispose();\n stores.clear();\n },\n clear() {\n for (const mode of new Set([...stores.keys(), ...modes]))\n get(mode).clear();\n stores.clear();\n },\n };\n}\n"]}