@opengeni/contracts 0.36.0 → 0.37.0

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.
@@ -0,0 +1,132 @@
1
+ import { z } from "zod";
2
+
3
+ export const ConnectorDocumentDestinationAuthority = z.enum([
4
+ "organization",
5
+ "workspace",
6
+ "personal",
7
+ ]);
8
+ export type ConnectorDocumentDestinationAuthority = z.infer<
9
+ typeof ConnectorDocumentDestinationAuthority
10
+ >;
11
+
12
+ export const ConnectorDocumentDestinationSelection = z.object({
13
+ authorityKind: ConnectorDocumentDestinationAuthority,
14
+ collectionId: z.string().uuid().nullable().default(null),
15
+ });
16
+ export type ConnectorDocumentDestinationSelection = z.infer<
17
+ typeof ConnectorDocumentDestinationSelection
18
+ >;
19
+
20
+ export const ConnectorDocumentDestination = z
21
+ .object({
22
+ authorityKind: ConnectorDocumentDestinationAuthority,
23
+ authorityAccountId: z.string().uuid(),
24
+ authorityWorkspaceId: z.string().uuid().nullable(),
25
+ authoritySubjectId: z.string().trim().min(1).max(1024).nullable(),
26
+ collectionId: z.string().uuid().nullable().default(null),
27
+ })
28
+ .superRefine((destination, context) => {
29
+ if (destination.authorityKind === "organization") {
30
+ if (destination.authorityWorkspaceId !== null || destination.authoritySubjectId !== null) {
31
+ context.addIssue({
32
+ code: "custom",
33
+ message: "organization connector destinations cannot bind workspace or subject authority",
34
+ });
35
+ }
36
+ return;
37
+ }
38
+ if (destination.authorityWorkspaceId === null) {
39
+ context.addIssue({
40
+ code: "custom",
41
+ message: "workspace and personal connector destinations require workspace authority",
42
+ });
43
+ }
44
+ if (destination.authorityKind === "workspace" && destination.authoritySubjectId !== null) {
45
+ context.addIssue({
46
+ code: "custom",
47
+ message: "workspace connector destinations cannot bind subject authority",
48
+ });
49
+ }
50
+ if (destination.authorityKind === "personal" && destination.authoritySubjectId === null) {
51
+ context.addIssue({
52
+ code: "custom",
53
+ message: "personal connector destinations require immutable subject authority",
54
+ });
55
+ }
56
+ });
57
+ export type ConnectorDocumentDestination = z.infer<typeof ConnectorDocumentDestination>;
58
+
59
+ export function bindConnectorDocumentDestination(
60
+ selection: ConnectorDocumentDestinationSelection,
61
+ context: { accountId: string; workspaceId: string; initiatingSubjectId: string },
62
+ ): ConnectorDocumentDestination {
63
+ return ConnectorDocumentDestination.parse({
64
+ authorityKind: selection.authorityKind,
65
+ authorityAccountId: context.accountId,
66
+ authorityWorkspaceId: selection.authorityKind === "organization" ? null : context.workspaceId,
67
+ authoritySubjectId: selection.authorityKind === "personal" ? context.initiatingSubjectId : null,
68
+ collectionId: selection.collectionId,
69
+ });
70
+ }
71
+
72
+ export function legacyWorkspaceConnectorDocumentDestination(context: {
73
+ accountId: string;
74
+ workspaceId: string;
75
+ }): ConnectorDocumentDestination {
76
+ return ConnectorDocumentDestination.parse({
77
+ authorityKind: "workspace",
78
+ authorityAccountId: context.accountId,
79
+ authorityWorkspaceId: context.workspaceId,
80
+ authoritySubjectId: null,
81
+ collectionId: null,
82
+ });
83
+ }
84
+
85
+ export function resolveConnectorDocumentDestination(
86
+ value: unknown,
87
+ context: {
88
+ accountId: string;
89
+ workspaceId: string;
90
+ connectionSubjectId?: string | null | undefined;
91
+ },
92
+ ): ConnectorDocumentDestination {
93
+ if (value === undefined || value === null) {
94
+ return legacyWorkspaceConnectorDocumentDestination(context);
95
+ }
96
+ const destination = ConnectorDocumentDestination.parse(value);
97
+ if (destination.authorityAccountId !== context.accountId) {
98
+ throw new Error("connector destination organization authority mismatch");
99
+ }
100
+ if (
101
+ destination.authorityKind !== "organization" &&
102
+ destination.authorityWorkspaceId !== context.workspaceId
103
+ ) {
104
+ throw new Error("connector destination workspace authority mismatch");
105
+ }
106
+ if (
107
+ destination.authorityKind === "personal" &&
108
+ destination.authoritySubjectId !== context.connectionSubjectId
109
+ ) {
110
+ throw new Error("connector destination personal authority mismatch");
111
+ }
112
+ return destination;
113
+ }
114
+
115
+ export function connectorDestinationDocumentAuthority(destination: ConnectorDocumentDestination): {
116
+ authorityKind: ConnectorDocumentDestinationAuthority;
117
+ authorityWorkspaceId: string | null;
118
+ authoritySubjectId: string | null;
119
+ } {
120
+ return {
121
+ authorityKind: destination.authorityKind,
122
+ authorityWorkspaceId: destination.authorityWorkspaceId,
123
+ authoritySubjectId: destination.authoritySubjectId,
124
+ };
125
+ }
126
+
127
+ export function connectorDocumentDestinationCollectionId(
128
+ destination: ConnectorDocumentDestination,
129
+ defaultCollectionId: string,
130
+ ): string {
131
+ return destination.collectionId ?? z.string().uuid().parse(defaultCollectionId);
132
+ }
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { ConnectionMetadata } from "./index";
4
+ import {
5
+ ConnectorDocumentDestination,
6
+ ConnectorDocumentDestinationSelection,
7
+ } from "./connector-destinations";
4
8
 
