@ganju/utils 0.0.6 → 0.0.9

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 (46) hide show
  1. package/dist/attachment.d.ts.map +1 -1
  2. package/dist/chunking.d.ts.map +1 -1
  3. package/dist/chunking.js +9 -3
  4. package/dist/coerce.d.ts.map +1 -1
  5. package/dist/coerce.js +4 -1
  6. package/dist/constants.d.ts +27 -3
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/constants.js +157 -22
  9. package/dist/containmentToken.d.ts +25 -0
  10. package/dist/containmentToken.d.ts.map +1 -0
  11. package/dist/containmentToken.js +81 -0
  12. package/dist/crypto.d.ts.map +1 -1
  13. package/dist/crypto.js +4 -1
  14. package/dist/getEnv.d.ts.map +1 -1
  15. package/dist/index.d.ts +34 -6
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +7 -0
  18. package/dist/llmBaseUrl.d.ts +39 -0
  19. package/dist/llmBaseUrl.d.ts.map +1 -0
  20. package/dist/llmBaseUrl.js +65 -0
  21. package/dist/localizeZodIssue.d.ts.map +1 -1
  22. package/dist/localizeZodIssue.js +6 -0
  23. package/dist/oauth.d.ts.map +1 -1
  24. package/dist/parseHttpError.d.ts.map +1 -1
  25. package/dist/schema.d.ts +2 -2
  26. package/dist/schema.d.ts.map +1 -1
  27. package/dist/schema.js +32 -8
  28. package/dist/sources.d.ts.map +1 -1
  29. package/dist/vendorError.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/attachment.ts +2 -1
  32. package/src/chunking.ts +9 -3
  33. package/src/coerce.ts +4 -1
  34. package/src/constants.ts +178 -22
  35. package/src/containmentToken.ts +146 -0
  36. package/src/crypto.ts +6 -2
  37. package/src/getEnv.ts +1 -4
  38. package/src/index.ts +16 -0
  39. package/src/llmBaseUrl.ts +65 -0
  40. package/src/localizeZodIssue.ts +6 -0
  41. package/src/oauth.ts +1 -3
  42. package/src/parseHttpError.ts +3 -1
  43. package/src/schema.ts +63 -11
  44. package/src/slackSend.ts +10 -10
  45. package/src/sources.ts +7 -2
  46. package/src/vendorError.ts +4 -1
package/src/schema.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  import { constants } from './constants';
4
+ import { isAllowedLlmBaseUrl, requiresLlmBaseUrl } from './llmBaseUrl';
4
5
  import { isReservedSlug, isValidSlugFormat } from './slug';
5
6
  import { isReservedToolName } from './reservedToolName';
6
7
  import { slugifyTitle } from './slugifyTitle';
@@ -572,11 +573,33 @@ const ORGANIZATION_LIST_LLM = z.object({
572
573
  organizationId: z.uuid()
573
574
  });
574
575
 
