@slates/provider 1.0.0-rc.29 → 1.0.0-rc.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -52,6 +52,7 @@ __export(index_exports, {
52
52
  SlatePublicTool: () => SlatePublicTool,
53
53
  SlateSpecification: () => SlateSpecification,
54
54
  SlateTool: () => SlateTool,
55
+ SlateToolCategory: () => SlateToolCategory,
55
56
  SlateTrigger: () => SlateTrigger,
56
57
  SlateTriggerGroup: () => SlateTriggerGroup,
57
58
  SlateTriggerGroupBuilder: () => SlateTriggerGroupBuilder,
@@ -112,6 +113,7 @@ __export(index_exports, {
112
113
  spec: () => spec,
113
114
  statusLabelForApiError: () => statusLabelForApiError,
114
115
  tool: () => tool,
116
+ toolCategory: () => toolCategory,
115
117
  trigger: () => trigger,
116
118
  triggerGroup: () => triggerGroup,
117
119
  truncateApiErrorMessage: () => truncateApiErrorMessage,
@@ -2178,6 +2180,33 @@ var SlatePublicTool = class {
2178
2180
  };
2179
2181
  var publicTool = (spec2, params) => SlatePublicTool.create(spec2, params);
2180
2182
 
2183
+ // src/action/toolCategory.ts
2184
+ var SlateToolCategory = class _SlateToolCategory {
2185
+ constructor(_params) {
2186
+ this._params = _params;
2187
+ }
2188
+ _params;
2189
+ static create(params) {
2190
+ return new _SlateToolCategory(params);
2191
+ }
2192
+ get key() {
2193
+ return this._params.key;
2194
+ }
2195
+ get name() {
2196
+ return this._params.name;
2197
+ }
2198
+ get description() {
2199
+ return this._params.description;
2200
+ }
2201
+ get imageUrl() {
2202
+ return this._params.imageUrl;
2203
+ }
2204
+ get tools() {
2205
+ return this._params.tools;
2206
+ }
2207
+ };
2208
+ var toolCategory = (params) => SlateToolCategory.create(params);
2209
+
2181
2210
  // src/action/trigger.ts
2182
2211
  var SlateTrigger = class _SlateTrigger extends SlateAction {
2183
2212
  #triggerGroup;
@@ -3201,16 +3230,20 @@ var setIfDefined = (target, key, value) => {
3201
3230
 
3202
3231
  // src/specification/slate.ts
3203
3232
  var Slate = class _Slate {
3204
- constructor(_spec, _actions, _adapters, _triggerGroups) {
3233
+ constructor(_spec, _actions, _adapters, _triggerGroups, _toolCategories, _toolCategoryKeysByActionKey) {
3205
3234
  this._spec = _spec;
3206
3235
  this._actions = _actions;
3207
3236
  this._adapters = _adapters;
3208
3237
  this._triggerGroups = _triggerGroups;
3238
+ this._toolCategories = _toolCategories;
3239
+ this._toolCategoryKeysByActionKey = _toolCategoryKeysByActionKey;
3209
3240
  }
3210
3241
  _spec;
3211
3242
  _actions;
3212
3243
  _adapters;
3213
3244
  _triggerGroups;
3245
+ _toolCategories;
3246
+ _toolCategoryKeysByActionKey;
3214
3247
  static create(params) {
3215
3248
  let adapters = params.adapters ?? [];
3216
3249
  let actions = [...params.triggers, ...params.tools];
@@ -3254,7 +3287,16 @@ var Slate = class _Slate {
3254
3287
  `Only one trigger group may use manual webhook registration, but found: ${manualWebhookTriggerGroups.map((group) => group.key).join(", ")}`
3255
3288
  );
3256
3289
  }
3257
- return new _Slate(params.spec, actions, adapters, triggerGroups);
3290
+ let toolCategories = params.toolCategories ?? [];
3291
+ let toolCategoryKeysByActionKey = resolveToolCategoryMembership(toolCategories, actions);
3292
+ return new _Slate(
3293
+ params.spec,
3294
+ actions,
3295
+ adapters,
3296
+ triggerGroups,
3297
+ toolCategories,
3298
+ toolCategoryKeysByActionKey
3299
+ );
3258
3300
  }
3259
3301
  get spec() {
3260
3302
  return this._spec;
@@ -3268,6 +3310,79 @@ var Slate = class _Slate {
3268
3310
  get triggerGroups() {
3269
3311
  return this._triggerGroups ?? [];
3270
3312
  }
3313
+ get toolCategories() {
3314
+ return this._toolCategories ?? [];
3315
+ }
3316
+ // Keys of the tool categories an action belongs to, in declared category order.
3317
+ getToolCategoryKeys(actionKey) {
3318
+ return [...this._toolCategoryKeysByActionKey?.get(actionKey) ?? []];
3319
+ }
3320
+ };
3321
+ var TOOL_CATEGORY_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
3322
+ var isHttpUrl = (value) => {
3323
+ try {
3324
+ let url = new URL(value);
3325
+ return url.protocol === "https:" || url.protocol === "http:";
3326
+ } catch {
3327
+ return false;
3328
+ }
3329
+ };
3330
+ var resolveToolCategoryMembership = (toolCategories, actions) => {
3331
+ let effectiveActions = /* @__PURE__ */ new Map();
3332
+ for (let action of actions) {
3333
+ if (!effectiveActions.has(action.key)) effectiveActions.set(action.key, action);
3334
+ }
3335
+ let registeredActions = new Set(actions);
3336
+ let membersByCategoryKey = /* @__PURE__ */ new Map();
3337
+ for (let category of toolCategories) {
3338
+ if (!TOOL_CATEGORY_KEY_PATTERN.test(category.key)) {
3339
+ throw new SlateDeclarationError(
3340
+ `Tool category key "${category.key}" must match ${TOOL_CATEGORY_KEY_PATTERN}`
3341
+ );
3342
+ }
3343
+ if (membersByCategoryKey.has(category.key)) {
3344
+ throw new SlateDeclarationError(
3345
+ `Tool category "${category.key}" is registered more than once`
3346
+ );
3347
+ }
3348
+ if (!category.name?.trim()) {
3349
+ throw new SlateDeclarationError(`Tool category "${category.key}" must have a name`);
3350
+ }
3351
+ if (category.imageUrl !== void 0 && !isHttpUrl(category.imageUrl)) {
3352
+ throw new SlateDeclarationError(
3353
+ `Tool category "${category.key}" imageUrl must be an absolute http(s) URL`
3354
+ );
3355
+ }
3356
+ if (!category.tools?.length) {
3357
+ throw new SlateDeclarationError(
3358
+ `Tool category "${category.key}" must list at least one tool`
3359
+ );
3360
+ }
3361
+ let members = /* @__PURE__ */ new Set();
3362
+ for (let tool2 of category.tools) {
3363
+ if (!registeredActions.has(tool2)) {
3364
+ throw new SlateDeclarationError(
3365
+ `Tool category "${category.key}" lists tool "${tool2.key}", which is not registered in this slate`
3366
+ );
3367
+ }
3368
+ if (tool2.type !== "tool") {
3369
+ throw new SlateDeclarationError(
3370
+ `Tool category "${category.key}" lists trigger "${tool2.key}", but only tools can belong to a tool category`
3371
+ );
3372
+ }
3373
+ if (effectiveActions.get(tool2.key) === tool2) members.add(tool2.key);
3374
+ }
3375
+ membersByCategoryKey.set(category.key, members);
3376
+ }
3377
+ let keysByActionKey = /* @__PURE__ */ new Map();
3378
+ for (let category of toolCategories) {
3379
+ for (let actionKey of membersByCategoryKey.get(category.key) ?? []) {
3380
+ let keys = keysByActionKey.get(actionKey) ?? [];
3381
+ keys.push(category.key);
3382
+ keysByActionKey.set(actionKey, keys);
3383
+ }
3384
+ }
3385
+ return keysByActionKey;
3271
3386
  };
3272
3387
 
3273
3388
  // src/specification/specification.ts
@@ -3397,6 +3512,7 @@ var skipWebhook = (reason, response) => ({
3397
3512
  SlatePublicTool,
3398
3513
  SlateSpecification,
3399
3514
  SlateTool,
3515
+ SlateToolCategory,
3400
3516
  SlateTrigger,
3401
3517
  SlateTriggerGroup,
3402
3518
  SlateTriggerGroupBuilder,
@@ -3457,6 +3573,7 @@ var skipWebhook = (reason, response) => ({
3457
3573
  spec,
3458
3574
  statusLabelForApiError,
3459
3575
  tool,
3576
+ toolCategory,
3460
3577
  trigger,
3461
3578
  triggerGroup,
3462
3579
  truncateApiErrorMessage,
package/dist/index.d.cts CHANGED
@@ -857,6 +857,25 @@ declare let allOf: (...inputs: SlateActionScopesInput[]) => SlateActionScopes;
857
857
  declare let mergeScopes: (...inputs: Array<SlateActionScopes | null | undefined | false>) => SlateActionScopes | undefined;
858
858
  declare let validateScopes: (scopes: SlateActionScopes) => SlateActionScopes;
859
859
 
860
+ interface SlateToolCategoryParameters {
861
+ key: string;
862
+ name: string;
863
+ description?: string;
864
+ imageUrl?: string;
865
+ tools: SlateTool<any, any, any, any>[];
866
+ }
867
+ declare class SlateToolCategory {
868
+ private readonly _params;
869
+ private constructor();
870
+ static create(params: SlateToolCategoryParameters): SlateToolCategory;
871
+ get key(): string;
872
+ get name(): string;
873
+ get description(): string | undefined;
874
+ get imageUrl(): string | undefined;
875
+ get tools(): SlateTool<any, any, any, any>[];
876
+ }
877
+ declare let toolCategory: (params: SlateToolCategoryParameters) => SlateToolCategory;
878
+
860
879
  interface SlateTriggerParameters<ConfigType extends {}, AuthType extends {}, InputType extends {}, OutputType extends {
861
880
  type: string;
862
881
  }> extends SlateActionParameters {
@@ -1123,6 +1142,8 @@ declare class Slate<ConfigType extends {}, AuthType extends {}> {
1123
1142
  private readonly _actions;
1124
1143
  private readonly _adapters;
1125
1144
  private readonly _triggerGroups;
1145
+ private readonly _toolCategories;
1146
+ private readonly _toolCategoryKeysByActionKey;
1126
1147
  private constructor();
1127
1148
  static create<ConfigType extends {}, AuthType extends {}>(params: {
1128
1149
  spec: SlateSpecification<ConfigType, AuthType>;
@@ -1130,11 +1151,14 @@ declare class Slate<ConfigType extends {}, AuthType extends {}> {
1130
1151
  tools: SlateTool<ConfigType, AuthType, any, any>[];
1131
1152
  adapters?: SlateAdapter<ConfigType, AuthType>[];
1132
1153
  triggerGroups?: SlateTriggerGroup<ConfigType, AuthType>[];
1154
+ toolCategories?: SlateToolCategory[];
1133
1155
  }): Slate<ConfigType, AuthType>;
1134
1156
  get spec(): SlateSpecification<ConfigType, AuthType>;
1135
1157
  get actions(): (SlateTrigger<ConfigType, AuthType, any, any> | SlateTool<ConfigType, AuthType, any, any>)[];
1136
1158
  get adapters(): SlateAdapter<ConfigType, AuthType>[];
1137
1159
  get triggerGroups(): SlateTriggerGroup<ConfigType, AuthType, Record<string, unknown>>[];
1160
+ get toolCategories(): SlateToolCategory[];
1161
+ getToolCategoryKeys(actionKey: string): string[];
1138
1162
  }
1139
1163
 
1140
1164
  declare const graphWebhookHttp: {
@@ -1168,4 +1192,4 @@ declare let skipWebhook: (reason: string, response?: Response | SlateWebhookHttp
1168
1192
  response: Response | SlateWebhookHttpResponseInit;
1169
1193
  };
1170
1194
 
1171
- export { API_ERROR_MAX_DETAILS, API_ERROR_MAX_MESSAGE_LENGTH, API_ERROR_TRUNCATED_PLACEHOLDER, AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX, type ApiErrorBuildHelpers, type ApiErrorDetailOptions, type ApiErrorResponse, type ApiErrorStatus, type ApiErrorTextOptions, type ApiServiceError, type AuthConfigSecretFilter, AuthConfigSecretRedactor, type BoundedApiErrorDetailOptions, type BuildApiServiceErrorOptions, type CreateHmacSignatureOptions, type DecodeApiErrorBodyOptions, GET_FILE_URL_TOOL_ID, type GetFileUrlHandler, type GetFileUrlToolInput, type GetFileUrlToolOutput, type NormalizeOAuthTokenResponseOptions, type OAuthExpiresAtOptions, type OAuthRefreshTokenFallbackMode, type OAuthTokenResponse, Slate, SlateAction, SlateActionBuilder, type SlateActionCreateParameters, type SlateActionDocsReference, type SlateActionParameters, type SlateActionParametersAny, type SlateActionParametersTool, type SlateActionParametersTrigger, type SlateActionScopeClause, type SlateActionScopeClauseInput, type SlateActionScopeValue, type SlateActionScopes, type SlateActionScopesInput, type SlateActionType, SlateAdapter, type SlateAdapterCapability, SlateAdapterSpec, type SlateAdapterSpecParameters, type SlateAddAttachmentContent, type SlateAddAttachmentInput, type SlateAttachment, type SlateAttachmentInlineContent, type SlateAttachmentUploadReferenceContent, type SlateAttachmentUrlContent, SlateAuth, type SlateAuthDocsReference, type SlateAuthType, type SlateAuthWithCustomData, type SlateAuthWithNone, type SlateAuthWithOauth, type SlateAuthWithServiceAccount, type SlateAuthWithToken, type SlateAuthenticatedAxiosDefaults, type SlateAxiosDefaults, type SlateAxiosErrorAdapter, type SlateAxiosErrorOptions, SlateConfig, type SlateConfigDocsReference, SlateContext, SlateDeclarationError, SlateDefaultPollingIntervalSeconds, type SlateDirectUploadInput, type SlateDocsReference, SlateError, type SlateErrorInput, type SlateErrorIssue, type SlateErrorKind, type SlateErrorProviderInfo, type SlateErrorResponse, type SlateErrorUpstreamInfo, type SlateGatewayEvent, type SlateLiveInvocationInfo, type SlateLogEntry, type SlateLogFields, type SlateLogListener, type SlateLogMessageInput, type SlateLogType, SlateLogger, type SlateProviderDocsReference, SlatePublicContext, SlatePublicTool, type SlatePublicToolInvocationHandler, type SlatePublicToolParameters, type SlateServiceErrorData, SlateSpecification, type SlateSpecificationCreateParameters, type SlateSpecificationParameters, SlateTool, type SlateToolInvocationHandler, type SlateToolParameters, SlateTrigger, SlateTriggerGroup, SlateTriggerGroupBuilder, type SlateTriggerGroupCreateParameters, type SlateTriggerGroupGatewayConnectHandler, type SlateTriggerGroupGatewayParameters, type SlateTriggerGroupGatewayReceiveHandler, type SlateTriggerGroupParameters, type SlateTriggerGroupPollHandler, type SlateTriggerGroupPollingParameters, type SlateTriggerGroupRoutingMatchersHandler, type SlateTriggerGroupWebhookAutoRegistrationParameters, type SlateTriggerGroupWebhookManualRegistrationParameters, type SlateTriggerGroupWebhookParameters, type SlateTriggerMappingHandler, type SlateTriggerMatchesHandler, type SlateTriggerParameters, type SlateTriggerRoutingMatcher, type SlateTypedDocsReference, type SlateWebhookHttpResponseInit, type SlateWebhookManualFinishHandler, type SlateWebhookManualSetupHandler, type SlateWebhookProcessHandler, type SlateWebhookRegisterHandler, type SlateWebhookSkipped, type SlateWebhookTarget, type SlateWebhookTargetListHandler, type SlateWebhookTargetOwnership, type SlateWebhookUnregisterHandler, type VerifyHmacSignatureOptions, adapterSpec, allOf, anyOf, auth, axios, buildApiServiceError, collectApiErrorDetails, config, createApiServiceError, createAuthenticatedAxios, createAxios, createBase64Attachment, createHmacSignature, createSlateError, createTextAttachment, createUrlAttachment, decodeApiErrorBody, extractApiErrorMessage, getApiErrorResponse, getApiErrorResponseWithBody, getApiErrorStatus, getBase64ByteLength, getCurrentContext, getFileUrlTool, getFileUrlToolInputSchema, getFileUrlToolOutputSchema, getGraphWebhookValidationResponse, getMetaWebhookVerificationResponse, getOAuthExpiresAtFromExpiresIn, getResponseHeaderValue, getServiceErrorData, graphWebhookHttp, inferSlateErrorFromAxios, isApiErrorRecord, isAxiosErrorLike, isServiceErrorData, isUrlEmbeddedCredential, looksLikeHtmlErrorBody, mapServiceErrorCode, mapServiceErrorToSlateErrorInput, mergeScopes, metaWebhookHttp, normalizeOAuthTokenResponse, pickDefined, publicTool, pushBoundedApiErrorDetail, readApiErrorText, redactUrlAttachmentSecrets, requestAxios, requestAxiosData, runWithContext, scopeClause, setIfDefined, skipWebhook, spec, statusLabelForApiError, tool, trigger, triggerGroup, truncateApiErrorMessage, uploadAttachmentDirect, validateScopes, verifyHmacSignature };
1195
+ export { API_ERROR_MAX_DETAILS, API_ERROR_MAX_MESSAGE_LENGTH, API_ERROR_TRUNCATED_PLACEHOLDER, AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX, type ApiErrorBuildHelpers, type ApiErrorDetailOptions, type ApiErrorResponse, type ApiErrorStatus, type ApiErrorTextOptions, type ApiServiceError, type AuthConfigSecretFilter, AuthConfigSecretRedactor, type BoundedApiErrorDetailOptions, type BuildApiServiceErrorOptions, type CreateHmacSignatureOptions, type DecodeApiErrorBodyOptions, GET_FILE_URL_TOOL_ID, type GetFileUrlHandler, type GetFileUrlToolInput, type GetFileUrlToolOutput, type NormalizeOAuthTokenResponseOptions, type OAuthExpiresAtOptions, type OAuthRefreshTokenFallbackMode, type OAuthTokenResponse, Slate, SlateAction, SlateActionBuilder, type SlateActionCreateParameters, type SlateActionDocsReference, type SlateActionParameters, type SlateActionParametersAny, type SlateActionParametersTool, type SlateActionParametersTrigger, type SlateActionScopeClause, type SlateActionScopeClauseInput, type SlateActionScopeValue, type SlateActionScopes, type SlateActionScopesInput, type SlateActionType, SlateAdapter, type SlateAdapterCapability, SlateAdapterSpec, type SlateAdapterSpecParameters, type SlateAddAttachmentContent, type SlateAddAttachmentInput, type SlateAttachment, type SlateAttachmentInlineContent, type SlateAttachmentUploadReferenceContent, type SlateAttachmentUrlContent, SlateAuth, type SlateAuthDocsReference, type SlateAuthType, type SlateAuthWithCustomData, type SlateAuthWithNone, type SlateAuthWithOauth, type SlateAuthWithServiceAccount, type SlateAuthWithToken, type SlateAuthenticatedAxiosDefaults, type SlateAxiosDefaults, type SlateAxiosErrorAdapter, type SlateAxiosErrorOptions, SlateConfig, type SlateConfigDocsReference, SlateContext, SlateDeclarationError, SlateDefaultPollingIntervalSeconds, type SlateDirectUploadInput, type SlateDocsReference, SlateError, type SlateErrorInput, type SlateErrorIssue, type SlateErrorKind, type SlateErrorProviderInfo, type SlateErrorResponse, type SlateErrorUpstreamInfo, type SlateGatewayEvent, type SlateLiveInvocationInfo, type SlateLogEntry, type SlateLogFields, type SlateLogListener, type SlateLogMessageInput, type SlateLogType, SlateLogger, type SlateProviderDocsReference, SlatePublicContext, SlatePublicTool, type SlatePublicToolInvocationHandler, type SlatePublicToolParameters, type SlateServiceErrorData, SlateSpecification, type SlateSpecificationCreateParameters, type SlateSpecificationParameters, SlateTool, SlateToolCategory, type SlateToolCategoryParameters, type SlateToolInvocationHandler, type SlateToolParameters, SlateTrigger, SlateTriggerGroup, SlateTriggerGroupBuilder, type SlateTriggerGroupCreateParameters, type SlateTriggerGroupGatewayConnectHandler, type SlateTriggerGroupGatewayParameters, type SlateTriggerGroupGatewayReceiveHandler, type SlateTriggerGroupParameters, type SlateTriggerGroupPollHandler, type SlateTriggerGroupPollingParameters, type SlateTriggerGroupRoutingMatchersHandler, type SlateTriggerGroupWebhookAutoRegistrationParameters, type SlateTriggerGroupWebhookManualRegistrationParameters, type SlateTriggerGroupWebhookParameters, type SlateTriggerMappingHandler, type SlateTriggerMatchesHandler, type SlateTriggerParameters, type SlateTriggerRoutingMatcher, type SlateTypedDocsReference, type SlateWebhookHttpResponseInit, type SlateWebhookManualFinishHandler, type SlateWebhookManualSetupHandler, type SlateWebhookProcessHandler, type SlateWebhookRegisterHandler, type SlateWebhookSkipped, type SlateWebhookTarget, type SlateWebhookTargetListHandler, type SlateWebhookTargetOwnership, type SlateWebhookUnregisterHandler, type VerifyHmacSignatureOptions, adapterSpec, allOf, anyOf, auth, axios, buildApiServiceError, collectApiErrorDetails, config, createApiServiceError, createAuthenticatedAxios, createAxios, createBase64Attachment, createHmacSignature, createSlateError, createTextAttachment, createUrlAttachment, decodeApiErrorBody, extractApiErrorMessage, getApiErrorResponse, getApiErrorResponseWithBody, getApiErrorStatus, getBase64ByteLength, getCurrentContext, getFileUrlTool, getFileUrlToolInputSchema, getFileUrlToolOutputSchema, getGraphWebhookValidationResponse, getMetaWebhookVerificationResponse, getOAuthExpiresAtFromExpiresIn, getResponseHeaderValue, getServiceErrorData, graphWebhookHttp, inferSlateErrorFromAxios, isApiErrorRecord, isAxiosErrorLike, isServiceErrorData, isUrlEmbeddedCredential, looksLikeHtmlErrorBody, mapServiceErrorCode, mapServiceErrorToSlateErrorInput, mergeScopes, metaWebhookHttp, normalizeOAuthTokenResponse, pickDefined, publicTool, pushBoundedApiErrorDetail, readApiErrorText, redactUrlAttachmentSecrets, requestAxios, requestAxiosData, runWithContext, scopeClause, setIfDefined, skipWebhook, spec, statusLabelForApiError, tool, toolCategory, trigger, triggerGroup, truncateApiErrorMessage, uploadAttachmentDirect, validateScopes, verifyHmacSignature };
package/dist/index.d.ts CHANGED
@@ -857,6 +857,25 @@ declare let allOf: (...inputs: SlateActionScopesInput[]) => SlateActionScopes;
857
857
  declare let mergeScopes: (...inputs: Array<SlateActionScopes | null | undefined | false>) => SlateActionScopes | undefined;
858
858
  declare let validateScopes: (scopes: SlateActionScopes) => SlateActionScopes;
859
859
 
860
+ interface SlateToolCategoryParameters {
861
+ key: string;
862
+ name: string;
863
+ description?: string;
864
+ imageUrl?: string;
865
+ tools: SlateTool<any, any, any, any>[];
866
+ }
867
+ declare class SlateToolCategory {
868
+ private readonly _params;
869
+ private constructor();
870
+ static create(params: SlateToolCategoryParameters): SlateToolCategory;
871
+ get key(): string;
872
+ get name(): string;
873
+ get description(): string | undefined;
874
+ get imageUrl(): string | undefined;
875
+ get tools(): SlateTool<any, any, any, any>[];
876
+ }
877
+ declare let toolCategory: (params: SlateToolCategoryParameters) => SlateToolCategory;
878
+
860
879
  interface SlateTriggerParameters<ConfigType extends {}, AuthType extends {}, InputType extends {}, OutputType extends {
861
880
  type: string;
862
881
  }> extends SlateActionParameters {
@@ -1123,6 +1142,8 @@ declare class Slate<ConfigType extends {}, AuthType extends {}> {
1123
1142
  private readonly _actions;
1124
1143
  private readonly _adapters;
1125
1144
  private readonly _triggerGroups;
1145
+ private readonly _toolCategories;
1146
+ private readonly _toolCategoryKeysByActionKey;
1126
1147
  private constructor();
1127
1148
  static create<ConfigType extends {}, AuthType extends {}>(params: {
1128
1149
  spec: SlateSpecification<ConfigType, AuthType>;
@@ -1130,11 +1151,14 @@ declare class Slate<ConfigType extends {}, AuthType extends {}> {
1130
1151
  tools: SlateTool<ConfigType, AuthType, any, any>[];
1131
1152
  adapters?: SlateAdapter<ConfigType, AuthType>[];
1132
1153
  triggerGroups?: SlateTriggerGroup<ConfigType, AuthType>[];
1154
+ toolCategories?: SlateToolCategory[];
1133
1155
  }): Slate<ConfigType, AuthType>;
1134
1156
  get spec(): SlateSpecification<ConfigType, AuthType>;
1135
1157
  get actions(): (SlateTrigger<ConfigType, AuthType, any, any> | SlateTool<ConfigType, AuthType, any, any>)[];
1136
1158
  get adapters(): SlateAdapter<ConfigType, AuthType>[];
1137
1159
  get triggerGroups(): SlateTriggerGroup<ConfigType, AuthType, Record<string, unknown>>[];
1160
+ get toolCategories(): SlateToolCategory[];
1161
+ getToolCategoryKeys(actionKey: string): string[];
1138
1162
  }
1139
1163
 
1140
1164
  declare const graphWebhookHttp: {
@@ -1168,4 +1192,4 @@ declare let skipWebhook: (reason: string, response?: Response | SlateWebhookHttp
1168
1192
  response: Response | SlateWebhookHttpResponseInit;
1169
1193
  };
1170
1194
 
1171
- export { API_ERROR_MAX_DETAILS, API_ERROR_MAX_MESSAGE_LENGTH, API_ERROR_TRUNCATED_PLACEHOLDER, AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX, type ApiErrorBuildHelpers, type ApiErrorDetailOptions, type ApiErrorResponse, type ApiErrorStatus, type ApiErrorTextOptions, type ApiServiceError, type AuthConfigSecretFilter, AuthConfigSecretRedactor, type BoundedApiErrorDetailOptions, type BuildApiServiceErrorOptions, type CreateHmacSignatureOptions, type DecodeApiErrorBodyOptions, GET_FILE_URL_TOOL_ID, type GetFileUrlHandler, type GetFileUrlToolInput, type GetFileUrlToolOutput, type NormalizeOAuthTokenResponseOptions, type OAuthExpiresAtOptions, type OAuthRefreshTokenFallbackMode, type OAuthTokenResponse, Slate, SlateAction, SlateActionBuilder, type SlateActionCreateParameters, type SlateActionDocsReference, type SlateActionParameters, type SlateActionParametersAny, type SlateActionParametersTool, type SlateActionParametersTrigger, type SlateActionScopeClause, type SlateActionScopeClauseInput, type SlateActionScopeValue, type SlateActionScopes, type SlateActionScopesInput, type SlateActionType, SlateAdapter, type SlateAdapterCapability, SlateAdapterSpec, type SlateAdapterSpecParameters, type SlateAddAttachmentContent, type SlateAddAttachmentInput, type SlateAttachment, type SlateAttachmentInlineContent, type SlateAttachmentUploadReferenceContent, type SlateAttachmentUrlContent, SlateAuth, type SlateAuthDocsReference, type SlateAuthType, type SlateAuthWithCustomData, type SlateAuthWithNone, type SlateAuthWithOauth, type SlateAuthWithServiceAccount, type SlateAuthWithToken, type SlateAuthenticatedAxiosDefaults, type SlateAxiosDefaults, type SlateAxiosErrorAdapter, type SlateAxiosErrorOptions, SlateConfig, type SlateConfigDocsReference, SlateContext, SlateDeclarationError, SlateDefaultPollingIntervalSeconds, type SlateDirectUploadInput, type SlateDocsReference, SlateError, type SlateErrorInput, type SlateErrorIssue, type SlateErrorKind, type SlateErrorProviderInfo, type SlateErrorResponse, type SlateErrorUpstreamInfo, type SlateGatewayEvent, type SlateLiveInvocationInfo, type SlateLogEntry, type SlateLogFields, type SlateLogListener, type SlateLogMessageInput, type SlateLogType, SlateLogger, type SlateProviderDocsReference, SlatePublicContext, SlatePublicTool, type SlatePublicToolInvocationHandler, type SlatePublicToolParameters, type SlateServiceErrorData, SlateSpecification, type SlateSpecificationCreateParameters, type SlateSpecificationParameters, SlateTool, type SlateToolInvocationHandler, type SlateToolParameters, SlateTrigger, SlateTriggerGroup, SlateTriggerGroupBuilder, type SlateTriggerGroupCreateParameters, type SlateTriggerGroupGatewayConnectHandler, type SlateTriggerGroupGatewayParameters, type SlateTriggerGroupGatewayReceiveHandler, type SlateTriggerGroupParameters, type SlateTriggerGroupPollHandler, type SlateTriggerGroupPollingParameters, type SlateTriggerGroupRoutingMatchersHandler, type SlateTriggerGroupWebhookAutoRegistrationParameters, type SlateTriggerGroupWebhookManualRegistrationParameters, type SlateTriggerGroupWebhookParameters, type SlateTriggerMappingHandler, type SlateTriggerMatchesHandler, type SlateTriggerParameters, type SlateTriggerRoutingMatcher, type SlateTypedDocsReference, type SlateWebhookHttpResponseInit, type SlateWebhookManualFinishHandler, type SlateWebhookManualSetupHandler, type SlateWebhookProcessHandler, type SlateWebhookRegisterHandler, type SlateWebhookSkipped, type SlateWebhookTarget, type SlateWebhookTargetListHandler, type SlateWebhookTargetOwnership, type SlateWebhookUnregisterHandler, type VerifyHmacSignatureOptions, adapterSpec, allOf, anyOf, auth, axios, buildApiServiceError, collectApiErrorDetails, config, createApiServiceError, createAuthenticatedAxios, createAxios, createBase64Attachment, createHmacSignature, createSlateError, createTextAttachment, createUrlAttachment, decodeApiErrorBody, extractApiErrorMessage, getApiErrorResponse, getApiErrorResponseWithBody, getApiErrorStatus, getBase64ByteLength, getCurrentContext, getFileUrlTool, getFileUrlToolInputSchema, getFileUrlToolOutputSchema, getGraphWebhookValidationResponse, getMetaWebhookVerificationResponse, getOAuthExpiresAtFromExpiresIn, getResponseHeaderValue, getServiceErrorData, graphWebhookHttp, inferSlateErrorFromAxios, isApiErrorRecord, isAxiosErrorLike, isServiceErrorData, isUrlEmbeddedCredential, looksLikeHtmlErrorBody, mapServiceErrorCode, mapServiceErrorToSlateErrorInput, mergeScopes, metaWebhookHttp, normalizeOAuthTokenResponse, pickDefined, publicTool, pushBoundedApiErrorDetail, readApiErrorText, redactUrlAttachmentSecrets, requestAxios, requestAxiosData, runWithContext, scopeClause, setIfDefined, skipWebhook, spec, statusLabelForApiError, tool, trigger, triggerGroup, truncateApiErrorMessage, uploadAttachmentDirect, validateScopes, verifyHmacSignature };
1195
+ export { API_ERROR_MAX_DETAILS, API_ERROR_MAX_MESSAGE_LENGTH, API_ERROR_TRUNCATED_PLACEHOLDER, AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX, type ApiErrorBuildHelpers, type ApiErrorDetailOptions, type ApiErrorResponse, type ApiErrorStatus, type ApiErrorTextOptions, type ApiServiceError, type AuthConfigSecretFilter, AuthConfigSecretRedactor, type BoundedApiErrorDetailOptions, type BuildApiServiceErrorOptions, type CreateHmacSignatureOptions, type DecodeApiErrorBodyOptions, GET_FILE_URL_TOOL_ID, type GetFileUrlHandler, type GetFileUrlToolInput, type GetFileUrlToolOutput, type NormalizeOAuthTokenResponseOptions, type OAuthExpiresAtOptions, type OAuthRefreshTokenFallbackMode, type OAuthTokenResponse, Slate, SlateAction, SlateActionBuilder, type SlateActionCreateParameters, type SlateActionDocsReference, type SlateActionParameters, type SlateActionParametersAny, type SlateActionParametersTool, type SlateActionParametersTrigger, type SlateActionScopeClause, type SlateActionScopeClauseInput, type SlateActionScopeValue, type SlateActionScopes, type SlateActionScopesInput, type SlateActionType, SlateAdapter, type SlateAdapterCapability, SlateAdapterSpec, type SlateAdapterSpecParameters, type SlateAddAttachmentContent, type SlateAddAttachmentInput, type SlateAttachment, type SlateAttachmentInlineContent, type SlateAttachmentUploadReferenceContent, type SlateAttachmentUrlContent, SlateAuth, type SlateAuthDocsReference, type SlateAuthType, type SlateAuthWithCustomData, type SlateAuthWithNone, type SlateAuthWithOauth, type SlateAuthWithServiceAccount, type SlateAuthWithToken, type SlateAuthenticatedAxiosDefaults, type SlateAxiosDefaults, type SlateAxiosErrorAdapter, type SlateAxiosErrorOptions, SlateConfig, type SlateConfigDocsReference, SlateContext, SlateDeclarationError, SlateDefaultPollingIntervalSeconds, type SlateDirectUploadInput, type SlateDocsReference, SlateError, type SlateErrorInput, type SlateErrorIssue, type SlateErrorKind, type SlateErrorProviderInfo, type SlateErrorResponse, type SlateErrorUpstreamInfo, type SlateGatewayEvent, type SlateLiveInvocationInfo, type SlateLogEntry, type SlateLogFields, type SlateLogListener, type SlateLogMessageInput, type SlateLogType, SlateLogger, type SlateProviderDocsReference, SlatePublicContext, SlatePublicTool, type SlatePublicToolInvocationHandler, type SlatePublicToolParameters, type SlateServiceErrorData, SlateSpecification, type SlateSpecificationCreateParameters, type SlateSpecificationParameters, SlateTool, SlateToolCategory, type SlateToolCategoryParameters, type SlateToolInvocationHandler, type SlateToolParameters, SlateTrigger, SlateTriggerGroup, SlateTriggerGroupBuilder, type SlateTriggerGroupCreateParameters, type SlateTriggerGroupGatewayConnectHandler, type SlateTriggerGroupGatewayParameters, type SlateTriggerGroupGatewayReceiveHandler, type SlateTriggerGroupParameters, type SlateTriggerGroupPollHandler, type SlateTriggerGroupPollingParameters, type SlateTriggerGroupRoutingMatchersHandler, type SlateTriggerGroupWebhookAutoRegistrationParameters, type SlateTriggerGroupWebhookManualRegistrationParameters, type SlateTriggerGroupWebhookParameters, type SlateTriggerMappingHandler, type SlateTriggerMatchesHandler, type SlateTriggerParameters, type SlateTriggerRoutingMatcher, type SlateTypedDocsReference, type SlateWebhookHttpResponseInit, type SlateWebhookManualFinishHandler, type SlateWebhookManualSetupHandler, type SlateWebhookProcessHandler, type SlateWebhookRegisterHandler, type SlateWebhookSkipped, type SlateWebhookTarget, type SlateWebhookTargetListHandler, type SlateWebhookTargetOwnership, type SlateWebhookUnregisterHandler, type VerifyHmacSignatureOptions, adapterSpec, allOf, anyOf, auth, axios, buildApiServiceError, collectApiErrorDetails, config, createApiServiceError, createAuthenticatedAxios, createAxios, createBase64Attachment, createHmacSignature, createSlateError, createTextAttachment, createUrlAttachment, decodeApiErrorBody, extractApiErrorMessage, getApiErrorResponse, getApiErrorResponseWithBody, getApiErrorStatus, getBase64ByteLength, getCurrentContext, getFileUrlTool, getFileUrlToolInputSchema, getFileUrlToolOutputSchema, getGraphWebhookValidationResponse, getMetaWebhookVerificationResponse, getOAuthExpiresAtFromExpiresIn, getResponseHeaderValue, getServiceErrorData, graphWebhookHttp, inferSlateErrorFromAxios, isApiErrorRecord, isAxiosErrorLike, isServiceErrorData, isUrlEmbeddedCredential, looksLikeHtmlErrorBody, mapServiceErrorCode, mapServiceErrorToSlateErrorInput, mergeScopes, metaWebhookHttp, normalizeOAuthTokenResponse, pickDefined, publicTool, pushBoundedApiErrorDetail, readApiErrorText, redactUrlAttachmentSecrets, requestAxios, requestAxiosData, runWithContext, scopeClause, setIfDefined, skipWebhook, spec, statusLabelForApiError, tool, toolCategory, trigger, triggerGroup, truncateApiErrorMessage, uploadAttachmentDirect, validateScopes, verifyHmacSignature };
@@ -2055,6 +2055,33 @@ var SlatePublicTool = class {
2055
2055
  };
2056
2056
  var publicTool = (spec2, params) => SlatePublicTool.create(spec2, params);
2057
2057
 
2058
+ // src/action/toolCategory.ts
2059
+ var SlateToolCategory = class _SlateToolCategory {
2060
+ constructor(_params) {
2061
+ this._params = _params;
2062
+ }
2063
+ _params;
2064
+ static create(params) {
2065
+ return new _SlateToolCategory(params);
2066
+ }
2067
+ get key() {
2068
+ return this._params.key;
2069
+ }
2070
+ get name() {
2071
+ return this._params.name;
2072
+ }
2073
+ get description() {
2074
+ return this._params.description;
2075
+ }
2076
+ get imageUrl() {
2077
+ return this._params.imageUrl;
2078
+ }
2079
+ get tools() {
2080
+ return this._params.tools;
2081
+ }
2082
+ };
2083
+ var toolCategory = (params) => SlateToolCategory.create(params);
2084
+
2058
2085
  // src/action/trigger.ts
2059
2086
  var SlateTrigger = class _SlateTrigger extends SlateAction {
2060
2087
  #triggerGroup;
@@ -3078,16 +3105,20 @@ var setIfDefined = (target, key, value) => {
3078
3105
 
3079
3106
  // src/specification/slate.ts
3080
3107
  var Slate = class _Slate {
3081
- constructor(_spec, _actions, _adapters, _triggerGroups) {
3108
+ constructor(_spec, _actions, _adapters, _triggerGroups, _toolCategories, _toolCategoryKeysByActionKey) {
3082
3109
  this._spec = _spec;
3083
3110
  this._actions = _actions;
3084
3111
  this._adapters = _adapters;
3085
3112
  this._triggerGroups = _triggerGroups;
3113
+ this._toolCategories = _toolCategories;
3114
+ this._toolCategoryKeysByActionKey = _toolCategoryKeysByActionKey;
3086
3115
  }
3087
3116
  _spec;
3088
3117
  _actions;
3089
3118
  _adapters;
3090
3119
  _triggerGroups;
3120
+ _toolCategories;
3121
+ _toolCategoryKeysByActionKey;
3091
3122
  static create(params) {
3092
3123
  let adapters = params.adapters ?? [];
3093
3124
  let actions = [...params.triggers, ...params.tools];
@@ -3131,7 +3162,16 @@ var Slate = class _Slate {
3131
3162
  `Only one trigger group may use manual webhook registration, but found: ${manualWebhookTriggerGroups.map((group) => group.key).join(", ")}`
3132
3163
  );
3133
3164
  }
3134
- return new _Slate(params.spec, actions, adapters, triggerGroups);
3165
+ let toolCategories = params.toolCategories ?? [];
3166
+ let toolCategoryKeysByActionKey = resolveToolCategoryMembership(toolCategories, actions);
3167
+ return new _Slate(
3168
+ params.spec,
3169
+ actions,
3170
+ adapters,
3171
+ triggerGroups,
3172
+ toolCategories,
3173
+ toolCategoryKeysByActionKey
3174
+ );
3135
3175
  }
3136
3176
  get spec() {
3137
3177
  return this._spec;
@@ -3145,6 +3185,79 @@ var Slate = class _Slate {
3145
3185
  get triggerGroups() {
3146
3186
  return this._triggerGroups ?? [];
3147
3187
  }
3188
+ get toolCategories() {
3189
+ return this._toolCategories ?? [];
3190
+ }
3191
+ // Keys of the tool categories an action belongs to, in declared category order.
3192
+ getToolCategoryKeys(actionKey) {
3193
+ return [...this._toolCategoryKeysByActionKey?.get(actionKey) ?? []];
3194
+ }
3195
+ };
3196
+ var TOOL_CATEGORY_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
3197
+ var isHttpUrl = (value) => {
3198
+ try {
3199
+ let url = new URL(value);
3200
+ return url.protocol === "https:" || url.protocol === "http:";
3201
+ } catch {
3202
+ return false;
3203
+ }
3204
+ };
3205
+ var resolveToolCategoryMembership = (toolCategories, actions) => {
3206
+ let effectiveActions = /* @__PURE__ */ new Map();
3207
+ for (let action of actions) {
3208
+ if (!effectiveActions.has(action.key)) effectiveActions.set(action.key, action);
3209
+ }
3210
+ let registeredActions = new Set(actions);
3211
+ let membersByCategoryKey = /* @__PURE__ */ new Map();
3212
+ for (let category of toolCategories) {
3213
+ if (!TOOL_CATEGORY_KEY_PATTERN.test(category.key)) {
3214
+ throw new SlateDeclarationError(
3215
+ `Tool category key "${category.key}" must match ${TOOL_CATEGORY_KEY_PATTERN}`
3216
+ );
3217
+ }
3218
+ if (membersByCategoryKey.has(category.key)) {
3219
+ throw new SlateDeclarationError(
3220
+ `Tool category "${category.key}" is registered more than once`
3221
+ );
3222
+ }
3223
+ if (!category.name?.trim()) {
3224
+ throw new SlateDeclarationError(`Tool category "${category.key}" must have a name`);
3225
+ }
3226
+ if (category.imageUrl !== void 0 && !isHttpUrl(category.imageUrl)) {
3227
+ throw new SlateDeclarationError(
3228
+ `Tool category "${category.key}" imageUrl must be an absolute http(s) URL`
3229
+ );
3230
+ }
3231
+ if (!category.tools?.length) {
3232
+ throw new SlateDeclarationError(
3233
+ `Tool category "${category.key}" must list at least one tool`
3234
+ );
3235
+ }
3236
+ let members = /* @__PURE__ */ new Set();
3237
+ for (let tool2 of category.tools) {
3238
+ if (!registeredActions.has(tool2)) {
3239
+ throw new SlateDeclarationError(
3240
+ `Tool category "${category.key}" lists tool "${tool2.key}", which is not registered in this slate`
3241
+ );
3242
+ }
3243
+ if (tool2.type !== "tool") {
3244
+ throw new SlateDeclarationError(
3245
+ `Tool category "${category.key}" lists trigger "${tool2.key}", but only tools can belong to a tool category`
3246
+ );
3247
+ }
3248
+ if (effectiveActions.get(tool2.key) === tool2) members.add(tool2.key);
3249
+ }
3250
+ membersByCategoryKey.set(category.key, members);
3251
+ }
3252
+ let keysByActionKey = /* @__PURE__ */ new Map();
3253
+ for (let category of toolCategories) {
3254
+ for (let actionKey of membersByCategoryKey.get(category.key) ?? []) {
3255
+ let keys = keysByActionKey.get(actionKey) ?? [];
3256
+ keys.push(category.key);
3257
+ keysByActionKey.set(actionKey, keys);
3258
+ }
3259
+ }
3260
+ return keysByActionKey;
3148
3261
  };
3149
3262
 
3150
3263
  // src/specification/specification.ts
@@ -3273,6 +3386,7 @@ export {
3273
3386
  SlatePublicTool,
3274
3387
  SlateSpecification,
3275
3388
  SlateTool,
3389
+ SlateToolCategory,
3276
3390
  SlateTrigger,
3277
3391
  SlateTriggerGroup,
3278
3392
  SlateTriggerGroupBuilder,
@@ -3333,6 +3447,7 @@ export {
3333
3447
  spec,
3334
3448
  statusLabelForApiError,
3335
3449
  tool,
3450
+ toolCategory,
3336
3451
  trigger,
3337
3452
  triggerGroup,
3338
3453
  truncateApiErrorMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slates/provider",
3
- "version": "1.0.0-rc.29",
3
+ "version": "1.0.0-rc.30",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -4,5 +4,6 @@ export * from './getFileUrlTool';
4
4
  export * from './publicTool';
5
5
  export * from './scopes';
6
6
  export * from './tool';
7
+ export * from './toolCategory';
7
8
  export * from './trigger';
8
9
  export * from './triggerGroup';
@@ -0,0 +1,40 @@
1
+ import type { SlateTool } from './tool';
2
+
3
+ export interface SlateToolCategoryParameters {
4
+ key: string;
5
+ name: string;
6
+ description?: string;
7
+ imageUrl?: string;
8
+ tools: SlateTool<any, any, any, any>[];
9
+ }
10
+
11
+ export class SlateToolCategory {
12
+ private constructor(private readonly _params: SlateToolCategoryParameters) {}
13
+
14
+ static create(params: SlateToolCategoryParameters) {
15
+ return new SlateToolCategory(params);
16
+ }
17
+
18
+ get key() {
19
+ return this._params.key;
20
+ }
21
+
22
+ get name() {
23
+ return this._params.name;
24
+ }
25
+
26
+ get description() {
27
+ return this._params.description;
28
+ }
29
+
30
+ get imageUrl() {
31
+ return this._params.imageUrl;
32
+ }
33
+
34
+ get tools() {
35
+ return this._params.tools;
36
+ }
37
+ }
38
+
39
+ export let toolCategory = (params: SlateToolCategoryParameters) =>
40
+ SlateToolCategory.create(params);
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { z } from 'zod';
3
- import { SlateTrigger, SlateTriggerGroup } from '../action';
3
+ import { SlateTool, SlateTrigger, SlateTriggerGroup, toolCategory } from '../action';
4
4
  import { SlateAuth } from '../auth';
5
5
  import { SlateConfig } from '../config';
6
6
  import { Slate } from './slate';
@@ -117,3 +117,155 @@ describe('Slate.create', () => {
117
117
  expect(slate.triggerGroups).toEqual([]);
118
118
  });
119
119
  });
120
+
121
+ let createTestTool = (spec: ReturnType<typeof createTestSpec>, key: string) =>
122
+ SlateTool.create(spec, { key, name: key })
123
+ .input(z.object({}))
124
+ .output(z.object({}))
125
+ .handleInvocation(async () => ({ output: {}, message: 'ok' }))
126
+ .build();
127
+
128
+ describe('Slate.create tool categories', () => {
129
+ it('resolves membership in declared category order', () => {
130
+ let spec = createTestSpec();
131
+ let records = createTestTool(spec, 'records');
132
+ let report = createTestTool(spec, 'report');
133
+ let other = createTestTool(spec, 'other');
134
+
135
+ let slate = Slate.create({
136
+ spec,
137
+ tools: [records, report, other],
138
+ triggers: [],
139
+ toolCategories: [
140
+ toolCategory({ key: 'crm', name: 'CRM', tools: [records, report] }),
141
+ toolCategory({ key: 'reporting', name: 'Reporting', tools: [report] })
142
+ ]
143
+ });
144
+
145
+ expect(slate.toolCategories.map(category => category.key)).toEqual(['crm', 'reporting']);
146
+ expect(slate.getToolCategoryKeys('records')).toEqual(['crm']);
147
+ expect(slate.getToolCategoryKeys('report')).toEqual(['crm', 'reporting']);
148
+ expect(slate.getToolCategoryKeys('other')).toEqual([]);
149
+ });
150
+
151
+ it('ignores a shadowed duplicate tool listed in a category', () => {
152
+ let spec = createTestSpec();
153
+ let writer = createTestTool(spec, 'dup');
154
+ let reader = createTestTool(spec, 'dup');
155
+
156
+ let slate = Slate.create({
157
+ spec,
158
+ tools: [writer, reader],
159
+ triggers: [],
160
+ toolCategories: [toolCategory({ key: 'read', name: 'Read', tools: [reader] })]
161
+ });
162
+
163
+ expect(slate.getToolCategoryKeys('dup')).toEqual([]);
164
+ });
165
+
166
+ it('returns a copy of the membership list', () => {
167
+ let spec = createTestSpec();
168
+ let tool = createTestTool(spec, 'a');
169
+ let slate = Slate.create({
170
+ spec,
171
+ tools: [tool],
172
+ triggers: [],
173
+ toolCategories: [toolCategory({ key: 'crm', name: 'CRM', tools: [tool] })]
174
+ });
175
+
176
+ slate.getToolCategoryKeys('a').push('mutated');
177
+ expect(slate.getToolCategoryKeys('a')).toEqual(['crm']);
178
+ });
179
+
180
+ it('defaults to no categories', () => {
181
+ let spec = createTestSpec();
182
+ let slate = Slate.create({ spec, tools: [createTestTool(spec, 'a')], triggers: [] });
183
+
184
+ expect(slate.toolCategories).toEqual([]);
185
+ expect(slate.getToolCategoryKeys('a')).toEqual([]);
186
+ });
187
+
188
+ it('rejects duplicate and malformed category keys', () => {
189
+ let spec = createTestSpec();
190
+ let tool = createTestTool(spec, 'a');
191
+
192
+ expect(() =>
193
+ Slate.create({
194
+ spec,
195
+ tools: [tool],
196
+ triggers: [],
197
+ toolCategories: [
198
+ toolCategory({ key: 'crm', name: 'CRM', tools: [tool] }),
199
+ toolCategory({ key: 'crm', name: 'CRM again', tools: [tool] })
200
+ ]
201
+ })
202
+ ).toThrow('Tool category "crm" is registered more than once');
203
+
204
+ expect(() =>
205
+ Slate.create({
206
+ spec,
207
+ tools: [tool],
208
+ triggers: [],
209
+ toolCategories: [toolCategory({ key: 'Zoho CRM', name: 'CRM', tools: [tool] })]
210
+ })
211
+ ).toThrow('Tool category key "Zoho CRM" must match');
212
+ });
213
+
214
+ it('accepts http(s) category images and rejects anything else', () => {
215
+ let spec = createTestSpec();
216
+ let tool = createTestTool(spec, 'a');
217
+ let slate = Slate.create({
218
+ spec,
219
+ tools: [tool],
220
+ triggers: [],
221
+ toolCategories: [
222
+ toolCategory({
223
+ key: 'crm',
224
+ name: 'CRM',
225
+ imageUrl: 'https://cdn.test/crm.svg',
226
+ tools: [tool]
227
+ })
228
+ ]
229
+ });
230
+ expect(slate.toolCategories[0]?.imageUrl).toBe('https://cdn.test/crm.svg');
231
+
232
+ expect(() =>
233
+ Slate.create({
234
+ spec,
235
+ tools: [tool],
236
+ triggers: [],
237
+ toolCategories: [
238
+ toolCategory({ key: 'crm', name: 'CRM', imageUrl: 'crm.svg', tools: [tool] })
239
+ ]
240
+ })
241
+ ).toThrow('Tool category "crm" imageUrl must be an absolute http(s) URL');
242
+ });
243
+
244
+ it('rejects a category without tools', () => {
245
+ let spec = createTestSpec();
246
+
247
+ expect(() =>
248
+ Slate.create({
249
+ spec,
250
+ tools: [createTestTool(spec, 'a')],
251
+ triggers: [],
252
+ toolCategories: [toolCategory({ key: 'crm', name: 'CRM', tools: [] })]
253
+ })
254
+ ).toThrow('Tool category "crm" must list at least one tool');
255
+ });
256
+
257
+ it('rejects unregistered member tools', () => {
258
+ let spec = createTestSpec();
259
+
260
+ expect(() =>
261
+ Slate.create({
262
+ spec,
263
+ tools: [],
264
+ triggers: [],
265
+ toolCategories: [
266
+ toolCategory({ key: 'crm', name: 'CRM', tools: [createTestTool(spec, 'stray')] })
267
+ ]
268
+ })
269
+ ).toThrow('Tool category "crm" lists tool "stray", which is not registered in this slate');
270
+ });
271
+ });
@@ -1,4 +1,4 @@
1
- import type { SlateTool, SlateTrigger, SlateTriggerGroup } from '../action';
1
+ import type { SlateTool, SlateToolCategory, SlateTrigger, SlateTriggerGroup } from '../action';
2
2
  import type { SlateAdapter } from '../adapter';
3
3
  import { SlateDeclarationError } from '../error';
4
4
  import type { SlateSpecification } from './specification';
@@ -11,7 +11,9 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
11
11
  | SlateTool<ConfigType, AuthType, any, any>
12
12
  )[],
13
13
  private readonly _adapters: SlateAdapter<ConfigType, AuthType>[],
14
- private readonly _triggerGroups: SlateTriggerGroup<ConfigType, AuthType>[]
14
+ private readonly _triggerGroups: SlateTriggerGroup<ConfigType, AuthType>[],
15
+ private readonly _toolCategories: SlateToolCategory[],
16
+ private readonly _toolCategoryKeysByActionKey: Map<string, string[]>
15
17
  ) {}
16
18
 
17
19
  static create<ConfigType extends {}, AuthType extends {}>(params: {
@@ -20,6 +22,7 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
20
22
  tools: SlateTool<ConfigType, AuthType, any, any>[];
21
23
  adapters?: SlateAdapter<ConfigType, AuthType>[];
22
24
  triggerGroups?: SlateTriggerGroup<ConfigType, AuthType>[];
25
+ toolCategories?: SlateToolCategory[];
23
26
  }) {
24
27
  let adapters = params.adapters ?? [];
25
28
  let actions = [...params.triggers, ...params.tools];
@@ -71,7 +74,17 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
71
74
  );
72
75
  }
73
76
 
74
- return new Slate(params.spec, actions, adapters, triggerGroups);
77
+ let toolCategories = params.toolCategories ?? [];
78
+ let toolCategoryKeysByActionKey = resolveToolCategoryMembership(toolCategories, actions);
79
+
80
+ return new Slate(
81
+ params.spec,
82
+ actions,
83
+ adapters,
84
+ triggerGroups,
85
+ toolCategories,
86
+ toolCategoryKeysByActionKey
87
+ );
75
88
  }
76
89
 
77
90
  get spec() {
@@ -89,4 +102,93 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
89
102
  get triggerGroups() {
90
103
  return this._triggerGroups ?? [];
91
104
  }
105
+
106
+ get toolCategories() {
107
+ return this._toolCategories ?? [];
108
+ }
109
+
110
+ // Keys of the tool categories an action belongs to, in declared category order.
111
+ getToolCategoryKeys(actionKey: string) {
112
+ return [...(this._toolCategoryKeysByActionKey?.get(actionKey) ?? [])];
113
+ }
92
114
  }
115
+
116
+ let TOOL_CATEGORY_KEY_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
117
+
118
+ let isHttpUrl = (value: string) => {
119
+ try {
120
+ let url = new URL(value);
121
+ return url.protocol === 'https:' || url.protocol === 'http:';
122
+ } catch {
123
+ return false;
124
+ }
125
+ };
126
+
127
+ type SlateActionAny = SlateTool<any, any, any, any> | SlateTrigger<any, any, any, any>;
128
+
129
+ let resolveToolCategoryMembership = (
130
+ toolCategories: SlateToolCategory[],
131
+ actions: SlateActionAny[]
132
+ ) => {
133
+ // Actions resolve by key and the first registered one wins, so a shadowed duplicate must
134
+ // not lend its categories to the action that actually runs.
135
+ let effectiveActions = new Map<string, SlateActionAny>();
136
+ for (let action of actions) {
137
+ if (!effectiveActions.has(action.key)) effectiveActions.set(action.key, action);
138
+ }
139
+ let registeredActions = new Set(actions);
140
+ let membersByCategoryKey = new Map<string, Set<string>>();
141
+
142
+ for (let category of toolCategories) {
143
+ if (!TOOL_CATEGORY_KEY_PATTERN.test(category.key)) {
144
+ throw new SlateDeclarationError(
145
+ `Tool category key "${category.key}" must match ${TOOL_CATEGORY_KEY_PATTERN}`
146
+ );
147
+ }
148
+ if (membersByCategoryKey.has(category.key)) {
149
+ throw new SlateDeclarationError(
150
+ `Tool category "${category.key}" is registered more than once`
151
+ );
152
+ }
153
+ if (!category.name?.trim()) {
154
+ throw new SlateDeclarationError(`Tool category "${category.key}" must have a name`);
155
+ }
156
+ if (category.imageUrl !== undefined && !isHttpUrl(category.imageUrl)) {
157
+ throw new SlateDeclarationError(
158
+ `Tool category "${category.key}" imageUrl must be an absolute http(s) URL`
159
+ );
160
+ }
161
+ if (!category.tools?.length) {
162
+ throw new SlateDeclarationError(
163
+ `Tool category "${category.key}" must list at least one tool`
164
+ );
165
+ }
166
+
167
+ let members = new Set<string>();
168
+ for (let tool of category.tools) {
169
+ if (!registeredActions.has(tool)) {
170
+ throw new SlateDeclarationError(
171
+ `Tool category "${category.key}" lists tool "${tool.key}", which is not registered in this slate`
172
+ );
173
+ }
174
+ if (tool.type !== 'tool') {
175
+ throw new SlateDeclarationError(
176
+ `Tool category "${category.key}" lists trigger "${tool.key}", but only tools can belong to a tool category`
177
+ );
178
+ }
179
+ if (effectiveActions.get(tool.key) === tool) members.add(tool.key);
180
+ }
181
+ membersByCategoryKey.set(category.key, members);
182
+ }
183
+
184
+ let keysByActionKey = new Map<string, string[]>();
185
+ for (let category of toolCategories) {
186
+ for (let actionKey of membersByCategoryKey.get(category.key) ?? []) {
187
+ let keys = keysByActionKey.get(actionKey) ?? [];
188
+ keys.push(category.key);
189
+ keysByActionKey.set(actionKey, keys);
190
+ }
191
+ }
192
+
193
+ return keysByActionKey;
194
+ };