5
9
  export const GOOGLE_DRIVE_PROVIDER_DOMAIN = "googleapis.com" as const;
6
10
  export const GOOGLE_DRIVE_FULL_SCOPE = "https://www.googleapis.com/auth/drive" as const;
@@ -63,6 +67,7 @@ export function googleDriveScopesAllowCapability(
63
67
  return googleDriveOAuthScopeDecision(grantedScopes).capabilities.includes(capability);
64
68
  }
65
69
 
70
+ /** @deprecated Connector document destinations use organization/workspace/personal authority. */
66
71
  export const GoogleDriveTargetScope = z.enum(["user", "workspace", "organization"]);
67
72
  export type GoogleDriveTargetScope = z.infer<typeof GoogleDriveTargetScope>;
68
73
 
@@ -130,7 +135,9 @@ export const GoogleDriveSelectedSource = z.object({
130
135
  name: z.string().min(1).max(1024),
131
136
  mimeType: z.string().min(1).max(256),
132
137
  driveId: z.string().min(1).max(256).nullable().default(null),
133
- targetScope: GoogleDriveTargetScope,
138
+ destination: ConnectorDocumentDestination.optional(),
139
+ /** @deprecated Missing destinations resolve to the current workspace boundary. */
140
+ targetScope: GoogleDriveTargetScope.optional(),
134
141
  syncCadence: GoogleDriveSyncCadence.default("hourly"),
135
142
  readPolicy: GoogleDriveReadPolicy.default("allow"),
136
143
  selectedAt: z.string().datetime({ offset: true }),
@@ -147,6 +154,7 @@ export const GoogleDriveConnectionMetadata = z
147
154
  verifiedAt: z.string().datetime({ offset: true }),
148
155
  accessMode: z.enum(["metadata_readonly", "readonly"]),
149
156
  lifecycle: GoogleDriveConnectionLifecycle.optional(),
157
+ documentDestination: ConnectorDocumentDestination.optional(),
150
158
  selectedSources: z.array(GoogleDriveSelectedSource).max(100).optional(),
151
159
  /** @deprecated Read `selectedSources`; retained while existing connections migrate. */
152
160
  selectedSource: GoogleDriveSelectedSource.nullable().optional(),
@@ -199,22 +207,28 @@ export const GoogleDriveBrowseResponse = z.object({
199
207
  });
200
208
  export type GoogleDriveBrowseResponse = z.infer<typeof GoogleDriveBrowseResponse>;
201
209
 
202
- export const SaveGoogleDriveSourceRequest = z.object({
203
- sources: z
204
- .array(
205
- GoogleDriveBrowseItem.pick({
206
- id: true,
207
- name: true,
208
- mimeType: true,
209
- driveId: true,
210
+ export const SaveGoogleDriveSourceRequest = z
211
+ .object({
212
+ sources: z
213
+ .array(
214
+ GoogleDriveBrowseItem.pick({
215
+ id: true,
216
+ name: true,
217
+ mimeType: true,
218
+ driveId: true,
219
+ }),
220
+ )
221
+ .max(100)
222
+ .refine((sources) => new Set(sources.map((source) => source.id)).size === sources.length, {
223
+ message: "Google Drive sources must be unique",
210
224
  }),
211
- )
212
- .max(100)
213
- .refine((sources) => new Set(sources.map((source) => source.id)).size === sources.length, {
214
- message: "Google Drive sources must be unique",
215
- }),
216
- targetScope: GoogleDriveTargetScope,
217
- syncCadence: GoogleDriveSyncCadence.default("hourly"),
218
- readPolicy: GoogleDriveReadPolicy.default("allow"),
219
- });
225
+ destination: ConnectorDocumentDestinationSelection.optional(),
226
+ /** @deprecated Legacy requests are accepted but resolve to workspace authority. */
227
+ targetScope: GoogleDriveTargetScope.optional(),
228
+ syncCadence: GoogleDriveSyncCadence.default("hourly"),
229
+ readPolicy: GoogleDriveReadPolicy.default("allow"),
230
+ })
231
+ .refine((request) => request.destination !== undefined || request.targetScope !== undefined, {
232
+ message: "Google Drive document destination is required",
233
+ });
220
234
  export type SaveGoogleDriveSourceRequest = z.infer<typeof SaveGoogleDriveSourceRequest>;
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  import { WorkspaceInstructionPolicyRoleKeyInput } from "./workspace-instruction-policies";
9
9
 
10
10
  export * from "./slack-bot-scopes";
11
+ export * from "./connector-destinations";
11
12
 
12
13
  export {
13
14
  CreateWorkspaceArtifactRequest,
@@ -1149,7 +1150,11 @@ export const ClientVoiceInputConfig = z
1149
1150
  .object({
1150
1151
  available: z.boolean(),
1151
1152
  maxDurationSeconds: z.number().int().positive().max(600),
1152
- maxSizeBytes: z.number().int().positive(),
1153
+ maxSizeBytes: z
1154
+ .number()
1155
+ .int()
1156
+ .positive()
1157
+ .max(25 * 1024 * 1024),
1153
1158
  acceptedMimeTypes: z.array(z.string().trim().min(1).max(128)).min(1).max(32),
1154
1159
  })
1155
1160
  .strict();
@@ -1164,7 +1169,7 @@ export const TranscribeAudioResponse = z
1164
1169
  .strict();
1165
1170
  export type TranscribeAudioResponse = z.infer<typeof TranscribeAudioResponse>;
1166
1171
 
1167
- /** Default ceilings for native voice input (hard-stop recording + upload). */
1172
+ /** Default safety ceiling for one-shot native voice input (60 seconds). */
1168
1173
  export const VOICE_INPUT_MAX_DURATION_SECONDS = 60 as const;
1169
1174
  export const VOICE_INPUT_MAX_SIZE_BYTES = 25 * 1024 * 1024;
1170
1175
  export const VOICE_INPUT_ACCEPTED_MIME_TYPES = [