@everfur/sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/CHANGELOG.md +648 -1
  2. package/README.md +58 -23
  3. package/consent/package.json +8 -0
  4. package/dist/Chat-DPUD6dnc.d.cts +128 -0
  5. package/dist/Chat-DwAC2vhB.d.ts +128 -0
  6. package/dist/DepthViews-DNEM96Se.d.ts +16 -0
  7. package/dist/DepthViews-DP0R-DhB.d.cts +19 -0
  8. package/dist/DepthViews-D_3hl4xk.d.ts +19 -0
  9. package/dist/DepthViews-DbFhR4Du.d.cts +16 -0
  10. package/dist/ErrorPolicyPort-CNf4uQZP.d.cts +22 -0
  11. package/dist/ErrorPolicyPort-CwbfmxzJ.d.ts +22 -0
  12. package/dist/{EverfurResult-D92-uL82.d.cts → EverfurResult-DN9pL2Ab.d.cts} +11 -10
  13. package/dist/{EverfurResult-D92-uL82.d.ts → EverfurResult-DN9pL2Ab.d.ts} +11 -10
  14. package/dist/{PhotoController-BItt5M7u.d.cts → PhotoController-3l7MT9jA.d.cts} +1 -1
  15. package/dist/{PhotoController-D8zMTdcW.d.ts → PhotoController-vzY5bfY_.d.ts} +1 -1
  16. package/dist/animations/index.cjs +1 -1997
  17. package/dist/animations/index.d.cts +35 -35
  18. package/dist/animations/index.d.ts +35 -35
  19. package/dist/animations/index.js +1 -1972
  20. package/dist/attachments-5NeobOCd.d.ts +256 -0
  21. package/dist/attachments-Bw3iMQgu.d.ts +146 -0
  22. package/dist/attachments-CQgFrEQ7.d.cts +146 -0
  23. package/dist/attachments-D8T5re7e.d.cts +256 -0
  24. package/dist/casesRepository-ClLv6qMl.d.ts +188 -0
  25. package/dist/casesRepository-DHwtGRYY.d.cts +188 -0
  26. package/dist/chat/index.cjs +1 -1170
  27. package/dist/chat/index.d.cts +31 -53
  28. package/dist/chat/index.d.ts +31 -53
  29. package/dist/chat/index.js +1 -1167
  30. package/dist/client/index.cjs +9 -2315
  31. package/dist/client/index.d.cts +77 -15
  32. package/dist/client/index.d.ts +77 -15
  33. package/dist/client/index.js +9 -2215
  34. package/dist/{config-CiJ0PVBB.d.ts → config-CzTZGlfF.d.cts} +63 -25
  35. package/dist/{config-BSjBdxrZ.d.cts → config-DW60uRb9.d.ts} +63 -25
  36. package/dist/consent/index.cjs +1 -0
  37. package/dist/consent/index.d.cts +33 -0
  38. package/dist/consent/index.d.ts +33 -0
  39. package/dist/consent/index.js +1 -0
  40. package/dist/context-DXhqHlPU.d.ts +159 -0
  41. package/dist/context-XvTzq1tE.d.cts +159 -0
  42. package/dist/copy-Mjoo91aS.d.ts +198 -0
  43. package/dist/copy-bKDNigLb.d.cts +198 -0
  44. package/dist/core/index.cjs +13 -3175
  45. package/dist/core/index.d.cts +58 -12
  46. package/dist/core/index.d.ts +58 -12
  47. package/dist/core/index.js +13 -3163
  48. package/dist/depth-CErXCU8J.d.cts +502 -0
  49. package/dist/depth-DjDcJ6kH.d.ts +502 -0
  50. package/dist/entitlementRepository-DKdXFgTo.d.cts +698 -0
  51. package/dist/entitlementRepository-DSbzuyAW.d.ts +698 -0
  52. package/dist/{identity-DK9zORrG.d.ts → identity-BMx2SUOM.d.ts} +19 -6
  53. package/dist/{identity-Brl-lDd6.d.cts → identity-DI4eVx9S.d.cts} +19 -6
  54. package/dist/{ids-CJ1S6adf.d.cts → ids-B2GAAifq.d.cts} +1 -1
  55. package/dist/{ids-CJ1S6adf.d.ts → ids-B2GAAifq.d.ts} +1 -1
  56. package/dist/index.cjs +12 -4488
  57. package/dist/index.d.cts +29 -121
  58. package/dist/index.d.ts +29 -121
  59. package/dist/index.js +12 -4464
  60. package/dist/models-B-Uh2LTf.d.ts +214 -0
  61. package/dist/models-D2EUxPZY.d.cts +214 -0
  62. package/dist/notifications/index.cjs +2 -0
  63. package/dist/notifications/index.d.cts +17 -0
  64. package/dist/notifications/index.d.ts +17 -0
  65. package/dist/notifications/index.js +2 -0
  66. package/dist/pets-CtQdRpWo.d.ts +92 -0
  67. package/dist/pets-aB8q0JyX.d.cts +92 -0
  68. package/dist/photo/index.cjs +1 -2189
  69. package/dist/photo/index.d.cts +6 -6
  70. package/dist/photo/index.d.ts +6 -6
  71. package/dist/photo/index.js +1 -2186
  72. package/dist/ports-BN6RHF9W.d.cts +48 -0
  73. package/dist/ports-BN6RHF9W.d.ts +48 -0
  74. package/dist/ports-DEBzEhbp.d.cts +386 -0
  75. package/dist/ports-DMolTRzU.d.ts +386 -0
  76. package/dist/profile-CSs1wlXT.d.cts +93 -0
  77. package/dist/profile-CSs1wlXT.d.ts +93 -0
  78. package/dist/records/depth/index.cjs +2 -0
  79. package/dist/records/depth/index.d.cts +13 -0
  80. package/dist/records/depth/index.d.ts +13 -0
  81. package/dist/records/depth/index.js +2 -0
  82. package/dist/records/index.cjs +3 -2839
  83. package/dist/records/index.d.cts +137 -203
  84. package/dist/records/index.d.ts +137 -203
  85. package/dist/records/index.js +3 -2833
  86. package/dist/requestFunnel-CjW7uDuA.d.ts +87 -0
  87. package/dist/requestFunnel-L-BQfi0z.d.cts +87 -0
  88. package/dist/{resolve-Dq_4_agU.d.ts → resolve-D4Ywz5OS.d.cts} +10 -4
  89. package/dist/{resolve-Dq_4_agU.d.cts → resolve-D4Ywz5OS.d.ts} +10 -4
  90. package/dist/{runtime-BgQnA594.d.cts → runtime-B8s-_hv8.d.cts} +134 -59
  91. package/dist/{runtime-CBA-LvdM.d.ts → runtime-BR9ysG0J.d.ts} +134 -59
  92. package/dist/server/events/index.cjs +2 -0
  93. package/dist/server/events/index.d.cts +550 -0
  94. package/dist/server/events/index.d.ts +550 -0
  95. package/dist/server/events/index.js +2 -0
  96. package/dist/server/index.cjs +2 -533
  97. package/dist/server/index.d.cts +73 -18
  98. package/dist/server/index.d.ts +73 -18
  99. package/dist/server/index.js +2 -530
  100. package/dist/species-BXAIMh7I.d.cts +9 -0
  101. package/dist/species-BXAIMh7I.d.ts +9 -0
  102. package/dist/televet/index.cjs +1 -0
  103. package/dist/televet/index.d.cts +60 -0
  104. package/dist/televet/index.d.ts +60 -0
  105. package/dist/televet/index.js +1 -0
  106. package/dist/testing/index.cjs +5 -823
  107. package/dist/testing/index.d.cts +17 -3
  108. package/dist/testing/index.d.ts +17 -3
  109. package/dist/testing/index.js +5 -820
  110. package/dist/testing/rn/index.cjs +1 -449
  111. package/dist/testing/rn/index.d.cts +17 -29
  112. package/dist/testing/rn/index.d.ts +17 -29
  113. package/dist/testing/rn/index.js +1 -444
  114. package/dist/testing/web/index.cjs +1 -0
  115. package/dist/testing/web/index.d.cts +131 -0
  116. package/dist/testing/web/index.d.ts +131 -0
  117. package/dist/testing/web/index.js +1 -0
  118. package/dist/timelineRows-CaohZzXS.d.cts +19 -0
  119. package/dist/timelineRows-DZed6dAM.d.ts +19 -0
  120. package/dist/typeStyle-0TydiZ1w.d.cts +19 -0
  121. package/dist/typeStyle-CWMsoe6b.d.ts +19 -0
  122. package/dist/uploadTransport-D0M0T4hN.d.ts +38 -0
  123. package/dist/uploadTransport-DKJHs3Yj.d.cts +38 -0
  124. package/dist/useRecordsDepth-BHBzJ76R.d.cts +237 -0
  125. package/dist/useRecordsDepth-BU-OhzH6.d.ts +237 -0
  126. package/dist/useVetVisit-Bc84hWUU.d.cts +187 -0
  127. package/dist/useVetVisit-CjmMa896.d.ts +187 -0
  128. package/dist/video/index.cjs +1 -1941
  129. package/dist/video/index.d.cts +4 -4
  130. package/dist/video/index.d.ts +4 -4
  131. package/dist/video/index.js +1 -1938
  132. package/dist/view-C3qPIXGX.d.cts +310 -0
  133. package/dist/view-DtSPYpKa.d.ts +310 -0
  134. package/dist/visitIntent-D7_yVp1I.d.cts +8 -0
  135. package/dist/visitIntent-D7_yVp1I.d.ts +8 -0
  136. package/dist/web/consent/index.cjs +1 -0
  137. package/dist/web/consent/index.d.cts +34 -0
  138. package/dist/web/consent/index.d.ts +34 -0
  139. package/dist/web/consent/index.js +1 -0
  140. package/dist/web/index.cjs +14 -0
  141. package/dist/web/index.d.cts +217 -0
  142. package/dist/web/index.d.ts +217 -0
  143. package/dist/web/index.js +14 -0
  144. package/dist/web/notifications/index.cjs +2 -0
  145. package/dist/web/notifications/index.d.cts +213 -0
  146. package/dist/web/notifications/index.d.ts +213 -0
  147. package/dist/web/notifications/index.js +2 -0
  148. package/dist/web/records/depth/index.cjs +2 -0
  149. package/dist/web/records/depth/index.d.cts +12 -0
  150. package/dist/web/records/depth/index.d.ts +12 -0
  151. package/dist/web/records/depth/index.js +2 -0
  152. package/dist/web/records/index.cjs +4 -0
  153. package/dist/web/records/index.d.cts +194 -0
  154. package/dist/web/records/index.d.ts +194 -0
  155. package/dist/web/records/index.js +4 -0
  156. package/dist/web/televet/index.cjs +1 -0
  157. package/dist/web/televet/index.d.cts +50 -0
  158. package/dist/web/televet/index.d.ts +50 -0
  159. package/dist/web/televet/index.js +1 -0
  160. package/notifications/package.json +8 -0
  161. package/package.json +197 -9
  162. package/records/depth/package.json +8 -0
  163. package/server/events/device-blocked.cjs +15 -0
  164. package/server/events/package.json +9 -0
  165. package/televet/package.json +8 -0
  166. package/testing/web/native-blocked.cjs +12 -0
  167. package/testing/web/package.json +8 -0
  168. package/web/consent/native-blocked.cjs +12 -0
  169. package/web/consent/package.json +8 -0
  170. package/web/native-blocked.cjs +12 -0
  171. package/web/notifications/native-blocked.cjs +12 -0
  172. package/web/notifications/package.json +8 -0
  173. package/web/package.json +8 -0
  174. package/web/records/depth/native-blocked.cjs +12 -0
  175. package/web/records/depth/package.json +8 -0
  176. package/web/records/native-blocked.cjs +12 -0
  177. package/web/records/package.json +8 -0
  178. package/web/televet/native-blocked.cjs +12 -0
  179. package/web/televet/package.json +8 -0
  180. package/dist/ChatController-CKdBvPj2.d.ts +0 -146
  181. package/dist/ChatController-CpUMvvZf.d.cts +0 -146
  182. package/dist/FilePort-BabWrv7I.d.cts +0 -22
  183. package/dist/FilePort-BabWrv7I.d.ts +0 -22
  184. package/dist/petsRepository-BEGb97M9.d.cts +0 -326
  185. package/dist/petsRepository-Bu18r2kK.d.ts +0 -326
  186. package/dist/requestFunnel-DuUH-kAe.d.cts +0 -28
  187. package/dist/requestFunnel-dio5OmR9.d.ts +0 -28
