@assistant-ui/core 0.3.1 → 0.3.3

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 (183) hide show
  1. package/dist/index.d.ts +4 -4
  2. package/dist/index.js.map +1 -1
  3. package/dist/internal.d.ts +6 -7
  4. package/dist/internal.js +5 -6
  5. package/dist/react/AssistantProvider.d.ts +1 -1
  6. package/dist/react/AssistantProvider.d.ts.map +1 -1
  7. package/dist/react/AssistantProvider.js +35 -9
  8. package/dist/react/AssistantProvider.js.map +1 -1
  9. package/dist/react/AssistantRuntimeProvider.d.ts +1 -1
  10. package/dist/react/AssistantRuntimeProvider.js +1 -1
  11. package/dist/react/RuntimeAdapter.js +2 -2
  12. package/dist/react/adapters/LocalStorageThreadListAdapter.d.ts.map +1 -1
  13. package/dist/react/adapters/LocalStorageThreadListAdapter.js +17 -33
  14. package/dist/react/adapters/LocalStorageThreadListAdapter.js.map +1 -1
  15. package/dist/react/client/DataRenderers.js +1 -1
  16. package/dist/react/client/Interactables.js +1 -1
  17. package/dist/react/client/Tools.js +2 -2
  18. package/dist/react/index.d.ts +2 -1
  19. package/dist/react/index.js +2 -1
  20. package/dist/react/interactables-legacy/Interactables.js +1 -1
  21. package/dist/react/interactables-legacy/useAssistantInteractable.js +1 -1
  22. package/dist/react/interactables-legacy/useInteractableState.js +2 -2
  23. package/dist/react/model-context/useAssistantContext.js +1 -1
  24. package/dist/react/model-context/useAssistantDataUI.js +2 -2
  25. package/dist/react/model-context/useAssistantInstructions.js +2 -2
  26. package/dist/react/model-context/useAssistantTool.js +2 -2
  27. package/dist/react/model-context/useAssistantToolUI.js +2 -2
  28. package/dist/react/model-context/useAuiToolOverrides.js +1 -1
  29. package/dist/react/model-context/useInlineRender.js +1 -1
  30. package/dist/react/model-context/useInteractable.js +1 -1
  31. package/dist/react/model-context/useInteractableState.js +2 -2
  32. package/dist/react/model-context/useInteractableVersions.js +2 -2
  33. package/dist/react/model-context/useToolArgsStatus.js +2 -2
  34. package/dist/react/primitive-hooks/useActionBarCopy.js +52 -25
  35. package/dist/react/primitive-hooks/useActionBarCopy.js.map +1 -1
  36. package/dist/react/primitive-hooks/useActionBarEdit.js +2 -2
  37. package/dist/react/primitive-hooks/useActionBarFeedback.js +2 -2
  38. package/dist/react/primitive-hooks/useActionBarReload.js +2 -2
  39. package/dist/react/primitive-hooks/useActionBarSpeak.js +2 -2
  40. package/dist/react/primitive-hooks/useActionBarStopSpeaking.js +2 -2
  41. package/dist/react/primitive-hooks/useBranchPickerNext.js +2 -2
  42. package/dist/react/primitive-hooks/useBranchPickerPrevious.js +2 -2
  43. package/dist/react/primitive-hooks/useComposerAddAttachment.js +2 -2
  44. package/dist/react/primitive-hooks/useComposerCancel.js +2 -2
  45. package/dist/react/primitive-hooks/useComposerDictate.js +2 -2
  46. package/dist/react/primitive-hooks/useComposerSend.js +2 -2
  47. package/dist/react/primitive-hooks/useEditComposerCancel.js +2 -2
  48. package/dist/react/primitive-hooks/useEditComposerSend.js +2 -2
  49. package/dist/react/primitive-hooks/useMessageBranching.js +2 -2
  50. package/dist/react/primitive-hooks/useMessageReload.js +2 -2
  51. package/dist/react/primitive-hooks/useSuggestionTrigger.js +2 -2
  52. package/dist/react/primitive-hooks/useThreadListItemArchive.js +2 -2
  53. package/dist/react/primitive-hooks/useThreadListItemDelete.js +2 -2
  54. package/dist/react/primitive-hooks/useThreadListItemTrigger.js +2 -2
  55. package/dist/react/primitive-hooks/useThreadListItemUnarchive.js +2 -2
  56. package/dist/react/primitive-hooks/useThreadListLoadMore.js +2 -2
  57. package/dist/react/primitive-hooks/useThreadListNew.js +2 -2
  58. package/dist/react/primitive-hooks/useThreadMessageIds.js +1 -1
  59. package/dist/react/primitive-hooks/useVoice.js +2 -2
  60. package/dist/react/primitives/chainOfThought/ChainOfThoughtParts.js +2 -2
  61. package/dist/react/primitives/composer/ComposerAttachments.js +2 -2
  62. package/dist/react/primitives/composer/ComposerIf.js +1 -1
  63. package/dist/react/primitives/composer/ComposerQueue.js +1 -1
  64. package/dist/react/primitives/generativeUI/GenerativeUI.js +2 -2
  65. package/dist/react/primitives/message/MessageAttachments.js +2 -2
  66. package/dist/react/primitives/message/MessageGroupedParts.d.ts +2 -1
  67. package/dist/react/primitives/message/MessageGroupedParts.d.ts.map +1 -1
  68. package/dist/react/primitives/message/MessageGroupedParts.js +3 -3
  69. package/dist/react/primitives/message/MessageGroupedParts.js.map +1 -1
  70. package/dist/react/primitives/message/MessageParts.js +2 -2
  71. package/dist/react/primitives/message/MessageQuote.js +2 -2
  72. package/dist/react/primitives/part/PartMessages.js +2 -2
  73. package/dist/react/primitives/thread/ThreadMessages.js +2 -2
  74. package/dist/react/primitives/thread/ThreadSuggestions.js +2 -2
  75. package/dist/react/primitives/threadList/ThreadListItems.js +2 -2
  76. package/dist/react/primitives/threadListItem/ThreadListItemTitle.js +1 -1
  77. package/dist/react/providers/AttachmentByIndexProvider.js +1 -1
  78. package/dist/react/providers/ChainOfThoughtByIndicesProvider.js +1 -1
  79. package/dist/react/providers/ChainOfThoughtPartByIndexProvider.js +1 -1
  80. package/dist/react/providers/MessageByIdProvider.js +1 -1
  81. package/dist/react/providers/MessageByIndexProvider.js +1 -1
  82. package/dist/react/providers/PartByIndexProvider.js +1 -1
  83. package/dist/react/providers/QueueItemByIndexProvider.js +1 -1
  84. package/dist/react/providers/ReadonlyThreadProvider.js +2 -2
  85. package/dist/react/providers/SuggestionByIndexProvider.js +1 -1
  86. package/dist/react/providers/TextMessagePartProvider.js +2 -2
  87. package/dist/react/providers/ThreadListItemByIndexProvider.js +1 -1
  88. package/dist/react/providers/ThreadListItemRuntimeProvider.js +1 -1
  89. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.d.ts.map +1 -1
  90. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js +14 -13
  91. package/dist/react/runtimes/RemoteThreadListHookInstanceManager.js.map +1 -1
  92. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts +3 -0
  93. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.d.ts.map +1 -1
  94. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js +22 -12
  95. package/dist/react/runtimes/RemoteThreadListThreadListRuntimeCore.js.map +1 -1
  96. package/dist/react/runtimes/RuntimeAdapterProvider.js +1 -1
  97. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js +9 -23
  98. package/dist/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.js.map +1 -1
  99. package/dist/react/runtimes/cloud/auiV0.d.ts +24 -2
  100. package/dist/react/runtimes/cloud/auiV0.d.ts.map +1 -1
  101. package/dist/react/runtimes/cloud/auiV0.js +24 -5
  102. package/dist/react/runtimes/cloud/auiV0.js.map +1 -1
  103. package/dist/react/runtimes/external-message-converter.js +1 -1
  104. package/dist/react/runtimes/useExternalStoreRuntime.js +1 -1
  105. package/dist/react/runtimes/useExternalStoreSharedOptions.js +1 -1
  106. package/dist/react/runtimes/useLocalRuntime.js +1 -1
  107. package/dist/react/runtimes/useRemoteThreadListRuntime.js +7 -13
  108. package/dist/react/runtimes/useRemoteThreadListRuntime.js.map +1 -1
  109. package/dist/react/utils/useJSONEqual.js +1 -1
  110. package/dist/runtime/api/message-runtime.d.ts.map +1 -1
  111. package/dist/runtime/api/message-runtime.js +5 -1
  112. package/dist/runtime/api/message-runtime.js.map +1 -1
  113. package/dist/runtime/internal.d.ts +4 -4
  114. package/dist/runtime/internal.js +4 -4
  115. package/dist/runtime/utils/thread-message-like.js +2 -2
  116. package/dist/runtime/utils/thread-message-like.js.map +1 -1
  117. package/dist/runtimes/remote-thread-list/types.d.ts +5 -4
  118. package/dist/runtimes/remote-thread-list/types.d.ts.map +1 -1
  119. package/dist/store/clients/chain-of-thought-client.d.ts.map +1 -1
  120. package/dist/store/clients/chain-of-thought-client.js +30 -24
  121. package/dist/store/clients/chain-of-thought-client.js.map +1 -1
  122. package/dist/store/clients/model-context-client.js +1 -1
  123. package/dist/store/clients/no-op-composer-client.js +1 -1
  124. package/dist/store/clients/suggestions.js +2 -2
  125. package/dist/store/clients/thread-message-client.d.ts.map +1 -1
  126. package/dist/store/clients/thread-message-client.js +103 -91
  127. package/dist/store/clients/thread-message-client.js.map +1 -1
  128. package/dist/store/runtime-clients/composer-runtime-client.js +2 -2
  129. package/dist/store/runtime-clients/message-runtime-client.js +2 -2
  130. package/dist/store/runtime-clients/thread-list-item-runtime-client.js +2 -2
  131. package/dist/store/runtime-clients/thread-list-runtime-client.js +2 -2
  132. package/dist/store/runtime-clients/thread-runtime-client.js +2 -2
  133. package/dist/store/scopes/thread.d.ts +1 -1
  134. package/dist/store/scopes/thread.d.ts.map +1 -1
  135. package/dist/types/index.d.ts +2 -2
  136. package/dist/types/message.d.ts +11 -1
  137. package/dist/types/message.d.ts.map +1 -1
  138. package/dist/types/message.js.map +1 -1
  139. package/dist/utils/data-url.js +2 -2
  140. package/dist/utils/data-url.js.map +1 -1
  141. package/dist/utils/getGroupStatus.d.ts +9 -0
  142. package/dist/utils/getGroupStatus.d.ts.map +1 -0
  143. package/dist/utils/getGroupStatus.js +15 -0
  144. package/dist/utils/getGroupStatus.js.map +1 -0
  145. package/dist/utils/normalizePartStatus.d.ts +8 -0
  146. package/dist/utils/normalizePartStatus.d.ts.map +1 -0
  147. package/dist/utils/normalizePartStatus.js +39 -0
  148. package/dist/utils/normalizePartStatus.js.map +1 -0
  149. package/package.json +4 -4
  150. package/src/index.ts +1 -0
  151. package/src/internal.ts +1 -7
  152. package/src/react/AssistantProvider.tsx +28 -15
  153. package/src/react/AssistantRuntimeProvider.test.tsx +101 -0
  154. package/src/react/adapters/LocalStorageThreadListAdapter.tsx +28 -7
  155. package/src/react/index.ts +4 -0
  156. package/src/react/primitive-hooks/useActionBarCopy.test.ts +109 -14
  157. package/src/react/primitive-hooks/useActionBarCopy.ts +27 -2
  158. package/src/react/primitives/message/MessageGroupedParts.tsx +4 -4
  159. package/src/react/runtimes/RemoteThreadListHookInstanceManager.tsx +16 -13
  160. package/src/react/runtimes/RemoteThreadListThreadListRuntimeCore.tsx +53 -10
  161. package/src/react/runtimes/cloud/AssistantCloudThreadHistoryAdapter.ts +7 -5
  162. package/src/react/runtimes/cloud/auiV0.ts +51 -12
  163. package/src/react/runtimes/useRemoteThreadListRuntime.ts +9 -15
  164. package/src/runtime/api/message-runtime.test.ts +140 -0
  165. package/src/runtime/api/message-runtime.ts +10 -5
  166. package/src/runtime/utils/thread-message-like.ts +2 -2
  167. package/src/runtimes/remote-thread-list/types.ts +5 -4
  168. package/src/store/clients/chain-of-thought-client.ts +2 -7
  169. package/src/store/clients/thread-message-client.test.ts +84 -0
  170. package/src/store/clients/thread-message-client.ts +14 -4
  171. package/src/store/scopes/thread.ts +1 -1
  172. package/src/tests/auiV0Encode.test.ts +93 -0
  173. package/src/tests/remote-thread-list-reactive-threadId.test.ts +0 -92
  174. package/src/tests/thread-message-like.test.ts +15 -0
  175. package/src/tests/useRemoteThreadListRuntime-controlled.test.tsx +293 -0
  176. package/src/types/index.ts +1 -0
  177. package/src/types/message.ts +19 -0
  178. package/src/utils/data-url.test.ts +26 -2
  179. package/src/utils/data-url.ts +2 -2
  180. package/src/utils/getGroupStatus.test.ts +36 -0
  181. package/src/utils/getGroupStatus.ts +31 -0
  182. package/src/utils/normalizePartStatus.test.ts +59 -0
  183. package/src/utils/normalizePartStatus.ts +55 -0
