@stainless-api/docs-ai-chat 0.1.0-beta.6 → 0.1.0-beta.60

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.
package/CHANGELOG.md CHANGED
@@ -1,61 +1,292 @@
1
1
  # @stainless-api/docs-ai-chat
2
2
 
3
- ## 0.1.0-beta.6
3
+ ## 0.1.0-beta.60
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies
8
- - @stainless-api/docs@0.1.0-beta.64
7
+ - Updated dependencies [005419c]
8
+ - @stainless-api/docs-ui@0.1.0-beta.89
9
9
 
10
- ## 0.1.0-beta.5
10
+ ## 0.1.0-beta.59
11
11
 
12
12
  ### Patch Changes
13
13
 
14
- - Updated dependencies [d2e3686]
15
- - Updated dependencies [5d6e5fa]
16
- - @stainless-api/docs@0.1.0-beta.63
17
- - @stainless-api/ui-primitives@0.1.0-beta.39
18
- - @stainless-api/docs-ui@0.1.0-beta.52
14
+ - Updated dependencies [af5a5d7]
15
+ - @stainless-api/docs-ui@0.1.0-beta.88
19
16
 
20
- ## 0.1.0-beta.4
17
+ ## 0.1.0-beta.58
18
+
19
+ ### Minor Changes
20
+
21
+ - f22893c: Add interactive examples to ai chat
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [f22893c]
26
+ - @stainless-api/ai-chat@0.1.0-beta.13
27
+
28
+ ## 0.1.0-beta.57
29
+
30
+ ### Patch Changes
31
+
32
+ - Updated dependencies [7701d8f]
33
+ - Updated dependencies [5460e81]
34
+ - @stainless-api/docs-ui@0.1.0-beta.87
35
+
36
+ ## 0.1.0-beta.56
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [d096f7d]
41
+ - @stainless-api/ai-chat@0.1.0-beta.12
42
+
43
+ ## 0.1.0-beta.55
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [848cff4]
48
+ - Updated dependencies [ea2b90c]
49
+ - @stainless-api/docs-ui@0.1.0-beta.86
50
+ - @stainless-api/ai-chat@0.1.0-beta.11
51
+
52
+ ## 0.1.0-beta.54
53
+
54
+ ### Patch Changes
55
+
56
+ - Updated dependencies [93a3767]
57
+ - @stainless-api/docs-ui@0.1.0-beta.85
58
+
59
+ ## 0.1.0-beta.53
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies [438a593]
64
+ - @stainless-api/docs-ui@0.1.0-beta.84
65
+
66
+ ## 0.1.0-beta.52
67
+
68
+ ### Patch Changes
69
+
70
+ - Updated dependencies [1c38511]
71
+ - @stainless-api/docs-ui@0.1.0-beta.83
72
+
73
+ ## 0.1.0-beta.51
21
74
 
22
75
  ### Patch Changes
23
76
 
24
- - Updated dependencies [3c4a030]
25
- - Updated dependencies [cd86726]
26
- - Updated dependencies [aa9d333]
27
- - Updated dependencies [07a2c87]
28
- - @stainless-api/docs@0.1.0-beta.62
29
- - @stainless-api/docs-ui@0.1.0-beta.51
30
- - @stainless-api/ui-primitives@0.1.0-beta.38
77
+ - Updated dependencies [37cc2ac]
78
+ - @stainless-api/docs-ui@0.1.0-beta.82
31
79
 
32
- ## 0.1.0-beta.3
80
+ ## 0.1.0-beta.50
33
81
 
34
82
  ### Patch Changes
35
83
 
36
- - 77c0d47: steelie: submit on enter
84
+ - Updated dependencies [134885b]
85
+ - Updated dependencies [5ba9135]
86
+ - Updated dependencies [df0c958]
87
+ - Updated dependencies [e641d33]
88
+ - @stainless-api/docs-ui@0.1.0-beta.81
37
89
 
38
- ## 0.1.0-beta.2
90
+ ## 0.1.0-beta.49
39
91
 
40
92
  ### Patch Changes
41
93
 
