@opengeni/contracts 0.38.3 → 0.40.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,281 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Versioned, provider-neutral receipt returned by first-party MCP mutation tools.
5
+ *
6
+ * A receipt intentionally contains only server-generated identity and outcome
7
+ * facts. Callers already have mutation inputs in their tool-call history, so
8
+ * copying names, prompts, instructions, commands, evidence, or other request
9
+ * fields into the result wastes context and can expose data twice.
10
+ */
11
+ export const MCP_MUTATION_RECEIPT_VERSION = "mcp-mutation-receipt.v1" as const;
12
+ export const MCP_MUTATION_RECEIPT_MAX_BYTES = 64 * 1024;
13
+
14
+ const utf8ByteLength = (value: string): number => new TextEncoder().encode(value).byteLength;
15
+
16
+ function boundedUtf8String(maxBytes: number, minLength = 0) {
17
+ return z
18
+ .string()
19
+ .min(minLength)
20
+ .max(maxBytes)
21
+ .superRefine((value, context) => {
22
+ if (utf8ByteLength(value) > maxBytes) {
23
+ context.addIssue({
24
+ code: "custom",
25
+ message: `string must contain at most ${maxBytes} UTF-8 bytes`,
26
+ });
27
+ }
28
+ });
29
+ }
30
+
31
+ export const McpMutationReceiptOutcome = z.enum([
32
+ "created",
33
+ "updated",
34
+ "deleted",
35
+ "unchanged",
36
+ "accepted",
37
+ "triggered",
38
+ "repaired",
39
+ "replayed",
40
+ "partial_failure",
41
+ ]);
42
+ export type McpMutationReceiptOutcome = z.infer<typeof McpMutationReceiptOutcome>;
43
+
44
+ export const McpMutationReceiptIdempotencyStatus = z.enum([
45
+ "not_supported",
46
+ "not_requested",
47
+ "applied",
48
+ "replayed",
49
+ "unknown",
50
+ ]);
51
+ export type McpMutationReceiptIdempotencyStatus = z.infer<
52
+ typeof McpMutationReceiptIdempotencyStatus
53
+ >;
54
+
55
+ export const McpMutationResource = z
56
+ .object({
57
+ type: boundedUtf8String(128, 1),
58
+ id: boundedUtf8String(256, 1),
59
+ version: z.union([z.number().int().nonnegative(), boundedUtf8String(128, 1)]).optional(),
60
+ etag: boundedUtf8String(512, 1).optional(),
61
+ state: boundedUtf8String(128, 1).optional(),
62
+ })
63
+ .strict();
64
+ export type McpMutationResource = z.infer<typeof McpMutationResource>;
65
+
66
+ const McpMutationReceiptFact = z.union([
67
+ boundedUtf8String(512),
68
+ z.number().finite(),
69
+ z.boolean(),
70
+ z.null(),
71
+ ]);
72
+
73
+ const McpMutationReceiptFacts = z
74
+ .record(boundedUtf8String(64, 1), McpMutationReceiptFact)
75
+ .superRefine((value, context) => {
76
+ if (Object.keys(value).length > 16) {
77
+ context.addIssue({
78
+ code: "custom",
79
+ message: "receipt facts may contain at most 16 scalar entries",
80
+ });
81
+ }
82
+ });
83
+
84
+ const McpMutationReceiptNextActionArguments = z
85
+ .record(boundedUtf8String(64, 1), McpMutationReceiptFact)
86
+ .superRefine((value, context) => {
87
+ if (Object.keys(value).length > 8) {
88
+ context.addIssue({
89
+ code: "custom",
90
+ message: "receipt nextAction arguments may contain at most 8 scalar entries",
91
+ });
92
+ }
93
+ });
94
+
95
+ export const McpMutationReceipt = z
96
+ .object({
97
+ receiptVersion: z.literal(MCP_MUTATION_RECEIPT_VERSION),
98
+ operation: boundedUtf8String(128, 1),
99
+ // v1 receipts describe committed truth only. Validation/auth/conflict and
100
+ // fully compensated failures remain MCP errors rather than success-shaped
101
+ // committed=false results.
102
+ committed: z.literal(true),
103
+ outcome: McpMutationReceiptOutcome,
104
+ changed: z.boolean(),
105
+ resource: McpMutationResource,
106
+ relatedResources: z.array(McpMutationResource).max(8).optional(),
107
+ timestamp: z.string().datetime({ offset: true }),
108
+ idempotency: z
109
+ .object({
110
+ status: McpMutationReceiptIdempotencyStatus,
111
+ })
112
+ .strict(),
113
+ partialFailure: z
114
+ .object({
115
+ stage: boundedUtf8String(128, 1),
116
+ retryable: z.boolean(),
117
+ })
118
+ .strict()
119
+ .optional(),
120
+ warnings: z.array(boundedUtf8String(512, 1)).max(20),
121
+ nextAction: z
122
+ .object({
123
+ tool: boundedUtf8String(128, 1),
124
+ arguments: McpMutationReceiptNextActionArguments,
125
+ })
126
+ .strict()
127
+ .optional(),
128
+ /** Operation-specific outcome facts. Values are deliberately bounded scalars. */
129
+ facts: McpMutationReceiptFacts.optional(),
130
+ /**
131
+ * Bounded session_create compatibility aliases. Existing orchestration
132
+ * consumers use these server-authored lineage facts to spawn descendants
133
+ * without fetching the full session entity.
134
+ */
135
+ id: boundedUtf8String(256, 1).optional(),
136
+ rootSessionId: boundedUtf8String(256, 1).optional(),
137
+ nestedAgentDepth: z.number().int().nonnegative().optional(),
138
+ effectiveMaxNestedAgentDepth: z.number().int().nonnegative().optional(),
139
+ /** Bounded session_steer compatibility alias for resource.id. */
140
+ updateId: boundedUtf8String(256, 1).optional(),
141
+ })
142
+ .strict()
143
+ .superRefine((receipt, context) => {
144
+ const sessionCreateCompatibility = [
145
+ ["id", receipt.id],
146
+ ["rootSessionId", receipt.rootSessionId],
147
+ ["nestedAgentDepth", receipt.nestedAgentDepth],
148
+ ["effectiveMaxNestedAgentDepth", receipt.effectiveMaxNestedAgentDepth],
149
+ ] as const;
150
+ if (receipt.operation === "session_create") {
151
+ for (const [field, value] of sessionCreateCompatibility) {
152
+ if (value === undefined) {
153
+ context.addIssue({
154
+ code: "custom",
155
+ path: [field],
156
+ message: `session_create receipts require ${field}`,
157
+ });
158
+ }
159
+ }
160
+ if (receipt.id !== undefined && receipt.id !== receipt.resource.id) {
161
+ context.addIssue({
162
+ code: "custom",
163
+ path: ["id"],
164
+ message: "session_create id must equal resource.id",
165
+ });
166
+ }
167
+ } else {
168
+ for (const [field, value] of sessionCreateCompatibility) {
169
+ if (value !== undefined) {
170
+ context.addIssue({
171
+ code: "custom",
172
+ path: [field],
173
+ message: `${field} is only valid for session_create receipts`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+
179
+ if (receipt.operation === "session_steer") {
180
+ if (receipt.updateId === undefined) {
181
+ context.addIssue({
182
+ code: "custom",
183
+ path: ["updateId"],
184
+ message: "session_steer receipts require updateId",
185
+ });
186
+ } else if (receipt.updateId !== receipt.resource.id) {
187
+ context.addIssue({
188
+ code: "custom",
189
+ path: ["updateId"],
190
+ message: "session_steer updateId must equal resource.id",
191
+ });
192
+ }
193
+ } else if (receipt.updateId !== undefined) {
194
+ context.addIssue({
195
+ code: "custom",
196
+ path: ["updateId"],
197
+ message: "updateId is only valid for session_steer receipts",
198
+ });
199
+ }
200
+
201
+ if (receipt.outcome === "partial_failure") {
202
+ if (!receipt.committed) {
203
+ context.addIssue({
204
+ code: "custom",
205
+ path: ["committed"],
206
+ message: "partial-failure receipts describe a committed mutation",
207
+ });
208
+ }
209
+ if (!receipt.partialFailure) {
210
+ context.addIssue({
211
+ code: "custom",
212
+ path: ["partialFailure"],
213
+ message: "partial-failure receipts require stage and retryability",
214
+ });
215
+ }
216
+ } else if (receipt.partialFailure) {
217
+ context.addIssue({
218
+ code: "custom",
219
+ path: ["partialFailure"],
220
+ message: "partialFailure is only valid for a partial_failure outcome",
221
+ });
222
+ }
223
+
224
+ if (receipt.outcome === "unchanged" && receipt.changed) {
225
+ context.addIssue({
226
+ code: "custom",
227
+ path: ["changed"],
228
+ message: "unchanged receipts cannot report changed=true",
229
+ });
230
+ }
231
+ if (receipt.outcome === "repaired") {
232
+ if (!receipt.changed) {
233
+ context.addIssue({
234
+ code: "custom",
235
+ path: ["changed"],
236
+ message: "a repair must report changed=true",
237
+ });
238
+ }
239
+ if (receipt.idempotency.status !== "applied") {
240
+ context.addIssue({
241
+ code: "custom",
242
+ path: ["idempotency", "status"],
243
+ message: "repaired outcomes require idempotency.status=applied",
244
+ });
245
+ }
246
+ }
247
+ if (receipt.outcome === "replayed") {
248
+ if (receipt.changed) {
249
+ context.addIssue({
250
+ code: "custom",
251
+ path: ["changed"],
252
+ message: "a replay does not apply a new mutation",
253
+ });
254
+ }
255
+ if (receipt.idempotency.status !== "replayed") {
256
+ context.addIssue({
257
+ code: "custom",
258
+ path: ["idempotency", "status"],
259
+ message: "replayed outcomes require idempotency.status=replayed",
260
+ });
261
+ }
262
+ } else if (
263
+ receipt.idempotency.status === "replayed" &&
264
+ !(receipt.outcome === "partial_failure" && !receipt.changed)
265
+ ) {
266
+ context.addIssue({
267
+ code: "custom",
268
+ path: ["outcome"],
269
+ message:
270
+ "idempotency.status=replayed requires a replayed outcome or unchanged partial failure",
271
+ });
272
+ }
273
+
274
+ if (utf8ByteLength(JSON.stringify(receipt, null, 2)) > MCP_MUTATION_RECEIPT_MAX_BYTES) {
275
+ context.addIssue({
276
+ code: "custom",
277
+ message: `receipt must contain at most ${MCP_MUTATION_RECEIPT_MAX_BYTES} UTF-8 bytes when serialized as pretty JSON`,
278
+ });
279
+ }
280
+ });
281
+ export type McpMutationReceipt = z.infer<typeof McpMutationReceipt>;
@@ -7,17 +7,30 @@ export const RETAINED_OUTPUT_DEFAULT_PAGE_BYTES = 256 * 1024;
7
7
  /** Receipts are timeline references, never an extensible metadata bag. */
8
8
  export const RETAINED_OUTPUT_RECEIPT_MAX_BYTES = 2 * 1024;
9
9
 
10
+ /** Screenshot bytes are accounted separately from the 10k-token text policy. */
11
+ export const COMPUTER_SCREENSHOT_MAX_BYTES = 32 * 1024 * 1024;
12
+ export const COMPUTER_SCREENSHOT_MAX_DIMENSION = 16_384;
13
+ export const COMPUTER_SCREENSHOT_MAX_PIXELS = 67_108_864;
14
+ /** Canonical ready-object retention window: 30 days from successful settlement. */
15
+ export const COMPUTER_SCREENSHOT_RETENTION_MS = 30 * 24 * 60 * 60 * 1_000;
16
+ /** Hard workspace reservation across pending + ready screenshot artifacts. */
17
+ export const COMPUTER_SCREENSHOT_WORKSPACE_QUOTA_BYTES = 5 * 1024 * 1024 * 1024;
18
+
10
19
  const encoder = new TextEncoder();
11
20
  const LOWERCASE_UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
12
21
  const LOWERCASE_SHA256 = /^[0-9a-f]{64}$/;
13
22
  const CANONICAL_MEDIA_TYPE = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,62}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,62}$/;
14
- const RETRIEVAL_PATH = /^\/v1\/workspaces\/([0-9a-f-]+)\/artifacts\/([0-9a-f-]+)\/content$/;
23
+ const WORKSPACE_RETRIEVAL_PATH =
24
+ /^\/v1\/workspaces\/([0-9a-f-]+)\/artifacts\/([0-9a-f-]+)\/content$/;
25
+ const SESSION_RETRIEVAL_PATH =
26
+ /^\/v1\/workspaces\/([0-9a-f-]+)\/sessions\/([0-9a-f-]+)\/artifacts\/([0-9a-f-]+)\/content$/;
15
27
 
16
28
  export const RetainedOutputKind = z.enum([
17
29
  "tool_result",
18
30
  "assistant_completion",
19
31
  "internal_update",
20
32
  "event_media",
33
+ "computer_screenshot",
21
34
  "file",
22
35
  ]);
23
36
  export type RetainedOutputKind = z.infer<typeof RetainedOutputKind>;
@@ -30,6 +43,9 @@ export const RetainedOutputUnavailableReason = z.enum([
30
43
  "deleted",
31
44
  "missing_storage",
32
45
  "storage_write_failed",
46
+ "quota_exceeded",
47
+ "invalid_content",
48
+ "oversized",
33
49
  "unsupported",
34
50
  ]);
35
51
  export type RetainedOutputUnavailableReason = z.infer<typeof RetainedOutputUnavailableReason>;
@@ -50,12 +66,27 @@ export const RetainedArtifactReferenceSchema = z
50
66
  originalBytes: z.number().int().nonnegative().safe(),
51
67
  sha256: z.string().regex(LOWERCASE_SHA256),
52
68
  retainedAt: z.string().datetime({ offset: true }),
53
- retention: z
69
+ dimensions: z
54
70
  .object({
55
- policy: z.literal("workspace_file"),
56
- expiresAt: z.null(),
71
+ width: z.number().int().positive().max(COMPUTER_SCREENSHOT_MAX_DIMENSION),
72
+ height: z.number().int().positive().max(COMPUTER_SCREENSHOT_MAX_DIMENSION),
57
73
  })
58
- .strict(),
74
+ .strict()
75
+ .optional(),
76
+ retention: z.union([
77
+ z
78
+ .object({
79
+ policy: z.literal("workspace_file"),
80
+ expiresAt: z.null(),
81
+ })
82
+ .strict(),
83
+ z
84
+ .object({
85
+ policy: z.literal("session_screenshot"),
86
+ expiresAt: z.string().datetime({ offset: true }),
87
+ })
88
+ .strict(),
89
+ ]),
59
90
  retrieval: z
60
91
  .object({
61
92
  method: z.literal("GET"),
@@ -67,15 +98,41 @@ export const RetainedArtifactReferenceSchema = z
67
98
  })
68
99
  .strict()
69
100
  .superRefine((value, ctx) => {
70
- const match = RETRIEVAL_PATH.exec(value.retrieval.path);
71
- if (!match || !LOWERCASE_UUID.test(match[1] ?? "") || match[2] !== value.artifactId) {
101
+ const workspaceMatch = WORKSPACE_RETRIEVAL_PATH.exec(value.retrieval.path);
102
+ const sessionMatch = SESSION_RETRIEVAL_PATH.exec(value.retrieval.path);
103
+ const workspaceId = workspaceMatch?.[1] ?? sessionMatch?.[1];
104
+ const sessionId = sessionMatch?.[2];
105
+ const artifactId = workspaceMatch?.[2] ?? sessionMatch?.[3];
106
+ if (
107
+ !workspaceId ||
108
+ !LOWERCASE_UUID.test(workspaceId) ||
109
+ (sessionId !== undefined && !LOWERCASE_UUID.test(sessionId)) ||
110
+ artifactId !== value.artifactId
111
+ ) {
72
112
  ctx.addIssue({
73
113
  code: "custom",
74
114
  path: ["retrieval", "path"],
75
- message: "retrieval path must be a workspace API content path for this artifact",
115
+ message: "retrieval path must be a workspace/session API content path for this artifact",
76
116
  });
77
117
  }
78
118
 
119
+ if (value.kind === "computer_screenshot") {
120
+ if (!value.dimensions) {
121
+ ctx.addIssue({
122
+ code: "custom",
123
+ path: ["dimensions"],
124
+ message: "computer screenshot receipts require exact dimensions",
125
+ });
126
+ }
127
+ if (value.retention.policy !== "session_screenshot" || !sessionMatch) {
128
+ ctx.addIssue({
129
+ code: "custom",
130
+ path: ["retention"],
131
+ message: "computer screenshots require session-scoped expiring retrieval",
132
+ });
133
+ }
134
+ }
135
+
79
136
  if (encoder.encode(JSON.stringify(value)).byteLength > RETAINED_OUTPUT_RECEIPT_MAX_BYTES) {
80
137
  ctx.addIssue({
81
138
  code: "custom",
@@ -126,6 +183,13 @@ export type RetainedArtifactFileInput = {
126
183
  updatedAt: string;
127
184
  };
128
185
 
186
+ export type RetainedScreenshotArtifactInput = RetainedArtifactFileInput & {
187
+ sessionId: string;
188
+ width: number;
189
+ height: number;
190
+ expiresAt: string;
191
+ };
192
+
129
193
  /**
130
194
  * Convert a ready, integrity-addressed workspace file into the only available
131
195
  * retained-output receipt shape. Invalid, pending, or checksum-less files fail
@@ -159,6 +223,35 @@ export function retainedArtifactReferenceFromFile(
159
223
  return parsed.success ? parsed.data : null;
160
224
  }
161
225
 
226
+ /** Build the closed, session-scoped receipt for one ready screenshot file. */
227
+ export function retainedScreenshotReferenceFromFile(
228
+ file: RetainedScreenshotArtifactInput,
229
+ ): RetainedArtifactReference | null {
230
+ if (file.status !== "ready" || !file.sha256 || file.sizeBytes <= 0) return null;
231
+ const value = {
232
+ available: true as const,
233
+ artifactId: file.id,
234
+ kind: "computer_screenshot" as const,
235
+ contentType: canonicalRetainedContentType(file.contentType),
236
+ originalBytes: file.sizeBytes,
237
+ sha256: file.sha256,
238
+ retainedAt: file.updatedAt,
239
+ dimensions: { width: file.width, height: file.height },
240
+ retention: {
241
+ policy: "session_screenshot" as const,
242
+ expiresAt: file.expiresAt,
243
+ },
244
+ retrieval: {
245
+ method: "GET" as const,
246
+ path: `/v1/workspaces/${file.workspaceId}/sessions/${file.sessionId}/artifacts/${file.id}/content`,
247
+ acceptRanges: "bytes" as const,
248
+ maxRangeBytes: RETAINED_OUTPUT_MAX_PAGE_BYTES,
249
+ },
250
+ };
251
+ const parsed = RetainedArtifactReferenceSchema.safeParse(value);
252
+ return parsed.success ? parsed.data : null;
253
+ }
254
+
162
255
  function canonicalRetainedContentType(value: string): string {
163
256
  const mediaType = value.split(";", 1)[0]?.trim().toLowerCase() ?? "";
164
257
  return CANONICAL_MEDIA_TYPE.test(mediaType) ? mediaType : "application/octet-stream";
@@ -0,0 +1,169 @@
1
+ import { z } from "zod";
2
+
3
+ export const TranscriptionRecordingErrorCode = z.enum([
4
+ "permission_denied",
5
+ "not_supported",
6
+ "network",
7
+ "provider",
8
+ "policy_blocked",
9
+ "timeout",
10
+ "cancelled",
11
+ "unavailable",
12
+ "too_large",
13
+ "invalid_audio",
14
+ "unknown",
15
+ ]);
16
+ export type TranscriptionRecordingErrorCode = z.infer<typeof TranscriptionRecordingErrorCode>;
17
+
18
+ export const TranscriptionRecordingState = z.enum([
19
+ "uploading",
20
+ "segmenting",
21
+ "ready",
22
+ "transcribing",
23
+ "complete",
24
+ "failed",
25
+ "discarded",
26
+ ]);
27
+ export type TranscriptionRecordingState = z.infer<typeof TranscriptionRecordingState>;
28
+
29
+ export const TranscriptionRecordingSegmentState = z.enum([
30
+ "preparing",
31
+ "pending",
32
+ "transcribing",
33
+ "complete",
34
+ "failed",
35
+ ]);
36
+ export type TranscriptionRecordingSegmentState = z.infer<typeof TranscriptionRecordingSegmentState>;
37
+
38
+ export const ClientResumableVoiceInputConfig = z
39
+ .object({
40
+ maxDurationSeconds: z
41
+ .number()
42
+ .int()
43
+ .positive()
44
+ .max(8 * 60 * 60),
45
+ maxSizeBytes: z
46
+ .number()
47
+ .int()
48
+ .positive()
49
+ .max(512 * 1024 * 1024),
50
+ maxChunkSizeBytes: z
51
+ .number()
52
+ .int()
53
+ .positive()
54
+ .max(25 * 1024 * 1024),
55
+ providerSegmentSeconds: z.number().int().positive().max(600),
56
+ })
57
+ .strict();
58
+ export type ClientResumableVoiceInputConfig = z.infer<typeof ClientResumableVoiceInputConfig>;
59
+
60
+ export const CreateTranscriptionRecordingRequest = z
61
+ .object({
62
+ recordingId: z.string().uuid(),
63
+ mimeType: z.string().trim().min(1).max(128),
64
+ })
65
+ .strict();
66
+ export type CreateTranscriptionRecordingRequest = z.infer<
67
+ typeof CreateTranscriptionRecordingRequest
68
+ >;
69
+
70
+ export const FinalizeTranscriptionRecordingRequest = z
71
+ .object({
72
+ chunkCount: z.number().int().positive().max(100_000),
73
+ totalBytes: z
74
+ .number()
75
+ .int()
76
+ .positive()
77
+ .max(512 * 1024 * 1024),
78
+ totalDurationMilliseconds: z
79
+ .number()
80
+ .int()
81
+ .positive()
82
+ .max(8 * 60 * 60 * 1_000),
83
+ })
84
+ .strict();
85
+ export type FinalizeTranscriptionRecordingRequest = z.infer<
86
+ typeof FinalizeTranscriptionRecordingRequest
87
+ >;
88
+
89
+ export const TranscriptionRecordingChunk = z
90
+ .object({
91
+ chunkNumber: z.number().int().nonnegative(),
92
+ byteLength: z.number().int().positive(),
93
+ sha256: z.string().regex(/^[0-9a-f]{64}$/),
94
+ startMilliseconds: z.number().int().nonnegative(),
95
+ durationMilliseconds: z.number().int().nonnegative(),
96
+ deduplicated: z.boolean(),
97
+ })
98
+ .strict();
99
+ export type TranscriptionRecordingChunk = z.infer<typeof TranscriptionRecordingChunk>;
100
+
101
+ export const TranscriptionRecordingSegment = z
102
+ .object({
103
+ segmentNumber: z.number().int().nonnegative(),
104
+ state: TranscriptionRecordingSegmentState,
105
+ startMilliseconds: z.number().int().nonnegative(),
106
+ durationMilliseconds: z.number().int().positive(),
107
+ byteLength: z.number().int().positive(),
108
+ errorCode: TranscriptionRecordingErrorCode.nullable(),
109
+ retryable: z.boolean(),
110
+ })
111
+ .strict();
112
+ export type TranscriptionRecordingSegment = z.infer<typeof TranscriptionRecordingSegment>;
113
+
114
+ export const TranscriptionRecording = z
115
+ .object({
116
+ id: z.string().uuid(),
117
+ workspaceId: z.string().uuid(),
118
+ mimeType: z.string().trim().min(1).max(128),
119
+ state: TranscriptionRecordingState,
120
+ nextChunkNumber: z.number().int().nonnegative(),
121
+ chunkCount: z.number().int().nonnegative(),
122
+ totalBytes: z.number().int().nonnegative(),
123
+ totalDurationMilliseconds: z.number().int().nonnegative(),
124
+ segmentCount: z.number().int().nonnegative(),
125
+ completedSegmentCount: z.number().int().nonnegative(),
126
+ transcriptText: z.string().max(1_000_000).nullable(),
127
+ languages: z.array(z.string().trim().min(1).max(64)).max(64),
128
+ errorCode: TranscriptionRecordingErrorCode.nullable(),
129
+ retryable: z.boolean(),
130
+ objectsCleaned: z.boolean(),
131
+ createdAt: z.string().datetime(),
132
+ updatedAt: z.string().datetime(),
133
+ expiresAt: z.string().datetime(),
134
+ })
135
+ .strict();
136
+ export type TranscriptionRecording = z.infer<typeof TranscriptionRecording>;
137
+
138
+ export const TranscriptionRecordingResponse = z
139
+ .object({
140
+ recording: TranscriptionRecording,
141
+ segments: z.array(TranscriptionRecordingSegment).max(1_000),
142
+ retryAfterMilliseconds: z.number().int().positive().max(60_000).optional(),
143
+ })
144
+ .strict();
145
+ export type TranscriptionRecordingResponse = z.infer<typeof TranscriptionRecordingResponse>;
146
+
147
+ export const TranscriptionRecordingListResponse = z
148
+ .object({
149
+ recordings: z.array(TranscriptionRecording).max(50),
150
+ })
151
+ .strict();
152
+ export type TranscriptionRecordingListResponse = z.infer<typeof TranscriptionRecordingListResponse>;
153
+
154
+ export const UploadTranscriptionRecordingChunkResponse = z
155
+ .object({
156
+ recording: TranscriptionRecording,
157
+ chunk: TranscriptionRecordingChunk,
158
+ })
159
+ .strict();
160
+ export type UploadTranscriptionRecordingChunkResponse = z.infer<
161
+ typeof UploadTranscriptionRecordingChunkResponse
162
+ >;
163
+
164
+ export const TRANSCRIPTION_RECORDING_MAX_DURATION_SECONDS = 2 * 60 * 60;
165
+ export const TRANSCRIPTION_RECORDING_MAX_SIZE_BYTES = 512 * 1024 * 1024;
166
+ export const TRANSCRIPTION_RECORDING_MAX_CHUNK_SIZE_BYTES = 8 * 1024 * 1024;
167
+ export const TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS = 50;
168
+ export const TRANSCRIPTION_RECORDING_RECOVERY_RETRY_AFTER_MILLISECONDS = 5_000;
169
+ export const TRANSCRIPTION_RECORDING_RETENTION_SECONDS = 24 * 60 * 60;