@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
@@ -0,0 +1,2542 @@
1
+ import {
2
+ CLI_OAUTH_CLIENT_NAME,
3
+ CLI_OAUTH_REDIRECT_PATH,
4
+ CLI_OAUTH_REDIRECT_PORTS,
5
+ CLI_OAUTH_SCOPES,
6
+ CLI_TOKEN_REFRESH_SKEW_SECONDS,
7
+ CUSTOM_CODE_SDK_SPECIFIER,
8
+ CUSTOM_CODE_MAX_BUNDLE_BYTES,
9
+ CREDENTIAL_PROVIDER_CUSTOM_CODE
10
+ } from './cliConstants';
11
+ import {
12
+ CUSTOM_CODE_BINDING_TOKEN,
13
+ CUSTOM_CODE_BINDING_BROKER,
14
+ CUSTOM_CODE_BROKER_ORIGIN,
15
+ CUSTOM_CODE_BROKER_PATH_CONNECTION,
16
+ CUSTOM_CODE_BROKER_PATH_SECRET,
17
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_SEARCH,
18
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_READ,
19
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_LIST,
20
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_CREATE,
21
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_DELETE,
22
+ CUSTOM_CODE_BROKER_PATH_SEND_FILE,
23
+ CUSTOM_CODE_HEALTH_TOOL,
24
+ CUSTOM_CODE_MAX_LOGS,
25
+ CUSTOM_CODE_MAX_LOG_LENGTH
26
+ } from './sdkConstants';
27
+
28
+ const USER_ROLE_ADMIN = 'ADMIN';
29
+ const USER_ROLES = [USER_ROLE_ADMIN];
30
+
31
+ const STATUS_COMPLETED = 'COMPLETED';
32
+ const STATUS_PENDING = 'PENDING';
33
+ const STATUS_FAILED = 'FAILED';
34
+ const STATUS_ACTIVE = 'ACTIVE';
35
+ const STATUS_DISABLED = 'DISABLED';
36
+ const STATUS_BLOCKED = 'BLOCKED';
37
+ const STATUS = [
38
+ STATUS_COMPLETED,
39
+ STATUS_PENDING,
40
+ STATUS_FAILED,
41
+ STATUS_ACTIVE,
42
+ STATUS_DISABLED,
43
+ STATUS_BLOCKED
44
+ ];
45
+ const USER_STATUS = [STATUS_ACTIVE, STATUS_BLOCKED];
46
+ const CHANNEL_STATUS = [STATUS_ACTIVE, STATUS_DISABLED];
47
+ const RESOURCE_STATUS = [STATUS_PENDING, STATUS_COMPLETED, STATUS_FAILED];
48
+ const INVITATION_STATUS = [STATUS_PENDING, STATUS_COMPLETED, STATUS_DISABLED];
49
+
50
+ const INVITATION_SCOPE_ORGANIZATION = 'ORGANIZATION' as 'ORGANIZATION';
51
+ const INVITATION_SCOPE_PROJECT = 'PROJECT' as 'PROJECT';
52
+ const INVITATION_SCOPES = [
53
+ INVITATION_SCOPE_ORGANIZATION,
54
+ INVITATION_SCOPE_PROJECT
55
+ ];
56
+
57
+ const INVITATION_RESPONSE_ACCEPT = 'accept' as 'accept';
58
+ const INVITATION_RESPONSE_DECLINE = 'decline' as 'decline';
59
+ const INVITATION_RESPONSES = [
60
+ INVITATION_RESPONSE_ACCEPT,
61
+ INVITATION_RESPONSE_DECLINE
62
+ ];
63
+
64
+ const INVITATION_EXPIRY_DAYS = 7;
65
+ const INVITATION_TOKEN_BYTES = 24;
66
+
67
+ // Contact form (marketing site → /contact API endpoint) field limits.
68
+ const CONTACT_MAX_NAME_LENGTH = 200;
69
+ const CONTACT_MAX_EMAIL_LENGTH = 254;
70
+ const CONTACT_MIN_MESSAGE_LENGTH = 5;
71
+ const CONTACT_MAX_MESSAGE_LENGTH = 5000;
72
+
73
+ const SLUG_BYTES = 16;
74
+ const SLUG_PATTERN = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
75
+
76
+ const SERVICE_NAME_API = 'api';
77
+ const SERVICE_NAME_MCP = 'mcp';
78
+ const SERVICE_NAME_RESOURCE_HANDLER = 'resource_handler';
79
+ // The custom-code broker. Its own label rather than reusing 'mcp': a failure
80
+ // here is a connection or secret that user code could not obtain, which is a
81
+ // different thing to debug from an MCP request that went wrong.
82
+ const SERVICE_NAME_TOOL_BROKER = 'tool_broker';
83
+ const SERVICE_NAMES = [
84
+ SERVICE_NAME_API,
85
+ SERVICE_NAME_MCP,
86
+ SERVICE_NAME_RESOURCE_HANDLER,
87
+ SERVICE_NAME_TOOL_BROKER
88
+ ];
89
+
90
+ const SOCIAL_PROVIDER_GOOGLE = 'google' as 'google';
91
+ const SOCIAL_PROVIDER_GITHUB = 'github' as 'github';
92
+ const SOCIAL_PROVIDERS = [SOCIAL_PROVIDER_GOOGLE, SOCIAL_PROVIDER_GITHUB];
93
+
94
+ const OAUTH_PROVIDER_GOOGLE_GMAIL = 'google-gmail' as 'google-gmail';
95
+ const OAUTH_PROVIDER_GOOGLE_DRIVE = 'google-drive' as 'google-drive';
96
+ const OAUTH_PROVIDER_GOOGLE_CALENDAR = 'google-calendar' as 'google-calendar';
97
+ const OAUTH_PROVIDER_MICROSOFT_OUTLOOK =
98
+ 'microsoft-outlook' as 'microsoft-outlook';
99
+ const OAUTH_PROVIDER_ONE_DRIVE = 'microsoft-onedrive' as 'microsoft-onedrive';
100
+ const OAUTH_PROVIDER_SLACK = 'slack' as 'slack';
101
+ const OAUTH_PROVIDER_SLACK_USER = 'slack-user' as 'slack-user';
102
+ const OAUTH_PROVIDERS = [
103
+ OAUTH_PROVIDER_GOOGLE_GMAIL,
104
+ OAUTH_PROVIDER_GOOGLE_DRIVE,
105
+ OAUTH_PROVIDER_GOOGLE_CALENDAR,
106
+ OAUTH_PROVIDER_MICROSOFT_OUTLOOK,
107
+ OAUTH_PROVIDER_ONE_DRIVE,
108
+ OAUTH_PROVIDER_SLACK,
109
+ OAUTH_PROVIDER_SLACK_USER
110
+ ];
111
+
112
+ const GOOGLE_OAUTH_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
113
+ const MICROSOFT_OAUTH_AUTH_URL =
114
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize';
115
+ const SLACK_OAUTH_AUTH_URL = 'https://slack.com/oauth/v2/authorize';
116
+ const OAUTH_AUTH_URLS: Record<string, string> = {
117
+ [OAUTH_PROVIDER_GOOGLE_GMAIL]: GOOGLE_OAUTH_AUTH_URL,
118
+ [OAUTH_PROVIDER_GOOGLE_DRIVE]: GOOGLE_OAUTH_AUTH_URL,
119
+ [OAUTH_PROVIDER_GOOGLE_CALENDAR]: GOOGLE_OAUTH_AUTH_URL,
120
+ [OAUTH_PROVIDER_MICROSOFT_OUTLOOK]: MICROSOFT_OAUTH_AUTH_URL,
121
+ [OAUTH_PROVIDER_ONE_DRIVE]: MICROSOFT_OAUTH_AUTH_URL,
122
+ [OAUTH_PROVIDER_SLACK]: SLACK_OAUTH_AUTH_URL,
123
+ [OAUTH_PROVIDER_SLACK_USER]: SLACK_OAUTH_AUTH_URL
124
+ };
125
+
126
+ const GOOGLE_OAUTH_TOKEN_URL = 'https://oauth2.googleapis.com/token';
127
+ const MICROSOFT_OAUTH_TOKEN_URL =
128
+ 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
129
+ const SLACK_OAUTH_TOKEN_URL = 'https://slack.com/api/oauth.v2.access';
130
+ const OAUTH_TOKEN_URLS: Record<string, string> = {
131
+ [OAUTH_PROVIDER_GOOGLE_GMAIL]: GOOGLE_OAUTH_TOKEN_URL,
132
+ [OAUTH_PROVIDER_GOOGLE_DRIVE]: GOOGLE_OAUTH_TOKEN_URL,
133
+ [OAUTH_PROVIDER_GOOGLE_CALENDAR]: GOOGLE_OAUTH_TOKEN_URL,
134
+ [OAUTH_PROVIDER_MICROSOFT_OUTLOOK]: MICROSOFT_OAUTH_TOKEN_URL,
135
+ [OAUTH_PROVIDER_ONE_DRIVE]: MICROSOFT_OAUTH_TOKEN_URL,
136
+ [OAUTH_PROVIDER_SLACK]: SLACK_OAUTH_TOKEN_URL,
137
+ [OAUTH_PROVIDER_SLACK_USER]: SLACK_OAUTH_TOKEN_URL
138
+ };
139
+
140
+ // Refresh an OAuth token when less than this remains, so a long call doesn't
141
+ // expire mid-flight. Shared by every credential-refresh path (native OAuth and
142
+ // MCP-proxy OAuth, in both apps/api and apps/mcp).
143
+ const CREDENTIAL_REFRESH_BUFFER_MS = 60 * 1000;
144
+
145
+ const GOOGLE_CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3';
146
+
147
+ const GOOGLE_DRIVE_API_BASE = 'https://www.googleapis.com/drive/v3';
148
+ const GOOGLE_DRIVE_DEFAULT_PAGE_SIZE = 200;
149
+ const GOOGLE_DRIVE_MAX_FOLDER_PAGES = 50;
150
+ const GOOGLE_DRIVE_TOKEN_REFRESH_LEEWAY_MS = 60_000;
151
+ const GOOGLE_DRIVE_URI_PREFIX = 'gdrive://files/';
152
+ const GOOGLE_DRIVE_FILE_FIELDS =
153
+ 'id,name,mimeType,modifiedTime,createdTime,size,md5Checksum,version,parents,owners(emailAddress,displayName),webViewLink,iconLink,description,trashed,capabilities/canDownload';
154
+ const GOOGLE_DRIVE_LIST_FIELDS = `nextPageToken,files(${GOOGLE_DRIVE_FILE_FIELDS})`;
155
+
156
+ const GOOGLE_DRIVE_TAB_MY_DRIVE = 'my-drive' as 'my-drive';
157
+ const GOOGLE_DRIVE_TAB_SHARED_WITH_ME = 'shared-with-me' as 'shared-with-me';
158
+ const GOOGLE_DRIVE_TAB_SHARED_DRIVES = 'shared-drives' as 'shared-drives';
159
+ const GOOGLE_DRIVE_TAB_STARRED = 'starred' as 'starred';
160
+ const GOOGLE_DRIVE_TABS = [
161
+ GOOGLE_DRIVE_TAB_MY_DRIVE,
162
+ GOOGLE_DRIVE_TAB_SHARED_WITH_ME,
163
+ GOOGLE_DRIVE_TAB_SHARED_DRIVES,
164
+ GOOGLE_DRIVE_TAB_STARRED
165
+ ];
166
+ const GOOGLE_DRIVE_TAB_LABEL_MY_DRIVE = 'My Drive';
167
+ const GOOGLE_DRIVE_TAB_LABEL_SHARED_WITH_ME = 'Shared with me';
168
+ const GOOGLE_DRIVE_TAB_LABEL_SHARED_DRIVES = 'Shared drives';
169
+ const GOOGLE_DRIVE_TAB_LABEL_STARRED = 'Starred';
170
+ const GOOGLE_DRIVE_TAB_LABELS: ReadonlyArray<{
171
+ value:
172
+ | typeof GOOGLE_DRIVE_TAB_MY_DRIVE
173
+ | typeof GOOGLE_DRIVE_TAB_SHARED_WITH_ME
174
+ | typeof GOOGLE_DRIVE_TAB_SHARED_DRIVES
175
+ | typeof GOOGLE_DRIVE_TAB_STARRED;
176
+ label: string;
177
+ }> = [
178
+ { value: GOOGLE_DRIVE_TAB_MY_DRIVE, label: GOOGLE_DRIVE_TAB_LABEL_MY_DRIVE },
179
+ {
180
+ value: GOOGLE_DRIVE_TAB_SHARED_WITH_ME,
181
+ label: GOOGLE_DRIVE_TAB_LABEL_SHARED_WITH_ME
182
+ },
183
+ {
184
+ value: GOOGLE_DRIVE_TAB_SHARED_DRIVES,
185
+ label: GOOGLE_DRIVE_TAB_LABEL_SHARED_DRIVES
186
+ },
187
+ { value: GOOGLE_DRIVE_TAB_STARRED, label: GOOGLE_DRIVE_TAB_LABEL_STARRED }
188
+ ];
189
+
190
+ const MICROSOFT_GRAPH_API_BASE = 'https://graph.microsoft.com/v1.0';
191
+ const ONE_DRIVE_DEFAULT_PAGE_SIZE = 200;
192
+ const ONE_DRIVE_MAX_FOLDER_PAGES = 50;
193
+ const ONE_DRIVE_TOKEN_REFRESH_LEEWAY_MS = 60_000;
194
+ const ONE_DRIVE_URI_PREFIX = 'onedrive://files/';
195
+ const ONE_DRIVE_ITEM_SELECT =
196
+ 'id,name,size,webUrl,createdDateTime,lastModifiedDateTime,eTag,cTag,file,folder,parentReference,deleted,@microsoft.graph.downloadUrl';
197
+
198
+ const ONE_DRIVE_TAB_MY_FILES = 'my-files' as 'my-files';
199
+ const ONE_DRIVE_TAB_SHARED_WITH_ME = 'shared-with-me' as 'shared-with-me';
200
+ const ONE_DRIVE_TAB_RECENT = 'recent' as 'recent';
201
+ const ONE_DRIVE_TAB_DRIVES = 'drives' as 'drives';
202
+ const ONE_DRIVE_TABS = [
203
+ ONE_DRIVE_TAB_MY_FILES,
204
+ ONE_DRIVE_TAB_SHARED_WITH_ME,
205
+ ONE_DRIVE_TAB_RECENT,
206
+ ONE_DRIVE_TAB_DRIVES
207
+ ];
208
+ const ONE_DRIVE_TAB_LABEL_MY_FILES = 'My files';
209
+ const ONE_DRIVE_TAB_LABEL_SHARED_WITH_ME = 'Shared with me';
210
+ const ONE_DRIVE_TAB_LABEL_RECENT = 'Recent';
211
+ const ONE_DRIVE_TAB_LABEL_DRIVES = 'Drives';
212
+ const ONE_DRIVE_TAB_LABELS: ReadonlyArray<{
213
+ value:
214
+ | typeof ONE_DRIVE_TAB_MY_FILES
215
+ | typeof ONE_DRIVE_TAB_SHARED_WITH_ME
216
+ | typeof ONE_DRIVE_TAB_RECENT
217
+ | typeof ONE_DRIVE_TAB_DRIVES;
218
+ label: string;
219
+ }> = [
220
+ { value: ONE_DRIVE_TAB_MY_FILES, label: ONE_DRIVE_TAB_LABEL_MY_FILES },
221
+ {
222
+ value: ONE_DRIVE_TAB_SHARED_WITH_ME,
223
+ label: ONE_DRIVE_TAB_LABEL_SHARED_WITH_ME
224
+ },
225
+ { value: ONE_DRIVE_TAB_RECENT, label: ONE_DRIVE_TAB_LABEL_RECENT },
226
+ { value: ONE_DRIVE_TAB_DRIVES, label: ONE_DRIVE_TAB_LABEL_DRIVES }
227
+ ];
228
+
229
+ const REAUTH_ERROR_CODES = ['invalid_grant', 'invalid_token'];
230
+
231
+ const RESOURCE_TYPE_TOOLTIP_MIN_WIDTH = 300;
232
+
233
+ const SEARCH_DEBOUNCE_MS = 300;
234
+
235
+ const SCHEMA_DEFINITION_TYPES = [
236
+ 'string',
237
+ 'number',
238
+ 'boolean',
239
+ 'object',
240
+ 'array'
241
+ ];
242
+
243
+ const ROLE_MESSAGE_USER = 'user' as 'user';
244
+ const ROLE_MESSAGE_ASSISTANT = 'assistant' as 'assistant';
245
+ const ROLE_MESSAGE_SYSTEM = 'system' as 'system';
246
+ const ROLE_MESSAGE_TOOL = 'tool' as 'tool';
247
+ const ROLE_MESSAGES = [ROLE_MESSAGE_USER, ROLE_MESSAGE_ASSISTANT];
248
+ const CHANNEL_ROLE_MESSAGES = [
249
+ ROLE_MESSAGE_USER,
250
+ ROLE_MESSAGE_ASSISTANT,
251
+ ROLE_MESSAGE_SYSTEM,
252
+ ROLE_MESSAGE_TOOL
253
+ ];
254
+
255
+ const RESOURCE_TYPE_STATIC = 'static' as 'static';
256
+ const RESOURCE_TYPE_TEMPLATE = 'template' as 'template';
257
+ const RESOURCE_TYPES = [RESOURCE_TYPE_STATIC, RESOURCE_TYPE_TEMPLATE];
258
+
259
+ const RESOURCE_SOURCE_TYPE_FILE = 'FILE' as 'FILE';
260
+ const RESOURCE_SOURCE_TYPE_WEBSITE = 'WEBSITE' as 'WEBSITE';
261
+ const RESOURCE_SOURCE_TYPE_GOOGLE_DRIVE_FOLDER =
262
+ 'GOOGLE_DRIVE_FOLDER' as 'GOOGLE_DRIVE_FOLDER';
263
+ const RESOURCE_SOURCE_TYPE_ONE_DRIVE_FOLDER =
264
+ 'ONE_DRIVE_FOLDER' as 'ONE_DRIVE_FOLDER';
265
+ // Written by a user script through the broker, and by nothing else. This is a
266
+ // provenance marker before it is a category: a script may replace a resource
267
+ // carrying it and may not touch any other row, which is what stops a tool from
268
+ // overwriting a document its owner uploaded. Nothing in the dashboard or the
269
+ // upload paths ever sets it.
270
+ const RESOURCE_SOURCE_TYPE_CUSTOM_CODE = 'CUSTOM_CODE' as 'CUSTOM_CODE';
271
+ const RESOURCE_SOURCE_TYPES = [
272
+ RESOURCE_SOURCE_TYPE_FILE,
273
+ RESOURCE_SOURCE_TYPE_WEBSITE,
274
+ RESOURCE_SOURCE_TYPE_GOOGLE_DRIVE_FOLDER,
275
+ RESOURCE_SOURCE_TYPE_ONE_DRIVE_FOLDER,
276
+ RESOURCE_SOURCE_TYPE_CUSTOM_CODE
277
+ ];
278
+
279
+ const CRAWL_RENDERER_CHEERIO = 'cheerio' as 'cheerio';
280
+ const CRAWL_RENDERER_PLAYWRIGHT = 'playwright' as 'playwright';
281
+ const CRAWL_RENDERERS = [CRAWL_RENDERER_CHEERIO, CRAWL_RENDERER_PLAYWRIGHT];
282
+
283
+ const CRAWL_DEFAULT_MAX_PAGES = 100;
284
+ const CRAWL_MAX_PAGES_LIMIT = 1000;
285
+ const CRAWL_DEFAULT_MAX_DEPTH = 3;
286
+ const CRAWL_MAX_DEPTH_LIMIT = 10;
287
+ const CRAWL_PAGE_FETCH_TIMEOUT_MS = 30000;
288
+ const CRAWL_USER_AGENT =
289
+ 'Mozilla/5.0 (compatible; GanjuCrawler/1.0; +https://ganju.ai)';
290
+ const CRAWL_PAGE_QUEUE_BATCH_SIZE = 100;
291
+
292
+ const RESOURCE_ICON_THEME_DARK = 'dark';
293
+ const RESOURCE_ICON_THEME_LIGHT = 'light';
294
+ const RESOURCE_ICON_THEMES = [
295
+ RESOURCE_ICON_THEME_DARK,
296
+ RESOURCE_ICON_THEME_LIGHT
297
+ ];
298
+
299
+ const ENCODING_UTF8 = 'utf-8' as 'utf-8';
300
+ const ENCODINGS = [
301
+ ENCODING_UTF8,
302
+ 'ascii',
303
+ 'base64',
304
+ 'latin1',
305
+ 'utf-16le',
306
+ 'binary'
307
+ ];
308
+
309
+ const LANGUAGE_EN = 'en';
310
+ const LANGUAGE_ES = 'es';
311
+ const LANGUAGES = [LANGUAGE_EN, LANGUAGE_ES];
312
+
313
+ /**
314
+ * Countries where Spanish is the (or an) official language.
315
+ *
316
+ * Location, not `Accept-Language`, is what picks a language for a first-time
317
+ * visitor: the Spanish pages carry Colombian legal content (Ley 1480, Ley
318
+ * 1581), so where someone is matters more than how their browser happens to be
319
+ * configured. Both surfaces read this list — the website's `/` Pages Function
320
+ * and the dashboard's middleware — so a visitor who lands on ganju.ai and then
321
+ * signs in at app.ganju.ai gets the same language on both.
322
+ */
323
+ const SPANISH_COUNTRIES = [
324
+ 'AR',
325
+ 'BO',
326
+ 'CL',
327
+ 'CO',
328
+ 'CR',
329
+ 'CU',
330
+ 'DO',
331
+ 'EC',
332
+ 'ES',
333
+ 'GQ',
334
+ 'GT',
335
+ 'HN',
336
+ 'MX',
337
+ 'NI',
338
+ 'PA',
339
+ 'PE',
340
+ 'PR',
341
+ 'PY',
342
+ 'SV',
343
+ 'UY',
344
+ 'VE'
345
+ ];
346
+
347
+ /**
348
+ * Where an explicit language choice is remembered. The website and the
349
+ * dashboard sit on different hosts under one registrable domain, and the cookie
350
+ * is scoped to that domain (see `languageCookieDomain`) so a choice made on
351
+ * ganju.ai is still honoured after signing in at app.ganju.ai.
352
+ */
353
+ const LANGUAGE_COOKIE = 'ganju_lang';
354
+ const LANGUAGE_COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
355
+ /** Query parameter that carries an explicit switch, e.g. `?lang=es`. */
356
+ const LANGUAGE_QUERY_PARAM = 'lang';
357
+
358
+ const MIMETYPE_TEXT = 'text/plain' as 'text/plain';
359
+ const MIMETYPE_TEXT_CSV = 'text/csv' as 'text/csv';
360
+ const MIMETYPE_TEXT_HTML = 'text/html' as 'text/html';
361
+ const MIMETYPE_TEXT_MARKDOWN = 'text/markdown' as 'text/markdown';
362
+ const MIMETYPE_IMAGE_PNG = 'image/png' as 'image/png';
363
+ const MIMETYPE_IMAGE_GIF = 'image/gif' as 'image/gif';
364
+ const MIMETYPE_IMAGE_JPEG = 'image/jpeg' as 'image/jpeg';
365
+ const MIMETYPE_IMAGE_WEBP = 'image/webp' as 'image/webp';
366
+ const MIMETYPE_IMAGE_SVG_XML = 'image/svg+xml' as 'image/svg+xml';
367
+ const MIMETYPE_APPLICATION_PDF = 'application/pdf' as 'application/pdf';
368
+ const MIMETYPE_APPLICATION_JSON = 'application/json' as 'application/json';
369
+ const MIMETYPE_APPLICATION_MSWORD =
370
+ 'application/msword' as 'application/msword';
371
+ const MIMETYPE_APPLICATION_VND_MS_EXCEL =
372
+ 'application/vnd.ms-excel' as 'application/vnd.ms-excel';
373
+ const MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET =
374
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' as 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
375
+ const MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT =
376
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' as 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
377
+ const MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION =
378
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation' as 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
379
+ const MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT =
380
+ 'application/vnd.google-apps.document' as 'application/vnd.google-apps.document';
381
+ const MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET =
382
+ 'application/vnd.google-apps.spreadsheet' as 'application/vnd.google-apps.spreadsheet';
383
+ const MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION =
384
+ 'application/vnd.google-apps.presentation' as 'application/vnd.google-apps.presentation';
385
+ const MIMETYPE_APPLICATION_VND_GOOGLE_APPS_FOLDER =
386
+ 'application/vnd.google-apps.folder' as 'application/vnd.google-apps.folder';
387
+ const MIMETYPE_APPLICATION_XML = 'application/xml' as 'application/xml';
388
+ const MIMETYPE_APPLICATION_JAVASCRIPT =
389
+ 'application/javascript' as 'application/javascript';
390
+ const MIMETYPE_APPLICATION_TYPESCRIPT =
391
+ 'application/typescript' as 'application/typescript';
392
+ const MIMETYPE_APPLICATION_YAML = 'application/yaml' as 'application/yaml';
393
+ const MIMETYPE_APPLICATION_X_YAML =
394
+ 'application/x-yaml' as 'application/x-yaml';
395
+ const MIMETYPE_APPLICATION_TOML = 'application/toml' as 'application/toml';
396
+ const MIMETYPE_APPLICATION_X_SH = 'application/x-sh' as 'application/x-sh';
397
+ const MIMETYPE_APPLICATION_SQL = 'application/sql' as 'application/sql';
398
+ const MIMETYPE_APPLICATION_GRAPHQL =
399
+ 'application/graphql' as 'application/graphql';
400
+ const MIMETYPE_APPLICATION_LD_JSON =
401
+ 'application/ld+json' as 'application/ld+json';
402
+ const MIMETYPE_APPLICATION_XHTML_XML =
403
+ 'application/xhtml+xml' as 'application/xhtml+xml';
404
+ const MIMETYPE_APPLICATION_X_HTTPD_PHP =
405
+ 'application/x-httpd-php' as 'application/x-httpd-php';
406
+ const MIMETYPE_APPLICATION_X_PYTHON_CODE =
407
+ 'application/x-python-code' as 'application/x-python-code';
408
+ const MIMETYPE_APPLICATION_X_WWW_FORM_URLENCODED =
409
+ 'application/x-www-form-urlencoded' as 'application/x-www-form-urlencoded';
410
+ const MIMETYPE_APPLICATION_CSV = 'application/csv' as 'application/csv';
411
+ const MIMETYPE_APPLICATION_X_RUBY =
412
+ 'application/x-ruby' as 'application/x-ruby';
413
+ const MIMETYPE_APPLICATION_X_PERL =
414
+ 'application/x-perl' as 'application/x-perl';
415
+ const MIMETYPE_APPLICATION_OCTET_STREAM =
416
+ 'application/octet-stream' as 'application/octet-stream';
417
+ const MIMETYPES = [
418
+ MIMETYPE_TEXT,
419
+ MIMETYPE_TEXT_CSV,
420
+ MIMETYPE_TEXT_HTML,
421
+ MIMETYPE_TEXT_MARKDOWN,
422
+ MIMETYPE_IMAGE_PNG,
423
+ MIMETYPE_IMAGE_GIF,
424
+ MIMETYPE_IMAGE_JPEG,
425
+ MIMETYPE_IMAGE_WEBP,
426
+ MIMETYPE_IMAGE_SVG_XML,
427
+ MIMETYPE_APPLICATION_PDF,
428
+ MIMETYPE_APPLICATION_JSON,
429
+ MIMETYPE_APPLICATION_MSWORD,
430
+ MIMETYPE_APPLICATION_VND_MS_EXCEL,
431
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET,
432
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT,
433
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION,
434
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT,
435
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET,
436
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION,
437
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_FOLDER,
438
+ MIMETYPE_APPLICATION_XML,
439
+ MIMETYPE_APPLICATION_JAVASCRIPT,
440
+ MIMETYPE_APPLICATION_TYPESCRIPT,
441
+ MIMETYPE_APPLICATION_YAML,
442
+ MIMETYPE_APPLICATION_X_YAML,
443
+ MIMETYPE_APPLICATION_TOML,
444
+ MIMETYPE_APPLICATION_X_SH,
445
+ MIMETYPE_APPLICATION_SQL,
446
+ MIMETYPE_APPLICATION_GRAPHQL,
447
+ MIMETYPE_APPLICATION_LD_JSON,
448
+ MIMETYPE_APPLICATION_XHTML_XML,
449
+ MIMETYPE_APPLICATION_X_HTTPD_PHP,
450
+ MIMETYPE_APPLICATION_X_PYTHON_CODE,
451
+ MIMETYPE_APPLICATION_X_WWW_FORM_URLENCODED,
452
+ MIMETYPE_APPLICATION_CSV,
453
+ MIMETYPE_APPLICATION_X_RUBY,
454
+ MIMETYPE_APPLICATION_X_PERL,
455
+ MIMETYPE_APPLICATION_OCTET_STREAM
456
+ ];
457
+ // Sensible file extension for a mime type, used to name a downloaded/forwarded
458
+ // file (e.g. a proxied MCP resource sent into a channel) when the source has no
459
+ // usable filename. Not exhaustive — callers fall back to .bin / .txt.
460
+ const EXTENSION_BY_MIME: Record<string, string> = {
461
+ [MIMETYPE_TEXT]: 'txt',
462
+ [MIMETYPE_TEXT_CSV]: 'csv',
463
+ [MIMETYPE_TEXT_HTML]: 'html',
464
+ [MIMETYPE_TEXT_MARKDOWN]: 'md',
465
+ [MIMETYPE_APPLICATION_JSON]: 'json',
466
+ [MIMETYPE_APPLICATION_PDF]: 'pdf',
467
+ [MIMETYPE_APPLICATION_XML]: 'xml',
468
+ [MIMETYPE_IMAGE_PNG]: 'png',
469
+ [MIMETYPE_IMAGE_JPEG]: 'jpg',
470
+ [MIMETYPE_IMAGE_GIF]: 'gif',
471
+ [MIMETYPE_IMAGE_WEBP]: 'webp',
472
+ [MIMETYPE_IMAGE_SVG_XML]: 'svg'
473
+ };
474
+ const TEXT_MIME_TYPES = [
475
+ MIMETYPE_TEXT,
476
+ MIMETYPE_TEXT_CSV,
477
+ MIMETYPE_TEXT_HTML,
478
+ MIMETYPE_APPLICATION_JSON,
479
+ MIMETYPE_APPLICATION_XML,
480
+ MIMETYPE_APPLICATION_JAVASCRIPT,
481
+ MIMETYPE_APPLICATION_TYPESCRIPT,
482
+ MIMETYPE_APPLICATION_YAML,
483
+ MIMETYPE_APPLICATION_X_YAML,
484
+ MIMETYPE_APPLICATION_TOML,
485
+ MIMETYPE_APPLICATION_X_SH,
486
+ MIMETYPE_APPLICATION_SQL,
487
+ MIMETYPE_APPLICATION_GRAPHQL,
488
+ MIMETYPE_APPLICATION_LD_JSON,
489
+ MIMETYPE_APPLICATION_XHTML_XML,
490
+ MIMETYPE_APPLICATION_X_HTTPD_PHP,
491
+ MIMETYPE_APPLICATION_X_PYTHON_CODE,
492
+ MIMETYPE_APPLICATION_X_WWW_FORM_URLENCODED,
493
+ MIMETYPE_APPLICATION_CSV,
494
+ MIMETYPE_APPLICATION_X_RUBY,
495
+ MIMETYPE_APPLICATION_X_PERL
496
+ ];
497
+ const EMBEDDABLE_MIME_TYPES = [
498
+ ...TEXT_MIME_TYPES,
499
+ MIMETYPE_APPLICATION_PDF,
500
+ MIMETYPE_APPLICATION_MSWORD,
501
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT,
502
+ MIMETYPE_APPLICATION_VND_MS_EXCEL,
503
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET,
504
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION,
505
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT,
506
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET,
507
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION
508
+ ];
509
+
510
+ const GOOGLE_DRIVE_EXPORT_MIME_TYPES: Record<string, string> = {
511
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT]:
512
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT,
513
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET]:
514
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET,
515
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION]:
516
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION
517
+ };
518
+
519
+ const GOOGLE_DRIVE_EXPORT_EXTENSIONS: Record<string, string> = {
520
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT]: 'docx',
521
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET]: 'xlsx',
522
+ [MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION]: 'pptx'
523
+ };
524
+ const USER_AVATAR_MIME_TYPES = [
525
+ MIMETYPE_IMAGE_PNG,
526
+ MIMETYPE_IMAGE_JPEG,
527
+ MIMETYPE_IMAGE_WEBP,
528
+ MIMETYPE_IMAGE_GIF
529
+ ];
530
+
531
+ const MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MB
532
+ const MAX_AVATAR_SIZE = 5 * 1024 * 1024; // 5MB
533
+
534
+ // Gmail caps a sent message at 25MB encoded. Combined raw attachment size
535
+ // must stay under ~18MB so the base64 multipart envelope fits.
536
+ const GMAIL_MAX_RAW_ATTACHMENT_BYTES = 18 * 1024 * 1024;
537
+
538
+ // Microsoft Graph: attachments ≤3MB go inline in the message JSON; larger
539
+ // ones need createUploadSession + chunked PUT. We enforce a per-attachment
540
+ // cap so a single huge file fails fast instead of after a partial upload.
541
+ const OUTLOOK_ATTACHMENT_INLINE_THRESHOLD = 3 * 1024 * 1024;
542
+ const OUTLOOK_MAX_ATTACHMENT_BYTES = 150 * 1024 * 1024;
543
+ const OUTLOOK_UPLOAD_CHUNK_BYTES = 4 * 1024 * 1024;
544
+
545
+ // Slack Web API base. Per-file cap for files.uploadV2 — Slack's hard limit
546
+ // is 1GB but anything that large should be a shared link, not an upload,
547
+ // and 100MB keeps memory pressure on the resource-handler container sane.
548
+ const SLACK_API_BASE = 'https://slack.com/api';
549
+ const SLACK_MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
550
+
551
+ // Telegram Bot API per-method file caps (sendPhoto vs sendDocument differ).
552
+ const TELEGRAM_MAX_PHOTO_BYTES = 10 * 1024 * 1024;
553
+ const TELEGRAM_MAX_FILE_BYTES = 50 * 1024 * 1024;
554
+
555
+ const RATE_LIMIT_BACKOFF_SECONDS = 60;
556
+
557
+ // Short backoff for transient Cloudflare platform errors (e.g. a Durable Object
558
+ // / Container reset) so the retried job lands after the object has recovered.
559
+ const TRANSIENT_BACKOFF_SECONDS = 5;
560
+
561
+ const CHANNEL_PLATFORM_TELEGRAM = 'telegram' as 'telegram';
562
+ const CHANNEL_PLATFORM_SLACK = 'slack' as 'slack';
563
+ const CHANNEL_PLATFORM_WHATSAPP = 'whatsapp' as 'whatsapp';
564
+ const CHANNEL_PLATFORM_DISCORD = 'discord' as 'discord';
565
+ const CHANNEL_PLATFORMS = [
566
+ CHANNEL_PLATFORM_TELEGRAM,
567
+ CHANNEL_PLATFORM_SLACK,
568
+ CHANNEL_PLATFORM_WHATSAPP,
569
+ CHANNEL_PLATFORM_DISCORD
570
+ ];
571
+
572
+ const CHANNEL_CONVERSATION_SCOPE_PRIVATE = 'private' as 'private';
573
+ const CHANNEL_CONVERSATION_SCOPE_GROUP = 'group' as 'group';
574
+ const CHANNEL_CONVERSATION_SCOPE_CHANNEL = 'channel' as 'channel';
575
+ const CHANNEL_CONVERSATION_SCOPES = [
576
+ CHANNEL_CONVERSATION_SCOPE_PRIVATE,
577
+ CHANNEL_CONVERSATION_SCOPE_GROUP,
578
+ CHANNEL_CONVERSATION_SCOPE_CHANNEL
579
+ ];
580
+
581
+ // Kinds of artifact usage — what was exercised on a request. Shared by channel
582
+ // message usage and the artifact_execution audit trail (who ran what, when).
583
+ const USAGE_KIND_PROMPT = 'prompt' as 'prompt';
584
+ const USAGE_KIND_RESOURCE = 'resource' as 'resource';
585
+ const USAGE_KIND_TOOL = 'tool' as 'tool';
586
+ const USAGE_KINDS = [USAGE_KIND_PROMPT, USAGE_KIND_RESOURCE, USAGE_KIND_TOOL];
587
+
588
+ const LLM_PROVIDER_ANTHROPIC = 'anthropic' as 'anthropic';
589
+ const LLM_PROVIDER_OPENAI = 'openai' as 'openai';
590
+ const LLM_PROVIDER_OPENAI_COMPATIBLE =
591
+ 'openai-compatible' as 'openai-compatible';
592
+ const LLM_PROVIDER_GOOGLE = 'google' as 'google';
593
+ const LLM_PROVIDERS = [
594
+ LLM_PROVIDER_ANTHROPIC,
595
+ LLM_PROVIDER_OPENAI,
596
+ LLM_PROVIDER_OPENAI_COMPATIBLE,
597
+ LLM_PROVIDER_GOOGLE
598
+ ];
599
+
600
+ const DEFAULT_LLM_PROVIDER = LLM_PROVIDER_GOOGLE;
601
+ // ⚠️ This is the SHARED platform model — every Free turn and every paid turn on
602
+ // an org with no LLM of its own runs here, and WE pay that inference. The whole
603
+ // plan economics rest on Flash-Lite's rate ($0.25/M in · $1.50/M out): the
604
+ // planning figure of ~$0.004 per channel turn, Free's ~$0.41/month worst case,
605
+ // and the shared-key overage rate all derive from it. Moving to Gemini 3 Flash
606
+ // doubles every one of those; a Sonnet-class model is ~15×. Treat a change here
607
+ // as a pricing change, not a config tweak — the plan rates have to be redone
608
+ // alongside it.
609
+ const DEFAULT_LLM_MODEL = 'gemini-3.1-flash-lite';
610
+ const DEFAULT_LLM_SYSTEM_PROMPT =
611
+ 'You are a helpful assistant. Answer the user using the tools and resources provided to you whenever they are relevant, and prefer that information over your prior knowledge. Cite the resources you used when applicable. Be concise and accurate. If you cannot find a clear answer in the available context, say so honestly instead of guessing.';
612
+
613
+ const LLM_CATALOG: ReadonlyArray<{
614
+ provider:
615
+ | typeof LLM_PROVIDER_GOOGLE
616
+ | typeof LLM_PROVIDER_OPENAI
617
+ | typeof LLM_PROVIDER_ANTHROPIC;
618
+ model: string;
619
+ label: string;
620
+ }> = [
621
+ {
622
+ provider: LLM_PROVIDER_GOOGLE,
623
+ model: 'gemini-3-flash-preview',
624
+ label: 'Gemini 3 Flash'
625
+ },
626
+ {
627
+ provider: LLM_PROVIDER_GOOGLE,
628
+ model: 'gemini-3.1-pro-preview',
629
+ label: 'Gemini 3.1 Pro'
630
+ },
631
+ {
632
+ provider: LLM_PROVIDER_OPENAI,
633
+ model: 'gpt-4o-mini',
634
+ label: 'GPT-4o mini'
635
+ },
636
+ {
637
+ provider: LLM_PROVIDER_OPENAI,
638
+ model: 'gpt-4o',
639
+ label: 'GPT-4o'
640
+ },
641
+ {
642
+ provider: LLM_PROVIDER_ANTHROPIC,
643
+ model: 'claude-haiku-4-5',
644
+ label: 'Claude Haiku 4.5'
645
+ },
646
+ {
647
+ provider: LLM_PROVIDER_ANTHROPIC,
648
+ model: 'claude-sonnet-4-6',
649
+ label: 'Claude Sonnet 4.6'
650
+ },
651
+ {
652
+ provider: LLM_PROVIDER_ANTHROPIC,
653
+ model: 'claude-opus-4.7',
654
+ label: 'Claude Sonnet 4.7'
655
+ }
656
+ ];
657
+
658
+ const LLM_SYSTEM_DEFAULT = 'SYSTEM_DEFAULT';
659
+
660
+ const MAX_TOOL_LOOPS = 8;
661
+
662
+ // How many recent messages a channel turn replays as context.
663
+ const CHANNEL_HISTORY_LIMIT = 20;
664
+
665
+ // Tighter turn envelope when a channel runs on the shared platform model key
666
+ // (Free, no org LLM configured) — we pay that inference, so we bound the two
667
+ // dominant cost drivers: input tokens (smaller history) and number of model
668
+ // calls (fewer tool loops). Orgs that bring their own key get the full envelope
669
+ // above. Lower numbers also cut latency on the same path.
670
+ const SHARED_KEY_HISTORY_LIMIT = 10;
671
+ const SHARED_KEY_MAX_TOOL_LOOPS = 6;
672
+
673
+ // Hard ceiling on how many tools a single channel turn exposes to the model.
674
+ //
675
+ // Tool count is the dominant input-token driver, and it is super-linear: our own
676
+ // channel_message rows show a 5-tool artifact averaging 1,103 input tokens/turn
677
+ // against 13,109 for a 12-tool one — 2.4× the tools, 12× the tokens, because
678
+ // every schema is re-sent on every model call and a turn makes ~3. Uncapped, an
679
+ // 80-tool artifact costs ~$21 per 1,000 turns, which is above the shared-key
680
+ // overage rate; capped at 40 it tops out near $11 and stays profitable.
681
+ //
682
+ // This is also a quality control, not only a cost one — tool selection degrades
683
+ // badly past a few dozen options. Applies to CHANNEL turns only: MCP-client
684
+ // traffic pays its own token cost on the client's model, so it isn't capped
685
+ // here.
686
+ const CHANNEL_MAX_TOOLS = 40;
687
+
688
+ // Message debounce — people type the way they talk, in bursts ("hey" / "quick
689
+ // question" / "about the invoice"). Answering each fragment separately produces
690
+ // three half-informed replies and bills three assistant turns. Instead a burst
691
+ // from ONE participant in ONE conversation is buffered and answered once.
692
+ //
693
+ // The window restarts on every new message (so it tracks the typist), bounded by
694
+ // CHANNEL_DEBOUNCE_MAX_WAIT_MS from the first buffered message so a chatty user
695
+ // still gets an answer, and by CHANNEL_DEBOUNCE_MAX_MESSAGES so one batch can't
696
+ // grow without limit. `debounceMs: 0` on channel.config disables buffering and
697
+ // restores the answer-every-message behavior.
698
+ const CHANNEL_DEBOUNCE_DEFAULT_MS = 5000;
699
+ const CHANNEL_DEBOUNCE_MIN_MS = 500;
700
+ const CHANNEL_DEBOUNCE_MAX_MS = 30_000;
701
+ const CHANNEL_DEBOUNCE_MAX_WAIT_MS = 60_000;
702
+ const CHANNEL_DEBOUNCE_MAX_MESSAGES = 20;
703
+ // Disables buffering for a channel when set as `config.debounceMs`.
704
+ const CHANNEL_DEBOUNCE_DISABLED = 0;
705
+ // How buffered texts are joined into the single user turn the model sees. A
706
+ // newline keeps each fragment on its own line rather than running them together.
707
+ const CHANNEL_DEBOUNCE_JOIN = '\n';
708
+ // Backoff between retries when the DO can't hand a flushed batch to the worker,
709
+ // and how many times it tries before dropping the batch rather than looping.
710
+ const CHANNEL_DEBOUNCE_RETRY_MS = 5000;
711
+ const CHANNEL_DEBOUNCE_MAX_ATTEMPTS = 3;
712
+
713
+ const TELEGRAM_SECRET_HEADER = 'x-telegram-bot-api-secret-token';
714
+ const TELEGRAM_API_BASE = 'https://api.telegram.org';
715
+ const TELEGRAM_MESSAGE_LIMIT = 3500;
716
+
717
+ // Slack channel (inbound bot webhook). Slack signs every request to the
718
+ // Events API / slash-command Request URL with the app signing secret; we verify
719
+ // `v0:{timestamp}:{rawBody}` against the x-slack-signature header and reject
720
+ // stale timestamps (replay protection). A `section` block's mrkdwn text caps at
721
+ // 3000 chars — we chunk a bit under that. Source links become Block Kit URL
722
+ // buttons (≤5 per actions block on Slack's side).
723
+ const SLACK_SIGNATURE_HEADER = 'x-slack-signature';
724
+ const SLACK_TIMESTAMP_HEADER = 'x-slack-request-timestamp';
725
+ const SLACK_RETRY_NUM_HEADER = 'x-slack-retry-num';
726
+ const SLACK_SIGNATURE_VERSION = 'v0';
727
+ const SLACK_SIGNATURE_MAX_SKEW_SECONDS = 300;
728
+ const SLACK_MESSAGE_LIMIT = 2900;
729
+ const SLACK_MAX_SOURCE_BUTTONS = 10;
730
+
731
+ // The Slack app configuration Ganju needs, surfaced in the UI when connecting a
732
+ // Slack channel. Required scopes back the runner's calls (mentions, DMs,
733
+ // posting, file upload); recommended scopes only enrich names/titles and
734
+ // degrade gracefully; bot events are what the webhook subscribes to.
735
+ const SLACK_REQUIRED_SCOPES = [
736
+ 'app_mentions:read',
737
+ 'im:history',
738
+ 'chat:write',
739
+ 'files:write'
740
+ ];
741
+ const SLACK_RECOMMENDED_SCOPES = ['users:read', 'channels:read', 'groups:read'];
742
+ const SLACK_BOT_EVENTS = ['app_mention', 'message.im'];
743
+
744
+ // Discord. Free-form messages / @mentions / DMs arrive over a persistent Gateway
745
+ // WebSocket (held in a Durable Object); native slash commands arrive over a
746
+ // separate Ed25519-signed Interactions HTTP endpoint. The bot identity token,
747
+ // the application id (for command registration + interaction follow-ups), and
748
+ // the application public key (for verifying interactions) are all stored as the
749
+ // channel's credentials.
750
+ const DISCORD_API_BASE = 'https://discord.com/api/v10';
751
+ // Gateway connection query (JSON encoding, API v10). The actual socket URL is
752
+ // discovered from GET /gateway/bot (or the resume_gateway_url after READY).
753
+ const DISCORD_GATEWAY_QUERY = '?v=10&encoding=json';
754
+ const DISCORD_MESSAGE_LIMIT = 2000;
755
+ // Default bot upload ceiling for a server without boosts. Larger files should be
756
+ // shared as links, not uploaded; this also keeps the container's memory sane.
757
+ const DISCORD_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
758
+ // Discord renders at most 5 buttons per action row — source links become a
759
+ // single row of link buttons (mirroring Telegram's inline keyboard).
760
+ const DISCORD_MAX_SOURCE_BUTTONS = 5;
761
+ // Gateway intents the bot identifies with: GUILDS (1<<0) + GUILD_MESSAGES
762
+ // (1<<9) + DIRECT_MESSAGES (1<<12) + MESSAGE_CONTENT (1<<15, privileged — must
763
+ // be enabled in the Developer Portal to read free-form text in servers).
764
+ const DISCORD_INTENTS = (1 << 0) | (1 << 9) | (1 << 12) | (1 << 15);
765
+ // Interactions are signed with Ed25519 over `{timestamp}{rawBody}`; verify
766
+ // against the application public key.
767
+ const DISCORD_SIGNATURE_HEADER = 'x-signature-ed25519';
768
+ const DISCORD_TIMESTAMP_HEADER = 'x-signature-timestamp';
769
+ // Interaction request types and response types (Discord API).
770
+ const DISCORD_INTERACTION_TYPE_PING = 1;
771
+ const DISCORD_INTERACTION_TYPE_APPLICATION_COMMAND = 2;
772
+ const DISCORD_INTERACTION_RESPONSE_PONG = 1;
773
+ const DISCORD_INTERACTION_RESPONSE_DEFERRED = 5;
774
+ // Channel types we map to conversation scopes: DM (1) / group DM (3) = private;
775
+ // everything else (guild text channels, threads) = channel.
776
+ const DISCORD_CHANNEL_TYPE_DM = 1;
777
+ const DISCORD_CHANNEL_TYPE_GROUP_DM = 3;
778
+ // Discord Gateway opcodes (the persistent WebSocket protocol the bot speaks to
779
+ // receive messages). See https://discord.com/developers/docs/topics/gateway.
780
+ const DISCORD_GATEWAY_OP_DISPATCH = 0;
781
+ const DISCORD_GATEWAY_OP_HEARTBEAT = 1;
782
+ const DISCORD_GATEWAY_OP_IDENTIFY = 2;
783
+ const DISCORD_GATEWAY_OP_RESUME = 6;
784
+ const DISCORD_GATEWAY_OP_RECONNECT = 7;
785
+ const DISCORD_GATEWAY_OP_INVALID_SESSION = 9;
786
+ const DISCORD_GATEWAY_OP_HELLO = 10;
787
+ const DISCORD_GATEWAY_OP_HEARTBEAT_ACK = 11;
788
+ // Default heartbeat cadence (ms) until HELLO supplies the real interval.
789
+ const DISCORD_GATEWAY_DEFAULT_HEARTBEAT_MS = 41250;
790
+
791
+ // WhatsApp (Meta Cloud API). Inbound messages and the GET verification handshake
792
+ // hit the same per-channel Request URL the tenant configures in their Meta app
793
+ // dashboard. Meta signs every POST with the app secret over the RAW body —
794
+ // `sha256=<hmac>` in the x-hub-signature-256 header (mirrors Slack's scheme).
795
+ // The GET handshake echoes hub.challenge when hub.verify_token matches the
796
+ // channel's stored token. Like Slack, there's no group concept for the Cloud
797
+ // API bot — every conversation is a 1:1 (private) chat keyed by the user's wa_id.
798
+ const WHATSAPP_API_BASE = 'https://graph.facebook.com';
799
+ const WHATSAPP_API_VERSION = 'v25.0';
800
+ // A WhatsApp text message body caps at 4096 chars; we chunk a bit under that.
801
+ const WHATSAPP_MESSAGE_LIMIT = 4000;
802
+ const WHATSAPP_SIGNATURE_HEADER = 'x-hub-signature-256';
803
+ const WHATSAPP_SIGNATURE_PREFIX = 'sha256=';
804
+ // GET-handshake query params + the expected hub.mode value.
805
+ const WHATSAPP_HUB_MODE_PARAM = 'hub.mode';
806
+ const WHATSAPP_HUB_VERIFY_TOKEN_PARAM = 'hub.verify_token';
807
+ const WHATSAPP_HUB_CHALLENGE_PARAM = 'hub.challenge';
808
+ const WHATSAPP_HUB_MODE_SUBSCRIBE = 'subscribe';
809
+ // Cloud API media ceilings by category (Meta-enforced). Documents are the
810
+ // largest; images the smallest. Used to reject an oversize send before upload.
811
+ const WHATSAPP_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
812
+ const WHATSAPP_MAX_VIDEO_BYTES = 16 * 1024 * 1024;
813
+ const WHATSAPP_MAX_AUDIO_BYTES = 16 * 1024 * 1024;
814
+ const WHATSAPP_MAX_DOCUMENT_BYTES = 100 * 1024 * 1024;
815
+
816
+ const EMBEDDING_MODEL = 'gemini-embedding-001';
817
+ // 1536, not the model's native 3072. Matryoshka training means the first half of
818
+ // the vector carries almost all the signal: measured on our own corpus, 1536
819
+ // agrees with exact 3072 search on 93% of top-10 results — while the HNSW index
820
+ // we already run agrees only 73% of the time, so this costs less fidelity than
821
+ // the index itself does. In exchange the vector and its index entry both halve,
822
+ // taking embedded storage from ~12.9x expansion to ~6.9x.
823
+ //
824
+ // Two things depend on this staying in step: the halfvec(1536) column in
825
+ // packages/db, and the l2Normalize call in both embed paths — the API does NOT
826
+ // normalise output below 3072 dimensions.
827
+ const EMBEDDING_DIMENSIONS = 1536;
828
+ const CHUNK_TARGET_CHARS = 2000;
829
+ const CHUNK_OVERLAP_CHARS = 200;
830
+ const EMBED_BATCH_SIZE = 96;
831
+
832
+ const RESOURCE_HANDLER_SLEEP_AFTER = '10m';
833
+
834
+ const DOCS_URL = 'https://docs.ganju.ai';
835
+
836
+ const BASE64_DATA_URI_RE =
837
+ /data:image\/[a-zA-Z0-9+\-.]+;base64,[A-Za-z0-9+/=\s]+/g;
838
+ const RAW_BASE64_BLOB_RE = /[A-Za-z0-9+/]{512,}={0,2}/g;
839
+
840
+ const CHUNK_SEPARATORS: Array<{ split: string | RegExp; join: string }> = [
841
+ { split: /\n(?=#{1,6}\s)/, join: '\n' },
842
+ { split: '\n\n', join: '\n\n' },
843
+ { split: '\n', join: '\n' },
844
+ { split: '. ', join: '. ' },
845
+ { split: ' ', join: ' ' }
846
+ ];
847
+
848
+ const DEFAULT_MAX_TOKENS = 4096;
849
+
850
+ const RESOURCE_TOOL_KEY_LIST_RESOURCES = 'list-resources';
851
+ const RESOURCE_TOOL_KEY_SEARCH_RESOURCES = 'search-resources';
852
+ const RESOURCE_TOOL_KEY_READ_RESOURCE = 'read-resource';
853
+ const RESOURCE_TOOL_KEY_SEND_RESOURCE = 'send-resource';
854
+ const PROMPT_TOOL_KEY_LIST_PROMPTS = 'list-prompts';
855
+ const RESOURCE_TOOL_KEYS = [
856
+ RESOURCE_TOOL_KEY_LIST_RESOURCES,
857
+ RESOURCE_TOOL_KEY_SEARCH_RESOURCES,
858
+ RESOURCE_TOOL_KEY_READ_RESOURCE,
859
+ RESOURCE_TOOL_KEY_SEND_RESOURCE,
860
+ PROMPT_TOOL_KEY_LIST_PROMPTS
861
+ ];
862
+ const URI_BEARING_RESOURCE_TOOL_KEYS = [
863
+ RESOURCE_TOOL_KEY_READ_RESOURCE,
864
+ RESOURCE_TOOL_KEY_SEND_RESOURCE
865
+ ];
866
+
867
+ const CALENDAR_TOOL_KEY_PREFIX = 'calendar-';
868
+
869
+ // Google Calendar `sendUpdates` query values — who gets emailed on event changes.
870
+ const CALENDAR_SEND_UPDATES_ALL = 'all' as 'all';
871
+ const CALENDAR_SEND_UPDATES_EXTERNAL_ONLY = 'externalOnly' as 'externalOnly';
872
+ const CALENDAR_SEND_UPDATES_NONE = 'none' as 'none';
873
+ const CALENDAR_SEND_UPDATES_VALUES = [
874
+ CALENDAR_SEND_UPDATES_ALL,
875
+ CALENDAR_SEND_UPDATES_EXTERNAL_ONLY,
876
+ CALENDAR_SEND_UPDATES_NONE
877
+ ];
878
+
879
+ // Google Calendar event `visibility`. DEFAULT means "inherit the calendar's
880
+ // default" and is omitted from the request body.
881
+ const CALENDAR_VISIBILITY_DEFAULT = 'default' as 'default';
882
+ const CALENDAR_VISIBILITY_PUBLIC = 'public' as 'public';
883
+ const CALENDAR_VISIBILITY_PRIVATE = 'private' as 'private';
884
+ const CALENDAR_VISIBILITY_VALUES = [
885
+ CALENDAR_VISIBILITY_DEFAULT,
886
+ CALENDAR_VISIBILITY_PUBLIC,
887
+ CALENDAR_VISIBILITY_PRIVATE
888
+ ];
889
+
890
+ const CALENDAR_DEFAULT_CALENDAR_ID = 'primary';
891
+ const CALENDAR_DEFAULT_EVENT_DURATION_MINUTES = 60;
892
+ const CALENDAR_CONFERENCE_TYPE_GOOGLE_MEET = 'hangoutsMeet';
893
+
894
+ // Declarative per-tool config schema for the calendar tools, rendered as a form
895
+ // in the Tools UI. Keyed by tool key; values are the artifact_tool.config keys
896
+ // each tool reads. Group-level keys (defaultCalendarId / defaultTimeZone /
897
+ // sendUpdates) are edited from the group header, so they are not listed here.
898
+ export type CalendarConfigField =
899
+ | {
900
+ key: string;
901
+ label: string;
902
+ type: 'number';
903
+ min?: number;
904
+ max?: number;
905
+ help?: string;
906
+ }
907
+ | { key: string; label: string; type: 'text'; help?: string }
908
+ | { key: string; label: string; type: 'boolean'; help?: string }
909
+ | {
910
+ key: string;
911
+ label: string;
912
+ type: 'select';
913
+ options: { value: string; label: string }[];
914
+ help?: string;
915
+ }
916
+ | { key: string; label: string; type: 'weekdays'; help?: string };
917
+
918
+ const CALENDAR_TOOL_FIELDS: Record<string, CalendarConfigField[]> = {
919
+ 'calendar-list-calendars': [
920
+ {
921
+ key: 'defaultMaxResults',
922
+ label: 'Default max results',
923
+ type: 'number',
924
+ min: 1,
925
+ max: 50,
926
+ help: 'Used when a call omits maxResults (1–50).'
927
+ },
928
+ {
929
+ key: 'defaultWindowDays',
930
+ label: 'Default look-ahead (days)',
931
+ type: 'number',
932
+ min: 1,
933
+ help: 'When no end time is given, list events this many days ahead.'
934
+ }
935
+ ],
936
+ 'calendar-create-event': [
937
+ {
938
+ key: 'defaultDurationMinutes',
939
+ label: 'Default duration (minutes)',
940
+ type: 'number',
941
+ min: 1,
942
+ help: 'Used when an event is created without an end time.'
943
+ },
944
+ {
945
+ key: 'addGoogleMeet',
946
+ label: 'Add a Google Meet link',
947
+ type: 'boolean',
948
+ help: 'Attach a Meet conference to every new event.'
949
+ },
950
+ { key: 'defaultLocation', label: 'Default location', type: 'text' },
951
+ {
952
+ key: 'defaultVisibility',
953
+ label: 'Visibility',
954
+ type: 'select',
955
+ options: [
956
+ { value: CALENDAR_VISIBILITY_DEFAULT, label: 'Calendar default' },
957
+ { value: CALENDAR_VISIBILITY_PUBLIC, label: 'Public' },
958
+ { value: CALENDAR_VISIBILITY_PRIVATE, label: 'Private' }
959
+ ]
960
+ }
961
+ ],
962
+ 'calendar-find-free-slots': [
963
+ {
964
+ key: 'workingHoursStart',
965
+ label: 'Working hours start (0–23)',
966
+ type: 'number',
967
+ min: 0,
968
+ max: 23,
969
+ help: 'Local hour. Requires a default time zone to take effect.'
970
+ },
971
+ {
972
+ key: 'workingHoursEnd',
973
+ label: 'Working hours end (1–24)',
974
+ type: 'number',
975
+ min: 1,
976
+ max: 24
977
+ },
978
+ { key: 'workingDays', label: 'Working days', type: 'weekdays' },
979
+ {
980
+ key: 'defaultDurationMinutes',
981
+ label: 'Default slot length (minutes)',
982
+ type: 'number',
983
+ min: 1
984
+ },
985
+ {
986
+ key: 'bufferMinutes',
987
+ label: 'Buffer between meetings (minutes)',
988
+ type: 'number',
989
+ min: 0
990
+ },
991
+ {
992
+ key: 'minNoticeHours',
993
+ label: 'Minimum notice (hours)',
994
+ type: 'number',
995
+ min: 0
996
+ },
997
+ {
998
+ key: 'maxAdvanceDays',
999
+ label: 'Max advance (days)',
1000
+ type: 'number',
1001
+ min: 1
1002
+ }
1003
+ ]
1004
+ };
1005
+
1006
+ // Cal.com uses a personal API key (no OAuth). The key is stored like any other
1007
+ // credential (artifact_credential, provider 'calcom', encrypted accessToken).
1008
+ const API_KEY_PROVIDER_CALCOM = 'calcom' as 'calcom';
1009
+ // Tavily web search uses a personal API key (no OAuth), same storage pattern.
1010
+ const API_KEY_PROVIDER_TAVILY = 'tavily' as 'tavily';
1011
+ // API-key providers surfaced in the Tools UI's "Add API key" affordance (one
1012
+ // key per provider, validated against the vendor before storage).
1013
+ const API_KEY_PROVIDERS = [API_KEY_PROVIDER_CALCOM, API_KEY_PROVIDER_TAVILY];
1014
+
1015
+ // http-endpoint secrets are stored as artifact_credential rows too, but unlike
1016
+ // the API-key providers above a single artifact can hold MANY of them (one per
1017
+ // endpoint), each referenced by id from a tool's auth config and carrying a
1018
+ // human label in metadata. There's no vendor to validate against. This is kept
1019
+ // out of API_KEY_PROVIDERS so the catalog UI doesn't treat it as a one-key
1020
+ // "Add API key" integration.
1021
+ const CREDENTIAL_PROVIDER_HTTP_ENDPOINT = 'http-endpoint' as 'http-endpoint';
1022
+ // mcp-proxy secrets behave exactly like http-endpoint ones: many per artifact
1023
+ // (one per proxied server), referenced by id from a tool's auth config and
1024
+ // labelled in metadata, with no vendor to validate against. Same multi-row,
1025
+ // per-tool storage — kept out of API_KEY_PROVIDERS for the same reason.
1026
+ const CREDENTIAL_PROVIDER_MCP_PROXY = 'mcp-proxy' as 'mcp-proxy';
1027
+ // custom-code secrets are the `ctx.secret(name)` values a user script reads at
1028
+ // runtime. Same multi-row shape as the two above — many per artifact, labelled
1029
+ // in metadata, no vendor to validate — but the label is also the LOOKUP KEY the
1030
+ // script passes to ctx.secret(), not just a display string. The broker resolves
1031
+ // them; the secret value itself never reaches user code as a binding. Defined in
1032
+ // cliConstants because `ganju secret` writes under it.
1033
+ // Providers whose secrets are per-tool (many rows per artifact, deleted with the
1034
+ // tool that owns them) rather than one-per-provider. See createCredential /
1035
+ // removeTool in apps/api ArtifactController.
1036
+ const PER_TOOL_CREDENTIAL_PROVIDERS = [
1037
+ CREDENTIAL_PROVIDER_HTTP_ENDPOINT,
1038
+ CREDENTIAL_PROVIDER_MCP_PROXY,
1039
+ CREDENTIAL_PROVIDER_CUSTOM_CODE
1040
+ ];
1041
+ // Every provider the generic credential create endpoint accepts.
1042
+ const CREDENTIAL_PROVIDERS = [
1043
+ API_KEY_PROVIDER_CALCOM,
1044
+ API_KEY_PROVIDER_TAVILY,
1045
+ CREDENTIAL_PROVIDER_HTTP_ENDPOINT,
1046
+ CREDENTIAL_PROVIDER_MCP_PROXY,
1047
+ CREDENTIAL_PROVIDER_CUSTOM_CODE
1048
+ ];
1049
+
1050
+ const CALCOM_API_BASE = 'https://api.cal.com/v2';
1051
+ // Cal.com pins behavior per endpoint with the `cal-api-version` header.
1052
+ const CALCOM_API_VERSION_EVENT_TYPES = '2024-06-14';
1053
+ const CALCOM_API_VERSION_SLOTS = '2024-09-04';
1054
+ const CALCOM_API_VERSION_BOOKINGS = '2026-02-25';
1055
+
1056
+ const CALCOM_TOOL_KEY_PREFIX = 'calcom-';
1057
+
1058
+ // Tavily web search. The key is validated against the live API before it is
1059
+ // persisted (a minimal 1-result search), then stored as an artifact_credential
1060
+ // (provider 'tavily', encrypted accessToken, no refresh token) like Cal.com.
1061
+ const TAVILY_API_BASE = 'https://api.tavily.com';
1062
+ const TAVILY_SEARCH_DEPTH_BASIC = 'basic' as 'basic';
1063
+ const TAVILY_SEARCH_DEPTH_ADVANCED = 'advanced' as 'advanced';
1064
+ const TAVILY_SEARCH_DEPTHS = [
1065
+ TAVILY_SEARCH_DEPTH_BASIC,
1066
+ TAVILY_SEARCH_DEPTH_ADVANCED
1067
+ ];
1068
+ const TAVILY_TOPIC_GENERAL = 'general' as 'general';
1069
+ const TAVILY_TOPIC_NEWS = 'news' as 'news';
1070
+ const TAVILY_TOPICS = [TAVILY_TOPIC_GENERAL, TAVILY_TOPIC_NEWS];
1071
+ const TAVILY_DEFAULT_MAX_RESULTS = 5;
1072
+ const TAVILY_MAX_RESULTS_LIMIT = 20;
1073
+
1074
+ const WEB_TOOL_KEY_PREFIX = 'web-';
1075
+ const WEB_TOOL_KEY_SEARCH = 'web-search';
1076
+ const WEB_TOOL_KEY_EXTRACT = 'web-extract';
1077
+ const WEB_TOOL_KEYS = [WEB_TOOL_KEY_SEARCH, WEB_TOOL_KEY_EXTRACT];
1078
+
1079
+ // `http-endpoint` is a proxied tool definition: a single tool_definition row
1080
+ // (key below) whose installed artifact_tool rows each describe one HTTP call.
1081
+ // At MCP server boot, every such row registers one named MCP tool derived from
1082
+ // its config (name/title/description/inputSchema). It lets users expose their
1083
+ // own backends to the agent without a TypeScript handler.
1084
+ const TOOL_DEFINITION_KEY_HTTP_ENDPOINT = 'http-endpoint';
1085
+
1086
+ const HTTP_ENDPOINT_METHOD_GET = 'GET' as 'GET';
1087
+ const HTTP_ENDPOINT_METHOD_POST = 'POST' as 'POST';
1088
+ const HTTP_ENDPOINT_METHOD_PUT = 'PUT' as 'PUT';
1089
+ const HTTP_ENDPOINT_METHOD_PATCH = 'PATCH' as 'PATCH';
1090
+ const HTTP_ENDPOINT_METHOD_DELETE = 'DELETE' as 'DELETE';
1091
+ const HTTP_ENDPOINT_METHODS = [
1092
+ HTTP_ENDPOINT_METHOD_GET,
1093
+ HTTP_ENDPOINT_METHOD_POST,
1094
+ HTTP_ENDPOINT_METHOD_PUT,
1095
+ HTTP_ENDPOINT_METHOD_PATCH,
1096
+ HTTP_ENDPOINT_METHOD_DELETE
1097
+ ];
1098
+
1099
+ const HTTP_ENDPOINT_BODY_KIND_NONE = 'none' as 'none';
1100
+ const HTTP_ENDPOINT_BODY_KIND_JSON = 'json' as 'json';
1101
+ const HTTP_ENDPOINT_BODY_KIND_FORM = 'form' as 'form';
1102
+ const HTTP_ENDPOINT_BODY_KIND_TEXT = 'text' as 'text';
1103
+ const HTTP_ENDPOINT_BODY_KINDS = [
1104
+ HTTP_ENDPOINT_BODY_KIND_NONE,
1105
+ HTTP_ENDPOINT_BODY_KIND_JSON,
1106
+ HTTP_ENDPOINT_BODY_KIND_FORM,
1107
+ HTTP_ENDPOINT_BODY_KIND_TEXT
1108
+ ];
1109
+
1110
+ const HTTP_ENDPOINT_AUTH_KIND_NONE = 'none' as 'none';
1111
+ const HTTP_ENDPOINT_AUTH_KIND_BEARER = 'bearer' as 'bearer';
1112
+ const HTTP_ENDPOINT_AUTH_KIND_BASIC = 'basic' as 'basic';
1113
+ const HTTP_ENDPOINT_AUTH_KIND_API_KEY = 'api-key' as 'api-key';
1114
+ const HTTP_ENDPOINT_AUTH_KIND_OAUTH = 'oauth' as 'oauth';
1115
+ const HTTP_ENDPOINT_AUTH_KINDS = [
1116
+ HTTP_ENDPOINT_AUTH_KIND_NONE,
1117
+ HTTP_ENDPOINT_AUTH_KIND_BEARER,
1118
+ HTTP_ENDPOINT_AUTH_KIND_BASIC,
1119
+ HTTP_ENDPOINT_AUTH_KIND_API_KEY,
1120
+ HTTP_ENDPOINT_AUTH_KIND_OAUTH
1121
+ ];
1122
+
1123
+ const HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO = 'auto' as 'auto';
1124
+ const HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_JSON = 'json' as 'json';
1125
+ const HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_TEXT = 'text' as 'text';
1126
+
1127
+ const HTTP_ENDPOINT_DEFAULT_TIMEOUT_MS = 10_000;
1128
+ const HTTP_ENDPOINT_MAX_TIMEOUT_MS = 30_000;
1129
+ // Response body cap returned to the model; truncated past this with a marker.
1130
+ const HTTP_ENDPOINT_DEFAULT_MAX_BYTES = 256 * 1024;
1131
+ // Hard ceiling on the outgoing request body, regardless of config.
1132
+ const HTTP_ENDPOINT_MAX_REQUEST_BYTES = 1024 * 1024;
1133
+
1134
+ // `mcp-proxy` is the second proxied tool definition: a single tool_definition
1135
+ // row (key below) whose installed artifact_tool rows each describe one remote
1136
+ // MCP server (a vendor's official server — GitHub, Notion, …). Unlike
1137
+ // http-endpoint (one row → one tool), one mcp-proxy row produces MANY MCP tools,
1138
+ // one per remote tool discovered from that server. Discovery happens once at
1139
+ // configure-time (apps/api) and the tool list is stored in
1140
+ // artifact_tool.metadata.discovery so the stateless MCP boot loop can register
1141
+ // the tools without a remote round-trip; only tools/call connects to the remote.
1142
+ const TOOL_DEFINITION_KEY_MCP_PROXY = 'mcp-proxy';
1143
+
1144
+ const MCP_PROXY_TRANSPORT_STREAMABLE_HTTP =
1145
+ 'streamable-http' as 'streamable-http';
1146
+ const MCP_PROXY_TRANSPORT_SSE = 'sse' as 'sse';
1147
+ const MCP_PROXY_TRANSPORTS = [
1148
+ MCP_PROXY_TRANSPORT_STREAMABLE_HTTP,
1149
+ MCP_PROXY_TRANSPORT_SSE
1150
+ ];
1151
+
1152
+ const MCP_PROXY_AUTH_KIND_NONE = 'none' as 'none';
1153
+ const MCP_PROXY_AUTH_KIND_BEARER = 'bearer' as 'bearer';
1154
+ const MCP_PROXY_AUTH_KIND_HEADER = 'header' as 'header';
1155
+ const MCP_PROXY_AUTH_KIND_OAUTH = 'oauth' as 'oauth';
1156
+ const MCP_PROXY_AUTH_KINDS = [
1157
+ MCP_PROXY_AUTH_KIND_NONE,
1158
+ MCP_PROXY_AUTH_KIND_BEARER,
1159
+ MCP_PROXY_AUTH_KIND_HEADER,
1160
+ MCP_PROXY_AUTH_KIND_OAUTH
1161
+ ];
1162
+
1163
+ const MCP_PROXY_DEFAULT_TIMEOUT_MS = 10_000;
1164
+ const MCP_PROXY_MAX_TIMEOUT_MS = 30_000;
1165
+ // Cap on how many remote tools a single proxied server may register, so a
1166
+ // chatty vendor can't flood the artifact's tool list.
1167
+ const MCP_PROXY_MAX_TOOLS = 100;
1168
+ // Response cap returned to the model from a proxied tools/call; truncated past
1169
+ // this with a marker (same ceiling as http-endpoint).
1170
+ const MCP_PROXY_MAX_RESPONSE_BYTES = 256 * 1024;
1171
+ // Proxied tools register as `<prefix><sep><remoteKey>` so the vendor prefix is
1172
+ // visually distinct from native `<group>-<verb>-<object>` names.
1173
+ const MCP_PROXY_TOOL_NAME_SEP = '__';
1174
+ // Max length of the composed local tool name. Remote names are untrusted; the
1175
+ // cap matches the tool-name limit MCP clients (incl. the Anthropic API) enforce.
1176
+ const MCP_PROXY_TOOL_NAME_MAX = 64;
1177
+
1178
+ // `custom-code` is the third proxied tool definition, and the only one whose
1179
+ // behaviour is USER CODE rather than user configuration: one artifact_tool row
1180
+ // per artifact, pointing at a Cloudflare Worker deployed to our Workers for
1181
+ // Platforms dispatch namespace. Like mcp-proxy, one row produces MANY MCP tools
1182
+ // — but the tool list comes from the manifest the user uploads rather than from
1183
+ // a remote round-trip, and it is versioned (artifact_tool_version) so code and
1184
+ // contract move together and a rollback restores both.
1185
+ //
1186
+ // The row's config holds only `activeVersionId` plus the egress/limit settings;
1187
+ // names and schemas live on the version. That's what keeps MCP boot off the
1188
+ // dispatcher — a slow or broken script must never break tools/list. See
1189
+ // docs/CUSTOM_TOOLS.md ("Boot contract").
1190
+ const TOOL_DEFINITION_KEY_CUSTOM_CODE = 'custom-code';
1191
+
1192
+ // A version is uploaded as `draft`, becomes `published` when it is made active,
1193
+ // and is `archived` when a later version replaces it. Exactly one version per
1194
+ // tool is ever `published` — the one config.activeVersionId points at.
1195
+ const CUSTOM_CODE_VERSION_STATUS_DRAFT = 'draft' as 'draft';
1196
+ const CUSTOM_CODE_VERSION_STATUS_PUBLISHED = 'published' as 'published';
1197
+ const CUSTOM_CODE_VERSION_STATUS_ARCHIVED = 'archived' as 'archived';
1198
+ const CUSTOM_CODE_VERSION_STATUSES = [
1199
+ CUSTOM_CODE_VERSION_STATUS_DRAFT,
1200
+ CUSTOM_CODE_VERSION_STATUS_PUBLISHED,
1201
+ CUSTOM_CODE_VERSION_STATUS_ARCHIVED
1202
+ ];
1203
+
1204
+ // Where a version's stored source came from, which decides whether it can be
1205
+ // opened in the dashboard editor.
1206
+ //
1207
+ // 'editor' — the bytes at sourceKey are exactly what a person typed. They are
1208
+ // the deployed module too: dashboard code imports the SDK from a sibling module
1209
+ // the upload always attaches, so there is no build step between the text box and
1210
+ // the running Worker, and no second copy that can drift from it.
1211
+ //
1212
+ // 'bundle' — the CLI compiled and uploaded it. Deployable, but minified and
1213
+ // machine-shaped; the editor shows it read-only rather than inviting someone to
1214
+ // overwrite a real build with the contents of a text box. It is the default
1215
+ // because every version that predates the editor arrived this way.
1216
+ const CUSTOM_CODE_SOURCE_KIND_EDITOR = 'editor' as 'editor';
1217
+ const CUSTOM_CODE_SOURCE_KIND_BUNDLE = 'bundle' as 'bundle';
1218
+ const CUSTOM_CODE_SOURCE_KINDS = [
1219
+ CUSTOM_CODE_SOURCE_KIND_EDITOR,
1220
+ CUSTOM_CODE_SOURCE_KIND_BUNDLE
1221
+ ];
1222
+
1223
+ // The SDK, shipped as a second ES module beside every uploaded script rather
1224
+ // than bundled into it. Attached on every deploy, including CLI ones: a bundle
1225
+ // that inlined the SDK simply never imports it, and one extra module costs less
1226
+ // than a branch in the deploy path that has to know which kind it is holding.
1227
+ const CUSTOM_CODE_SDK_MODULE = 'ganju-sdk.js';
1228
+
1229
+ // The module the dispatcher calls. Every other file in a project is reached only
1230
+ // by being imported from it, directly or otherwise.
1231
+ const CUSTOM_CODE_MAIN_MODULE = 'index.js';
1232
+
1233
+ // How many files one script may hold, and how long a path may be. Neither is a
1234
+ // runtime limit — a Worker takes far more — they bound what one editor session
1235
+ // can produce, and keep the stored envelope small enough to stay a single R2
1236
+ // object read on every deploy.
1237
+ const CUSTOM_CODE_MAX_FILES = 25;
1238
+ const CUSTOM_CODE_MAX_FILE_PATH = 100;
1239
+
1240
+ // WfP script name: `artifact_<artifactId>`. The id, never the slug — slugs are
1241
+ // user-editable and a rename would orphan the deployed script.
1242
+ const CUSTOM_CODE_SCRIPT_NAME_PREFIX = 'artifact_';
1243
+
1244
+ // A second script per artifact, `artifact_<id>_preview`, that the Test panel
1245
+ // deploys a draft into and calls.
1246
+ //
1247
+ // Separate from the live script and not a mode of it: testing must never disturb
1248
+ // what MCP clients are being served, and the only way to be sure of that is for
1249
+ // the test to run somewhere else entirely. It is overwritten by the next test
1250
+ // and deleted after each run.
1251
+ const CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX = '_preview';
1252
+
1253
+ // How long a preview token stays valid.
1254
+ //
1255
+ // The live token needs no expiry — the broker refuses any token whose version is
1256
+ // not the active one, so publishing rotates it. A preview token has no such
1257
+ // anchor: its whole purpose is to work for a version that is NOT active, so
1258
+ // something else has to end it. Ten minutes is longer than any test run and far
1259
+ // shorter than the life of a script that failed to delete.
1260
+ const CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS = 10 * 60 * 1_000;
1261
+
1262
+ // How long a single test run may take before the API gives up on it. Shorter
1263
+ // than the script's own CPU ceiling is not the point — this bounds the wait of
1264
+ // someone watching a spinner.
1265
+ const CUSTOM_CODE_TEST_TIMEOUT_MS = 30_000;
1266
+
1267
+ // R2 prefix for uploaded bundles. Keyed by artifact + version so a rollback can
1268
+ // always find the exact source that produced the running script.
1269
+ const CUSTOM_CODE_SOURCE_KEY_PREFIX = 'custom-code';
1270
+
1271
+ // How many MCP tools one script may declare. Deliberately tighter than
1272
+ // MCP_PROXY_MAX_TOOLS (100): a proxied vendor server is a fixed remote surface,
1273
+ // whereas a manifest is user-authored and tool count is the dominant input-token
1274
+ // driver on channel turns (see CHANNEL_MAX_TOOLS). A channel only ever sees 40
1275
+ // of them; this cap bounds the manifest itself, which every MCP client pays for.
1276
+ const CUSTOM_CODE_MAX_TOOLS = 50;
1277
+ // Max length of a declared tool name — the limit MCP clients (incl. the
1278
+ // Anthropic API) enforce, same ceiling as the composed mcp-proxy names.
1279
+ const CUSTOM_CODE_TOOL_NAME_MAX = 64;
1280
+
1281
+ // Per-invocation dispatch timeout, clamped rather than rejected (same shape as
1282
+ // http-endpoint / mcp-proxy). This bounds how long apps/mcp waits on the
1283
+ // dispatcher; the per-script CPU ceiling on the namespace is a separate control
1284
+ // and is a Phase 2 item.
1285
+ const CUSTOM_CODE_DEFAULT_TIMEOUT_MS = 10_000;
1286
+ const CUSTOM_CODE_MAX_TIMEOUT_MS = 30_000;
1287
+
1288
+ // Response body cap returned to the model; truncated past this with a marker
1289
+ // (same ceiling as the other two proxied definitions).
1290
+ const CUSTOM_CODE_MAX_RESPONSE_BYTES = 256 * 1024;
1291
+
1292
+ // Hard ceiling on an uploaded bundle, checked before the body is streamed to R2.
1293
+ // Set well below Cloudflare's own script-size ceiling so a rejection happens
1294
+ // here — with a legible error — rather than at deploy time in Phase 2.
1295
+
1296
+ // Three hops have to agree on a wire shape, and none of them share a module
1297
+ // graph at runtime — apps/mcp dispatches, the user's script runs inside the
1298
+ // namespace, and apps/tool-broker serves its host capabilities. Keeping every
1299
+ // path, header and binding name below is what stops the three from drifting.
1300
+
1301
+ // The dispatcher POSTs to the user script at this origin. The hostname is never
1302
+ // resolved: a dispatch-namespace `fetch` is routed by script name, so the URL
1303
+ // only has to be a well-formed absolute URL the user script can parse.
1304
+ const CUSTOM_CODE_INVOKE_ORIGIN = 'https://tool.ganju.internal';
1305
+ const CUSTOM_CODE_INVOKE_PATH = '/invoke';
1306
+
1307
+ // A reserved tool name the SDK answers itself, without running user code. The
1308
+ // publish pipeline calls it once against the freshly uploaded script and expects
1309
+ // back the list of tool names the bundle actually exports. Declared in
1310
+ // ./sdkConstants — it is one of the few values that ships inside every deployed
1311
+ // script — and re-exported below with the rest.
1312
+
1313
+ // Reserved MCP tool names
1314
+ //
1315
+ // `http-endpoint` and `custom-code` let the user choose the MCP tool name, and
1316
+ // apps/mcp registers every tool on an artifact into ONE flat namespace. A
1317
+ // user-chosen name equal to a native tool's definition key is therefore a
1318
+ // collision, and both possible resolutions are bad: whichever registers second
1319
+ // is silently dropped, and the channel runner attributes the call to the other
1320
+ // one. Rejecting the name at the write path is the only place the user can be
1321
+ // told about it.
1322
+ //
1323
+ // Reserved by NAMESPACE, not by an enumerated list of the ~60 shipped keys.
1324
+ // A blocklist would answer "is this name taken today" — which is the wrong
1325
+ // question, because a name that is free at publish time is taken the moment
1326
+ // someone installs a native tool that uses it. Owning the prefix means a tool
1327
+ // added to any of these groups later can never collide with a name already
1328
+ // published, so this list only changes when a whole new group is added.
1329
+ //
1330
+ // mcp-proxy needs no entry: its names are always `<prefix>__<remote>`, and no
1331
+ // native key contains the separator.
1332
+ const RESERVED_TOOL_NAME_PREFIXES = [
1333
+ 'gmail-',
1334
+ 'outlook-',
1335
+ 'slack-',
1336
+ CALENDAR_TOOL_KEY_PREFIX,
1337
+ CALCOM_TOOL_KEY_PREFIX,
1338
+ WEB_TOOL_KEY_PREFIX
1339
+ ];
1340
+
1341
+ // The keys that don't belong to a prefixed group: the RAG core the channel
1342
+ // runner intercepts by name, the smoke-test tool, the three proxied definition
1343
+ // keys (each registers under its own key when installed natively), and the
1344
+ // health tool the SDK answers before it routes to user code.
1345
+ const RESERVED_TOOL_NAMES = [
1346
+ ...RESOURCE_TOOL_KEYS,
1347
+ 'greeting',
1348
+ TOOL_DEFINITION_KEY_HTTP_ENDPOINT,
1349
+ TOOL_DEFINITION_KEY_MCP_PROXY,
1350
+ TOOL_DEFINITION_KEY_CUSTOM_CODE,
1351
+ CUSTOM_CODE_HEALTH_TOOL
1352
+ ];
1353
+
1354
+ // Shared by both write paths so there is one string to translate. It has to
1355
+ // carry a word `matchStatus` recognises (errorHandler.ts), because the
1356
+ // http-endpoint path re-throws the issue message as a plain Error and anything
1357
+ // unrecognised becomes an opaque 500 instead of a 400.
1358
+ const RESERVED_TOOL_NAME_MESSAGE =
1359
+ 'Invalid tool name — reserved by the platform';
1360
+
1361
+ // A declared connection that names no managed provider. Fixed string for the
1362
+ // same two reasons as the message above: localizeZodIssue keys on the exact
1363
+ // English text, and the value has to carry a word matchStatus recognises or the
1364
+ // custom-code config path — which rethrows the issue message as a plain Error —
1365
+ // answers 500 where it means 400. The offending entry is pinpointed by the issue
1366
+ // path (`connections.2`), not by the message.
1367
+ const CUSTOM_CODE_UNKNOWN_CONNECTION_MESSAGE =
1368
+ 'Invalid connection — no managed provider by that name';
1369
+
1370
+ // An output schema has to describe an object, because that is what MCP's
1371
+ // `structuredContent` is. A schema of any other type compiles to an empty shape
1372
+ // and can never be satisfied, so the tool would declare structured output and
1373
+ // then report every call as a failure to produce it.
1374
+ //
1375
+ // A fixed string rather than one that names the offending type: localizeZodIssue
1376
+ // keys its translations on the exact English text. The issue `path` says which
1377
+ // entry, which is what a 50-tool manifest needs anyway.
1378
+ const OUTPUT_SCHEMA_NOT_OBJECT_MESSAGE =
1379
+ 'Invalid output schema — it must describe an object';
1380
+
1381
+ // The script bindings and the broker routes they call live in ./sdkConstants:
1382
+ // they are the SDK's runtime contract, so they are bundled into every deployed
1383
+ // script and must not drag this module in with them.
1384
+
1385
+ // Outbound-worker parameters the dispatcher attaches to every `.get()`. The
1386
+ // outbound worker reads them from its own env to screen the script's `fetch`
1387
+ // calls — which is why egress control can't be bypassed by editing the script.
1388
+ const CUSTOM_CODE_OUTBOUND_PARAM_ARTIFACT_ID = 'artifactId' as 'artifactId';
1389
+ const CUSTOM_CODE_OUTBOUND_PARAM_ALLOWED_HOSTS =
1390
+ 'allowedHosts' as 'allowedHosts';
1391
+
1392
+ // Hosts the outbound worker allows regardless of a tool's `allowedHosts`.
1393
+ //
1394
+ // The dispatch namespace routes every subrequest that originates inside it
1395
+ // through the outbound worker — including the ones the BROKER makes on the
1396
+ // script's behalf, which is not obvious until you watch it happen. Without this
1397
+ // list, `ctx.resources.search` breaks the moment a tool sets an allow-list
1398
+ // (the embedding call is a fetch to Gemini), and so does refreshing a
1399
+ // connection (a fetch to the provider's token endpoint).
1400
+ //
1401
+ // `allowedHosts` exists to bound where a tool's OWN code may reach, so the
1402
+ // platform's capabilities have no business being caught by it. Reaching these
1403
+ // hosts is not itself a capability: without our API key or client secret,
1404
+ // neither answers usefully.
1405
+ //
1406
+ // The SSRF screen still applies to these — this widens the allow-list, never
1407
+ // the private-address block.
1408
+ const CUSTOM_CODE_PLATFORM_HOSTS = [
1409
+ // Embeddings for ctx.resources.search.
1410
+ 'generativelanguage.googleapis.com',
1411
+ // Token endpoints the broker refreshes managed connections against. Kept in
1412
+ // step with oauthProviders by hostname rather than by URL.
1413
+ 'oauth2.googleapis.com',
1414
+ 'login.microsoftonline.com',
1415
+ 'slack.com'
1416
+ ];
1417
+
1418
+ // `GANJU_TOOL_TOKEN` is `<base64url(payload)>.<base64url(hmac)>`, signed with
1419
+ // CUSTOM_CODE_TOKEN_SECRET. It carries the artifact and the version it was
1420
+ // minted for; the broker checks the signature AND that the version is still the
1421
+ // artifact's active one, so a superseded script's token stops working the moment
1422
+ // a newer version is published. Never accept an artifact id from a request body.
1423
+ const CUSTOM_CODE_TOKEN_SECRET_ENV = 'CUSTOM_CODE_TOKEN_SECRET';
1424
+ const CUSTOM_CODE_TOKEN_VERSION = 'v1';
1425
+
1426
+ // Cloudflare credentials the publish pipeline needs to upload a script into the
1427
+ // dispatch namespace.
1428
+ //
1429
+ // The token deliberately does NOT use Cloudflare's own `CLOUDFLARE_API_TOKEN`
1430
+ // name. Wrangler reads that variable from the .env in its working directory and
1431
+ // authenticates with it instead of the developer's OAuth login — so putting our
1432
+ // token there silently reroutes every `wrangler deploy` through a token scoped
1433
+ // only to Workers Scripts, breaking any deploy that touches zone routes and
1434
+ // making `wrangler login` refuse to run. Ours is namespaced so the two can
1435
+ // coexist. `CLOUDFLARE_ACCOUNT_ID` keeps its standard name: wrangler reads it
1436
+ // too, but selecting the right account is the behaviour we want.
1437
+ const CUSTOM_CODE_ACCOUNT_ID_ENV = 'CLOUDFLARE_ACCOUNT_ID';
1438
+ const CUSTOM_CODE_API_TOKEN_ENV = 'CUSTOM_CODE_CF_API_TOKEN';
1439
+ const CUSTOM_CODE_NAMESPACE_ENV = 'CUSTOM_CODE_DISPATCH_NAMESPACE';
1440
+ // The broker worker each uploaded script is service-bound to. An env var rather
1441
+ // than a constant because the name is environment-suffixed
1442
+ // (ganju-tool-broker-development / -production).
1443
+ const CUSTOM_CODE_BROKER_SERVICE_ENV = 'CUSTOM_CODE_BROKER_SERVICE';
1444
+
1445
+ // Compatibility date pinned for every uploaded user script. Deliberately a
1446
+ // constant rather than "today": a script uploaded now and re-uploaded on
1447
+ // rollback months later must run identically both times.
1448
+ const CUSTOM_CODE_COMPATIBILITY_DATE = '2025-11-17';
1449
+
1450
+ // Per-invocation CPU ceiling applied to each user script at upload time. Far
1451
+ // tighter than our own workers' 30s — this is the technical cap that bounds
1452
+ // what one adversarial call can cost us, so an infinite loop in a customer's
1453
+ // tool is billed as five seconds rather than as whatever it wanted.
1454
+ const CUSTOM_CODE_SCRIPT_CPU_MS = 5_000;
1455
+
1456
+ // The ctx.log() caps also live in ./sdkConstants — the buffer that enforces them
1457
+ // runs inside the isolate.
1458
+
1459
+ // ctx.sendFile destinations.
1460
+ //
1461
+ // The one capability a user script genuinely cannot reproduce: a script is
1462
+ // capped at 128MiB, holds no R2 binding, and has no path to the resource-handler
1463
+ // container. So the bytes never enter the isolate — the script names a resource
1464
+ // and a destination, and the broker does the read and the multipart assembly on
1465
+ // its behalf.
1466
+ //
1467
+ // Each destination maps onto a container route the native handlers already
1468
+ // drive, which is why the list is these three and not every provider we hold a
1469
+ // connection for: a destination here means a send path that has already solved
1470
+ // chunked uploads and MIME assembly for that vendor.
1471
+ const CUSTOM_CODE_SEND_FILE_TARGET_GMAIL = 'gmail' as 'gmail';
1472
+ const CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK = 'outlook' as 'outlook';
1473
+ const CUSTOM_CODE_SEND_FILE_TARGET_SLACK = 'slack' as 'slack';
1474
+ const CUSTOM_CODE_SEND_FILE_TARGETS = [
1475
+ CUSTOM_CODE_SEND_FILE_TARGET_GMAIL,
1476
+ CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK,
1477
+ CUSTOM_CODE_SEND_FILE_TARGET_SLACK
1478
+ ];
1479
+
1480
+ // Which managed connection each destination sends as. sendFile spends the
1481
+ // artifact's credential, so it passes through the SAME `connections` allow-list
1482
+ // ctx.connection() does — a script that may not read the Gmail token must not be
1483
+ // able to send mail as that account by naming a different capability.
1484
+ const CUSTOM_CODE_SEND_FILE_PROVIDERS: Record<string, string> = {
1485
+ [CUSTOM_CODE_SEND_FILE_TARGET_GMAIL]: OAUTH_PROVIDER_GOOGLE_GMAIL,
1486
+ [CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK]: OAUTH_PROVIDER_MICROSOFT_OUTLOOK,
1487
+ [CUSTOM_CODE_SEND_FILE_TARGET_SLACK]: OAUTH_PROVIDER_SLACK
1488
+ };
1489
+
1490
+ // The resource-handler container route each destination posts to. These are the
1491
+ // routes the native handlers use, unchanged — the container is where MIME
1492
+ // assembly and chunked upload already live, and a second implementation in the
1493
+ // broker is exactly what this indirection avoids.
1494
+ const CUSTOM_CODE_SEND_FILE_PATHS: Record<string, string> = {
1495
+ [CUSTOM_CODE_SEND_FILE_TARGET_GMAIL]: '/gmail/send',
1496
+ [CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK]: '/outlook/send',
1497
+ [CUSTOM_CODE_SEND_FILE_TARGET_SLACK]: '/slack/send'
1498
+ };
1499
+
1500
+ // How many resources one sendFile call may carry. Bounded because each one is
1501
+ // read out of R2 into the broker's 128MiB isolate before it is streamed on; the
1502
+ // per-destination byte caps below bound total size, this bounds the read fan-out.
1503
+ const CUSTOM_CODE_SEND_FILE_MAX_URIS = 10;
1504
+
1505
+ // Ceilings on what one ctx.resources.create call may write.
1506
+ //
1507
+ // Two numbers rather than one, because the two payloads land in different
1508
+ // places. Inline text becomes a Postgres column on a row every resource listing
1509
+ // reads, so it stays small; bytes become an R2 object nothing reads until it is
1510
+ // sent, so it can be larger.
1511
+ //
1512
+ // Both are far below the 128MiB isolate ceiling, and deliberately so: unlike
1513
+ // sendFile, this payload DOES transit user code — the script is holding it —
1514
+ // and it is then carried again as base64 in the broker request, so the real
1515
+ // footprint is roughly 2.3x the number here. The file ceiling is also under
1516
+ // every destination's attachment cap, so anything a script can create it can
1517
+ // also send.
1518
+ const CUSTOM_CODE_MAX_RESOURCE_TEXT_BYTES = 1024 * 1024;
1519
+ const CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES = 10 * 1024 * 1024;
1520
+
1521
+ // Default URI scheme for a script-created resource, matching what the dashboard
1522
+ // generates for an uploaded file — a resource a tool wrote should be addressable
1523
+ // exactly like one a person uploaded, including by sendFile.
1524
+ const CUSTOM_CODE_RESOURCE_URI_PREFIX = 'resource://';
1525
+
1526
+ // How far a script's resource writes reach, declared per tool rather than
1527
+ // assumed.
1528
+ //
1529
+ // `own` — the default — lets a script replace and delete only what a script
1530
+ // created. It is the safe floor: a tool cannot touch a document its owner
1531
+ // uploaded or a page the crawler indexed, so a buggy or model-generated tool has
1532
+ // nothing of the customer's to destroy.
1533
+ //
1534
+ // `all` lifts that, which is what a tool whose job is to prune a stale crawl or
1535
+ // retire an imported folder actually needs. It is declared in the tool's config
1536
+ // and enforced by the broker, so it cannot be granted from inside the script —
1537
+ // the same shape as allowedHosts and connections, and for the same reason: a
1538
+ // capability the code can widen is not a capability, it is a comment.
1539
+ const CUSTOM_CODE_RESOURCE_ACCESS_OWN = 'own' as 'own';
1540
+ const CUSTOM_CODE_RESOURCE_ACCESS_ALL = 'all' as 'all';
1541
+ const CUSTOM_CODE_RESOURCE_ACCESS_VALUES = [
1542
+ CUSTOM_CODE_RESOURCE_ACCESS_OWN,
1543
+ CUSTOM_CODE_RESOURCE_ACCESS_ALL
1544
+ ];
1545
+
1546
+ // Refusing to touch a row a script does not own. Two messages rather than one
1547
+ // because the two calls leave the reader in different positions: the fix for a
1548
+ // create is a different uri, and there is no fix for a delete — the resource
1549
+ // simply is not the script's to remove. Both are phrased for the model that
1550
+ // will read them, which needs to do something else rather than retry.
1551
+ const CUSTOM_CODE_RESOURCE_NOT_OWNED_MESSAGE =
1552
+ 'A resource with this uri already exists on this artifact and was not created by a tool, so it cannot be replaced. Choose a different uri.';
1553
+ const CUSTOM_CODE_RESOURCE_NOT_DELETABLE_MESSAGE =
1554
+ 'This resource was not created by a tool, so it cannot be deleted from one. Set this tool\'s resource access to "all" to allow it, or remove it from the Resources page.';
1555
+
1556
+ // Deleting a parent without saying so would take its children with it through
1557
+ // the FK cascade — silently, and leaving the artifact's counters describing rows
1558
+ // that no longer exist. Refused instead, naming the flag that means it.
1559
+ const CUSTOM_CODE_RESOURCE_HAS_CHILDREN_MESSAGE =
1560
+ 'This resource has children, which would be removed with it. Pass { children: true } to confirm.';
1561
+
1562
+ // Indexing a payload nothing can extract text from produces zero chunks and a
1563
+ // resource that looks indexed and never matches anything. Refused up front.
1564
+ const CUSTOM_CODE_RESOURCE_NOT_EMBEDDABLE_MESSAGE =
1565
+ 'This file type cannot be indexed for search. Create it without index, or pass the text as content.';
1566
+
1567
+ // Validation messages for ctx.resources.create. Constants rather than inline
1568
+ // strings because localizeZodIssue keys its translations on the exact English
1569
+ // text, so the two files have to name the same value.
1570
+ const CUSTOM_CODE_RESOURCE_PAYLOAD_MESSAGE =
1571
+ 'Pass exactly one of content (text) or bytes (base64)';
1572
+ const CUSTOM_CODE_RESOURCE_TEXT_TOO_LARGE_MESSAGE = `Inline content exceeds the ${CUSTOM_CODE_MAX_RESOURCE_TEXT_BYTES / (1024 * 1024)}MB limit`;
1573
+ const CUSTOM_CODE_RESOURCE_FILE_TOO_LARGE_MESSAGE = `File bytes exceed the ${CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES / (1024 * 1024)}MB limit`;
1574
+
1575
+ const MCP_REQUEST_METHOD_INITIALIZE = 'initialize' as 'initialize';
1576
+ const MCP_REQUEST_METHOD_PING = 'ping' as 'ping';
1577
+ const MCP_REQUEST_METHOD_TOOLS_LIST = 'tools/list' as 'tools/list';
1578
+ const MCP_REQUEST_METHOD_TOOLS_CALL = 'tools/call' as 'tools/call';
1579
+ const MCP_REQUEST_METHOD_RESOURCES_LIST = 'resources/list' as 'resources/list';
1580
+ const MCP_REQUEST_METHOD_RESOURCES_TEMPLATES_LIST =
1581
+ 'resources/templates/list' as 'resources/templates/list';
1582
+ const MCP_REQUEST_METHOD_RESOURCES_READ = 'resources/read' as 'resources/read';
1583
+ const MCP_REQUEST_METHOD_PROMPTS_LIST = 'prompts/list' as 'prompts/list';
1584
+ const MCP_REQUEST_METHOD_PROMPTS_GET = 'prompts/get' as 'prompts/get';
1585
+ const MCP_REQUEST_METHODS = [
1586
+ MCP_REQUEST_METHOD_INITIALIZE,
1587
+ MCP_REQUEST_METHOD_PING,
1588
+ MCP_REQUEST_METHOD_TOOLS_LIST,
1589
+ MCP_REQUEST_METHOD_TOOLS_CALL,
1590
+ MCP_REQUEST_METHOD_RESOURCES_LIST,
1591
+ MCP_REQUEST_METHOD_RESOURCES_TEMPLATES_LIST,
1592
+ MCP_REQUEST_METHOD_RESOURCES_READ,
1593
+ MCP_REQUEST_METHOD_PROMPTS_LIST,
1594
+ MCP_REQUEST_METHOD_PROMPTS_GET
1595
+ ];
1596
+
1597
+ const MCP_AUTH_KIND_JWT = 'jwt' as 'jwt';
1598
+ const MCP_AUTH_KIND_INTERNAL = 'internal' as 'internal';
1599
+ const MCP_AUTH_KINDS = [MCP_AUTH_KIND_JWT, MCP_AUTH_KIND_INTERNAL];
1600
+
1601
+ const MCP_SESSION_HEADER = 'mcp-session-id';
1602
+
1603
+ const MCP_INTERNAL_HEADER = 'x-ganju-internal-secret';
1604
+ const MCP_CHANNEL_ID_HEADER = 'x-ganju-channel-id';
1605
+ const MCP_CHANNEL_PLATFORM_HEADER = 'x-ganju-channel-platform';
1606
+ const MCP_CHANNEL_CLIENT_USER_AGENT = 'ganju-channel/0.0.1';
1607
+ const JWKS_KV_KEY = 'jwks:v1';
1608
+ const JWKS_TTL_SECONDS = 600;
1609
+
1610
+ // Standard OIDC scopes the better-auth oauthProvider honors for user OAuth
1611
+ // flows. Advertised in the OAuth discovery documents (RFC 8414
1612
+ // authorization-server metadata and RFC 9728 protected-resource metadata).
1613
+ const OAUTH_SCOPES_SUPPORTED = ['openid', 'profile', 'email', 'offline_access'];
1614
+
1615
+ // Ganju-specific OAuth scopes for MCP access. `mcp:read` is the default scope
1616
+ // minted by the bot-on-behalf-of grant (a custom grant that bypasses the OIDC
1617
+ // authorize endpoint). `artifact:<slug>` — built from this prefix — gates a
1618
+ // subjectless machine token to a single MCP server in the MCP auth middleware.
1619
+ // Neither is in better-auth's OIDC scope allowlist, so they are NOT advertised
1620
+ // in the discovery documents: a standard OIDC client requesting an advertised
1621
+ // scope that isn't allowlisted would be rejected with `invalid_scope`.
1622
+ // TODO: Net: mcp:read is a cosmetic claim on bot tokens; artifact: is a code path that can't fire. Neither affects security today. They become real only if you build the per-server confinement feature (which would make artifact:<slug> issued + enforced). This is more for OIDC.
1623
+ const MCP_SCOPE_READ = 'mcp:read';
1624
+ const ARTIFACT_SCOPE_PREFIX = 'artifact:';
1625
+
1626
+ // The CLI signs in as a public OAuth client through a loopback redirect
1627
+ // (RFC 8252) — the flow `wrangler`, `gh` and `vercel` all use. It registers
1628
+ // itself through RFC 7591 dynamic registration on first login rather than
1629
+ // relying on a client row someone had to provision by hand, and caches the id
1630
+ // it was given.
1631
+ //
1632
+ // A fixed port would fail whenever something else already held it, so the CLI
1633
+ // binds the first one here that is free. All of them are registered up front
1634
+ // rather than only the one that gets used: the provider does implement RFC 8252
1635
+ // loopback matching, where the port is ignored for a 127.0.0.0/8 redirect, but
1636
+ // that is one library's behaviour and this is a login that has already opened
1637
+ // someone's browser by the time it would fail.
1638
+
1639
+ // Recent custom-tool invocations, as `ganju logs` reads them.
1640
+ const CUSTOM_CODE_LOGS_DEFAULT_LIMIT = 20;
1641
+ const CUSTOM_CODE_LOGS_MAX_LIMIT = 100;
1642
+
1643
+ const BOT_GRANT_TYPE = 'urn:ganju:bot-on-behalf-of';
1644
+ const EXTERNAL_LINK_VERIFICATION_PREFIX = 'external_link:';
1645
+ const EXTERNAL_LINK_TTL_SECONDS = 600;
1646
+ const BOT_ACCESS_TOKEN_TTL_SECONDS = 3600;
1647
+
1648
+ const LINK_CODE_LENGTH = 12;
1649
+ const LINK_CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
1650
+
1651
+ const BOT_COMMAND_LINK = 'link';
1652
+ const RESERVED_BOT_COMMANDS = [BOT_COMMAND_LINK];
1653
+
1654
+ const RESERVED_SLUGS = [
1655
+ 'www',
1656
+ 'api',
1657
+ 'app',
1658
+ 'admin',
1659
+ 'auth',
1660
+ 'mcp',
1661
+ 'dev',
1662
+ 'prod',
1663
+ 'staging',
1664
+ 'development',
1665
+ 'production',
1666
+ 'docs',
1667
+ 'blog',
1668
+ 'help',
1669
+ 'support',
1670
+ 'status',
1671
+ 'mail',
1672
+ 'ftp',
1673
+ 'ns',
1674
+ 'ns1',
1675
+ 'ns2',
1676
+ 'cdn',
1677
+ 'static',
1678
+ 'assets',
1679
+ 'auth',
1680
+ 'oauth',
1681
+ 'authentication',
1682
+ 'authorization',
1683
+ 'login',
1684
+ 'logout',
1685
+ 'signin',
1686
+ 'signup',
1687
+ 'register',
1688
+ 'me',
1689
+ 'user',
1690
+ 'users',
1691
+ 'account',
1692
+ 'accounts',
1693
+ 'billing',
1694
+ 'settings',
1695
+ 'health',
1696
+ 'metrics',
1697
+ 'internal'
1698
+ ];
1699
+
1700
+ const PLAN_FREE = 'FREE' as 'FREE';
1701
+ const PLAN_PRO = 'PRO' as 'PRO';
1702
+ const PLAN_ENTERPRISE = 'ENTERPRISE' as 'ENTERPRISE';
1703
+ const PLANS = [PLAN_FREE, PLAN_PRO, PLAN_ENTERPRISE];
1704
+
1705
+ // Mirror Stripe's subscription statuses so the webhook can store them verbatim.
1706
+ const SUBSCRIPTION_STATUS_ACTIVE = 'active' as 'active';
1707
+ const SUBSCRIPTION_STATUS_TRIALING = 'trialing' as 'trialing';
1708
+ const SUBSCRIPTION_STATUS_PAST_DUE = 'past_due' as 'past_due';
1709
+ const SUBSCRIPTION_STATUS_CANCELED = 'canceled' as 'canceled';
1710
+ const SUBSCRIPTION_STATUS_INCOMPLETE = 'incomplete' as 'incomplete';
1711
+ const SUBSCRIPTION_STATUS_INCOMPLETE_EXPIRED =
1712
+ 'incomplete_expired' as 'incomplete_expired';
1713
+ const SUBSCRIPTION_STATUS_UNPAID = 'unpaid' as 'unpaid';
1714
+ const SUBSCRIPTION_STATUS_PAUSED = 'paused' as 'paused';
1715
+ const SUBSCRIPTION_STATUSES = [
1716
+ SUBSCRIPTION_STATUS_ACTIVE,
1717
+ SUBSCRIPTION_STATUS_TRIALING,
1718
+ SUBSCRIPTION_STATUS_PAST_DUE,
1719
+ SUBSCRIPTION_STATUS_CANCELED,
1720
+ SUBSCRIPTION_STATUS_INCOMPLETE,
1721
+ SUBSCRIPTION_STATUS_INCOMPLETE_EXPIRED,
1722
+ SUBSCRIPTION_STATUS_UNPAID,
1723
+ SUBSCRIPTION_STATUS_PAUSED
1724
+ ];
1725
+ // A subscription in one of these states still grants its paid entitlements.
1726
+ // Anything else falls back to the Free plan's limits.
1727
+ const SUBSCRIPTION_ENTITLED_STATUSES = [
1728
+ SUBSCRIPTION_STATUS_ACTIVE,
1729
+ SUBSCRIPTION_STATUS_TRIALING,
1730
+ SUBSCRIPTION_STATUS_PAST_DUE
1731
+ ];
1732
+
1733
+ const MB = 1024 * 1024;
1734
+ const GB = 1024 * 1024 * 1024;
1735
+
1736
+ // Pricing numbers (kept in sync with apps/website/src/lib/pricing.ts).
1737
+ //
1738
+ // $29 rather than $20 is positioning, not margin — $20 is comfortably safe on
1739
+ // the cost model. Code hosting, a CLI and managed OAuth put this beside Zapier
1740
+ // Professional (~$30) and Pipedream (~$29); $20 anchors it as a tool, $29 as a
1741
+ // platform.
1742
+ const PRICING_PRO_BASE_USD = 29;
1743
+ // Total assistant turns included per month, of any kind.
1744
+ const PRICING_INCLUDED_MESSAGES = 3_000;
1745
+ // How many of those included turns may run on the SHARED platform model. Shared
1746
+ // turns draw from the total pool like any other, but only up to this sub-cap —
1747
+ // past it they meter at the shared rate below even if the org is still under its
1748
+ // 3,000. The sub-cap exists because these are the only turns we pay inference on.
1749
+ const PRICING_INCLUDED_SHARED_MESSAGES = 1_000;
1750
+ // Embedded content costs more than it looks. The billed figure is chunk TEXT,
1751
+ // but each chunk also carries its halfvec plus an HNSW index entry — fixed
1752
+ // overhead per chunk, regardless of how little text the chunk holds. At the
1753
+ // current 1536 dimensions that's a ~6.9x expansion, so at Neon's $0.35/GB-month
1754
+ // a billed GB costs us ~$2.41/GB-month. (It was ~12.9x and ~$4.51 at 3072
1755
+ // dimensions, which is what motivated both this allowance and EMBEDDING_DIMENSIONS.)
1756
+ //
1757
+ // 1 GB rather than 5: at 5 GB the included allowance alone would have cost ~$22
1758
+ // of a $29 plan on the old basis, and ~$12 on the current one. 1 GB is still
1759
+ // ~500,000 pages of text — generous for the real use case, and it caps the
1760
+ // downside of a single heavy account.
1761
+ const PRICING_INCLUDED_EMBEDDED_GB = 1;
1762
+ // Two message rates, because the two kinds of turn cost us wildly different
1763
+ // amounts. A turn on the customer's own key costs us ~nothing — $2/1,000 is a
1764
+ // platform fee for running the loop and serving RAG. A turn on our model costs
1765
+ // ~$4/1,000 (up to ~$11 on a tool-rich artifact), so it's sold at $15/1,000: a
1766
+ // real margin rather than a subsidy. Selling it beats blocking — every Free→Pro
1767
+ // converter is on our key the day they convert, so forcing BYO breaks the bot
1768
+ // of every single upgrade.
1769
+ const PRICING_MESSAGE_PER_1K_USD = 2;
1770
+ const PRICING_SHARED_MESSAGE_PER_1K_USD = 15;
1771
+ // Abuse backstop on shared-model turns for paid plans — deliberately absurd as a
1772
+ // month of legitimate use (~3,300/day, and it would invoice five figures), so it
1773
+ // only ever catches a runaway loop or a stolen channel token. Raise it for an
1774
+ // Enterprise contract rather than treating it as a plan feature.
1775
+ const PRICING_SHARED_KEY_HARD_CAP = 100_000;
1776
+ // $2, up from $0.50 — which was set believing storage cost us ~$0.50/GB and was
1777
+ // therefore "at cost". It wasn't: see the expansion note above.
1778
+ //
1779
+ // This sat below cost when it was raised (~$4.51/GB at 3072 dimensions). The gap
1780
+ // was then closed from the COST side rather than by raising the price again:
1781
+ // halving the embedding to 1536 dimensions brought the real figure to ~$2.41/GB,
1782
+ // so $2 is now roughly break-even. Raising CHUNK_TARGET_CHARS would tip it
1783
+ // positive without touching this number.
1784
+ const PRICING_EMBEDDED_PER_GB_USD = 2;
1785
+ const PRICING_CUSTOM_DOMAIN_USD = 15;
1786
+
1787
+ interface PlanLimits {
1788
+ // null on any field means "no hard limit" for that plan.
1789
+ maxProjects: number | null;
1790
+ maxToolsPerArtifact: number | null;
1791
+ maxPromptsPerArtifact: number | null;
1792
+ maxChannelsPerArtifact: number | null;
1793
+ maxRawStorageBytes: number | null;
1794
+ maxEmbeddedBytes: number | null;
1795
+ // Hard monthly cap on assistant channel messages. Free is capped; paid plans
1796
+ // are `null` (metered, not blocked).
1797
+ monthlyMessageCap: number | null;
1798
+ // Monthly allowance of messages that may run on the SHARED platform model (our
1799
+ // key, our inference bill). This is a BILLING threshold, not a block: past it,
1800
+ // paid plans keep running and meter at the shared overage rate, which carries a
1801
+ // real margin over what the inference costs us. Free has no overage path, so it
1802
+ // simply stops — its hard total cap is set to the same number and trips first.
1803
+ includedSharedMessages: number;
1804
+ // Absolute stop on shared-model turns, regardless of billing. Not a pricing
1805
+ // lever — an abuse backstop, set far above any legitimate month so no paying
1806
+ // customer meets it. It bounds what a compromised or runaway channel can spend
1807
+ // of our inference before someone notices, in the window where the charges are
1808
+ // real but the payment hasn't settled. `null` = no backstop.
1809
+ sharedKeyHardCap: number | null;
1810
+ canInvite: boolean;
1811
+ // Whether the org may configure its own LLM (bring-your-own-key). A paid-only
1812
+ // feature: Free orgs run on the shared platform model key (capped); connecting
1813
+ // a private model — which lets the org run its own inference past the shared
1814
+ // allowance — requires upgrading.
1815
+ canUseCustomLlm: boolean;
1816
+ // Whether the org may write tools as code and deploy them. Paid-only, and for
1817
+ // a different reason than the flags above: this one runs the customer's own
1818
+ // code on infrastructure we pay for, which is the first cost axis a user can
1819
+ // turn against us. Free's escape hatch is `http-endpoint`, which already gives
1820
+ // a custom name, description and input schema against their own backend — at
1821
+ // no compute cost to us, because we make one screened request.
1822
+ canUseCustomCode: boolean;
1823
+ // How many `http-endpoint` tools one artifact may hold. Free gets a handful
1824
+ // rather than none: it IS the free tier's custom tool, and capping it is what
1825
+ // keeps that from becoming an unbounded tool list on the plan that runs on our
1826
+ // model key. `null` = no limit.
1827
+ maxHttpEndpointsPerArtifact: number | null;
1828
+ // Display-only allowances included in the plan (what overage is measured
1829
+ // against). Not used for blocking.
1830
+ includedMessages: number;
1831
+ includedEmbeddedBytes: number;
1832
+ }
1833
+
1834
+ const PLAN_LIMITS: Record<
1835
+ typeof PLAN_FREE | typeof PLAN_PRO | typeof PLAN_ENTERPRISE,
1836
+ PlanLimits
1837
+ > = {
1838
+ FREE: {
1839
+ maxProjects: 1,
1840
+ maxToolsPerArtifact: 7,
1841
+ maxPromptsPerArtifact: 3,
1842
+ maxChannelsPerArtifact: 1,
1843
+ maxRawStorageBytes: 30 * MB,
1844
+ maxEmbeddedBytes: 5 * MB,
1845
+ // Free runs on the shared platform model key, so we pay inference. The cap
1846
+ // is deliberately a trial-sized amount, not a home — anyone who wants more
1847
+ // for free can self-host (Apache-2.0). Cost per message is further bounded
1848
+ // by the tighter shared-key turn envelope (history + tool loops) below.
1849
+ monthlyMessageCap: 100,
1850
+ // Free can't bring its own key, so its whole allowance runs on our model:
1851
+ // every one of these three numbers is the same 100 turns seen from a
1852
+ // different angle. There is no overage path off Free — you upgrade.
1853
+ includedSharedMessages: 100,
1854
+ sharedKeyHardCap: 100,
1855
+ canInvite: false,
1856
+ // Free orgs use the shared platform model only; bringing your own model is a
1857
+ // paid feature.
1858
+ canUseCustomLlm: false,
1859
+ canUseCustomCode: false,
1860
+ maxHttpEndpointsPerArtifact: 3,
1861
+ includedMessages: 100,
1862
+ includedEmbeddedBytes: 5 * MB
1863
+ },
1864
+ PRO: {
1865
+ maxProjects: null,
1866
+ maxToolsPerArtifact: null,
1867
+ maxPromptsPerArtifact: null,
1868
+ maxChannelsPerArtifact: null,
1869
+ maxRawStorageBytes: null,
1870
+ maxEmbeddedBytes: null,
1871
+ monthlyMessageCap: null,
1872
+ // Pro has no hard message cap: own-key turns are unlimited and metered, and
1873
+ // shared turns keep running past the included allowance at the shared rate.
1874
+ // A 1,000-turn shared buffer is ~33/day — a typical small-business bot's
1875
+ // entire month — so most Pro customers never see the overage at all.
1876
+ includedSharedMessages: PRICING_INCLUDED_SHARED_MESSAGES,
1877
+ sharedKeyHardCap: PRICING_SHARED_KEY_HARD_CAP,
1878
+ canInvite: true,
1879
+ canUseCustomLlm: true,
1880
+ canUseCustomCode: true,
1881
+ maxHttpEndpointsPerArtifact: null,
1882
+ includedMessages: PRICING_INCLUDED_MESSAGES,
1883
+ includedEmbeddedBytes: PRICING_INCLUDED_EMBEDDED_GB * GB
1884
+ },
1885
+ ENTERPRISE: {
1886
+ maxProjects: null,
1887
+ maxToolsPerArtifact: null,
1888
+ maxPromptsPerArtifact: null,
1889
+ maxChannelsPerArtifact: null,
1890
+ maxRawStorageBytes: null,
1891
+ maxEmbeddedBytes: null,
1892
+ monthlyMessageCap: null,
1893
+ includedSharedMessages: PRICING_INCLUDED_SHARED_MESSAGES,
1894
+ sharedKeyHardCap: PRICING_SHARED_KEY_HARD_CAP,
1895
+ canInvite: true,
1896
+ canUseCustomLlm: true,
1897
+ canUseCustomCode: true,
1898
+ maxHttpEndpointsPerArtifact: null,
1899
+ includedMessages: PRICING_INCLUDED_MESSAGES,
1900
+ includedEmbeddedBytes: PRICING_INCLUDED_EMBEDDED_GB * GB
1901
+ }
1902
+ };
1903
+
1904
+ // Quota features — used as the `feature` discriminator on PlanLimitError so the
1905
+ // dashboard can tailor the upgrade prompt.
1906
+ const PLAN_FEATURE_ORGANIZATION = 'organization' as 'organization';
1907
+ const PLAN_FEATURE_PROJECT = 'project' as 'project';
1908
+ const PLAN_FEATURE_TOOL = 'tool' as 'tool';
1909
+ const PLAN_FEATURE_PROMPT = 'prompt' as 'prompt';
1910
+ const PLAN_FEATURE_CHANNEL = 'channel' as 'channel';
1911
+ const PLAN_FEATURE_INVITE = 'invite' as 'invite';
1912
+ const PLAN_FEATURE_LLM = 'llm' as 'llm';
1913
+ const PLAN_FEATURE_CUSTOM_CODE = 'customCode' as 'customCode';
1914
+ const PLAN_FEATURE_HTTP_ENDPOINT = 'httpEndpoint' as 'httpEndpoint';
1915
+ const PLAN_FEATURE_RAW_STORAGE = 'rawStorage' as 'rawStorage';
1916
+ const PLAN_FEATURE_EMBEDDED_STORAGE = 'embeddedStorage' as 'embeddedStorage';
1917
+ const PLAN_FEATURE_MESSAGE = 'message' as 'message';
1918
+
1919
+ // Stable code returned on a quota block so clients can branch on it (402).
1920
+ const PLAN_LIMIT_ERROR_CODE = 'PLAN_LIMIT_EXCEEDED';
1921
+
1922
+ // Stripe Billing Meter event names. The metering cron reports per-period
1923
+ // OVERAGE (usage above the plan's included allowance) to these meters; the
1924
+ // meters' prices on the subscription turn that into charges. Embedded storage
1925
+ // is reported in whole MB, messages as a raw count.
1926
+ //
1927
+ // Messages report to two separate meters because the two kinds of turn bill at
1928
+ // different rates — a turn on the org's own key is a platform fee, a turn on our
1929
+ // model is inference we bought. One meter can't price both.
1930
+ const STRIPE_METER_MESSAGES = 'ganju_channel_messages';
1931
+ const STRIPE_METER_SHARED_MESSAGES = 'ganju_shared_messages';
1932
+ const STRIPE_METER_EMBEDDED = 'ganju_embedded_storage';
1933
+
1934
+ // Legal documents a user accepts, and the version they're on. Bump the version
1935
+ // when the document changes materially — existing users are then re-prompted,
1936
+ // and the old acceptance stays on record. Keep in sync with the "Last updated"
1937
+ // date on apps/website/src/md/{terms,privacy}.md.
1938
+ const CONSENT_DOCUMENT_TERMS = 'terms' as 'terms';
1939
+ const CONSENT_DOCUMENT_PRIVACY = 'privacy' as 'privacy';
1940
+ const CONSENT_DOCUMENTS = [CONSENT_DOCUMENT_TERMS, CONSENT_DOCUMENT_PRIVACY];
1941
+ const CONSENT_CURRENT_VERSION = '2026-08-01';
1942
+
1943
+ const CONSENT_SOURCE_SIGNUP = 'signup' as 'signup';
1944
+ const CONSENT_SOURCE_REACCEPT = 'reaccept' as 'reaccept';
1945
+ const CONSENT_SOURCES = [CONSENT_SOURCE_SIGNUP, CONSENT_SOURCE_REACCEPT];
1946
+
1947
+ /**
1948
+ * Retention windows, in days, for the append-only tables that would otherwise
1949
+ * grow forever. These are the numbers the privacy policy publishes — change
1950
+ * one here and the policy has to change with it.
1951
+ *
1952
+ * `mcpRequest` is the sharpest of these: it stores the arguments AND results of
1953
+ * every tool call, which can include mail bodies, calendar entries, and drive
1954
+ * documents pulled from a connected account.
1955
+ */
1956
+ const RETENTION_DAYS = {
1957
+ // Tool-call arguments and results.
1958
+ mcpRequest: 90,
1959
+ // Stack traces, paths, IPs.
1960
+ errorLog: 90,
1961
+ // Channel conversation history.
1962
+ channelMessage: 365,
1963
+ // "Who ran what, when" audit rows.
1964
+ artifactExecution: 365
1965
+ } as const;
1966
+
1967
+ // Sessions are purged once they've been expired for this long — the row is
1968
+ // useless after expiry, but a short grace period keeps debugging possible.
1969
+ const RETENTION_EXPIRED_SESSION_DAYS = 30;
1970
+
1971
+ // Error alerting. The sweep emails a digest of new server-side failures so a
1972
+ // breach or outage is noticed rather than sitting in a table — the DPA commits
1973
+ // to notifying customers within 72 hours of BECOMING AWARE, which requires a
1974
+ // way to become aware.
1975
+ // Cron expressions from apps/api/wrangler.toml. The scheduled handler branches
1976
+ // on these, so the strings must match the config exactly.
1977
+ const CRON_HOURLY = '0 * * * *';
1978
+ const CRON_ERROR_ALERTS = '*/15 * * * *';
1979
+
1980
+ const ALERT_STATE_KEY_ERROR_LOG = 'error_log';
1981
+ // Only alert on genuine server failures. 4xx rows are expected client errors
1982
+ // (validation, not-found, quota) and would drown the signal.
1983
+ const ALERT_MIN_STATUS = 500;
1984
+ // Rows pulled per run. A larger backlog is counted but not itemised.
1985
+ const ALERT_MAX_ROWS = 500;
1986
+ // Distinct error signatures listed in the email body.
1987
+ const ALERT_MAX_GROUPS = 20;
1988
+ // Message prefix length used to group errors into a signature.
1989
+ const ALERT_SIGNATURE_LENGTH = 120;
1990
+ // At or above this many errors in one run, the subject is flagged as a spike.
1991
+ const ALERT_SPIKE_THRESHOLD = 25;
1992
+
1993
+ // Rows deleted per table per purge run. The cron runs hourly, so this drains a
1994
+ // backlog over a few passes instead of holding one very long transaction.
1995
+ const RETENTION_PURGE_BATCH = 5_000;
1996
+
1997
+ export type { PlanLimits };
1998
+
1999
+ export const constants = {
2000
+ MB,
2001
+ GB,
2002
+ PLAN_FREE,
2003
+ PLAN_PRO,
2004
+ PLAN_ENTERPRISE,
2005
+ PLANS,
2006
+ SUBSCRIPTION_STATUS_ACTIVE,
2007
+ SUBSCRIPTION_STATUS_TRIALING,
2008
+ SUBSCRIPTION_STATUS_PAST_DUE,
2009
+ SUBSCRIPTION_STATUS_CANCELED,
2010
+ SUBSCRIPTION_STATUS_INCOMPLETE,
2011
+ SUBSCRIPTION_STATUS_INCOMPLETE_EXPIRED,
2012
+ SUBSCRIPTION_STATUS_UNPAID,
2013
+ SUBSCRIPTION_STATUS_PAUSED,
2014
+ SUBSCRIPTION_STATUSES,
2015
+ SUBSCRIPTION_ENTITLED_STATUSES,
2016
+ PLAN_LIMITS,
2017
+ PRICING_PRO_BASE_USD,
2018
+ PRICING_INCLUDED_MESSAGES,
2019
+ PRICING_INCLUDED_SHARED_MESSAGES,
2020
+ PRICING_SHARED_MESSAGE_PER_1K_USD,
2021
+ PRICING_SHARED_KEY_HARD_CAP,
2022
+ PRICING_INCLUDED_EMBEDDED_GB,
2023
+ PRICING_MESSAGE_PER_1K_USD,
2024
+ PRICING_EMBEDDED_PER_GB_USD,
2025
+ PRICING_CUSTOM_DOMAIN_USD,
2026
+ PLAN_FEATURE_ORGANIZATION,
2027
+ PLAN_FEATURE_PROJECT,
2028
+ PLAN_FEATURE_TOOL,
2029
+ PLAN_FEATURE_PROMPT,
2030
+ PLAN_FEATURE_CHANNEL,
2031
+ PLAN_FEATURE_INVITE,
2032
+ PLAN_FEATURE_LLM,
2033
+ PLAN_FEATURE_CUSTOM_CODE,
2034
+ PLAN_FEATURE_HTTP_ENDPOINT,
2035
+ PLAN_FEATURE_RAW_STORAGE,
2036
+ PLAN_FEATURE_EMBEDDED_STORAGE,
2037
+ PLAN_FEATURE_MESSAGE,
2038
+ PLAN_LIMIT_ERROR_CODE,
2039
+ STRIPE_METER_MESSAGES,
2040
+ STRIPE_METER_SHARED_MESSAGES,
2041
+ STRIPE_METER_EMBEDDED,
2042
+ CONSENT_DOCUMENT_TERMS,
2043
+ CONSENT_DOCUMENT_PRIVACY,
2044
+ CONSENT_DOCUMENTS,
2045
+ CONSENT_CURRENT_VERSION,
2046
+ CONSENT_SOURCE_SIGNUP,
2047
+ CONSENT_SOURCE_REACCEPT,
2048
+ CONSENT_SOURCES,
2049
+ RETENTION_DAYS,
2050
+ RETENTION_EXPIRED_SESSION_DAYS,
2051
+ RETENTION_PURGE_BATCH,
2052
+ CRON_HOURLY,
2053
+ CRON_ERROR_ALERTS,
2054
+ ALERT_STATE_KEY_ERROR_LOG,
2055
+ ALERT_MIN_STATUS,
2056
+ ALERT_MAX_ROWS,
2057
+ ALERT_MAX_GROUPS,
2058
+ ALERT_SIGNATURE_LENGTH,
2059
+ ALERT_SPIKE_THRESHOLD,
2060
+ USER_ROLE_ADMIN,
2061
+ USER_ROLES,
2062
+ INVITATION_STATUS,
2063
+ INVITATION_SCOPE_ORGANIZATION,
2064
+ INVITATION_SCOPE_PROJECT,
2065
+ INVITATION_SCOPES,
2066
+ INVITATION_RESPONSE_ACCEPT,
2067
+ INVITATION_RESPONSE_DECLINE,
2068
+ INVITATION_RESPONSES,
2069
+ INVITATION_EXPIRY_DAYS,
2070
+ INVITATION_TOKEN_BYTES,
2071
+ CONTACT_MAX_NAME_LENGTH,
2072
+ CONTACT_MAX_EMAIL_LENGTH,
2073
+ CONTACT_MIN_MESSAGE_LENGTH,
2074
+ CONTACT_MAX_MESSAGE_LENGTH,
2075
+ SLUG_BYTES,
2076
+ SLUG_PATTERN,
2077
+ STATUS_COMPLETED,
2078
+ STATUS_PENDING,
2079
+ STATUS_FAILED,
2080
+ STATUS_ACTIVE,
2081
+ STATUS_DISABLED,
2082
+ STATUS_BLOCKED,
2083
+ STATUS,
2084
+ USER_STATUS,
2085
+ CHANNEL_STATUS,
2086
+ RESOURCE_STATUS,
2087
+ SERVICE_NAME_API,
2088
+ SERVICE_NAME_MCP,
2089
+ SERVICE_NAME_RESOURCE_HANDLER,
2090
+ SERVICE_NAME_TOOL_BROKER,
2091
+ SOCIAL_PROVIDER_GOOGLE,
2092
+ SOCIAL_PROVIDER_GITHUB,
2093
+ SOCIAL_PROVIDERS,
2094
+ OAUTH_PROVIDER_GOOGLE_GMAIL,
2095
+ OAUTH_PROVIDER_GOOGLE_DRIVE,
2096
+ OAUTH_PROVIDER_GOOGLE_CALENDAR,
2097
+ OAUTH_PROVIDER_MICROSOFT_OUTLOOK,
2098
+ OAUTH_PROVIDER_ONE_DRIVE,
2099
+ OAUTH_PROVIDER_SLACK,
2100
+ OAUTH_PROVIDER_SLACK_USER,
2101
+ OAUTH_PROVIDERS,
2102
+ GOOGLE_OAUTH_AUTH_URL,
2103
+ MICROSOFT_OAUTH_AUTH_URL,
2104
+ SLACK_OAUTH_AUTH_URL,
2105
+ OAUTH_AUTH_URLS,
2106
+ GOOGLE_OAUTH_TOKEN_URL,
2107
+ MICROSOFT_OAUTH_TOKEN_URL,
2108
+ SLACK_OAUTH_TOKEN_URL,
2109
+ OAUTH_TOKEN_URLS,
2110
+ CREDENTIAL_REFRESH_BUFFER_MS,
2111
+ GOOGLE_CALENDAR_API_BASE,
2112
+ GOOGLE_DRIVE_API_BASE,
2113
+ GOOGLE_DRIVE_DEFAULT_PAGE_SIZE,
2114
+ GOOGLE_DRIVE_MAX_FOLDER_PAGES,
2115
+ GOOGLE_DRIVE_TOKEN_REFRESH_LEEWAY_MS,
2116
+ GOOGLE_DRIVE_URI_PREFIX,
2117
+ GOOGLE_DRIVE_FILE_FIELDS,
2118
+ GOOGLE_DRIVE_LIST_FIELDS,
2119
+ GOOGLE_DRIVE_TAB_MY_DRIVE,
2120
+ GOOGLE_DRIVE_TAB_SHARED_WITH_ME,
2121
+ GOOGLE_DRIVE_TAB_SHARED_DRIVES,
2122
+ GOOGLE_DRIVE_TAB_STARRED,
2123
+ GOOGLE_DRIVE_TABS,
2124
+ GOOGLE_DRIVE_TAB_LABEL_MY_DRIVE,
2125
+ GOOGLE_DRIVE_TAB_LABEL_SHARED_WITH_ME,
2126
+ GOOGLE_DRIVE_TAB_LABEL_SHARED_DRIVES,
2127
+ GOOGLE_DRIVE_TAB_LABEL_STARRED,
2128
+ GOOGLE_DRIVE_TAB_LABELS,
2129
+ MICROSOFT_GRAPH_API_BASE,
2130
+ ONE_DRIVE_DEFAULT_PAGE_SIZE,
2131
+ ONE_DRIVE_MAX_FOLDER_PAGES,
2132
+ ONE_DRIVE_TOKEN_REFRESH_LEEWAY_MS,
2133
+ ONE_DRIVE_URI_PREFIX,
2134
+ ONE_DRIVE_ITEM_SELECT,
2135
+ ONE_DRIVE_TAB_MY_FILES,
2136
+ ONE_DRIVE_TAB_SHARED_WITH_ME,
2137
+ ONE_DRIVE_TAB_RECENT,
2138
+ ONE_DRIVE_TAB_DRIVES,
2139
+ ONE_DRIVE_TABS,
2140
+ ONE_DRIVE_TAB_LABEL_MY_FILES,
2141
+ ONE_DRIVE_TAB_LABEL_SHARED_WITH_ME,
2142
+ ONE_DRIVE_TAB_LABEL_RECENT,
2143
+ ONE_DRIVE_TAB_LABEL_DRIVES,
2144
+ ONE_DRIVE_TAB_LABELS,
2145
+ REAUTH_ERROR_CODES,
2146
+ RESOURCE_TYPE_TOOLTIP_MIN_WIDTH,
2147
+ SEARCH_DEBOUNCE_MS,
2148
+ GOOGLE_DRIVE_EXPORT_MIME_TYPES,
2149
+ GOOGLE_DRIVE_EXPORT_EXTENSIONS,
2150
+ SERVICE_NAMES,
2151
+ SCHEMA_DEFINITION_TYPES,
2152
+ ROLE_MESSAGE_USER,
2153
+ ROLE_MESSAGE_ASSISTANT,
2154
+ ROLE_MESSAGE_SYSTEM,
2155
+ ROLE_MESSAGE_TOOL,
2156
+ ROLE_MESSAGES,
2157
+ CHANNEL_ROLE_MESSAGES,
2158
+ LANGUAGE_EN,
2159
+ LANGUAGE_ES,
2160
+ LANGUAGES,
2161
+ SPANISH_COUNTRIES,
2162
+ LANGUAGE_COOKIE,
2163
+ LANGUAGE_COOKIE_MAX_AGE,
2164
+ LANGUAGE_QUERY_PARAM,
2165
+ MIMETYPE_TEXT,
2166
+ MIMETYPE_TEXT_CSV,
2167
+ MIMETYPE_TEXT_HTML,
2168
+ MIMETYPE_TEXT_MARKDOWN,
2169
+ MIMETYPE_IMAGE_PNG,
2170
+ MIMETYPE_IMAGE_GIF,
2171
+ MIMETYPE_IMAGE_JPEG,
2172
+ MIMETYPE_IMAGE_WEBP,
2173
+ MIMETYPE_IMAGE_SVG_XML,
2174
+ MIMETYPE_APPLICATION_PDF,
2175
+ MIMETYPE_APPLICATION_JSON,
2176
+ MIMETYPE_APPLICATION_MSWORD,
2177
+ MIMETYPE_APPLICATION_VND_MS_EXCEL,
2178
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_SPREADSHEETML_SHEET,
2179
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_WORDPROCESSINGML_DOCUMENT,
2180
+ MIMETYPE_APPLICATION_VND_OPENXMLFORMATS_OFFICEDOCUMENT_PRESENTATIONML_PRESENTATION,
2181
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_DOCUMENT,
2182
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_SPREADSHEET,
2183
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_PRESENTATION,
2184
+ MIMETYPE_APPLICATION_VND_GOOGLE_APPS_FOLDER,
2185
+ MIMETYPE_APPLICATION_XML,
2186
+ MIMETYPE_APPLICATION_JAVASCRIPT,
2187
+ MIMETYPE_APPLICATION_TYPESCRIPT,
2188
+ MIMETYPE_APPLICATION_YAML,
2189
+ MIMETYPE_APPLICATION_X_YAML,
2190
+ MIMETYPE_APPLICATION_TOML,
2191
+ MIMETYPE_APPLICATION_X_SH,
2192
+ MIMETYPE_APPLICATION_SQL,
2193
+ MIMETYPE_APPLICATION_GRAPHQL,
2194
+ MIMETYPE_APPLICATION_LD_JSON,
2195
+ MIMETYPE_APPLICATION_XHTML_XML,
2196
+ MIMETYPE_APPLICATION_X_HTTPD_PHP,
2197
+ MIMETYPE_APPLICATION_X_PYTHON_CODE,
2198
+ MIMETYPE_APPLICATION_X_WWW_FORM_URLENCODED,
2199
+ MIMETYPE_APPLICATION_CSV,
2200
+ MIMETYPE_APPLICATION_X_RUBY,
2201
+ MIMETYPE_APPLICATION_X_PERL,
2202
+ MIMETYPE_APPLICATION_OCTET_STREAM,
2203
+ MIMETYPES,
2204
+ EXTENSION_BY_MIME,
2205
+ TEXT_MIME_TYPES,
2206
+ EMBEDDABLE_MIME_TYPES,
2207
+ USER_AVATAR_MIME_TYPES,
2208
+ RESOURCE_TYPE_STATIC,
2209
+ RESOURCE_TYPE_TEMPLATE,
2210
+ RESOURCE_TYPES,
2211
+ RESOURCE_SOURCE_TYPE_FILE,
2212
+ RESOURCE_SOURCE_TYPE_WEBSITE,
2213
+ RESOURCE_SOURCE_TYPE_GOOGLE_DRIVE_FOLDER,
2214
+ RESOURCE_SOURCE_TYPE_ONE_DRIVE_FOLDER,
2215
+ RESOURCE_SOURCE_TYPE_CUSTOM_CODE,
2216
+ RESOURCE_SOURCE_TYPES,
2217
+ CRAWL_RENDERER_CHEERIO,
2218
+ CRAWL_RENDERER_PLAYWRIGHT,
2219
+ CRAWL_RENDERERS,
2220
+ CRAWL_DEFAULT_MAX_PAGES,
2221
+ CRAWL_MAX_PAGES_LIMIT,
2222
+ CRAWL_DEFAULT_MAX_DEPTH,
2223
+ CRAWL_MAX_DEPTH_LIMIT,
2224
+ CRAWL_PAGE_FETCH_TIMEOUT_MS,
2225
+ CRAWL_PAGE_QUEUE_BATCH_SIZE,
2226
+ CRAWL_USER_AGENT,
2227
+ RESOURCE_ICON_THEME_DARK,
2228
+ RESOURCE_ICON_THEME_LIGHT,
2229
+ RESOURCE_ICON_THEMES,
2230
+ MAX_FILE_SIZE,
2231
+ MAX_AVATAR_SIZE,
2232
+ GMAIL_MAX_RAW_ATTACHMENT_BYTES,
2233
+ OUTLOOK_ATTACHMENT_INLINE_THRESHOLD,
2234
+ OUTLOOK_MAX_ATTACHMENT_BYTES,
2235
+ OUTLOOK_UPLOAD_CHUNK_BYTES,
2236
+ SLACK_API_BASE,
2237
+ SLACK_MAX_UPLOAD_BYTES,
2238
+ TELEGRAM_MAX_PHOTO_BYTES,
2239
+ TELEGRAM_MAX_FILE_BYTES,
2240
+ RATE_LIMIT_BACKOFF_SECONDS,
2241
+ TRANSIENT_BACKOFF_SECONDS,
2242
+ ENCODINGS,
2243
+ ENCODING_UTF8,
2244
+ CHANNEL_PLATFORM_TELEGRAM,
2245
+ CHANNEL_PLATFORM_SLACK,
2246
+ CHANNEL_PLATFORM_WHATSAPP,
2247
+ CHANNEL_PLATFORM_DISCORD,
2248
+ CHANNEL_PLATFORMS,
2249
+ CHANNEL_CONVERSATION_SCOPE_PRIVATE,
2250
+ CHANNEL_CONVERSATION_SCOPE_GROUP,
2251
+ CHANNEL_CONVERSATION_SCOPE_CHANNEL,
2252
+ CHANNEL_CONVERSATION_SCOPES,
2253
+ USAGE_KIND_PROMPT,
2254
+ USAGE_KIND_RESOURCE,
2255
+ USAGE_KIND_TOOL,
2256
+ USAGE_KINDS,
2257
+ LLM_PROVIDER_ANTHROPIC,
2258
+ LLM_PROVIDER_OPENAI,
2259
+ LLM_PROVIDER_OPENAI_COMPATIBLE,
2260
+ LLM_PROVIDER_GOOGLE,
2261
+ LLM_PROVIDERS,
2262
+ DEFAULT_LLM_PROVIDER,
2263
+ DEFAULT_LLM_MODEL,
2264
+ DEFAULT_LLM_SYSTEM_PROMPT,
2265
+ LLM_CATALOG,
2266
+ LLM_SYSTEM_DEFAULT,
2267
+ MAX_TOOL_LOOPS,
2268
+ CHANNEL_HISTORY_LIMIT,
2269
+ SHARED_KEY_HISTORY_LIMIT,
2270
+ SHARED_KEY_MAX_TOOL_LOOPS,
2271
+ CHANNEL_MAX_TOOLS,
2272
+ CHANNEL_DEBOUNCE_DEFAULT_MS,
2273
+ CHANNEL_DEBOUNCE_MIN_MS,
2274
+ CHANNEL_DEBOUNCE_MAX_MS,
2275
+ CHANNEL_DEBOUNCE_MAX_WAIT_MS,
2276
+ CHANNEL_DEBOUNCE_MAX_MESSAGES,
2277
+ CHANNEL_DEBOUNCE_DISABLED,
2278
+ CHANNEL_DEBOUNCE_JOIN,
2279
+ CHANNEL_DEBOUNCE_RETRY_MS,
2280
+ CHANNEL_DEBOUNCE_MAX_ATTEMPTS,
2281
+ TELEGRAM_SECRET_HEADER,
2282
+ TELEGRAM_API_BASE,
2283
+ TELEGRAM_MESSAGE_LIMIT,
2284
+ SLACK_SIGNATURE_HEADER,
2285
+ SLACK_TIMESTAMP_HEADER,
2286
+ SLACK_RETRY_NUM_HEADER,
2287
+ SLACK_SIGNATURE_VERSION,
2288
+ SLACK_SIGNATURE_MAX_SKEW_SECONDS,
2289
+ SLACK_MESSAGE_LIMIT,
2290
+ SLACK_MAX_SOURCE_BUTTONS,
2291
+ SLACK_REQUIRED_SCOPES,
2292
+ SLACK_RECOMMENDED_SCOPES,
2293
+ SLACK_BOT_EVENTS,
2294
+ DISCORD_API_BASE,
2295
+ DISCORD_GATEWAY_QUERY,
2296
+ DISCORD_MESSAGE_LIMIT,
2297
+ DISCORD_MAX_UPLOAD_BYTES,
2298
+ DISCORD_MAX_SOURCE_BUTTONS,
2299
+ DISCORD_INTENTS,
2300
+ DISCORD_SIGNATURE_HEADER,
2301
+ DISCORD_TIMESTAMP_HEADER,
2302
+ DISCORD_INTERACTION_TYPE_PING,
2303
+ DISCORD_INTERACTION_TYPE_APPLICATION_COMMAND,
2304
+ DISCORD_INTERACTION_RESPONSE_PONG,
2305
+ DISCORD_INTERACTION_RESPONSE_DEFERRED,
2306
+ DISCORD_CHANNEL_TYPE_DM,
2307
+ DISCORD_CHANNEL_TYPE_GROUP_DM,
2308
+ DISCORD_GATEWAY_OP_DISPATCH,
2309
+ DISCORD_GATEWAY_OP_HEARTBEAT,
2310
+ DISCORD_GATEWAY_OP_IDENTIFY,
2311
+ DISCORD_GATEWAY_OP_RESUME,
2312
+ DISCORD_GATEWAY_OP_RECONNECT,
2313
+ DISCORD_GATEWAY_OP_INVALID_SESSION,
2314
+ DISCORD_GATEWAY_OP_HELLO,
2315
+ DISCORD_GATEWAY_OP_HEARTBEAT_ACK,
2316
+ DISCORD_GATEWAY_DEFAULT_HEARTBEAT_MS,
2317
+ WHATSAPP_API_BASE,
2318
+ WHATSAPP_API_VERSION,
2319
+ WHATSAPP_MESSAGE_LIMIT,
2320
+ WHATSAPP_SIGNATURE_HEADER,
2321
+ WHATSAPP_SIGNATURE_PREFIX,
2322
+ WHATSAPP_HUB_MODE_PARAM,
2323
+ WHATSAPP_HUB_VERIFY_TOKEN_PARAM,
2324
+ WHATSAPP_HUB_CHALLENGE_PARAM,
2325
+ WHATSAPP_HUB_MODE_SUBSCRIBE,
2326
+ WHATSAPP_MAX_IMAGE_BYTES,
2327
+ WHATSAPP_MAX_VIDEO_BYTES,
2328
+ WHATSAPP_MAX_AUDIO_BYTES,
2329
+ WHATSAPP_MAX_DOCUMENT_BYTES,
2330
+ EMBEDDING_MODEL,
2331
+ EMBEDDING_DIMENSIONS,
2332
+ CHUNK_TARGET_CHARS,
2333
+ CHUNK_OVERLAP_CHARS,
2334
+ EMBED_BATCH_SIZE,
2335
+ RESOURCE_HANDLER_SLEEP_AFTER,
2336
+ DOCS_URL,
2337
+ BASE64_DATA_URI_RE,
2338
+ RAW_BASE64_BLOB_RE,
2339
+ CHUNK_SEPARATORS,
2340
+ DEFAULT_MAX_TOKENS,
2341
+ RESOURCE_TOOL_KEY_LIST_RESOURCES,
2342
+ RESOURCE_TOOL_KEY_SEARCH_RESOURCES,
2343
+ RESOURCE_TOOL_KEY_READ_RESOURCE,
2344
+ RESOURCE_TOOL_KEY_SEND_RESOURCE,
2345
+ PROMPT_TOOL_KEY_LIST_PROMPTS,
2346
+ RESOURCE_TOOL_KEYS,
2347
+ URI_BEARING_RESOURCE_TOOL_KEYS,
2348
+ CALENDAR_TOOL_KEY_PREFIX,
2349
+ CALENDAR_SEND_UPDATES_ALL,
2350
+ CALENDAR_SEND_UPDATES_EXTERNAL_ONLY,
2351
+ CALENDAR_SEND_UPDATES_NONE,
2352
+ CALENDAR_SEND_UPDATES_VALUES,
2353
+ CALENDAR_VISIBILITY_DEFAULT,
2354
+ CALENDAR_VISIBILITY_PUBLIC,
2355
+ CALENDAR_VISIBILITY_PRIVATE,
2356
+ CALENDAR_VISIBILITY_VALUES,
2357
+ CALENDAR_DEFAULT_CALENDAR_ID,
2358
+ CALENDAR_DEFAULT_EVENT_DURATION_MINUTES,
2359
+ CALENDAR_CONFERENCE_TYPE_GOOGLE_MEET,
2360
+ CALENDAR_TOOL_FIELDS,
2361
+ API_KEY_PROVIDER_CALCOM,
2362
+ API_KEY_PROVIDER_TAVILY,
2363
+ API_KEY_PROVIDERS,
2364
+ CREDENTIAL_PROVIDER_HTTP_ENDPOINT,
2365
+ CREDENTIAL_PROVIDER_MCP_PROXY,
2366
+ CREDENTIAL_PROVIDER_CUSTOM_CODE,
2367
+ PER_TOOL_CREDENTIAL_PROVIDERS,
2368
+ CREDENTIAL_PROVIDERS,
2369
+ CALCOM_API_BASE,
2370
+ CALCOM_API_VERSION_EVENT_TYPES,
2371
+ CALCOM_API_VERSION_SLOTS,
2372
+ CALCOM_API_VERSION_BOOKINGS,
2373
+ CALCOM_TOOL_KEY_PREFIX,
2374
+ TAVILY_API_BASE,
2375
+ TAVILY_SEARCH_DEPTH_BASIC,
2376
+ TAVILY_SEARCH_DEPTH_ADVANCED,
2377
+ TAVILY_SEARCH_DEPTHS,
2378
+ TAVILY_TOPIC_GENERAL,
2379
+ TAVILY_TOPIC_NEWS,
2380
+ TAVILY_TOPICS,
2381
+ TAVILY_DEFAULT_MAX_RESULTS,
2382
+ TAVILY_MAX_RESULTS_LIMIT,
2383
+ WEB_TOOL_KEY_PREFIX,
2384
+ WEB_TOOL_KEY_SEARCH,
2385
+ WEB_TOOL_KEY_EXTRACT,
2386
+ WEB_TOOL_KEYS,
2387
+ TOOL_DEFINITION_KEY_HTTP_ENDPOINT,
2388
+ HTTP_ENDPOINT_METHOD_GET,
2389
+ HTTP_ENDPOINT_METHOD_POST,
2390
+ HTTP_ENDPOINT_METHOD_PUT,
2391
+ HTTP_ENDPOINT_METHOD_PATCH,
2392
+ HTTP_ENDPOINT_METHOD_DELETE,
2393
+ HTTP_ENDPOINT_METHODS,
2394
+ HTTP_ENDPOINT_BODY_KIND_NONE,
2395
+ HTTP_ENDPOINT_BODY_KIND_JSON,
2396
+ HTTP_ENDPOINT_BODY_KIND_FORM,
2397
+ HTTP_ENDPOINT_BODY_KIND_TEXT,
2398
+ HTTP_ENDPOINT_BODY_KINDS,
2399
+ HTTP_ENDPOINT_AUTH_KIND_NONE,
2400
+ HTTP_ENDPOINT_AUTH_KIND_BEARER,
2401
+ HTTP_ENDPOINT_AUTH_KIND_BASIC,
2402
+ HTTP_ENDPOINT_AUTH_KIND_API_KEY,
2403
+ HTTP_ENDPOINT_AUTH_KIND_OAUTH,
2404
+ HTTP_ENDPOINT_AUTH_KINDS,
2405
+ HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_AUTO,
2406
+ HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_JSON,
2407
+ HTTP_ENDPOINT_RESPONSE_CONTENT_TYPE_TEXT,
2408
+ HTTP_ENDPOINT_DEFAULT_TIMEOUT_MS,
2409
+ HTTP_ENDPOINT_MAX_TIMEOUT_MS,
2410
+ HTTP_ENDPOINT_DEFAULT_MAX_BYTES,
2411
+ HTTP_ENDPOINT_MAX_REQUEST_BYTES,
2412
+ TOOL_DEFINITION_KEY_MCP_PROXY,
2413
+ MCP_PROXY_TRANSPORT_STREAMABLE_HTTP,
2414
+ MCP_PROXY_TRANSPORT_SSE,
2415
+ MCP_PROXY_TRANSPORTS,
2416
+ MCP_PROXY_AUTH_KIND_NONE,
2417
+ MCP_PROXY_AUTH_KIND_BEARER,
2418
+ MCP_PROXY_AUTH_KIND_HEADER,
2419
+ MCP_PROXY_AUTH_KIND_OAUTH,
2420
+ MCP_PROXY_AUTH_KINDS,
2421
+ MCP_PROXY_DEFAULT_TIMEOUT_MS,
2422
+ MCP_PROXY_MAX_TIMEOUT_MS,
2423
+ MCP_PROXY_MAX_TOOLS,
2424
+ MCP_PROXY_MAX_RESPONSE_BYTES,
2425
+ MCP_PROXY_TOOL_NAME_SEP,
2426
+ MCP_PROXY_TOOL_NAME_MAX,
2427
+ TOOL_DEFINITION_KEY_CUSTOM_CODE,
2428
+ CUSTOM_CODE_VERSION_STATUS_DRAFT,
2429
+ CUSTOM_CODE_VERSION_STATUS_PUBLISHED,
2430
+ CUSTOM_CODE_VERSION_STATUS_ARCHIVED,
2431
+ CUSTOM_CODE_SOURCE_KIND_EDITOR,
2432
+ CUSTOM_CODE_SOURCE_KIND_BUNDLE,
2433
+ CUSTOM_CODE_SOURCE_KINDS,
2434
+ CUSTOM_CODE_SDK_MODULE,
2435
+ CUSTOM_CODE_SDK_SPECIFIER,
2436
+ CUSTOM_CODE_MAIN_MODULE,
2437
+ CUSTOM_CODE_MAX_FILES,
2438
+ CUSTOM_CODE_MAX_FILE_PATH,
2439
+ CUSTOM_CODE_VERSION_STATUSES,
2440
+ CUSTOM_CODE_SCRIPT_NAME_PREFIX,
2441
+ CUSTOM_CODE_PREVIEW_SCRIPT_SUFFIX,
2442
+ CUSTOM_CODE_PREVIEW_TOKEN_TTL_MS,
2443
+ CUSTOM_CODE_TEST_TIMEOUT_MS,
2444
+ CUSTOM_CODE_SOURCE_KEY_PREFIX,
2445
+ CUSTOM_CODE_MAX_TOOLS,
2446
+ CUSTOM_CODE_TOOL_NAME_MAX,
2447
+ CUSTOM_CODE_DEFAULT_TIMEOUT_MS,
2448
+ CUSTOM_CODE_MAX_TIMEOUT_MS,
2449
+ CUSTOM_CODE_MAX_RESPONSE_BYTES,
2450
+ CUSTOM_CODE_MAX_BUNDLE_BYTES,
2451
+ CUSTOM_CODE_INVOKE_ORIGIN,
2452
+ CUSTOM_CODE_INVOKE_PATH,
2453
+ CUSTOM_CODE_HEALTH_TOOL,
2454
+ RESERVED_TOOL_NAME_PREFIXES,
2455
+ RESERVED_TOOL_NAMES,
2456
+ RESERVED_TOOL_NAME_MESSAGE,
2457
+ CUSTOM_CODE_UNKNOWN_CONNECTION_MESSAGE,
2458
+ OUTPUT_SCHEMA_NOT_OBJECT_MESSAGE,
2459
+ CUSTOM_CODE_BINDING_TOKEN,
2460
+ CUSTOM_CODE_BINDING_BROKER,
2461
+ CUSTOM_CODE_BROKER_ORIGIN,
2462
+ CUSTOM_CODE_BROKER_PATH_CONNECTION,
2463
+ CUSTOM_CODE_BROKER_PATH_SECRET,
2464
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_SEARCH,
2465
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_READ,
2466
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_LIST,
2467
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_CREATE,
2468
+ CUSTOM_CODE_BROKER_PATH_RESOURCES_DELETE,
2469
+ CUSTOM_CODE_BROKER_PATH_SEND_FILE,
2470
+ CUSTOM_CODE_OUTBOUND_PARAM_ARTIFACT_ID,
2471
+ CUSTOM_CODE_OUTBOUND_PARAM_ALLOWED_HOSTS,
2472
+ CUSTOM_CODE_PLATFORM_HOSTS,
2473
+ CUSTOM_CODE_TOKEN_SECRET_ENV,
2474
+ CUSTOM_CODE_TOKEN_VERSION,
2475
+ CUSTOM_CODE_ACCOUNT_ID_ENV,
2476
+ CUSTOM_CODE_API_TOKEN_ENV,
2477
+ CUSTOM_CODE_NAMESPACE_ENV,
2478
+ CUSTOM_CODE_BROKER_SERVICE_ENV,
2479
+ CUSTOM_CODE_COMPATIBILITY_DATE,
2480
+ CUSTOM_CODE_SCRIPT_CPU_MS,
2481
+ CUSTOM_CODE_MAX_LOGS,
2482
+ CUSTOM_CODE_MAX_LOG_LENGTH,
2483
+ CUSTOM_CODE_SEND_FILE_TARGET_GMAIL,
2484
+ CUSTOM_CODE_SEND_FILE_TARGET_OUTLOOK,
2485
+ CUSTOM_CODE_SEND_FILE_TARGET_SLACK,
2486
+ CUSTOM_CODE_SEND_FILE_TARGETS,
2487
+ CUSTOM_CODE_SEND_FILE_PROVIDERS,
2488
+ CUSTOM_CODE_SEND_FILE_PATHS,
2489
+ CUSTOM_CODE_SEND_FILE_MAX_URIS,
2490
+ CUSTOM_CODE_MAX_RESOURCE_TEXT_BYTES,
2491
+ CUSTOM_CODE_MAX_RESOURCE_FILE_BYTES,
2492
+ CUSTOM_CODE_RESOURCE_URI_PREFIX,
2493
+ CUSTOM_CODE_RESOURCE_NOT_OWNED_MESSAGE,
2494
+ CUSTOM_CODE_RESOURCE_NOT_DELETABLE_MESSAGE,
2495
+ CUSTOM_CODE_RESOURCE_HAS_CHILDREN_MESSAGE,
2496
+ CUSTOM_CODE_RESOURCE_NOT_EMBEDDABLE_MESSAGE,
2497
+ CUSTOM_CODE_RESOURCE_ACCESS_OWN,
2498
+ CUSTOM_CODE_RESOURCE_ACCESS_ALL,
2499
+ CUSTOM_CODE_RESOURCE_ACCESS_VALUES,
2500
+ CUSTOM_CODE_RESOURCE_PAYLOAD_MESSAGE,
2501
+ CUSTOM_CODE_RESOURCE_TEXT_TOO_LARGE_MESSAGE,
2502
+ CUSTOM_CODE_RESOURCE_FILE_TOO_LARGE_MESSAGE,
2503
+ RESERVED_SLUGS,
2504
+ MCP_INTERNAL_HEADER,
2505
+ MCP_CHANNEL_ID_HEADER,
2506
+ MCP_CHANNEL_PLATFORM_HEADER,
2507
+ MCP_CHANNEL_CLIENT_USER_AGENT,
2508
+ MCP_SESSION_HEADER,
2509
+ MCP_REQUEST_METHOD_INITIALIZE,
2510
+ MCP_REQUEST_METHOD_PING,
2511
+ MCP_REQUEST_METHOD_TOOLS_LIST,
2512
+ MCP_REQUEST_METHOD_TOOLS_CALL,
2513
+ MCP_REQUEST_METHOD_RESOURCES_LIST,
2514
+ MCP_REQUEST_METHOD_RESOURCES_TEMPLATES_LIST,
2515
+ MCP_REQUEST_METHOD_RESOURCES_READ,
2516
+ MCP_REQUEST_METHOD_PROMPTS_LIST,
2517
+ MCP_REQUEST_METHOD_PROMPTS_GET,
2518
+ MCP_REQUEST_METHODS,
2519
+ MCP_AUTH_KIND_JWT,
2520
+ MCP_AUTH_KIND_INTERNAL,
2521
+ MCP_AUTH_KINDS,
2522
+ JWKS_KV_KEY,
2523
+ JWKS_TTL_SECONDS,
2524
+ OAUTH_SCOPES_SUPPORTED,
2525
+ MCP_SCOPE_READ,
2526
+ ARTIFACT_SCOPE_PREFIX,
2527
+ CLI_OAUTH_CLIENT_NAME,
2528
+ CLI_OAUTH_REDIRECT_PATH,
2529
+ CLI_OAUTH_REDIRECT_PORTS,
2530
+ CLI_OAUTH_SCOPES,
2531
+ CLI_TOKEN_REFRESH_SKEW_SECONDS,
2532
+ CUSTOM_CODE_LOGS_DEFAULT_LIMIT,
2533
+ CUSTOM_CODE_LOGS_MAX_LIMIT,
2534
+ BOT_GRANT_TYPE,
2535
+ EXTERNAL_LINK_VERIFICATION_PREFIX,
2536
+ EXTERNAL_LINK_TTL_SECONDS,
2537
+ BOT_ACCESS_TOKEN_TTL_SECONDS,
2538
+ LINK_CODE_LENGTH,
2539
+ LINK_CODE_ALPHABET,
2540
+ BOT_COMMAND_LINK,
2541
+ RESERVED_BOT_COMMANDS
2542
+ };