42
- - a1502cf: fix: close AIChat when clicking on SVG elements outside its bounds
43
- - 88a9894: patch vite optimizeDeps for docs-ai-chat
44
- - Updated dependencies [2a79bae]
45
- - Updated dependencies [88a9894]
46
- - @stainless-api/ui-primitives@0.1.0-beta.38
47
- - @stainless-api/docs@0.1.0-beta.61
48
- - @stainless-api/docs-ui@0.1.0-beta.51
94
+ - Updated dependencies [05cd046]
95
+ - @stainless-api/docs-ui@0.1.0-beta.80
49
96
 
50
- ## 0.1.0-beta.1
97
+ ## 0.1.0-beta.48
98
+
99
+ ### Patch Changes
100
+
101
+ - 8838d9f: remember presentation when window shrinks
102
+ - Updated dependencies [8838d9f]
103
+ - @stainless-api/ai-chat@0.1.0-beta.10
104
+
105
+ ## 0.1.0-beta.47
106
+
107
+ ### Minor Changes
108
+
109
+ - a8ed299: Adds the ability for AI chat to move into a sidebar “panel” position.
110
+
111
+ ### Patch Changes
112
+
113
+ - Updated dependencies [5b3b798]
114
+ - Updated dependencies [a8ed299]
115
+ - @stainless-api/ai-chat@0.1.0-beta.9
116
+
117
+ ## 0.1.0-beta.46
51
118
 
52
119
  ### Minor Changes
53
120
 
