@neta-art/cohub 5.10.0 → 7.0.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.
- package/README.md +47 -50
- package/dist/board/animation.d.ts +157 -67
- package/dist/board/animation.js +161 -306
- package/dist/board/core/connections.js +4 -4
- package/dist/board/core/export-plan.js +1 -1
- package/dist/board/core/shape-types.js +0 -1
- package/dist/board/geometry.js +4 -4
- package/dist/board/index.d.ts +8 -7
- package/dist/board/index.js +8 -9
- package/dist/board/mutation.d.ts +2 -17
- package/dist/board/mutation.js +2 -93
- package/dist/board/render/renderers/text-card-renderer.js +1 -1
- package/dist/board/semantic-document.d.ts +30 -0
- package/dist/board/semantic-document.js +271 -0
- package/dist/board/semantic-mutation.d.ts +10 -0
- package/dist/board/semantic-mutation.js +203 -0
- package/dist/chunks/http.d.ts +27 -95
- package/dist/chunks/http.js +628 -1610
- package/dist/chunks/transport.js +1 -1
- package/dist/chunks/websocket.d.ts +3462 -533
- package/dist/chunks/websocket.js +1 -1
- package/dist/http.d.ts +3 -3
- package/dist/index.d.ts +158 -299
- package/dist/index.js +1207 -499
- package/dist/protocol/dist/board-authoring.d.ts +1305 -0
- package/dist/protocol/dist/board-authoring.js +291 -0
- package/dist/protocol/dist/board-capability-registry.d.ts +2 -0
- package/dist/protocol/dist/board-capability-registry.js +74 -0
- package/dist/protocol/dist/board-codec.d.ts +2 -0
- package/dist/protocol/dist/board-codec.js +19 -0
- package/dist/protocol/dist/board-composition.d.ts +383 -0
- package/dist/protocol/dist/board-composition.js +310 -0
- package/dist/protocol/dist/board-connection.d.ts +16 -16
- package/dist/protocol/dist/board-connection.js +16 -16
- package/dist/protocol/dist/board-constants.js +0 -25
- package/dist/protocol/dist/board-document.d.ts +34 -3
- package/dist/protocol/dist/board-document.js +3 -1
- package/dist/protocol/dist/board-effect.d.ts +94 -0
- package/dist/protocol/dist/board-effect.js +53 -0
- package/dist/protocol/dist/board-json.js +16 -0
- package/dist/protocol/dist/board-node.d.ts +1 -12
- package/dist/protocol/dist/board-node.js +1 -81
- package/dist/protocol/dist/board.d.ts +42 -245
- package/dist/protocol/dist/board.js +56 -117
- package/dist/protocol/dist/index.d.ts +9 -4
- package/dist/types.d.ts +4 -1
- package/docs/work-runtime-guide.md +19 -3
- package/package.json +2 -2
- package/dist/board/codec.d.ts +0 -27
- package/dist/board/codec.js +0 -277
- package/dist/board/nodes.d.ts +0 -113
- package/dist/board/nodes.js +0 -154
- package/dist/protocol/dist/board-content.js +0 -26
- package/dist/protocol/dist/identifiers.js +0 -10
- package/dist/protocol/dist/index.js +0 -14
- package/dist/protocol/dist/provenance.js +0 -15
- package/dist/protocol/dist/public-identifiers.js +0 -38
- package/dist/protocol/dist/realtime/board-awareness.js +0 -119
- package/dist/protocol/dist/ui-command.js +0 -2
- package/dist/protocol/dist/work-promotion-stats.js +0 -11
- package/dist/protocol/dist/work-surface.js +0 -2
- package/dist/protocol/dist/work-view-stats.js +0 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,880 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as
|
|
1
|
+
import { $ as SessionAccessApi, A as isTerminalUiCommandStatus, B as BOARD_ANIMATION_CHANNELS, C as UI_COMMAND_DEFAULT_TIMEOUT_MS, D as UI_COMMAND_SETTLEMENT_GRACE_SECONDS, E as UI_COMMAND_PENDING_TTL_SECONDS, F as BoardItemPatchSchema, G as BoardTrackSchema, H as BoardCompositionInputSchema, I as BoardSemanticMutationSchema, J as BOARD_ARROW_STROKE_SIZE, K as parseBoardCompositionInput, L as BoardEffectInputSchema, M as parseUiCommand, N as parseUiCommandId, O as UI_COMMAND_TERMINAL_TTL_SECONDS, P as BoardAuthoringItemSchema, Q as DEFAULT_BOARD_RENDER_LIMITS, R as BoardEffectSchema, S as ensureRealtimeConnected, T as UI_COMMAND_PAYLOAD_MAX_BYTES, U as BoardCompositionSchema, V as BOARD_ANIMATION_CHANNEL_CAPABILITIES, W as BoardProceduralClipSchema, X as BOARD_BUILTIN_CLIP_KINDS, Y as BOARD_BUILTIN_CAPABILITIES, Z as BOARD_BUILTIN_EFFECT_KINDS, _ as SessionGenerationStreamClient, a as ReferralsApi, at as ModelsApi, b as SessionPatchReducer, c as UiCommandsApi, ct as ChannelsApi, d as SpaceClient, et as ReferencesApi, f as SpacePublicFilesApi, g as buildSpacePath, h as buildSpaceInvitePath, i as WorksApi, it as PromptsApi, j as isUiSurfaceMethod, k as UI_COMMAND_VERSION, l as TasksApi, m as PublicInviteApi, n as createHttpClient, nt as PublicAssetsApi, o as UsersApi, ot as GenerationsApi, p as SpacesApi, q as BoardConnectionSchema, r as WorkCommerceApi, rt as SkillsApi, s as UserApi, st as CronJobsApi, t as CohubHttpClient, tt as SearchApi, u as BoardClient, v as createSessionGenerationStreamClient, w as UI_COMMAND_MAX_TIMEOUT_MS, x as createSessionPatchReducer, y as parseAssistantMessageCommit, z as parseBoardEffectInput } from "./chunks/http.js";
|
|
2
|
+
import { _ as requestSourceToHeaders, a as sanitizeAccessToken, b as REALTIME_ROOM_EVENT_NAME_PATTERN, c as REQUEST_SOURCE_VIA_MAX_LENGTH, d as isRequestSourceEmpty, f as isRequestSourceUuid, g as readRequestSourceFromEnv, h as parseRequestSourceFromHeaders, i as matchesUnauthorizedErrorToken, l as hasRequestSourceIdentity, m as normalizeRequestSource, n as HttpTransport, o as COHUB_SOURCE_HEADER, p as mergeRequestSourceIntoMeta, r as joinApiUrl, s as COHUB_SOURCE_HEADER_NAMES, t as HttpError, u as isRequestSourceClientId, v as resolveRequestSourceChannel, y as isUuid } from "./chunks/transport.js";
|
|
3
3
|
import { a as resolveApiBaseUrl, c as resolveWebsocketUrl, i as normalizeWebsocketUrl, n as normalizeBaseUrl, o as resolveCohubEnvironment, r as normalizeVoiceInputWebsocketUrl, s as resolveVoiceInputWebsocketUrl, t as COHUB_ENVIRONMENTS } from "./chunks/environment.js";
|
|
4
4
|
import { a as extractBillingPayload, c as isFeatureNotEntitledError, i as FEATURE_NOT_ENTITLED_ERROR_CODE, l as isHttpErrorCode, n as createWebsocketClient, o as isBillingAccessBlockedCode, r as BILLING_ACCESS_BLOCKED_ERROR_CODE, s as isBillingAccessBlockedError, t as WebsocketClient } from "./chunks/websocket.js";
|
|
5
5
|
import { VoiceApi, VoiceInputClient, createVoiceInputClient } from "./voice-input.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
//#region ../protocol/dist/board.js
|
|
8
|
+
const BOARD_DOCUMENT_KIND = "cohub.board";
|
|
9
|
+
const BOARD_MANIFEST_KIND = "cohub.board.manifest";
|
|
10
|
+
const idSchema = z.string().min(1).max(160);
|
|
11
|
+
const jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
12
|
+
const finiteSchema = z.number().finite();
|
|
13
|
+
z.object({
|
|
14
|
+
kind: z.literal(BOARD_MANIFEST_KIND),
|
|
15
|
+
version: z.literal(1),
|
|
16
|
+
boardId: z.string().uuid(),
|
|
17
|
+
title: z.string().min(1).max(255)
|
|
18
|
+
});
|
|
19
|
+
z.object({
|
|
20
|
+
centerX: finiteSchema,
|
|
21
|
+
centerY: finiteSchema,
|
|
22
|
+
zoom: finiteSchema.positive()
|
|
23
|
+
});
|
|
24
|
+
const BoardCameraFocusSchema = z.discriminatedUnion("type", [
|
|
25
|
+
z.object({
|
|
26
|
+
type: z.literal("rect"),
|
|
27
|
+
rect: z.object({
|
|
28
|
+
x: finiteSchema,
|
|
29
|
+
y: finiteSchema,
|
|
30
|
+
width: finiteSchema.positive(),
|
|
31
|
+
height: finiteSchema.positive()
|
|
32
|
+
})
|
|
33
|
+
}),
|
|
34
|
+
z.object({
|
|
35
|
+
type: z.literal("item"),
|
|
36
|
+
itemId: idSchema
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
type: z.literal("items"),
|
|
40
|
+
itemIds: z.array(idSchema).min(1).max(1e3)
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
type: z.literal("frame"),
|
|
44
|
+
frameId: idSchema
|
|
45
|
+
})
|
|
46
|
+
]);
|
|
47
|
+
const BoardCameraFocusParamsSchema = z.object({
|
|
48
|
+
focus: BoardCameraFocusSchema,
|
|
49
|
+
fit: z.enum(["contain", "cover"]).default("contain"),
|
|
50
|
+
padding: finiteSchema.nonnegative().default(32),
|
|
51
|
+
minZoom: finiteSchema.positive().optional(),
|
|
52
|
+
maxZoom: finiteSchema.positive().optional()
|
|
53
|
+
}).superRefine((value, context) => {
|
|
54
|
+
if (value.minZoom !== void 0 && value.maxZoom !== void 0 && value.minZoom > value.maxZoom) context.addIssue({
|
|
55
|
+
code: "custom",
|
|
56
|
+
message: "minZoom must not exceed maxZoom",
|
|
57
|
+
path: ["minZoom"]
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
z.object({
|
|
61
|
+
nodeId: idSchema,
|
|
62
|
+
type: z.string().min(1).max(40),
|
|
63
|
+
parentId: idSchema.nullable(),
|
|
64
|
+
orderKey: z.string().max(4096).nullable(),
|
|
65
|
+
x: finiteSchema,
|
|
66
|
+
y: finiteSchema,
|
|
67
|
+
width: finiteSchema.positive(),
|
|
68
|
+
height: finiteSchema.positive(),
|
|
69
|
+
rotation: finiteSchema,
|
|
70
|
+
refKind: z.string().max(40).nullable(),
|
|
71
|
+
refPath: z.string().max(4096).nullable(),
|
|
72
|
+
refUrl: z.string().max(4096).nullable(),
|
|
73
|
+
view: jsonObjectSchema,
|
|
74
|
+
style: jsonObjectSchema,
|
|
75
|
+
data: jsonObjectSchema
|
|
76
|
+
});
|
|
77
|
+
const stripBoardServerFields = (value) => {
|
|
78
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
79
|
+
const { boardId: _boardId, revision: _revision, ...input } = value;
|
|
80
|
+
return input;
|
|
81
|
+
};
|
|
82
|
+
const BoardCreateEffectInputSchema = z.preprocess(stripBoardServerFields, BoardEffectInputSchema);
|
|
83
|
+
const BoardCreateCompositionInputSchema = z.preprocess(stripBoardServerFields, BoardCompositionInputSchema);
|
|
84
|
+
const BoardCreateInputSchema = z.object({
|
|
85
|
+
path: z.string().min(1),
|
|
86
|
+
/** Reused by clients when board creation is interrupted and retried. */
|
|
87
|
+
mutationId: z.string().max(128).optional(),
|
|
88
|
+
title: z.string().min(1).max(255).optional(),
|
|
89
|
+
metadata: jsonObjectSchema.optional(),
|
|
90
|
+
items: z.array(BoardAuthoringItemSchema).max(5e4).optional(),
|
|
91
|
+
connections: z.array(BoardConnectionSchema).max(5e4).optional(),
|
|
92
|
+
effects: z.array(BoardCreateEffectInputSchema).max(5e4).optional(),
|
|
93
|
+
compositions: z.array(BoardCreateCompositionInputSchema).max(5e4).optional()
|
|
94
|
+
});
|
|
95
|
+
const jsonSchema$1 = (schema) => z.toJSONSchema(schema);
|
|
96
|
+
const authoringSchemaFactories = {
|
|
97
|
+
item: () => jsonSchema$1(BoardAuthoringItemSchema),
|
|
98
|
+
itemPatch: () => jsonSchema$1(BoardItemPatchSchema),
|
|
99
|
+
mutation: () => jsonSchema$1(BoardSemanticMutationSchema),
|
|
100
|
+
composition: () => jsonSchema$1(BoardCompositionInputSchema),
|
|
101
|
+
effect: () => jsonSchema$1(BoardEffectInputSchema),
|
|
102
|
+
create: () => jsonSchema$1(BoardCreateInputSchema)
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* JSON Schemas for the semantic authoring surface. Each property is enumerable
|
|
106
|
+
* for Object.keys/JSON.stringify, but generated only when that property is
|
|
107
|
+
* requested. This keeps protocol imports cheap while capabilities still returns
|
|
108
|
+
* the complete machine-readable schema object.
|
|
109
|
+
*/
|
|
110
|
+
const BOARD_AUTHORING_SCHEMAS = {};
|
|
111
|
+
for (const key of Object.keys(authoringSchemaFactories)) {
|
|
112
|
+
let cached;
|
|
113
|
+
Object.defineProperty(BOARD_AUTHORING_SCHEMAS, key, {
|
|
114
|
+
enumerable: true,
|
|
115
|
+
configurable: false,
|
|
116
|
+
get() {
|
|
117
|
+
cached ??= authoringSchemaFactories[key]();
|
|
118
|
+
return cached;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
z.object({
|
|
123
|
+
include: z.array(z.enum([
|
|
124
|
+
"nodes",
|
|
125
|
+
"connections",
|
|
126
|
+
"effects",
|
|
127
|
+
"compositions",
|
|
128
|
+
"playback"
|
|
129
|
+
])).optional(),
|
|
130
|
+
nodeIds: z.array(idSchema).max(5e4).optional(),
|
|
131
|
+
connectionIds: z.array(idSchema).max(5e4).optional(),
|
|
132
|
+
effectIds: z.array(idSchema).max(5e4).optional(),
|
|
133
|
+
compositionIds: z.array(idSchema).max(5e4).optional(),
|
|
134
|
+
viewport: z.object({
|
|
135
|
+
x: finiteSchema,
|
|
136
|
+
y: finiteSchema,
|
|
137
|
+
width: finiteSchema.positive(),
|
|
138
|
+
height: finiteSchema.positive()
|
|
139
|
+
}).optional()
|
|
140
|
+
});
|
|
141
|
+
/** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
|
|
142
|
+
const BoardPlaybackPolicySchema = z.object({
|
|
143
|
+
compositionId: idSchema,
|
|
144
|
+
/** Delay before the first local playback after opening the Board, in milliseconds. */
|
|
145
|
+
delayMs: finiteSchema.nonnegative().default(0)
|
|
146
|
+
});
|
|
147
|
+
function parseBoardPlaybackPolicy(metadata) {
|
|
148
|
+
const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
|
|
149
|
+
return parsed.success ? parsed.data : null;
|
|
150
|
+
}
|
|
151
|
+
z.discriminatedUnion("type", [
|
|
152
|
+
z.object({
|
|
153
|
+
commandId: idSchema,
|
|
154
|
+
type: z.literal("play"),
|
|
155
|
+
compositionId: idSchema,
|
|
156
|
+
position: finiteSchema.nonnegative().optional(),
|
|
157
|
+
timeScale: finiteSchema.positive().max(4).optional(),
|
|
158
|
+
shared: z.boolean().optional(),
|
|
159
|
+
seed: idSchema.optional()
|
|
160
|
+
}),
|
|
161
|
+
z.object({
|
|
162
|
+
commandId: idSchema,
|
|
163
|
+
type: z.literal("pause"),
|
|
164
|
+
playbackId: z.string().uuid()
|
|
165
|
+
}),
|
|
166
|
+
z.object({
|
|
167
|
+
commandId: idSchema,
|
|
168
|
+
type: z.literal("seek"),
|
|
169
|
+
playbackId: z.string().uuid(),
|
|
170
|
+
position: finiteSchema.nonnegative()
|
|
171
|
+
}),
|
|
172
|
+
z.object({
|
|
173
|
+
commandId: idSchema,
|
|
174
|
+
type: z.literal("stop"),
|
|
175
|
+
playbackId: z.string().uuid()
|
|
176
|
+
})
|
|
177
|
+
]);
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region ../protocol/dist/board-capability-registry.js
|
|
180
|
+
const CLIPS = new Set(BOARD_BUILTIN_CLIP_KINDS);
|
|
181
|
+
new Set(BOARD_BUILTIN_EFFECT_KINDS);
|
|
182
|
+
const record = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
183
|
+
const finite = (value) => typeof value === "number" && Number.isFinite(value);
|
|
184
|
+
function validateBuiltinBoardClip(clip, path = "clip") {
|
|
185
|
+
if (!CLIPS.has(clip.kind)) return [];
|
|
186
|
+
const errors = [];
|
|
187
|
+
const error = (message, suffix, coordinateSpace, code = "INVALID_BOARD_CLIP") => {
|
|
188
|
+
errors.push({
|
|
189
|
+
severity: "error",
|
|
190
|
+
code,
|
|
191
|
+
message,
|
|
192
|
+
path: `${path}.${suffix}`,
|
|
193
|
+
...coordinateSpace ? { coordinateSpace } : {}
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
if ((clip.kind.startsWith("motion.") || clip.kind.startsWith("draw.") || clip.kind === "text.reveal" || clip.kind === "effects.trail") && clip.target.type !== "item") error(`${clip.kind} must target an item`, "target");
|
|
197
|
+
if (clip.kind.startsWith("camera.") && clip.target.type !== "camera") error(`${clip.kind} must target the camera`, "target");
|
|
198
|
+
if (clip.kind === "motion.path") {
|
|
199
|
+
const points = clip.params.points;
|
|
200
|
+
if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) error("motion path must contain 2 to 10000 world-offset points", "params.points", "world-offset");
|
|
201
|
+
else for (const [index, point] of points.entries()) if (!record(point) || !finite(point.x) || !finite(point.y)) error("motion path point must contain finite x and y", `params.points.${index}`, "world-offset");
|
|
202
|
+
}
|
|
203
|
+
if (clip.kind === "effects.particles") {
|
|
204
|
+
const count = clip.params.count;
|
|
205
|
+
if (!Number.isSafeInteger(count) || count < 1 || count > DEFAULT_BOARD_RENDER_LIMITS.particles) error(`particle count must be an integer from 1 to ${DEFAULT_BOARD_RENDER_LIMITS.particles}`, "params.count", void 0, "INVALID_PARTICLE_COUNT");
|
|
206
|
+
const bounds = clip.params.bounds;
|
|
207
|
+
if (!record(bounds) || !finite(bounds.x) || !finite(bounds.y) || !finite(bounds.width) || !finite(bounds.height) || bounds.width <= 0 || bounds.height <= 0) error("particles require positive finite world bounds", "params.bounds", "world", "PARTICLE_BOUNDS_REQUIRED");
|
|
208
|
+
}
|
|
209
|
+
if (clip.kind === "camera.focus") {
|
|
210
|
+
const parsed = BoardCameraFocusParamsSchema.safeParse(clip.params);
|
|
211
|
+
if (!parsed.success) error(parsed.error.issues[0]?.message ?? "invalid camera focus", "params", "world");
|
|
212
|
+
}
|
|
213
|
+
return errors;
|
|
214
|
+
}
|
|
215
|
+
function estimateBuiltinBoardClipCost(clip) {
|
|
216
|
+
switch (clip.kind) {
|
|
217
|
+
case "effects.particles": {
|
|
218
|
+
const count = Number.isSafeInteger(clip.params.count) ? Math.max(0, clip.params.count) : 0;
|
|
219
|
+
return {
|
|
220
|
+
particles: count,
|
|
221
|
+
vertices: count * 4,
|
|
222
|
+
dynamicVertices: count * 4,
|
|
223
|
+
drawCalls: 1,
|
|
224
|
+
bufferBytes: count * 48,
|
|
225
|
+
simulationSteps: count
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
case "effects.trail": return {
|
|
229
|
+
vertices: 32,
|
|
230
|
+
dynamicVertices: 32,
|
|
231
|
+
drawCalls: 1,
|
|
232
|
+
bufferBytes: 1024,
|
|
233
|
+
simulationSteps: 16
|
|
234
|
+
};
|
|
235
|
+
case "effects.impact":
|
|
236
|
+
case "effects.flash": return {
|
|
237
|
+
vertices: 64,
|
|
238
|
+
drawCalls: 1
|
|
239
|
+
};
|
|
240
|
+
case "draw.reveal":
|
|
241
|
+
case "draw.handwrite": return {
|
|
242
|
+
drawCalls: 1,
|
|
243
|
+
dynamicVertices: 1
|
|
244
|
+
};
|
|
245
|
+
case "motion.path": return { simulationSteps: Array.isArray(clip.params.points) ? clip.params.points.length : 0 };
|
|
246
|
+
default: return {};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region ../protocol/dist/board-url.js
|
|
251
|
+
const BOARD_REMOTE_URL_MAX_LENGTH = 4096;
|
|
252
|
+
function parseIpv4(host) {
|
|
253
|
+
const parts = host.split(".").map(Number);
|
|
254
|
+
return parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255) ? parts : null;
|
|
255
|
+
}
|
|
256
|
+
function isBlockedIpv4(host) {
|
|
257
|
+
const parts = parseIpv4(host);
|
|
258
|
+
if (!parts) return false;
|
|
259
|
+
const [first, second, third] = parts;
|
|
260
|
+
if (first === 0 || first === 10 || first === 127) return true;
|
|
261
|
+
if (first === 100 && second >= 64 && second <= 127) return true;
|
|
262
|
+
if (first === 169 && second === 254) return true;
|
|
263
|
+
if (first === 172 && second >= 16 && second <= 31) return true;
|
|
264
|
+
if (first === 192 && second === 168) return true;
|
|
265
|
+
if (first === 192 && second === 0 && (third === 0 || third === 2)) return true;
|
|
266
|
+
if (first === 192 && second === 88 && third === 99) return true;
|
|
267
|
+
if (first === 198 && (second === 18 || second === 19)) return true;
|
|
268
|
+
if (first === 198 && second === 51 && third === 100) return true;
|
|
269
|
+
if (first === 203 && second === 0 && third === 113) return true;
|
|
270
|
+
return first >= 224;
|
|
271
|
+
}
|
|
272
|
+
function expandIpv6(host) {
|
|
273
|
+
const [head, tail, extra] = host.toLowerCase().split("::");
|
|
274
|
+
if (extra !== void 0) return null;
|
|
275
|
+
const headParts = head ? head.split(":").filter(Boolean) : [];
|
|
276
|
+
const tailParts = tail ? tail.split(":").filter(Boolean) : [];
|
|
277
|
+
const missing = 8 - headParts.length - tailParts.length;
|
|
278
|
+
if (missing < 0 || tail === void 0 && missing !== 0) return null;
|
|
279
|
+
const parts = [
|
|
280
|
+
...headParts,
|
|
281
|
+
...Array.from({ length: missing }, () => "0"),
|
|
282
|
+
...tailParts
|
|
283
|
+
];
|
|
284
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part))) return null;
|
|
285
|
+
return parts.map((part) => part.padStart(4, "0"));
|
|
286
|
+
}
|
|
287
|
+
function isBlockedIpv6(host) {
|
|
288
|
+
const parts = expandIpv6(host);
|
|
289
|
+
if (!parts) return true;
|
|
290
|
+
if (parts.every((part) => part === "0000")) return true;
|
|
291
|
+
if (parts.slice(0, 7).every((part) => part === "0000") && parts[7] === "0001") return true;
|
|
292
|
+
if (parts.slice(0, 5).every((part) => part === "0000") && parts[5] === "ffff") {
|
|
293
|
+
const high = Number.parseInt(parts[6] ?? "0", 16);
|
|
294
|
+
const low = Number.parseInt(parts[7] ?? "0", 16);
|
|
295
|
+
return isBlockedIpv4(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
|
296
|
+
}
|
|
297
|
+
if (parts.slice(0, 6).every((part) => part === "0000")) return true;
|
|
298
|
+
const first = Number.parseInt(parts[0] ?? "0", 16);
|
|
299
|
+
if ((first & 65024) === 64512) return true;
|
|
300
|
+
if ((first & 65472) === 65152 || (first & 65472) === 65216) return true;
|
|
301
|
+
if ((first & 65280) === 65280) return true;
|
|
302
|
+
return parts[0] === "2001" && parts[1] === "0db8";
|
|
303
|
+
}
|
|
304
|
+
function isPublicBoardRemoteAddress(value) {
|
|
305
|
+
const address = value.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
306
|
+
if (parseIpv4(address)) return !isBlockedIpv4(address);
|
|
307
|
+
return address.includes(":") && !isBlockedIpv6(address);
|
|
308
|
+
}
|
|
309
|
+
function isBlockedHost(hostname) {
|
|
310
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
|
|
311
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal")) return true;
|
|
312
|
+
if (parseIpv4(host) || host.includes(":")) return !isPublicBoardRemoteAddress(host);
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Normalize a browser-loadable public HTTP(S) URL. This blocks explicit local
|
|
317
|
+
* addresses; any future server-side fetcher must additionally validate DNS
|
|
318
|
+
* resolution to defend against rebinding.
|
|
319
|
+
*/
|
|
320
|
+
function normalizeBoardRemoteUrl(value) {
|
|
321
|
+
if (typeof value !== "string") return void 0;
|
|
322
|
+
const input = value.trim();
|
|
323
|
+
if (!input || input.length > 4096) return void 0;
|
|
324
|
+
try {
|
|
325
|
+
const url = new URL(input);
|
|
326
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") return void 0;
|
|
327
|
+
if (url.username || url.password || isBlockedHost(url.hostname)) return;
|
|
328
|
+
const normalized = url.toString();
|
|
329
|
+
return normalized.length <= 4096 ? normalized : void 0;
|
|
330
|
+
} catch {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const BoardRemoteUrlSchema = z.string().max(BOARD_REMOTE_URL_MAX_LENGTH).refine((value) => normalizeBoardRemoteUrl(value) !== void 0, { message: "URL must be a public HTTP(S) URL without credentials" });
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region ../protocol/dist/board-document.js
|
|
337
|
+
const BoardFrameSchema = z.object({
|
|
338
|
+
x: z.number().finite(),
|
|
339
|
+
y: z.number().finite(),
|
|
340
|
+
width: z.number().finite().positive(),
|
|
341
|
+
height: z.number().finite().positive(),
|
|
342
|
+
rotation: z.number().finite().default(0)
|
|
343
|
+
});
|
|
344
|
+
/**
|
|
345
|
+
* The board camera. This is local UI state, not synced content: semantic ops
|
|
346
|
+
* (see diffBoardDocuments) never describe the viewport, and the editor holds
|
|
347
|
+
* the live camera separately from the persisted document. Here it only serves
|
|
348
|
+
* as an initial camera hint when a document is first loaded.
|
|
349
|
+
*/
|
|
350
|
+
const BoardViewportSchema = z.object({
|
|
351
|
+
x: z.number().finite(),
|
|
352
|
+
y: z.number().finite(),
|
|
353
|
+
zoom: z.number().finite().min(.05).max(8)
|
|
354
|
+
});
|
|
355
|
+
const BoardAppearanceSchema = z.object({
|
|
356
|
+
theme: z.string().min(1).default("clean"),
|
|
357
|
+
background: z.object({
|
|
358
|
+
kind: z.enum([
|
|
359
|
+
"solid",
|
|
360
|
+
"dots",
|
|
361
|
+
"grid",
|
|
362
|
+
"image",
|
|
363
|
+
"shader",
|
|
364
|
+
"custom"
|
|
365
|
+
]).default("dots"),
|
|
366
|
+
color: z.string().optional(),
|
|
367
|
+
imageUrl: BoardRemoteUrlSchema.optional(),
|
|
368
|
+
fit: z.enum([
|
|
369
|
+
"cover",
|
|
370
|
+
"contain",
|
|
371
|
+
"repeat"
|
|
372
|
+
]).optional(),
|
|
373
|
+
position: z.enum([
|
|
374
|
+
"center",
|
|
375
|
+
"top",
|
|
376
|
+
"bottom",
|
|
377
|
+
"left",
|
|
378
|
+
"right"
|
|
379
|
+
]).optional(),
|
|
380
|
+
opacity: z.number().finite().min(0).max(1).optional()
|
|
381
|
+
}).default({ kind: "solid" }),
|
|
382
|
+
grid: z.object({
|
|
383
|
+
visible: z.boolean().default(false),
|
|
384
|
+
size: z.number().finite().min(4).default(24),
|
|
385
|
+
opacity: z.number().finite().min(0).max(1).default(.12)
|
|
386
|
+
}).default({
|
|
387
|
+
visible: false,
|
|
388
|
+
size: 24,
|
|
389
|
+
opacity: .12
|
|
390
|
+
}),
|
|
391
|
+
mood: z.enum([
|
|
392
|
+
"clean",
|
|
393
|
+
"playful",
|
|
394
|
+
"arcane",
|
|
395
|
+
"cyber",
|
|
396
|
+
"natural"
|
|
397
|
+
]).default("clean")
|
|
398
|
+
});
|
|
399
|
+
/** Optional visual chrome still carried by a few older shapes. */
|
|
400
|
+
const BoardItemStyleSchema = z.object({
|
|
401
|
+
variant: z.string().min(1).default("default"),
|
|
402
|
+
theme: z.string().min(1).optional(),
|
|
403
|
+
accentColor: z.string().optional(),
|
|
404
|
+
size: z.enum([
|
|
405
|
+
"sm",
|
|
406
|
+
"md",
|
|
407
|
+
"lg"
|
|
408
|
+
]).default("md"),
|
|
409
|
+
emphasis: z.enum([
|
|
410
|
+
"normal",
|
|
411
|
+
"rare",
|
|
412
|
+
"epic",
|
|
413
|
+
"legendary"
|
|
414
|
+
]).default("normal"),
|
|
415
|
+
effects: z.array(z.string().min(1)).default([])
|
|
416
|
+
});
|
|
417
|
+
const SpaceFileRefSchema = z.object({
|
|
418
|
+
kind: z.literal("space-file"),
|
|
419
|
+
path: z.string().min(1)
|
|
420
|
+
});
|
|
421
|
+
const BoardMediaSnapshotSchema = z.object({
|
|
422
|
+
title: z.string().optional(),
|
|
423
|
+
mimeType: z.string().optional(),
|
|
424
|
+
size: z.number().finite().nonnegative().optional(),
|
|
425
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
426
|
+
/** Intrinsic pixel size once known. */
|
|
427
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
428
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
429
|
+
});
|
|
430
|
+
const BoardItemBaseSchema = z.object({
|
|
431
|
+
id: z.string().min(1),
|
|
432
|
+
frame: BoardFrameSchema,
|
|
433
|
+
parentId: z.string().min(1).nullable().optional(),
|
|
434
|
+
/** When true the shape cannot be moved, resized, or deleted. */
|
|
435
|
+
locked: z.boolean().optional(),
|
|
436
|
+
style: BoardItemStyleSchema.optional(),
|
|
437
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
438
|
+
});
|
|
439
|
+
/** Freestanding text — no card chrome; its frame always follows the glyphs. */
|
|
440
|
+
const BoardTextItemSchema = BoardItemBaseSchema.extend({
|
|
441
|
+
type: z.literal("text"),
|
|
442
|
+
text: z.string().default(""),
|
|
443
|
+
color: z.string().min(1).default("neutral"),
|
|
444
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
445
|
+
});
|
|
446
|
+
const BoardGeoItemSchema = BoardItemBaseSchema.extend({
|
|
447
|
+
type: z.literal("geo"),
|
|
448
|
+
geo: z.string().min(1).default("rectangle"),
|
|
449
|
+
text: z.string().default(""),
|
|
450
|
+
color: z.string().min(1).default("brand"),
|
|
451
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
452
|
+
});
|
|
453
|
+
/** A raw freehand sample. Pressure defaults to 0.5 (mouse) when absent. */
|
|
454
|
+
const DrawPointSchema = z.object({
|
|
455
|
+
x: z.number().finite(),
|
|
456
|
+
y: z.number().finite(),
|
|
457
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
458
|
+
});
|
|
459
|
+
/** A world-space point. */
|
|
460
|
+
const BoardPointSchema = z.object({
|
|
461
|
+
x: z.number().finite(),
|
|
462
|
+
y: z.number().finite()
|
|
463
|
+
});
|
|
464
|
+
const BoardDrawItemSchema = BoardItemBaseSchema.extend({
|
|
465
|
+
type: z.literal("draw"),
|
|
466
|
+
points: z.array(DrawPointSchema).default([]),
|
|
467
|
+
color: z.string().min(1).default("brand"),
|
|
468
|
+
size: z.number().finite().positive().default(4)
|
|
469
|
+
});
|
|
470
|
+
/**
|
|
471
|
+
* A free arrow — a standalone annotation stroke between two world points.
|
|
472
|
+
*
|
|
473
|
+
* Arrows do not relate nodes. A relation between two nodes is a
|
|
474
|
+
* `BoardConnection`, which is stored separately and resolves its geometry from
|
|
475
|
+
* the live node frames. Keeping the two apart is what lets an arrow be a plain
|
|
476
|
+
* shape (its own frame, freely movable) while a connection stays purely
|
|
477
|
+
* semantic — neither has to pretend to be the other.
|
|
478
|
+
*/
|
|
479
|
+
const BoardArrowItemSchema = BoardItemBaseSchema.extend({
|
|
480
|
+
type: z.literal("arrow"),
|
|
481
|
+
start: BoardPointSchema,
|
|
482
|
+
end: BoardPointSchema,
|
|
483
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
484
|
+
color: z.string().min(1).default("brand"),
|
|
485
|
+
size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
|
|
486
|
+
arrowStart: z.boolean().default(false),
|
|
487
|
+
arrowEnd: z.boolean().default(true),
|
|
488
|
+
label: z.string().default("")
|
|
489
|
+
});
|
|
490
|
+
/** A frame container for organising shapes. */
|
|
491
|
+
const BoardFrameItemSchema = BoardItemBaseSchema.extend({
|
|
492
|
+
type: z.literal("frame"),
|
|
493
|
+
label: z.string().default("Frame"),
|
|
494
|
+
color: z.string().min(1).default("neutral")
|
|
495
|
+
});
|
|
496
|
+
/** Image node — space file only, natural aspect, no chrome. */
|
|
497
|
+
const BoardImageItemSchema = BoardItemBaseSchema.extend({
|
|
498
|
+
type: z.literal("image"),
|
|
499
|
+
ref: SpaceFileRefSchema,
|
|
500
|
+
snapshot: BoardMediaSnapshotSchema.optional(),
|
|
501
|
+
/** Optional normalized crop in source image space (0..1). */
|
|
502
|
+
crop: z.object({
|
|
503
|
+
x: z.number().finite().min(0).max(1).default(0),
|
|
504
|
+
y: z.number().finite().min(0).max(1).default(0),
|
|
505
|
+
w: z.number().finite().min(0).max(1).default(1),
|
|
506
|
+
h: z.number().finite().min(0).max(1).default(1)
|
|
507
|
+
}).optional()
|
|
508
|
+
});
|
|
509
|
+
/** Video node — space file only. Playback state is local UI, never synced. */
|
|
510
|
+
const BoardVideoItemSchema = BoardItemBaseSchema.extend({
|
|
511
|
+
type: z.literal("video"),
|
|
512
|
+
ref: SpaceFileRefSchema,
|
|
513
|
+
snapshot: BoardMediaSnapshotSchema.optional()
|
|
514
|
+
});
|
|
515
|
+
/** Audio node — space file only. Playback state is local UI, never synced. */
|
|
516
|
+
const BoardAudioItemSchema = BoardItemBaseSchema.extend({
|
|
517
|
+
type: z.literal("audio"),
|
|
518
|
+
ref: SpaceFileRefSchema,
|
|
519
|
+
snapshot: BoardMediaSnapshotSchema.extend({ durationMs: z.number().finite().nonnegative().optional() }).optional()
|
|
520
|
+
});
|
|
521
|
+
/**
|
|
522
|
+
* Cached display facts for a file card.
|
|
523
|
+
*
|
|
524
|
+
* Purely derived from the referenced file and versioned by `mtimeMs`, so it is a
|
|
525
|
+
* cache and never a second source of truth: the workspace file remains
|
|
526
|
+
* authoritative, and a stale snapshot is detectable rather than silently wrong.
|
|
527
|
+
* It is stored so that opening a board renders complete cards immediately, with
|
|
528
|
+
* no per-node file read on the first paint.
|
|
529
|
+
*/
|
|
530
|
+
const BoardFileSnapshotSchema = z.object({
|
|
531
|
+
title: z.string().optional(),
|
|
532
|
+
mimeType: z.string().optional(),
|
|
533
|
+
size: z.number().finite().nonnegative().optional(),
|
|
534
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
535
|
+
/** Cleaned leading prose. Capped when written (see FILE_EXCERPT_MAX_CHARS). */
|
|
536
|
+
excerpt: z.string().optional(),
|
|
537
|
+
/** Cover image inside the space, already resolved to a workspace path. */
|
|
538
|
+
coverPath: z.string().optional(),
|
|
539
|
+
/** Cover image at an absolute https URL, as declared by the file itself. */
|
|
540
|
+
coverUrl: z.string().optional()
|
|
541
|
+
});
|
|
542
|
+
/**
|
|
543
|
+
* File node — a thumbnail entry point to any workspace file.
|
|
544
|
+
*
|
|
545
|
+
* This is the fallback for every dropped file that is not natively an image or
|
|
546
|
+
* video, including binaries and unknown extensions: a board should never refuse
|
|
547
|
+
* a file, only present it with less detail. Presentation tiers are derived from
|
|
548
|
+
* the snapshot (see filePreviewKind), not stored, so there is no display state
|
|
549
|
+
* to drift from the facts.
|
|
550
|
+
*/
|
|
551
|
+
const BoardFileItemSchema = BoardItemBaseSchema.extend({
|
|
552
|
+
type: z.literal("file"),
|
|
553
|
+
ref: SpaceFileRefSchema,
|
|
554
|
+
snapshot: BoardFileSnapshotSchema.optional()
|
|
555
|
+
});
|
|
556
|
+
const BoardTaskMediaArtifactFields = {
|
|
557
|
+
id: z.string().min(1).max(240),
|
|
558
|
+
title: z.string().max(240).optional(),
|
|
559
|
+
url: BoardRemoteUrlSchema,
|
|
560
|
+
mimeType: z.string().max(160).optional()
|
|
561
|
+
};
|
|
562
|
+
const BoardTaskArtifactSchema = z.discriminatedUnion("type", [
|
|
563
|
+
z.object({
|
|
564
|
+
...BoardTaskMediaArtifactFields,
|
|
565
|
+
type: z.literal("image"),
|
|
566
|
+
naturalWidth: z.number().positive().optional(),
|
|
567
|
+
naturalHeight: z.number().positive().optional()
|
|
568
|
+
}).strict(),
|
|
569
|
+
z.object({
|
|
570
|
+
...BoardTaskMediaArtifactFields,
|
|
571
|
+
type: z.literal("video"),
|
|
572
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
573
|
+
durationMs: z.number().int().positive().optional(),
|
|
574
|
+
naturalWidth: z.number().positive().optional(),
|
|
575
|
+
naturalHeight: z.number().positive().optional()
|
|
576
|
+
}).strict(),
|
|
577
|
+
z.object({
|
|
578
|
+
...BoardTaskMediaArtifactFields,
|
|
579
|
+
type: z.literal("audio"),
|
|
580
|
+
previewUrl: BoardRemoteUrlSchema.optional(),
|
|
581
|
+
durationMs: z.number().int().positive().optional()
|
|
582
|
+
}).strict(),
|
|
583
|
+
z.object({
|
|
584
|
+
id: z.string().min(1).max(240),
|
|
585
|
+
type: z.literal("text"),
|
|
586
|
+
title: z.string().max(240).optional(),
|
|
587
|
+
textExcerpt: z.string().min(1).max(480)
|
|
588
|
+
}).strict()
|
|
589
|
+
]);
|
|
590
|
+
/**
|
|
591
|
+
* Cached task facts used for an immediate first paint. The task run remains the
|
|
592
|
+
* source of truth and live clients refresh this projection by `taskRunId`.
|
|
593
|
+
*/
|
|
594
|
+
const BoardTaskSnapshotSchema = z.object({
|
|
595
|
+
taskType: z.string().min(1).max(120),
|
|
596
|
+
status: z.enum([
|
|
597
|
+
"pending",
|
|
598
|
+
"running",
|
|
599
|
+
"completed",
|
|
600
|
+
"failed"
|
|
601
|
+
]),
|
|
602
|
+
title: z.string().min(1).max(240),
|
|
603
|
+
model: z.string().max(160).optional(),
|
|
604
|
+
promptExcerpt: z.string().max(480).optional(),
|
|
605
|
+
artifactCount: z.number().int().nonnegative(),
|
|
606
|
+
artifacts: z.array(BoardTaskArtifactSchema).max(6).default([]),
|
|
607
|
+
updatedAt: z.string().optional()
|
|
608
|
+
}).strict();
|
|
609
|
+
/** A stable reference to a task run with a small, replaceable display cache. */
|
|
610
|
+
const BoardTaskItemSchema = BoardItemBaseSchema.extend({
|
|
611
|
+
type: z.literal("task"),
|
|
612
|
+
taskRunId: z.string().min(1),
|
|
613
|
+
snapshot: BoardTaskSnapshotSchema
|
|
614
|
+
});
|
|
615
|
+
/**
|
|
616
|
+
* A forward-compatible carrier for shape types this client does not recognise.
|
|
617
|
+
* Its discriminant is the literal `"unknown"` so the item union still narrows
|
|
618
|
+
* cleanly on `type`. The real type string and every original field are preserved
|
|
619
|
+
* verbatim in `raw`.
|
|
620
|
+
*/
|
|
621
|
+
const UNKNOWN_BOARD_ITEM_TYPE = "unknown";
|
|
622
|
+
const BoardItemSchema = z.any().transform((raw) => parseBoardItemLoose(raw));
|
|
623
|
+
/**
|
|
624
|
+
* Parse a single item leniently: known types are validated and normalised;
|
|
625
|
+
* anything else becomes a lossless unknown item. Never throws on shape data —
|
|
626
|
+
* a malformed known item degrades to unknown rather than failing the document.
|
|
627
|
+
*/
|
|
628
|
+
function parseBoardItemLoose(raw) {
|
|
629
|
+
if (!raw || typeof raw !== "object") return makeUnknownItem(raw);
|
|
630
|
+
switch (raw.type) {
|
|
631
|
+
case "image": {
|
|
632
|
+
const parsed = BoardImageItemSchema.safeParse(raw);
|
|
633
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
634
|
+
}
|
|
635
|
+
case "video": {
|
|
636
|
+
const parsed = BoardVideoItemSchema.safeParse(raw);
|
|
637
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
638
|
+
}
|
|
639
|
+
case "audio": {
|
|
640
|
+
const parsed = BoardAudioItemSchema.safeParse(raw);
|
|
641
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
642
|
+
}
|
|
643
|
+
case "file": {
|
|
644
|
+
const parsed = BoardFileItemSchema.safeParse(raw);
|
|
645
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
646
|
+
}
|
|
647
|
+
case "task": {
|
|
648
|
+
const parsed = BoardTaskItemSchema.safeParse(raw);
|
|
649
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
650
|
+
}
|
|
651
|
+
case "text": {
|
|
652
|
+
const parsed = BoardTextItemSchema.safeParse(raw);
|
|
653
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
654
|
+
}
|
|
655
|
+
case "geo": {
|
|
656
|
+
const parsed = BoardGeoItemSchema.safeParse(raw);
|
|
657
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
658
|
+
}
|
|
659
|
+
case "draw": {
|
|
660
|
+
const parsed = BoardDrawItemSchema.safeParse(raw);
|
|
661
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
662
|
+
}
|
|
663
|
+
case "arrow": {
|
|
664
|
+
const parsed = BoardArrowItemSchema.safeParse(raw);
|
|
665
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
666
|
+
}
|
|
667
|
+
case "frame": {
|
|
668
|
+
const parsed = BoardFrameItemSchema.safeParse(raw);
|
|
669
|
+
return parsed.success ? parsed.data : makeUnknownItem(raw);
|
|
670
|
+
}
|
|
671
|
+
default: return makeUnknownItem(raw);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function makeUnknownItem(raw) {
|
|
675
|
+
const record = raw && typeof raw === "object" ? raw : {};
|
|
676
|
+
const frameParsed = BoardFrameSchema.safeParse(record.frame);
|
|
677
|
+
const styleParsed = BoardItemStyleSchema.safeParse(record.style);
|
|
678
|
+
return {
|
|
679
|
+
id: typeof record.id === "string" && record.id ? record.id : "unknown",
|
|
680
|
+
type: UNKNOWN_BOARD_ITEM_TYPE,
|
|
681
|
+
frame: frameParsed.success ? frameParsed.data : {
|
|
682
|
+
x: 0,
|
|
683
|
+
y: 0,
|
|
684
|
+
width: 120,
|
|
685
|
+
height: 80,
|
|
686
|
+
rotation: 0
|
|
687
|
+
},
|
|
688
|
+
...typeof record.parentId === "string" || record.parentId === null ? { parentId: record.parentId } : {},
|
|
689
|
+
...record.locked === true ? { locked: true } : {},
|
|
690
|
+
...styleParsed.success && record.style ? { style: styleParsed.data } : {},
|
|
691
|
+
...record.metadata && typeof record.metadata === "object" ? { metadata: record.metadata } : {},
|
|
692
|
+
raw: record
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
z.object({
|
|
696
|
+
kind: z.literal(BOARD_DOCUMENT_KIND),
|
|
697
|
+
version: z.literal(1),
|
|
698
|
+
appearance: BoardAppearanceSchema.default({
|
|
699
|
+
theme: "clean",
|
|
700
|
+
background: { kind: "solid" },
|
|
701
|
+
grid: {
|
|
702
|
+
visible: false,
|
|
703
|
+
size: 24,
|
|
704
|
+
opacity: .12
|
|
705
|
+
},
|
|
706
|
+
mood: "clean"
|
|
707
|
+
}),
|
|
708
|
+
viewport: BoardViewportSchema,
|
|
709
|
+
items: z.array(BoardItemSchema),
|
|
710
|
+
/**
|
|
711
|
+
* Node relations. Separate from `items` because a connection has no frame of
|
|
712
|
+
* its own — its geometry is derived from the nodes it joins, so it is a
|
|
713
|
+
* relation over the item set rather than a member of it.
|
|
714
|
+
*
|
|
715
|
+
* Connections referencing a missing node are dropped on parse: a relation to
|
|
716
|
+
* nothing is not a relation, and keeping one would let an invisible dangling
|
|
717
|
+
* edge accumulate silently. Callers that need to know write through the
|
|
718
|
+
* transaction API, which reports the reference error instead.
|
|
719
|
+
*/
|
|
720
|
+
connections: z.array(BoardConnectionSchema).default([])
|
|
721
|
+
});
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region ../protocol/dist/board-node.js
|
|
724
|
+
const BOARD_COLOR_IDS = [
|
|
725
|
+
"brand",
|
|
726
|
+
"neutral",
|
|
727
|
+
"black",
|
|
728
|
+
"white",
|
|
729
|
+
"blue",
|
|
730
|
+
"green",
|
|
731
|
+
"amber",
|
|
732
|
+
"violet",
|
|
733
|
+
"rose"
|
|
734
|
+
];
|
|
735
|
+
const BoardColorIdSchema = z.enum(BOARD_COLOR_IDS);
|
|
736
|
+
const BOARD_GEO_KINDS = [
|
|
737
|
+
"rectangle",
|
|
738
|
+
"rounded",
|
|
739
|
+
"ellipse",
|
|
740
|
+
"diamond",
|
|
741
|
+
"triangle"
|
|
742
|
+
];
|
|
743
|
+
const BoardGeoKindSchema = z.enum(BOARD_GEO_KINDS);
|
|
744
|
+
const BOARD_NATIVE_NODE_TYPES = [
|
|
745
|
+
"image",
|
|
746
|
+
"video",
|
|
747
|
+
"audio",
|
|
748
|
+
"file",
|
|
749
|
+
"task",
|
|
750
|
+
"text",
|
|
751
|
+
"geo",
|
|
752
|
+
"draw",
|
|
753
|
+
"arrow",
|
|
754
|
+
"frame"
|
|
755
|
+
];
|
|
756
|
+
const metadataSchema = z.record(z.string(), z.unknown());
|
|
757
|
+
const commonData = {
|
|
758
|
+
locked: z.boolean().optional(),
|
|
759
|
+
metadata: metadataSchema.optional()
|
|
760
|
+
};
|
|
761
|
+
const pointSchema = z.object({
|
|
762
|
+
x: z.number().finite(),
|
|
763
|
+
y: z.number().finite(),
|
|
764
|
+
p: z.number().finite().min(0).max(1).default(.5)
|
|
765
|
+
}).strict();
|
|
766
|
+
const worldPointSchema = z.object({
|
|
767
|
+
x: z.number().finite(),
|
|
768
|
+
y: z.number().finite()
|
|
769
|
+
}).strict();
|
|
770
|
+
const strokeSizeSchema = z.number().finite().min(1).max(64);
|
|
771
|
+
const mediaViewSchema = z.object({
|
|
772
|
+
title: z.string().optional(),
|
|
773
|
+
mimeType: z.string().optional(),
|
|
774
|
+
size: z.number().finite().nonnegative().optional(),
|
|
775
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
776
|
+
naturalWidth: z.number().finite().positive().optional(),
|
|
777
|
+
naturalHeight: z.number().finite().positive().optional()
|
|
778
|
+
}).strict();
|
|
779
|
+
const audioViewSchema = mediaViewSchema.extend({ durationMs: z.number().finite().nonnegative().optional() });
|
|
780
|
+
const dataSchemas = {
|
|
781
|
+
text: z.object({
|
|
782
|
+
...commonData,
|
|
783
|
+
text: z.string().default(""),
|
|
784
|
+
color: BoardColorIdSchema.default("neutral"),
|
|
785
|
+
fontSize: z.number().finite().min(2).max(512).default(24)
|
|
786
|
+
}).strict(),
|
|
787
|
+
geo: z.object({
|
|
788
|
+
...commonData,
|
|
789
|
+
geo: BoardGeoKindSchema.default("rectangle"),
|
|
790
|
+
text: z.string().default(""),
|
|
791
|
+
color: BoardColorIdSchema.default("brand"),
|
|
792
|
+
fillOpacity: z.number().finite().min(0).max(1).default(0)
|
|
793
|
+
}).strict(),
|
|
794
|
+
draw: z.object({
|
|
795
|
+
...commonData,
|
|
796
|
+
points: z.array(pointSchema).min(1),
|
|
797
|
+
color: BoardColorIdSchema.default("brand"),
|
|
798
|
+
size: strokeSizeSchema.default(4)
|
|
799
|
+
}).strict(),
|
|
800
|
+
arrow: z.object({
|
|
801
|
+
...commonData,
|
|
802
|
+
start: worldPointSchema,
|
|
803
|
+
end: worldPointSchema,
|
|
804
|
+
bend: z.number().finite().min(-.85).max(.85).default(0),
|
|
805
|
+
color: BoardColorIdSchema.default("brand"),
|
|
806
|
+
size: strokeSizeSchema.default(BOARD_ARROW_STROKE_SIZE),
|
|
807
|
+
arrowStart: z.boolean().default(false),
|
|
808
|
+
arrowEnd: z.boolean().default(true),
|
|
809
|
+
label: z.string().default("")
|
|
810
|
+
}).strict(),
|
|
811
|
+
frame: z.object({
|
|
812
|
+
...commonData,
|
|
813
|
+
label: z.string().default("Frame"),
|
|
814
|
+
color: BoardColorIdSchema.default("neutral")
|
|
815
|
+
}).strict(),
|
|
816
|
+
image: z.object({
|
|
817
|
+
...commonData,
|
|
818
|
+
crop: z.object({
|
|
819
|
+
x: z.number().finite().min(0).max(1),
|
|
820
|
+
y: z.number().finite().min(0).max(1),
|
|
821
|
+
w: z.number().finite().min(0).max(1),
|
|
822
|
+
h: z.number().finite().min(0).max(1)
|
|
823
|
+
}).strict().optional()
|
|
824
|
+
}).strict(),
|
|
825
|
+
video: z.object(commonData).strict(),
|
|
826
|
+
audio: z.object(commonData).strict(),
|
|
827
|
+
file: z.object(commonData).strict(),
|
|
828
|
+
task: z.object({
|
|
829
|
+
...commonData,
|
|
830
|
+
taskRunId: z.string().min(1)
|
|
831
|
+
}).strict()
|
|
832
|
+
};
|
|
833
|
+
const fileViewSchema = z.object({
|
|
834
|
+
title: z.string().optional(),
|
|
835
|
+
mimeType: z.string().optional(),
|
|
836
|
+
size: z.number().finite().nonnegative().optional(),
|
|
837
|
+
mtimeMs: z.number().finite().nonnegative().optional(),
|
|
838
|
+
excerpt: z.string().optional(),
|
|
839
|
+
coverPath: z.string().optional(),
|
|
840
|
+
coverUrl: z.string().url().optional()
|
|
841
|
+
}).strict();
|
|
842
|
+
const taskViewSchema = BoardTaskSnapshotSchema;
|
|
843
|
+
const emptyViewSchema = z.object({}).strict();
|
|
844
|
+
const viewSchemas = {
|
|
845
|
+
image: mediaViewSchema,
|
|
846
|
+
video: mediaViewSchema,
|
|
847
|
+
audio: audioViewSchema,
|
|
848
|
+
file: fileViewSchema,
|
|
849
|
+
task: taskViewSchema,
|
|
850
|
+
text: emptyViewSchema,
|
|
851
|
+
geo: emptyViewSchema,
|
|
852
|
+
draw: emptyViewSchema,
|
|
853
|
+
arrow: emptyViewSchema,
|
|
854
|
+
frame: emptyViewSchema
|
|
855
|
+
};
|
|
856
|
+
const nodeEnvelopeSchema = z.object({
|
|
857
|
+
nodeId: z.string().min(1).max(160),
|
|
858
|
+
type: z.string().min(1).max(40),
|
|
859
|
+
parentId: z.string().min(1).max(160).nullable(),
|
|
860
|
+
orderKey: z.string().max(4096).nullable(),
|
|
861
|
+
x: z.number().finite(),
|
|
862
|
+
y: z.number().finite(),
|
|
863
|
+
width: z.number().finite().positive(),
|
|
864
|
+
height: z.number().finite().positive(),
|
|
865
|
+
rotation: z.number().finite(),
|
|
866
|
+
refKind: z.string().max(40).nullable(),
|
|
867
|
+
refPath: z.string().max(4096).nullable(),
|
|
868
|
+
refUrl: z.string().max(4096).nullable(),
|
|
869
|
+
view: z.record(z.string(), z.unknown()),
|
|
870
|
+
style: z.record(z.string(), z.unknown()),
|
|
871
|
+
data: z.record(z.string(), z.unknown())
|
|
872
|
+
}).strict();
|
|
873
|
+
function jsonSchema(schema) {
|
|
874
|
+
return z.toJSONSchema(schema);
|
|
875
|
+
}
|
|
876
|
+
jsonSchema(nodeEnvelopeSchema), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(dataSchemas[type])])), Object.fromEntries(BOARD_NATIVE_NODE_TYPES.map((type) => [type, jsonSchema(viewSchemas[type])]));
|
|
877
|
+
//#endregion
|
|
7
878
|
//#region ../protocol/dist/generation/catalog.js
|
|
8
879
|
function isGenerationModelHidden(model) {
|
|
9
880
|
return model.hidden === true;
|
|
@@ -21,14 +892,14 @@ var GenerationPolicyError = class extends Error {
|
|
|
21
892
|
this.name = "GenerationPolicyError";
|
|
22
893
|
}
|
|
23
894
|
};
|
|
24
|
-
function isRecord(value) {
|
|
895
|
+
function isRecord$1(value) {
|
|
25
896
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
26
897
|
}
|
|
27
898
|
function isPrimitiveEnumValue(value) {
|
|
28
899
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
29
900
|
}
|
|
30
901
|
function normalizeConstraint(value) {
|
|
31
|
-
if (!isRecord(value) || typeof value.kind !== "string") return null;
|
|
902
|
+
if (!isRecord$1(value) || typeof value.kind !== "string") return null;
|
|
32
903
|
if (value.kind === "enum") {
|
|
33
904
|
if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
|
|
34
905
|
return {
|
|
@@ -58,7 +929,7 @@ function normalizeConstraint(value) {
|
|
|
58
929
|
return null;
|
|
59
930
|
}
|
|
60
931
|
function normalizeGenerationPolicy(value) {
|
|
61
|
-
if (!isRecord(value) || value.version !== 1) return null;
|
|
932
|
+
if (!isRecord$1(value) || value.version !== 1) return null;
|
|
62
933
|
if (value.mode === "auto") return {
|
|
63
934
|
version: 1,
|
|
64
935
|
mode: "auto"
|
|
@@ -66,10 +937,10 @@ function normalizeGenerationPolicy(value) {
|
|
|
66
937
|
if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
|
|
67
938
|
const models = [];
|
|
68
939
|
for (const item of value.models) {
|
|
69
|
-
if (!isRecord(item) || typeof item.model !== "string" || !item.model.trim()) return null;
|
|
940
|
+
if (!isRecord$1(item) || typeof item.model !== "string" || !item.model.trim()) return null;
|
|
70
941
|
const modelPolicy = { model: item.model.trim() };
|
|
71
942
|
if (item.parameters !== void 0) {
|
|
72
|
-
if (!isRecord(item.parameters)) return null;
|
|
943
|
+
if (!isRecord$1(item.parameters)) return null;
|
|
73
944
|
const parameters = {};
|
|
74
945
|
for (const [key, rawConstraint] of Object.entries(item.parameters)) {
|
|
75
946
|
if (!key.trim()) return null;
|
|
@@ -244,6 +1115,172 @@ function filterGenerationDeclarationsByPolicy(declarations, policy) {
|
|
|
244
1115
|
});
|
|
245
1116
|
}
|
|
246
1117
|
//#endregion
|
|
1118
|
+
//#region ../protocol/dist/public-identifiers.js
|
|
1119
|
+
const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
|
|
1120
|
+
const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
|
|
1121
|
+
/**
|
|
1122
|
+
* Platform-owned path segments that must not be newly assigned to public
|
|
1123
|
+
* identities. Existing stored values remain readable through parse helpers.
|
|
1124
|
+
*/
|
|
1125
|
+
const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
|
|
1126
|
+
"admin",
|
|
1127
|
+
"api",
|
|
1128
|
+
"assets",
|
|
1129
|
+
"auth",
|
|
1130
|
+
"callback",
|
|
1131
|
+
"changelog",
|
|
1132
|
+
"docs",
|
|
1133
|
+
"explore",
|
|
1134
|
+
"invite",
|
|
1135
|
+
"landing",
|
|
1136
|
+
"login",
|
|
1137
|
+
"logout",
|
|
1138
|
+
"new",
|
|
1139
|
+
"org",
|
|
1140
|
+
"pricing",
|
|
1141
|
+
"pwa",
|
|
1142
|
+
"referrals",
|
|
1143
|
+
"sessions",
|
|
1144
|
+
"settings",
|
|
1145
|
+
"spaces",
|
|
1146
|
+
"static",
|
|
1147
|
+
"teams",
|
|
1148
|
+
"trending",
|
|
1149
|
+
"u",
|
|
1150
|
+
"user",
|
|
1151
|
+
"users",
|
|
1152
|
+
"work-auth"
|
|
1153
|
+
]);
|
|
1154
|
+
new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
|
|
1155
|
+
[...RESERVED_PLATFORM_PATH_SEGMENTS];
|
|
1156
|
+
function parseUsername(value) {
|
|
1157
|
+
if (value === null || value === void 0) return null;
|
|
1158
|
+
const normalized = value.trim().toLowerCase();
|
|
1159
|
+
return USERNAME_PATTERN.test(normalized) ? normalized : null;
|
|
1160
|
+
}
|
|
1161
|
+
function parseSpaceSlug(value) {
|
|
1162
|
+
if (value === null || value === void 0) return null;
|
|
1163
|
+
const normalized = value.trim();
|
|
1164
|
+
return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
|
|
1165
|
+
}
|
|
1166
|
+
//#endregion
|
|
1167
|
+
//#region ../protocol/dist/work-surface.js
|
|
1168
|
+
const WORK_SURFACE_PROTOCOL = "cohub.surface";
|
|
1169
|
+
const WORK_SURFACE_READY_TIMEOUT_MS = 1e4;
|
|
1170
|
+
const WORK_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
|
|
1171
|
+
const WORK_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
|
|
1172
|
+
const WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
|
|
1173
|
+
const WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
|
|
1174
|
+
const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1175
|
+
const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.surface" && value.version === 1;
|
|
1176
|
+
const parseWorkSurfaceReady = (value) => {
|
|
1177
|
+
if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
|
|
1178
|
+
const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
|
|
1179
|
+
return {
|
|
1180
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1181
|
+
version: 1,
|
|
1182
|
+
type: "ready",
|
|
1183
|
+
methods
|
|
1184
|
+
};
|
|
1185
|
+
};
|
|
1186
|
+
const parseWorkSurfaceResponse = (value) => {
|
|
1187
|
+
if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
|
|
1188
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1189
|
+
const error = isRecord(value.error) ? {
|
|
1190
|
+
code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
|
|
1191
|
+
message: typeof value.error.message === "string" ? value.error.message : "Work surface call failed"
|
|
1192
|
+
} : void 0;
|
|
1193
|
+
return {
|
|
1194
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1195
|
+
version: 1,
|
|
1196
|
+
type: "response",
|
|
1197
|
+
requestId: value.requestId,
|
|
1198
|
+
ok: value.ok === true,
|
|
1199
|
+
...error ? { error } : {}
|
|
1200
|
+
};
|
|
1201
|
+
};
|
|
1202
|
+
const parseComposerChipKey = (value) => {
|
|
1203
|
+
if (typeof value !== "string") return null;
|
|
1204
|
+
const key = value.trim();
|
|
1205
|
+
if (!key || key.length > 80) return null;
|
|
1206
|
+
return key;
|
|
1207
|
+
};
|
|
1208
|
+
const parseWorkComposerChipSet = (value) => {
|
|
1209
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord(value.chip)) return null;
|
|
1210
|
+
const key = parseComposerChipKey(value.chip.key);
|
|
1211
|
+
if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
|
|
1212
|
+
const label = value.chip.label.trim();
|
|
1213
|
+
if (!label || label.length > 120) return null;
|
|
1214
|
+
if (!value.chip.content.trim()) return null;
|
|
1215
|
+
if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
|
|
1216
|
+
return {
|
|
1217
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1218
|
+
version: 1,
|
|
1219
|
+
type: "composer.chip.set",
|
|
1220
|
+
chip: {
|
|
1221
|
+
key,
|
|
1222
|
+
label,
|
|
1223
|
+
content: value.chip.content
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
};
|
|
1227
|
+
const parseWorkComposerChipClear = (value) => {
|
|
1228
|
+
if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
|
|
1229
|
+
const key = parseComposerChipKey(value.key);
|
|
1230
|
+
return key ? {
|
|
1231
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1232
|
+
version: 1,
|
|
1233
|
+
type: "composer.chip.clear",
|
|
1234
|
+
key
|
|
1235
|
+
} : null;
|
|
1236
|
+
};
|
|
1237
|
+
const parseWorkSurfaceRequest = (value) => {
|
|
1238
|
+
if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
|
|
1239
|
+
if (typeof value.requestId !== "string" || !value.requestId) return null;
|
|
1240
|
+
if (typeof value.method !== "string" || !value.method) return null;
|
|
1241
|
+
const commandId = parseUiCommandId(value.commandId);
|
|
1242
|
+
if (!commandId) return null;
|
|
1243
|
+
return {
|
|
1244
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1245
|
+
version: 1,
|
|
1246
|
+
type: "request",
|
|
1247
|
+
requestId: value.requestId,
|
|
1248
|
+
method: value.method,
|
|
1249
|
+
...value.input === void 0 ? {} : { input: value.input },
|
|
1250
|
+
commandId
|
|
1251
|
+
};
|
|
1252
|
+
};
|
|
1253
|
+
const buildWorkSurfaceReady = (methods) => ({
|
|
1254
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1255
|
+
version: 1,
|
|
1256
|
+
type: "ready",
|
|
1257
|
+
methods: [...methods]
|
|
1258
|
+
});
|
|
1259
|
+
const buildWorkSurfaceRequest = (input) => ({
|
|
1260
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1261
|
+
version: 1,
|
|
1262
|
+
type: "request",
|
|
1263
|
+
...input
|
|
1264
|
+
});
|
|
1265
|
+
const buildWorkSurfaceResponse = (input) => ({
|
|
1266
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1267
|
+
version: 1,
|
|
1268
|
+
type: "response",
|
|
1269
|
+
...input
|
|
1270
|
+
});
|
|
1271
|
+
const buildWorkComposerChipSet = (chip) => ({
|
|
1272
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1273
|
+
version: 1,
|
|
1274
|
+
type: "composer.chip.set",
|
|
1275
|
+
chip
|
|
1276
|
+
});
|
|
1277
|
+
const buildWorkComposerChipClear = (key) => ({
|
|
1278
|
+
protocol: WORK_SURFACE_PROTOCOL,
|
|
1279
|
+
version: 1,
|
|
1280
|
+
type: "composer.chip.clear",
|
|
1281
|
+
key
|
|
1282
|
+
});
|
|
1283
|
+
//#endregion
|
|
247
1284
|
//#region src/apis/billing.ts
|
|
248
1285
|
var BillingApi = class {
|
|
249
1286
|
transport;
|
|
@@ -316,193 +1353,6 @@ var BillingApi = class {
|
|
|
316
1353
|
}
|
|
317
1354
|
};
|
|
318
1355
|
//#endregion
|
|
319
|
-
//#region ../protocol/dist/realtime/schema.js
|
|
320
|
-
const contentBlockMetaSchema = z.record(z.string(), z.unknown());
|
|
321
|
-
const realtimeRoomSchema = z.string().regex(/^(space|user|board|room):[^:]+$/);
|
|
322
|
-
const contentBlockSchema = z.discriminatedUnion("type", [
|
|
323
|
-
z.object({
|
|
324
|
-
type: z.literal("text"),
|
|
325
|
-
text: z.string(),
|
|
326
|
-
_meta: contentBlockMetaSchema.optional()
|
|
327
|
-
}),
|
|
328
|
-
z.object({
|
|
329
|
-
type: z.literal("thinking"),
|
|
330
|
-
thinking: z.string(),
|
|
331
|
-
signature: z.string().optional(),
|
|
332
|
-
_meta: contentBlockMetaSchema.optional()
|
|
333
|
-
}),
|
|
334
|
-
z.object({
|
|
335
|
-
type: z.literal("image"),
|
|
336
|
-
source: z.union([z.object({
|
|
337
|
-
type: z.literal("url"),
|
|
338
|
-
url: z.string().url()
|
|
339
|
-
}), z.object({
|
|
340
|
-
type: z.literal("base64"),
|
|
341
|
-
media_type: z.string(),
|
|
342
|
-
data: z.string()
|
|
343
|
-
})]),
|
|
344
|
-
_meta: contentBlockMetaSchema.optional()
|
|
345
|
-
}),
|
|
346
|
-
z.object({
|
|
347
|
-
type: z.literal("shell_command"),
|
|
348
|
-
command: z.string(),
|
|
349
|
-
rawText: z.string(),
|
|
350
|
-
_meta: contentBlockMetaSchema.optional()
|
|
351
|
-
}),
|
|
352
|
-
z.object({
|
|
353
|
-
type: z.literal("tool_use"),
|
|
354
|
-
id: z.string(),
|
|
355
|
-
name: z.string(),
|
|
356
|
-
input: z.record(z.string(), z.unknown()),
|
|
357
|
-
_meta: contentBlockMetaSchema.optional()
|
|
358
|
-
}),
|
|
359
|
-
z.object({
|
|
360
|
-
type: z.literal("tool_result"),
|
|
361
|
-
tool_use_id: z.string(),
|
|
362
|
-
content: z.union([z.string(), z.array(z.unknown())]),
|
|
363
|
-
is_error: z.boolean().optional(),
|
|
364
|
-
_meta: contentBlockMetaSchema.optional()
|
|
365
|
-
}),
|
|
366
|
-
z.object({
|
|
367
|
-
type: z.literal("system_note"),
|
|
368
|
-
note_type: z.enum([
|
|
369
|
-
"session_created",
|
|
370
|
-
"forked",
|
|
371
|
-
"compacted",
|
|
372
|
-
"info"
|
|
373
|
-
]),
|
|
374
|
-
text: z.string(),
|
|
375
|
-
_meta: contentBlockMetaSchema.optional()
|
|
376
|
-
})
|
|
377
|
-
]);
|
|
378
|
-
z.discriminatedUnion("type", [
|
|
379
|
-
z.object({
|
|
380
|
-
type: z.literal("auth"),
|
|
381
|
-
requestId: z.string().optional(),
|
|
382
|
-
payload: z.object({
|
|
383
|
-
token: z.string().min(1),
|
|
384
|
-
capabilities: z.array(z.string().min(1)).optional()
|
|
385
|
-
})
|
|
386
|
-
}),
|
|
387
|
-
z.object({
|
|
388
|
-
type: z.literal("subscribe"),
|
|
389
|
-
requestId: z.string().optional(),
|
|
390
|
-
payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
|
|
391
|
-
}),
|
|
392
|
-
z.object({
|
|
393
|
-
type: z.literal("unsubscribe"),
|
|
394
|
-
requestId: z.string().optional(),
|
|
395
|
-
payload: z.object({ rooms: z.array(realtimeRoomSchema).min(1) })
|
|
396
|
-
}),
|
|
397
|
-
z.object({
|
|
398
|
-
type: z.literal("session.message.create"),
|
|
399
|
-
requestId: z.string().optional(),
|
|
400
|
-
payload: z.object({
|
|
401
|
-
spaceId: z.string().uuid(),
|
|
402
|
-
sessionId: z.string().uuid(),
|
|
403
|
-
clientMessageId: z.string().optional(),
|
|
404
|
-
content: z.array(contentBlockSchema).min(1),
|
|
405
|
-
model: z.string().optional(),
|
|
406
|
-
provider: z.string().optional(),
|
|
407
|
-
thinkingLevel: z.enum([
|
|
408
|
-
"off",
|
|
409
|
-
"minimal",
|
|
410
|
-
"low",
|
|
411
|
-
"medium",
|
|
412
|
-
"high",
|
|
413
|
-
"xhigh",
|
|
414
|
-
"max"
|
|
415
|
-
]).optional()
|
|
416
|
-
})
|
|
417
|
-
}),
|
|
418
|
-
z.object({
|
|
419
|
-
type: z.literal("presence.update"),
|
|
420
|
-
requestId: z.string().optional(),
|
|
421
|
-
payload: z.object({
|
|
422
|
-
spaceId: z.string().uuid(),
|
|
423
|
-
meta: z.record(z.string(), z.unknown()).nullable().optional()
|
|
424
|
-
})
|
|
425
|
-
}),
|
|
426
|
-
z.object({
|
|
427
|
-
type: z.literal("board.awareness.update"),
|
|
428
|
-
requestId: z.string().optional(),
|
|
429
|
-
payload: BoardAwarenessClientPayloadSchema
|
|
430
|
-
}),
|
|
431
|
-
z.object({
|
|
432
|
-
type: z.literal("realtime.room.join"),
|
|
433
|
-
requestId: z.string().optional(),
|
|
434
|
-
payload: z.object({
|
|
435
|
-
roomId: z.string().uuid(),
|
|
436
|
-
ticket: z.string().min(1)
|
|
437
|
-
})
|
|
438
|
-
}),
|
|
439
|
-
z.object({
|
|
440
|
-
type: z.literal("realtime.room.publish"),
|
|
441
|
-
requestId: z.string().optional(),
|
|
442
|
-
payload: z.object({
|
|
443
|
-
roomId: z.string().uuid(),
|
|
444
|
-
event: z.string().regex(REALTIME_ROOM_EVENT_NAME_PATTERN),
|
|
445
|
-
data: z.unknown(),
|
|
446
|
-
clientEventId: z.string().max(128).optional()
|
|
447
|
-
})
|
|
448
|
-
}),
|
|
449
|
-
z.object({
|
|
450
|
-
type: z.literal("realtime.room.leave"),
|
|
451
|
-
requestId: z.string().optional(),
|
|
452
|
-
payload: z.object({ roomId: z.string().uuid() })
|
|
453
|
-
}),
|
|
454
|
-
z.object({
|
|
455
|
-
type: z.literal("realtime.room.presence.update"),
|
|
456
|
-
requestId: z.string().optional(),
|
|
457
|
-
payload: z.object({
|
|
458
|
-
roomId: z.string().uuid(),
|
|
459
|
-
presence: z.record(z.string(), z.unknown()).nullable()
|
|
460
|
-
})
|
|
461
|
-
}),
|
|
462
|
-
z.object({
|
|
463
|
-
type: z.literal("ping"),
|
|
464
|
-
requestId: z.string().optional(),
|
|
465
|
-
payload: z.record(z.string(), z.unknown()).optional()
|
|
466
|
-
}),
|
|
467
|
-
z.object({
|
|
468
|
-
type: z.literal("ack"),
|
|
469
|
-
requestId: z.string().optional(),
|
|
470
|
-
payload: z.object({ eventId: z.string().optional() }).optional()
|
|
471
|
-
})
|
|
472
|
-
]);
|
|
473
|
-
z.object({
|
|
474
|
-
id: z.string(),
|
|
475
|
-
timestamp: z.number(),
|
|
476
|
-
domain: z.enum(REALTIME_DOMAINS),
|
|
477
|
-
type: z.string(),
|
|
478
|
-
requestId: z.string().nullable().optional(),
|
|
479
|
-
spaceId: z.string().nullable().optional(),
|
|
480
|
-
sessionId: z.string().nullable().optional(),
|
|
481
|
-
rooms: z.array(realtimeRoomSchema).optional(),
|
|
482
|
-
payload: z.record(z.string(), z.unknown())
|
|
483
|
-
});
|
|
484
|
-
z.discriminatedUnion("t", [z.object({
|
|
485
|
-
t: z.literal("d"),
|
|
486
|
-
sid: z.string().min(1),
|
|
487
|
-
s: z.number().int().nonnegative(),
|
|
488
|
-
b: z.number().int().nonnegative(),
|
|
489
|
-
v: z.unknown()
|
|
490
|
-
}), z.object({
|
|
491
|
-
t: z.literal("p"),
|
|
492
|
-
sid: z.string().min(1),
|
|
493
|
-
s: z.number().int().nonnegative(),
|
|
494
|
-
b: z.number().int().nonnegative(),
|
|
495
|
-
o: z.enum([
|
|
496
|
-
"append",
|
|
497
|
-
"replace",
|
|
498
|
-
"add",
|
|
499
|
-
"merge",
|
|
500
|
-
"remove"
|
|
501
|
-
]),
|
|
502
|
-
p: z.string().min(1),
|
|
503
|
-
v: z.unknown().optional()
|
|
504
|
-
})]);
|
|
505
|
-
//#endregion
|
|
506
1356
|
//#region src/apis/work-realtime.ts
|
|
507
1357
|
const randomId = () => globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
508
1358
|
const textEncoder = new TextEncoder();
|
|
@@ -1934,6 +2784,7 @@ function createWorkBridgeCore(config) {
|
|
|
1934
2784
|
try {
|
|
1935
2785
|
if (data.type === "cohub.work.context") {
|
|
1936
2786
|
const workScopes = clonePermissionScopes(work.workScopes);
|
|
2787
|
+
const viewerUuid = await getViewerUuid();
|
|
1937
2788
|
reply(data.requestId, {
|
|
1938
2789
|
type: "cohub.work.context.result",
|
|
1939
2790
|
context: {
|
|
@@ -1943,6 +2794,8 @@ function createWorkBridgeCore(config) {
|
|
|
1943
2794
|
url: typeof location !== "undefined" ? location.href : ""
|
|
1944
2795
|
},
|
|
1945
2796
|
space: { id: work.spaceId },
|
|
2797
|
+
viewer: viewerUuid ? { userUuid: viewerUuid } : null,
|
|
2798
|
+
...config.invocation ? { invocation: { ...config.invocation } } : {},
|
|
1946
2799
|
permissions: {
|
|
1947
2800
|
scopes: workScopes,
|
|
1948
2801
|
workScopes,
|
|
@@ -2135,281 +2988,146 @@ function createWorkBridgeCore(config) {
|
|
|
2135
2988
|
}
|
|
2136
2989
|
//#endregion
|
|
2137
2990
|
//#region src/board/animation.ts
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
vertices: 0,
|
|
2141
|
-
dynamicVertices: 0,
|
|
2142
|
-
drawCalls: 0,
|
|
2143
|
-
filterPasses: 0,
|
|
2144
|
-
renderTexturePixels: 0,
|
|
2145
|
-
textureBytes: 0,
|
|
2146
|
-
bufferBytes: 0,
|
|
2147
|
-
simulationSteps: 0
|
|
2148
|
-
};
|
|
2149
|
-
const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
|
|
2150
|
-
const BUILTIN_CAPABILITIES = BOARD_BUILTIN_CAPABILITIES;
|
|
2151
|
-
function numericParam(params, key, fallback) {
|
|
2152
|
-
const value = params[key];
|
|
2153
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2991
|
+
function track(input) {
|
|
2992
|
+
return BoardTrackSchema.parse(input);
|
|
2154
2993
|
}
|
|
2155
|
-
function
|
|
2156
|
-
return {
|
|
2157
|
-
...
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
path: "params.bounds",
|
|
2174
|
-
coordinateSpace: "world"
|
|
2175
|
-
});
|
|
2176
|
-
else {
|
|
2177
|
-
const value = bounds;
|
|
2178
|
-
if (![
|
|
2179
|
-
value.x,
|
|
2180
|
-
value.y,
|
|
2181
|
-
value.width,
|
|
2182
|
-
value.height
|
|
2183
|
-
].every((item) => typeof item === "number" && Number.isFinite(item)) || value.width <= 0 || value.height <= 0) diagnostics.push({
|
|
2184
|
-
severity: "error",
|
|
2185
|
-
code: "INVALID_PARTICLE_BOUNDS",
|
|
2186
|
-
message: "particle bounds must have a positive finite size",
|
|
2187
|
-
path: "params.bounds",
|
|
2188
|
-
coordinateSpace: "world"
|
|
2189
|
-
});
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
if (capability.id === "motion.path") {
|
|
2193
|
-
const points = params.points;
|
|
2194
|
-
if (!Array.isArray(points) || points.length < 2 || points.length > 1e4) diagnostics.push({
|
|
2195
|
-
severity: "error",
|
|
2196
|
-
code: "INVALID_MOTION_PATH",
|
|
2197
|
-
message: "motion path must contain 2 to 10000 world-offset points",
|
|
2198
|
-
path: "params.points",
|
|
2199
|
-
coordinateSpace: "world-offset"
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
if (capability.id === "camera.focus") {
|
|
2203
|
-
const parsed = BoardCameraFocusParamsSchema.safeParse(params);
|
|
2204
|
-
if (!parsed.success) diagnostics.push({
|
|
2205
|
-
severity: "error",
|
|
2206
|
-
code: "INVALID_CAMERA_FOCUS",
|
|
2207
|
-
message: parsed.error.issues[0]?.message ?? "invalid camera focus",
|
|
2208
|
-
path: "params",
|
|
2209
|
-
coordinateSpace: "world"
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
return diagnostics;
|
|
2994
|
+
function proceduralClip(input) {
|
|
2995
|
+
return BoardProceduralClipSchema.parse({
|
|
2996
|
+
...input,
|
|
2997
|
+
kindVersion: input.kindVersion ?? 1
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
function composition(input) {
|
|
3001
|
+
return BoardCompositionSchema.parse(input);
|
|
3002
|
+
}
|
|
3003
|
+
function compileComposition(input) {
|
|
3004
|
+
return composition({
|
|
3005
|
+
id: input.id,
|
|
3006
|
+
name: input.name,
|
|
3007
|
+
timeline: {
|
|
3008
|
+
duration: input.duration,
|
|
3009
|
+
tracks: (input.tracks ?? []).map(track),
|
|
3010
|
+
clips: (input.clips ?? []).map(proceduralClip),
|
|
3011
|
+
markers: input.markers ?? []
|
|
2213
3012
|
},
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
return [
|
|
2219
|
-
value.x,
|
|
2220
|
-
value.y,
|
|
2221
|
-
value.width,
|
|
2222
|
-
value.height
|
|
2223
|
-
].every((item) => typeof item === "number" && Number.isFinite(item)) ? value : null;
|
|
3013
|
+
playback: input.playback ?? {
|
|
3014
|
+
loop: false,
|
|
3015
|
+
endBehavior: "hold",
|
|
3016
|
+
reducedMotion: { mode: "base" }
|
|
2224
3017
|
},
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
const particles = Math.max(0, Math.floor(numericParam(params, "count", 0)));
|
|
2229
|
-
return {
|
|
2230
|
-
particles,
|
|
2231
|
-
vertices: particles * 4,
|
|
2232
|
-
dynamicVertices: particles * 4,
|
|
2233
|
-
drawCalls: 1,
|
|
2234
|
-
bufferBytes: particles * 48,
|
|
2235
|
-
simulationSteps: particles
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
|
-
case "effects.trail": return {
|
|
2239
|
-
vertices: 32,
|
|
2240
|
-
dynamicVertices: 32,
|
|
2241
|
-
drawCalls: 1,
|
|
2242
|
-
bufferBytes: 1024,
|
|
2243
|
-
simulationSteps: 16
|
|
2244
|
-
};
|
|
2245
|
-
case "effects.impact":
|
|
2246
|
-
case "effects.flash": return {
|
|
2247
|
-
vertices: 64,
|
|
2248
|
-
drawCalls: 1
|
|
2249
|
-
};
|
|
2250
|
-
case "effects.color": return {
|
|
2251
|
-
drawCalls: 1,
|
|
2252
|
-
filterPasses: 1
|
|
2253
|
-
};
|
|
2254
|
-
case "draw.reveal":
|
|
2255
|
-
case "draw.handwrite": return {
|
|
2256
|
-
drawCalls: 1,
|
|
2257
|
-
dynamicVertices: 1
|
|
2258
|
-
};
|
|
2259
|
-
default: return {};
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
};
|
|
3018
|
+
metadata: input.metadata ?? {},
|
|
3019
|
+
revision: 0
|
|
3020
|
+
});
|
|
2263
3021
|
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
3022
|
+
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
3023
|
+
function sampleEasing(easing, value) {
|
|
3024
|
+
const t = clamp01(value);
|
|
3025
|
+
switch (easing) {
|
|
3026
|
+
case "ease-in-quad": return t * t;
|
|
3027
|
+
case "ease-out-quad": return 1 - (1 - t) ** 2;
|
|
3028
|
+
case "ease-in-out-quad": return t < .5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
3029
|
+
case "ease-in-cubic": return t ** 3;
|
|
3030
|
+
case "ease-out-cubic": return 1 - (1 - t) ** 3;
|
|
3031
|
+
case "ease-in-out-cubic": return t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2;
|
|
3032
|
+
case "ease-out-quart": return 1 - (1 - t) ** 4;
|
|
3033
|
+
case "ease-out-expo": return t === 1 ? 1 : 1 - 2 ** (-10 * t);
|
|
3034
|
+
default: return t;
|
|
2269
3035
|
}
|
|
2270
|
-
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
2271
3036
|
}
|
|
2272
|
-
function
|
|
2273
|
-
return
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
}
|
|
2278
|
-
const
|
|
2279
|
-
|
|
2280
|
-
return {
|
|
2281
|
-
type: "clip",
|
|
2282
|
-
clip
|
|
2283
|
-
};
|
|
2284
|
-
},
|
|
2285
|
-
parallel(...children) {
|
|
2286
|
-
return {
|
|
2287
|
-
type: "parallel",
|
|
2288
|
-
children
|
|
2289
|
-
};
|
|
2290
|
-
},
|
|
2291
|
-
sequence(...children) {
|
|
2292
|
-
return {
|
|
2293
|
-
type: "sequence",
|
|
2294
|
-
children
|
|
2295
|
-
};
|
|
2296
|
-
},
|
|
2297
|
-
stagger(each, ...children) {
|
|
2298
|
-
assertDuration(each, "stagger each");
|
|
2299
|
-
return {
|
|
2300
|
-
type: "stagger",
|
|
2301
|
-
each,
|
|
2302
|
-
children
|
|
2303
|
-
};
|
|
2304
|
-
},
|
|
2305
|
-
delay(duration, child) {
|
|
2306
|
-
assertDuration(duration, "delay duration");
|
|
2307
|
-
return {
|
|
2308
|
-
type: "delay",
|
|
2309
|
-
duration,
|
|
2310
|
-
child
|
|
2311
|
-
};
|
|
2312
|
-
},
|
|
2313
|
-
repeat(count, child) {
|
|
2314
|
-
if (!Number.isSafeInteger(count) || count < 1 || count > 1e3) throw new TypeError("repeat count must be an integer between 1 and 1000");
|
|
2315
|
-
return {
|
|
2316
|
-
type: "repeat",
|
|
2317
|
-
count,
|
|
2318
|
-
child
|
|
2319
|
-
};
|
|
3037
|
+
function interpolate(left, right, progress) {
|
|
3038
|
+
if (typeof left === "number" && typeof right === "number") return left + (right - left) * progress;
|
|
3039
|
+
if (left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right)) {
|
|
3040
|
+
const a = left;
|
|
3041
|
+
const b = right;
|
|
3042
|
+
const result = {};
|
|
3043
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) result[key] = interpolate(a[key], b[key], progress);
|
|
3044
|
+
return result;
|
|
2320
3045
|
}
|
|
2321
|
-
|
|
2322
|
-
function clip(input) {
|
|
2323
|
-
assertDuration(input.duration, "clip duration");
|
|
2324
|
-
if (input.duration === 0) throw new TypeError("clip duration must be greater than zero");
|
|
2325
|
-
return timeline.clip({
|
|
2326
|
-
id: input.id,
|
|
2327
|
-
kind: input.kind,
|
|
2328
|
-
kindVersion: input.kindVersion ?? 1,
|
|
2329
|
-
target: input.target,
|
|
2330
|
-
duration: input.duration,
|
|
2331
|
-
layer: input.layer ?? "content",
|
|
2332
|
-
fill: input.fill ?? "none",
|
|
2333
|
-
easing: input.easing ?? "linear",
|
|
2334
|
-
params: input.params ?? {},
|
|
2335
|
-
keyframes: input.keyframes ?? [],
|
|
2336
|
-
assetRefs: input.assetRefs ?? [],
|
|
2337
|
-
seed: input.seed,
|
|
2338
|
-
metadata: input.metadata ?? {}
|
|
2339
|
-
});
|
|
3046
|
+
return progress < 1 ? left : right;
|
|
2340
3047
|
}
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
start: offset,
|
|
2350
|
-
seed: clipSeed
|
|
2351
|
-
}]
|
|
2352
|
-
};
|
|
2353
|
-
}
|
|
2354
|
-
if (input.type === "delay") {
|
|
2355
|
-
const nested = flatten(input.child, offset + input.duration, sequenceSeed, `${path}.child`);
|
|
3048
|
+
/** Deterministically sample one validated Track without touching scene state. */
|
|
3049
|
+
function sampleTrack(trackValue, time) {
|
|
3050
|
+
const frames = trackValue.keyframes;
|
|
3051
|
+
const first = frames[0];
|
|
3052
|
+
const last = frames.at(-1);
|
|
3053
|
+
if (!first || !last) return null;
|
|
3054
|
+
if (time < first.time) {
|
|
3055
|
+
if (trackValue.fill !== "backwards" && trackValue.fill !== "both") return null;
|
|
2356
3056
|
return {
|
|
2357
|
-
|
|
2358
|
-
|
|
3057
|
+
target: trackValue.target,
|
|
3058
|
+
channel: trackValue.channel,
|
|
3059
|
+
value: first.value
|
|
2359
3060
|
};
|
|
2360
3061
|
}
|
|
2361
|
-
if (
|
|
2362
|
-
|
|
2363
|
-
let cursor = offset;
|
|
2364
|
-
let total = 0;
|
|
2365
|
-
for (let index = 0; index < input.count; index += 1) {
|
|
2366
|
-
const nested = flatten(input.child, cursor, sequenceSeed, `${path}.${index}`);
|
|
2367
|
-
clips.push(...nested.clips);
|
|
2368
|
-
cursor += nested.duration;
|
|
2369
|
-
total += nested.duration;
|
|
2370
|
-
}
|
|
3062
|
+
if (time > last.time) {
|
|
3063
|
+
if (trackValue.fill !== "forwards" && trackValue.fill !== "both") return null;
|
|
2371
3064
|
return {
|
|
2372
|
-
|
|
2373
|
-
|
|
3065
|
+
target: trackValue.target,
|
|
3066
|
+
channel: trackValue.channel,
|
|
3067
|
+
value: last.value
|
|
2374
3068
|
};
|
|
2375
3069
|
}
|
|
2376
|
-
|
|
2377
|
-
let
|
|
2378
|
-
|
|
2379
|
-
const
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
3070
|
+
let low = 0;
|
|
3071
|
+
let high = frames.length;
|
|
3072
|
+
while (low < high) {
|
|
3073
|
+
const middle = low + high >>> 1;
|
|
3074
|
+
if ((frames[middle]?.time ?? Number.POSITIVE_INFINITY) <= time) low = middle + 1;
|
|
3075
|
+
else high = middle;
|
|
3076
|
+
}
|
|
3077
|
+
const left = frames[Math.max(0, low - 1)] ?? first;
|
|
3078
|
+
const right = frames[Math.min(frames.length - 1, low)] ?? last;
|
|
3079
|
+
if (left === right || trackValue.interpolation === "step") return {
|
|
3080
|
+
target: trackValue.target,
|
|
3081
|
+
channel: trackValue.channel,
|
|
3082
|
+
value: left.value
|
|
3083
|
+
};
|
|
3084
|
+
const span = Math.max(Number.EPSILON, right.time - left.time);
|
|
3085
|
+
const progress = sampleEasing(right.easing ?? "linear", (time - left.time) / span);
|
|
2384
3086
|
return {
|
|
2385
|
-
|
|
2386
|
-
|
|
3087
|
+
target: trackValue.target,
|
|
3088
|
+
channel: trackValue.channel,
|
|
3089
|
+
value: interpolate(left.value, right.value, progress)
|
|
2387
3090
|
};
|
|
2388
3091
|
}
|
|
2389
|
-
function
|
|
2390
|
-
const
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
3092
|
+
function sampleCompositionTracks(value, time) {
|
|
3093
|
+
const position = Math.max(0, Math.min(value.timeline.duration, time));
|
|
3094
|
+
return value.timeline.tracks.flatMap((item) => {
|
|
3095
|
+
const sampled = sampleTrack(item, position);
|
|
3096
|
+
return sampled ? [sampled] : [];
|
|
3097
|
+
});
|
|
3098
|
+
}
|
|
3099
|
+
const ZERO_COST = {
|
|
3100
|
+
particles: 0,
|
|
3101
|
+
vertices: 0,
|
|
3102
|
+
dynamicVertices: 0,
|
|
3103
|
+
drawCalls: 0,
|
|
3104
|
+
filterPasses: 0,
|
|
3105
|
+
renderTexturePixels: 0,
|
|
3106
|
+
textureBytes: 0,
|
|
3107
|
+
bufferBytes: 0,
|
|
3108
|
+
simulationSteps: 0
|
|
3109
|
+
};
|
|
3110
|
+
const DEFAULT_BOARD_LIMITS = DEFAULT_BOARD_RENDER_LIMITS;
|
|
3111
|
+
function builtinDefinition(capability) {
|
|
2394
3112
|
return {
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
duration: flattened.duration,
|
|
2399
|
-
seed: input.seed,
|
|
2400
|
-
restPose: input.restPose ?? {},
|
|
2401
|
-
metadata: input.metadata ?? {}
|
|
3113
|
+
...capability,
|
|
3114
|
+
validate() {
|
|
3115
|
+
return [];
|
|
2402
3116
|
},
|
|
2403
|
-
|
|
2404
|
-
|
|
3117
|
+
estimateCost(params) {
|
|
3118
|
+
return estimateBuiltinBoardClipCost({
|
|
3119
|
+
kind: capability.id,
|
|
3120
|
+
params
|
|
3121
|
+
});
|
|
3122
|
+
}
|
|
2405
3123
|
};
|
|
2406
3124
|
}
|
|
2407
|
-
function addCost(target, source) {
|
|
2408
|
-
for (const key of Object.keys(target)) target[key] += source[key] ?? 0;
|
|
2409
|
-
}
|
|
2410
3125
|
var BoardExtensionRegistry = class {
|
|
2411
3126
|
#extensions = /* @__PURE__ */ new Map();
|
|
2412
3127
|
#presets = /* @__PURE__ */ new Map();
|
|
3128
|
+
constructor() {
|
|
3129
|
+
for (const capability of BOARD_BUILTIN_CAPABILITIES) this.register(builtinDefinition(capability));
|
|
3130
|
+
}
|
|
2413
3131
|
register(definition) {
|
|
2414
3132
|
const key = `${definition.kind}:${definition.id}@${definition.version}`;
|
|
2415
3133
|
const target = definition.kind === "preset" ? this.#presets : this.#extensions;
|
|
@@ -2418,9 +3136,15 @@ var BoardExtensionRegistry = class {
|
|
|
2418
3136
|
return this;
|
|
2419
3137
|
}
|
|
2420
3138
|
capabilities() {
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
3139
|
+
return [...this.#extensions.values(), ...this.#presets.values()].map((definition) => ({
|
|
3140
|
+
kind: definition.kind,
|
|
3141
|
+
id: definition.id,
|
|
3142
|
+
version: definition.version,
|
|
3143
|
+
...definition.digest ? { digest: definition.digest } : {},
|
|
3144
|
+
...definition.renderers ? { renderers: definition.renderers } : {},
|
|
3145
|
+
...definition.fallbackId ? { fallbackId: definition.fallbackId } : {},
|
|
3146
|
+
...definition.schema ? { schema: definition.schema } : {}
|
|
3147
|
+
}));
|
|
2424
3148
|
}
|
|
2425
3149
|
compilePreset(id, version, params) {
|
|
2426
3150
|
const preset = this.#presets.get(`preset:${id}@${version}`);
|
|
@@ -2428,88 +3152,72 @@ var BoardExtensionRegistry = class {
|
|
|
2428
3152
|
return preset.compile(params);
|
|
2429
3153
|
}
|
|
2430
3154
|
validate(input) {
|
|
3155
|
+
const parsed = BoardCompositionSchema.safeParse(input.composition);
|
|
3156
|
+
if (!parsed.success) return {
|
|
3157
|
+
valid: false,
|
|
3158
|
+
diagnostics: parsed.error.issues.map((issue) => ({
|
|
3159
|
+
severity: "error",
|
|
3160
|
+
code: "INVALID_COMPOSITION",
|
|
3161
|
+
message: issue.message,
|
|
3162
|
+
path: issue.path.join(".")
|
|
3163
|
+
})),
|
|
3164
|
+
peakCost: { ...ZERO_COST }
|
|
3165
|
+
};
|
|
2431
3166
|
const diagnostics = [];
|
|
3167
|
+
const composition = parsed.data;
|
|
3168
|
+
const cost = { ...ZERO_COST };
|
|
2432
3169
|
const events = [];
|
|
2433
|
-
const
|
|
2434
|
-
const persistentCost = { ...ZERO_COST };
|
|
2435
|
-
for (const [index, effect] of (input.effects ?? []).entries()) {
|
|
2436
|
-
const definition = this.#extensions.get(`effect:${effect.kind}@${effect.kindVersion}`);
|
|
2437
|
-
if (!definition) {
|
|
2438
|
-
diagnostics.push({
|
|
2439
|
-
severity: "warning",
|
|
2440
|
-
code: "UNKNOWN_EFFECT",
|
|
2441
|
-
message: `No renderer is registered for ${effect.kind}@${effect.kindVersion}`,
|
|
2442
|
-
path: `effects.${index}`
|
|
2443
|
-
});
|
|
2444
|
-
continue;
|
|
2445
|
-
}
|
|
2446
|
-
diagnostics.push(...definition.validate?.(effect.params) ?? []);
|
|
2447
|
-
addCost(persistentCost, definition.estimateCost(effect.params, profile));
|
|
2448
|
-
}
|
|
2449
|
-
for (const [index, clip] of input.clips.entries()) {
|
|
3170
|
+
for (const clip of composition.timeline.clips) {
|
|
2450
3171
|
const definition = this.#extensions.get(`clip:${clip.kind}@${clip.kindVersion}`);
|
|
2451
3172
|
if (!definition) {
|
|
2452
3173
|
diagnostics.push({
|
|
2453
3174
|
severity: "warning",
|
|
2454
3175
|
code: "UNKNOWN_CLIP",
|
|
2455
|
-
message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}
|
|
2456
|
-
path: `clips.${index}`
|
|
3176
|
+
message: `No renderer is registered for ${clip.kind}@${clip.kindVersion}`
|
|
2457
3177
|
});
|
|
2458
3178
|
continue;
|
|
2459
3179
|
}
|
|
3180
|
+
diagnostics.push(...validateBuiltinBoardClip(clip, `composition.timeline.clips.${clip.id}`));
|
|
2460
3181
|
diagnostics.push(...definition.validate?.(clip.params) ?? []);
|
|
2461
|
-
const
|
|
2462
|
-
|
|
3182
|
+
const estimate = definition.estimateCost(clip.params, input.profile ?? "high");
|
|
3183
|
+
const clipCost = { ...ZERO_COST };
|
|
3184
|
+
for (const key of Object.keys(clipCost)) clipCost[key] = estimate[key] ?? 0;
|
|
2463
3185
|
events.push({
|
|
2464
3186
|
at: clip.start,
|
|
2465
3187
|
direction: 1,
|
|
2466
|
-
cost
|
|
2467
|
-
}
|
|
3188
|
+
cost: clipCost
|
|
3189
|
+
});
|
|
3190
|
+
events.push({
|
|
2468
3191
|
at: clip.start + clip.duration,
|
|
2469
3192
|
direction: -1,
|
|
2470
|
-
cost
|
|
2471
|
-
});
|
|
2472
|
-
}
|
|
2473
|
-
const cameraFocusClips = input.clips.map((clip, index) => ({
|
|
2474
|
-
clip,
|
|
2475
|
-
index
|
|
2476
|
-
})).filter(({ clip }) => clip.kind === "camera.focus").sort((left, right) => left.clip.start - right.clip.start);
|
|
2477
|
-
for (let index = 1; index < cameraFocusClips.length; index += 1) {
|
|
2478
|
-
const previous = cameraFocusClips[index - 1];
|
|
2479
|
-
const currentFocus = cameraFocusClips[index];
|
|
2480
|
-
if (previous && currentFocus && currentFocus.clip.start < previous.clip.start + previous.clip.duration) diagnostics.push({
|
|
2481
|
-
severity: "error",
|
|
2482
|
-
code: "OVERLAPPING_CAMERA_FOCUS",
|
|
2483
|
-
message: "camera.focus clips must not overlap",
|
|
2484
|
-
path: `clips.${currentFocus.index}.start`
|
|
3193
|
+
cost: clipCost
|
|
2485
3194
|
});
|
|
2486
3195
|
}
|
|
2487
3196
|
events.sort((left, right) => left.at - right.at || left.direction - right.direction);
|
|
2488
|
-
const
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
peak[key] = Math.max(peak[key], current[key]);
|
|
3197
|
+
const active = { ...ZERO_COST };
|
|
3198
|
+
for (const event of events) for (const key of Object.keys(active)) {
|
|
3199
|
+
active[key] += event.cost[key] * event.direction;
|
|
3200
|
+
cost[key] = Math.max(cost[key], active[key]);
|
|
2493
3201
|
}
|
|
3202
|
+
for (const effect of input.effects ?? []) if (effect.kind === "effects.pulse" || effect.kind === "effects.float") cost.drawCalls += 1;
|
|
2494
3203
|
const limits = input.limits ?? DEFAULT_BOARD_LIMITS;
|
|
2495
|
-
for (const key of Object.keys(
|
|
3204
|
+
for (const key of Object.keys(cost)) if (cost[key] > limits[key]) diagnostics.push({
|
|
2496
3205
|
severity: "warning",
|
|
2497
3206
|
code: "RENDER_BUDGET_EXCEEDED",
|
|
2498
|
-
message: `${key} peaks at ${
|
|
3207
|
+
message: `${key} peaks at ${cost[key]}, above ${limits[key]}`,
|
|
2499
3208
|
path: key,
|
|
2500
3209
|
adaptation: { quality: "lower" }
|
|
2501
3210
|
});
|
|
2502
3211
|
return {
|
|
2503
3212
|
valid: !diagnostics.some((item) => item.severity === "error"),
|
|
2504
3213
|
diagnostics,
|
|
2505
|
-
peakCost:
|
|
3214
|
+
peakCost: cost
|
|
2506
3215
|
};
|
|
2507
3216
|
}
|
|
2508
3217
|
};
|
|
2509
|
-
function createBoardExtensionRegistry(
|
|
2510
|
-
|
|
2511
|
-
if (input.builtins !== false) for (const capability of BUILTIN_CAPABILITIES) registry.register(builtinDefinition(capability));
|
|
2512
|
-
return registry;
|
|
3218
|
+
function createBoardExtensionRegistry() {
|
|
3219
|
+
return new BoardExtensionRegistry();
|
|
2513
3220
|
}
|
|
3221
|
+
const BOARD_CHANNELS = BOARD_ANIMATION_CHANNELS;
|
|
2514
3222
|
//#endregion
|
|
2515
|
-
export { BILLING_ACCESS_BLOCKED_ERROR_CODE,
|
|
3223
|
+
export { BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_ANIMATION_CHANNEL_CAPABILITIES, BOARD_CHANNELS, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BillingApi, BoardAuthoringItemSchema, BoardClient, BoardCompositionInputSchema, BoardCompositionSchema, BoardEffectInputSchema, BoardEffectSchema, BoardExtensionRegistry, BoardItemPatchSchema, BoardPlaybackPolicySchema, COHUB_ENVIRONMENTS, COHUB_SOURCE_HEADER, COHUB_SOURCE_HEADER_NAMES, CohubClient, CohubHttpClient, DEFAULT_BOARD_LIMITS, FEATURE_NOT_ENTITLED_ERROR_CODE, GenerationPolicyError, HttpError, ParentBridgeTransport, PopupBrokerTransport, REQUEST_SOURCE_VIA_MAX_LENGTH, ReferencesApi, ReferralsApi, SessionGenerationStreamClient, SessionPatchReducer, SpacePublicFilesApi, UI_COMMAND_DEFAULT_TIMEOUT_MS, UI_COMMAND_MAX_TIMEOUT_MS, UI_COMMAND_PAYLOAD_MAX_BYTES, UI_COMMAND_PENDING_TTL_SECONDS, UI_COMMAND_SETTLEMENT_GRACE_SECONDS, UI_COMMAND_TERMINAL_TTL_SECONDS, UI_COMMAND_VERSION, UiCommandsApi, UsersApi, VoiceApi, VoiceInputClient, WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES, WORK_COMPOSER_CHIP_KEY_MAX_LENGTH, WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH, WORK_SURFACE_READY_TIMEOUT_MS, WORK_SURFACE_REQUEST_TIMEOUT_MS, WebsocketClient, WorkCommerceApi, WorkRealtimeApi, WorkRefParseError, WorkRoom, WorkRuntimeApi, WorkSurfaceApi, WorksApi, assertGenerationRequestAllowedByPolicy, buildSpaceInvitePath, buildSpacePath, buildWorkSurfaceRequest, clearGrantedWorkScopes, compileComposition, composition, createBoardExtensionRegistry, createCohubClient, createHttpClient, createSessionGenerationStreamClient, createSessionPatchReducer, createSlugWorkIdResolver, createVoiceInputClient, createWebsocketClient, createWorkBridgeCore, createWorkRuntime, decodeGenerationPolicy, encodeGenerationPolicy, extractBillingPayload, filterDiscoverableGenerationModels, filterGenerationDeclarationsByPolicy, findGenerationModelPolicy, formatWorkRef, getAllowedGenerationModelIds, hasGrantedWorkScopes, hasRequestSourceIdentity, isBillingAccessBlockedCode, isBillingAccessBlockedError, isFeatureNotEntitledError, isGenerationModelHidden, isHttpErrorCode, isRequestSourceClientId, isRequestSourceEmpty, isRequestSourceUuid, isTerminalUiCommandStatus, isUiSurfaceMethod, isUuid, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAssistantMessageCommit, parseBoardCompositionInput, parseBoardEffectInput, parseBoardPlaybackPolicy, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseUiCommand, parseWorkRef, parseWorkSurfaceReady, parseWorkSurfaceResponse, proceduralClip, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveCohubEnvironment, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, resolveWorkTransport, sampleCompositionTracks, sampleEasing, sampleTrack, sanitizeAccessToken, setGrantedWorkScopes, track };
|