@ganju/utils 0.0.1

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 (220) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.md +23 -0
  4. package/dist/apiError.d.ts +3 -0
  5. package/dist/apiError.d.ts.map +1 -0
  6. package/dist/apiError.js +36 -0
  7. package/dist/attachment.d.ts +33 -0
  8. package/dist/attachment.d.ts.map +1 -0
  9. package/dist/attachment.js +59 -0
  10. package/dist/base64.d.ts +10 -0
  11. package/dist/base64.d.ts.map +1 -0
  12. package/dist/base64.js +50 -0
  13. package/dist/channelDebounce.d.ts +26 -0
  14. package/dist/channelDebounce.d.ts.map +1 -0
  15. package/dist/channelDebounce.js +35 -0
  16. package/dist/channelNotifier.d.ts +9 -0
  17. package/dist/channelNotifier.d.ts.map +1 -0
  18. package/dist/channelNotifier.js +31 -0
  19. package/dist/chunking.d.ts +28 -0
  20. package/dist/chunking.d.ts.map +1 -0
  21. package/dist/chunking.js +97 -0
  22. package/dist/cliConstants.d.ts +9 -0
  23. package/dist/cliConstants.d.ts.map +1 -0
  24. package/dist/cliConstants.js +47 -0
  25. package/dist/coerce.d.ts +2 -0
  26. package/dist/coerce.d.ts.map +1 -0
  27. package/dist/coerce.js +13 -0
  28. package/dist/constants.d.ts +624 -0
  29. package/dist/constants.d.ts.map +1 -0
  30. package/dist/constants.js +2220 -0
  31. package/dist/crypto.d.ts +14 -0
  32. package/dist/crypto.d.ts.map +1 -0
  33. package/dist/crypto.js +93 -0
  34. package/dist/customCodeProject.d.ts +91 -0
  35. package/dist/customCodeProject.d.ts.map +1 -0
  36. package/dist/customCodeProject.js +154 -0
  37. package/dist/customCodeToken.d.ts +41 -0
  38. package/dist/customCodeToken.d.ts.map +1 -0
  39. package/dist/customCodeToken.js +115 -0
  40. package/dist/deadline.d.ts +6 -0
  41. package/dist/deadline.d.ts.map +1 -0
  42. package/dist/deadline.js +45 -0
  43. package/dist/discordSend.d.ts +25 -0
  44. package/dist/discordSend.d.ts.map +1 -0
  45. package/dist/discordSend.js +8 -0
  46. package/dist/embeddable.d.ts +2 -0
  47. package/dist/embeddable.d.ts.map +1 -0
  48. package/dist/embeddable.js +6 -0
  49. package/dist/exposedResource.d.ts +6 -0
  50. package/dist/exposedResource.d.ts.map +1 -0
  51. package/dist/exposedResource.js +18 -0
  52. package/dist/extractedDocument.d.ts +70 -0
  53. package/dist/extractedDocument.d.ts.map +1 -0
  54. package/dist/extractedDocument.js +2 -0
  55. package/dist/fetcher.d.ts +8 -0
  56. package/dist/fetcher.d.ts.map +1 -0
  57. package/dist/fetcher.js +68 -0
  58. package/dist/formatFilename.d.ts +2 -0
  59. package/dist/formatFilename.d.ts.map +1 -0
  60. package/dist/formatFilename.js +19 -0
  61. package/dist/formatRelative.d.ts +12 -0
  62. package/dist/formatRelative.d.ts.map +1 -0
  63. package/dist/formatRelative.js +28 -0
  64. package/dist/formatRelativeTime.d.ts +2 -0
  65. package/dist/formatRelativeTime.d.ts.map +1 -0
  66. package/dist/formatRelativeTime.js +49 -0
  67. package/dist/getEnv.d.ts +5 -0
  68. package/dist/getEnv.d.ts.map +1 -0
  69. package/dist/getEnv.js +11 -0
  70. package/dist/gmailSend.d.ts +24 -0
  71. package/dist/gmailSend.d.ts.map +1 -0
  72. package/dist/gmailSend.js +9 -0
  73. package/dist/index.d.ts +2839 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +155 -0
  76. package/dist/jsonSchemaToZodShape.d.ts +38 -0
  77. package/dist/jsonSchemaToZodShape.d.ts.map +1 -0
  78. package/dist/jsonSchemaToZodShape.js +117 -0
  79. package/dist/l2Normalize.d.ts +2 -0
  80. package/dist/l2Normalize.d.ts.map +1 -0
  81. package/dist/l2Normalize.js +25 -0
  82. package/dist/languageCookieDomain.d.ts +21 -0
  83. package/dist/languageCookieDomain.d.ts.map +1 -0
  84. package/dist/languageCookieDomain.js +39 -0
  85. package/dist/localizeZodIssue.d.ts +43 -0
  86. package/dist/localizeZodIssue.d.ts.map +1 -0
  87. package/dist/localizeZodIssue.js +90 -0
  88. package/dist/mailHeaders.d.ts +4 -0
  89. package/dist/mailHeaders.d.ts.map +1 -0
  90. package/dist/mailHeaders.js +11 -0
  91. package/dist/mcpProxy.d.ts +2 -0
  92. package/dist/mcpProxy.d.ts.map +1 -0
  93. package/dist/mcpProxy.js +21 -0
  94. package/dist/mimeMessage.d.ts +21 -0
  95. package/dist/mimeMessage.d.ts.map +1 -0
  96. package/dist/mimeMessage.js +58 -0
  97. package/dist/oauth.d.ts +21 -0
  98. package/dist/oauth.d.ts.map +1 -0
  99. package/dist/oauth.js +75 -0
  100. package/dist/oauthProviders.d.ts +12 -0
  101. package/dist/oauthProviders.d.ts.map +1 -0
  102. package/dist/oauthProviders.js +99 -0
  103. package/dist/outlookSend.d.ts +19 -0
  104. package/dist/outlookSend.d.ts.map +1 -0
  105. package/dist/outlookSend.js +9 -0
  106. package/dist/parseHttpError.d.ts +2 -0
  107. package/dist/parseHttpError.d.ts.map +1 -0
  108. package/dist/parseHttpError.js +23 -0
  109. package/dist/planLimitError.d.ts +18 -0
  110. package/dist/planLimitError.d.ts.map +1 -0
  111. package/dist/planLimitError.js +35 -0
  112. package/dist/processQueueBatch.d.ts +17 -0
  113. package/dist/processQueueBatch.d.ts.map +1 -0
  114. package/dist/processQueueBatch.js +32 -0
  115. package/dist/reservedToolName.d.ts +16 -0
  116. package/dist/reservedToolName.d.ts.map +1 -0
  117. package/dist/reservedToolName.js +25 -0
  118. package/dist/resourceUri.d.ts +2 -0
  119. package/dist/resourceUri.d.ts.map +1 -0
  120. package/dist/resourceUri.js +19 -0
  121. package/dist/retry.d.ts +10 -0
  122. package/dist/retry.d.ts.map +1 -0
  123. package/dist/retry.js +37 -0
  124. package/dist/sanitize.d.ts +7 -0
  125. package/dist/sanitize.d.ts.map +1 -0
  126. package/dist/sanitize.js +69 -0
  127. package/dist/schema.d.ts +2175 -0
  128. package/dist/schema.d.ts.map +1 -0
  129. package/dist/schema.js +1382 -0
  130. package/dist/sdkConstants.d.ts +15 -0
  131. package/dist/sdkConstants.d.ts.map +1 -0
  132. package/dist/sdkConstants.js +43 -0
  133. package/dist/slackSend.d.ts +38 -0
  134. package/dist/slackSend.d.ts.map +1 -0
  135. package/dist/slackSend.js +14 -0
  136. package/dist/slug.d.ts +5 -0
  137. package/dist/slug.d.ts.map +1 -0
  138. package/dist/slug.js +34 -0
  139. package/dist/slugifyTitle.d.ts +2 -0
  140. package/dist/slugifyTitle.d.ts.map +1 -0
  141. package/dist/slugifyTitle.js +11 -0
  142. package/dist/sources.d.ts +43 -0
  143. package/dist/sources.d.ts.map +1 -0
  144. package/dist/sources.js +73 -0
  145. package/dist/ssrf.d.ts +3 -0
  146. package/dist/ssrf.d.ts.map +1 -0
  147. package/dist/ssrf.js +46 -0
  148. package/dist/tallyUsageKinds.d.ts +9 -0
  149. package/dist/tallyUsageKinds.d.ts.map +1 -0
  150. package/dist/tallyUsageKinds.js +20 -0
  151. package/dist/telegramSend.d.ts +28 -0
  152. package/dist/telegramSend.d.ts.map +1 -0
  153. package/dist/telegramSend.js +9 -0
  154. package/dist/toolCatalog.d.ts +452 -0
  155. package/dist/toolCatalog.d.ts.map +1 -0
  156. package/dist/toolCatalog.js +552 -0
  157. package/dist/validateMessageVariables.d.ts +7 -0
  158. package/dist/validateMessageVariables.d.ts.map +1 -0
  159. package/dist/validateMessageVariables.js +16 -0
  160. package/dist/vendorError.d.ts +2 -0
  161. package/dist/vendorError.d.ts.map +1 -0
  162. package/dist/vendorError.js +25 -0
  163. package/dist/whatsappSend.d.ts +31 -0
  164. package/dist/whatsappSend.d.ts.map +1 -0
  165. package/dist/whatsappSend.js +11 -0
  166. package/package.json +76 -0
  167. package/src/apiError.ts +40 -0
  168. package/src/attachment.ts +97 -0
  169. package/src/base64.ts +60 -0
  170. package/src/channelDebounce.ts +76 -0
  171. package/src/channelNotifier.ts +39 -0
  172. package/src/chunking.ts +131 -0
  173. package/src/cliConstants.ts +50 -0
  174. package/src/coerce.ts +9 -0
  175. package/src/constants.ts +2542 -0
  176. package/src/crypto.ts +108 -0
  177. package/src/customCodeProject.ts +182 -0
  178. package/src/customCodeToken.ts +198 -0
  179. package/src/deadline.ts +43 -0
  180. package/src/discordSend.ts +41 -0
  181. package/src/embeddable.ts +4 -0
  182. package/src/exposedResource.ts +40 -0
  183. package/src/extractedDocument.ts +62 -0
  184. package/src/fetcher.ts +82 -0
  185. package/src/formatFilename.ts +19 -0
  186. package/src/formatRelative.ts +36 -0
  187. package/src/formatRelativeTime.ts +45 -0
  188. package/src/getEnv.ts +13 -0
  189. package/src/gmailSend.ts +34 -0
  190. package/src/index.ts +397 -0
  191. package/src/jsonSchemaToZodShape.ts +111 -0
  192. package/src/l2Normalize.ts +19 -0
  193. package/src/languageCookieDomain.ts +37 -0
  194. package/src/localizeZodIssue.ts +124 -0
  195. package/src/mailHeaders.ts +12 -0
  196. package/src/mcpProxy.ts +20 -0
  197. package/src/mimeMessage.ts +82 -0
  198. package/src/oauth.ts +103 -0
  199. package/src/oauthProviders.ts +118 -0
  200. package/src/outlookSend.ts +44 -0
  201. package/src/parseHttpError.ts +16 -0
  202. package/src/planLimitError.ts +50 -0
  203. package/src/processQueueBatch.ts +46 -0
  204. package/src/reservedToolName.ts +27 -0
  205. package/src/resourceUri.ts +17 -0
  206. package/src/retry.ts +43 -0
  207. package/src/sanitize.ts +67 -0
  208. package/src/schema.ts +1624 -0
  209. package/src/sdkConstants.ts +44 -0
  210. package/src/slackSend.ts +65 -0
  211. package/src/slug.ts +37 -0
  212. package/src/slugifyTitle.ts +8 -0
  213. package/src/sources.ts +117 -0
  214. package/src/ssrf.ts +34 -0
  215. package/src/tallyUsageKinds.ts +20 -0
  216. package/src/telegramSend.ts +39 -0
  217. package/src/toolCatalog.ts +635 -0
  218. package/src/validateMessageVariables.ts +18 -0
  219. package/src/vendorError.ts +19 -0
  220. package/src/whatsappSend.ts +47 -0
