@vinaverse/client 0.1.0-alpha.1 → 0.1.0-alpha.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.
Files changed (55) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +127 -1
  3. package/dist/agent/index.cjs +3321 -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 +295 -0
  10. package/dist/backend/index.cjs.map +1 -0
  11. package/dist/backend/index.d.cts +82 -0
  12. package/dist/backend/index.d.ts +82 -0
  13. package/dist/backend/index.js +277 -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-D56MNG3E.js +9 -0
  18. package/dist/chunk-D56MNG3E.js.map +1 -0
  19. package/dist/{chunk-AWHPWRGT.js → chunk-GWLLUOFY.js} +71 -15
  20. package/dist/chunk-GWLLUOFY.js.map +1 -0
  21. package/dist/chunk-JI6HUXH3.js +861 -0
  22. package/dist/chunk-JI6HUXH3.js.map +1 -0
  23. package/dist/contract-Be2oVHNr.d.cts +6 -0
  24. package/dist/contract-Be2oVHNr.d.ts +6 -0
  25. package/dist/conversation/index.cjs +356 -27
  26. package/dist/conversation/index.cjs.map +1 -1
  27. package/dist/conversation/index.d.cts +23 -307
  28. package/dist/conversation/index.d.ts +23 -307
  29. package/dist/conversation/index.js +36 -760
  30. package/dist/conversation/index.js.map +1 -1
  31. package/dist/history/index.cjs +282 -0
  32. package/dist/history/index.cjs.map +1 -0
  33. package/dist/history/index.d.cts +91 -0
  34. package/dist/history/index.d.ts +91 -0
  35. package/dist/history/index.js +261 -0
  36. package/dist/history/index.js.map +1 -0
  37. package/dist/index.cjs +70 -8
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +2 -1
  40. package/dist/index.d.ts +2 -1
  41. package/dist/index.js +2 -1
  42. package/dist/runtime-6O0qIPmo.d.cts +394 -0
  43. package/dist/runtime-CNgz2_8G.d.ts +394 -0
  44. package/dist/{session--W6JXfwo.d.cts → session-D0ijOQX3.d.cts} +3330 -1614
  45. package/dist/{session--W6JXfwo.d.ts → session-D0ijOQX3.d.ts} +3330 -1614
  46. package/dist/store-CGRSxVcL.d.ts +414 -0
  47. package/dist/store-CxoHwd64.d.cts +414 -0
  48. package/dist/testing/index.cjs +418 -0
  49. package/dist/testing/index.cjs.map +1 -0
  50. package/dist/testing/index.d.cts +97 -0
  51. package/dist/testing/index.d.ts +97 -0
  52. package/dist/testing/index.js +403 -0
  53. package/dist/testing/index.js.map +1 -0
  54. package/package.json +105 -11
  55. package/dist/chunk-AWHPWRGT.js.map +0 -1