54
- - 2d00f0d: first version of docs-ai-chat
121
+ - 9d24589: Steelie empty state
122
+ - 5263b85: Loading state for ai chat
123
+
124
+ ### Patch Changes
125
+
126
+ - 9d24589: Various steelie UI fixes
127
+ - Updated dependencies [9d24589]
128
+ - Updated dependencies [9d24589]
129
+ - Updated dependencies [5263b85]
130
+ - @stainless-api/ai-chat@0.1.0-beta.8
131
+
132
+ ## 0.1.0-beta.45
133
+
134
+ ### Patch Changes
135
+
136
+ - Updated dependencies [6d9933d]
137
+ - @stainless-api/docs-ui@0.1.0-beta.79
138
+ - @stainless-api/ai-chat@0.1.0-beta.7
139
+
140
+ ## 0.1.0-beta.44
141
+
142
+ ### Patch Changes
143
+
144
+ - Updated dependencies [54add64]
145
+ - @stainless-api/docs-ui@0.1.0-beta.78
146
+
147
+ ## 0.1.0-beta.43
148
+
149
+ ### Patch Changes
150
+
151
+ - Updated dependencies [21e50d3]
152
+ - @stainless-api/docs-ui@0.1.0-beta.77
153
+
154
+ ## 0.1.0-beta.42
155
+
156
+ ### Patch Changes
157
+
158
+ - Updated dependencies [95fe66c]
159
+ - Updated dependencies [5701494]
160
+ - @stainless-api/docs-ui@0.1.0-beta.76
161
+ - @stainless-api/ai-chat@0.1.0-beta.6
162
+
163
+ ## 0.1.0-beta.41
164
+
165
+ ### Patch Changes
166
+
167
+ - 6e762e2: fix steelie for multi-turn conversations
168
+
169
+ ## 0.1.0-beta.40
170
+
171
+ ### Patch Changes
172
+
173
+ - Updated dependencies [2919b0a]
174
+ - Updated dependencies [e005e5c]
175
+ - @stainless-api/docs-ui@0.1.0-beta.75
176
+
177
+ ## 0.1.0-beta.39
178
+
179
+ ### Patch Changes
180
+
181
+ - Updated dependencies [415629f]
182
+ - @stainless-api/docs-ui@0.1.0-beta.74
183
+
184
+ ## 0.1.0-beta.38
185
+
186
+ ### Patch Changes
187
+
188
+ - Updated dependencies [5c36876]
189
+ - @stainless-api/docs-ui@0.1.0-beta.73
190
+
191
+ ## 0.1.0-beta.37
192
+
193
+ ### Patch Changes
194
+
195
+ - Updated dependencies [6b86a8b]
196
+ - @stainless-api/docs-ui@0.1.0-beta.72
197
+
198
+ ## 0.1.0-beta.36
199
+
200
+ ### Patch Changes
201
+
202
+ - Updated dependencies [cd578b7]
203
+ - @stainless-api/docs-ui@0.1.0-beta.71
204
+
205
+ ## 0.1.0-beta.35
206
+
207
+ ### Patch Changes
208
+
209
+ - Updated dependencies [93c8f94]
210
+ - @stainless-api/docs-ui@0.1.0-beta.70
211
+
212
+ ## 0.1.0-beta.34
213
+
214
+ ### Patch Changes
215
+
216
+ - Updated dependencies [61ba36f]
217
+ - @stainless-api/docs-ui@0.1.0-beta.69
218
+
219
+ ## 0.1.0-beta.33
220
+
221
+ ### Patch Changes
222
+
223
+ - Updated dependencies [a3f1ede]
224
+ - @stainless-api/docs-ui@0.1.0-beta.68
225
+
226
+ ## 0.1.0-beta.32
227
+
228
+ ### Patch Changes
229
+
230
+ - @stainless-api/ai-chat@0.1.0-beta.5
231
+ - @stainless-api/docs-ui@0.1.0-beta.67
232
+
233
+ ## 0.1.0-beta.31
234
+
235
+ ### Patch Changes
236
+
237
+ - Updated dependencies [65a1c9b]
238
+ - Updated dependencies [4f1cee7]
239
+ - Updated dependencies [4c72a83]
240
+ - Updated dependencies [068469b]
241
+ - @stainless-api/docs-ui@0.1.0-beta.66
242
+
243
+ ## 0.1.0-beta.30
244
+
245
+ ### Patch Changes
246
+
247
+ - Updated dependencies [b62eb05]
248
+ - @stainless-api/docs-ui@0.1.0-beta.65
249
+ - @stainless-api/ai-chat@0.1.0-beta.4
250
+
251
+ ## 0.1.0-beta.29
252
+
253
+ ### Patch Changes
254
+
255
+ - Updated dependencies [52ece13]
256
+ - Updated dependencies [3411ffe]
257
+ - Updated dependencies [7439be7]
258
+ - @stainless-api/ai-chat@0.1.0-beta.3
259
+ - @stainless-api/docs-ui@0.1.0-beta.64
260
+
261
+ ## 0.1.0-beta.28
262
+
263
+ ### Patch Changes
264
+
265
+ - Updated dependencies [274cefc]
266
+ - @stainless-api/docs-ui@0.1.0-beta.63
267
+
268
+ ## 0.1.0-beta.27
269
+
270
+ ### Patch Changes
271
+
272
+ - Updated dependencies [6ef241e]
273
+ - Updated dependencies [d3a85b5]
274
+ - Updated dependencies [d3a85b5]
275
+ - Updated dependencies [2dcb5fb]
276
+ - @stainless-api/docs-ui@0.1.0-beta.62
277
+
278
+ ## 0.1.0-beta.26
279
+
280
+ ### Patch Changes
281
+
282
+ - Updated dependencies [7155fae]
283
+ - @stainless-api/ai-chat@0.1.0-beta.2
284
+ - @stainless-api/docs-ui@0.1.0-beta.61
285
+
286
+ ## 0.1.0-beta.25
55
287
 
56
288
  ### Patch Changes
57
289
 
58
- - Updated dependencies [2d00f0d]
59
- - @stainless-api/docs@0.1.0-beta.60
60
- - @stainless-api/docs-ui@0.1.0-beta.50
61
- - @stainless-api/ui-primitives@0.1.0-beta.37
290
+ - 5c257e2: separate steelie into separate packages
291
+ - Updated dependencies [9dda4cf]
292
+ - @stainless-api/ai-chat@0.1.0-beta.1
package/ambient.d.ts CHANGED
@@ -2,3 +2,9 @@ declare module '*.module.css' {
2
2
  const classes: { [key: string]: string };
3
3
  export default classes;
4
4
  }