@@ -14,6 +14,7 @@ export type PartProviderMetadata = {
14
14
  export type TextMessagePart = {
15
15
  readonly type: "text";
16
16
  readonly text: string;
17
+ readonly status?: MessagePartStreamStatus;
17
18
  readonly providerMetadata?: PartProviderMetadata;
18
19
  readonly parentId?: string;
19
20
  };
@@ -21,6 +22,7 @@ export type TextMessagePart = {
21
22
  export type ReasoningMessagePart = {
22
23
  readonly type: "reasoning";
23
24
  readonly text: string;
25
+ readonly status?: MessagePartStreamStatus;
24
26
  readonly providerMetadata?: PartProviderMetadata;
25
27
  readonly parentId?: string;
26
28
  };
@@ -266,6 +268,23 @@ export type MessagePartStatus =
266
268
  readonly error?: unknown;
267
269
  };
268
270
 
271
+ export type MessagePartStreamStatus =
272
+ | {
273
+ readonly type: "running";
274
+ }
275
+ | {
276
+ readonly type: "complete";
277
+ }
278
+ | {
279
+ readonly type: "incomplete";
280
+ readonly reason:
281
+ | "cancelled"
282
+ | "length"
283
+ | "content-filter"
284
+ | "other"
285
+ | "error";
286
+ };
287
+
269
288
  export type ToolCallMessagePartStatus =
270
289
  | {
271
290
  /** The tool call is waiting for UI or human input before continuing. */
@@ -20,8 +20,32 @@ describe("parseDataUrl", () => {
20
20
  expect(parseDataUrl("data:text/plain,hello")).toBeNull();
21
21
  });
22
22
 
23
- it("returns null for data URLs with an empty payload", () => {
24
- expect(parseDataUrl("data:image/png;base64,")).toBeNull();
23
+ it("parses a data URL with an empty payload as zero-byte data", () => {
24
+ expect(parseDataUrl("data:image/png;base64,")).toEqual({
25
+ mimeType: "image/png",
26
+ data: "",
27
+ });
28
+ });
29
+
30
+ it("parses an uppercase scheme", () => {
31
+ expect(parseDataUrl("DATA:image/png;base64,aGVsbG8=")).toEqual({
32
+ mimeType: "image/png",
33
+ data: "aGVsbG8=",
34
+ });
35
+ });
36
+
37
+ it("parses a mixed-case scheme and base64 token", () => {
38
+ expect(parseDataUrl("Data:image/png;Base64,aGVsbG8=")).toEqual({
39
+ mimeType: "image/png",
40
+ data: "aGVsbG8=",
41
+ });
42
+ });
43
+
44
+ it("lowercases the captured mime type", () => {
45
+ expect(parseDataUrl("data:IMAGE/PNG;base64,aGVsbG8=")).toEqual({
46
+ mimeType: "image/png",
47
+ data: "aGVsbG8=",
48
+ });
25
49
  });
26
50
 
27
51
  it("returns null for plain strings", () => {
@@ -3,7 +3,7 @@ export const httpUrlPattern = /^https?:\/\//i;
3
3
  export function parseDataUrl(
4
4
  value: string,
5
5
  ): { mimeType: string; data: string } | null {
6
- const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.+)$/);
6
+ const match = value.match(/^data:([^;,]+)(?:;[^;,]+)*;base64,(.*)$/i);
7
7
  if (!match) return null;
8
- return { mimeType: match[1]!, data: match[2]! };
8
+ return { mimeType: match[1]!.toLowerCase(), data: match[2]! };
9
9
  }
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getGroupStatus } from "./getGroupStatus";
3
+
4
+ describe("getGroupStatus", () => {
5
+ it("reports running when the first member is running and the last is complete", () => {
6
+ const parts = [
7
+ { status: { type: "running" as const } },
8
+ { status: { type: "complete" as const } },
9
+ ];
10
+ const status = getGroupStatus(parts);
11
+
12
+ expect(status).toEqual({ type: "running" });
13
+ expect(getGroupStatus(parts, [0, 1])).toBe(status);
14
+ expect(status).toBe(getGroupStatus([{ status: { type: "running" } }]));
15
+ expect(Object.isFrozen(status)).toBe(true);
16
+ });
17
+
18
+ it("reports the last status when every member is complete", () => {
19
+ const status = { type: "complete" } as const;
20
+
21
+ expect(getGroupStatus([{ status }, { status }])).toBe(status);
22
+ });
23
+
24
+ it("reports complete for an empty group", () => {
25
+ expect(getGroupStatus([])).toEqual({ type: "complete" });
26
+ });
27
+
28
+ it("matches the positional outcome for statusless adapters", () => {
29
+ expect(
30
+ getGroupStatus(
31
+ [{ status: { type: "complete" } }, { status: { type: "running" } }],
32
+ [0, 1],
33
+ ),
34
+ ).toEqual({ type: "running" });
35
+ });
36
+ });
@@ -0,0 +1,31 @@
1
+ import type {
2
+ MessagePartStatus,
3
+ ToolCallMessagePartStatus,
4
+ } from "../types/message";
5
+ import { COMPLETE_STATUS, RUNNING_STATUS } from "./normalizePartStatus";
6
+
7
+ type PartWithStatus = {
8
+ readonly status: MessagePartStatus | ToolCallMessagePartStatus;
9
+ };
10
+
11
+ export const getGroupStatus = (
12
+ parts: readonly (PartWithStatus | undefined)[],
13
+ indices?: readonly number[],
14
+ ): MessagePartStatus | ToolCallMessagePartStatus => {
15
+ if (indices) {
16
+ for (const index of indices) {
17
+ if (parts[index]?.status.type === "running") return RUNNING_STATUS;
18
+ }
19
+
20
+ const lastIndex = indices.at(-1);
21
+ return lastIndex === undefined
22
+ ? COMPLETE_STATUS
23
+ : (parts[lastIndex]?.status ?? COMPLETE_STATUS);
24
+ }
25
+
26
+ for (const part of parts) {
27
+ if (part?.status.type === "running") return RUNNING_STATUS;
28
+ }
29
+
30
+ return parts.at(-1)?.status ?? COMPLETE_STATUS;
31
+ };
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { ThreadAssistantMessagePart } from "../types/message";
3
+ import { normalizePartStatus } from "./normalizePartStatus";
4
+
5
+ const partWithStatus = (status: unknown): ThreadAssistantMessagePart =>
6
+ ({
7
+ type: "text",
8
+ text: "text",
9
+ status,
10
+ }) as unknown as ThreadAssistantMessagePart;
11
+
12
+ describe("normalizePartStatus", () => {
13
+ it("returns a frozen running status", () => {
14
+ const status = normalizePartStatus(partWithStatus({ type: "running" }));
15
+
16
+ expect(status).toEqual({ type: "running" });
17
+ expect(status).toBe(
18
+ normalizePartStatus(partWithStatus({ type: "running" })),
19
+ );
20
+ expect(Object.isFrozen(status)).toBe(true);
21
+ });
22
+
23
+ it("normalizes a complete status without a reason", () => {
24
+ expect(normalizePartStatus(partWithStatus({ type: "complete" }))).toEqual({
25
+ type: "complete",
26
+ });
27
+ });
28
+
29
+ it("drops an upstream complete reason", () => {
30
+ expect(
31
+ normalizePartStatus(
32
+ partWithStatus({ type: "complete", reason: "unknown" }),
33
+ ),
34
+ ).toEqual({ type: "complete" });
35
+ });
36
+
37
+ it.each(["cancelled", "length", "content-filter", "other", "error"] as const)(
38
+ "preserves the incomplete reason %s",
39
+ (reason) => {
40
+ expect(
41
+ normalizePartStatus(partWithStatus({ type: "incomplete", reason })),
42
+ ).toEqual({ type: "incomplete", reason });
43
+ },
44
+ );
45
+
46
+ it("coerces an unrecognized incomplete reason to other", () => {
47
+ expect(
48
+ normalizePartStatus(
49
+ partWithStatus({ type: "incomplete", reason: "unknown" }),
50
+ ),
51
+ ).toEqual({ type: "incomplete", reason: "other" });
52
+ });
53
+
54
+ it("returns undefined for an unknown status type", () => {
55
+ expect(
56
+ normalizePartStatus(partWithStatus({ type: "unknown" })),
57
+ ).toBeUndefined();
58
+ });
59
+ });
@@ -0,0 +1,55 @@
1
+ import type {
2
+ MessagePartStatus,
3
+ MessagePartStreamStatus,
4
+ ThreadAssistantMessagePart,
5
+ ThreadUserMessagePart,
6
+ } from "../types/message";
7
+
8
+ export const COMPLETE_STATUS: MessagePartStatus = Object.freeze({
9
+ type: "complete",
10
+ });
11
+ export const RUNNING_STATUS: MessagePartStatus = Object.freeze({
12
+ type: "running",
13
+ });
14
+ type IncompleteMessagePartStatus = Extract<
15
+ MessagePartStreamStatus,
16
+ { readonly type: "incomplete" }
17
+ >;
18
+ const INCOMPLETE_STATUSES: Readonly<
19
+ Record<IncompleteMessagePartStatus["reason"], IncompleteMessagePartStatus>
20
+ > = Object.freeze({
21
+ cancelled: Object.freeze({ type: "incomplete", reason: "cancelled" }),
22
+ length: Object.freeze({ type: "incomplete", reason: "length" }),
23
+ "content-filter": Object.freeze({
24
+ type: "incomplete",
25
+ reason: "content-filter",
26
+ }),
27
+ other: Object.freeze({ type: "incomplete", reason: "other" }),
28
+ error: Object.freeze({ type: "incomplete", reason: "error" }),
29
+ });
30
+
31
+ export const normalizePartStatus = (
32
+ part: ThreadUserMessagePart | ThreadAssistantMessagePart,
33
+ ): MessagePartStatus | undefined => {
34
+ const status = (part as { readonly status?: unknown }).status;
35
+ if (!status || typeof status !== "object") return undefined;
36
+
37
+ const { type } = status as { readonly type?: unknown };
38
+ if (type === "running") return RUNNING_STATUS;
39
+ if (type === "complete") return COMPLETE_STATUS;
40
+ if (type !== "incomplete") return undefined;
41
+
42
+ const { reason } = status as {
43
+ readonly reason?: unknown;
44
+ };
45
+ const normalizedReason: IncompleteMessagePartStatus["reason"] =
46
+ reason === "cancelled" ||
47
+ reason === "length" ||
48
+ reason === "content-filter" ||
49
+ reason === "other" ||
50
+ reason === "error"
51
+ ? reason
52
+ : "other";
53
+
54
+ return INCOMPLETE_STATUSES[normalizedReason];
55
+ };