@@ -0,0 +1,97 @@
1
+ import { A as AgentBackend, q as AgentSessionRequest, s as AgentStatusRead } from '../runtime-6O0qIPmo.cjs';
2
+ import { B as WebSocketFactory, a as Card, W as Welcome } from '../session-D0ijOQX3.cjs';
3
+ import '../store-CxoHwd64.cjs';
4
+
5
+ /** A message of a scripted past conversation. */
6
+ interface MockHistoryMessage {
7
+ readonly role: 'person' | 'assistant' | 'staff';
8
+ readonly text: string;
9
+ /** A guardrail refused this turn (on the person's text, and on the fixed line said instead of an answer). */
10
+ readonly refusal?: true;
11
+ /** `assistant` only: the reply stopped before it finished. */
12
+ readonly interrupted?: boolean;
13
+ readonly at?: string;
14
+ }
15
+ /** A card a turn of a scripted past conversation drew; a proposal with what became of it. */
16
+ interface MockHistoryCard {
17
+ readonly card: Card;
18
+ readonly proposal_outcome?: 'pending' | 'confirmed' | 'declined' | 'failed' | 'expired';
19
+ readonly at?: string;
20
+ }
21
+ /** A past conversation the mock serves in history (and resumes on "Continue" with its messages). */
22
+ interface MockConversation {
23
+ readonly id: string;
24
+ /** Default: the day it started. */
25
+ readonly title?: string;
26
+ /** Default `customer`. */
27
+ readonly mode?: string;
28
+ readonly scope?: string | null;
29
+ readonly agent?: string;
30
+ readonly startedAt?: string;
31
+ /** Default: the last item's `at`, else `startedAt`. */
32
+ readonly lastActivityAt?: string;
33
+ /** Default `ended`; `open` can be continued. */
34
+ readonly state?: 'open' | 'ended';
35
+ readonly items: readonly (MockHistoryMessage | MockHistoryCard)[];
36
+ }
37
+ /** One reply of the scripted agent. */
38
+ interface MockReply {
39
+ readonly text?: string;
40
+ readonly cards?: readonly Card[];
41
+ /** The turn fails before any text, with this code (`internal`, `timeout`, …). */
42
+ readonly fail?: string;
43
+ }
44
+ interface MockAgentOptions {
45
+ /** Replies in order, one per turn (text, cards, a failure), after which `reply` (or the echo) answers. */
46
+ readonly script?: readonly (MockReply | string)[];
47
+ /** The agent's answer to the person's text. Default: the `script`, then an echo. */
48
+ readonly reply?: (text: string, context: {
49
+ readonly turn: number;
50
+ readonly key: string;
51
+ }) => MockReply | string;
52
+ readonly agentName?: string;
53
+ readonly disclosure?: string;
54
+ readonly welcome?: Welcome;
55
+ /** A refusal when opening a session (the scripted backend's answer), by request. */
56
+ readonly refuse?: (request: AgentSessionRequest, attempt: number) => 'ended' | 'unreachable' | 'unavailable' | undefined;
57
+ /** How long a session lives. Default 30 minutes. */
58
+ readonly sessionMs?: number;
59
+ readonly status?: AgentStatusRead;
60
+ /** The clock sessions are dated by; default the device's. */
61
+ readonly now?: () => number;
62
+ /** Past conversations history serves, beside the ones the person has here. */
63
+ readonly history?: readonly MockConversation[];
64
+ }
65
+ interface MockAgent {
66
+ readonly backend: AgentBackend;
67
+ /** The store's transport: `conversation.transport.webSocket`. */
68
+ readonly webSocket: WebSocketFactory;
69
+ /** What the app sent: feedback, sessions asked for, places told, history asked for and deleted. */
70
+ readonly log: {
71
+ readonly sessions: AgentSessionRequest[];
72
+ readonly feedback: unknown[];
73
+ readonly contexts: unknown[];
74
+ readonly ended: number;
75
+ readonly lists: unknown[];
76
+ readonly reads: unknown[];
77
+ readonly deleted: string[];
78
+ };
79
+ }
80
+ declare function createMockAgent(options?: MockAgentOptions): MockAgent;
81
+ /** Cards as a tenant's tools would give them, for mock replies and tests. */
82
+ declare const mockCards: {
83
+ professional(id: string, extra?: Partial<Card>): Card;
84
+ workspace(id: string, extra?: Partial<Card>): Card;
85
+ /** A proposal the person confirms in the app (`confirm_mode: tenant_app`), pending for a day unless `expires_at`. */
86
+ proposal(id: string, proposal?: {
87
+ readonly topic?: string;
88
+ readonly title?: string;
89
+ readonly summary?: string;
90
+ readonly expires_at?: string;
91
+ }): Card;
92
+ /** Times to book, from `start` on the hour, one an hour. */
93
+ timeSlots(id: string, start: string, count?: number): Card;
94
+ links(id: string): Card;
95
+ };
96
+
97
+ export { type MockAgent, type MockAgentOptions, type MockConversation, type MockHistoryCard, type MockHistoryMessage, type MockReply, createMockAgent, mockCards };
@@ -0,0 +1,97 @@
1
+ import { A as AgentBackend, q as AgentSessionRequest, s as AgentStatusRead } from '../runtime-CNgz2_8G.js';
2
+ import { B as WebSocketFactory, a as Card, W as Welcome } from '../session-D0ijOQX3.js';
3
+ import '../store-CGRSxVcL.js';
4
+
5
+ /** A message of a scripted past conversation. */
6
+ interface MockHistoryMessage {
7
+ readonly role: 'person' | 'assistant' | 'staff';
8
+ readonly text: string;
9
+ /** A guardrail refused this turn (on the person's text, and on the fixed line said instead of an answer). */
10
+ readonly refusal?: true;
11
+ /** `assistant` only: the reply stopped before it finished. */
12
+ readonly interrupted?: boolean;
13
+ readonly at?: string;
14
+ }
15
+ /** A card a turn of a scripted past conversation drew; a proposal with what became of it. */
16
+ interface MockHistoryCard {
17
+ readonly card: Card;
18
+ readonly proposal_outcome?: 'pending' | 'confirmed' | 'declined' | 'failed' | 'expired';
19
+ readonly at?: string;
20
+ }
21
+ /** A past conversation the mock serves in history (and resumes on "Continue" with its messages). */
22
+ interface MockConversation {
23
+ readonly id: string;
24
+ /** Default: the day it started. */
25
+ readonly title?: string;
26
+ /** Default `customer`. */
27
+ readonly mode?: string;
28
+ readonly scope?: string | null;
29
+ readonly agent?: string;
30
+ readonly startedAt?: string;
31
+ /** Default: the last item's `at`, else `startedAt`. */
32
+ readonly lastActivityAt?: string;
33
+ /** Default `ended`; `open` can be continued. */
34
+ readonly state?: 'open' | 'ended';
35
+ readonly items: readonly (MockHistoryMessage | MockHistoryCard)[];
36
+ }
37
+ /** One reply of the scripted agent. */
38
+ interface MockReply {
39
+ readonly text?: string;
40
+ readonly cards?: readonly Card[];
41
+ /** The turn fails before any text, with this code (`internal`, `timeout`, …). */
42
+ readonly fail?: string;
43
+ }
44
+ interface MockAgentOptions {
45
+ /** Replies in order, one per turn (text, cards, a failure), after which `reply` (or the echo) answers. */
46
+ readonly script?: readonly (MockReply | string)[];
47
+ /** The agent's answer to the person's text. Default: the `script`, then an echo. */
48
+ readonly reply?: (text: string, context: {
49
+ readonly turn: number;
50
+ readonly key: string;
51
+ }) => MockReply | string;
52
+ readonly agentName?: string;
53
+ readonly disclosure?: string;
54
+ readonly welcome?: Welcome;
55
+ /** A refusal when opening a session (the scripted backend's answer), by request. */
56
+ readonly refuse?: (request: AgentSessionRequest, attempt: number) => 'ended' | 'unreachable' | 'unavailable' | undefined;
57
+ /** How long a session lives. Default 30 minutes. */
58
+ readonly sessionMs?: number;
59
+ readonly status?: AgentStatusRead;
60
+ /** The clock sessions are dated by; default the device's. */
61
+ readonly now?: () => number;
62
+ /** Past conversations history serves, beside the ones the person has here. */
63
+ readonly history?: readonly MockConversation[];
64
+ }
65
+ interface MockAgent {
66
+ readonly backend: AgentBackend;
67
+ /** The store's transport: `conversation.transport.webSocket`. */
68
+ readonly webSocket: WebSocketFactory;
69
+ /** What the app sent: feedback, sessions asked for, places told, history asked for and deleted. */
70
+ readonly log: {
71
+ readonly sessions: AgentSessionRequest[];
72
+ readonly feedback: unknown[];
73
+ readonly contexts: unknown[];
74
+ readonly ended: number;
75
+ readonly lists: unknown[];
76
+ readonly reads: unknown[];
77
+ readonly deleted: string[];
78
+ };
79
+ }
80
+ declare function createMockAgent(options?: MockAgentOptions): MockAgent;
81
+ /** Cards as a tenant's tools would give them, for mock replies and tests. */
82
+ declare const mockCards: {
83
+ professional(id: string, extra?: Partial<Card>): Card;
84
+ workspace(id: string, extra?: Partial<Card>): Card;
85
+ /** A proposal the person confirms in the app (`confirm_mode: tenant_app`), pending for a day unless `expires_at`. */
86
+ proposal(id: string, proposal?: {
87
+ readonly topic?: string;
88
+ readonly title?: string;
89
+ readonly summary?: string;
90
+ readonly expires_at?: string;
91
+ }): Card;
92
+ /** Times to book, from `start` on the hour, one an hour. */
93
+ timeSlots(id: string, start: string, count?: number): Card;
94
+ links(id: string): Card;
95
+ };
96
+
97
+ export { type MockAgent, type MockAgentOptions, type MockConversation, type MockHistoryCard, type MockHistoryMessage, type MockReply, createMockAgent, mockCards };
@@ -0,0 +1,403 @@
1
+ import { AgentRefusal } from '../chunk-JI6HUXH3.js';
2
+ import '../chunk-46D3FSLU.js';
3
+ import '../chunk-GWLLUOFY.js';
4
+
5
+ // src/testing/index.ts
6
+ var CURSOR = /^mock:(\d+)$/;
7
+ var refusal = (kind, reason) => Object.assign(
8
+ new AgentRefusal(kind, {
9
+ reason,
10
+ code: reason === "not_found" ? "conversation_not_found" : "unavailable"
11
+ }),
12
+ { retryable: false }
13
+ );
14
+ var sentences = (text) => text.match(/[^.!?]+[.!?]*\s*/g) ?? [text];
15
+ function createMockAgent(options = {}) {
16
+ const now = options.now ?? (() => Date.now());
17
+ const at = () => new Date(now()).toISOString();
18
+ const conversations = /* @__PURE__ */ new Map();
19
+ const sessions = /* @__PURE__ */ new Map();
20
+ const log = {
21
+ sessions: [],
22
+ feedback: [],
23
+ contexts: [],
24
+ ended: 0,
25
+ lists: [],
26
+ reads: [],
27
+ deleted: []
28
+ };
29
+ let attempts = 0;
30
+ let count = 0;
31
+ for (const c of options.history ?? []) {
32
+ const started = c.startedAt ?? at();
33
+ const items = c.items.map((item, n) => {
34
+ const id = `h_${c.id}_${n + 1}`;
35
+ const turn = `turn_${c.id}_${n + 1}`;
36
+ if ("card" in item)
37
+ return {
38
+ type: "card",
39
+ id,
40
+ turn_id: turn,
41
+ at: item.at ?? started,
42
+ card: item.card,
43
+ ...item.card.kind === "proposal" ? { proposal_outcome: item.proposal_outcome ?? "pending" } : {}
44
+ };
45
+ return {
46
+ type: "message",
47
+ id,
48
+ role: item.role,
49
+ text: item.text,
50
+ at: item.at ?? started,
51
+ event_id: id,
52
+ turn_id: turn,
53
+ ...item.interrupted ? { interrupted: true } : {},
54
+ ...item.refusal ? { refusal: true } : {}
55
+ };
56
+ });
57
+ conversations.set(c.id, {
58
+ id: c.id,
59
+ mode: c.mode ?? "customer",
60
+ scope: c.scope ?? null,
61
+ agent: c.agent ?? "mock_agent",
62
+ title: c.title ?? null,
63
+ started_at: started,
64
+ state: c.state ?? "ended",
65
+ items
66
+ });
67
+ }
68
+ const lastActivity = (held) => held.items.at(-1)?.at ?? held.started_at;
69
+ const summaryOf = (held) => ({
70
+ id: held.id,
71
+ agent: held.agent,
72
+ experience_mode: held.mode,
73
+ ...held.scope === null ? {} : { business_scope_id: held.scope },
74
+ title: held.title ?? held.started_at.slice(0, 10),
75
+ title_source: held.title ? "intent" : "date",
76
+ started_at: held.started_at,
77
+ last_activity_at: lastActivity(held),
78
+ message_count: held.items.filter((i) => i.type === "message").length,
79
+ state: held.state
80
+ });
81
+ const offsetOf = (cursor) => {
82
+ if (cursor === null) return 0;
83
+ const m = CURSOR.exec(cursor);
84
+ if (!m) throw refusal("unavailable", "server");
85
+ return Number(m[1]);
86
+ };
87
+ const backend = {
88
+ async openSession(request) {
89
+ log.sessions.push(request);
90
+ attempts++;
91
+ const refusal2 = options.refuse?.(request, attempts);
92
+ if (refusal2 === "unreachable")
93
+ throw Object.assign(new Error("mock: unreachable"), {
94
+ retryable: true,
95
+ reason: "network"
96
+ });
97
+ if (refusal2) throw new AgentRefusal(refusal2);
98
+ if (request.conversationId !== null && !conversations.has(request.conversationId))
99
+ throw new AgentRefusal("ended", { code: "conversation_not_found" });
100
+ count++;
101
+ const conversation = request.conversationId ?? `cnv_MOCK${count}`;
102
+ if (!conversations.has(conversation))
103
+ conversations.set(conversation, {
104
+ id: conversation,
105
+ mode: request.key.mode,
106
+ scope: request.key.scope ?? null,
107
+ agent: request.agent ?? "mock_agent",
108
+ title: null,
109
+ started_at: at(),
110
+ state: "open",
111
+ items: []
112
+ });
113
+ const id = `ses_MOCK${count}`;
114
+ sessions.set(id, { conversation, key: `${request.key.mode}` });
115
+ const session = {
116
+ id,
117
+ realtime_url: `wss://mock.vinaverse.invalid/v1/sessions/${id}/realtime`,
118
+ turns_url: `https://mock.vinaverse.invalid/v1/sessions/${id}/turns`,
119
+ client_secret: `vv_cs_${"mock".repeat(6)}`,
120
+ conversation_id: conversation,
121
+ expires_at: new Date(
122
+ now() + (options.sessionMs ?? 30 * 6e4)
123
+ ).toISOString(),
124
+ ...options.agentName ? { agent: { display_name: options.agentName } } : {},
125
+ ...options.disclosure ? { disclosure: options.disclosure } : {},
126
+ ...options.welcome ? { welcome: options.welcome } : {}
127
+ };
128
+ return session;
129
+ },
130
+ async refreshSecret() {
131
+ return `vv_cs_${"mock".repeat(6)}`;
132
+ },
133
+ async updateContext(sessionId, place, revision) {
134
+ log.contexts.push({ sessionId, place, revision });
135
+ },
136
+ async status() {
137
+ return options.status ?? {
138
+ available: true,
139
+ launcher: true,
140
+ displayName: options.agentName ?? null
141
+ };
142
+ },
143
+ async sendFeedback(feedback) {
144
+ log.feedback.push(feedback);
145
+ },
146
+ async endSessions() {
147
+ log.ended++;
148
+ },
149
+ async listConversations(query) {
150
+ log.lists.push(query);
151
+ const from = offsetOf(query.cursor);
152
+ const limit = query.limit ?? 20;
153
+ const all = [...conversations.values()].filter(
154
+ (c) => c.mode === query.mode && c.scope === (query.scope ?? null) && (query.agent === null || c.agent === query.agent) && c.items.some((i) => i.type === "message")
155
+ ).map(summaryOf).sort(
156
+ (a, b) => b.last_activity_at.localeCompare(a.last_activity_at) || b.id.localeCompare(a.id)
157
+ );
158
+ const data = all.slice(from, from + limit);
159
+ return {
160
+ data,
161
+ next_cursor: from + limit < all.length ? `mock:${from + limit}` : null
162
+ };
163
+ },
164
+ async readConversation(conversationId, page) {
165
+ log.reads.push({ conversationId, ...page });
166
+ const held = conversations.get(conversationId);
167
+ if (!held) throw refusal("ended", "not_found");
168
+ const limit = page.limit ?? 30;
169
+ const end = page.cursor === null ? held.items.length : offsetOf(page.cursor);
170
+ const start = Math.max(0, end - limit);
171
+ return {
172
+ data: held.items.slice(start, end),
173
+ next_cursor: start > 0 ? `mock:${start}` : null
174
+ };
175
+ },
176
+ async deleteConversation(conversationId) {
177
+ log.deleted.push(conversationId);
178
+ if (!conversations.delete(conversationId))
179
+ throw refusal("ended", "not_found");
180
+ for (const [id, s] of sessions)
181
+ if (s.conversation === conversationId) sessions.delete(id);
182
+ }
183
+ };
184
+ const webSocket = (url) => {
185
+ const sessionId = /sessions\/([^/]+)\/realtime/.exec(url)?.[1] ?? "";
186
+ const held = sessions.get(sessionId);
187
+ const items = held ? conversations.get(held.conversation)?.items ?? [] : [];
188
+ const said = () => items.filter(
189
+ (i) => i.type === "message"
190
+ );
191
+ let turn = said().filter((s) => s.role === "person").length;
192
+ let state = 0;
193
+ const socket = {
194
+ get readyState() {
195
+ return state;
196
+ },
197
+ onopen: null,
198
+ onmessage: null,
199
+ onclose: null,
200
+ onerror: null,
201
+ send(data) {
202
+ const frame = JSON.parse(data);
203
+ if (frame.type === "ping") return deliver({ type: "pong" });
204
+ if (frame.type !== "input.text") return;
205
+ const id = String(frame.client_event_id ?? "");
206
+ const text = String(frame.text ?? "");
207
+ turn++;
208
+ const turnId = `turn_MOCK${turn}`;
209
+ items.push({
210
+ type: "message",
211
+ id: `p_${turnId}`,
212
+ role: "person",
213
+ text,
214
+ at: at(),
215
+ event_id: id,
216
+ turn_id: turnId
217
+ });
218
+ const answer = options.script?.[turn - 1] ?? options.reply?.(text, { turn, key: held?.key ?? "" }) ?? `You said: ${text}`;
219
+ const reply = typeof answer === "string" ? { text: answer } : answer;
220
+ deliver({ type: "turn.started", turn_id: turnId, client_event_id: id });
221
+ if (reply.fail) {
222
+ deliver({
223
+ type: "turn.failed",
224
+ turn_id: turnId,
225
+ code: reply.fail,
226
+ message: "",
227
+ retryable: true
228
+ });
229
+ return;
230
+ }
231
+ let so = "";
232
+ for (const part of reply.text ? sentences(reply.text) : []) {
233
+ so += part;
234
+ deliver({ type: "message.delta", turn_id: turnId, text: part });
235
+ }
236
+ if (reply.text) {
237
+ deliver({
238
+ type: "message.completed",
239
+ turn_id: turnId,
240
+ text: so.trim()
241
+ });
242
+ items.push({
243
+ type: "message",
244
+ id: `a_${turnId}`,
245
+ role: "assistant",
246
+ text: so.trim(),
247
+ at: at(),
248
+ event_id: turnId,
249
+ turn_id: turnId
250
+ });
251
+ }
252
+ if (reply.cards?.length) {
253
+ deliver({ type: "ui.cards", turn_id: turnId, cards: reply.cards });
254
+ for (const card of reply.cards)
255
+ items.push({
256
+ type: "card",
257
+ id: card.id,
258
+ turn_id: turnId,
259
+ at: at(),
260
+ card,
261
+ ...card.kind === "proposal" ? { proposal_outcome: "pending" } : {}
262
+ });
263
+ }
264
+ deliver({ type: "turn.completed", turn_id: turnId });
265
+ },
266
+ close(code) {
267
+ if (state === 3) return;
268
+ state = 3;
269
+ setTimeout(() => socket.onclose?.({ code: code ?? 1e3 }), 0);
270
+ }
271
+ };
272
+ function deliver(event) {
273
+ setTimeout(() => {
274
+ if (state === 1) socket.onmessage?.({ data: JSON.stringify(event) });
275
+ }, 0);
276
+ }
277
+ setTimeout(() => {
278
+ state = 1;
279
+ socket.onopen?.({});
280
+ const transcript = said();
281
+ const resumed = transcript.length > 0;
282
+ socket.onmessage?.({
283
+ data: JSON.stringify({
284
+ type: "session.ready",
285
+ session: { id: sessionId, status: "open" },
286
+ resumed,
287
+ limits: {},
288
+ disclosure: options.disclosure ?? "An AI agent. Powered by Vinaverse.",
289
+ ...resumed ? {
290
+ transcript: transcript.map((s) => ({
291
+ role: s.role,
292
+ text: s.text,
293
+ id: s.event_id,
294
+ at: s.at,
295
+ ...s.turn_id ? { turn_id: s.turn_id } : {},
296
+ ...s.interrupted ? { interrupted: true } : {}
297
+ }))
298
+ } : {}
299
+ })
300
+ });
301
+ }, 0);
302
+ return socket;
303
+ };
304
+ return { backend, webSocket, log };
305
+ }
306
+ var mockCards = {
307
+ professional(id, extra = {}) {
308
+ return {
309
+ id,
310
+ kind: "professional",
311
+ title: "Riley Sample",
312
+ subtitle: "Barber \xB7 Los Angeles",
313
+ ref: { type: "professional", id },
314
+ images: [
315
+ {
316
+ url: `https://images.example.test/${id}.webp`,
317
+ width: 1600,
318
+ height: 1e3
319
+ }
320
+ ],
321
+ rating: { value_ppm: 48e5, count: 132 },
322
+ price_from: { amount: 4500, currency: "USD" },
323
+ actions: [],
324
+ source: { tool: "search_professionals", call_id: "call_MOCK" },
325
+ ...extra
326
+ };
327
+ },
328
+ workspace(id, extra = {}) {
329
+ return {
330
+ id,
331
+ kind: "workspace",
332
+ title: "Suite 4",
333
+ subtitle: "Private suite \xB7 Atlanta",
334
+ ref: { type: "workspace", id },
335
+ price_from: { amount: 6500, currency: "USD" },
336
+ actions: [],
337
+ source: { tool: "search_spaces", call_id: "call_MOCK" },
338
+ ...extra
339
+ };
340
+ },
341
+ /** A proposal the person confirms in the app (`confirm_mode: tenant_app`), pending for a day unless `expires_at`. */
342
+ proposal(id, proposal = {}) {
343
+ const title = proposal.title ?? "Silk press with Riley";
344
+ return {
345
+ id,
346
+ kind: "proposal",
347
+ title,
348
+ actions: [],
349
+ source: { tool: "create_proposal", call_id: "call_MOCK" },
350
+ proposal: {
351
+ id: `prp_${id}`,
352
+ status: "pending",
353
+ topic: proposal.topic ?? "booking",
354
+ title,
355
+ summary: proposal.summary ?? "Wednesday at 10:00 \xB7 $45",
356
+ fields: [],
357
+ expires_at: proposal.expires_at ?? new Date(Date.now() + 24 * 36e5).toISOString(),
358
+ confirm_mode: "tenant_app"
359
+ }
360
+ };
361
+ },
362
+ /** Times to book, from `start` on the hour, one an hour. */
363
+ timeSlots(id, start, count = 3) {
364
+ const at = Date.parse(start);
365
+ return {
366
+ id,
367
+ kind: "time_slots",
368
+ title: "Open times",
369
+ slots: Array.from({ length: count }, (_, i) => ({
370
+ id: `slot_${i + 1}`,
371
+ start: new Date(at + i * 36e5).toISOString(),
372
+ end: new Date(at + (i + 1) * 36e5).toISOString()
373
+ })),
374
+ actions: [],
375
+ source: { tool: "get_availability", call_id: "call_MOCK" }
376
+ };
377
+ },
378
+ links(id) {
379
+ return {
380
+ id,
381
+ kind: "links",
382
+ title: "Pages that can help",
383
+ links: [
384
+ {
385
+ label: "Help center",
386
+ url: "https://help.example.test/",
387
+ purpose: "help"
388
+ },
389
+ {
390
+ label: "Contact us",
391
+ url: "https://help.example.test/contact",
392
+ purpose: "contact"
393
+ }
394
+ ],
395
+ actions: [],
396
+ source: { tool: "scope", call_id: "turn_MOCK" }
397
+ };
398
+ }
399
+ };
400
+
401
+ export { createMockAgent, mockCards };
402
+ //# sourceMappingURL=index.js.map
403
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/testing/index.ts"],"names":["refusal"],"mappings":"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","file":"index.js","sourcesContent":["// @vinaverse/client/testing: a scripted agent for an app's mock mode and tests, with no tenant and no network. It is an\n// `AgentBackend` (sessions, fresh secrets, status, feedback, history) and a WebSocket factory that speaks the realtime\n// protocol: session.ready (resumed with the conversation's transcript), replies streamed sentence by sentence, cards, a\n// turn that fails, refusals when opening (ended, unreachable, unavailable). History as the contract serves it: the\n// conversations scripted (`history`) and the ones the person has here, listed a page at a time, read back with their\n// cards, refusals and proposal outcomes, deleted (then unknown: `not_found`). The app scripts only its own answers.\nimport type {\n AgentBackend,\n AgentSessionRequest,\n AgentStatusRead,\n} from '../agent/runtime.js';\nimport { AgentRefusal } from '../agent/runtime.js';\nimport type { ConversationSession } from '../conversation/store.js';\nimport type { Card, Schemas, Welcome } from '../events.js';\nimport type { WebSocketFactory, WebSocketLike } from '../session.js';\n\ntype HistoryItem = Schemas['TimelineItem'];\ntype Summary = Schemas['ConversationSummary'];\n\n/** A message of a scripted past conversation. */\nexport interface MockHistoryMessage {\n readonly role: 'person' | 'assistant' | 'staff';\n readonly text: string;\n /** A guardrail refused this turn (on the person's text, and on the fixed line said instead of an answer). */\n readonly refusal?: true;\n /** `assistant` only: the reply stopped before it finished. */\n readonly interrupted?: boolean;\n readonly at?: string;\n}\n/** A card a turn of a scripted past conversation drew; a proposal with what became of it. */\nexport interface MockHistoryCard {\n readonly card: Card;\n readonly proposal_outcome?:\n | 'pending'\n | 'confirmed'\n | 'declined'\n | 'failed'\n | 'expired';\n readonly at?: string;\n}\n/** A past conversation the mock serves in history (and resumes on \"Continue\" with its messages). */\nexport interface MockConversation {\n readonly id: string;\n /** Default: the day it started. */\n readonly title?: string;\n /** Default `customer`. */\n readonly mode?: string;\n readonly scope?: string | null;\n readonly agent?: string;\n readonly startedAt?: string;\n /** Default: the last item's `at`, else `startedAt`. */\n readonly lastActivityAt?: string;\n /** Default `ended`; `open` can be continued. */\n readonly state?: 'open' | 'ended';\n readonly items: readonly (MockHistoryMessage | MockHistoryCard)[];\n}\n\n/** One reply of the scripted agent. */\nexport interface MockReply {\n readonly text?: string;\n readonly cards?: readonly Card[];\n /** The turn fails before any text, with this code (`internal`, `timeout`, …). */\n readonly fail?: string;\n}\n\nexport interface MockAgentOptions {\n /** Replies in order, one per turn (text, cards, a failure), after which `reply` (or the echo) answers. */\n readonly script?: readonly (MockReply | string)[];\n /** The agent's answer to the person's text. Default: the `script`, then an echo. */\n readonly reply?: (\n text: string,\n context: { readonly turn: number; readonly key: string },\n ) => MockReply | string;\n readonly agentName?: string;\n readonly disclosure?: string;\n readonly welcome?: Welcome;\n /** A refusal when opening a session (the scripted backend's answer), by request. */\n readonly refuse?: (\n request: AgentSessionRequest,\n attempt: number,\n ) => 'ended' | 'unreachable' | 'unavailable' | undefined;\n /** How long a session lives. Default 30 minutes. */\n readonly sessionMs?: number;\n readonly status?: AgentStatusRead;\n /** The clock sessions are dated by; default the device's. */\n readonly now?: () => number;\n /** Past conversations history serves, beside the ones the person has here. */\n readonly history?: readonly MockConversation[];\n}\n\nexport interface MockAgent {\n readonly backend: AgentBackend;\n /** The store's transport: `conversation.transport.webSocket`. */\n readonly webSocket: WebSocketFactory;\n /** What the app sent: feedback, sessions asked for, places told, history asked for and deleted. */\n readonly log: {\n readonly sessions: AgentSessionRequest[];\n readonly feedback: unknown[];\n readonly contexts: unknown[];\n readonly ended: number;\n readonly lists: unknown[];\n readonly reads: unknown[];\n readonly deleted: string[];\n };\n}\n\n/** A conversation the mock holds: as history lists it, with its items as history reads them. */\ninterface Held {\n readonly id: string;\n readonly mode: string;\n readonly scope: string | null;\n readonly agent: string;\n readonly title: string | null;\n readonly started_at: string;\n state: 'open' | 'ended';\n readonly items: HistoryItem[];\n}\n\nconst CURSOR = /^mock:(\\d+)$/;\nconst refusal = (\n kind: 'ended' | 'unavailable',\n reason: 'not_found' | 'server',\n) =>\n Object.assign(\n new AgentRefusal(kind, {\n reason,\n code: reason === 'not_found' ? 'conversation_not_found' : 'unavailable',\n }),\n { retryable: false },\n );\n\nconst sentences = (text: string) => text.match(/[^.!?]+[.!?]*\\s*/g) ?? [text];\n\nexport function createMockAgent(options: MockAgentOptions = {}): MockAgent {\n const now = options.now ?? (() => Date.now());\n const at = () => new Date(now()).toISOString();\n const conversations = new Map<string, Held>();\n const sessions = new Map<string, { conversation: string; key: string }>();\n const log = {\n sessions: [] as AgentSessionRequest[],\n feedback: [] as unknown[],\n contexts: [] as unknown[],\n ended: 0,\n lists: [] as unknown[],\n reads: [] as unknown[],\n deleted: [] as string[],\n };\n let attempts = 0;\n let count = 0;\n for (const c of options.history ?? []) {\n const started = c.startedAt ?? at();\n const items = c.items.map((item, n): HistoryItem => {\n const id = `h_${c.id}_${n + 1}`;\n const turn = `turn_${c.id}_${n + 1}`;\n if ('card' in item)\n return {\n type: 'card',\n id,\n turn_id: turn,\n at: item.at ?? started,\n card: item.card,\n ...(item.card.kind === 'proposal'\n ? { proposal_outcome: item.proposal_outcome ?? 'pending' }\n : {}),\n };\n return {\n type: 'message',\n id,\n role: item.role,\n text: item.text,\n at: item.at ?? started,\n event_id: id,\n turn_id: turn,\n ...(item.interrupted ? { interrupted: true } : {}),\n ...(item.refusal ? { refusal: true } : {}),\n };\n });\n conversations.set(c.id, {\n id: c.id,\n mode: c.mode ?? 'customer',\n scope: c.scope ?? null,\n agent: c.agent ?? 'mock_agent',\n title: c.title ?? null,\n started_at: started,\n state: c.state ?? 'ended',\n items,\n });\n }\n const lastActivity = (held: Held) => held.items.at(-1)?.at ?? held.started_at;\n const summaryOf = (held: Held): Summary => ({\n id: held.id,\n agent: held.agent,\n experience_mode: held.mode as Summary['experience_mode'],\n ...(held.scope === null ? {} : { business_scope_id: held.scope }),\n title: held.title ?? held.started_at.slice(0, 10),\n title_source: held.title ? 'intent' : 'date',\n started_at: held.started_at,\n last_activity_at: lastActivity(held),\n message_count: held.items.filter((i) => i.type === 'message').length,\n state: held.state,\n });\n /** `cursor` as an offset the mock wrote; another one is refused as the gateway refuses a stale cursor. */\n const offsetOf = (cursor: string | null) => {\n if (cursor === null) return 0;\n const m = CURSOR.exec(cursor);\n if (!m) throw refusal('unavailable', 'server');\n return Number(m[1]);\n };\n\n const backend: AgentBackend = {\n async openSession(request) {\n log.sessions.push(request);\n attempts++;\n const refusal = options.refuse?.(request, attempts);\n if (refusal === 'unreachable')\n throw Object.assign(new Error('mock: unreachable'), {\n retryable: true,\n reason: 'network',\n });\n if (refusal) throw new AgentRefusal(refusal);\n if (\n request.conversationId !== null &&\n !conversations.has(request.conversationId)\n )\n throw new AgentRefusal('ended', { code: 'conversation_not_found' });\n count++;\n const conversation = request.conversationId ?? `cnv_MOCK${count}`;\n if (!conversations.has(conversation))\n conversations.set(conversation, {\n id: conversation,\n mode: request.key.mode,\n scope: request.key.scope ?? null,\n agent: request.agent ?? 'mock_agent',\n title: null,\n started_at: at(),\n state: 'open',\n items: [],\n });\n const id = `ses_MOCK${count}`;\n sessions.set(id, { conversation, key: `${request.key.mode}` });\n const session: ConversationSession = {\n id,\n realtime_url: `wss://mock.vinaverse.invalid/v1/sessions/${id}/realtime`,\n turns_url: `https://mock.vinaverse.invalid/v1/sessions/${id}/turns`,\n client_secret: `vv_cs_${'mock'.repeat(6)}`,\n conversation_id: conversation,\n expires_at: new Date(\n now() + (options.sessionMs ?? 30 * 60_000),\n ).toISOString(),\n ...(options.agentName\n ? { agent: { display_name: options.agentName } }\n : {}),\n ...(options.disclosure ? { disclosure: options.disclosure } : {}),\n ...(options.welcome ? { welcome: options.welcome } : {}),\n };\n return session;\n },\n async refreshSecret() {\n return `vv_cs_${'mock'.repeat(6)}`;\n },\n async updateContext(sessionId, place, revision) {\n log.contexts.push({ sessionId, place, revision });\n },\n async status() {\n return (\n options.status ?? {\n available: true,\n launcher: true,\n displayName: options.agentName ?? null,\n }\n );\n },\n async sendFeedback(feedback) {\n log.feedback.push(feedback);\n },\n async endSessions() {\n log.ended++;\n },\n async listConversations(query) {\n log.lists.push(query);\n const from = offsetOf(query.cursor);\n const limit = query.limit ?? 20;\n const all = [...conversations.values()]\n .filter(\n (c) =>\n c.mode === query.mode &&\n c.scope === (query.scope ?? null) &&\n (query.agent === null || c.agent === query.agent) &&\n c.items.some((i) => i.type === 'message'),\n )\n .map(summaryOf)\n .sort(\n (a, b) =>\n b.last_activity_at.localeCompare(a.last_activity_at) ||\n b.id.localeCompare(a.id),\n );\n const data = all.slice(from, from + limit);\n return {\n data,\n next_cursor: from + limit < all.length ? `mock:${from + limit}` : null,\n };\n },\n async readConversation(conversationId, page) {\n log.reads.push({ conversationId, ...page });\n const held = conversations.get(conversationId);\n if (!held) throw refusal('ended', 'not_found');\n const limit = page.limit ?? 30;\n // Newest page first: `cursor` is how many items from the start the page before began at.\n const end =\n page.cursor === null ? held.items.length : offsetOf(page.cursor);\n const start = Math.max(0, end - limit);\n return {\n data: held.items.slice(start, end),\n next_cursor: start > 0 ? `mock:${start}` : null,\n };\n },\n async deleteConversation(conversationId) {\n log.deleted.push(conversationId);\n if (!conversations.delete(conversationId))\n throw refusal('ended', 'not_found');\n for (const [id, s] of sessions)\n if (s.conversation === conversationId) sessions.delete(id);\n },\n };\n\n const webSocket: WebSocketFactory = (url) => {\n const sessionId = /sessions\\/([^/]+)\\/realtime/.exec(url)?.[1] ?? '';\n const held = sessions.get(sessionId);\n const items = held\n ? (conversations.get(held.conversation)?.items ?? [])\n : [];\n const said = () =>\n items.filter(\n (i): i is Extract<HistoryItem, { type: 'message' }> =>\n i.type === 'message',\n );\n let turn = said().filter((s) => s.role === 'person').length;\n let state = 0;\n const socket: WebSocketLike = {\n get readyState() {\n return state;\n },\n onopen: null,\n onmessage: null,\n onclose: null,\n onerror: null,\n send(data) {\n const frame = JSON.parse(data) as Record<string, unknown>;\n if (frame.type === 'ping') return deliver({ type: 'pong' });\n if (frame.type !== 'input.text') return;\n const id = String(frame.client_event_id ?? '');\n const text = String(frame.text ?? '');\n turn++;\n const turnId = `turn_MOCK${turn}`;\n items.push({\n type: 'message',\n id: `p_${turnId}`,\n role: 'person',\n text,\n at: at(),\n event_id: id,\n turn_id: turnId,\n });\n const answer =\n options.script?.[turn - 1] ??\n options.reply?.(text, { turn, key: held?.key ?? '' }) ??\n `You said: ${text}`;\n const reply: MockReply =\n typeof answer === 'string' ? { text: answer } : answer;\n deliver({ type: 'turn.started', turn_id: turnId, client_event_id: id });\n if (reply.fail) {\n deliver({\n type: 'turn.failed',\n turn_id: turnId,\n code: reply.fail,\n message: '',\n retryable: true,\n });\n return;\n }\n let so = '';\n for (const part of reply.text ? sentences(reply.text) : []) {\n so += part;\n deliver({ type: 'message.delta', turn_id: turnId, text: part });\n }\n if (reply.text) {\n deliver({\n type: 'message.completed',\n turn_id: turnId,\n text: so.trim(),\n });\n items.push({\n type: 'message',\n id: `a_${turnId}`,\n role: 'assistant',\n text: so.trim(),\n at: at(),\n event_id: turnId,\n turn_id: turnId,\n });\n }\n if (reply.cards?.length) {\n deliver({ type: 'ui.cards', turn_id: turnId, cards: reply.cards });\n for (const card of reply.cards)\n items.push({\n type: 'card',\n id: card.id,\n turn_id: turnId,\n at: at(),\n card,\n ...(card.kind === 'proposal'\n ? { proposal_outcome: 'pending' }\n : {}),\n });\n }\n deliver({ type: 'turn.completed', turn_id: turnId });\n },\n close(code) {\n if (state === 3) return;\n state = 3;\n setTimeout(() => socket.onclose?.({ code: code ?? 1000 }), 0);\n },\n };\n function deliver(event: unknown) {\n setTimeout(() => {\n if (state === 1) socket.onmessage?.({ data: JSON.stringify(event) });\n }, 0);\n }\n setTimeout(() => {\n state = 1;\n socket.onopen?.({});\n const transcript = said();\n const resumed = transcript.length > 0;\n socket.onmessage?.({\n data: JSON.stringify({\n type: 'session.ready',\n session: { id: sessionId, status: 'open' },\n resumed,\n limits: {},\n disclosure:\n options.disclosure ?? 'An AI agent. Powered by Vinaverse.',\n ...(resumed\n ? {\n transcript: transcript.map((s) => ({\n role: s.role,\n text: s.text,\n id: s.event_id,\n at: s.at,\n ...(s.turn_id ? { turn_id: s.turn_id } : {}),\n ...(s.interrupted ? { interrupted: true } : {}),\n })),\n }\n : {}),\n }),\n });\n }, 0);\n return socket;\n };\n\n return { backend, webSocket, log };\n}\n\n/** Cards as a tenant's tools would give them, for mock replies and tests. */\nexport const mockCards = {\n professional(id: string, extra: Partial<Card> = {}): Card {\n return {\n id,\n kind: 'professional',\n title: 'Riley Sample',\n subtitle: 'Barber · Los Angeles',\n ref: { type: 'professional', id },\n images: [\n {\n url: `https://images.example.test/${id}.webp`,\n width: 1600,\n height: 1000,\n },\n ],\n rating: { value_ppm: 4_800_000, count: 132 },\n price_from: { amount: 4500, currency: 'USD' },\n actions: [],\n source: { tool: 'search_professionals', call_id: 'call_MOCK' },\n ...extra,\n } as Card;\n },\n workspace(id: string, extra: Partial<Card> = {}): Card {\n return {\n id,\n kind: 'workspace',\n title: 'Suite 4',\n subtitle: 'Private suite · Atlanta',\n ref: { type: 'workspace', id },\n price_from: { amount: 6500, currency: 'USD' },\n actions: [],\n source: { tool: 'search_spaces', call_id: 'call_MOCK' },\n ...extra,\n } as Card;\n },\n /** A proposal the person confirms in the app (`confirm_mode: tenant_app`), pending for a day unless `expires_at`. */\n proposal(\n id: string,\n proposal: {\n readonly topic?: string;\n readonly title?: string;\n readonly summary?: string;\n readonly expires_at?: string;\n } = {},\n ): Card {\n const title = proposal.title ?? 'Silk press with Riley';\n return {\n id,\n kind: 'proposal',\n title,\n actions: [],\n source: { tool: 'create_proposal', call_id: 'call_MOCK' },\n proposal: {\n id: `prp_${id}`,\n status: 'pending',\n topic: proposal.topic ?? 'booking',\n title,\n summary: proposal.summary ?? 'Wednesday at 10:00 · $45',\n fields: [],\n expires_at:\n proposal.expires_at ??\n new Date(Date.now() + 24 * 3_600_000).toISOString(),\n confirm_mode: 'tenant_app',\n },\n } as Card;\n },\n /** Times to book, from `start` on the hour, one an hour. */\n timeSlots(id: string, start: string, count = 3): Card {\n const at = Date.parse(start);\n return {\n id,\n kind: 'time_slots',\n title: 'Open times',\n slots: Array.from({ length: count }, (_, i) => ({\n id: `slot_${i + 1}`,\n start: new Date(at + i * 3_600_000).toISOString(),\n end: new Date(at + (i + 1) * 3_600_000).toISOString(),\n })),\n actions: [],\n source: { tool: 'get_availability', call_id: 'call_MOCK' },\n } as Card;\n },\n links(id: string): Card {\n return {\n id,\n kind: 'links',\n title: 'Pages that can help',\n links: [\n {\n label: 'Help center',\n url: 'https://help.example.test/',\n purpose: 'help',\n },\n {\n label: 'Contact us',\n url: 'https://help.example.test/contact',\n purpose: 'contact',\n },\n ],\n actions: [],\n source: { tool: 'scope', call_id: 'turn_MOCK' },\n } as Card;\n },\n};\n"]}