package/dist/schema.js ADDED
@@ -0,0 +1,1382 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Schema = void 0;
4
+ const zod_1 = require("zod");
5
+ const constants_1 = require("./constants");
6
+ const slug_1 = require("./slug");
7
+ const reservedToolName_1 = require("./reservedToolName");
8
+ const slugifyTitle_1 = require("./slugifyTitle");
9
+ // A prompt title becomes a slash command; it must not collide with a command
10
+ // the channel runner handles itself (e.g. `/link`).
11
+ const PROMPT_TITLE = zod_1.z
12
+ .string()
13
+ .min(3)
14
+ .max(200)
15
+ .refine(title => !constants_1.constants.RESERVED_BOT_COMMANDS.includes((0, slugifyTitle_1.slugifyTitle)(title)), { message: 'This title is reserved as a bot command' });
16
+ const SCHEMA_DEFINITION = zod_1.z.object({
17
+ type: zod_1.z.enum(constants_1.constants.SCHEMA_DEFINITION_TYPES),
18
+ properties: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
19
+ required: zod_1.z.array(zod_1.z.string()).optional(),
20
+ items: zod_1.z.any().optional(),
21
+ minimum: zod_1.z.number().optional(),
22
+ maximum: zod_1.z.number().optional(),
23
+ minLength: zod_1.z.number().optional(),
24
+ maxLength: zod_1.z.number().optional(),
25
+ pattern: zod_1.z.string().optional(),
26
+ enum: zod_1.z.array(zod_1.z.any()).optional()
27
+ });
28
+ const ORGANIZATION_CREATE = zod_1.z.object({
29
+ userId: zod_1.z.uuid(),
30
+ name: zod_1.z.string().min(3).max(100),
31
+ projectName: zod_1.z.string().min(3).max(100),
32
+ projectDescription: zod_1.z.string().max(500)
33
+ });
34
+ const ORGANIZATION_CREATE_VIEW = zod_1.z.object({
35
+ name: zod_1.z.string().min(3).max(100),
36
+ projectName: zod_1.z.string().min(3).max(100),
37
+ projectDescription: zod_1.z.string().max(500)
38
+ });
39
+ const ORGANIZATION_UPDATE = zod_1.z.object({
40
+ id: zod_1.z.uuid(),
41
+ userId: zod_1.z.uuid(),
42
+ name: zod_1.z.string().min(3).max(100)
43
+ });
44
+ const ORGANIZATION_GET = zod_1.z.object({
45
+ id: zod_1.z.uuid(),
46
+ userId: zod_1.z.uuid()
47
+ });
48
+ const AUTH_USER_GET = zod_1.z.object({
49
+ userId: zod_1.z.uuid()
50
+ });
51
+ const PROJECT_CREATE = zod_1.z.object({
52
+ userId: zod_1.z.uuid(),
53
+ organizationId: zod_1.z.uuid(),
54
+ name: zod_1.z.string().min(3).max(100),
55
+ description: zod_1.z.string().max(500).optional()
56
+ });
57
+ const PROJECT_CREATE_VIEW = PROJECT_CREATE.omit({
58
+ userId: true,
59
+ organizationId: true
60
+ });
61
+ const PROJECT_UPDATE = zod_1.z.object({
62
+ id: zod_1.z.uuid(),
63
+ userId: zod_1.z.uuid(),
64
+ organizationId: zod_1.z.uuid(),
65
+ name: zod_1.z.string().min(3).max(100),
66
+ description: zod_1.z.string().max(500).optional()
67
+ });
68
+ const PROJECT_GET = zod_1.z.object({
69
+ id: zod_1.z.uuid(),
70
+ organizationId: zod_1.z.uuid(),
71
+ userId: zod_1.z.uuid()
72
+ });
73
+ // Trim + lowercase before validating so invitations match regardless of how
74
+ // the inviter typed the address (membership lookups compare lowercased email).
75
+ const INVITATION_EMAIL = zod_1.z
76
+ .string()
77
+ .trim()
78
+ .toLowerCase()
79
+ .pipe(zod_1.z.email({ message: 'Enter a valid email address' }).max(254));
80
+ // Marketing-site contact form. `company_url` is a honeypot — a hidden field
81
+ // real users leave empty; the controller drops the submission if it's filled.
82
+ const CONTACT_MESSAGE = zod_1.z.object({
83
+ name: zod_1.z.string().trim().min(1).max(constants_1.constants.CONTACT_MAX_NAME_LENGTH),
84
+ email: zod_1.z
85
+ .string()
86
+ .trim()
87
+ .toLowerCase()
88
+ .pipe(zod_1.z
89
+ .email({ message: 'Enter a valid email address' })
90
+ .max(constants_1.constants.CONTACT_MAX_EMAIL_LENGTH)),
91
+ message: zod_1.z
92
+ .string()
93
+ .trim()
94
+ .min(constants_1.constants.CONTACT_MIN_MESSAGE_LENGTH)
95
+ .max(constants_1.constants.CONTACT_MAX_MESSAGE_LENGTH),
96
+ company_url: zod_1.z.string().optional()
97
+ });
98
+ const ORGANIZATION_INVITATION_CREATE = zod_1.z.object({
99
+ email: INVITATION_EMAIL,
100
+ userId: zod_1.z.uuid(),
101
+ organizationId: zod_1.z.uuid()
102
+ });
103
+ const ORGANIZATION_INVITATION_CREATE_VIEW = ORGANIZATION_INVITATION_CREATE.omit({
104
+ userId: true,
105
+ organizationId: true
106
+ });
107
+ const ORGANIZATION_INVITATION_LIST = zod_1.z.object({
108
+ userId: zod_1.z.uuid(),
109
+ organizationId: zod_1.z.uuid()
110
+ });
111
+ const ORGANIZATION_INVITATION_REMOVE = zod_1.z.object({
112
+ invitationId: zod_1.z.uuid(),
113
+ userId: zod_1.z.uuid(),
114
+ organizationId: zod_1.z.uuid()
115
+ });
116
+ const PROJECT_INVITATION_CREATE = zod_1.z.object({
117
+ email: INVITATION_EMAIL,
118
+ userId: zod_1.z.uuid(),
119
+ organizationId: zod_1.z.uuid(),
120
+ projectId: zod_1.z.uuid()
121
+ });
122
+ const PROJECT_INVITATION_CREATE_VIEW = PROJECT_INVITATION_CREATE.omit({
123
+ userId: true,
124
+ organizationId: true,
125
+ projectId: true
126
+ });
127
+ const PROJECT_INVITATION_LIST = zod_1.z.object({
128
+ userId: zod_1.z.uuid(),
129
+ organizationId: zod_1.z.uuid(),
130
+ projectId: zod_1.z.uuid()
131
+ });
132
+ const PROJECT_INVITATION_REMOVE = zod_1.z.object({
133
+ invitationId: zod_1.z.uuid(),
134
+ userId: zod_1.z.uuid(),
135
+ organizationId: zod_1.z.uuid(),
136
+ projectId: zod_1.z.uuid()
137
+ });
138
+ // Invitee acting on one of their own pending invitations. No org/project id —
139
+ // the invitation is matched to the caller by id + session email.
140
+ const INVITATION_RESPOND = zod_1.z.object({
141
+ invitationId: zod_1.z.uuid(),
142
+ userId: zod_1.z.uuid(),
143
+ action: zod_1.z.enum(constants_1.constants.INVITATION_RESPONSES)
144
+ });
145
+ const INVITATION_GET_BY_TOKEN = zod_1.z.object({
146
+ token: zod_1.z.string().min(8).max(128)
147
+ });
148
+ const ORGANIZATION_MEMBER_LIST = zod_1.z.object({
149
+ userId: zod_1.z.uuid(),
150
+ organizationId: zod_1.z.uuid()
151
+ });
152
+ const ORGANIZATION_MEMBER_REMOVE = zod_1.z.object({
153
+ memberUserId: zod_1.z.uuid(),
154
+ userId: zod_1.z.uuid(),
155
+ organizationId: zod_1.z.uuid()
156
+ });
157
+ const PROJECT_MEMBER_LIST = zod_1.z.object({
158
+ userId: zod_1.z.uuid(),
159
+ organizationId: zod_1.z.uuid(),
160
+ projectId: zod_1.z.uuid()
161
+ });
162
+ const PROJECT_MEMBER_REMOVE = zod_1.z.object({
163
+ memberUserId: zod_1.z.uuid(),
164
+ userId: zod_1.z.uuid(),
165
+ organizationId: zod_1.z.uuid(),
166
+ projectId: zod_1.z.uuid()
167
+ });
168
+ const ARTIFACT_CREATE_PROMPT = zod_1.z.object({
169
+ title: PROMPT_TITLE,
170
+ description: zod_1.z.string().max(1000).optional(),
171
+ messages: zod_1.z
172
+ .array(zod_1.z.object({
173
+ role: zod_1.z.enum(constants_1.constants.ROLE_MESSAGES),
174
+ content: zod_1.z.string()
175
+ }))
176
+ .min(1),
177
+ schema: SCHEMA_DEFINITION,
178
+ projectId: zod_1.z.uuid(),
179
+ userId: zod_1.z.uuid(),
180
+ organizationId: zod_1.z.uuid()
181
+ });
182
+ const ARTIFACT_UPDATE_PROMPT = zod_1.z.object({
183
+ promptId: zod_1.z.uuid(),
184
+ title: PROMPT_TITLE,
185
+ description: zod_1.z.string().max(1000).optional(),
186
+ messages: zod_1.z
187
+ .array(zod_1.z.object({
188
+ role: zod_1.z.enum(constants_1.constants.ROLE_MESSAGES),
189
+ content: zod_1.z.string()
190
+ }))
191
+ .min(1),
192
+ schema: SCHEMA_DEFINITION,
193
+ projectId: zod_1.z.uuid(),
194
+ userId: zod_1.z.uuid(),
195
+ organizationId: zod_1.z.uuid()
196
+ });
197
+ const ARTIFACT_REMOVE_PROMPT = zod_1.z.object({
198
+ promptId: zod_1.z.uuid(),
199
+ projectId: zod_1.z.uuid(),
200
+ userId: zod_1.z.uuid(),
201
+ organizationId: zod_1.z.uuid()
202
+ });
203
+ const ARTIFACT_GET_PROMPT = zod_1.z.object({
204
+ projectId: zod_1.z.uuid(),
205
+ userId: zod_1.z.uuid(),
206
+ organizationId: zod_1.z.uuid()
207
+ });
208
+ const CRAWL_CONFIG = zod_1.z.object({
209
+ maxPages: zod_1.z
210
+ .number()
211
+ .int()
212
+ .min(1)
213
+ .max(constants_1.constants.CRAWL_MAX_PAGES_LIMIT)
214
+ .default(constants_1.constants.CRAWL_DEFAULT_MAX_PAGES),
215
+ maxDepth: zod_1.z
216
+ .number()
217
+ .int()
218
+ .min(0)
219
+ .max(constants_1.constants.CRAWL_MAX_DEPTH_LIMIT)
220
+ .default(constants_1.constants.CRAWL_DEFAULT_MAX_DEPTH)
221
+ });
222
+ const ARTIFACT_CREATE_WEBSITE_VIEW = zod_1.z.object({
223
+ title: zod_1.z.string().min(3).max(200),
224
+ uri: zod_1.z
225
+ .url('Enter a valid URL')
226
+ .refine(v => /^https?:\/\//i.test(v), 'Only http and https URLs are supported'),
227
+ description: zod_1.z.string().optional(),
228
+ maxPages: zod_1.z.number().int().min(1).max(constants_1.constants.CRAWL_MAX_PAGES_LIMIT),
229
+ maxDepth: zod_1.z.number().int().min(0).max(constants_1.constants.CRAWL_MAX_DEPTH_LIMIT)
230
+ });
231
+ const ARTIFACT_CREATE_WEBSITE = zod_1.z.object({
232
+ title: zod_1.z.string().min(3).max(200),
233
+ uri: zod_1.z
234
+ .url('Enter a valid URL')
235
+ .refine(v => /^https?:\/\//i.test(v), 'Only http and https URLs are supported'),
236
+ description: zod_1.z.string().optional(),
237
+ crawlConfig: zod_1.z.object({
238
+ maxPages: zod_1.z.number().int().min(1).max(constants_1.constants.CRAWL_MAX_PAGES_LIMIT),
239
+ maxDepth: zod_1.z.number().int().min(0).max(constants_1.constants.CRAWL_MAX_DEPTH_LIMIT)
240
+ }),
241
+ projectId: zod_1.z.uuid(),
242
+ userId: zod_1.z.uuid(),
243
+ organizationId: zod_1.z.uuid()
244
+ });
245
+ const ARTIFACT_UPDATE_WEBSITE_VIEW = zod_1.z.object({
246
+ title: zod_1.z.string().min(3).max(200),
247
+ description: zod_1.z.string().optional()
248
+ });
249
+ const ARTIFACT_UPDATE_WEBSITE = ARTIFACT_UPDATE_WEBSITE_VIEW.extend({
250
+ resourceId: zod_1.z.uuid(),
251
+ projectId: zod_1.z.uuid(),
252
+ userId: zod_1.z.uuid(),
253
+ organizationId: zod_1.z.uuid()
254
+ });
255
+ const GOOGLE_DRIVE_ITEM = zod_1.z.object({
256
+ fileId: zod_1.z.string(),
257
+ name: zod_1.z.string(),
258
+ mimeType: zod_1.z.string(),
259
+ isFolder: zod_1.z.boolean().default(false),
260
+ iconLink: zod_1.z.string().optional(),
261
+ webViewLink: zod_1.z.string().optional(),
262
+ modifiedTime: zod_1.z.string().optional(),
263
+ size: zod_1.z.coerce.number().int().optional()
264
+ });
265
+ const ARTIFACT_CREATE_GOOGLE_DRIVE_VIEW = zod_1.z.object({
266
+ items: zod_1.z.array(GOOGLE_DRIVE_ITEM)
267
+ });
268
+ const ARTIFACT_CREATE_GOOGLE_DRIVE = ARTIFACT_CREATE_GOOGLE_DRIVE_VIEW.extend({
269
+ projectId: zod_1.z.uuid(),
270
+ userId: zod_1.z.uuid(),
271
+ organizationId: zod_1.z.uuid()
272
+ });
273
+ const ARTIFACT_SYNC_GOOGLE_DRIVE = zod_1.z.object({
274
+ resourceId: zod_1.z.uuid(),
275
+ projectId: zod_1.z.uuid(),
276
+ userId: zod_1.z.uuid(),
277
+ organizationId: zod_1.z.uuid()
278
+ });
279
+ const ONE_DRIVE_ITEM = zod_1.z.object({
280
+ itemId: zod_1.z.string(),
281
+ driveId: zod_1.z.string().optional(),
282
+ name: zod_1.z.string(),
283
+ mimeType: zod_1.z.string().optional(),
284
+ isFolder: zod_1.z.boolean().default(false),
285
+ webUrl: zod_1.z.string().optional(),
286
+ lastModifiedDateTime: zod_1.z.string().optional(),
287
+ size: zod_1.z.coerce.number().int().optional()
288
+ });
289
+ const ARTIFACT_CREATE_ONE_DRIVE_VIEW = zod_1.z.object({
290
+ items: zod_1.z.array(ONE_DRIVE_ITEM)
291
+ });
292
+ const ARTIFACT_CREATE_ONE_DRIVE = ARTIFACT_CREATE_ONE_DRIVE_VIEW.extend({
293
+ projectId: zod_1.z.uuid(),
294
+ userId: zod_1.z.uuid(),
295
+ organizationId: zod_1.z.uuid()
296
+ });
297
+ const ARTIFACT_SYNC_ONE_DRIVE = zod_1.z.object({
298
+ resourceId: zod_1.z.uuid(),
299
+ projectId: zod_1.z.uuid(),
300
+ userId: zod_1.z.uuid(),
301
+ organizationId: zod_1.z.uuid()
302
+ });
303
+ const ARTIFACT_CREATE_RESOURCE = zod_1.z.object({
304
+ title: zod_1.z.string().min(3).max(200),
305
+ uri: zod_1.z.string(),
306
+ type: zod_1.z
307
+ .enum(constants_1.constants.RESOURCE_TYPES)
308
+ .default(constants_1.constants.RESOURCE_TYPE_STATIC),
309
+ sourceType: zod_1.z
310
+ .enum(constants_1.constants.RESOURCE_SOURCE_TYPES)
311
+ .default(constants_1.constants.RESOURCE_SOURCE_TYPE_FILE),
312
+ description: zod_1.z.string().max(1000).optional(),
313
+ mimeType: zod_1.z.enum(constants_1.constants.MIMETYPES, {
314
+ message: 'Unsupported mime type'
315
+ }),
316
+ content: zod_1.z.string().optional(),
317
+ size: zod_1.z
318
+ .number()
319
+ .int()
320
+ .min(0)
321
+ .max(constants_1.constants.MAX_FILE_SIZE, {
322
+ message: `File size exceeds the ${constants_1.constants.MAX_FILE_SIZE / (1024 * 1024)}MB limit`
323
+ })
324
+ .optional(),
325
+ encoding: zod_1.z.string().max(50).optional(),
326
+ fileKey: zod_1.z.string().optional(),
327
+ fileName: zod_1.z.string().optional(),
328
+ annotations: zod_1.z
329
+ .object({
330
+ audience: zod_1.z.array(zod_1.z.enum(constants_1.constants.ROLE_MESSAGES)).optional(),
331
+ priority: zod_1.z.number().min(0).max(1).optional(),
332
+ lastModified: zod_1.z.string().datetime().optional()
333
+ })
334
+ .optional(),
335
+ icons: zod_1.z
336
+ .array(zod_1.z.object({
337
+ src: zod_1.z.string(),
338
+ mimeType: zod_1.z.string().optional(),
339
+ sizes: zod_1.z.array(zod_1.z.string()).optional(),
340
+ theme: zod_1.z.enum(constants_1.constants.RESOURCE_ICON_THEMES).optional()
341
+ }))
342
+ .optional(),
343
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
344
+ crawlConfig: CRAWL_CONFIG.optional(),
345
+ projectId: zod_1.z.uuid(),
346
+ userId: zod_1.z.uuid(),
347
+ organizationId: zod_1.z.uuid()
348
+ });
349
+ const ARTIFACT_UPDATE_RESOURCE = zod_1.z.object({
350
+ resourceId: zod_1.z.uuid(),
351
+ title: zod_1.z.string().min(3).max(200),
352
+ uri: zod_1.z.string(),
353
+ type: zod_1.z
354
+ .enum(constants_1.constants.RESOURCE_TYPES)
355
+ .default(constants_1.constants.RESOURCE_TYPE_STATIC),
356
+ sourceType: zod_1.z
357
+ .enum(constants_1.constants.RESOURCE_SOURCE_TYPES)
358
+ .default(constants_1.constants.RESOURCE_SOURCE_TYPE_FILE),
359
+ description: zod_1.z.string().max(1000).optional(),
360
+ mimeType: zod_1.z.enum(constants_1.constants.MIMETYPES, {
361
+ message: 'Unsupported mime type'
362
+ }),
363
+ content: zod_1.z.string().optional(),
364
+ size: zod_1.z
365
+ .number()
366
+ .int()
367
+ .min(0)
368
+ .max(constants_1.constants.MAX_FILE_SIZE, {
369
+ message: `File size exceeds the ${constants_1.constants.MAX_FILE_SIZE / (1024 * 1024)}MB limit`
370
+ })
371
+ .optional(),
372
+ encoding: zod_1.z.string().max(50).optional(),
373
+ fileKey: zod_1.z.string().optional(),
374
+ fileName: zod_1.z.string().optional(),
375
+ annotations: zod_1.z
376
+ .object({
377
+ audience: zod_1.z.array(zod_1.z.enum(constants_1.constants.ROLE_MESSAGES)).optional(),
378
+ priority: zod_1.z.number().min(0).max(1).optional(),
379
+ lastModified: zod_1.z.string().datetime().optional()
380
+ })
381
+ .optional(),
382
+ icons: zod_1.z
383
+ .array(zod_1.z.object({
384
+ src: zod_1.z.string(),
385
+ mimeType: zod_1.z.string().optional(),
386
+ sizes: zod_1.z.array(zod_1.z.string()).optional(),
387
+ theme: zod_1.z.enum(constants_1.constants.RESOURCE_ICON_THEMES).optional()
388
+ }))
389
+ .optional(),
390
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
391
+ projectId: zod_1.z.uuid(),
392
+ userId: zod_1.z.uuid(),
393
+ organizationId: zod_1.z.uuid()
394
+ });
395
+ const ARTIFACT_GET_RESOURCE = zod_1.z.object({
396
+ projectId: zod_1.z.uuid(),
397
+ userId: zod_1.z.uuid(),
398
+ organizationId: zod_1.z.uuid()
399
+ });
400
+ const ARTIFACT_GET_RESOURCE_BY_ID = zod_1.z.object({
401
+ resourceId: zod_1.z.uuid(),
402
+ projectId: zod_1.z.uuid(),
403
+ userId: zod_1.z.uuid(),
404
+ organizationId: zod_1.z.uuid()
405
+ });
406
+ const ARTIFACT_REMOVE_RESOURCE = zod_1.z.object({
407
+ resourceId: zod_1.z.uuid(),
408
+ projectId: zod_1.z.uuid(),
409
+ userId: zod_1.z.uuid(),
410
+ organizationId: zod_1.z.uuid()
411
+ });
412
+ const ARTIFACT_UPLOAD_RESOURCE_FILE = zod_1.z.object({
413
+ resourceId: zod_1.z.uuid(),
414
+ projectId: zod_1.z.uuid(),
415
+ userId: zod_1.z.uuid(),
416
+ organizationId: zod_1.z.uuid()
417
+ });
418
+ const ARTIFACT_DOWNLOAD_RESOURCE_FILE = zod_1.z.object({
419
+ resourceId: zod_1.z.uuid(),
420
+ projectId: zod_1.z.uuid(),
421
+ userId: zod_1.z.uuid(),
422
+ organizationId: zod_1.z.uuid()
423
+ });
424
+ const ARTIFACT_CREATE_TOOL = zod_1.z.object({
425
+ toolKey: zod_1.z.string().min(1).max(64),
426
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
427
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
428
+ projectId: zod_1.z.uuid(),
429
+ userId: zod_1.z.uuid(),
430
+ organizationId: zod_1.z.uuid()
431
+ });
432
+ const ARTIFACT_UPDATE_TOOL = zod_1.z.object({
433
+ toolId: zod_1.z.uuid(),
434
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
435
+ metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
436
+ projectId: zod_1.z.uuid(),
437
+ userId: zod_1.z.uuid(),
438
+ organizationId: zod_1.z.uuid()
439
+ });
440
+ const ARTIFACT_GET_TOOL = zod_1.z.object({
441
+ projectId: zod_1.z.uuid(),
442
+ userId: zod_1.z.uuid(),
443
+ organizationId: zod_1.z.uuid()
444
+ });
445
+ const ARTIFACT_REMOVE_TOOL = zod_1.z.object({
446
+ toolId: zod_1.z.uuid(),
447
+ projectId: zod_1.z.uuid(),
448
+ userId: zod_1.z.uuid(),
449
+ organizationId: zod_1.z.uuid()
450
+ });
451
+ // Turning one tool on or off. Deliberately its own route rather than a field on
452
+ // ARTIFACT_UPDATE_TOOL: that path re-validates config and, for mcp-proxy,
453
+ // re-runs remote discovery — a network round trip nobody should pay to flip a
454
+ // switch, and one that would fail the toggle whenever the remote is down.
455
+ const ARTIFACT_SET_TOOL_ENABLED = zod_1.z.object({
456
+ toolId: zod_1.z.uuid(),
457
+ enabled: zod_1.z.boolean(),
458
+ projectId: zod_1.z.uuid(),
459
+ userId: zod_1.z.uuid(),
460
+ organizationId: zod_1.z.uuid()
461
+ });
462
+ const ARTIFACT_GET_CREDENTIAL = zod_1.z.object({
463
+ projectId: zod_1.z.uuid(),
464
+ userId: zod_1.z.uuid(),
465
+ organizationId: zod_1.z.uuid()
466
+ });
467
+ const ARTIFACT_REMOVE_CREDENTIAL = zod_1.z.object({
468
+ credentialId: zod_1.z.uuid(),
469
+ projectId: zod_1.z.uuid(),
470
+ userId: zod_1.z.uuid(),
471
+ organizationId: zod_1.z.uuid()
472
+ });
473
+ // Reads every managed OAuth provider and where the artifact stands with it.
474
+ // Same fields as the credential read above, kept separate because the two answer
475
+ // different questions — one lists stored secrets, the other lists connections
476
+ // whether or not one exists yet.
477
+ const ARTIFACT_LIST_CONNECTIONS = zod_1.z.object({
478
+ projectId: zod_1.z.uuid(),
479
+ userId: zod_1.z.uuid(),
480
+ organizationId: zod_1.z.uuid()
481
+ });
482
+ const ARTIFACT_CREATE_CREDENTIAL = zod_1.z.object({
483
+ provider: zod_1.z.enum(constants_1.constants.CREDENTIAL_PROVIDERS),
484
+ apiKey: zod_1.z.string().min(1),
485
+ label: zod_1.z.string().trim().min(1).max(100).optional(),
486
+ projectId: zod_1.z.uuid(),
487
+ userId: zod_1.z.uuid(),
488
+ organizationId: zod_1.z.uuid()
489
+ });
490
+ const ORGANIZATION_LIST_LLM = zod_1.z.object({
491
+ userId: zod_1.z.uuid(),
492
+ organizationId: zod_1.z.uuid()
493
+ });
494
+ const ORGANIZATION_CREATE_LLM = zod_1.z.object({
495
+ name: zod_1.z.string().min(1).max(200),
496
+ provider: zod_1.z.enum(constants_1.constants.LLM_PROVIDERS),
497
+ model: zod_1.z.string().min(1).max(200),
498
+ baseUrl: zod_1.z.url().optional().or(zod_1.z.literal('')),
499
+ apiKey: zod_1.z.string().min(1).max(500),
500
+ systemPrompt: zod_1.z.string().max(10000).optional(),
501
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
502
+ userId: zod_1.z.uuid(),
503
+ organizationId: zod_1.z.uuid()
504
+ });
505
+ const ORGANIZATION_UPDATE_LLM = zod_1.z.object({
506
+ llmId: zod_1.z.uuid(),
507
+ name: zod_1.z.string().min(1).max(200).optional(),
508
+ provider: zod_1.z.enum(constants_1.constants.LLM_PROVIDERS).optional(),
509
+ model: zod_1.z.string().min(1).max(200).optional(),
510
+ baseUrl: zod_1.z.url().optional().or(zod_1.z.literal('')).nullable(),
511
+ apiKey: zod_1.z.string().min(1).max(500).optional(),
512
+ systemPrompt: zod_1.z.string().max(10000).optional().nullable(),
513
+ config: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().nullable(),
514
+ userId: zod_1.z.uuid(),
515
+ organizationId: zod_1.z.uuid()
516
+ });
517
+ const ORGANIZATION_REMOVE_LLM = zod_1.z.object({
518
+ llmId: zod_1.z.uuid(),
519
+ userId: zod_1.z.uuid(),
520
+ organizationId: zod_1.z.uuid()
521
+ });
522
+ const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM.omit({
523
+ userId: true,
524
+ organizationId: true
525
+ });
526
+ const ORGANIZATION_UPDATE_LLM_VIEW = ORGANIZATION_UPDATE_LLM.omit({
527
+ llmId: true,
528
+ userId: true,
529
+ organizationId: true
530
+ });
531
+ // `channel.config` is an open bag, so this stays loose — it only pins down the
532
+ // keys we actually read, and passes everything else through untouched.
533
+ // `debounceMs` is how long a burst from one participant is buffered before the
534
+ // agent answers it as a single turn; 0 disables buffering.
535
+ const CHANNEL_CONFIG = zod_1.z.looseObject({
536
+ debounceMs: zod_1.z
537
+ .number()
538
+ .int()
539
+ .refine(value => value === constants_1.constants.CHANNEL_DEBOUNCE_DISABLED ||
540
+ (value >= constants_1.constants.CHANNEL_DEBOUNCE_MIN_MS &&
541
+ value <= constants_1.constants.CHANNEL_DEBOUNCE_MAX_MS), `debounceMs must be 0 (disabled) or between ${constants_1.constants.CHANNEL_DEBOUNCE_MIN_MS} and ${constants_1.constants.CHANNEL_DEBOUNCE_MAX_MS} ms`)
542
+ .optional()
543
+ });
544
+ const CHANNEL_CREATE = zod_1.z.object({
545
+ platform: zod_1.z.enum(constants_1.constants.CHANNEL_PLATFORMS),
546
+ config: CHANNEL_CONFIG.optional(),
547
+ credentials: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
548
+ llmId: zod_1.z.uuid().nullable().optional(),
549
+ projectId: zod_1.z.uuid(),
550
+ userId: zod_1.z.uuid(),
551
+ organizationId: zod_1.z.uuid()
552
+ });
553
+ const CHANNEL_UPDATE = zod_1.z.object({
554
+ channelId: zod_1.z.uuid(),
555
+ status: zod_1.z.enum(constants_1.constants.CHANNEL_STATUS).optional(),
556
+ config: CHANNEL_CONFIG.optional(),
557
+ credentials: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).optional(),
558
+ llmId: zod_1.z.uuid().nullable().optional(),
559
+ projectId: zod_1.z.uuid(),
560
+ userId: zod_1.z.uuid(),
561
+ organizationId: zod_1.z.uuid()
562
+ });
563
+ // Validates one artifact_tool.config of definition `http-endpoint`. Each row of
564
+ // this kind registers one named MCP tool at server boot (see apps/mcp tools
565
+ // README). Defaults are filled here so the dispatcher receives a fully-resolved
566
+ // config. Secrets are never inlined — auth references an artifact_credential by
567
+ // id (credentialId), validated as a uuid.
568
+ const HTTP_ENDPOINT_KEY_VALUE = zod_1.z.object({
569
+ name: zod_1.z.string().min(1).max(256),
570
+ value: zod_1.z.string().max(8192)
571
+ });
572
+ const HTTP_ENDPOINT_AUTH = zod_1.z.discriminatedUnion('kind', [
573
+ zod_1.z.object({ kind: zod_1.z.literal(constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_NONE) }),
574
+ zod_1.z.object({
575
+ kind: zod_1.z.literal(constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_BEARER),
576
+ credentialId: zod_1.z.uuid()
577
+ }),
578
+ zod_1.z.object({
579
+ kind: zod_1.z.literal(constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_BASIC),
580
+ credentialId: zod_1.z.uuid()
581
+ }),
582
+ zod_1.z.object({
583
+ kind: zod_1.z.literal(constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_OAUTH),
584
+ credentialId: zod_1.z.uuid()
585
+ }),
586
+ zod_1.z.object({
587
+ kind: zod_1.z.literal(constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_API_KEY),
588
+ in: zod_1.z.enum(['header', 'query']),
589
+ name: zod_1.z.string().min(1).max(256),
590
+ credentialId: zod_1.z.uuid()
591
+ })
592
+ ]);
593
+ const HTTP_ENDPOINT_CONFIG = zod_1.z
594
+ .object({
595
+ // Identity — surfaced to the model. `name` becomes the MCP tool key.
596
+ name: zod_1.z
597
+ .string()
598
+ .regex(/^[a-zA-Z0-9_-]{1,64}$/, 'Tool name must be 1-64 chars: letters, digits, underscore or hyphen'),
599
+ title: zod_1.z.string().min(1).max(200).optional(),
600
+ description: zod_1.z.string().max(2000).optional(),
601
+ // Request.
602
+ method: zod_1.z.preprocess(v => (typeof v === 'string' ? v.toUpperCase() : v), zod_1.z.enum(constants_1.constants.HTTP_ENDPOINT_METHODS)),
603
+ url: zod_1.z.string().min(1).max(2048),
604
+ headers: zod_1.z.array(HTTP_ENDPOINT_KEY_VALUE).max(50).default([]),
605
+ query: zod_1.z.array(HTTP_ENDPOINT_KEY_VALUE).max(50).default([]),
606
+ body: zod_1.z
607
+ .object({
608
+ kind: zod_1.z.enum(constants_1.constants.HTTP_ENDPOINT_BODY_KINDS),
609
+ template: zod_1.z.string().max(100_000).default('')
610
+ })
611
+ .default({ kind: constants_1.constants.HTTP_ENDPOINT_BODY_KIND_NONE, template: '' }),
612
+ // Input schema the model fills in (reuses the shared JSON-schema shape).
613
+ inputSchema: SCHEMA_DEFINITION.default({
614
+ type: 'object',
615
+ properties: {}
616
+ }),
617
+ // What the tool promises to return, and the last asymmetry between the two
618
+ // user-authored tool shapes — `custom-code` has carried one since the
619
+ // manifest existed.
620
+ //
621
+ // Optional, and absent on every endpoint that predates it: declaring one
622
+ // turns the response into MCP `structuredContent`, and a client that asked
623
+ // for structure gets an object instead of a wall of text. Leaving it out
624
+ // keeps the text behaviour exactly as it was.
625
+ outputSchema: SCHEMA_DEFINITION.optional(),
626
+ // Response handling.
627
+ response: zod_1.z
628
+ .object({
629
+ contentType: zod_1.z
630
+ .enum([
631
+ constants_1.constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO,
632
+ constants_1.constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_JSON,
633
+ constants_1.constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_TEXT
634
+ ])
635
+ .default(constants_1.constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO),
636
+ // Clamp to the hard cap rather than reject an over-large request.
637
+ maxBytes: zod_1.z
638
+ .number()
639
+ .int()
640
+ .positive()
641
+ .default(constants_1.constants.HTTP_ENDPOINT_DEFAULT_MAX_BYTES)
642
+ .transform(n => Math.min(n, constants_1.constants.HTTP_ENDPOINT_DEFAULT_MAX_BYTES)),
643
+ jsonPath: zod_1.z.string().max(256).optional(),
644
+ successStatus: zod_1.z
645
+ .array(zod_1.z.number().int().min(100).max(599))
646
+ .max(20)
647
+ .optional()
648
+ })
649
+ .default({
650
+ contentType: constants_1.constants.HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO,
651
+ maxBytes: constants_1.constants.HTTP_ENDPOINT_DEFAULT_MAX_BYTES
652
+ }),
653
+ // Auth — credentials referenced by id, never inlined.
654
+ auth: HTTP_ENDPOINT_AUTH.default({
655
+ kind: constants_1.constants.HTTP_ENDPOINT_AUTH_KIND_NONE
656
+ }),
657
+ // Safety.
658
+ timeoutMs: zod_1.z
659
+ .number()
660
+ .int()
661
+ .positive()
662
+ .default(constants_1.constants.HTTP_ENDPOINT_DEFAULT_TIMEOUT_MS)
663
+ .transform(n => Math.min(n, constants_1.constants.HTTP_ENDPOINT_MAX_TIMEOUT_MS)),
664
+ allowedHosts: zod_1.z
665
+ .array(zod_1.z.string().min(1).max(253).toLowerCase())
666
+ .max(50)
667
+ .optional()
668
+ })
669
+ .transform(cfg => ({
670
+ ...cfg,
671
+ title: cfg.title || cfg.name,
672
+ description: cfg.description ||
673
+ `Call the configured ${cfg.method} ${cfg.url} endpoint.`
674
+ }));
675
+ // The same config, plus the rule that its `name` must not be one the platform
676
+ // owns — `name` becomes the MCP tool key, in the same flat namespace the native
677
+ // tools register into. Split from the schema above rather than folded into it
678
+ // because that one is also how apps/mcp READS a stored row at boot: an install
679
+ // that predates this rule, or predates a group being added to it, has to keep
680
+ // registering. It loses the name to the native tool (apps/mcp registers those
681
+ // first) instead of vanishing.
682
+ //
683
+ // Every path that accepts a config from a user validates with this one.
684
+ const HTTP_ENDPOINT_CONFIG_WRITE = HTTP_ENDPOINT_CONFIG.superRefine((cfg, ctx) => {
685
+ if ((0, reservedToolName_1.isReservedToolName)(cfg.name)) {
686
+ ctx.addIssue({
687
+ code: 'custom',
688
+ path: ['name'],
689
+ message: constants_1.constants.RESERVED_TOOL_NAME_MESSAGE
690
+ });
691
+ }
692
+ // On the write path only, like the rule above: no stored row can have one
693
+ // yet, but the read shape must stay permissive on principle — a rule
694
+ // tightened later must never stop an installed tool from registering.
695
+ if (cfg.outputSchema && cfg.outputSchema.type !== 'object') {
696
+ ctx.addIssue({
697
+ code: 'custom',
698
+ path: ['outputSchema', 'type'],
699
+ message: constants_1.constants.OUTPUT_SCHEMA_NOT_OBJECT_MESSAGE
700
+ });
701
+ }
702
+ });
703
+ // Validates one artifact_tool.config of definition `mcp-proxy`. Each row of this
704
+ // kind connects a remote MCP server (a vendor's official server) and registers
705
+ // one local MCP tool per discovered remote tool at boot. `url`/`transport` are
706
+ // resolved server-side from the curated mcp_server_catalog row referenced by
707
+ // `curatedServerId` (the client never picks an arbitrary URL for now), then
708
+ // re-validated here. Secrets are never inlined — auth references an
709
+ // artifact_credential by id, same as http-endpoint.
710
+ const MCP_PROXY_AUTH = zod_1.z.discriminatedUnion('kind', [
711
+ zod_1.z.object({ kind: zod_1.z.literal(constants_1.constants.MCP_PROXY_AUTH_KIND_NONE) }),
712
+ zod_1.z.object({
713
+ kind: zod_1.z.literal(constants_1.constants.MCP_PROXY_AUTH_KIND_BEARER),
714
+ credentialId: zod_1.z.uuid()
715
+ }),
716
+ zod_1.z.object({
717
+ kind: zod_1.z.literal(constants_1.constants.MCP_PROXY_AUTH_KIND_HEADER),
718
+ name: zod_1.z.string().min(1).max(256),
719
+ credentialId: zod_1.z.uuid()
720
+ }),
721
+ zod_1.z.object({
722
+ kind: zod_1.z.literal(constants_1.constants.MCP_PROXY_AUTH_KIND_OAUTH),
723
+ credentialId: zod_1.z.uuid()
724
+ })
725
+ ]);
726
+ const MCP_PROXY_CONFIG = zod_1.z.object({
727
+ // Which curated server this row connects. url/transport are filled from the
728
+ // catalog server-side; included here so the stored config is self-contained
729
+ // for the boot loop (which never re-reads the catalog).
730
+ curatedServerId: zod_1.z.uuid(),
731
+ url: zod_1.z.string().min(1).max(2048).default(''),
732
+ transport: zod_1.z
733
+ .enum(constants_1.constants.MCP_PROXY_TRANSPORTS)
734
+ .default(constants_1.constants.MCP_PROXY_TRANSPORT_STREAMABLE_HTTP),
735
+ // Tools register as `<prefix>__<remoteKey>`. Optional — the API defaults it to
736
+ // the catalog slug when unset.
737
+ prefix: zod_1.z
738
+ .string()
739
+ .regex(/^[a-zA-Z0-9_-]{1,40}$/, 'Prefix must be 1-40 chars: letters, digits, underscore or hyphen')
740
+ .optional(),
741
+ // Per-item enable lists; the full available set lives on metadata.discovery so
742
+ // the UI can render toggles without re-hitting the remote. The empty/absent
743
+ // semantics differ by kind, matching the UI defaults:
744
+ // - allowedTools: absent/empty = ALL tools enabled (UI defaults them on).
745
+ // - allowedResources / allowedPrompts: opt-in — ONLY the listed items
746
+ // register; absent/empty = none (UI defaults them off).
747
+ // All three are filtered at boot.
748
+ allowedTools: zod_1.z.array(zod_1.z.string().min(1).max(256)).max(500).optional(),
749
+ allowedResources: zod_1.z.array(zod_1.z.string().min(1).max(2048)).max(500).optional(),
750
+ allowedPrompts: zod_1.z.array(zod_1.z.string().min(1).max(256)).max(500).optional(),
751
+ auth: MCP_PROXY_AUTH.default({ kind: constants_1.constants.MCP_PROXY_AUTH_KIND_NONE }),
752
+ timeoutMs: zod_1.z
753
+ .number()
754
+ .int()
755
+ .positive()
756
+ .default(constants_1.constants.MCP_PROXY_DEFAULT_TIMEOUT_MS)
757
+ .transform(n => Math.min(n, constants_1.constants.MCP_PROXY_MAX_TIMEOUT_MS))
758
+ });
759
+ // Validates one artifact_tool.config of definition `custom-code`. Unlike the two
760
+ // definitions above, this config describes almost nothing about the tools — the
761
+ // names and schemas live on the ACTIVE VERSION (artifact_tool_version.tools),
762
+ // written at publish time. Everything here is the row-level envelope: which
763
+ // version is live, and the limits that apply to whatever code that version runs.
764
+ //
765
+ // `activeVersionId` is nullable on purpose: a freshly installed row has an
766
+ // uploaded draft but nothing published, and the MCP boot loop must treat that as
767
+ // "this artifact contributes zero tools" rather than as an error.
768
+ const CUSTOM_CODE_CONFIG = zod_1.z.object({
769
+ activeVersionId: zod_1.z.uuid().nullable().default(null),
770
+ // Which of the active version's tools are exposed. Absent or empty means all
771
+ // of them — the same convention `mcp-proxy` uses for its remote tools, and
772
+ // reused rather than reinvented so one row of many tools behaves the same way
773
+ // whichever kind of row it is.
774
+ //
775
+ // It lives on the row's config and not in the version, because it answers a
776
+ // different question. The manifest is what this code CAN do and moves only by
777
+ // deploying; this is what the server currently offers, and turning one tool
778
+ // off to shorten a bloated tool list must not require a redeploy or leave the
779
+ // author's manifest disagreeing with their own file.
780
+ //
781
+ // Names that no longer appear in the active manifest are simply never matched,
782
+ // so a version that drops a tool leaves nothing to clean up here.
783
+ allowedTools: zod_1.z
784
+ .array(zod_1.z.string().min(1).max(constants_1.constants.CUSTOM_CODE_TOOL_NAME_MAX))
785
+ .max(constants_1.constants.CUSTOM_CODE_MAX_TOOLS)
786
+ .optional(),
787
+ // Egress allow-list, enforced by the outbound worker (Phase 2) — never in the
788
+ // SDK, which is user-editable and therefore not a control.
789
+ allowedHosts: zod_1.z
790
+ .array(zod_1.z.string().min(1).max(253).toLowerCase())
791
+ .max(50)
792
+ .optional(),
793
+ // Providers the script may request via ctx.connection(), and — for the three
794
+ // that have one — send files as. The broker refuses anything not listed here,
795
+ // so widening it is an explicit, auditable edit.
796
+ //
797
+ // Left as a free string on this READ shape: the connectable set grows and
798
+ // shrinks as providers are added and retired, and a stored row naming a
799
+ // provider we no longer offer has to keep parsing or the whole install stops
800
+ // registering. CUSTOM_CODE_CONFIG_WRITE below is where a name is checked.
801
+ connections: zod_1.z.array(zod_1.z.string().min(1).max(100)).max(50).optional(),
802
+ timeoutMs: zod_1.z
803
+ .number()
804
+ .int()
805
+ .positive()
806
+ .default(constants_1.constants.CUSTOM_CODE_DEFAULT_TIMEOUT_MS)
807
+ .transform(n => Math.min(n, constants_1.constants.CUSTOM_CODE_MAX_TIMEOUT_MS)),
808
+ // How far ctx.resources.create and .delete reach. `own` is the default and
809
+ // the safe floor; `all` lets the script replace and remove resources it did
810
+ // not write, which is what a tool that prunes a stale crawl needs. Declared
811
+ // here so the owner grants it at publish time and the script cannot.
812
+ resourceAccess: zod_1.z
813
+ .enum(constants_1.constants.CUSTOM_CODE_RESOURCE_ACCESS_VALUES)
814
+ .default(constants_1.constants.CUSTOM_CODE_RESOURCE_ACCESS_OWN)
815
+ });
816
+ // The same config, plus the rule that every declared connection names a provider
817
+ // we actually run a managed OAuth app for.
818
+ //
819
+ // Without this a typo is invisible until a customer's tool call fails: the
820
+ // broker answers 403 "not one of this tool's declared connections" for
821
+ // `google_gmail`, which is true and useless, because the entry IS in the list —
822
+ // it just doesn't name anything. Same split as HTTP_ENDPOINT_CONFIG_WRITE, and
823
+ // for the same reason: only the write path may tighten.
824
+ const CUSTOM_CODE_CONFIG_WRITE = CUSTOM_CODE_CONFIG.superRefine((cfg, ctx) => {
825
+ cfg.connections?.forEach((provider, index) => {
826
+ if (constants_1.constants.OAUTH_PROVIDERS.includes(provider)) {
827
+ return;
828
+ }
829
+ ctx.addIssue({
830
+ code: 'custom',
831
+ path: ['connections', index],
832
+ message: constants_1.constants.CUSTOM_CODE_UNKNOWN_CONNECTION_MESSAGE
833
+ });
834
+ });
835
+ });
836
+ // One tool entry in an uploaded manifest. This is the CONTRACT half of a
837
+ // version: what apps/mcp registers at boot without ever calling the dispatcher.
838
+ // `outputSchema` is optional — MCP allows a tool to declare structured output,
839
+ // and a version that omits it just returns text.
840
+ //
841
+ // This is the shape used to READ a stored version back, so it carries no
842
+ // reserved-name rule: a rule tightened after a version was published would stop
843
+ // that version registering, and the owner would see tools quietly disappear with
844
+ // only a log line to explain it. Reservation belongs to the manifest below,
845
+ // which is the write path.
846
+ const CUSTOM_CODE_TOOL = zod_1.z.object({
847
+ name: zod_1.z
848
+ .string()
849
+ .min(1)
850
+ .max(constants_1.constants.CUSTOM_CODE_TOOL_NAME_MAX)
851
+ .regex(/^[a-zA-Z0-9_-]+$/, 'Tool name may only contain letters, digits, underscore or hyphen'),
852
+ title: zod_1.z.string().min(1).max(200).optional(),
853
+ description: zod_1.z.string().max(2000).optional(),
854
+ inputSchema: SCHEMA_DEFINITION.default({ type: 'object', properties: {} }),
855
+ outputSchema: SCHEMA_DEFINITION.optional()
856
+ });
857
+ // The manifest a `ganju deploy` (or the dashboard) uploads alongside a bundle.
858
+ // Names must be unique within one script: apps/mcp registers them into a single
859
+ // flat namespace, and a duplicate would silently register once and drop the rest.
860
+ // For the same reason a name may not be one the platform owns — see
861
+ // isReservedToolName.
862
+ //
863
+ // The offending entry is identified by `path` (`tools.3.name`) rather than by
864
+ // quoting the name into the message: a manifest can declare 50 tools, so "one
865
+ // of them is reserved" would not be actionable, but an interpolated message can
866
+ // never be localized — localizeZodIssue keys on the exact English string.
867
+ const CUSTOM_CODE_MANIFEST = zod_1.z.object({
868
+ tools: zod_1.z
869
+ .array(CUSTOM_CODE_TOOL)
870
+ .min(1, 'A version must declare at least one tool')
871
+ .max(constants_1.constants.CUSTOM_CODE_MAX_TOOLS, `A script may declare at most ${constants_1.constants.CUSTOM_CODE_MAX_TOOLS} tools`)
872
+ .refine(tools => new Set(tools.map(t => t.name)).size === tools.length, {
873
+ message: 'Tool names must be unique within a version'
874
+ })
875
+ .superRefine((tools, ctx) => {
876
+ tools.forEach((tool, index) => {
877
+ if ((0, reservedToolName_1.isReservedToolName)(tool.name)) {
878
+ ctx.addIssue({
879
+ code: 'custom',
880
+ path: [index, 'name'],
881
+ message: constants_1.constants.RESERVED_TOOL_NAME_MESSAGE
882
+ });
883
+ }
884
+ // Same rule as http-endpoint's write path, and here for the same
885
+ // reason: `structuredContent` is an object, so a schema of any other
886
+ // type promises something the tool can never return.
887
+ if (tool.outputSchema && tool.outputSchema.type !== 'object') {
888
+ ctx.addIssue({
889
+ code: 'custom',
890
+ path: [index, 'outputSchema', 'type'],
891
+ message: constants_1.constants.OUTPUT_SCHEMA_NOT_OBJECT_MESSAGE
892
+ });
893
+ }
894
+ });
895
+ })
896
+ });
897
+ // Creates a draft version. Carries the manifest — the tool names and schemas the
898
+ // MCP server will register from once this version is published — and optionally
899
+ // the row-level config, since limits on how the code may run belong to the same
900
+ // review as the code itself. The compiled bundle arrives separately.
901
+ const ARTIFACT_CUSTOM_CODE_CREATE_VERSION = zod_1.z.object({
902
+ manifest: CUSTOM_CODE_MANIFEST,
903
+ config: CUSTOM_CODE_CONFIG_WRITE.optional(),
904
+ projectId: zod_1.z.uuid(),
905
+ userId: zod_1.z.uuid(),
906
+ organizationId: zod_1.z.uuid()
907
+ });
908
+ // Attaches the compiled script to a draft. Separate from the create above for
909
+ // the same reason resource upload is separate from resource create: the bundle
910
+ // is a raw binary body while the manifest is JSON, and one request carries one
911
+ // body. Only the identifiers are validated here — the bytes are the body.
912
+ const ARTIFACT_CUSTOM_CODE_UPLOAD_BUNDLE = zod_1.z.object({
913
+ versionId: zod_1.z.uuid(),
914
+ // Whether these bytes are readable source or a compiled artifact. Defaults to
915
+ // 'bundle' because the CLI — which sends no such flag — is uploading one, and
916
+ // because the safe answer for anything unknown is "do not offer this in a text
917
+ // box".
918
+ sourceKind: zod_1.z
919
+ .enum(constants_1.constants.CUSTOM_CODE_SOURCE_KINDS)
920
+ .default(constants_1.constants.CUSTOM_CODE_SOURCE_KIND_BUNDLE),
921
+ projectId: zod_1.z.uuid(),
922
+ userId: zod_1.z.uuid(),
923
+ organizationId: zod_1.z.uuid()
924
+ });
925
+ // Runs ONE tool of ONE version against a sample input, without publishing it.
926
+ //
927
+ // The version is named rather than assumed to be the draft: a test that could
928
+ // only run the newest version would be useless the moment someone wanted to
929
+ // check what the live one actually does. `input` is whatever the tool's own
930
+ // input schema accepts, and is checked against it before anything is deployed.
931
+ const ARTIFACT_CUSTOM_CODE_TEST_VERSION = zod_1.z.object({
932
+ versionId: zod_1.z.uuid(),
933
+ tool: zod_1.z
934
+ .string()
935
+ .min(1)
936
+ .max(constants_1.constants.CUSTOM_CODE_TOOL_NAME_MAX)
937
+ .regex(/^[a-zA-Z0-9_-]+$/, 'Tool name is not valid'),
938
+ input: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).default({}),
939
+ projectId: zod_1.z.uuid(),
940
+ userId: zod_1.z.uuid(),
941
+ organizationId: zod_1.z.uuid()
942
+ });
943
+ // Reads one version's stored source back for the editor.
944
+ const ARTIFACT_CUSTOM_CODE_GET_SOURCE = zod_1.z.object({
945
+ versionId: zod_1.z.uuid(),
946
+ projectId: zod_1.z.uuid(),
947
+ userId: zod_1.z.uuid(),
948
+ organizationId: zod_1.z.uuid()
949
+ });
950
+ // Makes a draft the artifact's active version.
951
+ const ARTIFACT_CUSTOM_CODE_PUBLISH = zod_1.z.object({
952
+ versionId: zod_1.z.uuid(),
953
+ projectId: zod_1.z.uuid(),
954
+ userId: zod_1.z.uuid(),
955
+ organizationId: zod_1.z.uuid()
956
+ });
957
+ // Makes a previously published (now archived) version active again. The same
958
+ // state transition as publish with a different intent, kept apart so the two are
959
+ // distinguishable in logs and in the UI.
960
+ const ARTIFACT_CUSTOM_CODE_ROLLBACK = zod_1.z.object({
961
+ versionId: zod_1.z.uuid(),
962
+ projectId: zod_1.z.uuid(),
963
+ userId: zod_1.z.uuid(),
964
+ organizationId: zod_1.z.uuid()
965
+ });
966
+ // Recent invocations of the artifact's custom tools, newest first.
967
+ //
968
+ // The rows already exist — apps/mcp records one `mcp_request` per call with the
969
+ // tool name and, for a custom tool, the `ctx.log` lines on its output. What was
970
+ // missing is a way to read them without a database client, which is the whole of
971
+ // what `ganju logs` needs. `tool` narrows to one name; `before` pages backwards
972
+ // through the same ordering rather than by offset, so a row arriving mid-page
973
+ // can't make the reader see one twice.
974
+ const ARTIFACT_CUSTOM_CODE_LIST_LOGS = zod_1.z.object({
975
+ tool: zod_1.z
976
+ .string()
977
+ .min(1)
978
+ .max(constants_1.constants.CUSTOM_CODE_TOOL_NAME_MAX)
979
+ .regex(/^[a-zA-Z0-9_-]+$/, 'Tool name is not valid')
980
+ .optional(),
981
+ limit: zod_1.z.coerce
982
+ .number()
983
+ .int()
984
+ .min(1)
985
+ .max(constants_1.constants.CUSTOM_CODE_LOGS_MAX_LIMIT)
986
+ .default(constants_1.constants.CUSTOM_CODE_LOGS_DEFAULT_LIMIT),
987
+ before: zod_1.z.coerce.date().optional(),
988
+ projectId: zod_1.z.uuid(),
989
+ userId: zod_1.z.uuid(),
990
+ organizationId: zod_1.z.uuid()
991
+ });
992
+ // Reads the artifact's version history and which one is currently active.
993
+ const ARTIFACT_CUSTOM_CODE_LIST_VERSIONS = zod_1.z.object({
994
+ projectId: zod_1.z.uuid(),
995
+ userId: zod_1.z.uuid(),
996
+ organizationId: zod_1.z.uuid()
997
+ });
998
+ // What apps/mcp POSTs to a dispatched user script. Parsed inside the script by
999
+ // @ganju/sdk, which routes on `tool` — one script exports many tools, so the
1000
+ // name is in the body rather than in the path.
1001
+ const CUSTOM_CODE_INVOKE_REQUEST = zod_1.z.object({
1002
+ tool: zod_1.z.string().min(1).max(constants_1.constants.CUSTOM_CODE_TOOL_NAME_MAX),
1003
+ input: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).default({}),
1004
+ // Correlates the dispatcher's mcp_request row with whatever the script logs.
1005
+ // Opaque to user code.
1006
+ requestId: zod_1.z.string().max(100).optional()
1007
+ });
1008
+ // What the script returns. `output` is validated against the version's declared
1009
+ // outputSchema by the dispatcher, not here — this schema only establishes the
1010
+ // envelope, so a script that returns garbage produces a legible tool error
1011
+ // instead of an unhandled parse failure in the MCP worker.
1012
+ //
1013
+ // `error` is how a script reports a handled failure. It travels as a 200 with an
1014
+ // error field rather than an HTTP status, because a non-2xx from the dispatch
1015
+ // namespace is ambiguous — it could equally be the platform refusing to run the
1016
+ // script at all.
1017
+ const CUSTOM_CODE_INVOKE_RESPONSE = zod_1.z.object({
1018
+ output: zod_1.z.unknown().optional(),
1019
+ logs: zod_1.z
1020
+ .array(zod_1.z.object({
1021
+ level: zod_1.z.enum(['log', 'warn', 'error']).default('log'),
1022
+ message: zod_1.z.string().max(constants_1.constants.CUSTOM_CODE_MAX_LOG_LENGTH)
1023
+ }))
1024
+ .max(constants_1.constants.CUSTOM_CODE_MAX_LOGS)
1025
+ .default([]),
1026
+ error: zod_1.z.string().max(2000).optional()
1027
+ });
1028
+ // Broker request bodies. Every one of these is authenticated by the bearer token
1029
+ // alone — no body field names the artifact, because a script could lie about it.
1030
+ const CUSTOM_CODE_BROKER_CONNECTION = zod_1.z.object({
1031
+ provider: zod_1.z.string().min(1).max(100)
1032
+ });
1033
+ const CUSTOM_CODE_BROKER_SECRET = zod_1.z.object({
1034
+ // The credential's label, which is the lookup key for custom-code secrets
1035
+ // (they carry no id inside the script). See CREDENTIAL_PROVIDER_CUSTOM_CODE.
1036
+ name: zod_1.z.string().min(1).max(200)
1037
+ });
1038
+ const CUSTOM_CODE_BROKER_RESOURCE_SEARCH = zod_1.z.object({
1039
+ query: zod_1.z.string().min(1).max(2000),
1040
+ limit: zod_1.z.number().int().positive().max(20).default(5)
1041
+ });
1042
+ const CUSTOM_CODE_BROKER_RESOURCE_READ = zod_1.z.object({
1043
+ uri: zod_1.z.string().min(1).max(2000)
1044
+ });
1045
+ // One ctx.resources.delete call. Same shape as a read — a uri is the only thing
1046
+ // a script holds — but its own schema so the two can diverge without one
1047
+ // quietly accepting what the other meant.
1048
+ const CUSTOM_CODE_BROKER_RESOURCE_DELETE = zod_1.z.object({
1049
+ uri: zod_1.z.string().min(1).max(2000),
1050
+ // Opt-in rather than implied. The FK cascades regardless, so a delete that
1051
+ // took children silently would be the difference between removing one page
1052
+ // and removing a 400-page crawl — a distinction the caller has to make on
1053
+ // purpose.
1054
+ children: zod_1.z.boolean().default(false)
1055
+ });
1056
+ // One ctx.resources.create call.
1057
+ //
1058
+ // `content` and `bytes` are the two payload shapes and exactly one is required:
1059
+ // text goes inline on the row, bytes (base64) go to storage. Requiring one and
1060
+ // refusing both is what keeps the row's meaning unambiguous — a resource with
1061
+ // inline text AND a stored object has two answers to "what is in it", and every
1062
+ // reader would have to pick.
1063
+ //
1064
+ // `mimeType` is constrained to the same list an uploaded file is, so a script
1065
+ // cannot introduce a type no other surface knows how to render or attach.
1066
+ const CUSTOM_CODE_BROKER_RESOURCE_CREATE = zod_1.z
1067
+ .object({
1068
+ title: zod_1.z.string().min(1).max(200),
1069
+ // Optional: derived from the title when omitted. A script that re-runs and
1070
+ // wants to replace its previous output passes the same one.
1071
+ uri: zod_1.z.string().min(1).max(2000).optional(),
1072
+ description: zod_1.z.string().max(1000).optional(),
1073
+ mimeType: zod_1.z
1074
+ .enum(constants_1.constants.MIMETYPES, { message: 'Unsupported mime type' })
1075
+ .optional(),
1076
+ content: zod_1.z.string().optional(),
1077
+ bytes: zod_1.z.string().optional(),
1078
+ // Only meaningful for the bytes path; drives the stored object's name and
1079
+ // the filename an attachment arrives under.
1080
+ fileName: zod_1.z.string().min(1).max(255).optional(),
1081
+ // Put this resource in the artifact's search corpus. Off by default: script
1082
+ // output that lands in the corpus is content the assistant will answer other
1083
+ // people's questions from, and that should be a decision rather than a
1084
+ // side effect of writing a file.
1085
+ index: zod_1.z.boolean().default(false)
1086
+ })
1087
+ .superRefine((value, ctx) => {
1088
+ const hasContent = value.content !== undefined;
1089
+ const hasBytes = value.bytes !== undefined;
1090
+ if (hasContent === hasBytes) {
1091
+ ctx.addIssue({
1092
+ code: 'custom',
1093
+ path: [hasContent ? 'bytes' : 'content'],
1094
+ message: constants_1.constants.CUSTOM_CODE_RESOURCE_PAYLOAD_MESSAGE
1095
+ });
1096
+ return;
1097
+ }
1098
+ // Measured in bytes rather than characters: the cap exists to bound what is
1099
+ // stored, and a string of emoji is four times its length on the way to
1100
+ // Postgres.
1101
+ if (hasContent) {
1102
+ const size = new TextEncoder().encode(value.content).byteLength;
1103
+ if (size > constants_1.constants.CUSTOM_CODE_MAX_RESOURCE_TEXT_BYTES) {
1104
+ ctx.addIssue({
1105
+ code: 'custom',
1106
+ path: ['content'],
1107
+ message: constants_1.constants.CUSTOM_CODE_RESOURCE_TEXT_TOO_LARGE_MESSAGE
1108
+ });
1109
+ }
1110
+ return;
1111
+ }
1112
+ // The base64 length bounds the decoded length, so this rejects an oversized
1113
+ // payload without decoding it first — the decode is what would actually cost
1114
+ // the memory being defended.
1115
+ const decodedSize = Math.floor((value.bytes.length * 3) / 4);
1116
+ if (decodedSize > constants_1.constants.CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES) {
1117
+ ctx.addIssue({
1118
+ code: 'custom',
1119
+ path: ['bytes'],
1120
+ message: constants_1.constants.CUSTOM_CODE_RESOURCE_FILE_TOO_LARGE_MESSAGE
1121
+ });
1122
+ }
1123
+ });
1124
+ // The resources to deliver. Named by URI rather than by id because a URI is what
1125
+ // every other resource surface a script can see hands back — ctx.resources.list
1126
+ // and .search both return one, and neither returns a row id.
1127
+ const CUSTOM_CODE_SEND_FILE_URIS = zod_1.z
1128
+ .array(zod_1.z.string().min(1).max(2000))
1129
+ .min(1, 'At least one resource uri is required')
1130
+ .max(constants_1.constants.CUSTOM_CODE_SEND_FILE_MAX_URIS, `At most ${constants_1.constants.CUSTOM_CODE_SEND_FILE_MAX_URIS} resources can be sent in one call`);
1131
+ const CUSTOM_CODE_SEND_FILE_BODY = zod_1.z.string().max(100_000);
1132
+ const CUSTOM_CODE_SEND_FILE_RECIPIENTS = zod_1.z.string().min(1).max(2000);
1133
+ // What ctx.sendFile posts to the broker.
1134
+ //
1135
+ // Discriminated on `to` because the three destinations genuinely disagree about
1136
+ // what a send is: two are mail with recipients and a subject, one is a channel
1137
+ // upload with a comment. A single flat shape with everything optional would
1138
+ // accept `{ to: 'slack', subject: '…' }` and drop the subject silently.
1139
+ //
1140
+ // Each variant deliberately covers ONE operation — a new mail, a new upload.
1141
+ // The container routes behind them can also reply, forward, and update drafts,
1142
+ // but sendFile exists to move bytes a script cannot hold, not to be a mail
1143
+ // client: anything else a script wants from these APIs it can do itself with
1144
+ // ctx.connection() and a fetch, where its own allow-list applies.
1145
+ const CUSTOM_CODE_SEND_FILE = zod_1.z.discriminatedUnion('to', [
1146
+ zod_1.z.object({
1147
+ to: zod_1.z.literal(constants_1.constants.CUSTOM_CODE_SEND_FILE_TARGET_GMAIL),
1148
+ uris: CUSTOM_CODE_SEND_FILE_URIS,
1149
+ message: zod_1.z.object({
1150
+ // Gmail's own field name for the recipient; the destination lives on `to`
1151
+ // one level up.
1152
+ to: CUSTOM_CODE_SEND_FILE_RECIPIENTS,
1153
+ subject: zod_1.z.string().max(2000).optional(),
1154
+ body: CUSTOM_CODE_SEND_FILE_BODY.default(''),
1155
+ cc: zod_1.z.string().max(2000).optional(),
1156
+ bcc: zod_1.z.string().max(2000).optional(),
1157
+ contentType: zod_1.z.enum(['text/html', 'text/plain']).optional(),
1158
+ // Threads an attachment onto an existing conversation, which a script
1159
+ // typically got from a gmail- tool call earlier in the same turn.
1160
+ threadId: zod_1.z.string().max(200).optional()
1161
+ })
1162
+ }),
1163
+ zod_1.z.object({
1164
+ to: zod_1.z.literal(constants_1.constants.CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK),
1165
+ uris: CUSTOM_CODE_SEND_FILE_URIS,
1166
+ message: zod_1.z.object({
1167
+ to: CUSTOM_CODE_SEND_FILE_RECIPIENTS,
1168
+ subject: zod_1.z.string().max(2000).optional(),
1169
+ body: CUSTOM_CODE_SEND_FILE_BODY.default(''),
1170
+ cc: zod_1.z.string().max(2000).optional(),
1171
+ bcc: zod_1.z.string().max(2000).optional(),
1172
+ // Graph's vocabulary, not a MIME type — the container passes it straight
1173
+ // through to the message payload.
1174
+ contentType: zod_1.z.enum(['html', 'text']).optional()
1175
+ })
1176
+ }),
1177
+ zod_1.z.object({
1178
+ to: zod_1.z.literal(constants_1.constants.CUSTOM_CODE_SEND_FILE_TARGET_SLACK),
1179
+ // Exactly one, unlike the two mail destinations. Slack's external-upload
1180
+ // flow moves a single file per call — three round trips per file — and the
1181
+ // container route behind it reads one `attachment` field. Accepting a list
1182
+ // here would take ten and deliver the first, silently. A script sending
1183
+ // several files calls sendFile several times.
1184
+ uris: CUSTOM_CODE_SEND_FILE_URIS.length(1, 'Slack delivers one file per call'),
1185
+ message: zod_1.z.object({
1186
+ channel: zod_1.z.string().min(1).max(200),
1187
+ title: zod_1.z.string().max(200).optional(),
1188
+ initialComment: zod_1.z.string().max(4000).optional(),
1189
+ threadTs: zod_1.z.string().max(100).optional()
1190
+ })
1191
+ })
1192
+ ]);
1193
+ const ARTIFACT_UPDATE_RESOURCE_SHOW_SOURCE = zod_1.z.object({
1194
+ resourceId: zod_1.z.uuid(),
1195
+ showSource: zod_1.z.enum(constants_1.constants.CHANNEL_STATUS),
1196
+ projectId: zod_1.z.uuid(),
1197
+ userId: zod_1.z.uuid(),
1198
+ organizationId: zod_1.z.uuid()
1199
+ });
1200
+ const ARTIFACT_GET = zod_1.z.object({
1201
+ projectId: zod_1.z.uuid(),
1202
+ userId: zod_1.z.uuid(),
1203
+ organizationId: zod_1.z.uuid()
1204
+ });
1205
+ const ARTIFACT_UPDATE_SLUG = zod_1.z.object({
1206
+ slug: zod_1.z
1207
+ .string()
1208
+ .trim()
1209
+ .toLowerCase()
1210
+ .refine(slug_1.isValidSlugFormat, {
1211
+ message: 'Slug must be 3–63 chars, lowercase letters, digits, or hyphens, and start/end with a letter or digit'
1212
+ })
1213
+ .refine(s => !(0, slug_1.isReservedSlug)(s), { message: 'Slug is reserved' }),
1214
+ projectId: zod_1.z.uuid(),
1215
+ userId: zod_1.z.uuid(),
1216
+ organizationId: zod_1.z.uuid()
1217
+ });
1218
+ const CHANNEL_GET = zod_1.z.object({
1219
+ projectId: zod_1.z.uuid(),
1220
+ userId: zod_1.z.uuid(),
1221
+ organizationId: zod_1.z.uuid()
1222
+ });
1223
+ const CHANNEL_REMOVE = zod_1.z.object({
1224
+ channelId: zod_1.z.uuid(),
1225
+ projectId: zod_1.z.uuid(),
1226
+ userId: zod_1.z.uuid(),
1227
+ organizationId: zod_1.z.uuid()
1228
+ });
1229
+ const CHANNEL_LIST_CONVERSATIONS = zod_1.z.object({
1230
+ channelId: zod_1.z.uuid(),
1231
+ projectId: zod_1.z.uuid(),
1232
+ userId: zod_1.z.uuid(),
1233
+ organizationId: zod_1.z.uuid()
1234
+ });
1235
+ const CHANNEL_LIST_MESSAGES = zod_1.z.object({
1236
+ channelId: zod_1.z.uuid(),
1237
+ conversationId: zod_1.z.uuid(),
1238
+ projectId: zod_1.z.uuid(),
1239
+ userId: zod_1.z.uuid(),
1240
+ organizationId: zod_1.z.uuid()
1241
+ });
1242
+ const CHANNEL_CREATE_VIEW = CHANNEL_CREATE.omit({
1243
+ projectId: true,
1244
+ userId: true,
1245
+ organizationId: true
1246
+ });
1247
+ const CHANNEL_UPDATE_VIEW = CHANNEL_UPDATE.omit({
1248
+ channelId: true,
1249
+ projectId: true,
1250
+ userId: true,
1251
+ organizationId: true
1252
+ });
1253
+ const ARTIFACT_CREATE_PROMPT_VIEW = ARTIFACT_CREATE_PROMPT.omit({
1254
+ projectId: true,
1255
+ userId: true,
1256
+ organizationId: true
1257
+ });
1258
+ const ARTIFACT_UPDATE_PROMPT_VIEW = ARTIFACT_UPDATE_PROMPT.omit({
1259
+ promptId: true,
1260
+ projectId: true,
1261
+ userId: true,
1262
+ organizationId: true
1263
+ });
1264
+ const ARTIFACT_CREATE_RESOURCE_VIEW = ARTIFACT_CREATE_RESOURCE.omit({
1265
+ projectId: true,
1266
+ userId: true,
1267
+ organizationId: true
1268
+ });
1269
+ const ARTIFACT_UPDATE_RESOURCE_VIEW = ARTIFACT_UPDATE_RESOURCE.omit({
1270
+ resourceId: true,
1271
+ projectId: true,
1272
+ userId: true,
1273
+ organizationId: true
1274
+ });
1275
+ const ARTIFACT_UPDATE_SLUG_VIEW = ARTIFACT_UPDATE_SLUG.omit({
1276
+ projectId: true,
1277
+ userId: true,
1278
+ organizationId: true
1279
+ });
1280
+ exports.Schema = {
1281
+ CONTACT_MESSAGE,
1282
+ ORGANIZATION_CREATE,
1283
+ ORGANIZATION_CREATE_VIEW,
1284
+ ORGANIZATION_UPDATE,
1285
+ ORGANIZATION_GET,
1286
+ AUTH_USER_GET,
1287
+ PROJECT_CREATE,
1288
+ PROJECT_CREATE_VIEW,
1289
+ PROJECT_UPDATE,
1290
+ PROJECT_GET,
1291
+ ORGANIZATION_INVITATION_CREATE,
1292
+ ORGANIZATION_INVITATION_CREATE_VIEW,
1293
+ ORGANIZATION_INVITATION_LIST,
1294
+ ORGANIZATION_INVITATION_REMOVE,
1295
+ PROJECT_INVITATION_CREATE,
1296
+ PROJECT_INVITATION_CREATE_VIEW,
1297
+ PROJECT_INVITATION_LIST,
1298
+ PROJECT_INVITATION_REMOVE,
1299
+ INVITATION_RESPOND,
1300
+ INVITATION_GET_BY_TOKEN,
1301
+ ORGANIZATION_MEMBER_LIST,
1302
+ ORGANIZATION_MEMBER_REMOVE,
1303
+ PROJECT_MEMBER_LIST,
1304
+ PROJECT_MEMBER_REMOVE,
1305
+ ARTIFACT_CREATE_PROMPT,
1306
+ ARTIFACT_CREATE_PROMPT_VIEW,
1307
+ ARTIFACT_UPDATE_PROMPT,
1308
+ ARTIFACT_UPDATE_PROMPT_VIEW,
1309
+ ARTIFACT_GET_PROMPT,
1310
+ ARTIFACT_REMOVE_PROMPT,
1311
+ ARTIFACT_CREATE_RESOURCE,
1312
+ ARTIFACT_CREATE_RESOURCE_VIEW,
1313
+ ARTIFACT_CREATE_WEBSITE,
1314
+ ARTIFACT_CREATE_WEBSITE_VIEW,
1315
+ ARTIFACT_UPDATE_WEBSITE,
1316
+ ARTIFACT_UPDATE_WEBSITE_VIEW,
1317
+ ARTIFACT_CREATE_GOOGLE_DRIVE,
1318
+ ARTIFACT_CREATE_GOOGLE_DRIVE_VIEW,
1319
+ ARTIFACT_SYNC_GOOGLE_DRIVE,
1320
+ ARTIFACT_CREATE_ONE_DRIVE,
1321
+ ARTIFACT_CREATE_ONE_DRIVE_VIEW,
1322
+ ARTIFACT_SYNC_ONE_DRIVE,
1323
+ ARTIFACT_UPDATE_RESOURCE,
1324
+ ARTIFACT_UPDATE_RESOURCE_VIEW,
1325
+ ARTIFACT_GET_RESOURCE,
1326
+ ARTIFACT_GET_RESOURCE_BY_ID,
1327
+ ARTIFACT_REMOVE_RESOURCE,
1328
+ ARTIFACT_UPLOAD_RESOURCE_FILE,
1329
+ ARTIFACT_DOWNLOAD_RESOURCE_FILE,
1330
+ ARTIFACT_CREATE_TOOL,
1331
+ ARTIFACT_UPDATE_TOOL,
1332
+ ARTIFACT_GET_TOOL,
1333
+ ARTIFACT_REMOVE_TOOL,
1334
+ ARTIFACT_SET_TOOL_ENABLED,
1335
+ ARTIFACT_GET_CREDENTIAL,
1336
+ ARTIFACT_LIST_CONNECTIONS,
1337
+ ARTIFACT_REMOVE_CREDENTIAL,
1338
+ ARTIFACT_CREATE_CREDENTIAL,
1339
+ ARTIFACT_GET,
1340
+ ARTIFACT_UPDATE_SLUG,
1341
+ ARTIFACT_UPDATE_SLUG_VIEW,
1342
+ ORGANIZATION_LIST_LLM,
1343
+ ORGANIZATION_CREATE_LLM,
1344
+ ORGANIZATION_CREATE_LLM_VIEW,
1345
+ ORGANIZATION_UPDATE_LLM,
1346
+ ORGANIZATION_UPDATE_LLM_VIEW,
1347
+ ORGANIZATION_REMOVE_LLM,
1348
+ CHANNEL_CONFIG,
1349
+ CHANNEL_CREATE,
1350
+ CHANNEL_CREATE_VIEW,
1351
+ CHANNEL_UPDATE,
1352
+ CHANNEL_UPDATE_VIEW,
1353
+ CHANNEL_GET,
1354
+ CHANNEL_REMOVE,
1355
+ CHANNEL_LIST_CONVERSATIONS,
1356
+ CHANNEL_LIST_MESSAGES,
1357
+ ARTIFACT_UPDATE_RESOURCE_SHOW_SOURCE,
1358
+ HTTP_ENDPOINT_CONFIG,
1359
+ HTTP_ENDPOINT_CONFIG_WRITE,
1360
+ MCP_PROXY_CONFIG,
1361
+ CUSTOM_CODE_CONFIG,
1362
+ CUSTOM_CODE_CONFIG_WRITE,
1363
+ CUSTOM_CODE_TOOL,
1364
+ CUSTOM_CODE_MANIFEST,
1365
+ ARTIFACT_CUSTOM_CODE_CREATE_VERSION,
1366
+ ARTIFACT_CUSTOM_CODE_UPLOAD_BUNDLE,
1367
+ ARTIFACT_CUSTOM_CODE_GET_SOURCE,
1368
+ ARTIFACT_CUSTOM_CODE_TEST_VERSION,
1369
+ ARTIFACT_CUSTOM_CODE_PUBLISH,
1370
+ ARTIFACT_CUSTOM_CODE_ROLLBACK,
1371
+ ARTIFACT_CUSTOM_CODE_LIST_LOGS,
1372
+ ARTIFACT_CUSTOM_CODE_LIST_VERSIONS,
1373
+ CUSTOM_CODE_INVOKE_REQUEST,
1374
+ CUSTOM_CODE_INVOKE_RESPONSE,
1375
+ CUSTOM_CODE_BROKER_CONNECTION,
1376
+ CUSTOM_CODE_BROKER_SECRET,
1377
+ CUSTOM_CODE_BROKER_RESOURCE_SEARCH,
1378
+ CUSTOM_CODE_BROKER_RESOURCE_READ,
1379
+ CUSTOM_CODE_BROKER_RESOURCE_CREATE,
1380
+ CUSTOM_CODE_BROKER_RESOURCE_DELETE,
1381
+ CUSTOM_CODE_SEND_FILE
1382
+ };