@opengeni/react 1.4.2 → 1.4.5-canary.37458084490001

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 (240) hide show
  1. package/README.md +17 -6
  2. package/dist/artifacts.d.ts +1 -0
  3. package/dist/artifacts.js +22 -15
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{browser-viewer-WGJ43YGI.js → browser-viewer-CV23VIHB.js} +8 -6
  6. package/dist/chunk-3ZC6QPOM.js +174 -0
  7. package/dist/chunk-3ZC6QPOM.js.map +1 -0
  8. package/dist/{chunk-YVFRBXST.js → chunk-4BCVCJSU.js} +2 -2
  9. package/dist/{chunk-JHTIMBOL.js → chunk-5JSUNNQN.js} +26 -4
  10. package/dist/{chunk-JHTIMBOL.js.map → chunk-5JSUNNQN.js.map} +1 -1
  11. package/dist/{chunk-MDIXFKA3.js → chunk-6FOTSVWU.js} +303 -109
  12. package/dist/chunk-6FOTSVWU.js.map +1 -0
  13. package/dist/{chunk-IUHHPMAX.js → chunk-7RRBX5EM.js} +14 -85
  14. package/dist/chunk-7RRBX5EM.js.map +1 -0
  15. package/dist/{chunk-L2J3PU7A.js → chunk-7ZB7I3KP.js} +24 -98
  16. package/dist/chunk-7ZB7I3KP.js.map +1 -0
  17. package/dist/{chunk-LVS3MH5T.js → chunk-ATIRUHBZ.js} +128 -10
  18. package/dist/chunk-ATIRUHBZ.js.map +1 -0
  19. package/dist/chunk-BOFEQC6K.js +162 -0
  20. package/dist/chunk-BOFEQC6K.js.map +1 -0
  21. package/dist/{chunk-VCSERYXH.js → chunk-CESBPQP5.js} +309 -162
  22. package/dist/chunk-CESBPQP5.js.map +1 -0
  23. package/dist/{chunk-IYQCWGU5.js → chunk-GHJU7XG7.js} +35 -62
  24. package/dist/chunk-GHJU7XG7.js.map +1 -0
  25. package/dist/{chunk-G6YRCIYJ.js → chunk-HNVWMDNN.js} +55 -60
  26. package/dist/chunk-HNVWMDNN.js.map +1 -0
  27. package/dist/{chunk-PHBFJCEU.js → chunk-I5EGRXXG.js} +110 -7
  28. package/dist/chunk-I5EGRXXG.js.map +1 -0
  29. package/dist/{chunk-PBCZL4JP.js → chunk-INIZSNXT.js} +2 -2
  30. package/dist/{chunk-XS67YG5R.js → chunk-IX3K4THH.js} +24 -3
  31. package/dist/chunk-IX3K4THH.js.map +1 -0
  32. package/dist/chunk-IZV5FTJC.js +55 -0
  33. package/dist/chunk-IZV5FTJC.js.map +1 -0
  34. package/dist/chunk-JDJJMBB3.js +1883 -0
  35. package/dist/chunk-JDJJMBB3.js.map +1 -0
  36. package/dist/{chunk-C5BF4L26.js → chunk-KHF2VRXF.js} +7 -7
  37. package/dist/{chunk-QMMCJGFI.js → chunk-L5OKM2VX.js} +2 -2
  38. package/dist/chunk-LHF6BVM4.js +83 -0
  39. package/dist/chunk-LHF6BVM4.js.map +1 -0
  40. package/dist/chunk-N7S72EXK.js +25 -0
  41. package/dist/chunk-N7S72EXK.js.map +1 -0
  42. package/dist/{chunk-CVYCLFMH.js → chunk-PEHTX3V7.js} +19 -1
  43. package/dist/chunk-PEHTX3V7.js.map +1 -0
  44. package/dist/chunk-R5R5RQQD.js +51 -0
  45. package/dist/chunk-R5R5RQQD.js.map +1 -0
  46. package/dist/chunk-RI42H5IU.js +326 -0
  47. package/dist/chunk-RI42H5IU.js.map +1 -0
  48. package/dist/{chunk-JH7V627O.js → chunk-SHLZMAJN.js} +2 -11
  49. package/dist/chunk-SHLZMAJN.js.map +1 -0
  50. package/dist/{chunk-CREMJMVN.js → chunk-TZQSHCZN.js} +692 -1996
  51. package/dist/chunk-TZQSHCZN.js.map +1 -0
  52. package/dist/chunk-ZIB4H5LN.js +101 -0
  53. package/dist/chunk-ZIB4H5LN.js.map +1 -0
  54. package/dist/client.d.ts +9 -3
  55. package/dist/clipboard.js +1 -1
  56. package/dist/components/browser-viewer.d.ts +6 -0
  57. package/dist/components/composer.d.ts +3 -69
  58. package/dist/components/human-input-form.d.ts +3 -32
  59. package/dist/components/markdown.d.ts +8 -0
  60. package/dist/components/model-policy-picker.d.ts +4 -37
  61. package/dist/components/opper-mark.d.ts +7 -0
  62. package/dist/components/provider-recovery-notice.d.ts +15 -0
  63. package/dist/components/remark-soft-line-breaks.d.ts +38 -0
  64. package/dist/components/sandbox-workspace.d.ts +13 -0
  65. package/dist/components/session-chrome.d.ts +5 -3
  66. package/dist/components/session-conversation.d.ts +15 -3
  67. package/dist/composer-messages.d.ts +74 -0
  68. package/dist/composer-messages.js +9 -0
  69. package/dist/composer.js +15 -10
  70. package/dist/{computer-viewer-46UIN7F3.js → computer-viewer-K4CKYRX6.js} +4 -4
  71. package/dist/connect-setup.d.ts +2 -0
  72. package/dist/connect.d.ts +1 -1
  73. package/dist/connect.js +21 -6
  74. package/dist/connect.js.map +1 -1
  75. package/dist/{embedded-voice-NIL4RDX3.js → embedded-voice-3ZFWWJ6O.js} +6 -4
  76. package/dist/{embedded-voice-NIL4RDX3.js.map → embedded-voice-3ZFWWJ6O.js.map} +1 -1
  77. package/dist/hooks/shared-machines-list.d.ts +4 -0
  78. package/dist/hooks/use-client-config-flags.d.ts +6 -0
  79. package/dist/hooks/use-computer-session.d.ts +2 -0
  80. package/dist/hooks/use-goal.d.ts +2 -1
  81. package/dist/human-input-model.d.ts +40 -0
  82. package/dist/index.d.ts +4 -1
  83. package/dist/index.js +116 -73
  84. package/dist/index.js.map +1 -1
  85. package/dist/interaction.js +5 -5
  86. package/dist/{latest-question-2POFPYNB.js → latest-question-SWC6QEVN.js} +3 -3
  87. package/dist/lib/clipboard.d.ts +7 -0
  88. package/dist/lib/provider-recovery.d.ts +49 -0
  89. package/dist/machines.js +2 -2
  90. package/dist/model-mark-paths.d.ts +34 -0
  91. package/dist/model-mark-paths.js +16 -0
  92. package/dist/model-picker-model.d.ts +111 -0
  93. package/dist/model-picker-model.js +25 -0
  94. package/dist/model-picker-model.js.map +1 -0
  95. package/dist/{model-policy-picker-menu-WRNSRFQC.js → model-policy-picker-menu-NM4RPTY6.js} +6 -4
  96. package/dist/model-policy-picker-menu-NM4RPTY6.js.map +1 -0
  97. package/dist/model-policy.d.ts +10 -0
  98. package/dist/model-policy.js +5 -1
  99. package/dist/organization-model.d.ts +32 -0
  100. package/dist/organization-model.js +65 -0
  101. package/dist/organization-model.js.map +1 -0
  102. package/dist/{platform-activity-row-PJJVATTZ.js → platform-activity-row-NJ47YBXW.js} +7 -84
  103. package/dist/platform-activity-row-NJ47YBXW.js.map +1 -0
  104. package/dist/queue-presentation-model.d.ts +72 -0
  105. package/dist/queue-presentation-model.js +38 -0
  106. package/dist/queue-presentation-model.js.map +1 -0
  107. package/dist/{queue-surface-implementation-67FS7FUI.js → queue-surface-implementation-YPJFQ3O6.js} +24 -252
  108. package/dist/queue-surface-implementation-YPJFQ3O6.js.map +1 -0
  109. package/dist/realtime.js +5 -3
  110. package/dist/{sandbox-files-34CJJNOY.js → sandbox-files-RCVCMXG7.js} +5 -5
  111. package/dist/sandbox-files-RCVCMXG7.js.map +1 -0
  112. package/dist/sandbox-label-model.d.ts +23 -0
  113. package/dist/sandbox-label-model.js +28 -0
  114. package/dist/sandbox-label-model.js.map +1 -0
  115. package/dist/session-attention-model.d.ts +8 -0
  116. package/dist/session-attention-model.js +46 -0
  117. package/dist/session-attention-model.js.map +1 -0
  118. package/dist/session-feedback-model.d.ts +20 -0
  119. package/dist/session-feedback-model.js +56 -0
  120. package/dist/session-feedback-model.js.map +1 -0
  121. package/dist/session-list-model.d.ts +96 -0
  122. package/dist/session-list-model.js +219 -0
  123. package/dist/session-list-model.js.map +1 -0
  124. package/dist/session-status-model.d.ts +19 -0
  125. package/dist/session-ui.js +26 -17
  126. package/dist/session-ui.js.map +1 -1
  127. package/dist/session.js +8 -6
  128. package/dist/testing/session-scenarios.d.ts +10 -0
  129. package/dist/testing/session-scenarios.js +303 -0
  130. package/dist/testing/session-scenarios.js.map +1 -0
  131. package/dist/timeline/genie-copy.d.ts +3 -0
  132. package/dist/timeline/genie-loading.d.ts +11 -0
  133. package/dist/timeline/index.d.ts +1 -1
  134. package/dist/timeline/notice-presentation.d.ts +7 -0
  135. package/dist/timeline/patch-apply-command.d.ts +18 -0
  136. package/dist/timeline/platform-activity-presentation.d.ts +7 -0
  137. package/dist/timeline/presented-image.d.ts +6 -1
  138. package/dist/timeline/question-nav-model.d.ts +23 -0
  139. package/dist/timeline/registry.d.ts +10 -1
  140. package/dist/timeline/retained-loaders.d.ts +1 -1
  141. package/dist/timeline/tool-presentation.d.ts +172 -0
  142. package/dist/timeline/turn-summary-model.d.ts +90 -0
  143. package/dist/timeline/turn-summary.d.ts +4 -74
  144. package/dist/timeline/work-presentation.d.ts +42 -0
  145. package/dist/timeline-model.d.ts +24 -0
  146. package/dist/timeline-model.js +152 -0
  147. package/dist/timeline-model.js.map +1 -0
  148. package/package.json +47 -3
  149. package/src/artifacts.ts +1 -0
  150. package/src/client.ts +9 -3
  151. package/src/components/browser-viewer.tsx +23 -2
  152. package/src/components/claude-mark.tsx +2 -1
  153. package/src/components/composer.tsx +4 -144
  154. package/src/components/computer-viewer.tsx +139 -30
  155. package/src/components/grok-mark.tsx +2 -1
  156. package/src/components/human-input-form.tsx +13 -124
  157. package/src/components/markdown.tsx +19 -1
  158. package/src/components/message-timeline.tsx +45 -99
  159. package/src/components/model-mark.tsx +2 -1
  160. package/src/components/model-policy-picker-menu.tsx +30 -85
  161. package/src/components/model-policy-picker.tsx +15 -148
  162. package/src/components/opper-mark.tsx +18 -0
  163. package/src/components/provider-recovery-notice.tsx +41 -0
  164. package/src/components/queue-surface-implementation.tsx +20 -349
  165. package/src/components/remark-soft-line-breaks.ts +166 -0
  166. package/src/components/sandbox-workspace.tsx +17 -1
  167. package/src/components/session-chrome.tsx +114 -99
  168. package/src/components/session-conversation.tsx +55 -6
  169. package/src/components/session-list.tsx +1 -1
  170. package/src/components/session-status.tsx +17 -16
  171. package/src/composer-messages.ts +150 -0
  172. package/src/connect-setup.tsx +45 -11
  173. package/src/connect.ts +1 -1
  174. package/src/hooks/shared-machines-list.ts +34 -1
  175. package/src/hooks/use-browser-session.ts +445 -168
  176. package/src/hooks/use-client-config-flags.ts +8 -0
  177. package/src/hooks/use-computer-session.ts +334 -98
  178. package/src/hooks/use-goal.ts +66 -10
  179. package/src/human-input-model.ts +147 -0
  180. package/src/index.ts +16 -0
  181. package/src/lib/clipboard.ts +29 -0
  182. package/src/lib/format.ts +7 -0
  183. package/src/lib/provider-recovery.ts +202 -0
  184. package/src/model-mark-paths.ts +53 -0
  185. package/src/model-picker-model.ts +330 -0
  186. package/src/model-policy.ts +43 -2
  187. package/src/organization-model.ts +118 -0
  188. package/src/queue-presentation-model.ts +464 -0
  189. package/src/sandbox-label-model.ts +41 -0
  190. package/src/session-attention-model.ts +59 -0
  191. package/src/session-feedback-model.ts +95 -0
  192. package/src/session-list-model.ts +327 -0
  193. package/src/session-status-model.ts +62 -0
  194. package/src/testing/session-scenarios.ts +326 -0
  195. package/src/timeline/activity-rail.tsx +5 -17
  196. package/src/timeline/genie-copy.ts +19 -0
  197. package/src/timeline/genie-loading.tsx +13 -18
  198. package/src/timeline/index.ts +1 -0
  199. package/src/timeline/notice-presentation.ts +22 -0
  200. package/src/timeline/patch-apply-command.tsx +135 -0
  201. package/src/timeline/platform-activity-presentation.ts +122 -0
  202. package/src/timeline/platform-activity-row.tsx +6 -96
  203. package/src/timeline/presented-image.ts +16 -2
  204. package/src/timeline/question-nav-model.ts +28 -0
  205. package/src/timeline/registry.ts +11 -1
  206. package/src/timeline/retained-loaders.ts +9 -4
  207. package/src/timeline/rolling-activity.tsx +7 -19
  208. package/src/timeline/tool-presentation.ts +1316 -0
  209. package/src/timeline/tool-renderers.tsx +226 -1025
  210. package/src/timeline/turn-summary-model.ts +338 -0
  211. package/src/timeline/turn-summary.tsx +21 -326
  212. package/src/timeline/work-presentation.ts +139 -0
  213. package/src/timeline-model.ts +108 -0
  214. package/styles/compiled.css +8 -0
  215. package/styles/connect.css +4 -0
  216. package/dist/chunk-CREMJMVN.js.map +0 -1
  217. package/dist/chunk-CVYCLFMH.js.map +0 -1
  218. package/dist/chunk-G6YRCIYJ.js.map +0 -1
  219. package/dist/chunk-HXYOSSQO.js +0 -117
  220. package/dist/chunk-HXYOSSQO.js.map +0 -1
  221. package/dist/chunk-IUHHPMAX.js.map +0 -1
  222. package/dist/chunk-IYQCWGU5.js.map +0 -1
  223. package/dist/chunk-JH7V627O.js.map +0 -1
  224. package/dist/chunk-L2J3PU7A.js.map +0 -1
  225. package/dist/chunk-LVS3MH5T.js.map +0 -1
  226. package/dist/chunk-MDIXFKA3.js.map +0 -1
  227. package/dist/chunk-PHBFJCEU.js.map +0 -1
  228. package/dist/chunk-VCSERYXH.js.map +0 -1
  229. package/dist/chunk-XS67YG5R.js.map +0 -1
  230. package/dist/platform-activity-row-PJJVATTZ.js.map +0 -1
  231. package/dist/queue-surface-implementation-67FS7FUI.js.map +0 -1
  232. /package/dist/{browser-viewer-WGJ43YGI.js.map → browser-viewer-CV23VIHB.js.map} +0 -0
  233. /package/dist/{chunk-YVFRBXST.js.map → chunk-4BCVCJSU.js.map} +0 -0
  234. /package/dist/{chunk-PBCZL4JP.js.map → chunk-INIZSNXT.js.map} +0 -0
  235. /package/dist/{chunk-C5BF4L26.js.map → chunk-KHF2VRXF.js.map} +0 -0
  236. /package/dist/{chunk-QMMCJGFI.js.map → chunk-L5OKM2VX.js.map} +0 -0
  237. /package/dist/{computer-viewer-46UIN7F3.js.map → composer-messages.js.map} +0 -0
  238. /package/dist/{model-policy-picker-menu-WRNSRFQC.js.map → computer-viewer-K4CKYRX6.js.map} +0 -0
  239. /package/dist/{latest-question-2POFPYNB.js.map → latest-question-SWC6QEVN.js.map} +0 -0
  240. /package/dist/{sandbox-files-34CJJNOY.js.map → model-mark-paths.js.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { OpenGeniApiError, type SessionEvent, type SessionGoal } from "@opengeni/sdk";
2
- import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { useEmbeddedGoal, type EmbeddedGoalClientOverride } from "../session-context";
4
4
  import { normalizeError } from "../lib/error-message";
5
5
  import {
@@ -65,7 +65,8 @@ export type UseGoalResult = {
65
65
  /**
66
66
  * The session's goal: state, the autonomy counters (`autoContinuations`,
67
67
  * `noProgressStreak`), and pause/resume control. A goal-less session yields
68
- * `goal: null` (the 404 is absorbed). Live-updates on goal, turn, session,
68
+ * `goal: null`: read through `findGoal` as a successful null when the client
69
+ * has it, otherwise from an absorbed 404. Live-updates on goal, turn, session,
69
70
  * control, and system-update events — pass `options.events` from
70
71
  * `useSessionEvents` to reuse its stream.
71
72
  */
@@ -84,10 +85,19 @@ export function useGoal(
84
85
  const { run, mutating, mutationError, clearMutationError } = useMutationRunner();
85
86
  const generation = useRef(0);
86
87
  const targetKeyRef = useRef<string | null>(null);
87
- // True after the server answered 404. Only a goal event can create a goal,
88
- // so turn/session traffic must not refetch (and 404) while none exists.
88
+ // True after the server answered that no goal exists. Only a goal event can
89
+ // create a goal, so turn/session traffic must not refetch while none exists.
89
90
  const goalAbsentRef = useRef(false);
91
+ // Refresh events not yet covered by a read: "goal" when any was a goal.*
92
+ // event. Events that arrive while a read is in flight are decided when it
93
+ // settles, so the stream's opening burst never re-reads a goal-less session.
94
+ const pendingRefreshRef = useRef<"none" | "refresh" | "goal">("none");
95
+ const sharedEventsRef = useRef(sharedEvents);
96
+ useLayoutEffect(() => {
97
+ sharedEventsRef.current = sharedEvents;
98
+ }, [sharedEvents]);
90
99
  const loadAbort = useRef<AbortController | null>(null);
100
+ const scheduleRefreshRef = useRef<() => void>(() => undefined);
91
101
 
92
102
  const retireLoads = useCallback(() => {
93
103
  generation.current += 1;
@@ -100,15 +110,20 @@ export function useGoal(
100
110
  return;
101
111
  }
102
112
  const ticket = ++generation.current;
113
+ pendingRefreshRef.current = "none";
103
114
  loadAbort.current?.abort();
104
115
  const controller = new AbortController();
105
116
  loadAbort.current = controller;
106
117
  try {
107
- const fetched = await client.getGoal(workspaceId, sessionId, {
108
- signal: controller.signal,
109
- });
118
+ // findGoal reads a goal-less session as a successful null, so no failed
119
+ // request reaches the browser console. Custom clients without it keep
120
+ // getGoal, whose 404 is absorbed below (as it is from older servers).
121
+ const fetched =
122
+ typeof client.findGoal === "function"
123
+ ? await client.findGoal(workspaceId, sessionId, { signal: controller.signal })
124
+ : await client.getGoal(workspaceId, sessionId, { signal: controller.signal });
110
125
  if (ticket === generation.current) {
111
- goalAbsentRef.current = false;
126
+ goalAbsentRef.current = fetched === null;
112
127
  setGoal(fetched);
113
128
  setError(null);
114
129
  setLoading(false);
@@ -129,6 +144,26 @@ export function useGoal(
129
144
  } finally {
130
145
  if (loadAbort.current === controller) loadAbort.current = null;
131
146
  }
147
+ if (ticket !== generation.current || pendingRefreshRef.current === "none") {
148
+ return;
149
+ }
150
+ // Events arrived during this read. A shared feed's first batch reports only
151
+ // its latest match, so a goal.* event behind newer turn traffic is visible
152
+ // only in the log: re-read when its latest goal event is not a clear.
153
+ if (goalAbsentRef.current && pendingRefreshRef.current === "refresh") {
154
+ const events = sharedEventsRef.current ?? [];
155
+ let latestGoalEvent: SessionEvent | undefined;
156
+ for (let index = events.length - 1; index >= 0 && !latestGoalEvent; index -= 1) {
157
+ const event = events[index];
158
+ if (event && isGoalEvent(event)) latestGoalEvent = event;
159
+ }
160
+ if (!latestGoalEvent || latestGoalEvent.type === "goal.cleared") {
161
+ pendingRefreshRef.current = "none";
162
+ return;
163
+ }
164
+ pendingRefreshRef.current = "goal";
165
+ }
166
+ scheduleRefreshRef.current();
132
167
  }, [client, workspaceId, sessionId]);
133
168
 
134
169
  useEffect(() => {
@@ -136,6 +171,7 @@ export function useGoal(
136
171
  if (targetKeyRef.current !== targetKey) {
137
172
  targetKeyRef.current = targetKey;
138
173
  goalAbsentRef.current = false;
174
+ pendingRefreshRef.current = "none";
139
175
  setGoal(null);
140
176
  setError(null);
141
177
  }
@@ -186,13 +222,33 @@ export function useGoal(
186
222
  sharedFeed,
187
223
  ]);
188
224
 
189
- const scheduleRefresh = useDebouncedCallback(() => void load());
225
+ const scheduleRefresh = useDebouncedCallback(() => {
226
+ const pending = pendingRefreshRef.current;
227
+ // An in-flight read settles pending events itself.
228
+ if (pending === "none" || loadAbort.current !== null) return;
229
+ if (pending === "refresh" && goalAbsentRef.current) {
230
+ pendingRefreshRef.current = "none";
231
+ return;
232
+ }
233
+ void load();
234
+ });
235
+ useLayoutEffect(() => {
236
+ scheduleRefreshRef.current = scheduleRefresh;
237
+ }, [scheduleRefresh]);
190
238
  const isRefreshEvent = useCallback(
191
239
  (event: SessionEvent) =>
192
240
  goalAbsentRef.current ? isGoalEvent(event) : isGoalRefreshEvent(event),
193
241
  [],
194
242
  );
195
- useSessionEventTrigger(client, workspaceId, sessionId, isRefreshEvent, scheduleRefresh, {
243
+ const onRefreshEvent = useCallback(
244
+ (event: SessionEvent) => {
245
+ if (isGoalEvent(event)) pendingRefreshRef.current = "goal";
246
+ else if (pendingRefreshRef.current === "none") pendingRefreshRef.current = "refresh";
247
+ scheduleRefresh();
248
+ },
249
+ [scheduleRefresh],
250
+ );
251
+ useSessionEventTrigger(client, workspaceId, sessionId, isRefreshEvent, onRefreshEvent, {
196
252
  enabled,
197
253
  ...(sharedEvents !== undefined ? { events: sharedEvents } : {}),
198
254
  });
@@ -0,0 +1,147 @@
1
+ import type { HumanInputAnswer, HumanInputQuestion } from "@opengeni/sdk";
2
+
3
+ /* Pure drafting and validation rules for structured human-input requests,
4
+ shared by the web HumanInputForm and non-DOM renderers. */
5
+
6
+ export type HumanInputAnswerDraft = {
7
+ values: string[];
8
+ other: string;
9
+ otherSelected: boolean;
10
+ };
11
+
12
+ export type HumanInputFormMessages = {
13
+ title: string;
14
+ description: string;
15
+ submit: string;
16
+ skip: string;
17
+ submitting: string;
18
+ other: string;
19
+ deadlineLabel: string;
20
+ formatDeadline: (value: string) => string;
21
+ required: string;
22
+ otherRequired: string;
23
+ minSelections: (count: number) => string;
24
+ maxSelections: (count: number) => string;
25
+ optional: string;
26
+ /** Shown when the question list overflows the card and more content is below. */
27
+ moreBelow: string;
28
+ questionCount: (count: number) => string;
29
+ collapse: string;
30
+ expand: string;
31
+ selectionHint: (min: number | null | undefined, max: number | null | undefined) => string | null;
32
+ };
33
+
34
+ export const defaultHumanInputFormMessages: HumanInputFormMessages = {
35
+ title: "Input required",
36
+ /** Multi-question chrome has no default subtitle; hosts may still override. */
37
+ description: "",
38
+ submit: "Send answers",
39
+ skip: "Skip",
40
+ submitting: "Submitting…",
41
+ other: "Other",
42
+ deadlineLabel: "Expires",
43
+ formatDeadline,
44
+ required: "This question is required.",
45
+ otherRequired: "Enter a value for Other.",
46
+ minSelections: (count) => `Choose at least ${count} option${count === 1 ? "" : "s"}.`,
47
+ maxSelections: (count) => `Choose no more than ${count} option${count === 1 ? "" : "s"}.`,
48
+ optional: "Optional",
49
+ moreBelow: "More below",
50
+ questionCount: (count) => `${count} questions`,
51
+ collapse: "Collapse",
52
+ expand: "Expand",
53
+ selectionHint: (min, max) => {
54
+ if (min != null && max != null) return `Choose ${min}–${max}.`;
55
+ if (min != null) return `Choose at least ${min}.`;
56
+ if (max != null) return `Choose up to ${max}.`;
57
+ return null;
58
+ },
59
+ };
60
+
61
+ export function answersFromDrafts(
62
+ questions: HumanInputQuestion[],
63
+ drafts: Record<string, HumanInputAnswerDraft>,
64
+ messageOverrides: Partial<HumanInputFormMessages> = {},
65
+ ): { answers: HumanInputAnswer[]; errors: Record<string, string> } {
66
+ const messages = { ...defaultHumanInputFormMessages, ...messageOverrides };
67
+ const answers: HumanInputAnswer[] = [];
68
+ const errors: Record<string, string> = {};
69
+ for (const question of questions) {
70
+ const draft = drafts[question.id] ?? emptyDraft();
71
+ const values = question.kind === "text" ? draft.values.filter(Boolean) : draft.values;
72
+ const other = draft.otherSelected ? draft.other : "";
73
+ const hasOther = Boolean(other.trim());
74
+ const supplied = values.length + (hasOther ? 1 : 0);
75
+
76
+ // Other-selected-but-empty must win over generic "required" — otherwise the
77
+ // user sees the wrong diagnosis next to a clearly selected control.
78
+ if (question.kind !== "text" && draft.otherSelected && !hasOther) {
79
+ errors[question.id] = messages.otherRequired;
80
+ continue;
81
+ }
82
+
83
+ if (question.required && supplied === 0) {
84
+ errors[question.id] = messages.required;
85
+ continue;
86
+ }
87
+ if (question.kind !== "text") {
88
+ const min = question.validation?.minSelections;
89
+ const max = question.kind === "single_select" ? 1 : question.validation?.maxSelections;
90
+ if (min != null && supplied < min) {
91
+ errors[question.id] = messages.minSelections(min);
92
+ continue;
93
+ }
94
+ if (max != null && supplied > max) {
95
+ errors[question.id] = messages.maxSelections(max);
96
+ continue;
97
+ }
98
+ }
99
+ if (supplied > 0) {
100
+ answers.push({
101
+ questionId: question.id,
102
+ values,
103
+ ...(hasOther ? { other } : {}),
104
+ });
105
+ }
106
+ }
107
+ return { answers, errors };
108
+ }
109
+
110
+ export function initialDrafts(
111
+ questions: HumanInputQuestion[],
112
+ ): Record<string, HumanInputAnswerDraft> {
113
+ return Object.fromEntries(questions.map((question) => [question.id, emptyDraft()]));
114
+ }
115
+
116
+ export function emptyDraft(): HumanInputAnswerDraft {
117
+ return { values: [], other: "", otherSelected: false };
118
+ }
119
+
120
+ export function formatDeadline(value: string): string {
121
+ const date = new Date(value);
122
+ if (Number.isNaN(date.getTime())) return value;
123
+ const ms = date.getTime() - Date.now();
124
+ if (ms <= 0) return "deadline passed";
125
+ const minutes = Math.round(ms / 60_000);
126
+ if (minutes < 1) return "in under a minute";
127
+ if (minutes < 60) return `in ${minutes}m`;
128
+ const hours = Math.round(minutes / 60);
129
+ if (hours < 48) return `in ${hours}h`;
130
+ return date.toLocaleString();
131
+ }
132
+
133
+ /** The form heading: a single question leads with its own label/prompt. */
134
+ export function humanInputHeading(
135
+ questions: readonly HumanInputQuestion[],
136
+ messages: Pick<HumanInputFormMessages, "title" | "description">,
137
+ ): { title: string; description: string | null } {
138
+ const single = questions.length === 1 ? questions[0]! : null;
139
+ return {
140
+ title: single ? (single.label ?? single.prompt) : messages.title,
141
+ description: single
142
+ ? single.label
143
+ ? single.prompt
144
+ : (single.helpText ?? null)
145
+ : messages.description || null,
146
+ };
147
+ }
package/src/index.ts CHANGED
@@ -401,6 +401,7 @@ export {
401
401
  export type {
402
402
  CreateToolRegistryOptions,
403
403
  RetainedArtifactLoader,
404
+ RetainedArtifactLoadOptions,
404
405
  RetainedArtifactLoaderClient,
405
406
  RetainedScreenshotLoader,
406
407
  RetainedScreenshotLoaderClient,
@@ -716,6 +717,20 @@ export {
716
717
  truncate,
717
718
  tryParseJson,
718
719
  } from "./lib/format";
720
+ export {
721
+ currentProviderRecovery,
722
+ parseProviderRecovery,
723
+ providerRecoveryExhaustedText,
724
+ providerRecoveryRetryingText,
725
+ providerRecoverySubject,
726
+ type ProviderRecoveryCondition,
727
+ type ProviderRecoveryFacts,
728
+ } from "./lib/provider-recovery";
729
+ export {
730
+ ProviderRecoveryNotice,
731
+ PROVIDER_RECOVERY_NOTICE_DETAIL,
732
+ type ProviderRecoveryNoticeProps,
733
+ } from "./components/provider-recovery-notice";
719
734
  export { SessionCommandsPanel } from "./components/session-commands-panel";
720
735
  export { SessionCommands } from "./components/session-commands";
721
736
 
@@ -728,6 +743,7 @@ export { setStartupDetails, useStartupDetails } from "./timeline/startup-prefere
728
743
  export { ClaudeMark } from "./components/claude-mark";
729
744
  export { AnthropicMark } from "./components/anthropic-mark";
730
745
  export { OpenRouterMark } from "./components/openrouter-mark";
746
+ export { OpperMark } from "./components/opper-mark";
731
747
  export { GrokMark } from "./components/grok-mark";
732
748
 
733
749
  export { ToolReviewHistoryProvider } from "./components/tool-review-history";
@@ -35,6 +35,35 @@ export async function copyTextToClipboard(text: string): Promise<boolean> {
35
35
  }
36
36
  }
37
37
 
38
+ /**
39
+ * Copy text that is still being prepared, such as a command around a freshly
40
+ * minted URL. Safari only allows a clipboard write during the click itself, so
41
+ * hand the pending text to a `ClipboardItem` where supported; otherwise wait
42
+ * for it and copy. Resolves false when the text failed or nothing was copied.
43
+ */
44
+ export async function copyPendingTextToClipboard(text: Promise<string>): Promise<boolean> {
45
+ if (
46
+ typeof ClipboardItem !== "undefined" &&
47
+ typeof navigator !== "undefined" &&
48
+ typeof navigator.clipboard?.write === "function"
49
+ ) {
50
+ const blob = text.then((value) => new Blob([value], { type: "text/plain" }));
51
+ // The caller observes a failed `text`; never leave this copy unhandled.
52
+ blob.catch(() => {});
53
+ try {
54
+ await navigator.clipboard.write([new ClipboardItem({ "text/plain": blob })]);
55
+ return true;
56
+ } catch {
57
+ // Fall through: some browsers reject promised clipboard items.
58
+ }
59
+ }
60
+ try {
61
+ return await copyTextToClipboard(await text);
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
38
67
  /** Serialize an HTML table to tab-separated values (spreadsheet-friendly). */
39
68
  export function tableElementToTsv(table: HTMLTableElement | null | undefined): string {
40
69
  if (!table) {
package/src/lib/format.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { formatErrorMessage, OpenGeniApiError } from "@opengeni/sdk";
2
+ import { parseProviderRecovery, providerRecoveryExhaustedText } from "./provider-recovery";
2
3
 
3
4
  const clockTimeFormatter = new Intl.DateTimeFormat(undefined, {
4
5
  month: "short",
@@ -312,6 +313,12 @@ export function presentFailure(payload: Record<string, unknown>): {
312
313
  safetyRefusal: true,
313
314
  };
314
315
  }
316
+ // Spent automatic retries: name the model and the remedy instead of the
317
+ // recorded wrapper plus raw provider text (still in the stored event).
318
+ const recovery = parseProviderRecovery(payload);
319
+ if (recovery && payload.recoveryExhausted === true) {
320
+ return { reason: providerRecoveryExhaustedText(recovery), safetyRefusal: false };
321
+ }
315
322
  return {
316
323
  reason:
317
324
  detail && detail !== message
@@ -0,0 +1,202 @@
1
+ import type { SessionEvent, SessionStatus } from "@opengeni/sdk";
2
+
3
+ /**
4
+ * Finite same-turn provider recovery, projected for people. Presentation only:
5
+ * the worker owns retry authority, pacing and the recovery budget. The worker
6
+ * (`apps/worker/src/activities/agent-turn/provider-recovery-copy.ts`) writes
7
+ * the same exhausted sentence into `turn.failed.error`; keep the two in step.
8
+ */
9
+ export type ProviderRecoveryCondition =
10
+ | "overloaded"
11
+ | "unavailable"
12
+ | "unresponsive"
13
+ | "rate_limited";
14
+
15
+ export type ProviderRecoveryFacts = {
16
+ /** Recorded worker failure code (`provider_unavailable`, `mcp_transport_timeout`, ...). */
17
+ code: string;
18
+ condition: ProviderRecoveryCondition | null;
19
+ /** Display label of the affected model, when the route was recorded. */
20
+ modelLabel: string | null;
21
+ /** Display label of the serving provider, when the route was recorded. */
22
+ providerLabel: string | null;
23
+ /** Automatic retries scheduled (live) or spent (exhausted). */
24
+ attempt: number | null;
25
+ /** Finite automatic retry budget. */
26
+ maxAttempts: number | null;
27
+ /** True when the model route itself is affected, so another model may help. */
28
+ modelRoute: boolean;
29
+ };
30
+
31
+ const MODEL_CODES: ReadonlySet<string> = new Set([
32
+ "provider_unavailable",
33
+ "provider_rate_limited",
34
+ "provider_unknown_finish_reason",
35
+ "post_compaction_continuation_empty",
36
+ ]);
37
+
38
+ const RECOVERY_CODES: ReadonlySet<string> = new Set([
39
+ ...MODEL_CODES,
40
+ "upstream_connectivity_unavailable",
41
+ "mcp_transport_timeout",
42
+ "mcp_transport_unavailable",
43
+ "sandbox_command_start_unavailable",
44
+ "turn_execution_policy_definition_mismatch",
45
+ ]);
46
+
47
+ /** Matches the worker's default budget for legacy events that omit it. */
48
+ const DEFAULT_MAX_ATTEMPTS = 5;
49
+
50
+ function text(value: unknown, max = 120): string | null {
51
+ if (typeof value !== "string") return null;
52
+ const normalized = value.replace(/\s+/g, " ").trim();
53
+ return normalized ? normalized.slice(0, max) : null;
54
+ }
55
+
56
+ function count(value: unknown): number | null {
57
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
58
+ }
59
+
60
+ function condition(
61
+ payload: Record<string, unknown>,
62
+ code: string,
63
+ ): ProviderRecoveryCondition | null {
64
+ const recorded = payload.providerCondition;
65
+ if (
66
+ recorded === "overloaded" ||
67
+ recorded === "unavailable" ||
68
+ recorded === "unresponsive" ||
69
+ recorded === "rate_limited"
70
+ )
71
+ return recorded;
72
+ if (code === "provider_rate_limited") return "rate_limited";
73
+ if (code !== "provider_unavailable") return null;
74
+ // Older events predate the typed condition; their provider text still says so.
75
+ return [payload.lastRetryableError, payload.detail, payload.error].some(
76
+ (value) => typeof value === "string" && /\boverload(?:ed)?(?:_error)?\b/i.test(value),
77
+ )
78
+ ? "overloaded"
79
+ : null;
80
+ }
81
+
82
+ /**
83
+ * Read a retryable `turn.recovery.requested` payload or an exhausted
84
+ * `turn.failed` payload. Returns null for every other recovery reason (human
85
+ * Retry, worker shutdown, credential rotation, capacity waits, ...).
86
+ */
87
+ export function parseProviderRecovery(payload: unknown): ProviderRecoveryFacts | null {
88
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
89
+ const record = payload as Record<string, unknown>;
90
+ const code = text(record.code) ?? text(record.reason);
91
+ if (!code || !RECOVERY_CODES.has(code)) return null;
92
+ // A live recovery request is retryable; a spent budget is explicitly marked.
93
+ if (record.recoveryExhausted !== true && record.retryable === false) return null;
94
+ const attempt = count(record.providerRecoveryCount);
95
+ return {
96
+ code,
97
+ condition: condition(record, code),
98
+ modelLabel: text(record.modelLabel),
99
+ providerLabel: text(record.providerLabel),
100
+ attempt,
101
+ maxAttempts: count(record.maxProviderRecoveryCount) ?? DEFAULT_MAX_ATTEMPTS,
102
+ modelRoute: MODEL_CODES.has(code),
103
+ };
104
+ }
105
+
106
+ /** One present-tense clause naming what is unavailable, without a trailing period. */
107
+ export function providerRecoverySubject(facts: ProviderRecoveryFacts): string {
108
+ const model = facts.modelLabel ?? "The model";
109
+ const at = facts.providerLabel ? ` at the provider (${facts.providerLabel})` : " at the provider";
110
+ switch (facts.code) {
111
+ case "provider_rate_limited":
112
+ return `${model} is rate limited${at}`;
113
+ case "provider_unavailable":
114
+ return facts.condition === "overloaded"
115
+ ? `${model} is overloaded${at}`
116
+ : facts.condition === "unresponsive"
117
+ ? `${model} stopped responding${at}`
118
+ : `${model} is temporarily unavailable${at}`;
119
+ case "provider_unknown_finish_reason":
120
+ return `${model} ended its response unexpectedly`;
121
+ case "post_compaction_continuation_empty":
122
+ return `${model} stopped right after compacting the conversation`;
123
+ case "upstream_connectivity_unavailable":
124
+ return "Opengeni can't reach an upstream service";
125
+ case "mcp_transport_timeout":
126
+ return "A required MCP server isn't responding";
127
+ case "mcp_transport_unavailable":
128
+ return "A required MCP server is unreachable";
129
+ case "sandbox_command_start_unavailable":
130
+ return "The sandbox isn't ready yet";
131
+ case "turn_execution_policy_definition_mismatch":
132
+ return "Opengeni is applying a configuration update";
133
+ default:
134
+ return "A service this turn depends on is temporarily unavailable";
135
+ }
136
+ }
137
+
138
+ /** Live status while the same turn waits for its next automatic retry. */
139
+ export function providerRecoveryRetryingText(facts: ProviderRecoveryFacts): string {
140
+ const progress =
141
+ facts.attempt !== null && facts.maxAttempts !== null
142
+ ? ` (attempt ${Math.min(facts.attempt, facts.maxAttempts)} of ${facts.maxAttempts})`
143
+ : " automatically";
144
+ return `${providerRecoverySubject(facts)} — retrying${progress}…`;
145
+ }
146
+
147
+ /** Terminal sentence once the automatic budget is spent. */
148
+ export function providerRecoveryExhaustedText(facts: ProviderRecoveryFacts): string {
149
+ const spent = facts.attempt ?? facts.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
150
+ const retried = `Opengeni retried ${spent} ${spent === 1 ? "time" : "times"} without success.`;
151
+ const remedy = facts.modelRoute
152
+ ? "Try again in a few minutes, or switch to another model."
153
+ : "Try again in a few minutes.";
154
+ return `${providerRecoverySubject(facts)}. ${retried} ${remedy}`;
155
+ }
156
+
157
+ const BOUNDARY_TYPES: ReadonlySet<string> = new Set([
158
+ "turn.recovery.requested",
159
+ "turn.started",
160
+ "turn.completed",
161
+ "turn.failed",
162
+ "turn.cancelled",
163
+ "turn.superseded",
164
+ ]);
165
+
166
+ /**
167
+ * The active turn's current automatic provider retry, or null. Only a
168
+ * `recovering`, unpaused session qualifies, and only the newest boundary of its
169
+ * exact active turn counts, so merged or replayed pages never borrow another
170
+ * turn's diagnosis.
171
+ */
172
+ export function currentProviderRecovery(
173
+ session: {
174
+ id: string;
175
+ status: SessionStatus | null | undefined;
176
+ activeTurnId: string | null | undefined;
177
+ effectiveControl?: { state: string } | null | undefined;
178
+ },
179
+ events: readonly SessionEvent[],
180
+ ): ProviderRecoveryFacts | null {
181
+ if (
182
+ session.status !== "recovering" ||
183
+ (session.effectiveControl && session.effectiveControl.state !== "active") ||
184
+ !session.activeTurnId
185
+ )
186
+ return null;
187
+ let latest: SessionEvent | undefined;
188
+ for (const event of events) {
189
+ if (
190
+ event.sessionId !== session.id ||
191
+ event.turnId !== session.activeTurnId ||
192
+ event.duplicateOfEventId ||
193
+ (event.turnAssociation && event.turnAssociation !== "current") ||
194
+ !BOUNDARY_TYPES.has(event.type)
195
+ )
196
+ continue;
197
+ if (!latest || event.sequence > latest.sequence) latest = event;
198
+ }
199
+ if (latest?.type !== "turn.recovery.requested") return null;
200
+ const payload = latest.payload as Record<string, unknown> | null | undefined;
201
+ return payload?.recoveryExhausted === true ? null : parseProviderRecovery(payload);
202
+ }
@@ -0,0 +1,53 @@
1
+ // Model maker marks as plain path data (24×24 viewBox, filled with the current
2
+ // color), shared by the web SVG components and native renderers so both draw
3
+ // identical geometry.
4
+ import { modelVendor, type ModelDisplayInput, type ModelVendor } from "@opengeni/sdk/model-display";
5
+
6
+ /** ChatGPT / OpenAI mark (Simple Icons, CC0). */
7
+ export const CHATGPT_MARK_PATH =
8
+ "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z";
9
+
10
+ /** Claude mark (Simple Icons, CC0; https://github.com/simple-icons/simple-icons). */
11
+ export const CLAUDE_MARK_PATH =
12
+ "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z";
13
+
14
+ /**
15
+ * MIT License
16
+ *
17
+ * Copyright (c) 2023 LobeHub
18
+ *
19
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
20
+ * of this software and associated documentation files (the "Software"), to deal
21
+ * in the Software without restriction, including without limitation the rights
22
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23
+ * copies of the Software, and to permit persons to whom the Software is
24
+ * furnished to do so, subject to the following conditions:
25
+ *
26
+ * The above copyright notice and this permission notice shall be included in all
27
+ * copies or substantial portions of the Software.
28
+ *
29
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35
+ * SOFTWARE.
36
+ * Source: https://raw.githubusercontent.com/lobehub/lobe-icons/master/src/Grok/components/Mono.tsx
37
+ */
38
+ export const GROK_MARK_PATH =
39
+ "M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815";
40
+
41
+ export type MarkedModelVendor = Extract<ModelVendor, "openai" | "anthropic" | "xai">;
42
+
43
+ export const MODEL_MARK_PATHS: Record<MarkedModelVendor, string> = {
44
+ openai: CHATGPT_MARK_PATH,
45
+ anthropic: CLAUDE_MARK_PATH,
46
+ xai: GROK_MARK_PATH,
47
+ };
48
+
49
+ /** The maker whose logo marks this model, or null for the neutral fallback. */
50
+ export function modelMarkVendor(model: ModelDisplayInput): MarkedModelVendor | null {
51
+ const vendor = modelVendor(model);
52
+ return vendor === "openai" || vendor === "anthropic" || vendor === "xai" ? vendor : null;
53
+ }