@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,3651 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ */
5
+
6
+ interface paths {
7
+ "/v1/sessions": {
8
+ parameters: {
9
+ query?: never;
10
+ header?: never;
11
+ path?: never;
12
+ cookie?: never;
13
+ };
14
+ get?: never;
15
+ put?: never;
16
+ /**
17
+ * Create an agent session for one subject
18
+ * @description A session belongs to a conversation when it names one (`conversation_key`, `conversation_id`, or both) or when its agent runs a guided flow; a flow session that names none starts a new conversation. `Session` then returns its `conversation_id`, and a conversation started without a key can later be resumed by that id. A conversation is scoped to the tenant, the environment, the `subject.id` and the `agent`, and (v1-draft.20) the mode and business scope it started with; `subject.type` itself is not part of it, so a guest who signs in keeps the conversation.
19
+ * **`business_setup`.** The `business_setup` capability is only for a business's own people: a `subject.type` of `professional`, `host` or `staff`. Asked for any other subject, it is `400 invalid_request` with the pointer `/tool_access/capabilities`.
20
+ * **`booking_proposals`.** The `booking_proposals` capability is only for a `client`: not a guest, who has no account in the tenant's app to confirm with. Asked for any other subject, it is `400 invalid_request` with the pointer `/tool_access/capabilities`.
21
+ * **Audience** (v1-draft.20). An agent talks only to the subject types of its template (admin-v1 `AgentProfile.audience`): the customer agent to a `client` or `guest`, a business agent to a `professional`, `host` or `staff`, a guided flow to anyone its tenant names. Any other `subject.type` is `400 invalid_request` with the pointer `/subject/type`. The tenant's backend sets the type by the side of its app the person is on, never by the account's roles: a professional booking for themselves is a `client`.
22
+ * **Mode and business scope** (v1-draft.20). `experience_mode` is the agent's side: `customer` for the customer agent, `business` for a business agent. Left out, it follows the agent's template; given, it must match it (`400 invalid_request`, `/experience_mode`). `business_scope_id` names the business the person acts for (a professional's business, a host's place): only with `business` (`400`, `/business_scope_id`), and the tenant's backend derives it from its own records, never from the app. A conversation is bound, when it starts, to the subject, the agent, the mode and the business scope. The mode stands for the side of the app, so a guest who signs in as a client keeps their conversation, while a professional never resumes a customer conversation (the audience rule keeps each side's types apart). Resuming with any of them different is `404 not_found` (step 1 below), never a switch: a person acting for business A never resumes business B's conversation.
23
+ * **The checks, in this order:** 1. `conversation_id` is given, and is unknown, erased, or belongs to another tenant, environment, `agent` or
24
+ * `subject.id`, or (v1-draft.20) was started with another `experience_mode` or `business_scope_id`: `404
25
+ * not_found`. A `conversation_key` names a conversation within the same binding,
26
+ * so the same key under another binding names another conversation.
27
+ * 2. Both are given, and the key names a different conversation, or none: `409 conversation_mismatch`. 3. The conversation expired: `410 conversation_expired`. With `conversation_key` alone, an expired
28
+ * conversation instead lets a new one start under the key; the id never starts one silently.
29
+ * 4. The conversation is completed (its flow was accepted): `409 conversation_completed`. This holds by key too,
30
+ * so a finished survey is never started twice.
31
+ *
32
+ * **Resuming** is the same however the conversation is named. The new session continues the conversation, and `conversation_id` stays the same. The agent gets the answers recorded so far and a short summary, and re-emits the pending question card under its original `id`. A question card keeps its `id` across the sessions of a conversation, so an answer resent on a later session still names it. `client_event_id`s are deduplicated across all the sessions of the conversation. The older open session of the conversation is replaced (`session.closed replaced`, close `4409`). Each session still has its own short-lived client secret.
33
+ */
34
+ post: operations["createSession"];
35
+ delete?: never;
36
+ options?: never;
37
+ head?: never;
38
+ patch?: never;
39
+ trace?: never;
40
+ };
41
+ "/v1/suggestions": {
42
+ parameters: {
43
+ query?: never;
44
+ header?: never;
45
+ path?: never;
46
+ cookie?: never;
47
+ };
48
+ get?: never;
49
+ put?: never;
50
+ /**
51
+ * Pick, rank and word a few explore suggestions from the candidates the tenant sends
52
+ * @description Synchronous, with a service key that has `sessions:write` (a key without scopes has every scope). Feature `suggestions`, for caps and switches: when it is off or capped, the answer is `feature_unavailable`, as everywhere else.
53
+ * **Time.** The tenant waits at most 1.5 seconds. The runtime answers within 1.2 seconds, with what it has by then, or an empty list. A request whose caller disconnected before the answer is not charged and does not count toward the `suggestions` cap, so the tenant's ledger, which records the `usage` it receives, stays exact.
54
+ * **The split.** The tenant supplies the facts: `candidates` from its own live data, each with a `ref`, a `kind`, a `title` and open `facts`. The agent picks between `min` and `max` of them, ranks them, and writes a short `title` and `why` for each. It may also turn one into a question for the agent (`action: ask`). - **Never an invented target.** `ref` is always one of the request's candidates, and `null` only for `ask`. - **Never a claim beyond the facts.** `why` uses only the facts sent. An unknown `facts` key is ignored. - **No filler.** With fewer than `min` good suggestions, the list is empty. `min` greater than `max` is
55
+ * `400 invalid_request` with the pointer `/max`.
56
+ * - **Language and length.** `title`, `why` and `question` are in `subject.locale`, falling back to the
57
+ * tenant's default locale and then English. Their lengths are counted in Unicode code points; a check in a
58
+ * language whose strings are UTF-16 (.NET, JavaScript) must count code points, not UTF-16 units.
59
+ *
60
+ * **Candidates are untrusted data.** Candidate `title`s and `facts` often come from other people (a host's listing title, a professional's name). They enter the model as quoted data in a delimited block, never as instructions, and the platform floor applies: every `title`, `why` and `question` passes the leak check and the blocked-topic filter before it is returned.
61
+ * **Tool mode.** Only when `candidates` is omitted and `tool_access` is given does the agent read the tenant's `read` tools for candidates. With `candidates` present, the tools are not read, and every `ref` is a candidate's. In tool mode: - `why` uses only the tool results; - a `ref` is `<kind>:<id>`, with the `id` exactly as the tenant's read tool returned it: `professional` from
62
+ * `get_professional` and the search tools, `space` from `get_workspace`, `service` from the catalogue's
63
+ * `service_type`, `class` from a tool that returns classes;
64
+ * - the tenant validates a `ref` by fetching that object again before it shows the suggestion.
65
+ * **The agent's guardrails apply.** The suggestions follow the `scope` and `blocked_topics` of the request's `agent` (default `customer_assistant`). An `agent` the tenant does not have is `400 invalid_request` with the pointer `/agent`. `business_setup` or `booking_proposals` in `tool_access` is refused (`400 invalid_request`, `/tool_access/capabilities`): suggestions never propose, and the subject is a `client` or a `guest`.
66
+ * **Privacy and caching.** `personalized` says whether any suggestion used the subject's own history, from a `facts` key such as `booked_by_subject` or from the tools. It is always `false` for a `guest`. A personalised answer never crosses subjects: its cache key includes the tenant, environment and `subject.id`, and the rest of the key (place, filters, a cell size) is the tenant's choice. A response with `personalized: false` may also be cached across subjects. The decision records are kept for the tenant's `transcript_retention` (at most 30 days; with `none`, not after the answer), and are erased with the subject (`eraseSubject`).
67
+ * **An `ask` suggestion** opens the agent with `question` already typed in the composer. It becomes the person's message only when they send it, and they may edit it first. No session field carries the suggestion.
68
+ */
69
+ post: operations["createSuggestions"];
70
+ delete?: never;
71
+ options?: never;
72
+ head?: never;
73
+ patch?: never;
74
+ trace?: never;
75
+ };
76
+ "/v1/conversations": {
77
+ parameters: {
78
+ query?: never;
79
+ header?: never;
80
+ path?: never;
81
+ cookie?: never;
82
+ };
83
+ /**
84
+ * A person's past conversations with one agent, newest activity first
85
+ * @description v1-draft.21 (VIN-246): the conversations a history screen lists, for one subject and one agent, on one side of the app (`experience_mode`, and for a business `business_scope_id`, as the conversation was bound when it started). Newest activity first; ties by `id`.
86
+ * **Access.** A service key only, never a client secret: the app asks the tenant's backend, which checks the person and calls this. The key needs `conversations:read` (a key without scopes has every scope) and sees its own tenant and environment only.
87
+ * **What is listed.** Only what the tenant's `transcript_retention` still keeps: a conversation past it, a deleted one (`deleteConversation`) or an erased subject's is never listed, nor one where nothing was said (a session opened and left). A subject Vinaverse holds nothing for gets an empty page, the same as one with no conversations, so the answer never says whether a subject exists.
88
+ * **The title** is deterministic and never the person's words: from the conversation's last search intent (`title_source: intent`, for example "Haircut · Los Angeles", in the subject's locale), else the date it started (`title_source: date`). There is no preview of the messages.
89
+ * **Paging.** `next_cursor` is opaque and signed. It is bound to the filters, the key's tenant and environment, and expires after 24 hours: a cursor changed, used with other filters or expired is `400 invalid_request` at `/cursor`, and the caller starts again without it. An outage is `503 unavailable` (retryable), never an empty page: the app does not take it as "no conversations".
90
+ */
91
+ get: operations["listConversations"];
92
+ put?: never;
93
+ post?: never;
94
+ delete?: never;
95
+ options?: never;
96
+ head?: never;
97
+ patch?: never;
98
+ trace?: never;
99
+ };
100
+ "/v1/conversations/{conversation_id}": {
101
+ parameters: {
102
+ query?: never;
103
+ header?: never;
104
+ path?: never;
105
+ cookie?: never;
106
+ };
107
+ /**
108
+ * The state of one conversation, with all its answers
109
+ * @description The same `ConversationState` the conversation webhooks carry, always complete: `answers` and `remarks` are never omitted here, and `sequence` is the same counter the webhooks carry. Use it when a webhook arrives with `answers_omitted: true`, or to check where a person is.
110
+ * **Access.** A service key only, never a client secret, and only for the key's own tenant and environment. The key needs the `conversations:read` scope (a key without scopes has every scope); otherwise `403 forbidden`.
111
+ * **How long the state can be read.** - While the conversation lives. - **72 hours after it ends without acceptance** (`expired`, including one whose last submission
112
+ * `failed`): the final state, answers included, stays readable, so a backend that missed an omitted
113
+ * webhook can still fetch the answers.
114
+ * - **Under `transcript_retention: none`,** the state is deleted on acceptance, and the tenant already holds
115
+ * the accepted submission. The read then answers `410` at once.
116
+ *
117
+ * After that window the read answers `410 conversation_expired`, from a tombstone that keeps no answers (see `docs/conversation-state.md`). An unknown or erased conversation, or one of another tenant or environment, is `404 not_found`.
118
+ */
119
+ get: operations["getConversation"];
120
+ put?: never;
121
+ post?: never;
122
+ /**
123
+ * Delete one of a person's conversations, everywhere
124
+ * @description v1-draft.21 (VIN-246): the person deletes a conversation from their history, through the tenant's backend. A service key with `conversations:write`, for its own tenant and environment; `subject_id` must be the conversation's subject.
125
+ * **What it does, before it answers.** - Ends the conversation's open sessions: their sockets get `session.closed` with `reason: deleted` and
126
+ * close with `4403` (a client that does not know `deleted` reads it as `revoked`). Their status ends as
127
+ * `deleted`.
128
+ * - Blanks every copy of the conversation's messages, cards, screening texts and state (and the guided flow's
129
+ * answers), across both sides of the app, and leaves no title or count behind.
130
+ * - Fences late writes: a turn still running, a webhook retry or a worker job that finishes after the delete
131
+ * records nothing for it (a deletion barrier, kept as long as the longest session could run).
132
+ * - Frees its `conversation_key`: a session with the same key afterwards starts a new conversation.
133
+ * **Answers.** `200` with the number of sessions revoked. An unknown id, another tenant's, environment's or subject's, or one already deleted or erased: `404 not_found`, the same answer for each, so it never says whether the conversation exists. Deleting is not erasing the subject (`eraseSubject`): the person's other conversations and memory stay.
134
+ */
135
+ delete: operations["deleteConversation"];
136
+ options?: never;
137
+ head?: never;
138
+ patch?: never;
139
+ trace?: never;
140
+ };
141
+ "/v1/conversations/{conversation_id}/messages": {
142
+ parameters: {
143
+ query?: never;
144
+ header?: never;
145
+ path?: never;
146
+ cookie?: never;
147
+ };
148
+ /**
149
+ * One past conversation, as one ordered timeline of messages and cards
150
+ * @description v1-draft.21 (VIN-246): what a history screen draws when the person opens a past conversation. A service key with `conversations:read`, for its own tenant and environment; `subject_id` must be the conversation's subject.
151
+ * **The timeline.** One list of messages and the cards their turns drew, in the order they happened, each with an `id` that is stable and unique within the conversation. The first page is the newest; within a page, oldest first; `next_cursor` pages toward older items. A message carries `event_id`, the identifier its live event gave it (as `TranscriptItem.id`: an `assistant` message's `turn_id`, a `person` message's `client_event_id`, a `staff` message's `message_id`), so an app merges what it drew live, or a resumed `session.ready.transcript`, into the fetched history by `role` and `event_id` instead of replacing either. An input the runtime never recorded is not in it, as in the transcript.
152
+ * **Cards from history are read-only.** An app draws their actions disabled: a past slot, price or availability is not checked again, so a tap would act on stale data. The person asks again in the conversation, and a fresh turn draws fresh cards.
153
+ * **What is kept.** Only what the tenant's `transcript_retention` still keeps; a conversation past it is `410 conversation_expired`. An unknown id, another tenant's, environment's or subject's, or a deleted or erased one: `404 not_found`, the same answer for each. A cursor changed, used for another conversation or expired (24 hours) is `400 invalid_request` at `/cursor`. An outage is `503 unavailable`, never an empty timeline.
154
+ */
155
+ get: operations["listConversationMessages"];
156
+ put?: never;
157
+ post?: never;
158
+ delete?: never;
159
+ options?: never;
160
+ head?: never;
161
+ patch?: never;
162
+ trace?: never;
163
+ };
164
+ "/v1/sessions/{session_id}": {
165
+ parameters: {
166
+ query?: never;
167
+ header?: never;
168
+ path?: never;
169
+ cookie?: never;
170
+ };
171
+ get: operations["getSession"];
172
+ put?: never;
173
+ post?: never;
174
+ delete?: never;
175
+ options?: never;
176
+ head?: never;
177
+ patch?: never;
178
+ trace?: never;
179
+ };
180
+ "/v1/sessions/{session_id}/revoke": {
181
+ parameters: {
182
+ query?: never;
183
+ header?: never;
184
+ path?: never;
185
+ cookie?: never;
186
+ };
187
+ get?: never;
188
+ put?: never;
189
+ post: operations["revokeSession"];
190
+ delete?: never;
191
+ options?: never;
192
+ head?: never;
193
+ patch?: never;
194
+ trace?: never;
195
+ };
196
+ "/v1/sessions/{session_id}/client_secret": {
197
+ parameters: {
198
+ query?: never;
199
+ header?: never;
200
+ path?: never;
201
+ cookie?: never;
202
+ };
203
+ get?: never;
204
+ put?: never;
205
+ /**
206
+ * A fresh client secret for an open session
207
+ * @description A service key with the `sessions:write` scope gets a new connect secret for an open session, for an app that missed the connect window or connects from another device (whose connection then replaces the open one, with close code 4409). The previous secret no longer authenticates a new connection or an SSE turn. A socket already open stays open and keeps its session: refreshing never drops the person's live connection. To cut it off, revoke the session (`revokeSession`). An ended session is `409 session_closed`. With an optional `Idempotency-Key` (declared in v1-draft.22), a retry within 24 hours answers the secret first issued for that key instead of a newer one.
208
+ */
209
+ post: operations["refreshClientSecret"];
210
+ delete?: never;
211
+ options?: never;
212
+ head?: never;
213
+ patch?: never;
214
+ trace?: never;
215
+ };
216
+ "/v1/sessions/{session_id}/turns": {
217
+ parameters: {
218
+ query?: never;
219
+ header?: never;
220
+ path?: never;
221
+ cookie?: never;
222
+ };
223
+ get?: never;
224
+ put?: never;
225
+ /**
226
+ * Run one turn and stream its events as SSE (alternative to the WebSocket)
227
+ * @description Browsers and WebViews (v1-draft.18): the preflight (`OPTIONS`) is answered for the tenant's client origins of the session's environment only (admin-v1 `ClientOrigins.effective`), with `Access-Control-Allow-Origin` the request's origin (never `*`), `Vary: Origin`, `Access-Control-Allow-Methods: POST`, `Access-Control-Allow-Headers: Authorization, Content-Type` and `Access-Control-Max-Age: 600`; no credentials mode is needed (the client secret is a header). Any other origin, or a session that does not exist, gets no CORS headers. A request whose `Origin` is not one of them is refused with `403` `forbidden` and runs nothing; a request with no `Origin` (a native app, a server) is not checked.
228
+ */
229
+ post: operations["createTurnSse"];
230
+ delete?: never;
231
+ options?: never;
232
+ head?: never;
233
+ patch?: never;
234
+ trace?: never;
235
+ };
236
+ "/v1/sessions/{session_id}/turns/{turn_id}/actions": {
237
+ parameters: {
238
+ query?: never;
239
+ header?: never;
240
+ path?: never;
241
+ cookie?: never;
242
+ };
243
+ get?: never;
244
+ put?: never;
245
+ /**
246
+ * Send a card action and stream the resulting turn as SSE
247
+ * @description Browsers and WebViews (v1-draft.18), as for `createTurnSse`: the preflight (`OPTIONS`) is answered for the tenant's client origins of the session's environment only (admin-v1 `ClientOrigins.effective`), with `Access-Control-Allow-Origin` the request's origin (never `*`), `Vary: Origin`, `Access-Control-Allow-Methods: POST`, `Access-Control-Allow-Headers: Authorization, Content-Type` and `Access-Control-Max-Age: 600`; no credentials mode is needed (the client secret is a header). Any other origin, or a session that does not exist, gets no CORS headers. A request whose `Origin` is not one of them is refused with `403` `forbidden` and runs nothing; a request with no `Origin` (a native app, a server) is not checked.
248
+ */
249
+ post: operations["createCardActionSse"];
250
+ delete?: never;
251
+ options?: never;
252
+ head?: never;
253
+ patch?: never;
254
+ trace?: never;
255
+ };
256
+ "/v1/moderation/jobs": {
257
+ parameters: {
258
+ query?: never;
259
+ header?: never;
260
+ path?: never;
261
+ cookie?: never;
262
+ };
263
+ get?: never;
264
+ put?: never;
265
+ post: operations["createModerationJob"];
266
+ delete?: never;
267
+ options?: never;
268
+ head?: never;
269
+ patch?: never;
270
+ trace?: never;
271
+ };
272
+ "/v1/moderation/jobs/{job_id}": {
273
+ parameters: {
274
+ query?: never;
275
+ header?: never;
276
+ path?: never;
277
+ cookie?: never;
278
+ };
279
+ get: operations["getModerationJob"];
280
+ put?: never;
281
+ post?: never;
282
+ delete?: never;
283
+ options?: never;
284
+ head?: never;
285
+ patch?: never;
286
+ trace?: never;
287
+ };
288
+ "/v1/moderation/items/{item_id}": {
289
+ parameters: {
290
+ query?: never;
291
+ header?: never;
292
+ path?: never;
293
+ cookie?: never;
294
+ };
295
+ get: operations["getModerationItem"];
296
+ put?: never;
297
+ post?: never;
298
+ delete?: never;
299
+ options?: never;
300
+ head?: never;
301
+ patch?: never;
302
+ trace?: never;
303
+ };
304
+ "/v1/media/avatars/{avatar_file}": {
305
+ parameters: {
306
+ query?: never;
307
+ header?: never;
308
+ path?: never;
309
+ cookie?: never;
310
+ };
311
+ /**
312
+ * An agent's avatar image (v1-draft.19), as `Session.agent.avatar_url` names it
313
+ * @description Public: no credential, since an app draws it in an image view. Only an avatar that passed the screen is served, and only while it is an agent's current or previous avatar of the last 30 days; anything else is `404`. The bytes never change under a name (`Cache-Control: public, max-age=31536000, immutable`), and the response is `image/webp` with `X-Content-Type-Options: nosniff` and a sandboxing `Content-Security-Policy`.
314
+ */
315
+ get: operations["getAgentAvatarImage"];
316
+ put?: never;
317
+ post?: never;
318
+ delete?: never;
319
+ options?: never;
320
+ head?: never;
321
+ patch?: never;
322
+ trace?: never;
323
+ };
324
+ "/v1/media/jobs": {
325
+ parameters: {
326
+ query?: never;
327
+ header?: never;
328
+ path?: never;
329
+ cookie?: never;
330
+ };
331
+ get: operations["listMediaJobs"];
332
+ put?: never;
333
+ /**
334
+ * @description Queue a media job. `moderate` is required; `transcode`, `poster` and `captions` are optional (video only)
335
+ * and write to the tenant's destination under `<prefix>/<job id>/`. Same tenant, same `media.sha256`, same
336
+ * steps and same policy version within 30 days returns the earlier completed job with `200` and
337
+ * `deduplicated: true`, not charged; `force: true` reprocesses. When the expected wait exceeds the tenant's
338
+ * soft limit the job is accepted with `delayed: true` and a later `estimated_completion_at`; past the hard
339
+ * limit the request is refused with `429` and `Retry-After`.
340
+ */
341
+ post: operations["createMediaJob"];
342
+ delete?: never;
343
+ options?: never;
344
+ head?: never;
345
+ patch?: never;
346
+ trace?: never;
347
+ };
348
+ "/v1/media/jobs/{job_id}": {
349
+ parameters: {
350
+ query?: never;
351
+ header?: never;
352
+ path?: never;
353
+ cookie?: never;
354
+ };
355
+ get: operations["getMediaJob"];
356
+ put?: never;
357
+ post?: never;
358
+ delete?: never;
359
+ options?: never;
360
+ head?: never;
361
+ patch?: never;
362
+ trace?: never;
363
+ };
364
+ "/v1/media/jobs/{job_id}/cancel": {
365
+ parameters: {
366
+ query?: never;
367
+ header?: never;
368
+ path?: never;
369
+ cookie?: never;
370
+ };
371
+ get?: never;
372
+ put?: never;
373
+ /**
374
+ * @description Stops the job at the next step boundary (within seconds) and deletes outputs already written. Idempotent:
375
+ * cancelling an ended job returns it unchanged.
376
+ */
377
+ post: operations["cancelMediaJob"];
378
+ delete?: never;
379
+ options?: never;
380
+ head?: never;
381
+ patch?: never;
382
+ trace?: never;
383
+ };
384
+ "/v1/media/profiles": {
385
+ parameters: {
386
+ query?: never;
387
+ header?: never;
388
+ path?: never;
389
+ cookie?: never;
390
+ };
391
+ get: operations["listMediaProfiles"];
392
+ put?: never;
393
+ post?: never;
394
+ delete?: never;
395
+ options?: never;
396
+ head?: never;
397
+ patch?: never;
398
+ trace?: never;
399
+ };
400
+ "/v1/screening/text": {
401
+ parameters: {
402
+ query?: never;
403
+ header?: never;
404
+ path?: never;
405
+ cookie?: never;
406
+ };
407
+ get?: never;
408
+ put?: never;
409
+ post: operations["screenText"];
410
+ delete?: never;
411
+ options?: never;
412
+ head?: never;
413
+ patch?: never;
414
+ trace?: never;
415
+ };
416
+ "/v1/document-checks": {
417
+ parameters: {
418
+ query?: never;
419
+ header?: never;
420
+ path?: never;
421
+ cookie?: never;
422
+ };
423
+ get?: never;
424
+ put?: never;
425
+ post: operations["createDocumentCheck"];
426
+ delete?: never;
427
+ options?: never;
428
+ head?: never;
429
+ patch?: never;
430
+ trace?: never;
431
+ };
432
+ "/v1/document-checks/{check_id}": {
433
+ parameters: {
434
+ query?: never;
435
+ header?: never;
436
+ path?: never;
437
+ cookie?: never;
438
+ };
439
+ /** @description Fields are present only within 24 hours of completion; afterwards only flags remain. */
440
+ get: operations["getDocumentCheck"];
441
+ put?: never;
442
+ post?: never;
443
+ delete?: never;
444
+ options?: never;
445
+ head?: never;
446
+ patch?: never;
447
+ trace?: never;
448
+ };
449
+ "/v1/feedback": {
450
+ parameters: {
451
+ query?: never;
452
+ header?: never;
453
+ path?: never;
454
+ cookie?: never;
455
+ };
456
+ get?: never;
457
+ put?: never;
458
+ post: operations["sendFeedback"];
459
+ delete?: never;
460
+ options?: never;
461
+ head?: never;
462
+ patch?: never;
463
+ trace?: never;
464
+ };
465
+ "/v1/subjects/{subject_id}/erase": {
466
+ parameters: {
467
+ query?: never;
468
+ header?: never;
469
+ path?: never;
470
+ cookie?: never;
471
+ };
472
+ get?: never;
473
+ put?: never;
474
+ /**
475
+ * @description Erases the subject's data: transcripts, screening texts, memory, indexes, the conversation state of its guided flows (recorded answers, remarks, transcript), and the explore suggestion decisions made for it, and revokes its open sessions. `subject.erased` follows.
476
+ * **A subject Vinaverse holds nothing for** (never seen, or already erased) is `404` with `code: not_found` and `type: https://docs.vinaverse.com/problems/subject-not-found`; a client matches on the `type` ending in `/problems/subject-not-found`. Only that answer means nothing is held, and nothing needs erasing. Vinaverse keeps no record of erased subjects to tell the two apart. Any other `404` (a wrong base URL, say) is not that answer, and the caller retries.
477
+ */
478
+ post: operations["eraseSubject"];
479
+ delete?: never;
480
+ options?: never;
481
+ head?: never;
482
+ patch?: never;
483
+ trace?: never;
484
+ };
485
+ "/v1/events": {
486
+ parameters: {
487
+ query?: never;
488
+ header?: never;
489
+ path?: never;
490
+ cookie?: never;
491
+ };
492
+ get?: never;
493
+ put?: never;
494
+ /** @description Contract §13.1. Each event must validate against its type's JSON Schema in the event catalogue (`contracts/events/beauty_marketplace@1/`); one invalid event rejects the call. */
495
+ post: operations["sendEvents"];
496
+ delete?: never;
497
+ options?: never;
498
+ head?: never;
499
+ patch?: never;
500
+ trace?: never;
501
+ };
502
+ "/v1/knowledge/documents": {
503
+ parameters: {
504
+ query?: never;
505
+ header?: never;
506
+ path?: never;
507
+ cookie?: never;
508
+ };
509
+ get?: never;
510
+ put?: never;
511
+ post: operations["sendKnowledgeDocument"];
512
+ delete?: never;
513
+ options?: never;
514
+ head?: never;
515
+ patch?: never;
516
+ trace?: never;
517
+ };
518
+ "/v1/catalog/{type}/{id}": {
519
+ parameters: {
520
+ query?: never;
521
+ header?: never;
522
+ path?: never;
523
+ cookie?: never;
524
+ };
525
+ get?: never;
526
+ put: operations["putCatalogEntity"];
527
+ post?: never;
528
+ delete?: never;
529
+ options?: never;
530
+ head?: never;
531
+ patch?: never;
532
+ trace?: never;
533
+ };
534
+ "/v1/usage": {
535
+ parameters: {
536
+ query?: never;
537
+ header?: never;
538
+ path?: never;
539
+ cookie?: never;
540
+ };
541
+ get: operations["getUsage"];
542
+ put?: never;
543
+ post?: never;
544
+ delete?: never;
545
+ options?: never;
546
+ head?: never;
547
+ patch?: never;
548
+ trace?: never;
549
+ };
550
+ }
551
+ interface components {
552
+ schemas: {
553
+ Problem: {
554
+ /** Format: uri */
555
+ type: string;
556
+ title: string;
557
+ status: number;
558
+ /** @enum {string} */
559
+ code: "invalid_request" | "unauthenticated" | "forbidden" | "feature_unavailable" | "not_found" | "idempotency_conflict" | "session_closed" | "payload_too_large" | "unprocessable_media" | "rate_limited" | "internal" | "unavailable" | "conversation_mismatch" | "conversation_expired" | "conversation_completed";
560
+ detail?: string;
561
+ request_id: string;
562
+ retryable: boolean;
563
+ /** @description field-level problems for invalid_request */
564
+ errors?: {
565
+ /** @description JSON Pointer into the request */
566
+ pointer: string;
567
+ message: string;
568
+ }[];
569
+ };
570
+ Money: {
571
+ /** @description minor units */
572
+ amount: number;
573
+ currency: string;
574
+ };
575
+ MicroMoney: {
576
+ /** @description millionths of the currency unit */
577
+ amount_micros: number;
578
+ currency: string;
579
+ };
580
+ SubjectId: string;
581
+ /** @enum {string} */
582
+ SubjectType: "client" | "professional" | "host" | "guest" | "staff";
583
+ Subject: {
584
+ id: components["schemas"]["SubjectId"];
585
+ type: components["schemas"]["SubjectType"];
586
+ /**
587
+ * @example en-US
588
+ * @example es-US
589
+ * @example pt-BR
590
+ */
591
+ locale?: string;
592
+ /** @example America/New_York */
593
+ timezone?: string;
594
+ approximate_location?: {
595
+ lat: number;
596
+ lng: number;
597
+ /** @enum {string} */
598
+ precision: "city" | "neighborhood";
599
+ };
600
+ };
601
+ /** @enum {string} */
602
+ Feature: "assistant" | "moderation_image" | "moderation_video" | "moderation_text" | "screening" | "document_check" | "insights" | "suggestions";
603
+ /**
604
+ * @description `business_setup`: grants the business's own read tools and its `propose` tools (`tool-provider-v1.yaml`, `x-effect: propose`), for the `business_setup` agent; never a write tool. Only a subject of type `professional`, `host` or `staff` may be granted it. `booking_proposals`: grants the tenant's `propose` tools for a client's bookings (a booking, a reschedule, a cancellation the client confirms in the tenant's app), for the customer agent; never a write tool. Only a subject of type `client` may be granted it (`docs/agents-guardrails-suggestions-writes.md` §3.4). These two are the only capabilities that carry `propose` tools: `create_proposal` and every tenant `propose` tool require one of them. `flows`: grants the guided-flow endpoints (`tool-provider-v1.yaml`, `/flows/*`). It names no flow and no record: the tenant limits the `tool_access` token itself to one flow and one record. `my_orders`: grants reading the customer's own kits and orders from a styling-kit retailer (`tool-provider-v1.yaml`, `list_my_kits`, `get_my_kit`, `list_my_orders`, `get_my_order`), read only. Like `my_bookings`, it is meant for a signed-in `client`, and the tenant answers for the token's customer only.
605
+ * @enum {string}
606
+ */
607
+ Capability: "search" | "availability" | "quotes" | "my_bookings" | "policies" | "handoff" | "flows" | "business_setup" | "booking_proposals" | "my_orders";
608
+ ToolAccess: {
609
+ token: string;
610
+ /** Format: date-time */
611
+ expires_at: string;
612
+ capabilities: components["schemas"]["Capability"][];
613
+ };
614
+ /** @description `candidates`, or `tool_access` alone (tool mode). With `candidates`, the tools are not read. */
615
+ SuggestionsRequest: {
616
+ /** @enum {string} */
617
+ surface: "explore";
618
+ /** @description a `client` or a `guest`; a guest has no history */
619
+ subject: components["schemas"]["Subject"] & {
620
+ /** @enum {unknown} */
621
+ type?: "client" | "guest";
622
+ };
623
+ /**
624
+ * @description whose guardrails apply
625
+ * @default customer_assistant
626
+ * @example customer_assistant
627
+ */
628
+ agent: string;
629
+ context?: {
630
+ place?: {
631
+ /** @example Midtown, Atlanta */
632
+ label?: string;
633
+ lat: number;
634
+ lng: number;
635
+ };
636
+ /**
637
+ * Format: date-time
638
+ * @description the person's local time, with its offset
639
+ */
640
+ local_time?: string;
641
+ /** @description the filters the person has on, by name */
642
+ filters?: {
643
+ [key: string]: string;
644
+ };
645
+ };
646
+ candidates?: components["schemas"]["SuggestionCandidate"][];
647
+ /**
648
+ * @description fewer good suggestions than this is an empty list
649
+ * @default 3
650
+ */
651
+ min: number;
652
+ /**
653
+ * @description at least `min`
654
+ * @default 5
655
+ */
656
+ max: number;
657
+ /** @description Optional. Without `candidates`, the agent reads the tenant's `read` tools with it (tool mode). */
658
+ tool_access?: components["schemas"]["ToolAccess"];
659
+ } | unknown | unknown;
660
+ SuggestionCandidate: {
661
+ /**
662
+ * @example pro:66f1a2
663
+ * @example search:service:silk press
664
+ */
665
+ ref: string;
666
+ /**
667
+ * @description what `ref` names
668
+ * @enum {string}
669
+ */
670
+ kind: "professional" | "space" | "class" | "service" | "search";
671
+ title: string;
672
+ /** @description Open: what the tenant knows about the candidate (price, rating, distance, next open time, whether the subject booked it before…). A key the agent does not know is ignored. `why` uses only these facts. */
673
+ facts?: {
674
+ [key: string]: unknown;
675
+ };
676
+ };
677
+ SuggestionsResponse: {
678
+ decision_id: string;
679
+ /** @description between `min` and `max`, or empty */
680
+ suggestions: components["schemas"]["Suggestion"][];
681
+ /** @description true when any suggestion used the subject's own history; such a response is cached only per tenant, environment and `subject.id`. Always false for a guest. */
682
+ personalized: boolean;
683
+ usage: components["schemas"]["SuggestionsUsage"];
684
+ };
685
+ Suggestion: {
686
+ /** @description one of the request's candidates (in tool mode, `<kind>:<id>` from a tool result); `null` only for `ask` */
687
+ ref: string | null;
688
+ /**
689
+ * @description `open`: open the candidate. `search`: run its search. `ask`: open the agent with `question` in the composer, unsent.
690
+ * @enum {string}
691
+ */
692
+ action: "open" | "search" | "ask";
693
+ /** @description in Unicode code points */
694
+ title: string;
695
+ /** @description a plain reason taken from the facts sent; in Unicode code points */
696
+ why: string;
697
+ /** @description `ask` only; in Unicode code points */
698
+ question?: string;
699
+ } & unknown;
700
+ SuggestionsUsage: {
701
+ /** @constant */
702
+ unit: "request";
703
+ quantity: number;
704
+ cost: components["schemas"]["MicroMoney"];
705
+ };
706
+ SessionCreate: {
707
+ /** @example customer_assistant */
708
+ agent: string;
709
+ subject: components["schemas"]["Subject"];
710
+ tool_access: components["schemas"]["ToolAccess"];
711
+ limits?: {
712
+ max_turns?: number;
713
+ max_duration_seconds?: number;
714
+ };
715
+ context?: {
716
+ [key: string]: string;
717
+ };
718
+ /**
719
+ * @description Optional. An opaque key the tenant chooses: printable ASCII, no spaces, at most 128 characters. It names a conversation within the tenant, the environment, the `subject.id` and the `agent`. A new session with the same key resumes that conversation; `createSession` describes resuming and its checks, the same whether a conversation is named by key or by `conversation_id`.
720
+ * One session per conversation is open at a time. Creating a new one closes the older open session: its socket gets `session.closed` with reason `replaced` and close code `4409`, and the session ends as `revoked` (so in `SessionStatus` a replacement looks like a revoke). When two creations for the same conversation race, the later one wins. Conversation state lasts as long as the tenant's `transcript_retention`, counted from the conversation's creation, and never beyond the tenant's `conversation_max_days` (at most 30) from it, however often it is resumed. The key never reaches a model prompt, and logs keep only a hash of it.
721
+ */
722
+ conversation_key?: string;
723
+ /** @description Optional. Resumes the conversation with this id (`Session.conversation_id`, `ConversationState`), as an alternative to `conversation_key` or together with it; `createSession` describes resuming and the order of its checks. */
724
+ conversation_id?: components["schemas"]["ConversationId"];
725
+ experience_mode?: components["schemas"]["ExperienceMode"];
726
+ business_scope_id?: components["schemas"]["BusinessScopeId"];
727
+ screen_context?: components["schemas"]["ScreenContext"];
728
+ };
729
+ /**
730
+ * @description v1-draft.20, optional on `createSession`: the side of the tenant's app the person is on. `customer` for the customer agent (a `client` or `guest`), `business` for a business agent (a `professional`, `host` or `staff`). Left out, it follows the agent's template; `createSession` describes the binding.
731
+ * @enum {string}
732
+ */
733
+ ExperienceMode: "customer" | "business";
734
+ /** @description v1-draft.20, optional, with `experience_mode: business` only: the business the person acts for, as the tenant's backend knows it (a professional's business, a host's place). Opaque to Vinaverse; it binds the conversation, so a person acting for two businesses keeps two conversations. It never reaches a model prompt. */
735
+ BusinessScopeId: string;
736
+ /** @description v1-draft.20, optional on `createSession`: where the person is in the tenant's app when the session opens, as the tenant's backend vouches for it (it checked that the person may see the entity). It grants nothing: no tool, capability or record. The agent reads it as data in its working notes (never in the cached system prompt, never as something the person said), as a hint: before it states anything about the entity it reads it with a tool, and a tool that refuses means the agent says it cannot see it. A screen change while the chat is open does not open a new session; live updates are a later addition. Never arbitrary page text, URLs, names, amounts or instructions: only these fields, from this vocabulary. */
737
+ ScreenContext: {
738
+ /**
739
+ * @description A coarse screen from a fixed vocabulary; the tenant maps its own screens onto it. `other` when none fits. Customer side: `home`, `explore`, `search_results`, `professional`, `workspace`, `service`, `class`, `checkout`, `booking`, `bookings`, `inbox`, `profile`. Business side: `business_hub`, `setup`, `services`, `hours`, `location`, `gallery`, `spaces`, `space_manage`, `renters`, `payouts`, `plans`, `team`.
740
+ * @enum {string}
741
+ */
742
+ screen: "home" | "explore" | "search_results" | "professional" | "workspace" | "service" | "class" | "checkout" | "booking" | "bookings" | "inbox" | "profile" | "business_hub" | "setup" | "services" | "hours" | "location" | "gallery" | "spaces" | "space_manage" | "renters" | "payouts" | "plans" | "team" | "other";
743
+ /** @description the object on the screen, as the tenant's read tools name it; `id` is a tool id (letters, digits and `_`, `.`, `:`, `-`), since it reaches the agent's working notes */
744
+ entity?: {
745
+ /** @enum {string} */
746
+ type: "professional" | "workspace" | "service" | "class" | "booking" | "location";
747
+ id: string;
748
+ };
749
+ /** @description the filters the screen shows, typed; free text never */
750
+ filters?: {
751
+ service_ids?: string[];
752
+ category_ids?: string[];
753
+ area_id?: string;
754
+ /** Format: date */
755
+ date_from?: string;
756
+ /** Format: date */
757
+ date_to?: string;
758
+ price_max?: components["schemas"]["Money"];
759
+ };
760
+ /** @description the tenant's counter for this person's screen state; a later update never goes back to a lower one */
761
+ revision: number;
762
+ };
763
+ /** @description v1-draft.20: a tap target the app draws under the welcome. Tapping it sends `message` as the person's own text (an `input.text`, or `createTurnSse`), exactly as if they typed it; it starts nothing else. */
764
+ Chip: {
765
+ /** @description in Unicode code points */
766
+ label: string;
767
+ /** @description in Unicode code points */
768
+ message: string;
769
+ };
770
+ /** @description v1-draft.20: the tenant's greeting for the session's mode, with the chips of the session's screen when its manifest has some for it (else the mode's own), from the connector manifest (admin-v1 `Connector.welcome`), checked when the manifest loads. The app shows it the moment the session is ready: it costs no model call and is not a turn. The agent knows it was shown and does not greet again. */
771
+ Welcome: {
772
+ /** @description in Unicode code points */
773
+ text: string;
774
+ chips: components["schemas"]["Chip"][];
775
+ };
776
+ ClientSecret: {
777
+ /** @description `vv_cs_` and base64url characters, so the secret is a valid WebSocket subprotocol token in `bearer.<secret>`. */
778
+ value: string;
779
+ /**
780
+ * Format: date-time
781
+ * @description The end of the connect window (120 s after issue): the secret must open a connection (over SSE: run its first turn) by then. Once used, it is bound to the session until the session ends or a new secret is issued (`refreshClientSecret`), so the app reconnects with the same secret (and sends it on every SSE turn) without asking for a new one.
782
+ */
783
+ expires_at: string;
784
+ };
785
+ Session: {
786
+ id: string;
787
+ /** @enum {string} */
788
+ status: "open";
789
+ agent: {
790
+ id: string;
791
+ version: string;
792
+ display_name: string;
793
+ /**
794
+ * Format: uri
795
+ * @description v1-draft.19, optional: the agent's avatar (`getAgentAvatarImage`), a square 512 px WebP with no metadata, re-encoded by Vinaverse from the tenant's upload and passed by its image screen. The URL is immutable (it changes when the image does), so an app may cache the image for as long as it likes; it needs no credential. Absent: no avatar yet, so draw the name's monogram.
796
+ */
797
+ avatar_url?: string;
798
+ };
799
+ client_secret: components["schemas"]["ClientSecret"];
800
+ /** Format: uri */
801
+ realtime_url: string;
802
+ /** Format: uri */
803
+ turns_url: string;
804
+ /**
805
+ * Format: date-time
806
+ * @description When the session ends: the earliest of the session's own `limits.max_duration_seconds` (when `createSession` sets it), the agent's and the environment's longest session, and `tool_access.expires_at`. A short-lived tool token shortens the session. On the WebSocket, `session.expiring` comes 60 s before, then `session.closed` with `expired`.
807
+ */
808
+ expires_at: string;
809
+ disclosure: string;
810
+ /** @description The conversation this session belongs to, the same for every session of it, however it was resumed (by `conversation_key` or `conversation_id`). Present whenever the session belongs to a conversation: when the request named one, and for every guided-flow session. It is absent only for a session with no conversation, and optional in the schema so clients built on earlier drafts keep working. */
811
+ conversation_id?: components["schemas"]["ConversationId"];
812
+ /** @description v1-draft.20, the session's mode, as given or as the agent's template sets it */
813
+ experience_mode?: components["schemas"]["ExperienceMode"];
814
+ /** @description v1-draft.20, present when the session acts for a business */
815
+ business_scope_id?: components["schemas"]["BusinessScopeId"];
816
+ /** @description v1-draft.20, the screen context the session accepted, when `createSession` gave one */
817
+ screen_context?: components["schemas"]["ScreenContext"];
818
+ /** @description v1-draft.20, absent when the tenant's manifest has no welcome for the session's mode */
819
+ welcome?: components["schemas"]["Welcome"];
820
+ };
821
+ SessionStatus: {
822
+ id: string;
823
+ /** @enum {string} */
824
+ status: "open" | "closed" | "revoked" | "expired";
825
+ subject_id: components["schemas"]["SubjectId"];
826
+ agent_version?: string;
827
+ turns: number;
828
+ handoff_ticket_ids?: string[];
829
+ /**
830
+ * @description Why the session ended. `server_restart` is reserved and never appears here: it never ends a session (`docs/realtime-protocol.md` §2). `deleted` (v1-draft.21): its conversation was deleted from history.
831
+ * @enum {string}
832
+ */
833
+ end_reason?: "expired" | "revoked" | "limit_reached" | "idle" | "server_restart" | "user_left" | "deleted";
834
+ /** Format: date-time */
835
+ created_at: string;
836
+ /** Format: date-time */
837
+ closed_at?: string;
838
+ usage?: components["schemas"]["Usage"];
839
+ };
840
+ TurnInput: {
841
+ client_event_id?: string;
842
+ text: string;
843
+ };
844
+ Usage: {
845
+ /** @enum {string} */
846
+ unit: "turn" | "image" | "video_second" | "text_item" | "screening_call" | "document_page";
847
+ quantity: number;
848
+ input_tokens?: number;
849
+ output_tokens?: number;
850
+ cost: components["schemas"]["MicroMoney"];
851
+ };
852
+ UsageReport: {
853
+ period: string;
854
+ features: {
855
+ feature: components["schemas"]["Feature"];
856
+ quantity: number;
857
+ cost: components["schemas"]["MicroMoney"];
858
+ /** @description null when the feature has no monthly cap */
859
+ cap: components["schemas"]["MicroMoney"] | null;
860
+ /** @enum {string} */
861
+ state: "enabled" | "disabled";
862
+ }[];
863
+ };
864
+ CardRef: {
865
+ /** @enum {string} */
866
+ type: "professional" | "workspace" | "service" | "booking" | "quote" | "policy" | "slot_list" | "ticket" | "metric_series" | "metric_table" | "gap_list" | "intent_cluster" | "memory_claim" | "knowledge_snippet";
867
+ id: string;
868
+ };
869
+ CardAction: {
870
+ /**
871
+ * @description `confirmed`, `declined`, `failed` and `expired` are reserved for proposal outcomes, never a card action
872
+ * @example open
873
+ * @example show_times
874
+ * @example show_more
875
+ * @example get_quote
876
+ * @example contact_support
877
+ */
878
+ action: string;
879
+ label: string;
880
+ deep_link?: string;
881
+ };
882
+ /** @description v1-draft.20: one photo of a `professional` or `workspace` card. Https only, from a prefix the tenant registered (admin-v1 `Connector.image_url_prefixes`), compared after the URL is parsed and normalized (no user info, backslash, fragment or dot segment): an image that fails is dropped, never the card. The tenant serves only photos its own moderation approved, at URLs that stay valid as long as the transcript (a card in history draws them again). An image that fails to load draws the name's monogram; the card stays. */
883
+ CardImage: {
884
+ /** Format: uri */
885
+ url: string;
886
+ width?: number;
887
+ height?: number;
888
+ };
889
+ /** @description v1-draft.20: the cards one turn draws together carry the same `id`. `layout` is a hint: `carousel` draws them in one horizontal row, `list` one under another. A client that does not know `group`, or a `layout` value, draws a list, as before. */
890
+ CardGroup: {
891
+ id: string;
892
+ /** @enum {string} */
893
+ layout: "list" | "carousel";
894
+ };
895
+ /** @description v1-draft.20, one link of a `links` card: one of the tenant's own destinations from its connector manifest (admin-v1 `Connector.help_links`), never a link the model wrote. */
896
+ CardLink: {
897
+ /** @description in Unicode code points */
898
+ label: string;
899
+ /** @description an https page of the tenant's, or a deep link that passes the deep-link rule (`next_step`) */
900
+ url: string;
901
+ /** @enum {string} */
902
+ purpose: "help" | "support" | "policy" | "contact" | "feedback";
903
+ };
904
+ /** @description v1-draft.20, on a `links` card: the tenant's scope policy, at the version in force, that kept the agent from answering, and why. */
905
+ ScopeProvenance: {
906
+ policy_id: string;
907
+ version: string;
908
+ /** @enum {string} */
909
+ reason: "off_scope" | "competitor" | "blocked_topic";
910
+ };
911
+ /**
912
+ * @description `ref` is present on every customer-agent card (professional … handoff); copilot cards (metric_chart … document) may omit it, since a metric, cluster or claim is not a tenant object. A `question` card (guided flows) carries `question`, has no `actions` (the person answers with `ui.answer`), uses `title` as a short label for the question, and its `source` names the tool call that fetched the flow's definition.
913
+ * A `next_step` card is a step the person finishes on the tenant's own screen (payouts, a plan purchase, photos). It has a `title` (at most 80 code points), a `body` (at most 300) and exactly one action: `action: open`, with a `label` (at most 40) and a `deep_link`. It may omit `ref`, and its `source` names the tool call the step came from. - **Nothing is written.** Tapping the action opens `deep_link` in the tenant's app. The app sends no
914
+ * `ui.action`, nor `createCardActionSse`, for it.
915
+ * - **The link passes the deep-link rule** (`admin-v1.yaml`, `Connector.deep_link_prefixes`). It must be a
916
+ * whole string value in the result of the tenant tool call that `source` names (`source.call_id`, the
917
+ * tenant's `get_setup_state` for example), in the same session. A substring, or any other tool's result,
918
+ * does not count, and the model never composes a link. It must start with a prefix the tenant registered
919
+ * for the session's environment, compared code point for code point. And what follows the prefix must be
920
+ * on an allowlist: path segments of `A`–`Z`, `a`–`z`, `0`–`9`, `.`, `_`, `~` and `-`, each with a character
921
+ * that is not `.`, then optionally a query of those characters, `=`, `&` and `%20`–`%7E` escapes other
922
+ * than `%25`, `%2F`, `%3A` and `%5C`. No `%` in the path, no fragment, nothing else. Query values are
923
+ * data: the app never navigates to one.
924
+ * - **A card whose link fails is dropped.** The model is told the step could not be shown, so the agent
925
+ * does not point to a card, and the connector is flagged in Studio (`Connector.health`).
926
+ * - **The app also checks** the page the link names on its own side, and drops a link to a page it does not
927
+ * know.
928
+ * - **It is not restored.** A `next_step` card is in neither `pending_cards` nor `transcript`; after a
929
+ * reconnect the agent shows the step again once it reads the tenant's state.
930
+ *
931
+ * **Listing cards** (v1-draft.20). A `professional` or `workspace` card may carry `images` (up to five, the first the hero; `image_url`, when present, is the first one's URL, for clients that predate `images`), `next_available` (the earliest open time the tenant's tool returned; a hint, so a tap on its time reads availability again), `price_from` and a `group`. An app draws one hero image first and the rest on a swipe.
932
+ * **A `links` card** (v1-draft.20) answers what the tenant's scope keeps the agent from answering: a question off its scope, a comparison with a competitor, a blocked topic. Its `links` are the tenant's own help, support, policy, contact or feedback destinations (`CardLink`), its `provenance` names the scope policy and the reason, and it has no `actions`: each link opens the tenant's page. It belongs to a recorded turn, with the agent's one line around it, never to `input.rejected`. Its `source.tool` is `scope` and its `source.call_id` the turn's id: no tool call made it. An earlier client skips the unknown kind, as it skips any.
933
+ */
934
+ Card: {
935
+ id: string;
936
+ /**
937
+ * @description customer-agent kinds, then business-copilot kinds (Studio), then `question` (guided flows), then `proposal` (a change the person confirms or declines in the tenant's app), then `next_step` (a step the person finishes on the tenant's own screen), then `links` (v1-draft.20: the tenant's own pages, when its scope keeps the agent from answering)
938
+ * @enum {string}
939
+ */
940
+ kind: "professional" | "workspace" | "service" | "time_slots" | "booking" | "quote" | "policy" | "handoff" | "metric_chart" | "metric_table" | "gap_list" | "cluster" | "claim" | "document" | "question" | "proposal" | "next_step" | "links";
941
+ question?: components["schemas"]["Question"];
942
+ proposal?: components["schemas"]["Proposal"];
943
+ ref?: components["schemas"]["CardRef"];
944
+ title: string;
945
+ subtitle?: string;
946
+ /** Format: uri */
947
+ image_url?: string;
948
+ /** @description v1-draft.20, a `professional` or `workspace` card's photos, the first the hero */
949
+ images?: components["schemas"]["CardImage"][];
950
+ /**
951
+ * Format: date-time
952
+ * @description v1-draft.20, a `professional` or `workspace` card's earliest open time, as the tenant's tool returned it
953
+ */
954
+ next_available?: string;
955
+ group?: components["schemas"]["CardGroup"];
956
+ /** @description v1-draft.20, a `links` card's links */
957
+ links?: components["schemas"]["CardLink"][];
958
+ provenance?: components["schemas"]["ScopeProvenance"];
959
+ rating?: {
960
+ value_ppm: number;
961
+ count: number;
962
+ };
963
+ price_from?: components["schemas"]["Money"];
964
+ price?: components["schemas"]["Money"];
965
+ /** Format: date-time */
966
+ starts_at?: string;
967
+ /** @description A `time_slots` card's open times, as the tenant's tool returned them: each with its `start` and `end` (its length), and the card's `subtitle` names the professional or space when the tool result does. Each has an `id` (v1-draft.17): the app may draw the slots as choices, and a tap sends `ui.action` `select_slot` with that `id` and the card's `source.call_id` (`SlotSelection`), over SSE through `createCardActionSse`. A slot without an `id` (an earlier runtime) is drawn read-only. */
968
+ slots?: {
969
+ /** @description Opaque, unique within the card, the same for the same slot of the same tool result. Never parse it. */
970
+ id?: string;
971
+ /** Format: date-time */
972
+ start: string;
973
+ /** Format: date-time */
974
+ end: string;
975
+ }[];
976
+ status?: string;
977
+ /** @description policy excerpt, handoff text, or what a `next_step` is for */
978
+ body?: string;
979
+ badges?: string[];
980
+ actions: components["schemas"]["CardAction"][];
981
+ source: {
982
+ tool: string;
983
+ call_id: string;
984
+ /** @description optional; the tool arguments that produced this card (shown by Studio as the source query) */
985
+ arguments?: Record<string, unknown>;
986
+ };
987
+ metric?: components["schemas"]["MetricRef"];
988
+ /** @enum {string} */
989
+ grain?: "day" | "week";
990
+ period?: {
991
+ /** Format: date */
992
+ from: string;
993
+ /** Format: date */
994
+ to: string;
995
+ };
996
+ series?: {
997
+ key: string;
998
+ label: string;
999
+ points: {
1000
+ /** Format: date */
1001
+ t: string;
1002
+ /** @description null when suppressed by the k-anonymity floor; on a metric without k_floor, nothing to measure */
1003
+ value: number | null;
1004
+ suppressed?: boolean;
1005
+ }[];
1006
+ }[];
1007
+ dimensions?: {
1008
+ key: string;
1009
+ label: string;
1010
+ }[];
1011
+ columns?: {
1012
+ key: string;
1013
+ label: string;
1014
+ unit: components["schemas"]["ValueUnit"];
1015
+ }[];
1016
+ rows?: {
1017
+ dims: {
1018
+ [key: string]: string;
1019
+ };
1020
+ /** @description null when suppressed by the metric's k_floor; on a metric without k_floor, nothing to measure */
1021
+ values: {
1022
+ [key: string]: number | null;
1023
+ };
1024
+ }[];
1025
+ /** @description gap_list entries */
1026
+ items?: {
1027
+ service: string;
1028
+ area: string;
1029
+ demand: number;
1030
+ supply: number;
1031
+ zero_result_share_ppm: number;
1032
+ trend_ppm?: number | null;
1033
+ }[];
1034
+ label?: string;
1035
+ count?: number;
1036
+ share_ppm?: number;
1037
+ /** @enum {string} */
1038
+ outcome?: "zero_results" | "withheld" | "handoff" | "mixed";
1039
+ /** @description redacted */
1040
+ examples?: string[];
1041
+ claim?: {
1042
+ id: string;
1043
+ statement: string;
1044
+ /** @enum {string} */
1045
+ status: "candidate" | "confirmed" | "rejected";
1046
+ evidence: {
1047
+ label: string;
1048
+ call_id: string;
1049
+ }[];
1050
+ /** Format: date-time */
1051
+ proposed_at: string;
1052
+ decided_by?: string | null;
1053
+ /** Format: date-time */
1054
+ decided_at?: string | null;
1055
+ };
1056
+ document?: {
1057
+ title: string;
1058
+ section?: string;
1059
+ url?: string;
1060
+ version?: string;
1061
+ };
1062
+ snippet?: string;
1063
+ /** Format: date-time */
1064
+ updated_at?: string;
1065
+ } & (unknown & unknown & unknown & unknown);
1066
+ /** @enum {string} */
1067
+ ValueUnit: "count" | "ppm" | "micros_usd" | "ms";
1068
+ MetricRef: {
1069
+ name: string;
1070
+ label: string;
1071
+ unit: components["schemas"]["ValueUnit"];
1072
+ definition: string;
1073
+ /** @description The privacy floor: a value built from fewer than this many distinct people is suppressed (`null`). Absent when no floor applies, as on a business's own operational data (Studio's pages, read by the business copilot); a `null` value there means there was nothing to measure, never a hidden count. */
1074
+ k_floor?: number;
1075
+ };
1076
+ /** @description Links a span of the reply text to the card or tool call it came from. Offsets are in Unicode code points (not bytes, not UTF-16 units), start inclusive, end exclusive. */
1077
+ Citation: {
1078
+ start: number;
1079
+ end: number;
1080
+ card_id?: string;
1081
+ call_id?: string;
1082
+ };
1083
+ /** @description The tenant's stable ref for a question (or a `rate_each` row), echoed in answers. */
1084
+ QuestionId: string;
1085
+ /** @enum {string} */
1086
+ QuestionKind: "single_choice" | "multi_choice" | "image_choice" | "yes_no" | "list_choice" | "rate_each" | "short_text" | "long_text" | "date" | "number" | "scale" | "photo";
1087
+ QuestionOption: {
1088
+ /** @description The tenant's stable ref for this choice. Answers carry it, never the label. */
1089
+ value: string;
1090
+ label: string;
1091
+ /**
1092
+ * Format: uri
1093
+ * @description on the tenant's CDN; required for `image_choice`
1094
+ */
1095
+ image_url?: string;
1096
+ description?: string;
1097
+ };
1098
+ /**
1099
+ * @description One question of a guided flow, as the app renders it natively. The tenant's definition supplies every field except `prompt` (the agent's phrasing), `progress`, `state`, `answer` and `row_answers`. Option `value`s are the tenant's refs: the server rejects any value that is not on the card. For `rate_each`, each row is its own question with its own `question_id`, and `options` are the columns. The newest `open` question card is the one to answer. An `answered` card is drawn locked with its `answer`. A `superseded` card was replaced by a newer one and takes no answer.
1100
+ * A `rate_each` card has no single `answer`. Its answered rows are in `row_answers`, on any card where at least one row is answered, in `pending_cards` too. Each answered row is drawn locked, and the others stay open. The card is `answered` only when every row has an answer. A row of a question that is not `required` may be answered `skipped`.
1101
+ */
1102
+ Question: {
1103
+ question_id: components["schemas"]["QuestionId"];
1104
+ kind: components["schemas"]["QuestionKind"];
1105
+ /** @description the question as the agent phrased it */
1106
+ prompt: string;
1107
+ /** @description optional, from the definition */
1108
+ help?: string;
1109
+ /** @description when false the person may skip it (`answer.skipped`) */
1110
+ required: boolean;
1111
+ /** @description The choices, and for `rate_each` the columns. Choice kinds and `rate_each` need at least 2 and at most 12; `yes_no` needs exactly 2; `list_choice` allows up to 500. */
1112
+ options?: components["schemas"]["QuestionOption"][];
1113
+ /** @description fewest selections (`multi_choice`, `image_choice`) */
1114
+ min?: number;
1115
+ /** @description most selections (`multi_choice`, `image_choice`) */
1116
+ max?: number;
1117
+ /**
1118
+ * @description offers a free-text "Other", answered with `other_text`
1119
+ * @default false
1120
+ */
1121
+ allows_other: boolean;
1122
+ /**
1123
+ * @description With `allows_other` only: the longest "Other" text (`other_text`), in Unicode code points; 500 when absent. A longer `ui.answer` is rejected with `invalid_value` at any length, past 500 too (`AnswerInput`), and its `message` names the limit, so an app that predates this field can say why. Other text the agent takes from a typed message is held to it too: the runtime asks for a shorter one and never submits a longer one. The app enforces it as the person types.
1124
+ * @default 500
1125
+ */
1126
+ other_max_length: number;
1127
+ /** @description an exclusive choice, answered as `values` with its `value` alone */
1128
+ none_of_the_above?: {
1129
+ value: string;
1130
+ label: string;
1131
+ };
1132
+ /** @description `rate_each` only: each row is rated on the `options` scale and answered on its own `question_id` */
1133
+ rows?: {
1134
+ question_id: components["schemas"]["QuestionId"];
1135
+ label: string;
1136
+ /** Format: uri */
1137
+ image_url?: string;
1138
+ }[];
1139
+ /** @description `short_text` (max_length at most 200) and `long_text` (at most 2000) */
1140
+ text?: {
1141
+ max_length: number;
1142
+ };
1143
+ number?: {
1144
+ min: number;
1145
+ max: number;
1146
+ step: number;
1147
+ /**
1148
+ * @example cm
1149
+ * @example kg
1150
+ * @example years
1151
+ */
1152
+ unit?: string;
1153
+ };
1154
+ /** @description optional bounds, inclusive */
1155
+ date?: {
1156
+ /** Format: date */
1157
+ min?: string;
1158
+ /** Format: date */
1159
+ max?: string;
1160
+ };
1161
+ scale?: {
1162
+ min: number;
1163
+ max: number;
1164
+ step: number;
1165
+ min_label: string;
1166
+ max_label: string;
1167
+ };
1168
+ /** @description The app uploads the photo to the tenant (never through Vinaverse) and answers with the `upload_ref` the tenant issued. */
1169
+ photo?: {
1170
+ max_bytes: number;
1171
+ mime_types: string[];
1172
+ };
1173
+ /** @description `total` is an estimate when the flow branches */
1174
+ progress?: {
1175
+ step: number;
1176
+ total: number;
1177
+ };
1178
+ /** @enum {string} */
1179
+ state: "open" | "answered" | "superseded";
1180
+ /** @description the recorded answer, when `state` is `answered`; never on `rate_each` (see `row_answers`) */
1181
+ answer?: components["schemas"]["QuestionAnswer"];
1182
+ /** @description `rate_each` only: the rows answered so far, each on its row's `question_id`, and each row at most once. A row is answered with one column (`values` holding one value, never `other_text`) or, when the question is not `required`, `skipped`. */
1183
+ row_answers?: components["schemas"]["RowAnswer"][];
1184
+ } & (unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown & unknown);
1185
+ /** @description A `rate_each` row's answer: one column (`values` holding one value, never `other_text`) or `skipped`. */
1186
+ RowAnswer: {
1187
+ question_id: components["schemas"]["QuestionId"];
1188
+ answer: components["schemas"]["QuestionAnswer"] & ({
1189
+ values: unknown;
1190
+ } | unknown) & unknown;
1191
+ };
1192
+ ConversationId: string;
1193
+ /**
1194
+ * @description Where one conversation stands, for the tenant backend that created its sessions. It is the `data` of the conversation webhooks and the response of `getConversation`. The backend keeps a durable copy, so it still has the answers after Vinaverse's state ends.
1195
+ * **Personal data.** It never carries the transcript or the `conversation_key`, and nothing about the person beyond the opaque `subject_id` except what the person gave the flow. The answers and remarks (`text`, `other_text`, `upload_ref`, the verbatim remarks) are personal data the person gave the tenant's flow; handle them as such. The object is closed: no other field is ever added to it without a contract change.
1196
+ * **`sequence`** is one counter per conversation, shared by the webhooks and `getConversation`. It goes up by one on every change to any field, `session_id` included, and on nothing else; `last_activity_at` is the time of that change. Each step produces exactly one webhook, so a gap means a delivery still to come. Two states with the same `sequence` are identical. A terminal event (`completed`, `expired`) always takes a new, higher `sequence`.
1197
+ * **Receiving.** Delivery is at least once and in no guaranteed order. Keep, per `conversation_id`, the state with the highest `sequence`, and ignore an equal or lower one. A state with `answers_omitted: true` updates every other field, and never replaces `answers` and `remarks` already held. Merge it, then read the full state with `getConversation`.
1198
+ * **Size.** A webhook body is at most 64 KB: 65,536 bytes of the UTF-8 request body, envelope included. When the state would make it larger, the webhook leaves out `answers` and `remarks` and sets `answers_omitted: true`.
1199
+ */
1200
+ ConversationState: {
1201
+ conversation_id: components["schemas"]["ConversationId"];
1202
+ subject_id: components["schemas"]["SubjectId"];
1203
+ /** @description the agent profile the conversation runs under */
1204
+ agent: string;
1205
+ /** @description the guided flow's name; null outside a guided flow */
1206
+ flow: string | null;
1207
+ /** @description the flow definition version the answers follow; null outside a guided flow */
1208
+ version: number | null;
1209
+ /**
1210
+ * @description `completed`: submitted and accepted. `failed`: the last submission is not known to be accepted (`flow.completed failed`); a later retry, in this session or a later one, can still complete it. `expired`: retention or `conversation_max_days` ended it.
1211
+ * @enum {string}
1212
+ */
1213
+ status: "in_progress" | "completed" | "failed" | "expired";
1214
+ sequence: number;
1215
+ progress: {
1216
+ /** @description questions answered, a `rate_each` row counting as one */
1217
+ answered: number;
1218
+ /** @description an estimate when the flow branches; null outside a guided flow */
1219
+ total_estimate: number | null;
1220
+ };
1221
+ /** @description the open question, or null when none is open (finished, between questions, or no flow) */
1222
+ pending_question_id: components["schemas"]["QuestionId"] | null;
1223
+ /** @description The answers recorded so far, in the order they were given. */
1224
+ answers?: components["schemas"]["ConversationAnswer"][];
1225
+ /** @description By `question_id`, extra things the person volunteered; never answers. */
1226
+ remarks?: {
1227
+ [key: string]: string;
1228
+ };
1229
+ /** @description true when a webhook left out `answers` and `remarks` to stay within 64 KB; never in `getConversation` */
1230
+ answers_omitted?: boolean;
1231
+ /**
1232
+ * Format: date-time
1233
+ * @description the time of the change that produced this `sequence`
1234
+ */
1235
+ last_activity_at: string;
1236
+ /** @description the latest session of the conversation */
1237
+ session_id: string;
1238
+ } & unknown;
1239
+ /** @description One recorded answer: `{ question_id, answer }`, or for `rate_each` `{ question_id, row_answers }`. */
1240
+ ConversationAnswer: {
1241
+ question_id: components["schemas"]["QuestionId"];
1242
+ answer: components["schemas"]["QuestionAnswer"];
1243
+ } | {
1244
+ question_id: components["schemas"]["QuestionId"];
1245
+ row_answers: components["schemas"]["RowAnswer"][];
1246
+ };
1247
+ /** @description Exactly one of: `values` (option values, validated against the card; one for single-choice kinds), `other_text` (the free-text "Other", when `allows_other`), `text`, `number`, `date` (YYYY-MM-DD), `upload_ref` (`photo`) or `skipped` (only when the question is not `required`). The one combination is `values` with `other_text`, for a `single_choice` or `multi_choice` question that `allows_other`: the person picked options and also typed an "Other". On any other question it is refused (`not_allowed`). */
1248
+ QuestionAnswer: {
1249
+ values: string[];
1250
+ } | {
1251
+ other_text: string;
1252
+ } | {
1253
+ values: string[];
1254
+ other_text: string;
1255
+ } | {
1256
+ text: string;
1257
+ } | {
1258
+ number: number;
1259
+ } | {
1260
+ /** Format: date */
1261
+ date: string;
1262
+ } | {
1263
+ upload_ref: string;
1264
+ } | {
1265
+ /** @constant */
1266
+ skipped: true;
1267
+ };
1268
+ /** @description What `ui.answer` carries: a `QuestionAnswer`, or the same with an "Other" longer than 500. An `other_text` longer than the question's `other_max_length` is answered `ui.answer.rejected` with `invalid_value`, at any length, and its `message` names the limit, so a person always learns it. It is never recorded, so recorded and submitted answers keep the `QuestionAnswer` bound. */
1269
+ AnswerInput: components["schemas"]["QuestionAnswer"] | {
1270
+ other_text: string;
1271
+ } | {
1272
+ values: string[];
1273
+ other_text: string;
1274
+ };
1275
+ /**
1276
+ * @description A message, oldest first: `{ role: assistant | person | staff, text, at }` (`staff`: a reply from a person from the business during a takeover, text only, never a name; v1-draft.15 widened the enum, so a client must tolerate a role it does not know), or an answered question card as `{ role: card, card, at }` (its `question.state` is `answered`, with its `answer`, or for `rate_each` its `row_answers`).
1277
+ * A `person` message is the person's own text as the runtime recorded it, card-like digits masked; a card tap, an answer or a proposal's outcome is not a message, and neither is an input the runtime never recorded (one refused with `input.rejected`, held at the turn limit, or a guardrail's refusal and its reply): the app keeps its own copy of those. An `assistant` message with `interrupted: true` (v1-draft.17) is a reply that stopped before it finished: its turn ended with `turn.failed` after it had streamed (a takeover, `input.cancel`, `output_blocked`, an outage), and the text is what was streamed. Draw it as cut off. A client that does not know the field draws it as any reply.
1278
+ * **Identity (v1-draft.17).** A message carries `id`, the identifier the live events gave it, so an app reconciles the transcript with what it drew instead of drawing it again: an `assistant` message's `turn_id` (`message.delta`, `message.completed`), a `person` message's `client_event_id` (`input.text`; absent when the app sent none), a `staff` message's `message_id` (`message.staff`). Unique per role within a conversation. A message of a turn also carries `turn_id`, the person's text and the agent's reply alike, so the cards the turn drew stay anchored after its text; a message sent during a takeover has none.
1279
+ */
1280
+ TranscriptItem: {
1281
+ /** @enum {string} */
1282
+ role: "assistant" | "person" | "staff";
1283
+ text: string;
1284
+ /** Format: date-time */
1285
+ at: string;
1286
+ /** @description `assistant` only, present only when true: the reply stopped before it finished */
1287
+ interrupted?: boolean;
1288
+ /** @description v1-draft.17: the live event's identifier for this message (`turn_id`, `client_event_id` or `message_id`, by role); absent on a `person` message sent without a `client_event_id` */
1289
+ id?: string;
1290
+ /** @description v1-draft.17; the turn the message belongs to, when it belongs to one */
1291
+ turn_id?: string;
1292
+ } | {
1293
+ /** @constant */
1294
+ role: "card";
1295
+ card: components["schemas"]["Card"] & {
1296
+ /** @constant */
1297
+ kind: "question";
1298
+ question: {
1299
+ /** @constant */
1300
+ state: "answered";
1301
+ };
1302
+ };
1303
+ /** Format: date-time */
1304
+ at: string;
1305
+ };
1306
+ ConversationPage: {
1307
+ data: components["schemas"]["ConversationSummary"][];
1308
+ next_cursor: string | null;
1309
+ };
1310
+ /** @description v1-draft.21: one past conversation as a history screen lists it. No message text: the title is from the conversation's last search intent or its date, never the person's words. */
1311
+ ConversationSummary: {
1312
+ id: components["schemas"]["ConversationId"];
1313
+ agent: string;
1314
+ experience_mode: components["schemas"]["ExperienceMode"];
1315
+ business_scope_id?: components["schemas"]["BusinessScopeId"];
1316
+ title: string;
1317
+ /**
1318
+ * @description `intent`: the last search intent (service and place); `date`: the day it started
1319
+ * @enum {string}
1320
+ */
1321
+ title_source: "intent" | "date";
1322
+ /** Format: date-time */
1323
+ started_at: string;
1324
+ /** Format: date-time */
1325
+ last_activity_at: string;
1326
+ /** @description the messages its timeline holds (cards not counted) */
1327
+ message_count: number;
1328
+ /**
1329
+ * @description `open`: a session can resume it; `ended`: it can only be read
1330
+ * @enum {string}
1331
+ */
1332
+ state: "open" | "ended";
1333
+ };
1334
+ TimelinePage: {
1335
+ data: components["schemas"]["TimelineItem"][];
1336
+ /** @description the next older page; null at the conversation's start */
1337
+ next_cursor: string | null;
1338
+ };
1339
+ /**
1340
+ * @description v1-draft.21: one item of a past conversation, a message or a card its turn drew. `id` is stable and unique within the conversation; a message's `event_id` is its live event's identifier (`TranscriptItem.id`), the key an app merges live and resumed messages by, with `role`. A card is read-only in history.
1341
+ * Unlike `session.ready.transcript`, the history holds a guardrail's refusals: the person's message a guardrail refused and the fixed line said instead of an answer both carry `refusal: true`, so the app draws them as it drew them live (muted, never offered again). A `proposal` card carries `proposal_outcome`, what became of it.
1342
+ */
1343
+ TimelineItem: {
1344
+ /** @constant */
1345
+ type: "message";
1346
+ id: string;
1347
+ /**
1348
+ * @description a client tolerates a role it does not know, as in `TranscriptItem`
1349
+ * @enum {string}
1350
+ */
1351
+ role: "person" | "assistant" | "staff";
1352
+ text: string;
1353
+ /** Format: date-time */
1354
+ at: string;
1355
+ /** @description absent on a `person` message sent without a `client_event_id` */
1356
+ event_id?: string;
1357
+ turn_id?: string;
1358
+ /** @description `assistant` only, present only when true: the reply stopped before it finished */
1359
+ interrupted?: boolean;
1360
+ /**
1361
+ * @description Present only when true: a guardrail refused this turn. On a `person` message, the text it refused (as recorded, card-like digits masked); on an `assistant` message, the fixed line said instead of an answer.
1362
+ * @constant
1363
+ */
1364
+ refusal?: true;
1365
+ } | {
1366
+ /** @constant */
1367
+ type: "card";
1368
+ id: string;
1369
+ turn_id: string;
1370
+ /** Format: date-time */
1371
+ at: string;
1372
+ card: components["schemas"]["Card"];
1373
+ /**
1374
+ * @description A `proposal` card only, always present on one: the outcome the app reported for it (`ui.action` `confirmed`, `declined`, `failed` or `expired`), or `pending` while none was reported. Draw it as settled; a past proposal is never confirmed from history.
1375
+ * @enum {string}
1376
+ */
1377
+ proposal_outcome?: "pending" | "confirmed" | "declined" | "failed" | "expired";
1378
+ };
1379
+ ConversationDeleted: {
1380
+ conversation_id: components["schemas"]["ConversationId"];
1381
+ sessions_revoked: number;
1382
+ };
1383
+ /** @description A time slot the person picked on a `time_slots` card (v1-draft.17, `docs/realtime-protocol.md` §6): the slot's `id`, and the `call_id` of the tool call the card came from (the card's `source.call_id`), so the runtime checks the pick against the evidence it holds for that call. Both are copied from the card; never built by the app. */
1384
+ SlotSelection: {
1385
+ id: string;
1386
+ call_id: string;
1387
+ };
1388
+ /**
1389
+ * @description A change the agent proposed through a `propose` tool. The tenant holds it as pending; nothing has changed yet, and Vinaverse never applies it. The app shows `title`, `summary` and each field's `before` and `after`, with its own Confirm and Decline buttons (`confirm_mode: tenant_app`; the card has no `actions`). - **Confirm or decline** happens in the tenant's app, against the tenant's backend, with the person's own
1390
+ * token, before `expires_at`. The tenant applies the change on confirm.
1391
+ * - **Then** the app sends `ui.action` with the card's `turn_id` and `card_id`, a `client_event_id`, and the
1392
+ * outcome as `action`. The runtime gives that to the model as an event, never as a message from the person,
1393
+ * and the agent carries on from it.
1394
+ * - `confirmed`: the tenant's backend applied the change;
1395
+ * - `declined`: the person declined it;
1396
+ * - `failed`: the person confirmed but the tenant did not apply it (it refused the change, or the proposal
1397
+ * had expired);
1398
+ * - `expired`: the tenant says `expires_at` passed with no answer.
1399
+ * - **The tenant is the source of truth, and only an outcome the app reports is final.** At `expires_at` the
1400
+ * runtime tells the model, provisionally, that the proposal was not confirmed, so the model never waits on it.
1401
+ * This counts for nothing. A `confirmed`, `declined` or `failed` the app reports later is accepted, wins,
1402
+ * and is fed to the model as a correction.
1403
+ * - **The first app-reported outcome per card is final.** A repeat of that same outcome changes nothing. A
1404
+ * second, conflicting outcome, or an outcome on a card that is not a `proposal`, is answered with `error`
1405
+ * (`code: proposal_outcome_rejected`, with the `card_id` and `client_event_id`), and nothing reaches the
1406
+ * model.
1407
+ * - **`status`** is the tenant's state when the card was drawn. A card restored after a reconnect
1408
+ * (`session.ready.pending_cards`) still waits for its outcome.
1409
+ * - **Only a `pending` proposal is actionable.** The app draws Confirm and Decline only for
1410
+ * `proposal.status: pending`. A `propose` result that is not `pending` (a reused key that met an expired,
1411
+ * declined, confirmed or superseded proposal) is never drawn as an actionable card. The runtime advances the
1412
+ * key's attempt counter and calls once more with the new key, or, if that one is not `pending` either, gives
1413
+ * the `status` to the model as the outcome.
1414
+ * - **`edit_deep_link`** opens the tenant's own screen for the change, to edit it there. It is held to the
1415
+ * deep-link rule (`admin-v1.yaml`, `Connector.deep_link_prefixes`), the same check as a `next_step` link.
1416
+ * The runtime removes an `edit_deep_link` that fails, and the card is still sent. With no prefix registered,
1417
+ * nothing passes.
1418
+ *
1419
+ * - **A booking proposal** (topic `booking`, `booking_reschedule` or `booking_cancellation`) carries the fields a
1420
+ * client must see under fixed keys (`BookingFields`): what is booked and with whom, when, and the price; for a
1421
+ * reschedule, the booking (unchanged) and its time before and after; for a cancellation, the booking and its
1422
+ * time (both unchanged), and any fee or refund. It expires no later than the booking starts, and a
1423
+ * reschedule no later than the earlier of its current and its new start
1424
+ * (`docs/agents-guardrails-suggestions-writes.md` §3.4).
1425
+ *
1426
+ * Each field `key` appears once. `expires_at` is at most 7 days after the proposal was created. A field whose `before` equals its `after` does not change: show its value once.
1427
+ */
1428
+ Proposal: {
1429
+ /** @description the tenant's proposal id */
1430
+ id: string;
1431
+ /**
1432
+ * @description The tenant's current state of the proposal. Only `pending` can still be confirmed. The tenant is the source of truth, and `confirmed` means the change was applied.
1433
+ * @enum {string}
1434
+ */
1435
+ status: "pending" | "confirmed" | "declined" | "expired" | "superseded";
1436
+ /**
1437
+ * @example business_address
1438
+ * @example opening_hours
1439
+ * @example services
1440
+ * @example booking
1441
+ * @example booking_reschedule
1442
+ * @example booking_cancellation
1443
+ */
1444
+ topic: string;
1445
+ title: string;
1446
+ summary: string;
1447
+ fields: components["schemas"]["ProposalField"][];
1448
+ /**
1449
+ * Format: date-time
1450
+ * @description after it the tenant declines the proposal on its own
1451
+ */
1452
+ expires_at: string;
1453
+ /** @description a link into the tenant's app; removed by the runtime when it fails the deep-link rule (see `edit_deep_link` above) */
1454
+ edit_deep_link?: string;
1455
+ /** @enum {string} */
1456
+ confirm_mode: "tenant_app";
1457
+ } & (unknown & unknown & unknown & unknown);
1458
+ /** @description The fields of a booking proposal (topic `booking`, `booking_reschedule` or `booking_cancellation`), whose keys are fixed so every app draws a booking card the same way (`docs/agents-guardrails-suggestions-writes.md` §3.4): `service`, `professional`, `space`, `booking`, `location`, `note` and `reason` are `text`, `services` is a `list`, `time` is a `time_range`, `place` an `address`, and `price`, `deposit`, `fee`, `cancellation_fee` and `refund` are `money_cents`. */
1459
+ BookingFields: (unknown & unknown & unknown & unknown & unknown)[];
1460
+ /** @description `before` is the current value, absent or null when the proposal creates it, and equal to `after` when the field does not change but says what the change is about (the booking a reschedule moves); `before` and `after` take the shape `type` names. */
1461
+ ProposalField: {
1462
+ key: string;
1463
+ label: string;
1464
+ /**
1465
+ * @description `money_cents` is an amount in the currency's minor units (ISO 4217: cents for USD, yen for JPY, fils for BHD), never negative. `time_range` is one dated range whose `end` is after its `start`. `weekly_hours` is recurring opening hours, by weekday, in the business's time zone.
1466
+ * @enum {string}
1467
+ */
1468
+ type: "text" | "money_cents" | "time_range" | "address" | "list" | "weekly_hours";
1469
+ before?: unknown;
1470
+ after: unknown;
1471
+ } & (unknown & unknown & unknown & unknown & unknown & unknown);
1472
+ ProposalMoney: {
1473
+ /** @description in the currency's minor units (ISO 4217) */
1474
+ amount: number;
1475
+ currency: string;
1476
+ };
1477
+ /** @description Recurring opening hours. A day absent from `entries` is closed; a day may have several entries (a lunch break), which never overlap. `end` may be `24:00` (closing at midnight), and `00:00`–`24:00` is open all day. An `end` earlier than `start` runs past midnight into the next day (a bar's `22:00`–`02:00`), and that spill counts against the next day's entries: `fri 22:00`–`02:00` and `sat 01:00`–`09:00` overlap. `end` never equals `start`. */
1478
+ WeeklyHours: {
1479
+ /**
1480
+ * @description an IANA time zone name
1481
+ * @example America/New_York
1482
+ */
1483
+ time_zone: string;
1484
+ entries: {
1485
+ /** @enum {string} */
1486
+ day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun";
1487
+ start: string;
1488
+ end: string;
1489
+ }[];
1490
+ };
1491
+ TimeRange: {
1492
+ /** Format: date-time */
1493
+ start: string;
1494
+ /** Format: date-time */
1495
+ end: string;
1496
+ };
1497
+ PostalAddress: {
1498
+ line1: string;
1499
+ line2?: string;
1500
+ city: string;
1501
+ region?: string;
1502
+ postal_code?: string;
1503
+ /** @description ISO 3166-1 alpha-2 */
1504
+ country: string;
1505
+ };
1506
+ TextList: string[];
1507
+ /** @description What the app sends. **The turn limit narrows the typed message's deduplication rule below**, which lists `limit_reached` among the refusals a resend gets again: that holds in the same session only. A text or an answer refused with `input.rejected` `limit_reached` is not recorded. On a new session of the conversation it is checked as new: it runs, as a turn of that session, or, if its turn had already run, gets `duplicate` (`input.rejected` for a text, `ui.answer.recorded` with `duplicate: true` for an answer) (`docs/realtime-protocol.md` §2, a session out of turns). */
1508
+ ClientEvent: {
1509
+ /** @constant */
1510
+ type: "session.auth";
1511
+ client_secret: string;
1512
+ } | {
1513
+ /** @constant */
1514
+ type: "input.text";
1515
+ client_event_id?: string;
1516
+ text: string;
1517
+ } | {
1518
+ /** @constant */
1519
+ type: "input.cancel";
1520
+ turn_id: string;
1521
+ } | {
1522
+ /** @constant */
1523
+ type: "playback.fenced";
1524
+ turn_id: string;
1525
+ heard_chars: number;
1526
+ } | ({
1527
+ /** @constant */
1528
+ type: "ui.action";
1529
+ client_event_id?: string;
1530
+ turn_id: string;
1531
+ card_id: string;
1532
+ /**
1533
+ * @description On a `proposal` card: `confirmed`, `declined`, `failed` or `expired` (`Proposal`), sent after the outcome in the tenant's app. It tells the session; it changes nothing.
1534
+ * On a `time_slots` card: `select_slot` (v1-draft.17), with `slot`, the person's pick. It is checked against this session's evidence: the card must be one this session drew from the tool call `slot.call_id` names, and the slot one that call returned; anything else gets `error` (`invalid_action`) and reaches nothing. An accepted pick starts a turn: `turn.started` echoes the `client_event_id`, and it is deduplicated as `input.text` is (a resend whose turn ran gets `input.rejected` `duplicate`). It is a tap on a card: refused with `input.rejected` `turn_in_progress` during a turn or a takeover, never held at the turn limit, and never sent on another session.
1535
+ */
1536
+ action: string;
1537
+ slot?: components["schemas"]["SlotSelection"];
1538
+ } & (unknown & unknown)) | {
1539
+ /** @constant */
1540
+ type: "ui.answer";
1541
+ /** @description the action id, unique per answer */
1542
+ client_event_id: string;
1543
+ card_id: string;
1544
+ question_id: components["schemas"]["QuestionId"];
1545
+ answer: components["schemas"]["AnswerInput"];
1546
+ } | {
1547
+ /** @constant */
1548
+ type: "ping";
1549
+ };
1550
+ /** @description What the server sends on the WebSocket, and as SSE events of a turn. `session.expiring` is WebSocket only: sent once per connection, 60 s before `Session.expires_at`, never for the client secret; `session.closed` with `expired` follows at the end (`docs/realtime-protocol.md` §1). What the app does after each `session.closed` reason is in §2 of the same document; `server_restart` is reserved, nothing sends it today, and it never ends a session. */
1551
+ ServerEvent: {
1552
+ /** @constant */
1553
+ type: "session.ready";
1554
+ session: components["schemas"]["SessionStatus"];
1555
+ limits: {
1556
+ max_turns?: number;
1557
+ turns_left?: number;
1558
+ /** Format: date-time */
1559
+ expires_at?: string;
1560
+ };
1561
+ disclosure: string;
1562
+ resumed: boolean;
1563
+ /** @description v1-draft.20, the session's mode (`Session.experience_mode`), so the app keeps one store per mode */
1564
+ experience_mode?: components["schemas"]["ExperienceMode"];
1565
+ /** @description v1-draft.20, present when the session acts for a business */
1566
+ business_scope_id?: components["schemas"]["BusinessScopeId"];
1567
+ /**
1568
+ * @description Optional. The last messages of the conversation, oldest first, text only, so the app can redraw it after a kill or a reconnect. Older context is not sent.
1569
+ * A guided flow's resumed `session.ready` always carries it. Any other session's (a chat; v1-draft.17) carries it when `resumed` is true and there is something to restore, whether the app reconnects to the same session or the backend created a new session of the conversation: - the conversation's messages across its sessions (the session's own when it has no conversation):
1570
+ * the person's texts, the agent's replies (`interrupted` when cut off) and the staff replies of a
1571
+ * takeover; the last 20, and none older than 7 days;
1572
+ * - only what the tenant's `transcript_retention` still keeps: under `none`, only the open session's
1573
+ * own messages; never anything of a subject erased since (`eraseSubject`).
1574
+ *
1575
+ * When present, it is the server's view: redraw the messages from it, including any the app missed while disconnected. When absent, the server holds nothing to restore: keep what the app holds. It is sent only on the connection the client secret opened, so it reaches no one the session does not.
1576
+ */
1577
+ transcript?: components["schemas"]["TranscriptItem"][];
1578
+ /**
1579
+ * @description Optional. The open question card, if there is one (at most one); render it unlocked. For `rate_each`, its `row_answers` say which rows are already answered: draw those locked. Also the `proposal` cards still awaiting an outcome from the app, so the app can report `confirmed`, `declined`, `failed` or `expired` with its `turn_id` and `card_id` after a reconnect. A proposal card awaits until the app reports an outcome, or until 7 days after its `expires_at`. When more than 10 await, the 10 newest are restored. An older one still takes an outcome if the app has its ids.
1580
+ * Every item carries `turn_id`, the turn that drew the card. `ui.action` needs it with the card's `id`, so a proposal card restored after a reconnect or an app kill can still be reported.
1581
+ */
1582
+ pending_cards?: (components["schemas"]["Card"] & {
1583
+ /** @description the turn that drew the card; `ui.action` names it with the card's `id` */
1584
+ turn_id: string;
1585
+ } & ({
1586
+ /** @constant */
1587
+ kind: "question";
1588
+ question: {
1589
+ /** @constant */
1590
+ state: "open";
1591
+ };
1592
+ } | {
1593
+ /** @constant */
1594
+ kind: "proposal";
1595
+ proposal: {
1596
+ /** @constant */
1597
+ status: "pending";
1598
+ };
1599
+ }))[];
1600
+ } | {
1601
+ /** @constant */
1602
+ type: "turn.started";
1603
+ turn_id: string;
1604
+ client_event_id?: string;
1605
+ } | {
1606
+ /** @constant */
1607
+ type: "tool.status";
1608
+ turn_id: string;
1609
+ label: string;
1610
+ /** @enum {string} */
1611
+ state: "running" | "done" | "failed";
1612
+ } | {
1613
+ /** @constant */
1614
+ type: "message.delta";
1615
+ turn_id: string;
1616
+ /** @description One or more whole sentences. Each sentence passes the leak check, and the tenant's blocked-topic filter, before it is sent. */
1617
+ text: string;
1618
+ } | {
1619
+ /** @constant */
1620
+ type: "ui.cards";
1621
+ turn_id: string;
1622
+ cards: components["schemas"]["Card"][];
1623
+ } | {
1624
+ /** @constant */
1625
+ type: "message.completed";
1626
+ turn_id: string;
1627
+ text: string;
1628
+ /** @description optional; spans of `text` with their source card or tool call */
1629
+ citations?: components["schemas"]["Citation"][];
1630
+ } | {
1631
+ /** @constant */
1632
+ type: "handoff.created";
1633
+ turn_id: string;
1634
+ ticket: {
1635
+ id: string;
1636
+ /** @description the tenant's phrase completing "they'll reply {expected_response}" (`within one business day by email`), never a sentence of its own */
1637
+ expected_response: string;
1638
+ };
1639
+ } | {
1640
+ /** @constant */
1641
+ type: "turn.completed";
1642
+ turn_id: string;
1643
+ usage: components["schemas"]["Usage"];
1644
+ } | {
1645
+ /** @constant */
1646
+ type: "turn.failed";
1647
+ turn_id: string;
1648
+ /**
1649
+ * @description Whatever the code, the sentences the turn already streamed (`message.delta`) stand: they are the reply recorded, marked interrupted (`TranscriptItem.interrupted`, v1-draft.17), so the app keeps them, drawn as cut off, and adds `message` after them. Includes: - `output_blocked`: a sentence of the reply would have leaked (the prompt, keys, other subjects' data),
1650
+ * so it was never sent, and the turn ended (`retryable: false`). A sentence that only touches a
1651
+ * blocked topic is dropped instead, and the turn goes on.
1652
+ * - `cancelled`: the turn was stopped: by `input.cancel`, because the person left, or because a person
1653
+ * from the business took the conversation over (`takeover.started` follows, and nothing of the turn
1654
+ * comes after it).
1655
+ * - `tool_result_invalid`: a `propose` tool returned something that is not a `Proposal`. No card is
1656
+ * shown, and the connector is flagged.
1657
+ */
1658
+ code: string;
1659
+ message: string;
1660
+ retryable: boolean;
1661
+ } | {
1662
+ /** @constant */
1663
+ type: "input.rejected";
1664
+ client_event_id?: string;
1665
+ /**
1666
+ * @description `duplicate`: the same `client_event_id` was already handled in this conversation, and its turn ran; do not resend it. `turn_in_progress`: send the input again, with the same `client_event_id`, after `turn.completed`. `screen_unavailable`: the chat-safety screen could not check the input, so nothing ran and the model never saw it, and nothing was recorded or remembered. Resend the same input with the same `client_event_id` after a short backoff (for example 2 s, doubling, three or four tries); if it is still refused, show it as not sent, with `message`, and let the person send it again. It replaces, for this case, the retryable `error` with `code: screen_unavailable` that earlier runtimes sent; an older server still sends that `error`. An app that does not know the code treats it as any unknown `input.rejected` code: it does not resend, and shows `message`. `guardrail`: the message was refused before the model saw it, by one of the agent's guardrails (a blocked topic, or outside its scope under `strict`) or by the platform floor (an instruction override or persona hijack); `message` is the tenant's `refusal_message` (or a platform default in the subject's locale): show it as the agent's reply, and do not resend.
1667
+ * @enum {string}
1668
+ */
1669
+ code: "screened" | "too_long" | "turn_in_progress" | "limit_reached" | "duplicate" | "guardrail" | "screen_unavailable";
1670
+ message: string;
1671
+ } | {
1672
+ /** @constant */
1673
+ type: "session.expiring";
1674
+ seconds_left: number;
1675
+ } | {
1676
+ /** @constant */
1677
+ type: "session.closed";
1678
+ /**
1679
+ * @description `replaced`: a newer session of the same conversation was created (close code `4409`). `deleted` (v1-draft.21): the person deleted the conversation from their history (`deleteConversation`, close code `4403`); drop it from the app's store and never resume it. A client that does not know `deleted` reads it as `revoked`, as any unknown reason.
1680
+ * @enum {string}
1681
+ */
1682
+ reason: "expired" | "revoked" | "limit_reached" | "idle" | "server_restart" | "replaced" | "deleted";
1683
+ } | {
1684
+ /** @constant */
1685
+ type: "ui.answer.recorded";
1686
+ card_id: string;
1687
+ question_id: components["schemas"]["QuestionId"];
1688
+ client_event_id: string;
1689
+ duplicate: boolean;
1690
+ } | {
1691
+ /** @constant */
1692
+ type: "ui.answer.rejected";
1693
+ card_id: string;
1694
+ question_id: components["schemas"]["QuestionId"];
1695
+ client_event_id: string;
1696
+ /** @enum {string} */
1697
+ code: "invalid_value" | "too_many" | "too_few" | "card_superseded" | "not_allowed" | "client_event_id_reused";
1698
+ message?: string;
1699
+ } | ({
1700
+ /** @constant */
1701
+ type: "flow.completed";
1702
+ /** @example intake-survey */
1703
+ flow: string;
1704
+ /** @enum {string} */
1705
+ result: "ok" | "refused" | "failed";
1706
+ /**
1707
+ * @description For `refused`, the tenant's refusal code, as it sent it: `REQUIRED_MISSING`, `UNKNOWN_VALUE`, `TOO_LONG`, `VERSION_NOT_ACCEPTED`, `TOO_MANY`, `TOO_FEW`, `BAD_DATE`, `UPLOAD_NOT_FOUND`, `OUT_OF_RANGE`, or a newer one. Show it as it is; the agent has already asked again.
1708
+ * @example REQUIRED_MISSING
1709
+ * @example unavailable
1710
+ */
1711
+ code?: string;
1712
+ /** @description `failed` only */
1713
+ retryable?: boolean;
1714
+ } & (unknown & unknown & unknown)) | {
1715
+ /** @constant */
1716
+ type: "error";
1717
+ code: string;
1718
+ message: string;
1719
+ retryable: boolean;
1720
+ /** @description the card the refused event named (`proposal_outcome_rejected`) */
1721
+ card_id?: string;
1722
+ /** @description the refused event's `client_event_id` */
1723
+ client_event_id?: string;
1724
+ } | {
1725
+ /** @constant */
1726
+ type: "pong";
1727
+ } | {
1728
+ /** @constant */
1729
+ type: "takeover.started";
1730
+ /** Format: date-time */
1731
+ at: string;
1732
+ /** @description the replier's role as the person sees it, never a name (e.g. "the team") */
1733
+ by?: string;
1734
+ } | {
1735
+ /** @constant */
1736
+ type: "takeover.ended";
1737
+ /** Format: date-time */
1738
+ at: string;
1739
+ } | {
1740
+ /** @constant */
1741
+ type: "message.staff";
1742
+ message_id: string;
1743
+ text: string;
1744
+ /** Format: date-time */
1745
+ at: string;
1746
+ } | {
1747
+ /** @constant */
1748
+ type: "input.received";
1749
+ client_event_id: string;
1750
+ };
1751
+ /**
1752
+ * @description The tenant's binding lists its surfaces; unknown surfaces are rejected.
1753
+ * @example listing_photo
1754
+ * @example gallery_photo
1755
+ * @example profile_photo
1756
+ * @example service_photo
1757
+ * @example class_photo
1758
+ * @example listing_video
1759
+ * @example bio
1760
+ * @example listing_text
1761
+ * @example business_name
1762
+ * @example service_name
1763
+ * @example chat_message
1764
+ * @example review
1765
+ */
1766
+ Surface: string;
1767
+ Media: {
1768
+ /** Format: uri */
1769
+ url: string;
1770
+ /** Format: date-time */
1771
+ url_expires_at?: string;
1772
+ /** @enum {string} */
1773
+ content_type: "image/jpeg" | "image/png" | "image/webp" | "image/heic" | "video/mp4" | "video/quicktime" | "video/webm";
1774
+ bytes?: number;
1775
+ sha256?: string;
1776
+ };
1777
+ ModerationItemInput: {
1778
+ ref: string;
1779
+ surface: components["schemas"]["Surface"];
1780
+ /** @enum {string} */
1781
+ kind: "image" | "video" | "text";
1782
+ subject: components["schemas"]["Subject"];
1783
+ media?: components["schemas"]["Media"];
1784
+ text?: string;
1785
+ context?: {
1786
+ [key: string]: string;
1787
+ };
1788
+ options?: {
1789
+ /** @default false */
1790
+ assess_quality: boolean;
1791
+ };
1792
+ } & ({
1793
+ /** @enum {unknown} */
1794
+ kind?: "image" | "video";
1795
+ } | {
1796
+ /** @constant */
1797
+ kind?: "text";
1798
+ });
1799
+ ModerationJobCreate: {
1800
+ /** @default default */
1801
+ policy: string;
1802
+ items: components["schemas"]["ModerationItemInput"][];
1803
+ };
1804
+ Reason: {
1805
+ /**
1806
+ * @example contact.off_platform
1807
+ * @example sexual.nudity
1808
+ * @example quality.not_a_space
1809
+ */
1810
+ code: string;
1811
+ /** @enum {string} */
1812
+ severity: "info" | "review" | "hold" | "never";
1813
+ evidence?: {
1814
+ /** @enum {string} */
1815
+ kind?: "visual" | "text_in_image" | "text" | "speech" | "metadata";
1816
+ /** @description redacted excerpt, at most 120 chars */
1817
+ excerpt?: string;
1818
+ /** @description normalized box, integers in ppm of width/height */
1819
+ region?: {
1820
+ x?: number;
1821
+ y?: number;
1822
+ w?: number;
1823
+ h?: number;
1824
+ } | null;
1825
+ timestamp_ms?: number | null;
1826
+ };
1827
+ explanation?: string;
1828
+ };
1829
+ /** @description A vv-decision/0 decision record as exposed to tenants. */
1830
+ Decision: {
1831
+ id: string;
1832
+ /** @enum {string} */
1833
+ domain: "image_safety" | "video_safety" | "text_safety" | "chat_safety" | "document_check";
1834
+ domain_version: string;
1835
+ domain_hash?: string;
1836
+ /** @enum {string} */
1837
+ class: "clean" | "review" | "hold" | "never";
1838
+ abstained: boolean;
1839
+ reasons: components["schemas"]["Reason"][];
1840
+ distribution_ppm?: {
1841
+ [key: string]: number;
1842
+ } | null;
1843
+ /** @enum {string} */
1844
+ calibration: "absent" | "calibrated";
1845
+ threshold_policy?: string;
1846
+ policy_version: string;
1847
+ model: {
1848
+ id: string;
1849
+ provider: string;
1850
+ revision?: string;
1851
+ };
1852
+ prompt_version?: string;
1853
+ audit: {
1854
+ sampled: boolean;
1855
+ probability_ppm: number;
1856
+ };
1857
+ };
1858
+ Quality: {
1859
+ /** @enum {string} */
1860
+ rating: "good" | "fair" | "poor";
1861
+ issues: ("dark" | "blurry" | "low_resolution" | "tilted" | "cluttered" | "watermark" | "heavy_filter" | "not_a_space" | "duplicate_in_listing")[];
1862
+ is_space: boolean;
1863
+ note?: string;
1864
+ };
1865
+ VideoFindings: {
1866
+ duration_ms: number;
1867
+ frames_analyzed: number;
1868
+ transcript_language?: string | null;
1869
+ segments: {
1870
+ start_ms: number;
1871
+ end_ms: number;
1872
+ reasons: components["schemas"]["Reason"][];
1873
+ /** @description timestamps of the frames supporting visual reasons; the tenant seeks its own video */
1874
+ frame_timestamps_ms: number[];
1875
+ note?: string;
1876
+ }[];
1877
+ summary_for_reviewer: string;
1878
+ };
1879
+ ModerationItem: {
1880
+ id: string;
1881
+ ref: string;
1882
+ surface: components["schemas"]["Surface"];
1883
+ /** @enum {string} */
1884
+ kind: "image" | "video" | "text";
1885
+ /** @enum {string} */
1886
+ status: "queued" | "processing" | "completed" | "failed";
1887
+ decision?: components["schemas"]["Decision"];
1888
+ quality?: components["schemas"]["Quality"];
1889
+ video?: components["schemas"]["VideoFindings"];
1890
+ error?: components["schemas"]["Problem"];
1891
+ /** Format: date-time */
1892
+ completed_at?: string;
1893
+ processing_ms?: number;
1894
+ usage?: components["schemas"]["Usage"];
1895
+ };
1896
+ ModerationJob: {
1897
+ id: string;
1898
+ /** @enum {string} */
1899
+ status: "queued" | "processing" | "completed" | "partially_failed";
1900
+ items: components["schemas"]["ModerationItem"][];
1901
+ estimated_completion_seconds?: {
1902
+ [key: string]: number;
1903
+ };
1904
+ };
1905
+ ModerationJobSummary: {
1906
+ id: string;
1907
+ /** @enum {string} */
1908
+ status: "completed" | "partially_failed";
1909
+ counts: {
1910
+ clean?: number;
1911
+ review?: number;
1912
+ hold?: number;
1913
+ never?: number;
1914
+ failed?: number;
1915
+ };
1916
+ };
1917
+ /** @description A destination configured in Studio (an S3-compatible bucket and a write-only key). */
1918
+ DestinationId: string;
1919
+ /** @description `poster` and `captions` write where `transcode` writes unless they name a destination. Outside the mock environment a step that writes needs a destination. An image takes only `moderate`. */
1920
+ MediaSteps: {
1921
+ moderate: {
1922
+ /** @default default */
1923
+ policy: string;
1924
+ };
1925
+ transcode?: {
1926
+ /** @enum {string} */
1927
+ profile: "tour_hls_v1";
1928
+ destination?: components["schemas"]["DestinationId"];
1929
+ };
1930
+ /** @description One JPEG plus a 6-second silent preview MP4. `auto` picks a sharp, well-lit frame. */
1931
+ poster?: {
1932
+ /** @default auto */
1933
+ at: "auto" | number;
1934
+ destination?: components["schemas"]["DestinationId"];
1935
+ };
1936
+ /** @description WebVTT from the transcript the moderation step makes. */
1937
+ captions?: {
1938
+ languages: string[];
1939
+ destination?: components["schemas"]["DestinationId"];
1940
+ };
1941
+ };
1942
+ MediaJobCreate: {
1943
+ ref: string;
1944
+ surface: components["schemas"]["Surface"];
1945
+ subject: components["schemas"]["Subject"];
1946
+ media: components["schemas"]["Media"];
1947
+ steps: components["schemas"]["MediaSteps"];
1948
+ /**
1949
+ * @description Orders work inside the tenant's own turn; never ahead of another tenant's fair share.
1950
+ * @default normal
1951
+ * @enum {string}
1952
+ */
1953
+ priority: "high" | "normal" | "low";
1954
+ context?: {
1955
+ [key: string]: string;
1956
+ };
1957
+ /**
1958
+ * @description Reprocess even when a dedupe match exists.
1959
+ * @default false
1960
+ */
1961
+ force: boolean;
1962
+ };
1963
+ /**
1964
+ * @description `waiting`: for another step; `skipped`: not run because a step it needs failed or the verdict was `never`.
1965
+ * @enum {string}
1966
+ */
1967
+ MediaStepStatus: "waiting" | "queued" | "processing" | "completed" | "failed" | "skipped" | "cancelled";
1968
+ MediaJobStep: {
1969
+ status: components["schemas"]["MediaStepStatus"];
1970
+ progress_percent: number;
1971
+ /** @enum {string} */
1972
+ stage?: "fetching" | "analyzing" | "transcoding" | "uploading";
1973
+ /** @description `moderate`, when completed */
1974
+ decision?: components["schemas"]["Decision"];
1975
+ video?: components["schemas"]["VideoFindings"];
1976
+ error?: components["schemas"]["Problem"];
1977
+ /** Format: date-time */
1978
+ completed_at?: string;
1979
+ };
1980
+ MediaOutput: {
1981
+ /** @enum {string} */
1982
+ step: "transcode" | "poster" | "captions";
1983
+ /**
1984
+ * @example hls_master
1985
+ * @example hls_720p
1986
+ * @example mp4_fallback
1987
+ * @example poster
1988
+ * @example preview
1989
+ * @example captions
1990
+ */
1991
+ role?: string;
1992
+ /** @description Object key in the tenant's bucket, under `<prefix>/<job id>/` */
1993
+ key: string;
1994
+ destination_id?: components["schemas"]["DestinationId"];
1995
+ content_type: string;
1996
+ bytes: number;
1997
+ sha256: string;
1998
+ duration_ms?: number;
1999
+ width?: number;
2000
+ height?: number;
2001
+ language?: string;
2002
+ };
2003
+ MediaJob: {
2004
+ id: string;
2005
+ ref: string;
2006
+ surface: components["schemas"]["Surface"];
2007
+ /** @enum {string} */
2008
+ kind: "image" | "video";
2009
+ subject: {
2010
+ id: components["schemas"]["SubjectId"];
2011
+ type: components["schemas"]["SubjectType"];
2012
+ };
2013
+ /** @enum {string} */
2014
+ status: "queued" | "processing" | "completed" | "failed" | "cancelled";
2015
+ /** @enum {string} */
2016
+ stage: "waiting" | "fetching" | "analyzing" | "transcoding" | "uploading" | "done";
2017
+ progress_percent: number;
2018
+ /** @enum {string} */
2019
+ priority: "high" | "normal" | "low";
2020
+ /** @description While queued, the job's place among this tenant's waiting work in its first lane. */
2021
+ queue_position: number | null;
2022
+ /**
2023
+ * Format: date-time
2024
+ * @description From the current queue and measured step durations; null once the job has ended.
2025
+ */
2026
+ estimated_completion_at: string | null;
2027
+ /** @description One entry per requested step. */
2028
+ steps: {
2029
+ moderate?: components["schemas"]["MediaJobStep"];
2030
+ transcode?: components["schemas"]["MediaJobStep"];
2031
+ poster?: components["schemas"]["MediaJobStep"];
2032
+ captions?: components["schemas"]["MediaJobStep"];
2033
+ };
2034
+ /** @description Set when the job completes (empty after a `never` verdict); null before. */
2035
+ outputs: components["schemas"]["MediaOutput"][] | null;
2036
+ error?: components["schemas"]["Problem"];
2037
+ /** @description 1, plus one per retry from Studio's dead letters */
2038
+ attempts: number;
2039
+ context: {
2040
+ [key: string]: string;
2041
+ };
2042
+ /** Format: date-time */
2043
+ created_at: string;
2044
+ /** Format: date-time */
2045
+ completed_at?: string;
2046
+ };
2047
+ MediaJobCreated: components["schemas"]["MediaJob"] & {
2048
+ /** @description `202` only: the expected wait is past the tenant's soft limit. */
2049
+ delayed?: boolean;
2050
+ /** @description `200` only: the earlier job with the same content, steps and policy version. */
2051
+ deduplicated?: boolean;
2052
+ };
2053
+ MediaJobProgress: {
2054
+ id: string;
2055
+ ref: string;
2056
+ /** @enum {string} */
2057
+ status: "processing";
2058
+ /** @enum {string} */
2059
+ stage: "fetching" | "analyzing" | "transcoding" | "uploading";
2060
+ progress_percent: number;
2061
+ /** Format: date-time */
2062
+ estimated_completion_at: string | null;
2063
+ };
2064
+ MediaJobStepCompleted: components["schemas"]["MediaJob"] & {
2065
+ /** @enum {string} */
2066
+ completed_step: "moderate" | "transcode" | "poster" | "captions";
2067
+ };
2068
+ MediaProfile: {
2069
+ /** @example tour_hls_v1 */
2070
+ id: string;
2071
+ description: string;
2072
+ /** @enum {string} */
2073
+ container: "hls";
2074
+ segment_seconds?: number;
2075
+ video_codec?: string;
2076
+ audio_codec?: string;
2077
+ renditions: {
2078
+ name: string;
2079
+ height: number;
2080
+ video_kbps?: number;
2081
+ audio_kbps?: number;
2082
+ }[];
2083
+ fallback?: {
2084
+ container?: string;
2085
+ height?: number;
2086
+ faststart?: boolean;
2087
+ };
2088
+ };
2089
+ ScreeningRequest: {
2090
+ /** @default default */
2091
+ policy: string;
2092
+ surface: components["schemas"]["Surface"];
2093
+ subject: components["schemas"]["Subject"];
2094
+ text: string;
2095
+ context?: {
2096
+ conversation_ref?: string;
2097
+ booking_exists?: boolean;
2098
+ previous_messages?: {
2099
+ /** @enum {string} */
2100
+ author: "self" | "other";
2101
+ text: string;
2102
+ }[];
2103
+ };
2104
+ };
2105
+ ScreeningResponse: {
2106
+ decision: components["schemas"]["Decision"];
2107
+ /** @enum {string} */
2108
+ action: "allow" | "nudge" | "hold" | "block";
2109
+ nudge?: {
2110
+ /** @enum {string} */
2111
+ audience: "author" | "recipient" | "both";
2112
+ text: string;
2113
+ };
2114
+ };
2115
+ FeedbackRecord: {
2116
+ decision_id?: string;
2117
+ turn_id?: string;
2118
+ /** @enum {string} */
2119
+ outcome: "approved" | "rejected" | "edited" | "escalated" | "user_appealed" | "appeal_upheld" | "appeal_denied" | "thumbs_up" | "thumbs_down";
2120
+ /** @enum {string} */
2121
+ final_class?: "clean" | "review" | "hold" | "never";
2122
+ reason_codes?: string[];
2123
+ reviewer?: {
2124
+ /** @enum {string} */
2125
+ role?: "admin" | "moderator" | "support" | "end_user";
2126
+ ref?: string;
2127
+ };
2128
+ /** Format: date-time */
2129
+ decided_at: string;
2130
+ note?: string;
2131
+ } & (unknown | unknown);
2132
+ FeedbackBatch: {
2133
+ records: components["schemas"]["FeedbackRecord"][];
2134
+ };
2135
+ WebhookEnvelope: {
2136
+ /** @description evt_…; deduplicate on it */
2137
+ id: string;
2138
+ /** @enum {string} */
2139
+ type: "moderation.item.completed" | "moderation.job.completed" | "document_check.completed" | "session.completed" | "subject.erased" | "usage.threshold_reached" | "feature.state_changed" | "media.job.queued" | "media.job.progress" | "media.job.step.completed" | "media.job.completed" | "media.job.failed" | "media.job.cancelled" | "conversation.updated" | "conversation.completed" | "conversation.failed" | "conversation.expired";
2140
+ /** Format: date-time */
2141
+ created_at: string;
2142
+ /** @enum {string} */
2143
+ environment: "mock" | "development" | "production";
2144
+ data: Record<string, unknown>;
2145
+ };
2146
+ /** @enum {string} */
2147
+ DocumentType: "driver_license" | "state_id" | "business_license" | "cosmetology_license" | "insurance_card" | "insurance_certificate";
2148
+ DocumentCheckCreate: {
2149
+ ref: string;
2150
+ document_type: components["schemas"]["DocumentType"];
2151
+ subject: components["schemas"]["Subject"];
2152
+ pages: {
2153
+ /** @enum {string} */
2154
+ side: "front" | "back" | "page";
2155
+ media: {
2156
+ /** Format: uri */
2157
+ url: string;
2158
+ /** Format: date-time */
2159
+ url_expires_at?: string;
2160
+ /** @enum {string} */
2161
+ content_type: "image/jpeg" | "image/png" | "image/webp" | "image/heic" | "application/pdf";
2162
+ bytes?: number;
2163
+ sha256?: string;
2164
+ };
2165
+ }[];
2166
+ expected?: {
2167
+ full_name?: string | null;
2168
+ business_name?: string | null;
2169
+ region?: string | null;
2170
+ };
2171
+ };
2172
+ DocumentFlag: {
2173
+ /** @enum {string} */
2174
+ code: "expired" | "expires_soon" | "name_mismatch" | "business_name_mismatch" | "region_mismatch" | "wrong_document_type" | "unreadable" | "incomplete" | "photo_of_screen" | "possible_edit" | "barcode_text_mismatch";
2175
+ /** @enum {string} */
2176
+ severity: "info" | "review";
2177
+ detail?: string;
2178
+ /** @description for *_mismatch: true when normalisation found them equivalent */
2179
+ matched?: boolean;
2180
+ /** @enum {string} */
2181
+ signal_strength?: "weak" | "moderate" | "strong";
2182
+ };
2183
+ DocumentCheck: {
2184
+ id: string;
2185
+ ref: string;
2186
+ /** @enum {string} */
2187
+ status: "queued" | "processing" | "completed" | "failed";
2188
+ document_type_detected?: string | null;
2189
+ /** @description null after 24 hours; the date of birth is never returned */
2190
+ fields?: {
2191
+ full_name?: string | null;
2192
+ document_number?: string | null;
2193
+ issuer?: string | null;
2194
+ region?: string | null;
2195
+ license_class?: string | null;
2196
+ /** Format: date */
2197
+ issued_on?: string | null;
2198
+ /** Format: date */
2199
+ expires_on?: string | null;
2200
+ date_of_birth_present?: boolean | null;
2201
+ business_name?: string | null;
2202
+ insured_name?: string | null;
2203
+ policy_number?: string | null;
2204
+ carrier?: string | null;
2205
+ /** Format: date */
2206
+ coverage_end?: string | null;
2207
+ } | null;
2208
+ field_sources?: {
2209
+ [key: string]: "barcode" | "text_recognition" | "visual";
2210
+ } | null;
2211
+ flags?: components["schemas"]["DocumentFlag"][];
2212
+ decision?: components["schemas"]["Decision"];
2213
+ model?: {
2214
+ id?: string;
2215
+ provider?: string;
2216
+ };
2217
+ error?: components["schemas"]["Problem"];
2218
+ /** Format: date-time */
2219
+ completed_at?: string;
2220
+ /** Format: date-time */
2221
+ fields_expire_at?: string;
2222
+ processing_ms?: number;
2223
+ };
2224
+ /** @description Common fields; the per-type fields and their strictness come from the event catalogue's JSON Schemas (contract §13.2). */
2225
+ Event: {
2226
+ event_id: string;
2227
+ /** @enum {string} */
2228
+ type: "search.performed" | "listing.viewed" | "booking.requested" | "booking.confirmed" | "booking.completed" | "booking.no_show" | "booking.cancelled" | "signup.completed" | "listing.submitted" | "listing.approved" | "listing.rejected" | "lead.created" | "lead.contacted" | "lead.converted" | "review.posted";
2229
+ /** Format: date-time */
2230
+ occurred_at: string;
2231
+ subject_id: components["schemas"]["SubjectId"];
2232
+ subject_type: components["schemas"]["SubjectType"];
2233
+ } & {
2234
+ [key: string]: unknown;
2235
+ };
2236
+ };
2237
+ responses: {
2238
+ /** @description Problem details (RFC 9457) */
2239
+ Problem: {
2240
+ headers: {
2241
+ "Vinaverse-Request-Id": components["headers"]["RequestId"];
2242
+ [name: string]: unknown;
2243
+ };
2244
+ content: {
2245
+ "application/problem+json": components["schemas"]["Problem"];
2246
+ };
2247
+ };
2248
+ /** @description Too many requests */
2249
+ RateLimited: {
2250
+ headers: {
2251
+ "Retry-After"?: number;
2252
+ [name: string]: unknown;
2253
+ };
2254
+ content: {
2255
+ "application/problem+json": components["schemas"]["Problem"];
2256
+ };
2257
+ };
2258
+ };
2259
+ parameters: {
2260
+ IdempotencyKey: string;
2261
+ /** @description Accepted, not required (declared in v1-draft.22). Scoped to the tenant and environment: the same key and request within 24 hours answers the first success again (`Idempotent-Replayed: true`); the same key with another request is `409 idempotency_conflict`. */
2262
+ OptionalIdempotencyKey: string;
2263
+ MediaJobId: string;
2264
+ ConversationId: components["schemas"]["ConversationId"];
2265
+ SessionId: string;
2266
+ };
2267
+ requestBodies: never;
2268
+ headers: {
2269
+ /** @description Vinaverse-Request-Id */
2270
+ RequestId: string;
2271
+ };
2272
+ pathItems: never;
2273
+ }
2274
+ interface operations {
2275
+ createSession: {
2276
+ parameters: {
2277
+ query?: never;
2278
+ header: {
2279
+ "Idempotency-Key": components["parameters"]["IdempotencyKey"];
2280
+ };
2281
+ path?: never;
2282
+ cookie?: never;
2283
+ };
2284
+ requestBody: {
2285
+ content: {
2286
+ "application/json": components["schemas"]["SessionCreate"];
2287
+ };
2288
+ };
2289
+ responses: {
2290
+ /** @description Created */
2291
+ 201: {
2292
+ headers: {
2293
+ [name: string]: unknown;
2294
+ };
2295
+ content: {
2296
+ "application/json": components["schemas"]["Session"];
2297
+ };
2298
+ };
2299
+ 400: components["responses"]["Problem"];
2300
+ 401: components["responses"]["Problem"];
2301
+ 403: components["responses"]["Problem"];
2302
+ 404: components["responses"]["Problem"];
2303
+ 409: components["responses"]["Problem"];
2304
+ 410: components["responses"]["Problem"];
2305
+ 429: components["responses"]["RateLimited"];
2306
+ 503: components["responses"]["Problem"];
2307
+ };
2308
+ };
2309
+ createSuggestions: {
2310
+ parameters: {
2311
+ query?: never;
2312
+ header?: never;
2313
+ path?: never;
2314
+ cookie?: never;
2315
+ };
2316
+ requestBody: {
2317
+ content: {
2318
+ "application/json": components["schemas"]["SuggestionsRequest"];
2319
+ };
2320
+ };
2321
+ responses: {
2322
+ /** @description OK */
2323
+ 200: {
2324
+ headers: {
2325
+ [name: string]: unknown;
2326
+ };
2327
+ content: {
2328
+ "application/json": components["schemas"]["SuggestionsResponse"];
2329
+ };
2330
+ };
2331
+ 400: components["responses"]["Problem"];
2332
+ 401: components["responses"]["Problem"];
2333
+ 403: components["responses"]["Problem"];
2334
+ 429: components["responses"]["RateLimited"];
2335
+ 503: components["responses"]["Problem"];
2336
+ };
2337
+ };
2338
+ listConversations: {
2339
+ parameters: {
2340
+ query: {
2341
+ subject_id: components["schemas"]["SubjectId"];
2342
+ agent: string;
2343
+ /** @description the side of the app; left out, the agent's template's */
2344
+ experience_mode?: components["schemas"]["ExperienceMode"];
2345
+ /** @description the business the person acts for; only with `experience_mode=business` */
2346
+ business_scope_id?: components["schemas"]["BusinessScopeId"];
2347
+ limit?: number;
2348
+ cursor?: string;
2349
+ };
2350
+ header?: never;
2351
+ path?: never;
2352
+ cookie?: never;
2353
+ };
2354
+ requestBody?: never;
2355
+ responses: {
2356
+ /** @description Newest activity first. */
2357
+ 200: {
2358
+ headers: {
2359
+ [name: string]: unknown;
2360
+ };
2361
+ content: {
2362
+ "application/json": components["schemas"]["ConversationPage"];
2363
+ };
2364
+ };
2365
+ 400: components["responses"]["Problem"];
2366
+ 401: components["responses"]["Problem"];
2367
+ 403: components["responses"]["Problem"];
2368
+ 503: components["responses"]["Problem"];
2369
+ };
2370
+ };
2371
+ getConversation: {
2372
+ parameters: {
2373
+ query?: never;
2374
+ header?: never;
2375
+ path: {
2376
+ conversation_id: components["parameters"]["ConversationId"];
2377
+ };
2378
+ cookie?: never;
2379
+ };
2380
+ requestBody?: never;
2381
+ responses: {
2382
+ /** @description OK */
2383
+ 200: {
2384
+ headers: {
2385
+ [name: string]: unknown;
2386
+ };
2387
+ content: {
2388
+ "application/json": components["schemas"]["ConversationState"] & unknown;
2389
+ };
2390
+ };
2391
+ 401: components["responses"]["Problem"];
2392
+ 403: components["responses"]["Problem"];
2393
+ 404: components["responses"]["Problem"];
2394
+ 410: components["responses"]["Problem"];
2395
+ };
2396
+ };
2397
+ deleteConversation: {
2398
+ parameters: {
2399
+ query: {
2400
+ subject_id: components["schemas"]["SubjectId"];
2401
+ };
2402
+ header?: never;
2403
+ path: {
2404
+ conversation_id: components["parameters"]["ConversationId"];
2405
+ };
2406
+ cookie?: never;
2407
+ };
2408
+ requestBody?: never;
2409
+ responses: {
2410
+ /** @description Deleted. */
2411
+ 200: {
2412
+ headers: {
2413
+ [name: string]: unknown;
2414
+ };
2415
+ content: {
2416
+ "application/json": components["schemas"]["ConversationDeleted"];
2417
+ };
2418
+ };
2419
+ 401: components["responses"]["Problem"];
2420
+ 403: components["responses"]["Problem"];
2421
+ 404: components["responses"]["Problem"];
2422
+ 503: components["responses"]["Problem"];
2423
+ };
2424
+ };
2425
+ listConversationMessages: {
2426
+ parameters: {
2427
+ query: {
2428
+ subject_id: components["schemas"]["SubjectId"];
2429
+ limit?: number;
2430
+ cursor?: string;
2431
+ };
2432
+ header?: never;
2433
+ path: {
2434
+ conversation_id: components["parameters"]["ConversationId"];
2435
+ };
2436
+ cookie?: never;
2437
+ };
2438
+ requestBody?: never;
2439
+ responses: {
2440
+ /** @description The newest page first; oldest first within a page. */
2441
+ 200: {
2442
+ headers: {
2443
+ [name: string]: unknown;
2444
+ };
2445
+ content: {
2446
+ "application/json": components["schemas"]["TimelinePage"];
2447
+ };
2448
+ };
2449
+ 400: components["responses"]["Problem"];
2450
+ 401: components["responses"]["Problem"];
2451
+ 403: components["responses"]["Problem"];
2452
+ 404: components["responses"]["Problem"];
2453
+ 410: components["responses"]["Problem"];
2454
+ 503: components["responses"]["Problem"];
2455
+ };
2456
+ };
2457
+ getSession: {
2458
+ parameters: {
2459
+ query?: never;
2460
+ header?: never;
2461
+ path: {
2462
+ session_id: components["parameters"]["SessionId"];
2463
+ };
2464
+ cookie?: never;
2465
+ };
2466
+ requestBody?: never;
2467
+ responses: {
2468
+ /** @description OK */
2469
+ 200: {
2470
+ headers: {
2471
+ [name: string]: unknown;
2472
+ };
2473
+ content: {
2474
+ "application/json": components["schemas"]["SessionStatus"];
2475
+ };
2476
+ };
2477
+ 404: components["responses"]["Problem"];
2478
+ };
2479
+ };
2480
+ revokeSession: {
2481
+ parameters: {
2482
+ query?: never;
2483
+ header?: {
2484
+ /** @description Accepted, not required (declared in v1-draft.22). Scoped to the tenant and environment: the same key and request within 24 hours answers the first success again (`Idempotent-Replayed: true`); the same key with another request is `409 idempotency_conflict`. */
2485
+ "Idempotency-Key"?: components["parameters"]["OptionalIdempotencyKey"];
2486
+ };
2487
+ path: {
2488
+ session_id: components["parameters"]["SessionId"];
2489
+ };
2490
+ cookie?: never;
2491
+ };
2492
+ requestBody?: never;
2493
+ responses: {
2494
+ /** @description Revoked (idempotent) */
2495
+ 200: {
2496
+ headers: {
2497
+ [name: string]: unknown;
2498
+ };
2499
+ content: {
2500
+ "application/json": components["schemas"]["SessionStatus"];
2501
+ };
2502
+ };
2503
+ 404: components["responses"]["Problem"];
2504
+ 409: components["responses"]["Problem"];
2505
+ };
2506
+ };
2507
+ refreshClientSecret: {
2508
+ parameters: {
2509
+ query?: never;
2510
+ header?: {
2511
+ /** @description Accepted, not required (declared in v1-draft.22). Scoped to the tenant and environment: the same key and request within 24 hours answers the first success again (`Idempotent-Replayed: true`); the same key with another request is `409 idempotency_conflict`. */
2512
+ "Idempotency-Key"?: components["parameters"]["OptionalIdempotencyKey"];
2513
+ };
2514
+ path: {
2515
+ session_id: components["parameters"]["SessionId"];
2516
+ };
2517
+ cookie?: never;
2518
+ };
2519
+ requestBody?: never;
2520
+ responses: {
2521
+ /** @description OK */
2522
+ 200: {
2523
+ headers: {
2524
+ [name: string]: unknown;
2525
+ };
2526
+ content: {
2527
+ "application/json": components["schemas"]["ClientSecret"];
2528
+ };
2529
+ };
2530
+ 409: components["responses"]["Problem"];
2531
+ };
2532
+ };
2533
+ createTurnSse: {
2534
+ parameters: {
2535
+ query?: never;
2536
+ header?: never;
2537
+ path: {
2538
+ session_id: components["parameters"]["SessionId"];
2539
+ };
2540
+ cookie?: never;
2541
+ };
2542
+ requestBody: {
2543
+ content: {
2544
+ "application/json": components["schemas"]["TurnInput"];
2545
+ };
2546
+ };
2547
+ responses: {
2548
+ /** @description `text/event-stream`. Each event's `event:` is a ServerEvent `type`; `data:` is the ServerEvent JSON. */
2549
+ 200: {
2550
+ headers: {
2551
+ [name: string]: unknown;
2552
+ };
2553
+ content: {
2554
+ "text/event-stream": string;
2555
+ };
2556
+ };
2557
+ 401: components["responses"]["Problem"];
2558
+ 403: components["responses"]["Problem"];
2559
+ 409: components["responses"]["Problem"];
2560
+ 429: components["responses"]["RateLimited"];
2561
+ };
2562
+ };
2563
+ createCardActionSse: {
2564
+ parameters: {
2565
+ query?: never;
2566
+ header?: never;
2567
+ path: {
2568
+ session_id: components["parameters"]["SessionId"];
2569
+ turn_id: string;
2570
+ };
2571
+ cookie?: never;
2572
+ };
2573
+ requestBody: {
2574
+ content: {
2575
+ "application/json": {
2576
+ card_id: string;
2577
+ action: string;
2578
+ /** @description as on the WebSocket `ui.action`; required for a proposal outcome and for `select_slot` (the server refuses either without it) */
2579
+ client_event_id?: string;
2580
+ /** @description `select_slot` only (v1-draft.17): the slot picked, as on the WebSocket `ui.action` */
2581
+ slot?: components["schemas"]["SlotSelection"];
2582
+ };
2583
+ };
2584
+ };
2585
+ responses: {
2586
+ /** @description `text/event-stream` as for createTurnSse */
2587
+ 200: {
2588
+ headers: {
2589
+ [name: string]: unknown;
2590
+ };
2591
+ content: {
2592
+ "text/event-stream": string;
2593
+ };
2594
+ };
2595
+ 403: components["responses"]["Problem"];
2596
+ };
2597
+ };
2598
+ createModerationJob: {
2599
+ parameters: {
2600
+ query?: never;
2601
+ header: {
2602
+ "Idempotency-Key": components["parameters"]["IdempotencyKey"];
2603
+ };
2604
+ path?: never;
2605
+ cookie?: never;
2606
+ };
2607
+ requestBody: {
2608
+ content: {
2609
+ "application/json": components["schemas"]["ModerationJobCreate"];
2610
+ };
2611
+ };
2612
+ responses: {
2613
+ /** @description Accepted */
2614
+ 202: {
2615
+ headers: {
2616
+ [name: string]: unknown;
2617
+ };
2618
+ content: {
2619
+ "application/json": components["schemas"]["ModerationJob"];
2620
+ };
2621
+ };
2622
+ 400: components["responses"]["Problem"];
2623
+ 403: components["responses"]["Problem"];
2624
+ 413: components["responses"]["Problem"];
2625
+ 429: components["responses"]["RateLimited"];
2626
+ };
2627
+ };
2628
+ getModerationJob: {
2629
+ parameters: {
2630
+ query?: never;
2631
+ header?: never;
2632
+ path: {
2633
+ job_id: string;
2634
+ };
2635
+ cookie?: never;
2636
+ };
2637
+ requestBody?: never;
2638
+ responses: {
2639
+ /** @description OK */
2640
+ 200: {
2641
+ headers: {
2642
+ [name: string]: unknown;
2643
+ };
2644
+ content: {
2645
+ "application/json": components["schemas"]["ModerationJob"];
2646
+ };
2647
+ };
2648
+ 404: components["responses"]["Problem"];
2649
+ };
2650
+ };
2651
+ getModerationItem: {
2652
+ parameters: {
2653
+ query?: never;
2654
+ header?: never;
2655
+ path: {
2656
+ item_id: string;
2657
+ };
2658
+ cookie?: never;
2659
+ };
2660
+ requestBody?: never;
2661
+ responses: {
2662
+ /** @description OK */
2663
+ 200: {
2664
+ headers: {
2665
+ [name: string]: unknown;
2666
+ };
2667
+ content: {
2668
+ "application/json": components["schemas"]["ModerationItem"];
2669
+ };
2670
+ };
2671
+ 404: components["responses"]["Problem"];
2672
+ };
2673
+ };
2674
+ getAgentAvatarImage: {
2675
+ parameters: {
2676
+ query?: never;
2677
+ header?: never;
2678
+ path: {
2679
+ /** @description the SHA-256 of the image's bytes, its first 32 hex digits, then `.webp` */
2680
+ avatar_file: string;
2681
+ };
2682
+ cookie?: never;
2683
+ };
2684
+ requestBody?: never;
2685
+ responses: {
2686
+ /** @description the image */
2687
+ 200: {
2688
+ headers: {
2689
+ [name: string]: unknown;
2690
+ };
2691
+ content: {
2692
+ "image/webp": string;
2693
+ };
2694
+ };
2695
+ 404: components["responses"]["Problem"];
2696
+ };
2697
+ };
2698
+ listMediaJobs: {
2699
+ parameters: {
2700
+ query?: {
2701
+ ref?: string;
2702
+ status?: "queued" | "processing" | "completed" | "failed" | "cancelled";
2703
+ limit?: number;
2704
+ cursor?: string;
2705
+ };
2706
+ header?: never;
2707
+ path?: never;
2708
+ cookie?: never;
2709
+ };
2710
+ requestBody?: never;
2711
+ responses: {
2712
+ /** @description Newest first. */
2713
+ 200: {
2714
+ headers: {
2715
+ [name: string]: unknown;
2716
+ };
2717
+ content: {
2718
+ "application/json": {
2719
+ data: components["schemas"]["MediaJob"][];
2720
+ next_cursor: string | null;
2721
+ };
2722
+ };
2723
+ };
2724
+ };
2725
+ };
2726
+ createMediaJob: {
2727
+ parameters: {
2728
+ query?: never;
2729
+ header: {
2730
+ "Idempotency-Key": components["parameters"]["IdempotencyKey"];
2731
+ };
2732
+ path?: never;
2733
+ cookie?: never;
2734
+ };
2735
+ requestBody: {
2736
+ content: {
2737
+ "application/json": components["schemas"]["MediaJobCreate"];
2738
+ };
2739
+ };
2740
+ responses: {
2741
+ /** @description The same content was processed with the same steps and policy in the last 30 days. */
2742
+ 200: {
2743
+ headers: {
2744
+ [name: string]: unknown;
2745
+ };
2746
+ content: {
2747
+ "application/json": components["schemas"]["MediaJobCreated"];
2748
+ };
2749
+ };
2750
+ /** @description Accepted */
2751
+ 202: {
2752
+ headers: {
2753
+ [name: string]: unknown;
2754
+ };
2755
+ content: {
2756
+ "application/json": components["schemas"]["MediaJobCreated"];
2757
+ };
2758
+ };
2759
+ 400: components["responses"]["Problem"];
2760
+ 403: components["responses"]["Problem"];
2761
+ 409: components["responses"]["Problem"];
2762
+ 413: components["responses"]["Problem"];
2763
+ 429: components["responses"]["RateLimited"];
2764
+ };
2765
+ };
2766
+ getMediaJob: {
2767
+ parameters: {
2768
+ query?: never;
2769
+ header?: never;
2770
+ path: {
2771
+ job_id: components["parameters"]["MediaJobId"];
2772
+ };
2773
+ cookie?: never;
2774
+ };
2775
+ requestBody?: never;
2776
+ responses: {
2777
+ /** @description OK */
2778
+ 200: {
2779
+ headers: {
2780
+ [name: string]: unknown;
2781
+ };
2782
+ content: {
2783
+ "application/json": components["schemas"]["MediaJob"];
2784
+ };
2785
+ };
2786
+ 404: components["responses"]["Problem"];
2787
+ };
2788
+ };
2789
+ cancelMediaJob: {
2790
+ parameters: {
2791
+ query?: never;
2792
+ header?: never;
2793
+ path: {
2794
+ job_id: components["parameters"]["MediaJobId"];
2795
+ };
2796
+ cookie?: never;
2797
+ };
2798
+ requestBody?: never;
2799
+ responses: {
2800
+ /** @description The job, `cancelled` (or as it ended). */
2801
+ 200: {
2802
+ headers: {
2803
+ [name: string]: unknown;
2804
+ };
2805
+ content: {
2806
+ "application/json": components["schemas"]["MediaJob"];
2807
+ };
2808
+ };
2809
+ 404: components["responses"]["Problem"];
2810
+ };
2811
+ };
2812
+ listMediaProfiles: {
2813
+ parameters: {
2814
+ query?: never;
2815
+ header?: never;
2816
+ path?: never;
2817
+ cookie?: never;
2818
+ };
2819
+ requestBody?: never;
2820
+ responses: {
2821
+ /** @description The transcoding profiles, named and versioned. */
2822
+ 200: {
2823
+ headers: {
2824
+ [name: string]: unknown;
2825
+ };
2826
+ content: {
2827
+ "application/json": {
2828
+ data: components["schemas"]["MediaProfile"][];
2829
+ };
2830
+ };
2831
+ };
2832
+ };
2833
+ };
2834
+ screenText: {
2835
+ parameters: {
2836
+ query?: never;
2837
+ header?: never;
2838
+ path?: never;
2839
+ cookie?: never;
2840
+ };
2841
+ requestBody: {
2842
+ content: {
2843
+ "application/json": components["schemas"]["ScreeningRequest"];
2844
+ };
2845
+ };
2846
+ responses: {
2847
+ /** @description OK */
2848
+ 200: {
2849
+ headers: {
2850
+ [name: string]: unknown;
2851
+ };
2852
+ content: {
2853
+ "application/json": components["schemas"]["ScreeningResponse"];
2854
+ };
2855
+ };
2856
+ 400: components["responses"]["Problem"];
2857
+ 403: components["responses"]["Problem"];
2858
+ 429: components["responses"]["RateLimited"];
2859
+ 503: components["responses"]["Problem"];
2860
+ };
2861
+ };
2862
+ createDocumentCheck: {
2863
+ parameters: {
2864
+ query?: never;
2865
+ header: {
2866
+ "Idempotency-Key": components["parameters"]["IdempotencyKey"];
2867
+ };
2868
+ path?: never;
2869
+ cookie?: never;
2870
+ };
2871
+ requestBody: {
2872
+ content: {
2873
+ "application/json": components["schemas"]["DocumentCheckCreate"];
2874
+ };
2875
+ };
2876
+ responses: {
2877
+ /** @description Accepted */
2878
+ 202: {
2879
+ headers: {
2880
+ [name: string]: unknown;
2881
+ };
2882
+ content: {
2883
+ "application/json": {
2884
+ id: string;
2885
+ /** @enum {string} */
2886
+ status: "queued";
2887
+ estimated_completion_seconds?: number;
2888
+ };
2889
+ };
2890
+ };
2891
+ 400: components["responses"]["Problem"];
2892
+ 403: components["responses"]["Problem"];
2893
+ 429: components["responses"]["RateLimited"];
2894
+ };
2895
+ };
2896
+ getDocumentCheck: {
2897
+ parameters: {
2898
+ query?: never;
2899
+ header?: never;
2900
+ path: {
2901
+ check_id: string;
2902
+ };
2903
+ cookie?: never;
2904
+ };
2905
+ requestBody?: never;
2906
+ responses: {
2907
+ /** @description OK */
2908
+ 200: {
2909
+ headers: {
2910
+ [name: string]: unknown;
2911
+ };
2912
+ content: {
2913
+ "application/json": components["schemas"]["DocumentCheck"];
2914
+ };
2915
+ };
2916
+ 404: components["responses"]["Problem"];
2917
+ };
2918
+ };
2919
+ sendFeedback: {
2920
+ parameters: {
2921
+ query?: never;
2922
+ header?: never;
2923
+ path?: never;
2924
+ cookie?: never;
2925
+ };
2926
+ requestBody: {
2927
+ content: {
2928
+ "application/json": components["schemas"]["FeedbackBatch"];
2929
+ };
2930
+ };
2931
+ responses: {
2932
+ /** @description Accepted */
2933
+ 202: {
2934
+ headers: {
2935
+ [name: string]: unknown;
2936
+ };
2937
+ content: {
2938
+ "application/json": {
2939
+ accepted: number;
2940
+ rejected: {
2941
+ index: number;
2942
+ code: string;
2943
+ detail?: string;
2944
+ }[];
2945
+ };
2946
+ };
2947
+ };
2948
+ 400: components["responses"]["Problem"];
2949
+ };
2950
+ };
2951
+ eraseSubject: {
2952
+ parameters: {
2953
+ query?: never;
2954
+ header: {
2955
+ "Idempotency-Key": components["parameters"]["IdempotencyKey"];
2956
+ };
2957
+ path: {
2958
+ subject_id: components["schemas"]["SubjectId"];
2959
+ };
2960
+ cookie?: never;
2961
+ };
2962
+ requestBody?: never;
2963
+ responses: {
2964
+ /** @description Accepted; `subject.erased` follows */
2965
+ 202: {
2966
+ headers: {
2967
+ [name: string]: unknown;
2968
+ };
2969
+ content: {
2970
+ "application/json": {
2971
+ subject_id: components["schemas"]["SubjectId"];
2972
+ /** @enum {string} */
2973
+ status: "accepted" | "completed";
2974
+ sessions_revoked: number;
2975
+ /** Format: date-time */
2976
+ deadline: string;
2977
+ };
2978
+ };
2979
+ };
2980
+ /** @description Nothing held for the subject (never seen, or already erased); the problem's `type` ends in `/problems/subject-not-found` */
2981
+ 404: {
2982
+ headers: {
2983
+ "Vinaverse-Request-Id": components["headers"]["RequestId"];
2984
+ [name: string]: unknown;
2985
+ };
2986
+ content: {
2987
+ "application/problem+json": components["schemas"]["Problem"] & {
2988
+ /** @constant */
2989
+ code?: "not_found";
2990
+ type?: unknown;
2991
+ };
2992
+ };
2993
+ };
2994
+ };
2995
+ };
2996
+ sendEvents: {
2997
+ parameters: {
2998
+ query?: never;
2999
+ header?: never;
3000
+ path?: never;
3001
+ cookie?: never;
3002
+ };
3003
+ requestBody: {
3004
+ content: {
3005
+ "application/json": {
3006
+ events: components["schemas"]["Event"][];
3007
+ };
3008
+ };
3009
+ };
3010
+ responses: {
3011
+ /** @description Accepted */
3012
+ 202: {
3013
+ headers: {
3014
+ [name: string]: unknown;
3015
+ };
3016
+ content: {
3017
+ "application/json": {
3018
+ accepted: number;
3019
+ duplicates: number;
3020
+ batch_id: string;
3021
+ batch_cid: string;
3022
+ };
3023
+ };
3024
+ };
3025
+ 400: components["responses"]["Problem"];
3026
+ 403: components["responses"]["Problem"];
3027
+ 429: components["responses"]["RateLimited"];
3028
+ };
3029
+ };
3030
+ sendKnowledgeDocument: {
3031
+ parameters: {
3032
+ query?: never;
3033
+ header?: never;
3034
+ path?: never;
3035
+ cookie?: never;
3036
+ };
3037
+ requestBody: {
3038
+ content: {
3039
+ "application/json": {
3040
+ doc_id: string;
3041
+ version: string;
3042
+ title: string;
3043
+ /** @description Markdown */
3044
+ body: string;
3045
+ /** @enum {string} */
3046
+ audience: "public" | "staff";
3047
+ /** @default en */
3048
+ language?: string;
3049
+ /** Format: uri */
3050
+ url?: string;
3051
+ };
3052
+ };
3053
+ };
3054
+ responses: {
3055
+ /** @description Same version and body already stored (no change) */
3056
+ 200: {
3057
+ headers: {
3058
+ [name: string]: unknown;
3059
+ };
3060
+ content?: never;
3061
+ };
3062
+ /** @description Stored */
3063
+ 201: {
3064
+ headers: {
3065
+ [name: string]: unknown;
3066
+ };
3067
+ content: {
3068
+ "application/json": {
3069
+ doc_id: string;
3070
+ version: string;
3071
+ audience: string;
3072
+ artifact_cid: string;
3073
+ chunks: number;
3074
+ };
3075
+ };
3076
+ };
3077
+ 400: components["responses"]["Problem"];
3078
+ 403: components["responses"]["Problem"];
3079
+ };
3080
+ };
3081
+ putCatalogEntity: {
3082
+ parameters: {
3083
+ query?: never;
3084
+ header?: never;
3085
+ path: {
3086
+ type: "professional" | "workspace" | "service_type" | "area";
3087
+ id: string;
3088
+ };
3089
+ cookie?: never;
3090
+ };
3091
+ requestBody: {
3092
+ content: {
3093
+ "application/json": {
3094
+ name?: string;
3095
+ display_name?: string;
3096
+ title?: string;
3097
+ service_type_ids?: string[];
3098
+ area?: {
3099
+ city?: string;
3100
+ neighborhood?: string;
3101
+ };
3102
+ /** @default true */
3103
+ active?: boolean;
3104
+ };
3105
+ };
3106
+ };
3107
+ responses: {
3108
+ /** @description Stored or unchanged */
3109
+ 200: {
3110
+ headers: {
3111
+ [name: string]: unknown;
3112
+ };
3113
+ content: {
3114
+ "application/json": {
3115
+ type: string;
3116
+ id: string;
3117
+ changed: boolean;
3118
+ snapshot_cid: string | null;
3119
+ };
3120
+ };
3121
+ };
3122
+ 400: components["responses"]["Problem"];
3123
+ 403: components["responses"]["Problem"];
3124
+ };
3125
+ };
3126
+ getUsage: {
3127
+ parameters: {
3128
+ query?: {
3129
+ period?: string;
3130
+ };
3131
+ header?: never;
3132
+ path?: never;
3133
+ cookie?: never;
3134
+ };
3135
+ requestBody?: never;
3136
+ responses: {
3137
+ /** @description OK */
3138
+ 200: {
3139
+ headers: {
3140
+ [name: string]: unknown;
3141
+ };
3142
+ content: {
3143
+ "application/json": components["schemas"]["UsageReport"];
3144
+ };
3145
+ };
3146
+ };
3147
+ };
3148
+ }
3149
+
3150
+ /** Every schema of the pinned public contract (vinaverse-v1), by name: `Schemas['Session']`, `Schemas['Card']`, … */
3151
+ type Schemas = components['schemas'];
3152
+ type ClientEvent = Schemas['ClientEvent'];
3153
+ type ServerEvent = Schemas['ServerEvent'];
3154
+ type ServerEventType = ServerEvent['type'];
3155
+ type ServerEventOf<T extends ServerEventType> = Extract<ServerEvent, {
3156
+ type: T;
3157
+ }>;
3158
+ type Card = Schemas['Card'];
3159
+ type CardKind = Card['kind'];
3160
+ type Question = Schemas['Question'];
3161
+ type AnswerInput = Schemas['AnswerInput'];
3162
+ type Proposal = Schemas['Proposal'];
3163
+ type TranscriptItem = Schemas['TranscriptItem'];
3164
+ type ClientSecret = Schemas['ClientSecret'];
3165
+ type TurnInput = Schemas['TurnInput'];
3166
+ /** v1-draft.20: the tenant's greeting for the session's mode, and the chips that send a message as the person's own. */
3167
+ type Welcome = Schemas['Welcome'];
3168
+ type CardImage = Schemas['CardImage'];
3169
+ type CardGroup = Schemas['CardGroup'];
3170
+ type CardLink = Schemas['CardLink'];
3171
+ /** The session as createSession returns it to the tenant's backend, which passes this part to the app. */
3172
+ type SessionInfo = Pick<Schemas['Session'], 'id' | 'realtime_url' | 'turns_url' | 'conversation_id'> & {
3173
+ client_secret: ClientSecret | string;
3174
+ };
3175
+ /** The outcomes an app reports on a proposal card with `ui.action`. */
3176
+ type ProposalOutcome = 'confirmed' | 'declined' | 'failed' | 'expired';
3177
+ /** Whether this client knows how to read a card of this kind; skip any other card (§5). */
3178
+ declare function isKnownCard(card: {
3179
+ kind: string;
3180
+ }): card is Card;
3181
+
3182
+ /** A frame as received or sent: its `type`, and whatever else the contract gives it. */
3183
+ interface Frame {
3184
+ readonly type: string;
3185
+ readonly [field: string]: unknown;
3186
+ }
3187
+ /** A card as the app holds it. `locked` lists the question ids (the card's, or a `rate_each` row's) drawn locked. */
3188
+ interface HeldCard {
3189
+ readonly id: string;
3190
+ readonly kind: string;
3191
+ readonly card: Readonly<Record<string, unknown>>;
3192
+ readonly question?: HeldQuestion;
3193
+ readonly locked: readonly string[];
3194
+ /** The turn that drew the card, when known: `ui.action` names it with the card's `id`. */
3195
+ readonly turnId?: string;
3196
+ /** A proposal's first reported outcome (`confirmed`, `declined`, `failed`, `expired`); later ones change nothing. */
3197
+ readonly outcome?: string | null;
3198
+ /** Arrival order, shared with messages: a timeline sorts both by it. Moves when the card is drawn again. */
3199
+ readonly seq: number;
3200
+ }
3201
+ interface HeldQuestion {
3202
+ readonly question_id: string;
3203
+ readonly kind: string;
3204
+ readonly state: string;
3205
+ readonly answer?: unknown;
3206
+ readonly row_answers?: readonly {
3207
+ question_id: string;
3208
+ answer: unknown;
3209
+ }[];
3210
+ readonly rows?: readonly {
3211
+ question_id: string;
3212
+ }[];
3213
+ readonly [field: string]: unknown;
3214
+ }
3215
+ interface Message {
3216
+ readonly role: 'assistant' | 'person' | 'staff' | (string & {});
3217
+ readonly text: string;
3218
+ /**
3219
+ * Stable for the message's life, to key it in a list: a person's text by its `client_event_id`, an agent's reply by
3220
+ * its turn (`a:<turn_id>`), a staff reply by its `message_id` (`s:<id>`), a guardrail refusal by the input it answers
3221
+ * (`r:<client_event_id>`), a transcript item by its server `id` as the live key it matches, or by its time and role
3222
+ * (`t:<at>:<role>`) from a server that sends no `id`.
3223
+ */
3224
+ readonly key: string;
3225
+ /** Arrival order, shared with cards: a timeline sorts both by it. */
3226
+ readonly seq: number;
3227
+ /**
3228
+ * A reply cut off after some of it streamed; what streamed stands (§6): `blocked` (`output_blocked`), `interrupted`
3229
+ * (`cancelled`, as when a person from the business takes over, or the connection dropped), `failed` (any other code).
3230
+ */
3231
+ readonly partial?: 'blocked' | 'interrupted' | 'failed';
3232
+ /** A person's text recorded during a takeover (`input.received`): the business reads it; no agent turn runs. */
3233
+ readonly received?: true;
3234
+ /** The agent's reply is a guardrail refusal (`input.rejected` `guardrail`, its `message`). */
3235
+ readonly refusal?: true;
3236
+ }
3237
+ /** A person from the business is replying (`takeover.started`); `by` is their role as the person sees it. */
3238
+ interface Takeover {
3239
+ readonly by: string | null;
3240
+ readonly at: string;
3241
+ }
3242
+ /** The turn's text streamed so far (`message.delta`), until its `message.completed`. */
3243
+ interface Streaming {
3244
+ readonly turnId: string;
3245
+ readonly text: string;
3246
+ /** Taken at the turn's first delta, and kept by its message: a reply sorts before the cards its turn drew after it. */
3247
+ readonly seq: number;
3248
+ }
3249
+ interface Connection {
3250
+ readonly name: string;
3251
+ readonly state: 'open' | 'closed';
3252
+ readonly code?: number;
3253
+ readonly reason?: string;
3254
+ }
3255
+ interface ToolLine {
3256
+ readonly turn_id: string;
3257
+ readonly label: string;
3258
+ readonly state: string;
3259
+ }
3260
+ /**
3261
+ * Whether and how the conversation goes on after a connection ends:
3262
+ * - `allowed`: reconnect to the same session;
3263
+ * - `user_action_only`: the session was revoked or replaced (the conversation continued elsewhere): resume only when
3264
+ * the person asks;
3265
+ * - `new_session_on_next_action`: the session ended (idle, expired, limit_reached) but the conversation did not; its
3266
+ * secret is spent. On the person's next action, the app has its backend create a new session for the same
3267
+ * conversation and calls `resume()`.
3268
+ */
3269
+ type Reconnect = 'allowed' | 'user_action_only' | 'new_session_on_next_action';
3270
+ /** The engine's whole state, for an app to draw. */
3271
+ interface ConversationState {
3272
+ readonly sessionId: string | null;
3273
+ readonly conversationId: string | null;
3274
+ readonly connection: Connection | null;
3275
+ /** How the conversation goes on after the connection ends (`Reconnect`). */
3276
+ readonly reconnect: Reconnect;
3277
+ readonly messages: readonly Message[];
3278
+ /** Every card held, oldest first. */
3279
+ readonly cards: readonly HeldCard[];
3280
+ /** The newest question card still open: the one to answer. */
3281
+ readonly openQuestion: string | null;
3282
+ /** Events sent and neither acknowledged nor refused, by `client_event_id`: resend them, same id, after a reconnect. */
3283
+ readonly unacknowledged: readonly Frame[];
3284
+ /** Ids refused for now (`turn_in_progress`, `screen_unavailable`): send again with the same id. */
3285
+ readonly resend: readonly string[];
3286
+ /**
3287
+ * Texts and answers refused with `limit_reached`: never resent in this session (it would get the same refusal);
3288
+ * resent, same id, on the next session's `session.ready`. Kept apart from `resend` (conformance README, `held`). A
3289
+ * refused action is dropped: a new session cannot act on an earlier session's card.
3290
+ */
3291
+ readonly held: readonly string[];
3292
+ /** Typed messages whose refusal stands, by `client_event_id`: shown as not sent, never resent. */
3293
+ readonly refused: Readonly<Record<string, string>>;
3294
+ /** The last `flow.completed`: `ok`, `refused` or `failed` (an unknown result reads as `failed`). */
3295
+ readonly flow: {
3296
+ readonly flow: unknown;
3297
+ readonly result: 'ok' | 'refused' | 'failed';
3298
+ } | null;
3299
+ /** The turn in progress. */
3300
+ readonly turn: string | null;
3301
+ /** The last state of each tool line (the last 100). */
3302
+ readonly tools: readonly ToolLine[];
3303
+ readonly handoffs: readonly unknown[];
3304
+ /** The codes of `error` and `turn.failed` events (the last 100). */
3305
+ readonly errors: readonly string[];
3306
+ /**
3307
+ * While `reconnect` is `new_session_on_next_action`: the `client_event_id`s held for the next session (unacknowledged,
3308
+ * or refused for this session's turn limit). Draw them as "sends when you continue", not as failed. Empty otherwise.
3309
+ */
3310
+ readonly heldForNextSession: readonly string[];
3311
+ /** Seconds left when `session.expiring` arrived (WebSocket only, about a minute before the end); null otherwise. */
3312
+ readonly expiring: number | null;
3313
+ /** The turn's text streamed so far, until its `message.completed` (or its end, which keeps it as a message). */
3314
+ readonly streaming: Streaming | null;
3315
+ /** While a person from the business replies (§8): from `takeover.started` to `takeover.ended`. */
3316
+ readonly takeover: Takeover | null;
3317
+ /** Every refusal's code and `message` (`input.rejected`), by `client_event_id`; `refused` lists those that stand. */
3318
+ readonly rejections: Readonly<Record<string, {
3319
+ code: string;
3320
+ message: string;
3321
+ }>>;
3322
+ /** The AI disclosure `session.ready` carries: always shown, never removed. Null until the first one. */
3323
+ readonly disclosure: string | null;
3324
+ /** `session.ready`'s `limits.turns_left`, when given. */
3325
+ readonly turnsLeft: number | null;
3326
+ }
3327
+ declare class ConversationEngine {
3328
+ private sessionId;
3329
+ private conversationId;
3330
+ private connection;
3331
+ private reconnect;
3332
+ private messages;
3333
+ private cards;
3334
+ private unacknowledged;
3335
+ private resend;
3336
+ private held;
3337
+ private refused;
3338
+ private flow;
3339
+ private turn;
3340
+ private tools;
3341
+ private handoffs;
3342
+ private errors;
3343
+ private expiring;
3344
+ private streaming;
3345
+ private takeover;
3346
+ private rejections;
3347
+ private disclosure;
3348
+ private turnsLeft;
3349
+ /** The next arrival number for a message or a card. */
3350
+ private seq;
3351
+ /** The cards drawn before a resumed flow's transcript replaced them: proposal outcomes carry over. */
3352
+ private replaced;
3353
+ /** client_event_id → the first event the app sent under it, until it is settled. */
3354
+ private readonly inputs;
3355
+ /** The tenant's backend created a session (and named its conversation). */
3356
+ sessionCreated(sessionId: string, conversationId?: string | null): void;
3357
+ /** A connection opened: the user's own action, or an allowed reconnect, so it lifts a hold on reconnecting. */
3358
+ connected(name: string): void;
3359
+ /** A connection closed with its code. `4403` (revoked) and `4409` (replaced) hold reconnecting for a user action. */
3360
+ closed(name: string, code: number): void;
3361
+ /** The app was killed: what it drew is gone; what it sent and never saw acknowledged it kept, to resend. */
3362
+ killed(): void;
3363
+ sent(event: Frame): void;
3364
+ received(event: Frame): void;
3365
+ private ready;
3366
+ /**
3367
+ * A transcript merged by key: an item the app holds keeps its place, with the text the server recorded; one it does
3368
+ * not hold goes just after the nearest earlier item it does (just before the first later one when none came before,
3369
+ * at the end when it holds none of them). Nothing the app holds is dropped.
3370
+ */
3371
+ private mergeTranscript;
3372
+ private hold;
3373
+ private recorded;
3374
+ private rejected;
3375
+ private inputRejected;
3376
+ /** No longer to resend, nor held for the next session (sent again, or settled). */
3377
+ private unqueue;
3378
+ /** An input acknowledged or refused for good: nothing more is resent under its id. */
3379
+ private settle;
3380
+ /** Adds a message once: a key already held (a frame seen twice) changes nothing. */
3381
+ private say;
3382
+ /** The turn's streamed text, kept as the agent's message when the turn ends without its `message.completed`. */
3383
+ private cutOff;
3384
+ private error;
3385
+ /**
3386
+ * An input that could not be delivered, or whose retries are spent (`screen_unavailable` after the last try): it is
3387
+ * no longer resent. A typed message is shown as not sent, with `code`; an answer's question unlocks so the person
3388
+ * can answer again; a proposal outcome it reported is taken back, so it can be reported again.
3389
+ */
3390
+ giveUp(clientEventId: string, code: string): void;
3391
+ /** A turn the app stopped where no event will say so (an SSE turn cancelled): none is in progress. */
3392
+ turnEnded(): void;
3393
+ /** The event first sent under a `client_event_id`, to resend it unchanged. */
3394
+ input(clientEventId: string): Frame | undefined;
3395
+ state(): ConversationState;
3396
+ }
3397
+
3398
+ /** Why the client refused to do something; `code` is stable, `message` is for people. */
3399
+ type ClientErrorCode =
3400
+ /** A service key (`vv_sk_`) was given: it belongs on the tenant's backend, never in an app. */
3401
+ 'service_key'
3402
+ /** The value is not a client secret (`vv_cs_` and at least 16 base64url characters). */
3403
+ | 'invalid_secret'
3404
+ /** The action needs the WebSocket (answers to question cards); the session runs over SSE. */
3405
+ | 'needs_websocket'
3406
+ /** The session was closed by the app, or ended for good (replaced, revoked, expired). */
3407
+ | 'session_ended'
3408
+ /** An SSE request failed: `status` holds the HTTP status. */
3409
+ | 'http_error'
3410
+ /**
3411
+ * The session ended but the conversation did not (idle, expired, its turn limit): its secret is spent. Have your
3412
+ * backend create a new session for the conversation and call `resume()` with it.
3413
+ */
3414
+ | 'needs_new_session';
3415
+ declare class VinaverseClientError extends Error {
3416
+ readonly name = "VinaverseClientError";
3417
+ readonly code: ClientErrorCode;
3418
+ readonly status: number | undefined;
3419
+ constructor(code: ClientErrorCode, message: string, status?: number);
3420
+ }
3421
+
3422
+ type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
3423
+ /** One SSE request: where and with which secret, and the `fetch` to use. */
3424
+ interface SseTarget {
3425
+ /** The session's `turns_url` (`…/v1/sessions/{session_id}/turns`). */
3426
+ readonly turnsUrl: string;
3427
+ readonly secret: string;
3428
+ readonly fetch?: FetchLike;
3429
+ readonly signal?: AbortSignal;
3430
+ }
3431
+ /** Run one turn from a typed message (`createTurnSse`), yielding its events as they arrive. */
3432
+ declare function turnSse(target: SseTarget, input: {
3433
+ text: string;
3434
+ client_event_id?: string;
3435
+ }): AsyncGenerator<Frame>;
3436
+ /** Send a card action, such as a proposal outcome, and stream the resulting turn (`createCardActionSse`). */
3437
+ declare function cardActionSse(target: SseTarget, turnId: string, input: {
3438
+ card_id: string;
3439
+ action: string;
3440
+ client_event_id?: string;
3441
+ }): AsyncGenerator<Frame>;
3442
+ /** Frames from an SSE text stream: blank-line separated events, `data:` lines joined; comments and non-JSON skipped. */
3443
+ declare function sseFrames(chunks: AsyncIterable<string>): AsyncGenerator<Frame>;
3444
+
3445
+ /** The part of a WebSocket the session uses: the browser, React Native and Node 22+ ones all fit. */
3446
+ interface WebSocketLike {
3447
+ readonly readyState: number;
3448
+ send(data: string): void;
3449
+ close(code?: number, reason?: string): void;
3450
+ onopen: ((event: unknown) => void) | null;
3451
+ onmessage: ((event: {
3452
+ data: unknown;
3453
+ }) => void) | null;
3454
+ onclose: ((event: {
3455
+ code: number;
3456
+ reason?: string;
3457
+ }) => void) | null;
3458
+ onerror: ((event: unknown) => void) | null;
3459
+ }
3460
+ /**
3461
+ * Opens a WebSocket. `headers` is set only with `auth: 'header'`. The default factory passes them as React Native's
3462
+ * WebSocket takes them (its third argument) or, on Node 22+, as its WebSocket does (`{ protocols, headers }`); a
3463
+ * browser's WebSocket cannot send headers, so browsers use the subprotocol.
3464
+ */
3465
+ type WebSocketFactory = (url: string, protocols: string[], headers?: Record<string, string>) => WebSocketLike;
3466
+ /** How the client secret reaches the server (§1); the secret never goes in the URL. */
3467
+ type AuthMode = 'header' | 'subprotocol' | 'first_frame';
3468
+ type SessionStatus =
3469
+ /** Opening a connection, or waiting for `session.ready`. */
3470
+ 'connecting'
3471
+ /** `session.ready` arrived: events flow. */
3472
+ | 'ready'
3473
+ /** Waiting to reconnect after the connection dropped. */
3474
+ | 'reconnecting'
3475
+ /** Disconnected; connects again on the next send or `connect()`. */
3476
+ | 'idle'
3477
+ /** Running turns over SSE, because the WebSocket could not be opened (twice); `connect()` tries it again. */
3478
+ | 'sse'
3479
+ /**
3480
+ * The session ended. `state.reconnect` says what next: `new_session_on_next_action` after it ended idle, expired
3481
+ * or at its limit (have your backend create a new session for the conversation on the person's next action, then
3482
+ * `resume()` it; what they send meanwhile is held and resent there), or `user_action_only` (the conversation
3483
+ * continued elsewhere, or the session was revoked: resume only when the person asks).
3484
+ */
3485
+ | 'ended'
3486
+ /** The client secret was refused (4401) and no fresh one could be had. */
3487
+ | 'unauthorized'
3488
+ /** Closed by the app. */
3489
+ | 'closed';
3490
+ interface SessionOptions {
3491
+ /** The session the tenant's backend created: its `id`, `realtime_url`, `turns_url` and `client_secret`. */
3492
+ readonly session: SessionInfo;
3493
+ /**
3494
+ * A fresh client secret for the session named, from the tenant's backend (refreshClientSecret is a service-key
3495
+ * call), when the server refuses the current one (4401): for example after the first connection's window (two
3496
+ * minutes from issue on the current gateway) passed. It is asked for the session in use; an answer that arrives
3497
+ * after `resume()` moved to another session, or after `close()`, is dropped. Without it, a refused secret ends in
3498
+ * `unauthorized`.
3499
+ */
3500
+ readonly refreshSecret?: (sessionId: string) => Promise<ClientSecret | string>;
3501
+ /**
3502
+ * Default `subprotocol` (`vinaverse.v1, bearer.<secret>`). `header` where the WebSocket takes headers: React
3503
+ * Native and Node 22+ with the default factory, or your own `webSocket`.
3504
+ */
3505
+ readonly auth?: AuthMode;
3506
+ /** Default: the global WebSocket. */
3507
+ readonly webSocket?: WebSocketFactory;
3508
+ /** Default: the global fetch. Used by the SSE fallback. */
3509
+ readonly fetch?: FetchLike;
3510
+ /** Fall back to SSE turns when the WebSocket cannot be opened. Default true. */
3511
+ readonly sseFallback?: boolean;
3512
+ /** Delays before reconnecting after a drop, in ms; the last one repeats. Default 1 s doubling to 30 s. */
3513
+ readonly reconnectDelays?: readonly number[];
3514
+ /** Attempts to reconnect before going `idle`; they count again once a connection stayed up 30 s. Default 8. */
3515
+ readonly maxReconnects?: number;
3516
+ /** Delays before resending an input refused with `screen_unavailable`, in ms. Default 2 s, 4 s, 8 s. */
3517
+ readonly screenRetryDelays?: readonly number[];
3518
+ /** The `ping` interval while in the foreground, in ms. Default 25 s. */
3519
+ readonly pingInterval?: number;
3520
+ /** New `client_event_id`s. Default: random, `ce_` and 22 characters. */
3521
+ readonly createId?: () => string;
3522
+ /** An engine to continue, for example one an app kept across a restart. */
3523
+ readonly engine?: ConversationEngine;
3524
+ }
3525
+ interface SessionEvents {
3526
+ /**
3527
+ * Every event the server sends, `message.delta` included. A type this client does not know arrives too (§5), so a
3528
+ * `switch` on `type` keeps a default branch.
3529
+ */
3530
+ event: ServerEvent;
3531
+ /** The conversation's state changed. */
3532
+ state: ConversationState;
3533
+ status: SessionStatus;
3534
+ /**
3535
+ * Something failed that no call returned: an input that could not be delivered (it is also shown as not sent in
3536
+ * the state), an SSE turn, or getting a fresh secret.
3537
+ */
3538
+ error: VinaverseClientError;
3539
+ }
3540
+ type Listener<T> = (value: T) => void;
3541
+ declare class VinaverseSession {
3542
+ readonly engine: ConversationEngine;
3543
+ private info;
3544
+ private secret;
3545
+ private readonly options;
3546
+ private socket;
3547
+ /** The engine's name for the connection in `socket`, once it opened. */
3548
+ private opened;
3549
+ private connection;
3550
+ private ready;
3551
+ private current;
3552
+ private everOpened;
3553
+ private firstFailures;
3554
+ private attempts;
3555
+ /** The reason of the `session.closed` that preceded the close, on the current connection. */
3556
+ private closeReason;
3557
+ private stableTimer;
3558
+ /** SSE requests in flight. */
3559
+ private readonly streams;
3560
+ private reconnectTimer;
3561
+ private pingTimer;
3562
+ private foreground;
3563
+ private refreshed;
3564
+ /** client_event_id → why it waits: after the turn, or after a backoff. */
3565
+ private readonly waiting;
3566
+ /** client_event_id → the session that refused it for its turn limit: resent only on another session. */
3567
+ private readonly heldForSession;
3568
+ private readonly screenTries;
3569
+ private readonly screenTimers;
3570
+ private readonly listeners;
3571
+ constructor(options: SessionOptions);
3572
+ /** The engine names the session, and keeps the conversation it already knew when this one does not say. */
3573
+ private named;
3574
+ get status(): SessionStatus;
3575
+ get state(): ConversationState;
3576
+ on<K extends keyof SessionEvents>(name: K, listener: Listener<SessionEvents[K]>): () => void;
3577
+ /**
3578
+ * Connect, or reconnect: the user's own action, so it also resumes after the conversation continued elsewhere
3579
+ * or the secret was refused.
3580
+ */
3581
+ connect(): void;
3582
+ private start;
3583
+ /**
3584
+ * Continue the conversation on a new session the tenant's backend created for it (by `conversation_key` or
3585
+ * `conversation_id`): its `session.ready` brings the transcript and pending cards, and what was never
3586
+ * acknowledged is resent there. A user action, like `connect()`.
3587
+ */
3588
+ resume(session: SessionInfo): void;
3589
+ /** Close for good: the connection, any SSE turn still streaming, and every timer. */
3590
+ close(): void;
3591
+ /** Ping only while the app is in the foreground (every 25 s); in the background the connection may idle out. */
3592
+ setForeground(foreground: boolean): void;
3593
+ private open;
3594
+ private afterClose;
3595
+ /** After a 4401: one fresh secret for the session in use, unless it moved on or was closed meanwhile. */
3596
+ private refresh;
3597
+ private scheduleReconnect;
3598
+ private cancelReconnect;
3599
+ private fallBack;
3600
+ /** Close the current connection, if any, without reacting to its close event. */
3601
+ private drop;
3602
+ private forget;
3603
+ private frame;
3604
+ private receive;
3605
+ private refusedForNow;
3606
+ private resendUnacknowledged;
3607
+ /** What a turn limit held, on a session other than the one that refused it. */
3608
+ private resendHeldForSession;
3609
+ private resendWaiting;
3610
+ private resend;
3611
+ /** Keep an input for a later session: unacknowledged, so the next `session.ready` (after resume()) sends it. */
3612
+ private hold;
3613
+ /** The session ended: retries waiting on a timer would go nowhere, so their inputs are held for the next session. */
3614
+ private holdRetries;
3615
+ /** Send a typed message; returns its `client_event_id`. Over SSE it runs the turn there. */
3616
+ sendText(text: string): string;
3617
+ /** Answer a question card (or a `rate_each` row, by the row's `question_id`); returns the `client_event_id`. */
3618
+ answer(cardId: string, questionId: string, answer: AnswerInput): string;
3619
+ /**
3620
+ * Report a proposal's outcome after the tenant's backend answered (§6): `confirmed`, `declined`, `failed` or
3621
+ * `expired`. The first outcome per card is final. Returns the `client_event_id`.
3622
+ */
3623
+ reportOutcome(cardId: string, outcome: ProposalOutcome): string;
3624
+ /** A card action (`ui.action`), named by the card's `id`; the card's turn is taken from the state. */
3625
+ action(cardId: string, action: string): string;
3626
+ /** Stop the turn in progress: `input.cancel` on the WebSocket; over SSE, the turn's request is stopped. */
3627
+ cancel(turnId: string): void;
3628
+ /** Ended idle: inputs are held for the new session that `resume()` brings. */
3629
+ private awaitingNewSession;
3630
+ private live;
3631
+ /**
3632
+ * Every input goes through here: recorded as sent (unacknowledged until the server answers it), then over SSE,
3633
+ * or on the WebSocket when the session is ready, or held and resent once `session.ready` arrives.
3634
+ */
3635
+ private deliver;
3636
+ private transmit;
3637
+ /** One input over SSE: a typed message or a card action; an answer cannot go, and is shown as not sent. */
3638
+ private overSse;
3639
+ private isUnacknowledged;
3640
+ private inFlight;
3641
+ private stopSse;
3642
+ private sseTarget;
3643
+ private newId;
3644
+ private startPing;
3645
+ private stopPing;
3646
+ private setStatus;
3647
+ private emitState;
3648
+ private emit;
3649
+ }
3650
+
3651
+ export { type AnswerInput as A, type WebSocketFactory as B, type CardLink as C, type WebSocketLike as D, cardActionSse as E, type FetchLike as F, type components as G, type HeldCard as H, type operations as I, type paths as J, sseFrames as K, turnSse as L, type Message as M, type Proposal as P, type Question as Q, type Reconnect as R, type Schemas as S, type ToolLine as T, VinaverseClientError as V, type Welcome as W, type Card as a, type CardGroup as b, type CardImage as c, type CardKind as d, type ClientErrorCode as e, type HeldQuestion as f, type ProposalOutcome as g, type AuthMode as h, isKnownCard as i, type ClientEvent as j, type ClientSecret as k, type Connection as l, ConversationEngine as m, type ConversationState as n, type Frame as o, type ServerEvent as p, type ServerEventOf as q, type ServerEventType as r, type SessionEvents as s, type SessionInfo as t, type SessionOptions as u, type SessionStatus as v, type SseTarget as w, type TranscriptItem as x, type TurnInput as y, VinaverseSession as z };