575
- const ORGANIZATION_CREATE_LLM = z.object({
576
+ // The two rules live in `llmBaseUrl.ts`, because the update controller applies
577
+ // them again to the merged row — the only place both halves are visible. Here
578
+ // they are just the field validator and the cross-field check.
579
+ const LLM_BASE_URL = z
580
+ .url()
581
+ .max(500)
582
+ .refine(isAllowedLlmBaseUrl, constants.LLM_BASE_URL_INVALID_MESSAGE);
583
+
584
+ const BASE_URL_REQUIRED = {
585
+ message: constants.LLM_BASE_URL_REQUIRED_MESSAGE,
586
+ path: ['baseUrl']
587
+ };
588
+
589
+ const hasBaseUrlWhenRequired = (
590
+ provider: string | undefined,
591
+ baseUrl: unknown
592
+ ) => !requiresLlmBaseUrl(provider) || !!baseUrl;
593
+
594
+ // The base-URL rule is a cross-field check, and zod cannot `.omit()` from a
595
+ // schema that already carries one. So the shape stays a plain object — the
596
+ // `_VIEW` variants below derive from it — and the refinement is applied last,
597
+ // to each schema in turn, which also keeps the two in step.
598
+ const ORGANIZATION_CREATE_LLM_SHAPE = z.object({
576
599
  name: z.string().min(1).max(200),
577
600
  provider: z.enum(constants.LLM_PROVIDERS),
578
601
  model: z.string().min(1).max(200),
579
- baseUrl: z.url().optional().or(z.literal('')),
602
+ baseUrl: LLM_BASE_URL.optional().or(z.literal('')),
580
603
  apiKey: z.string().min(1).max(500),
581
604
  systemPrompt: z.string().max(10000).optional(),
582
605
  config: z.record(z.string(), z.any()).optional(),
@@ -584,12 +607,21 @@ const ORGANIZATION_CREATE_LLM = z.object({
584
607
  organizationId: z.uuid()
585
608
  });
586
609
 
587
- const ORGANIZATION_UPDATE_LLM = z.object({
610
+ const ORGANIZATION_CREATE_LLM = ORGANIZATION_CREATE_LLM_SHAPE.refine(
611
+ v => hasBaseUrlWhenRequired(v.provider, v.baseUrl),
612
+ BASE_URL_REQUIRED
613
+ );
614
+
615
+ // An update is a partial: an omitted field keeps its stored value, so the
616
+ // base-URL rule can only be checked here when the caller sends the provider and
617
+ // the base URL together. The controller re-checks it against the merged row,
618
+ // which is the only place both halves are known.
619
+ const ORGANIZATION_UPDATE_LLM_SHAPE = z.object({
588
620
  llmId: z.uuid(),
589
621
  name: z.string().min(1).max(200).optional(),
590
622
  provider: z.enum(constants.LLM_PROVIDERS).optional(),
591
623
  model: z.string().min(1).max(200).optional(),
592
- baseUrl: z.url().optional().or(z.literal('')).nullable(),
624
+ baseUrl: LLM_BASE_URL.optional().or(z.literal('')).nullable(),
593
625
  apiKey: z.string().min(1).max(500).optional(),
594
626
  systemPrompt: z.string().max(10000).optional().nullable(),
595
627
  config: z.record(z.string(), z.any()).optional().nullable(),
@@ -597,6 +629,21 @@ const ORGANIZATION_UPDATE_LLM = z.object({
597
629
  organizationId: z.uuid()
598
630
  });
599
631
 
632
+ const refineUpdateBaseUrl = <
633
+ T extends z.ZodType<{ provider?: string; baseUrl?: unknown }>
634
+ >(
635
+ schema: T
636
+ ) =>
637
+ schema.refine(
638
+ v =>
639
+ v.baseUrl === undefined || hasBaseUrlWhenRequired(v.provider, v.baseUrl),
640
+ BASE_URL_REQUIRED
641
+ );
642
+
643
+ const ORGANIZATION_UPDATE_LLM = refineUpdateBaseUrl(
644
+ ORGANIZATION_UPDATE_LLM_SHAPE
645
+ );
646
+
600
647
  const ORGANIZATION_REMOVE_LLM = z.object({
601
648
  llmId: z.uuid(),
602
649
  userId: z.uuid(),
@@ -649,16 +696,21 @@ const ACCESS_TOKEN_CREATE_VIEW = ACCESS_TOKEN_CREATE.omit({
649
696
  projectId: true
650
697
  });
651
698
 
652
- const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM.omit({
699
+ const ORGANIZATION_CREATE_LLM_VIEW = ORGANIZATION_CREATE_LLM_SHAPE.omit({
653
700
  userId: true,
654
701
  organizationId: true
655
- });
702
+ }).refine(
703
+ v => hasBaseUrlWhenRequired(v.provider, v.baseUrl),
704
+ BASE_URL_REQUIRED
705
+ );
656
706
 
657
- const ORGANIZATION_UPDATE_LLM_VIEW = ORGANIZATION_UPDATE_LLM.omit({
658
- llmId: true,
659
- userId: true,
660
- organizationId: true
661
- });
707
+ const ORGANIZATION_UPDATE_LLM_VIEW = refineUpdateBaseUrl(
708
+ ORGANIZATION_UPDATE_LLM_SHAPE.omit({
709
+ llmId: true,
710
+ userId: true,
711
+ organizationId: true
712
+ })
713
+ );
662
714
 
663
715
  // `channel.config` is an open bag, so this stays loose — it only pins down the
664
716
  // keys we actually read, and passes everything else through untouched.
package/src/slackSend.ts CHANGED
@@ -18,17 +18,17 @@ export interface SlackSendRequest {
18
18
  operation: SlackOperation;
19
19
 
20
20
  // chat.postMessage / files.completeUploadExternal share these
21
- channel: string; // channel ID (C…/G…/D…) or name (#general)
22
- threadTs?: string; // reply in-thread when set
21
+ channel: string; // channel ID (C…/G…/D…) or name (#general)
22
+ threadTs?: string; // reply in-thread when set
23
23
 
24
24
  // post-message
25
- text?: string; // required for post-message
26
- mrkdwn?: boolean; // defaults to true on Slack's side
27
- blocks?: unknown[]; // optional Block Kit payload
25
+ text?: string; // required for post-message
26
+ mrkdwn?: boolean; // defaults to true on Slack's side
27
+ blocks?: unknown[]; // optional Block Kit payload
28
28
 
29
29
  // upload-file
30
- title?: string; // shown in the Slack file viewer
31
- initialComment?: string; // message body posted alongside the file
30
+ title?: string; // shown in the Slack file viewer
31
+ initialComment?: string; // message body posted alongside the file
32
32
  }
33
33
 
34
34
  // Wire protocol for sending a PROXIED (remote MCP) resource as a Slack file.
@@ -59,7 +59,7 @@ export interface SlackSendResponse {
59
59
  // file id. Empty string when Slack returns ok:true with no id (shouldn't
60
60
  // happen, but defensively typed).
61
61
  id: string;
62
- channel?: string; // resolved channel id
63
- ts?: string; // message timestamp (post-message only)
64
- permalink?: string; // file permalink (upload-file only)
62
+ channel?: string; // resolved channel id
63
+ ts?: string; // message timestamp (post-message only)
64
+ permalink?: string; // file permalink (upload-file only)
65
65
  }
package/src/sources.ts CHANGED
@@ -30,7 +30,9 @@ export interface ResourceUrlContext {
30
30
  * nothing to a browser. Formatting it as a web link produced a dead button in
31
31
  * whichever channel the answer went to.
32
32
  */
33
- export const isDownloadableSource = (sourceType: Source['sourceType']): boolean =>
33
+ export const isDownloadableSource = (
34
+ sourceType: Source['sourceType']
35
+ ): boolean =>
34
36
  sourceType === constants.RESOURCE_SOURCE_TYPE_FILE ||
35
37
  sourceType === constants.RESOURCE_SOURCE_TYPE_CUSTOM_CODE;
36
38
 
@@ -79,7 +81,10 @@ export const formatSourcesAsButtons = (
79
81
  source.pageNumber
80
82
  );
81
83
  return {
82
- text: truncateLabel(`${position} ${label}${pageSuffix}`, maxLabelLength),
84
+ text: truncateLabel(
85
+ `${position} ${label}${pageSuffix}`,
86
+ maxLabelLength
87
+ ),
83
88
  url
84
89
  };
85
90
  }
@@ -7,7 +7,10 @@
7
7
  // Error message turns a perfectly diagnosable failure into "[object Object]",
8
8
  // which is what every one of these call sites did until a real Gmail rejection
9
9
  // was put through them.
10
- export const describeVendorError = (body: unknown, fallback: string): string => {
10
+ export const describeVendorError = (
11
+ body: unknown,
12
+ fallback: string
13
+ ): string => {
11
14
  const error = (body as { error?: unknown } | null | undefined)?.error;
12
15
  if (typeof error === 'string' && error) return error;
13
16
  if (error && typeof error === 'object') {