5
+
6
+ declare module 'virtual:stl-docs-ai-chat-examples' {
7
+ import type { ExamplePrompt } from '@stainless-api/ai-chat/src/types';
8
+
9
+ export const examples: ExamplePrompt[] | undefined;
10
+ }
@@ -0,0 +1,2 @@
1
+ import { config } from '@stainless/eslint-config/react';
2
+ export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs-ai-chat",
3
- "version": "0.1.0-beta.6",
3
+ "version": "0.1.0-beta.60",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -8,25 +8,16 @@
8
8
  "peerDependencies": {
9
9
  "react": ">=19.0.0",
10
10
  "react-dom": ">=19.0.0",
11
- "@stainless-api/docs": "0.1.0-beta.64",
12
- "@stainless-api/docs-ui": "0.1.0-beta.52",
13
- "@stainless-api/ui-primitives": "0.1.0-beta.39"
11
+ "@stainless-api/docs-ui": "0.1.0-beta.89"
14
12
  },
15
13
  "dependencies": {
16
14
  "@streamparser/json-whatwg": "^0.0.22",
17
- "clsx": "^2.1.1",
18
- "lucide-react": "^0.561.0",
19
- "motion": "^12.23.25",
20
- "react": "^19.2.3",
21
- "react-markdown": "^10.1.0",
22
- "react-syntax-highlighter": "^16.1.0",
23
- "remend": "^1.0.1",
24
- "zod": "^4.1.13"
15
+ "zod": "^4.3.6",
16
+ "@stainless-api/ai-chat": "0.1.0-beta.13"
25
17
  },
26
18
  "devDependencies": {
27
- "@types/react": "19.2.7",
19
+ "@types/react": "19.2.14",
28
20
  "@types/react-dom": "^19.2.3",
29
- "@types/react-syntax-highlighter": "^15.5.13",
30
21
  "typescript": "5.9.3",
31
22
  "@stainless/eslint-config": "0.1.0-beta.1"
32
23
  },
@@ -34,12 +25,12 @@
34
25
  "./plugin": {
35
26
  "import": "./plugin.tsx"
36
27
  },
37
- "./src/AiChat.tsx": {
38
- "import": "./src/AiChat.tsx"
28
+ "./src/DocsChat.tsx": {
29
+ "import": "./src/DocsChat.tsx"
39
30
  }
40
31
  },
41
32
  "scripts": {
42
- "lint": "eslint .",
33
+ "lint": "eslint --flag unstable_native_nodejs_ts_config .",
43
34
  "check:types": "tsc --noEmit"
44
35
  }
45
36
  }