@@ -0,0 +1,48 @@
1
+ /** Opaque handle to a local file to upload. Server-owned upload target is defined by records/video specs. */
2
+ interface FileHandle {
3
+ /** Local file URI. */
4
+ readonly uri: string;
5
+ readonly mimeType: string;
6
+ readonly sizeBytes?: number;
7
+ /** Original file name, when the host has one. */
8
+ readonly name?: string;
9
+ /**
10
+ * The bytes themselves, for a host that holds them in memory rather than at a dereferenceable `uri` (a
11
+ * browser `File`). Additive: a native host leaves it unset and uploads from `uri`. A web host sets `uri`
12
+ * to a marker that is never fetched, and never mints an object URL for the file (records are PHI).
13
+ */
14
+ readonly blob?: Blob;
15
+ }
16
+ /**
17
+ * Presigned PUT upload. `idempotencyKey` is reused on every retry (a re-uploaded identical file dedupes
18
+ * server-side). `signal` cancels an in-flight upload. Settles at the controller; this port itself may reject,
19
+ * which the controller normalizes to a typed EverfurError.
20
+ */
21
+ interface FilePort {
22
+ presignedPut(file: FileHandle, opts: {
23
+ readonly idempotencyKey: string;
24
+ readonly signal?: AbortSignal;
25
+ }): Promise<void>;
26
+ }
27
+
28
+ interface UploadPolicyWire {
29
+ readonly upload_id: string;
30
+ readonly upload_url: string;
31
+ readonly upload_fields: Readonly<Record<string, string>>;
32
+ readonly expires_in_seconds: number;
33
+ readonly max_content_length: number;
34
+ }
35
+ /**
36
+ * Platform-specific: the rn adapter binds `expo-file-system` (uploadAsync) behind `optionalModule`; the
37
+ * controller stays platform-neutral and never sees a native module. Resolves on S3's 204; rejects on a
38
+ * policy 403 / stream failure, which the controller normalizes to a typed `mediaUploadFailed` (never a throw
39
+ * that escapes the controller).
40
+ */
41
+ interface UploadTransport {
42
+ postMultipart(policy: UploadPolicyWire, file: FileHandle, opts: {
43
+ readonly onProgress?: (fraction: number) => void;
44
+ readonly signal?: AbortSignal;
45
+ }): Promise<void>;
46
+ }
47
+
48
+ export type { FileHandle as F, UploadTransport as U, UploadPolicyWire as a, FilePort as b };
@@ -0,0 +1,48 @@
1
+ /** Opaque handle to a local file to upload. Server-owned upload target is defined by records/video specs. */
2
+ interface FileHandle {
3
+ /** Local file URI. */
4
+ readonly uri: string;
5
+ readonly mimeType: string;
6
+ readonly sizeBytes?: number;
7
+ /** Original file name, when the host has one. */
8
+ readonly name?: string;
9
+ /**
10
+ * The bytes themselves, for a host that holds them in memory rather than at a dereferenceable `uri` (a
11
+ * browser `File`). Additive: a native host leaves it unset and uploads from `uri`. A web host sets `uri`
12
+ * to a marker that is never fetched, and never mints an object URL for the file (records are PHI).
13
+ */
14
+ readonly blob?: Blob;
15
+ }
16
+ /**
17
+ * Presigned PUT upload. `idempotencyKey` is reused on every retry (a re-uploaded identical file dedupes
18
+ * server-side). `signal` cancels an in-flight upload. Settles at the controller; this port itself may reject,
19
+ * which the controller normalizes to a typed EverfurError.
20
+ */
21
+ interface FilePort {
22
+ presignedPut(file: FileHandle, opts: {
23
+ readonly idempotencyKey: string;
24
+ readonly signal?: AbortSignal;
25
+ }): Promise<void>;
26
+ }
27
+
28
+ interface UploadPolicyWire {
29
+ readonly upload_id: string;
30
+ readonly upload_url: string;
31
+ readonly upload_fields: Readonly<Record<string, string>>;
32
+ readonly expires_in_seconds: number;
33
+ readonly max_content_length: number;
34
+ }
35
+ /**
36
+ * Platform-specific: the rn adapter binds `expo-file-system` (uploadAsync) behind `optionalModule`; the
37
+ * controller stays platform-neutral and never sees a native module. Resolves on S3's 204; rejects on a
38
+ * policy 403 / stream failure, which the controller normalizes to a typed `mediaUploadFailed` (never a throw
39
+ * that escapes the controller).
40
+ */
41
+ interface UploadTransport {
42
+ postMultipart(policy: UploadPolicyWire, file: FileHandle, opts: {
43
+ readonly onProgress?: (fraction: number) => void;
44
+ readonly signal?: AbortSignal;
45
+ }): Promise<void>;
46
+ }
47
+
48
+ export type { FileHandle as F, UploadTransport as U, UploadPolicyWire as a, FilePort as b };
@@ -0,0 +1,386 @@
1
+ import { R as RequestFunnel } from './requestFunnel-L-BQfi0z.cjs';
2
+ import { C as CheckinAnswer, a as CheckinOutcome } from './casesRepository-DHwtGRYY.cjs';
3
+ import { A as AuthContext } from './identity-DI4eVx9S.cjs';
4
+ import { E as EverfurError, c as EverfurResult } from './EverfurResult-DN9pL2Ab.cjs';
5
+ import { P as PetRef, C as ConversationId, b as ResponseId } from './ids-B2GAAifq.cjs';
6
+ import { i as UrgencyLevel, F as Frame } from './config-CzTZGlfF.cjs';
7
+ import { E as ErrorPolicyPort } from './ErrorPolicyPort-CNf4uQZP.cjs';
8
+ import { T as TelemetryPort } from './TelemetryPort-BDNr00hu.cjs';
9
+ import { F as FileHandle } from './ports-BN6RHF9W.cjs';
10
+
11
+ /** Injectable timers so the watchdog + deadlines are testable with a fake clock (no direct setTimeout). */
12
+ interface Timers$1 {
13
+ setTimeout(handler: () => void, ms: number): unknown;
14
+ clearTimeout(handle: unknown): void;
15
+ now(): number;
16
+ }
17
+
18
+ /** User-scoped history. Titles are restricted data; keep them out of logs and persistent storage. */
19
+ interface ChatConversation {
20
+ readonly id: ConversationId;
21
+ readonly title: string | null;
22
+ readonly createdAt: string;
23
+ readonly updatedAt: string;
24
+ /**
25
+ * Unread assistant messages for the calling user (`unread_message_count`), for a badge. null when the wire
26
+ * did not carry it, which means the server does not speak unread, never "all read".
27
+ */
28
+ readonly unreadCount: number | null;
29
+ }
30
+ interface ChatHistoryOptions {
31
+ readonly cursor?: string;
32
+ readonly limit?: number;
33
+ readonly signal?: AbortSignal;
34
+ }
35
+ interface ChatConversationListOptions extends ChatHistoryOptions {
36
+ readonly petRef?: PetRef;
37
+ }
38
+ interface ChatConversationPage {
39
+ readonly conversations: readonly ChatConversation[];
40
+ readonly nextCursor: string | null;
41
+ }
42
+ /** Each page is chronological; nextCursor loads the older window, to prepend rather than append. */
43
+ interface ChatMessagePage {
44
+ readonly messages: readonly ChatMessage[];
45
+ readonly nextCursor: string | null;
46
+ }
47
+
48
+ /**
49
+ * The answerable check-in behind an assistant message, derived from its `proactiveOriginRef` and kept up to date
50
+ * by `ChatController.answerCheckin`. `answer` is the answer on record for this session (null while unanswered);
51
+ * `resolutionText` is the server's own post-answer line when it sent one.
52
+ */
53
+ interface ChatCheckin {
54
+ readonly caseId: string;
55
+ readonly answer: CheckinAnswer | null;
56
+ readonly resolutionText: string | null;
57
+ /**
58
+ * The case's lifecycle status as last known (`watching` while open, then `resolved`, `escalated`, `lapsed`),
59
+ * null while UNKNOWN. The server accepts an answer only while the case is watching, so a surface draws no
60
+ * answer row for any other known status: leaving one tappable produced a 404 that reads to the owner as
61
+ * "your answer did not save". Unknown does NOT lock, so a surface that cannot read the status keeps exactly
62
+ * the behaviour it had before this existed.
63
+ */
64
+ readonly status: string | null;
65
+ }
66
+ /** The one status that still takes an answer (the consumer's `ANSWERABLE_STATUS`). */
67
+ declare const CHECKIN_ANSWERABLE_STATUS = "watching";
68
+ /** True when the case will no longer take an answer, so the surface draws no answer row. */
69
+ declare function isCheckinSettled(checkin: ChatCheckin): boolean;
70
+
71
+ type ChatStatus = 'idle' | 'streaming' | 'complete' | 'error';
72
+ /** Opaque citation record (DoneFrame.citations shape). Rendered by the UI layer, never trusted for logic. */
73
+ type Citation = Readonly<Record<string, unknown>>;
74
+ interface ChatMessage {
75
+ /** Stable list key: a client id for a user turn, the responseId for a finalized assistant turn. */
76
+ readonly id: string;
77
+ readonly role: 'user' | 'assistant';
78
+ readonly text: string;
79
+ /**
80
+ * ISO 8601. A history row carries the server's `created_at`; an optimistic user turn and a streamed reply
81
+ * are stamped from the controller clock when the row is created, and the stamp survives the reply's
82
+ * finalize. Rendered by the message footer as the app does (`toLocaleTimeString`, hour and minute).
83
+ */
84
+ readonly createdAt: string;
85
+ readonly citations?: readonly Citation[];
86
+ readonly responseId?: ResponseId;
87
+ /** Optimistic user bubble; removed if the turn fails BEFORE the first delta. */
88
+ readonly pending?: boolean;
89
+ /** Server classification, never inferred by the SDK from message text. */
90
+ readonly urgencyLevel?: UrgencyLevel | null;
91
+ readonly urgencyReason?: string | null;
92
+ /**
93
+ * True when the done frame carried the urgency slot (`DoneFrame.urgencyProvided`): with `urgencyLevel`
94
+ * null it means the server classified the turn as informational, which the surfaces show as the calm
95
+ * "Good to know" tier. Absent on user turns, on history rows and on a turn the server never classified.
96
+ */
97
+ readonly urgencyProvided?: boolean;
98
+ /**
99
+ * The platform's occasion marker on a message it wrote itself (`checkin:<case_id>:<YYYY-MM-DD>` for a
100
+ * follow-up check-in), verbatim from the wire. Absent on ordinary turns.
101
+ */
102
+ readonly proactiveOriginRef?: string;
103
+ /** The answerable check-in behind this message, when `proactiveOriginRef` names one (see `answerCheckin`). */
104
+ readonly checkin?: ChatCheckin;
105
+ }
106
+ interface ChatSnapshot {
107
+ readonly status: ChatStatus;
108
+ /** [] => Empty state; length > 0 => Populated. */
109
+ readonly messages: readonly ChatMessage[];
110
+ /** nulled on a 404 before recreate. */
111
+ readonly conversationId: ConversationId | null;
112
+ /** present iff status === 'error'; NEVER thrown. */
113
+ readonly error: EverfurError | null;
114
+ readonly isStreaming: boolean;
115
+ /** true during the initial conversation prime => Loading. */
116
+ readonly isBootstrapping: boolean;
117
+ /** Opaque cursor for older messages in the active conversation, not a forward-page cursor. */
118
+ readonly historyCursor?: string | null;
119
+ readonly isLoadingHistory?: boolean;
120
+ /**
121
+ * The last turn was STOPPED by the owner before its first delta, so the message they typed is on screen with
122
+ * no answer under it and nothing said about why. Not an error (nothing failed), which is why it is its own
123
+ * flag: a surface renders the consumer app's `Response stopped` notice with a retry, and the next send,
124
+ * retry or thread change clears it.
125
+ */
126
+ readonly stopped?: boolean;
127
+ /**
128
+ * The turn in flight is being RE-ATTEMPTED by the controller with nothing asked of the owner: the mid-stream
129
+ * token refresh replay, or the one recreate-and-resend after the conversation went missing. The consumer app
130
+ * shows its `Reconnecting\u2026` caption for exactly this (`streamStatus === 'reconnecting'`, set from the
131
+ * stream registry's `recovering`). Cleared by the turn settling either way, by `stop()` and by a thread
132
+ * change. Optional on the type for the same reason `stopped` is: it was added to a shipped interface.
133
+ */
134
+ readonly reconnecting?: boolean;
135
+ /**
136
+ * Flattened, de-duplicated suggested-prompt strings (backend-owned copy). After a reply they are THAT reply's
137
+ * `follow_up_questions` from the done frame; a reply that carries none falls back to the bootstrap set
138
+ * (empty until the bootstrap resolves, empty when the backend returns none). The UI renders these as the
139
+ * tappable "Follow up questions" list; each string is a ready-to-send message.
140
+ */
141
+ readonly suggestedPrompts: readonly string[];
142
+ /**
143
+ * Unread assistant messages in the active thread, for a badge: the count the conversation list last
144
+ * reported for it (`unread_message_count`), 0 once `markRead()` settles. null while unknown, which is also
145
+ * what a server that does not speak unread leaves it at; never read null as "all read".
146
+ */
147
+ readonly unreadCount: number | null;
148
+ /**
149
+ * CRN-01. Does ANY thread this controller has listed still carry unread assistant messages? The header's
150
+ * dot, answerable without the history drawer ever being opened, because the chat bootstrap reads the head
151
+ * of the list on mount. False while nothing has been listed and against a server that does not speak
152
+ * unread, which is the same thing the dot already means: nothing to show.
153
+ */
154
+ readonly hasUnreadThreads?: boolean;
155
+ }
156
+ interface SendOptions {
157
+ readonly conversationId?: ConversationId;
158
+ readonly petRef?: PetRef;
159
+ readonly signal?: AbortSignal;
160
+ readonly idempotencyKey?: string;
161
+ /**
162
+ * The S3 keys of images already staged through `core/chat/attachments` (`POST /widget/v1/uploads/initiate`,
163
+ * then the multipart upload), sent as `image_s3_keys`. At most `MAX_IMAGE_KEYS_PER_MESSAGE`; the server
164
+ * checks each key is the caller's own. The message text is still required.
165
+ */
166
+ readonly imageS3Keys?: readonly string[];
167
+ }
168
+ /** The server's cap on `image_s3_keys` per message (`SendWidgetMessageRequest`, mirrored from the consumer send). */
169
+ declare const MAX_IMAGE_KEYS_PER_MESSAGE = 5;
170
+ /**
171
+ * The feedback a reader records on an assistant message (`POST /widget/v1/messages/{message_id}/feedback`,
172
+ * the consumer `MessageFeedbackType` enum): the three sentiments the thumbs record on tap, and the reasons the
173
+ * not-helpful sheet upgrades a thumbs-down to.
174
+ */
175
+ type ChatFeedbackType = 'helpful' | 'neutral' | 'not_helpful' | 'wrong' | 'outdated' | 'dangerous' | 'unclear';
176
+ /** Every `ChatFeedbackType`, for a validator or a picker. */
177
+ declare const CHAT_FEEDBACK_TYPES: readonly ChatFeedbackType[];
178
+ /** The server's cap on the free-text `notes` (`notes: Field(None, max_length=4000)`). */
179
+ declare const FEEDBACK_NOTES_MAX_CHARS = 4000;
180
+ /** What a recorded feedback settles to: the server's row id. */
181
+ interface ChatFeedbackReceipt {
182
+ readonly feedbackId: string;
183
+ }
184
+ /**
185
+ * What a check-in tap is about: the `case` target of an Everfur push
186
+ * (src/core/notifications/parse.ts `EverfurNotificationTarget`), which carries the conversation id when the
187
+ * platform knew it and only the pet when it did not. Both optional, so a host that has just the pet (a
188
+ * deep link, a home-screen card) passes what it has.
189
+ */
190
+ interface ChatCheckinTarget {
191
+ readonly conversationId?: ConversationId | null;
192
+ readonly petRef?: PetRef | null;
193
+ }
194
+ interface ChatHandlers {
195
+ onFrame(frame: Frame): void;
196
+ onDone(responseId: ResponseId | null): void;
197
+ onError(error: EverfurError): void;
198
+ }
199
+ /** A suggested-prompt category returned by the prompts bootstrap endpoint. */
200
+ interface SuggestedPromptCategory {
201
+ readonly category: string;
202
+ readonly prompts: readonly string[];
203
+ }
204
+ interface ChatControllerDeps {
205
+ /** Device auth context (bearer-XOR-pk precedence + lazy token). Carries the TransportPort. */
206
+ readonly auth: AuthContext;
207
+ readonly telemetry?: TelemetryPort;
208
+ readonly errorPolicy?: ErrorPolicyPort;
209
+ readonly timers?: Timers$1;
210
+ /** Injected RNG for deterministic backoff in tests. */
211
+ readonly rng?: () => number;
212
+ /** Injected id factory (client message ids) for deterministic tests. */
213
+ readonly newId?: () => string;
214
+ /** Injected idempotency-key factory for the TURN identity (see `buildSendRequest`). */
215
+ readonly makeIdempotencyKey?: () => string;
216
+ /** The ONE request funnel. Built from `auth` when absent; injected in tests for a deterministic clock. */
217
+ readonly funnel?: RequestFunnel;
218
+ readonly budget?: {
219
+ readonly idleMs?: number;
220
+ readonly overallMs?: number;
221
+ };
222
+ readonly initialConversationId?: ConversationId | null;
223
+ /** The pet this controller is scoped to (the registry's scope key), the pet a check-in answer is recorded against. */
224
+ readonly petRef?: PetRef | null;
225
+ }
226
+ interface ChatController {
227
+ /** useSyncExternalStore subscribe. Returns an unsubscribe. */
228
+ subscribe(listener: () => void): () => void;
229
+ /** A stable frozen snapshot; identity changes only on real change. */
230
+ getSnapshot(): ChatSnapshot;
231
+ /** Stream a reply. The iterable ALWAYS completes cleanly; a failure is a terminal Frame, never a throw. */
232
+ send(text: string, opts?: SendOptions): AsyncIterable<Frame>;
233
+ /** Imperative escape hatch; returns a teardown, also torn down on dispose(). */
234
+ send(text: string, opts: SendOptions, handlers: ChatHandlers): () => void;
235
+ /**
236
+ * CRN-06. Ask a grounded question in a FRESH conversation: the thread on screen is left (its transcript
237
+ * clears and anything in flight is dropped), a new conversation is opened and `prompt` is the first turn
238
+ * in it. This is the consumer app's "Ask Everfur" handoff, whose whole point is that a question about one
239
+ * lab marker does not land in the middle of an unrelated thread
240
+ * (`useConsumerChatRouteHandoffs.ts`: `sendMessage(prompt, { forceFreshConversation: true })`).
241
+ *
242
+ * NOT GROUNDED ON THE WIRE YET. The consumer plane carries a `focus_ref` on conversation create, which
243
+ * orients the SYSTEM PROMPT at the entity the owner is looking at. `CreateWidgetConversationRequest`
244
+ * declares `title`, `pet_ref` and `pet` and nothing else, and it is a plain pydantic v2 model
245
+ * (`extra="ignore"`), so a `focus_ref` sent here would be discarded without a 422 and the SDK would be
246
+ * asserting a grounding that never happened. The prompt the records surfaces build already names the
247
+ * entity in words, which is what actually reaches the model today.
248
+ */
249
+ askEverfur(prompt: string, opts?: SendOptions): AsyncIterable<Frame>;
250
+ /** Re-send the last turn without a duplicate user bubble, reusing its idempotency key. */
251
+ retryLast(): AsyncIterable<Frame>;
252
+ /** Abort in-flight; not an error. Returns to pre-send (idle) and drops a never-answered optimistic bubble. */
253
+ stop(): void;
254
+ /** Idempotent. Aborts in-flight, bumps the epoch, drops listeners + handler subscriptions. */
255
+ dispose(): void;
256
+ /** Re-scope on user/pet switch: aborts in-flight and bumps the epoch so a late write drops. */
257
+ rescope(): void;
258
+ /** Bootstrap helper (§8): create a conversation. Unary, authed, settles. */
259
+ createConversation(opts?: {
260
+ readonly petRef?: PetRef;
261
+ }): Promise<EverfurResult<ConversationId>>;
262
+ /**
263
+ * Bootstrap helper (CRN-01): the whole "which thread does chat open in" decision, made HERE rather than in
264
+ * `bootstrapChat`, because the race guard that stops a send opening a second conversation lives here and
265
+ * has to cover the head read as well as the create. Reads the head of the pet's list (which is also what
266
+ * materializes a follow-up check-in the member is owed), resumes it when it carries unread assistant
267
+ * content, and otherwise creates exactly as `createConversation` did. Settles; never throws.
268
+ */
269
+ primeConversation(opts?: {
270
+ readonly petRef?: PetRef;
271
+ }): Promise<EverfurResult<ConversationId>>;
272
+ /** Bootstrap helper (§8): fetch the session's suggested prompts, optionally personalized to a pet. Settles; never throws. */
273
+ getPrompts(opts?: {
274
+ readonly petRef?: PetRef;
275
+ }): Promise<EverfurResult<readonly SuggestedPromptCategory[]>>;
276
+ /** Adopt (or clear) the active conversation id, e.g. after bootstrap primes one. */
277
+ adoptConversation(id: ConversationId | null): void;
278
+ /** Read a page of this user's conversations; optional petRef filters on the server. */
279
+ listConversations(opts?: ChatConversationListOptions): Promise<EverfurResult<ChatConversationPage>>;
280
+ /**
281
+ * CRN-01. Open the thread a follow-up check-in landed in: the conversation the push named, or, with only a
282
+ * pet, the HEAD of that pet's list. Reading the list is what materializes a check-in the member is owed
283
+ * (`widget_follow_ups_for_list`), so the pet-only form is not a lookup, it is the trigger. Settles to the
284
+ * conversation opened, or `ok(null)` when the member has no thread yet, which is not a failure: the empty
285
+ * chat they are already looking at is the right place for them to be. Never throws.
286
+ */
287
+ openCheckin(target?: ChatCheckinTarget): Promise<EverfurResult<ConversationId | null>>;
288
+ /** Read a chronological message window without changing the active conversation. */
289
+ getMessages(id: ConversationId, opts?: ChatHistoryOptions): Promise<EverfurResult<ChatMessagePage>>;
290
+ /** Switch threads and hydrate the latest message window. Clears the old thread immediately. */
291
+ resumeConversation(id: ConversationId): Promise<EverfurResult<void>>;
292
+ /** Prepend the next older window, preserving any replies sent while the page was loading. */
293
+ loadOlderMessages(): Promise<EverfurResult<void>>;
294
+ /**
295
+ * CRN-16. Re-read the active thread's NEWEST window and the pet's limit-1 conversation list, and merge
296
+ * what is new into the transcript. This is how a message the PLATFORM wrote (a follow-up check-in, a
297
+ * reminder) becomes visible on a plane with no websocket and no polling; the host decides when to call it
298
+ * by wiring a lifecycle port, and `decideChatRefresh` decides whether it may run. Non-destructive: rows
299
+ * already on screen keep their place and any older pages the reader scrolled back to are kept. A no-op
300
+ * without a thread and while a turn is in flight. Settles; never throws.
301
+ */
302
+ refreshActiveThread(): Promise<EverfurResult<void>>;
303
+ /**
304
+ * Adopt the suggested-prompt categories fetched by the bootstrap (§8): flattened + de-duplicated onto the
305
+ * snapshot's `suggestedPrompts` so the prebuilt UI can render the tappable follow-up list. Idempotent; a
306
+ * no-op when the flattened set is empty and none are shown.
307
+ */
308
+ adoptPrompts(categories: readonly SuggestedPromptCategory[]): void;
309
+ /**
310
+ * Record the owner's answer to a follow-up check-in (`POST /widget/v1/pets/{pet_ref}/cases/{case_id}/checkin`)
311
+ * against the scoped pet (or `opts.petRef`), and reflect it on the message carrying that case: `checkin.answer`
312
+ * and the server's `resolutionText`. A stale, closed or foreign case is the ordinary not-found error; without
313
+ * a pet it settles to `validationFailed` before any I/O. Never posts a message into the thread.
314
+ */
315
+ answerCheckin(caseId: string, answer: CheckinAnswer, opts?: {
316
+ readonly petRef?: PetRef;
317
+ readonly signal?: AbortSignal;
318
+ }): Promise<EverfurResult<CheckinOutcome>>;
319
+ /**
320
+ * Advance the caller's read watermark on the active thread (`POST /widget/v1/conversations/{id}/read`,
321
+ * idempotent) and zero `unreadCount`. Called for you when `resumeConversation` opens a thread the list reported
322
+ * unread; call it yourself when you opened the thread another way. A no-op without an active thread.
323
+ */
324
+ markRead(opts?: {
325
+ readonly signal?: AbortSignal;
326
+ }): Promise<EverfurResult<void>>;
327
+ /**
328
+ * Record the reader's feedback on an assistant message (`POST /widget/v1/messages/{message_id}/feedback`,
329
+ * body `{ feedback_type, notes? }`). The server upserts per reader, so a second call replaces the first.
330
+ * Only a message with a server `responseId` can be rated: a local id settles to `validationFailed` before
331
+ * any I/O, as do an unknown `feedbackType` and notes over `FEEDBACK_NOTES_MAX_CHARS`. Clearing a rating is
332
+ * local to the surface (the consumer app sends nothing for it). Settles `ok({ feedbackId })`; never throws.
333
+ */
334
+ submitFeedback(responseId: ResponseId, feedbackType: ChatFeedbackType, notes?: string | null, opts?: {
335
+ readonly signal?: AbortSignal;
336
+ }): Promise<EverfurResult<ChatFeedbackReceipt>>;
337
+ }
338
+ declare function createChatController(deps: ChatControllerDeps): ChatController;
339
+ /**
340
+ * The server's own limits on a chat message, mirrored here so a violation is caught before any UI is
341
+ * rendered for it. `SendWidgetMessageRequest.message` on EFBackend main declares
342
+ * `min_length=1, max_length=4000`; the server remains authoritative and still enforces both.
343
+ *
344
+ * These are NOT in the generated contract projection, so they are pinned here with their source rather
345
+ * than derived. If the server relaxes either bound, this is the one place to update.
346
+ */
347
+ declare const MESSAGE_MAX_CHARS = 4000;
348
+
349
+ /** The presigned POST/PUT policy returned by `/uploads/initiate` (SPEC-11b §2.1). */
350
+ interface PresignedPolicy {
351
+ /** Absolute S3 endpoint URL to POST/PUT the bytes at. */
352
+ readonly url: string;
353
+ /** POST-policy form fields to include with the upload (empty for a plain PUT). */
354
+ readonly fields: Readonly<Record<string, string>>;
355
+ /** The owned-uploads-prefixed key the analyze call then references. */
356
+ readonly s3Key: string;
357
+ /** epoch seconds the policy expires; telemetry only. */
358
+ readonly expiresAt: number | null;
359
+ }
360
+ /**
361
+ * The native upload seam. It PUTs `file` at the presigned `target` (SPEC-11b §2.1). `idempotencyKey` is reused
362
+ * on every retry so a re-uploaded identical file dedupes server-side. A network failure REJECTS; the controller
363
+ * normalizes that to a `mediaUploadFailed` and exposes a retake affordance (never auto-retried, SPEC-00 §3.2).
364
+ *
365
+ * NOTE: the spine `FilePort.presignedPut(file, { idempotencyKey, signal })` has no slot for the presigned
366
+ * target; this richer port is the shape it must grow to. See the manifest gap note.
367
+ */
368
+ interface UploadPort {
369
+ presignedPut(file: FileHandle, opts: {
370
+ readonly target: PresignedPolicy;
371
+ readonly idempotencyKey: string;
372
+ readonly signal?: AbortSignal;
373
+ }): Promise<void>;
374
+ }
375
+ interface Timers {
376
+ setTimeout(handler: () => void, ms: number): unknown;
377
+ clearTimeout(handle: unknown): void;
378
+ now(): number;
379
+ }
380
+ /** The useSyncExternalStore contract the rn hooks bind to. */
381
+ interface ReadableStore<S> {
382
+ subscribe(listener: () => void): () => void;
383
+ getSnapshot(): S;
384
+ }
385
+
386
+ export { type ChatCheckin as C, FEEDBACK_NOTES_MAX_CHARS as F, MAX_IMAGE_KEYS_PER_MESSAGE as M, type PresignedPolicy as P, type ReadableStore as R, type SendOptions as S, type Timers as T, type UploadPort as U, type ChatCheckinTarget as a, type ChatConversation as b, type ChatConversationListOptions as c, type ChatConversationPage as d, type ChatFeedbackReceipt as e, type ChatFeedbackType as f, type ChatHistoryOptions as g, type ChatMessagePage as h, type ChatMessage as i, type ChatStatus as j, type Citation as k, type SuggestedPromptCategory as l, type ChatController as m, CHAT_FEEDBACK_TYPES as n, CHECKIN_ANSWERABLE_STATUS as o, type ChatControllerDeps as p, type ChatHandlers as q, type ChatSnapshot as r, MESSAGE_MAX_CHARS as s, type Timers$1 as t, createChatController as u, isCheckinSettled as v };