package/plugin.tsx CHANGED
@@ -1,3 +1,3 @@
1
- export default function aiChatPlugin() {
2
- return { chatComponentPath: '@stainless-api/docs-ai-chat/src/AiChat.tsx' };
1
+ export default function docsChatPlugin() {
2
+ return { chatComponentPath: '@stainless-api/docs-ai-chat/src/DocsChat.tsx' };
3
3
  }
@@ -0,0 +1,60 @@
1
+ import AiChat from '@stainless-api/ai-chat/src/AiChat.tsx';
2
+ import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
3
+ import { setResponseMetadata, submitResponseFeedback } from './api';
4
+ import { useSyncExternalStore } from 'react';
5
+ import { useChat } from './hook';
6
+
7
+ const examplesPromise = import('virtual:stl-docs-ai-chat-examples')
8
+ .then((mod) => mod.examples)
9
+ .catch(() => undefined);
10
+
11
+ function onCopyMessage(spanId: string) {
12
+ setResponseMetadata(spanId, { copied_to_clipboard: 'true' }).catch(() => {});
13
+ }
14
+
15
+ async function rateMessage(spanId: string, rating: 'up' | 'down'): Promise<boolean> {
16
+ try {
17
+ const { success } = await submitResponseFeedback(spanId, { up: 1 as const, down: 0 as const }[rating]);
18
+ return success;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ export default function DocsChat({
25
+ projectId,
26
+ language,
27
+ siteTitle,
28
+ }: {
29
+ projectId: string;
30
+ language: DocsLanguage | undefined;
31
+ siteTitle: string | undefined;
32
+ }) {
33
+ const { chatMessages, sendMessage } = useChat({
34
+ projectId,
35
+ language: language ?? 'http',
36
+ siteTitle,
37
+ });
38
+
39
+ // panel mode is supported only on larger viewports
40
+ const supportsPanel = useSyncExternalStore(
41
+ (cb) => {
42
+ window.addEventListener('resize', cb);
43
+ return () => window.removeEventListener('resize', cb);
44
+ },
45
+ () => window.innerWidth >= 968,
46
+ () => false,
47
+ );
48
+
49
+ return (
50
+ <AiChat
51
+ chatMessages={chatMessages}
52
+ sendMessage={sendMessage}
53
+ rateMessage={rateMessage}
54
+ onCopyMessage={onCopyMessage}
55
+ siteTitle={siteTitle}
56
+ promptExamples={examplesPromise}
57
+ supportsPanel={supportsPanel}
58
+ />
59
+ );
60
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Identifier for tracking unique users in braintrust
3
+ */
4
+ export function getClientId() {
5
+ let clientId = localStorage.getItem('stainless-client-id');
6
+ if (!clientId) {
7
+ clientId = crypto.randomUUID();
8
+ localStorage.setItem('stainless-client-id', clientId);
9
+ }
10
+ return clientId;
11
+ }
package/src/api/index.ts CHANGED
@@ -1,9 +1,37 @@
1
1
  import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
2
2
  import { JSONParser } from '@streamparser/json-whatwg';
3
- import { type RequestBody, responseChunk } from './schemas';
3
+ import {
4
+ type RequestBody,
5
+ responseChunk,
6
+ type FeedbackRequestBody,
7
+ feedbackResponseBody,
8
+ type MetadataRequestBody,
9
+ metadataResponseBody,
10
+ } from './schemas';
4
11
  import { streamAsyncIterator } from './util';
12
+ import { getClientId } from './client-id';
5
13
 
6
- export const CHAT_ENDPOINT = 'https://app.stainless.com/api/ai/get-agentic-help';
14
+ export const API_URL = new URL('https://app.stainless.com/api/');
15
+ export const CHAT_ENDPOINT = new URL('ai/get-agentic-help', API_URL);
16
+
17
+ export const FEEDBACK_ENDPOINT = (spanId: string) => new URL(`ai/agentic-help/${spanId}/score`, API_URL);
18
+ export const METADATA_ENDPOINT = (spanId: string) => new URL(`ai/agentic-help/${spanId}/metadata`, API_URL);
19
+
20
+ /** Context on what the user is currently viewing to pass to the agent */
21
+ function getPageContext({ siteTitle }: { siteTitle: string | undefined }) {
22
+ const { href } = window.location;
23
+ const markdownUrl = `${href.replace(/\/$/, '')}/index.md`;
24
+ const pageTitle = document.querySelector('h1')?.textContent;
25
+ return [
26
+ `The user is viewing a documentation page${siteTitle ? ` for ${siteTitle}` : ''}.`,
27
+ `- Content URL: ${markdownUrl}`,
28
+ pageTitle && `- Page title: “${pageTitle}”`,
29
+ // TODO: include stainless path here? does the agent know how to use it?
30
+ // TODO: pass more of the page content into context without the agent having to retrieve it
31
+ ]
32
+ .filter(Boolean)
33
+ .join('\n');
34
+ }
7
35
 
8
36
  /**
9
37
  * Stream chat response from the server
@@ -14,15 +42,17 @@ export async function* getChatResponse(
14
42
  project,
15
43
  language,
16
44
  priorMessages,
45
+ siteTitle,
17
46
  }: {
18
47
  query: string;
19
48
  project: string;
20
49
  language: DocsLanguage;
21
50
  priorMessages: NonNullable<RequestBody['additionalContext']>['prior_messages'];
51
+ siteTitle: string | undefined;
22
52
  },
23
53
  abortSignal: AbortSignal,
24
54
  ) {
25
- const req = await fetch(CHAT_ENDPOINT, {
55
+ const res = await fetch(CHAT_ENDPOINT, {
26
56
  method: 'POST',
27
57
  headers: {
28
58
  'Content-Type': 'application/json',
@@ -31,17 +61,53 @@ export async function* getChatResponse(
31
61
  query,
32
62
  sdk: { project, language },
33
63
  stream: true,
34
- additionalContext: { prior_messages: priorMessages },
64
+ additionalContext: {
65
+ prior_messages: priorMessages,
66
+ intent: getPageContext({ siteTitle }),
67
+ },
68
+ browser_id: getClientId(),
35
69
  } satisfies RequestBody),
36
70
 
37
71
  signal: abortSignal,
38
72
  });
39
73
 
40
- if (!req.ok || !req.body) throw new Error(`Chat request failed with status ${req.status}`);
74
+ if (!res.ok || !res.body) throw new Error(`Chat request failed with status ${res.status}`);
41
75
 
42
76
  const parser = new JSONParser({ separator: '\n', paths: ['$'] });
43
- for await (const chunk of streamAsyncIterator(req.body.pipeThrough(parser))) {
77
+ for await (const chunk of streamAsyncIterator(res.body.pipeThrough(parser))) {
44
78
  const chunkParsed = responseChunk.safeParse(chunk.value);
45
79
  if (chunkParsed.success) yield chunkParsed.data;
46
80
  }
47
81
  }
82
+
83
+ /**
84
+ * Attach a score to a response
85
+ */
86
+ export async function submitResponseFeedback(spanId: string, score: 0 | 1) {
87
+ const res = await fetch(FEEDBACK_ENDPOINT(spanId), {
88
+ method: 'PUT',
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ },
92
+ body: JSON.stringify({ score } satisfies FeedbackRequestBody),
93
+ });
94
+
95
+ if (!res.ok) throw new Error(`Feedback request failed with status ${res.status}`);
96
+ return feedbackResponseBody.parse(await res.json());
97
+ }
98
+
99
+ /**
100
+ * Attach a score to a response
101
+ */
102
+ export async function setResponseMetadata(spanId: string, metadata: Record<string, string>) {
103
+ const res = await fetch(METADATA_ENDPOINT(spanId), {
104
+ method: 'PUT',
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ },
108
+ body: JSON.stringify({ metadata } satisfies MetadataRequestBody),
109
+ });
110
+
111
+ if (!res.ok) throw new Error(`Metadata request failed with status ${res.status}`);
112
+ return metadataResponseBody.parse(await res.json());
113
+ }
@@ -14,6 +14,7 @@ export const requestBody = z.object({
14
14
  maxTokens: z.number().optional(),
15
15
  })
16
16
  .optional(),
17
+ session_id: z.string().optional(),
17
18
  additionalContext: z
18
19
  .object({
19
20
  prior_messages: z.array(
@@ -28,6 +29,7 @@ export const requestBody = z.object({
28
29
  errors: z.string().optional(),
29
30
  })
30
31
  .optional(),
32
+ browser_id: z.string().optional(),
31
33
  });
32
34
  export type RequestBody = z.input<typeof requestBody>;
33
35
 
@@ -48,6 +50,19 @@ export const responseChunk = z.discriminatedUnion('type', [
48
50
  }),
49
51
  z.object({
50
52
  type: z.literal('done'),
53
+ span_id: z.string(),
54
+ }),
55
+ z.object({
56
+ type: z.literal('start_session'),
57
+ session_id: z.string(),
51
58
  }),
52
59
  ]);
53
60
  export type ResponseChunk = z.infer<typeof responseChunk>;
61
+
62
+ export const feedbackRequestBody = z.object({ score: z.number().min(0).max(1) });
63
+ export type FeedbackRequestBody = z.infer<typeof feedbackRequestBody>;
64
+ export const feedbackResponseBody = z.object({ success: z.boolean() });
65
+
66
+ export const metadataRequestBody = z.object({ metadata: z.record(z.string(), z.string()) });
67
+ export type MetadataRequestBody = z.infer<typeof metadataRequestBody>;
68
+ export const metadataResponseBody = z.object({ success: z.boolean() });