@neta-art/cohub 5.6.0 → 5.8.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.
Files changed (59) hide show
  1. package/README.md +18 -5
  2. package/dist/board/codec.js +15 -0
  3. package/dist/board/core/palette.d.ts +3 -2
  4. package/dist/board/core/shape-types.d.ts +8 -2
  5. package/dist/board/core/shape-types.js +3 -7
  6. package/dist/board/core/tool-styles.d.ts +2 -1
  7. package/dist/board/image-key.js +3 -5
  8. package/dist/board/index.d.ts +10 -5
  9. package/dist/board/index.js +7 -3
  10. package/dist/board/media-playback.d.ts +22 -0
  11. package/dist/board/media-playback.js +67 -0
  12. package/dist/board/media.d.ts +7 -0
  13. package/dist/board/media.js +70 -0
  14. package/dist/board/nodes.d.ts +113 -0
  15. package/dist/board/nodes.js +154 -0
  16. package/dist/board/render/audio-waveform.d.ts +12 -0
  17. package/dist/board/render/audio-waveform.js +36 -0
  18. package/dist/board/render/index.d.ts +4 -1
  19. package/dist/board/render/index.js +4 -1
  20. package/dist/board/render/media-interaction.d.ts +19 -0
  21. package/dist/board/render/media-interaction.js +26 -0
  22. package/dist/board/render/renderers/audio-card-renderer.d.ts +5 -0
  23. package/dist/board/render/renderers/audio-card-renderer.js +120 -0
  24. package/dist/board/render/renderers/board-renderer-registry.js +4 -2
  25. package/dist/board/render/renderers/draw-card-renderer.js +1 -1
  26. package/dist/board/render/renderers/file-card-renderer.js +1 -1
  27. package/dist/board/render/renderers/frame-card-renderer.js +1 -1
  28. package/dist/board/render/renderers/geo-card-renderer.js +1 -1
  29. package/dist/board/render/renderers/image-card-renderer.js +1 -1
  30. package/dist/board/render/renderers/task-card-renderer.d.ts +2 -1
  31. package/dist/board/render/renderers/task-card-renderer.js +21 -53
  32. package/dist/board/render/renderers/text-card-renderer.js +1 -1
  33. package/dist/board/render/renderers/unknown-card-renderer.js +1 -1
  34. package/dist/board/render/renderers/video-card-renderer.js +1 -1
  35. package/dist/board/render/video-thumbnail.d.ts +16 -0
  36. package/dist/board/render/video-thumbnail.js +86 -0
  37. package/dist/board/task.d.ts +10 -5
  38. package/dist/board/task.js +159 -103
  39. package/dist/chunks/environment.d.ts +6 -6
  40. package/dist/chunks/environment.js +6 -6
  41. package/dist/chunks/http.d.ts +71 -5
  42. package/dist/chunks/http.js +1535 -167
  43. package/dist/chunks/transport.js +8 -1
  44. package/dist/chunks/websocket.d.ts +139 -3
  45. package/dist/http.d.ts +3 -3
  46. package/dist/index.d.ts +245 -4
  47. package/dist/index.js +438 -788
  48. package/dist/protocol/dist/board-document.d.ts +271 -53
  49. package/dist/protocol/dist/board-document.js +54 -22
  50. package/dist/protocol/dist/board-node.d.ts +18 -0
  51. package/dist/protocol/dist/board-node.js +239 -0
  52. package/dist/protocol/dist/board-url.d.ts +12 -0
  53. package/dist/protocol/dist/board-url.js +83 -0
  54. package/dist/protocol/dist/board.d.ts +5 -0
  55. package/dist/protocol/dist/index.d.ts +2 -1
  56. package/dist/protocol/dist/index.js +2 -1
  57. package/dist/protocol/dist/provenance.js +1 -0
  58. package/docs/work-runtime-guide.md +7 -7
  59. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,191 +1,438 @@
1
- import { A as parseAssistantMessageCommit, B as ReferencesApi, C as SpaceClient, D as buildSpacePath, E as buildSpaceInvitePath, F as BOARD_ARROW_STROKE_SIZE, G as ModelsApi, H as PublicAssetsApi, I as BOARD_BUILTIN_CAPABILITIES, J as ChannelsApi, K as GenerationsApi, L as BOARD_CONNECTION_STROKE_SIZE, M as createSessionPatchReducer, N as ensureRealtimeConnected, O as SessionGenerationStreamClient, P as BoardConnectionSchema, R as DEFAULT_BOARD_RENDER_LIMITS, S as BoardTransactionError, T as PublicInviteApi, U as SkillsApi, V as SearchApi, W as PromptsApi, _ as isUiSurfaceMethod, a as ReferralsApi, b as TasksApi, c as UiCommandsApi, d as UI_COMMAND_PAYLOAD_MAX_BYTES, f as UI_COMMAND_PENDING_TTL_SECONDS, g as isTerminalUiCommandStatus, h as UI_COMMAND_VERSION, i as WorksApi, j as SessionPatchReducer, k as createSessionGenerationStreamClient, l as UI_COMMAND_DEFAULT_TIMEOUT_MS, m as UI_COMMAND_TERMINAL_TTL_SECONDS, n as createHttpClient, o as UsersApi, p as UI_COMMAND_SETTLEMENT_GRACE_SECONDS, q as CronJobsApi, r as WorkCommerceApi, s as UserApi, t as CohubHttpClient, u as UI_COMMAND_MAX_TIMEOUT_MS, v as parseUiCommand, w as SpacesApi, x as BoardClient, y as parseUiCommandId, z as SessionAccessApi } from "./chunks/http.js";
1
+ import { $ as SearchApi, A as UI_COMMAND_PENDING_TTL_SECONDS, B as BoardAwarenessClientPayloadSchema, C as BoardInputError, D as UI_COMMAND_DEFAULT_TIMEOUT_MS, E as validateBoardNodes, F as isUiSurfaceMethod, G as validateBoardNodeInput, H as BOARD_GEO_KINDS, I as parseUiCommand, J as ensureRealtimeConnected, K as BoardPlaybackPolicySchema, L as parseUiCommandId, M as UI_COMMAND_TERMINAL_TTL_SECONDS, N as UI_COMMAND_VERSION, O as UI_COMMAND_MAX_TIMEOUT_MS, P as isTerminalUiCommandStatus, Q as ReferencesApi, R as parseSpaceSlug, S as createSessionPatchReducer, T as createBoardNode, U as BOARD_NATIVE_NODE_TYPES, V as BOARD_COLOR_IDS, W as BOARD_NODE_CONTRACT, X as DEFAULT_BOARD_RENDER_LIMITS, Y as BOARD_BUILTIN_CAPABILITIES, Z as SessionAccessApi, _ as buildSpacePath, a as ReferralsApi, at as CronJobsApi, b as parseAssistantMessageCommit, c as UiCommandsApi, d as BoardTransactionError, et as PublicAssetsApi, f as SpaceClient, g as buildSpaceInvitePath, h as PublicInviteApi, i as WorksApi, it as GenerationsApi, j as UI_COMMAND_SETTLEMENT_GRACE_SECONDS, k as UI_COMMAND_PAYLOAD_MAX_BYTES, l as TasksApi, m as SpacesApi, n as createHttpClient, nt as PromptsApi, o as UsersApi, ot as ChannelsApi, p as SpacePublicFilesApi, q as parseBoardPlaybackPolicy, r as WorkCommerceApi, rt as ModelsApi, s as UserApi, t as CohubHttpClient, tt as SkillsApi, u as BoardClient, v as SessionGenerationStreamClient, w as assertBoardNodes, x as SessionPatchReducer, y as createSessionGenerationStreamClient, z as parseUsername } from "./chunks/http.js";
2
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 REALTIME_DOMAINS } 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 src/apis/billing.ts
8
- var BillingApi = class {
9
- transport;
10
- constructor(transport) {
11
- this.transport = transport;
7
+ //#region ../protocol/dist/generation/catalog.js
8
+ function isGenerationModelHidden(model) {
9
+ return model.hidden === true;
10
+ }
11
+ function filterDiscoverableGenerationModels(models, options = {}) {
12
+ const includeModelIds = new Set(options.includeModelIds);
13
+ return models.filter((model) => !isGenerationModelHidden(model) || includeModelIds.has(model.model));
14
+ }
15
+ //#endregion
16
+ //#region ../protocol/dist/generation/policy.js
17
+ const GENERATION_POLICY_ENV_KEY = "COHUB_GENERATION_POLICY_B64";
18
+ var GenerationPolicyError = class extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "GenerationPolicyError";
12
22
  }
13
- async getCredits(input) {
14
- const query = input?.tokenType ? `?tokenType=${encodeURIComponent(input.tokenType)}` : "";
15
- return this.transport.request(`/api/billing/credits${query}`);
23
+ };
24
+ function isRecord$1(value) {
25
+ return !!value && typeof value === "object" && !Array.isArray(value);
26
+ }
27
+ function isPrimitiveEnumValue(value) {
28
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
29
+ }
30
+ function normalizeConstraint(value) {
31
+ if (!isRecord$1(value) || typeof value.kind !== "string") return null;
32
+ if (value.kind === "enum") {
33
+ if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
34
+ return {
35
+ kind: "enum",
36
+ values: value.values
37
+ };
16
38
  }
17
- async getBalanceActivities(input) {
18
- const params = new URLSearchParams();
19
- if (input?.tokenType) params.set("tokenType", input.tokenType);
20
- if (input?.page) params.set("page", String(input.page));
21
- if (input?.limit) params.set("limit", String(input.limit));
22
- const query = params.toString();
23
- return this.transport.request(`/api/billing/balance-activities${query ? `?${query}` : ""}`);
39
+ if (value.kind === "number" || value.kind === "integer") {
40
+ const result = { kind: value.kind };
41
+ if (typeof value.min === "number" && Number.isFinite(value.min)) result.min = value.min;
42
+ if (typeof value.max === "number" && Number.isFinite(value.max)) result.max = value.max;
43
+ if (result.min !== void 0 && result.max !== void 0 && result.min > result.max) return null;
44
+ if (Array.isArray(value.values) && value.values.every((item) => typeof item === "number" && Number.isFinite(item))) {
45
+ if (value.kind === "integer" && !value.values.every(Number.isInteger)) return null;
46
+ const min = result.min;
47
+ const max = result.max;
48
+ if (min !== void 0 && value.values.some((item) => item < min)) return null;
49
+ if (max !== void 0 && value.values.some((item) => item > max)) return null;
50
+ result.values = value.values;
51
+ }
52
+ return result;
24
53
  }
25
- async getCatalog() {
26
- return this.transport.request("/api/billing/catalog");
54
+ if (value.kind === "boolean") return typeof value.value === "boolean" ? {
55
+ kind: "boolean",
56
+ value: value.value
57
+ } : { kind: "boolean" };
58
+ return null;
59
+ }
60
+ function normalizeGenerationPolicy(value) {
61
+ if (!isRecord$1(value) || value.version !== 1) return null;
62
+ if (value.mode === "auto") return {
63
+ version: 1,
64
+ mode: "auto"
65
+ };
66
+ if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
67
+ const models = [];
68
+ for (const item of value.models) {
69
+ if (!isRecord$1(item) || typeof item.model !== "string" || !item.model.trim()) return null;
70
+ const modelPolicy = { model: item.model.trim() };
71
+ if (item.parameters !== void 0) {
72
+ if (!isRecord$1(item.parameters)) return null;
73
+ const parameters = {};
74
+ for (const [key, rawConstraint] of Object.entries(item.parameters)) {
75
+ if (!key.trim()) return null;
76
+ const constraint = normalizeConstraint(rawConstraint);
77
+ if (!constraint) return null;
78
+ parameters[key] = constraint;
79
+ }
80
+ if (Object.keys(parameters).length > 0) modelPolicy.parameters = parameters;
81
+ }
82
+ models.push(modelPolicy);
27
83
  }
28
- async getFeatureEntitlement(featureKey) {
29
- return this.transport.request(`/api/billing/features/${encodeURIComponent(featureKey)}`);
84
+ return {
85
+ version: 1,
86
+ mode: "limited",
87
+ models
88
+ };
89
+ }
90
+ function bytesToBase64(bytes) {
91
+ let binary = "";
92
+ for (const byte of bytes) binary += String.fromCharCode(byte);
93
+ return btoa(binary);
94
+ }
95
+ function base64ToBytes(value) {
96
+ const binary = atob(value);
97
+ const bytes = new Uint8Array(binary.length);
98
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
99
+ return bytes;
100
+ }
101
+ function toBase64Url(value) {
102
+ return bytesToBase64(new TextEncoder().encode(value)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
103
+ }
104
+ function fromBase64Url(value) {
105
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
106
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
107
+ return new TextDecoder().decode(base64ToBytes(padded));
108
+ }
109
+ function encodeGenerationPolicy(policy) {
110
+ return toBase64Url(JSON.stringify(policy));
111
+ }
112
+ function decodeGenerationPolicy(value) {
113
+ try {
114
+ return normalizeGenerationPolicy(JSON.parse(fromBase64Url(value)));
115
+ } catch {
116
+ return null;
30
117
  }
31
- async getSubscriptions(input) {
32
- const params = new URLSearchParams();
33
- if (input?.page) params.set("page", String(input.page));
34
- if (input?.limit) params.set("limit", String(input.limit));
35
- const query = params.toString();
36
- return this.transport.request(`/api/billing/subscriptions${query ? `?${query}` : ""}`);
118
+ }
119
+ function parseGenerationPolicyFromEnv(env) {
120
+ const value = env[GENERATION_POLICY_ENV_KEY]?.trim();
121
+ return value ? decodeGenerationPolicy(value) : null;
122
+ }
123
+ function getAllowedGenerationModelIds(policy) {
124
+ if (!policy || policy.mode === "auto") return null;
125
+ return [...new Set((policy.models ?? []).map((item) => item.model))];
126
+ }
127
+ function findGenerationModelPolicy(policy, model) {
128
+ if (!policy || policy.mode === "auto") return null;
129
+ return policy.models?.find((item) => item.model === model) ?? null;
130
+ }
131
+ function formatList(values) {
132
+ return values.map((value) => `- ${String(value)}`).join("\n");
133
+ }
134
+ function modelNotSupportedMessage(allowedModels) {
135
+ return [
136
+ "This turn supports the following generation models:",
137
+ "",
138
+ formatList(allowedModels),
139
+ "",
140
+ "Choose one of these models or update Generation settings in the Models panel."
141
+ ].join("\n");
142
+ }
143
+ function valuesNotSupportedMessage(model, name, values, received) {
144
+ return [
145
+ `This turn supports selected values for ${model}.${name}:`,
146
+ "",
147
+ formatList(values),
148
+ "",
149
+ "Received:",
150
+ `- ${String(received)}`
151
+ ].join("\n");
152
+ }
153
+ function rangeNotSupportedMessage(model, name, min, max, received) {
154
+ return [
155
+ `This turn supports ${model}.${name} in the range ${min !== void 0 && max !== void 0 ? `${min}–${max}` : min !== void 0 ? `at least ${min}` : `at most ${max}`}.`,
156
+ "",
157
+ "Received:",
158
+ `- ${String(received)}`
159
+ ].join("\n");
160
+ }
161
+ function booleanNotSupportedMessage(model, name, expected, received) {
162
+ return [
163
+ `This turn supports ${model}.${name}=${String(expected)}.`,
164
+ "",
165
+ "Received:",
166
+ `- ${String(received)}`
167
+ ].join("\n");
168
+ }
169
+ function sameEnumValue(a, b) {
170
+ return typeof a === typeof b && a === b;
171
+ }
172
+ function validateConstraint(input) {
173
+ const { model, name, value, constraint } = input;
174
+ if (constraint.kind === "enum") {
175
+ if (!constraint.values.some((item) => sameEnumValue(item, value))) throw new GenerationPolicyError(valuesNotSupportedMessage(model, name, constraint.values, value));
176
+ return;
37
177
  }
38
- async createOrder(productKey, input) {
39
- return this.transport.request("/api/billing/orders", {
40
- method: "POST",
41
- body: JSON.stringify({
42
- ...input ?? {},
43
- productKey
44
- })
45
- });
178
+ if (constraint.kind === "number" || constraint.kind === "integer") {
179
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
180
+ if (constraint.kind === "integer" && !Number.isInteger(value)) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
181
+ if (constraint.values?.length && !constraint.values.includes(value)) throw new GenerationPolicyError(valuesNotSupportedMessage(model, name, constraint.values, value));
182
+ if (constraint.min !== void 0 && value < constraint.min) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
183
+ if (constraint.max !== void 0 && value > constraint.max) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
184
+ return;
46
185
  }
47
- async createSubscription(productKey, input) {
48
- return this.transport.request("/api/billing/subscriptions", {
49
- method: "POST",
50
- body: JSON.stringify({
51
- ...input ?? {},
52
- productKey
53
- })
186
+ if (constraint.kind === "boolean" && constraint.value !== void 0 && value !== constraint.value) throw new GenerationPolicyError(booleanNotSupportedMessage(model, name, constraint.value, value));
187
+ }
188
+ function assertGenerationRequestAllowedByPolicy(input) {
189
+ const allowedModels = getAllowedGenerationModelIds(input.policy);
190
+ if (!allowedModels) return;
191
+ const modelPolicy = findGenerationModelPolicy(input.policy, input.model);
192
+ if (!modelPolicy) throw new GenerationPolicyError(modelNotSupportedMessage(allowedModels));
193
+ const parameters = input.parameters ?? {};
194
+ for (const [name, value] of Object.entries(parameters)) {
195
+ const constraint = modelPolicy.parameters?.[name];
196
+ if (!constraint) continue;
197
+ validateConstraint({
198
+ model: input.model,
199
+ name,
200
+ value,
201
+ constraint
54
202
  });
55
203
  }
56
- async cancelSubscriptionCheckout(subscriptionId) {
57
- return this.transport.request(`/api/billing/subscriptions/${encodeURIComponent(subscriptionId)}/checkout`, { method: "DELETE" });
204
+ }
205
+ function cloneDeclaration(declaration) {
206
+ return {
207
+ ...declaration,
208
+ content: {
209
+ ...declaration.content,
210
+ input: [...declaration.content.input]
211
+ },
212
+ parameters: declaration.parameters ? Object.fromEntries(Object.entries(declaration.parameters).map(([key, value]) => [key, { ...value }])) : declaration.parameters,
213
+ examples: declaration.examples ? [...declaration.examples] : declaration.examples
214
+ };
215
+ }
216
+ function narrowParameterSpec(spec, constraint) {
217
+ const next = { ...spec };
218
+ if (constraint.kind === "enum") {
219
+ if ("enum" in next && Array.isArray(next.enum)) next.enum = next.enum.filter((value) => constraint.values.some((allowed) => sameEnumValue(allowed, value)));
220
+ return next;
58
221
  }
59
- async cancelSubscriptionAutoRenew(subscriptionId) {
60
- return this.transport.request(`/api/billing/subscriptions/${encodeURIComponent(subscriptionId)}`, {
61
- method: "PATCH",
62
- body: JSON.stringify({ cancelAtPeriodEnd: true })
63
- });
222
+ if (constraint.kind === "number" || constraint.kind === "integer") {
223
+ if ("min" in next && typeof next.min === "number" && constraint.min !== void 0) next.min = Math.max(next.min, constraint.min);
224
+ else if ("min" in next && constraint.min !== void 0) next.min = constraint.min;
225
+ if ("max" in next && typeof next.max === "number" && constraint.max !== void 0) next.max = Math.min(next.max, constraint.max);
226
+ else if ("max" in next && constraint.max !== void 0) next.max = constraint.max;
227
+ if (constraint.values?.length && "enum" in next && Array.isArray(next.enum)) next.enum = next.enum.filter((value) => constraint.values?.some((allowed) => sameEnumValue(allowed, value)));
64
228
  }
65
- async createRedemption(input) {
66
- return this.transport.request("/api/billing/redemptions", {
67
- method: "POST",
68
- body: JSON.stringify(input)
69
- });
70
- }
71
- };
229
+ return next;
230
+ }
231
+ function filterGenerationDeclarationsByPolicy(declarations, policy) {
232
+ const allowedModels = getAllowedGenerationModelIds(policy);
233
+ if (!allowedModels) return declarations;
234
+ const allowedSet = new Set(allowedModels);
235
+ return declarations.filter((declaration) => allowedSet.has(declaration.model)).map((declaration) => {
236
+ const cloned = cloneDeclaration(declaration);
237
+ const modelPolicy = findGenerationModelPolicy(policy, declaration.model);
238
+ if (!modelPolicy?.parameters || !cloned.parameters) return cloned;
239
+ for (const [name, constraint] of Object.entries(modelPolicy.parameters)) {
240
+ const spec = cloned.parameters[name];
241
+ if (spec) cloned.parameters[name] = narrowParameterSpec(spec, constraint);
242
+ }
243
+ return cloned;
244
+ });
245
+ }
72
246
  //#endregion
73
- //#region ../protocol/dist/realtime/board-awareness.js
74
- const idSchema$1 = z.string().min(1).max(160);
75
- const finiteSchema$1 = z.number().finite();
76
- const BoardAwarenessPointSchema = z.object({
77
- x: finiteSchema$1,
78
- y: finiteSchema$1
247
+ //#region ../protocol/dist/work-surface.js
248
+ const WORK_SURFACE_PROTOCOL = "cohub.surface";
249
+ const WORK_SURFACE_READY_TIMEOUT_MS = 1e4;
250
+ const WORK_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
251
+ const WORK_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
252
+ const WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
253
+ const WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
254
+ const isRecord = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
255
+ const isSurfaceEnvelope = (value) => isRecord(value) && value.protocol === "cohub.surface" && value.version === 1;
256
+ const parseWorkSurfaceReady = (value) => {
257
+ if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
258
+ const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
259
+ return {
260
+ protocol: WORK_SURFACE_PROTOCOL,
261
+ version: 1,
262
+ type: "ready",
263
+ methods
264
+ };
265
+ };
266
+ const parseWorkSurfaceResponse = (value) => {
267
+ if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
268
+ if (typeof value.requestId !== "string" || !value.requestId) return null;
269
+ const error = isRecord(value.error) ? {
270
+ code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
271
+ message: typeof value.error.message === "string" ? value.error.message : "Work surface call failed"
272
+ } : void 0;
273
+ return {
274
+ protocol: WORK_SURFACE_PROTOCOL,
275
+ version: 1,
276
+ type: "response",
277
+ requestId: value.requestId,
278
+ ok: value.ok === true,
279
+ ...error ? { error } : {}
280
+ };
281
+ };
282
+ const parseComposerChipKey = (value) => {
283
+ if (typeof value !== "string") return null;
284
+ const key = value.trim();
285
+ if (!key || key.length > 80) return null;
286
+ return key;
287
+ };
288
+ const parseWorkComposerChipSet = (value) => {
289
+ if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord(value.chip)) return null;
290
+ const key = parseComposerChipKey(value.chip.key);
291
+ if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
292
+ const label = value.chip.label.trim();
293
+ if (!label || label.length > 120) return null;
294
+ if (!value.chip.content.trim()) return null;
295
+ if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
296
+ return {
297
+ protocol: WORK_SURFACE_PROTOCOL,
298
+ version: 1,
299
+ type: "composer.chip.set",
300
+ chip: {
301
+ key,
302
+ label,
303
+ content: value.chip.content
304
+ }
305
+ };
306
+ };
307
+ const parseWorkComposerChipClear = (value) => {
308
+ if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
309
+ const key = parseComposerChipKey(value.key);
310
+ return key ? {
311
+ protocol: WORK_SURFACE_PROTOCOL,
312
+ version: 1,
313
+ type: "composer.chip.clear",
314
+ key
315
+ } : null;
316
+ };
317
+ const parseWorkSurfaceRequest = (value) => {
318
+ if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
319
+ if (typeof value.requestId !== "string" || !value.requestId) return null;
320
+ if (typeof value.method !== "string" || !value.method) return null;
321
+ const commandId = parseUiCommandId(value.commandId);
322
+ if (!commandId) return null;
323
+ return {
324
+ protocol: WORK_SURFACE_PROTOCOL,
325
+ version: 1,
326
+ type: "request",
327
+ requestId: value.requestId,
328
+ method: value.method,
329
+ ...value.input === void 0 ? {} : { input: value.input },
330
+ commandId
331
+ };
332
+ };
333
+ const buildWorkSurfaceReady = (methods) => ({
334
+ protocol: WORK_SURFACE_PROTOCOL,
335
+ version: 1,
336
+ type: "ready",
337
+ methods: [...methods]
79
338
  });
80
- const BoardAwarenessDrawPointSchema = BoardAwarenessPointSchema.extend({ p: finiteSchema$1.min(0).max(1) });
81
- const BoardAwarenessFrameSchema = z.object({
82
- x: finiteSchema$1,
83
- y: finiteSchema$1,
84
- width: finiteSchema$1.positive(),
85
- height: finiteSchema$1.positive(),
86
- rotation: finiteSchema$1
339
+ const buildWorkSurfaceRequest = (input) => ({
340
+ protocol: WORK_SURFACE_PROTOCOL,
341
+ version: 1,
342
+ type: "request",
343
+ ...input
87
344
  });
88
- const BoardAwarenessNodePreviewSchema = z.object({
89
- nodeId: idSchema$1,
90
- frame: BoardAwarenessFrameSchema,
91
- /** Live endpoints of a free arrow being dragged. */
92
- arrow: z.object({
93
- start: BoardAwarenessPointSchema,
94
- end: BoardAwarenessPointSchema,
95
- bend: finiteSchema$1
96
- }).optional()
345
+ const buildWorkSurfaceResponse = (input) => ({
346
+ protocol: WORK_SURFACE_PROTOCOL,
347
+ version: 1,
348
+ type: "response",
349
+ ...input
97
350
  });
98
- const BoardAwarenessStateUpdateSchema = z.object({
99
- type: z.literal("state"),
100
- client: z.object({ formFactor: z.enum(["desktop", "mobile"]) }).optional(),
101
- cursor: BoardAwarenessPointSchema.extend({ pointerType: z.enum([
102
- "mouse",
103
- "pen",
104
- "touch"
105
- ]) }).nullable(),
106
- tool: z.string().min(1).max(40),
107
- selection: z.object({
108
- ids: z.array(idSchema$1).max(64),
109
- count: z.number().int().nonnegative().max(5e4),
110
- bounds: BoardAwarenessFrameSchema.nullable()
111
- }),
112
- editingId: idSchema$1.nullable()
351
+ const buildWorkComposerChipSet = (chip) => ({
352
+ protocol: WORK_SURFACE_PROTOCOL,
353
+ version: 1,
354
+ type: "composer.chip.set",
355
+ chip
113
356
  });
114
- const BoardAwarenessGestureSchema = z.discriminatedUnion("kind", [
115
- z.object({
116
- kind: z.literal("draw"),
117
- id: idSchema$1,
118
- nodeId: idSchema$1,
119
- color: z.string().min(1).max(64),
120
- size: finiteSchema$1.positive().max(256),
121
- from: z.number().int().nonnegative().max(1e5),
122
- points: z.array(BoardAwarenessDrawPointSchema).min(1).max(64)
123
- }),
124
- z.object({
125
- kind: z.literal("arrow"),
126
- id: idSchema$1,
127
- nodeId: idSchema$1,
128
- start: BoardAwarenessPointSchema,
129
- current: BoardAwarenessPointSchema,
130
- color: z.string().min(1).max(64),
131
- size: finiteSchema$1.positive().max(256).default(BOARD_ARROW_STROKE_SIZE)
132
- }),
133
- z.object({
134
- kind: z.literal("box"),
135
- id: idSchema$1,
136
- nodeId: idSchema$1,
137
- shape: z.enum(["geo", "frame"]),
138
- start: BoardAwarenessPointSchema,
139
- current: BoardAwarenessPointSchema,
140
- color: z.string().min(1).max(64),
141
- geo: z.string().min(1).max(40)
142
- }),
143
- z.object({
144
- kind: z.literal("connection"),
145
- id: idSchema$1,
146
- sourceNodeId: idSchema$1,
147
- targetNodeId: idSchema$1.nullable(),
148
- current: BoardAwarenessPointSchema,
149
- color: z.string().min(1).max(64),
150
- size: finiteSchema$1.positive().max(256).default(BOARD_CONNECTION_STROKE_SIZE)
151
- }),
152
- z.object({
153
- kind: z.literal("transform"),
154
- id: idSchema$1,
155
- mode: z.enum([
156
- "translate",
157
- "resize",
158
- "rotate",
159
- "arrow",
160
- "connection"
161
- ]),
162
- nodes: z.array(BoardAwarenessNodePreviewSchema).max(64),
163
- bounds: BoardAwarenessFrameSchema.nullable()
164
- })
165
- ]);
166
- const BoardAwarenessUpdateSchema = z.discriminatedUnion("type", [
167
- BoardAwarenessStateUpdateSchema,
168
- z.object({
169
- type: z.literal("gesture"),
170
- gesture: BoardAwarenessGestureSchema
171
- }),
172
- z.object({
173
- type: z.literal("gesture.end"),
174
- gestureId: idSchema$1,
175
- resultingNodeIds: z.array(idSchema$1).max(64)
176
- }),
177
- z.object({
178
- type: z.literal("gesture.cancel"),
179
- gestureId: idSchema$1
180
- })
181
- ]);
182
- const BoardAwarenessClientPayloadSchema = z.object({
183
- spaceId: z.string().uuid(),
184
- boardId: z.string().uuid(),
185
- seq: z.number().int().nonnegative(),
186
- update: BoardAwarenessUpdateSchema
357
+ const buildWorkComposerChipClear = (key) => ({
358
+ protocol: WORK_SURFACE_PROTOCOL,
359
+ version: 1,
360
+ type: "composer.chip.clear",
361
+ key
187
362
  });
188
363
  //#endregion
364
+ //#region src/apis/billing.ts
365
+ var BillingApi = class {
366
+ transport;
367
+ constructor(transport) {
368
+ this.transport = transport;
369
+ }
370
+ async getCredits(input) {
371
+ const query = input?.tokenType ? `?tokenType=${encodeURIComponent(input.tokenType)}` : "";
372
+ return this.transport.request(`/api/billing/credits${query}`);
373
+ }
374
+ async getBalanceActivities(input) {
375
+ const params = new URLSearchParams();
376
+ if (input?.tokenType) params.set("tokenType", input.tokenType);
377
+ if (input?.page) params.set("page", String(input.page));
378
+ if (input?.limit) params.set("limit", String(input.limit));
379
+ const query = params.toString();
380
+ return this.transport.request(`/api/billing/balance-activities${query ? `?${query}` : ""}`);
381
+ }
382
+ async getCatalog() {
383
+ return this.transport.request("/api/billing/catalog");
384
+ }
385
+ async getFeatureEntitlement(featureKey) {
386
+ return this.transport.request(`/api/billing/features/${encodeURIComponent(featureKey)}`);
387
+ }
388
+ async getSubscriptions(input) {
389
+ const params = new URLSearchParams();
390
+ if (input?.page) params.set("page", String(input.page));
391
+ if (input?.limit) params.set("limit", String(input.limit));
392
+ const query = params.toString();
393
+ return this.transport.request(`/api/billing/subscriptions${query ? `?${query}` : ""}`);
394
+ }
395
+ async previewPromotionCode(input) {
396
+ return this.transport.request("/api/billing/promotion-code-preview", {
397
+ method: "POST",
398
+ body: JSON.stringify(input)
399
+ });
400
+ }
401
+ async createOrder(productKey, input) {
402
+ return this.transport.request("/api/billing/orders", {
403
+ method: "POST",
404
+ body: JSON.stringify({
405
+ ...input ?? {},
406
+ productKey
407
+ })
408
+ });
409
+ }
410
+ async createSubscription(productKey, input) {
411
+ return this.transport.request("/api/billing/subscriptions", {
412
+ method: "POST",
413
+ body: JSON.stringify({
414
+ ...input ?? {},
415
+ productKey
416
+ })
417
+ });
418
+ }
419
+ async cancelSubscriptionCheckout(subscriptionId) {
420
+ return this.transport.request(`/api/billing/subscriptions/${encodeURIComponent(subscriptionId)}/checkout`, { method: "DELETE" });
421
+ }
422
+ async cancelSubscriptionAutoRenew(subscriptionId) {
423
+ return this.transport.request(`/api/billing/subscriptions/${encodeURIComponent(subscriptionId)}`, {
424
+ method: "PATCH",
425
+ body: JSON.stringify({ cancelAtPeriodEnd: true })
426
+ });
427
+ }
428
+ async createRedemption(input) {
429
+ return this.transport.request("/api/billing/redemptions", {
430
+ method: "POST",
431
+ body: JSON.stringify(input)
432
+ });
433
+ }
434
+ };
435
+ //#endregion
189
436
  //#region ../protocol/dist/realtime/schema.js
190
437
  const contentBlockMetaSchema = z.record(z.string(), z.unknown());
191
438
  const realtimeRoomSchema = z.string().regex(/^(space|user|board|room):[^:]+$/);
@@ -796,145 +1043,31 @@ var WorkRealtimeApi = class {
796
1043
  }
797
1044
  };
798
1045
  //#endregion
799
- //#region ../protocol/dist/work-surface.js
800
- const WORK_SURFACE_PROTOCOL = "cohub.surface";
801
- const WORK_SURFACE_READY_TIMEOUT_MS = 1e4;
802
- const WORK_SURFACE_REQUEST_TIMEOUT_MS = 15e3;
803
- const WORK_COMPOSER_CHIP_KEY_MAX_LENGTH = 80;
804
- const WORK_COMPOSER_CHIP_LABEL_MAX_LENGTH = 120;
805
- const WORK_COMPOSER_CHIP_CONTENT_MAX_BYTES = 32 * 1024;
806
- const isRecord$1 = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
807
- const isSurfaceEnvelope = (value) => isRecord$1(value) && value.protocol === "cohub.surface" && value.version === 1;
808
- const parseWorkSurfaceReady = (value) => {
809
- if (!isSurfaceEnvelope(value) || value.type !== "ready") return null;
810
- const methods = Array.isArray(value.methods) ? value.methods.filter((method) => typeof method === "string" && Boolean(method)) : [];
811
- return {
812
- protocol: WORK_SURFACE_PROTOCOL,
813
- version: 1,
814
- type: "ready",
815
- methods
816
- };
817
- };
818
- const parseWorkSurfaceResponse = (value) => {
819
- if (!isSurfaceEnvelope(value) || value.type !== "response") return null;
820
- if (typeof value.requestId !== "string" || !value.requestId) return null;
821
- const error = isRecord$1(value.error) ? {
822
- code: typeof value.error.code === "string" && value.error.code ? value.error.code : "surface_error",
823
- message: typeof value.error.message === "string" ? value.error.message : "Work surface call failed"
824
- } : void 0;
825
- return {
826
- protocol: WORK_SURFACE_PROTOCOL,
827
- version: 1,
828
- type: "response",
829
- requestId: value.requestId,
830
- ok: value.ok === true,
831
- ...error ? { error } : {}
832
- };
833
- };
834
- const parseComposerChipKey = (value) => {
835
- if (typeof value !== "string") return null;
836
- const key = value.trim();
837
- if (!key || key.length > 80) return null;
838
- return key;
839
- };
840
- const parseWorkComposerChipSet = (value) => {
841
- if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.set" || !isRecord$1(value.chip)) return null;
842
- const key = parseComposerChipKey(value.chip.key);
843
- if (!key || typeof value.chip.label !== "string" || typeof value.chip.content !== "string") return null;
844
- const label = value.chip.label.trim();
845
- if (!label || label.length > 120) return null;
846
- if (!value.chip.content.trim()) return null;
847
- if (new TextEncoder().encode(value.chip.content).length > 32768) return null;
848
- return {
849
- protocol: WORK_SURFACE_PROTOCOL,
850
- version: 1,
851
- type: "composer.chip.set",
852
- chip: {
853
- key,
854
- label,
855
- content: value.chip.content
856
- }
857
- };
858
- };
859
- const parseWorkComposerChipClear = (value) => {
860
- if (!isSurfaceEnvelope(value) || value.type !== "composer.chip.clear") return null;
861
- const key = parseComposerChipKey(value.key);
862
- return key ? {
863
- protocol: WORK_SURFACE_PROTOCOL,
864
- version: 1,
865
- type: "composer.chip.clear",
866
- key
867
- } : null;
868
- };
869
- const parseWorkSurfaceRequest = (value) => {
870
- if (!isSurfaceEnvelope(value) || value.type !== "request") return null;
871
- if (typeof value.requestId !== "string" || !value.requestId) return null;
872
- if (typeof value.method !== "string" || !value.method) return null;
873
- const commandId = parseUiCommandId(value.commandId);
874
- if (!commandId) return null;
875
- return {
876
- protocol: WORK_SURFACE_PROTOCOL,
877
- version: 1,
878
- type: "request",
879
- requestId: value.requestId,
880
- method: value.method,
881
- ...value.input === void 0 ? {} : { input: value.input },
882
- commandId
883
- };
884
- };
885
- const buildWorkSurfaceReady = (methods) => ({
886
- protocol: WORK_SURFACE_PROTOCOL,
887
- version: 1,
888
- type: "ready",
889
- methods: [...methods]
890
- });
891
- const buildWorkSurfaceRequest = (input) => ({
892
- protocol: WORK_SURFACE_PROTOCOL,
893
- version: 1,
894
- type: "request",
895
- ...input
896
- });
897
- const buildWorkSurfaceResponse = (input) => ({
898
- protocol: WORK_SURFACE_PROTOCOL,
899
- version: 1,
900
- type: "response",
901
- ...input
902
- });
903
- const buildWorkComposerChipSet = (chip) => ({
904
- protocol: WORK_SURFACE_PROTOCOL,
905
- version: 1,
906
- type: "composer.chip.set",
907
- chip
908
- });
909
- const buildWorkComposerChipClear = (key) => ({
910
- protocol: WORK_SURFACE_PROTOCOL,
911
- version: 1,
912
- type: "composer.chip.clear",
913
- key
914
- });
915
- //#endregion
916
- //#region src/work-surface.ts
917
- /**
918
- * Explicit app origins, not a `*.cohub.run` suffix match: Works themselves are
919
- * served from Cohub subdomains, so a suffix match would let one Work call into
920
- * another and turn a subdomain takeover into surface access.
921
- */
922
- const COHUB_APP_ORIGINS = [
923
- "https://cohub.run",
924
- "https://www.cohub.run",
925
- "https://dev.cohub.run"
926
- ];
927
- const isCohubHostOrigin = (origin) => COHUB_APP_ORIGINS.includes(origin);
928
- const resolveEmbedderOrigin = () => {
929
- if (typeof window === "undefined" || window.parent === window) return null;
930
- const ancestor = window.location?.ancestorOrigins?.[0];
931
- if (typeof ancestor === "string" && ancestor) return ancestor;
932
- try {
933
- const referrer = typeof document === "undefined" ? "" : document.referrer;
934
- return referrer ? new URL(referrer).origin : null;
935
- } catch {
936
- return null;
937
- }
1046
+ //#region src/work-surface.ts
1047
+ /**
1048
+ * Explicit app origins, not a Cohub suffix match: Works themselves are
1049
+ * served from Cohub subdomains, so a suffix match would let one Work call into
1050
+ * another and turn a subdomain takeover into surface access.
1051
+ */
1052
+ const COHUB_APP_ORIGINS = [
1053
+ "https://cohub.live",
1054
+ "https://www.cohub.live",
1055
+ "https://dev.cohub.live",
1056
+ "https://cohub.run",
1057
+ "https://www.cohub.run",
1058
+ "https://dev.cohub.run"
1059
+ ];
1060
+ const isCohubHostOrigin = (origin) => COHUB_APP_ORIGINS.includes(origin);
1061
+ const resolveEmbedderOrigin = () => {
1062
+ if (typeof window === "undefined" || window.parent === window) return null;
1063
+ const ancestor = window.location?.ancestorOrigins?.[0];
1064
+ if (typeof ancestor === "string" && ancestor) return ancestor;
1065
+ try {
1066
+ const referrer = typeof document === "undefined" ? "" : document.referrer;
1067
+ return referrer ? new URL(referrer).origin : null;
1068
+ } catch {
1069
+ return null;
1070
+ }
938
1071
  };
939
1072
  var WorkSurfaceApi = class {
940
1073
  handlers = /* @__PURE__ */ new Map();
@@ -1607,55 +1740,6 @@ var CohubClient = class {
1607
1740
  };
1608
1741
  const createCohubClient = (options) => new CohubClient(options);
1609
1742
  //#endregion
1610
- //#region ../protocol/dist/public-identifiers.js
1611
- const USERNAME_PATTERN = /^(?!-)(?!.*--)[a-z0-9-]{1,39}(?<!-)$/;
1612
- const SPACE_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9_-]{0,78}[a-z0-9])?$/;
1613
- /**
1614
- * Platform-owned path segments that must not be newly assigned to public
1615
- * identities. Existing stored values remain readable through parse helpers.
1616
- */
1617
- const RESERVED_PLATFORM_PATH_SEGMENTS = Object.freeze([
1618
- "admin",
1619
- "api",
1620
- "assets",
1621
- "auth",
1622
- "callback",
1623
- "changelog",
1624
- "docs",
1625
- "explore",
1626
- "invite",
1627
- "landing",
1628
- "login",
1629
- "logout",
1630
- "new",
1631
- "org",
1632
- "pricing",
1633
- "pwa",
1634
- "referrals",
1635
- "sessions",
1636
- "settings",
1637
- "spaces",
1638
- "static",
1639
- "teams",
1640
- "trending",
1641
- "u",
1642
- "user",
1643
- "users",
1644
- "work-auth"
1645
- ]);
1646
- new Set(RESERVED_PLATFORM_PATH_SEGMENTS);
1647
- [...RESERVED_PLATFORM_PATH_SEGMENTS];
1648
- function parseUsername(value) {
1649
- if (value === null || value === void 0) return null;
1650
- const normalized = value.trim().toLowerCase();
1651
- return USERNAME_PATTERN.test(normalized) ? normalized : null;
1652
- }
1653
- function parseSpaceSlug(value) {
1654
- if (value === null || value === void 0) return null;
1655
- const normalized = value.trim();
1656
- return SPACE_SLUG_PATTERN.test(normalized) ? normalized : null;
1657
- }
1658
- //#endregion
1659
1743
  //#region src/work-ref.ts
1660
1744
  /**
1661
1745
  * Accepts every way a Work is named across Cohub — id, management URL, public
@@ -2166,440 +2250,6 @@ function createWorkBridgeCore(config) {
2166
2250
  };
2167
2251
  }
2168
2252
  //#endregion
2169
- //#region ../protocol/dist/generation/catalog.js
2170
- function isGenerationModelHidden(model) {
2171
- return model.hidden === true;
2172
- }
2173
- function filterDiscoverableGenerationModels(models, options = {}) {
2174
- const includeModelIds = new Set(options.includeModelIds);
2175
- return models.filter((model) => !isGenerationModelHidden(model) || includeModelIds.has(model.model));
2176
- }
2177
- //#endregion
2178
- //#region ../protocol/dist/generation/policy.js
2179
- const GENERATION_POLICY_ENV_KEY = "COHUB_GENERATION_POLICY_B64";
2180
- var GenerationPolicyError = class extends Error {
2181
- constructor(message) {
2182
- super(message);
2183
- this.name = "GenerationPolicyError";
2184
- }
2185
- };
2186
- function isRecord(value) {
2187
- return !!value && typeof value === "object" && !Array.isArray(value);
2188
- }
2189
- function isPrimitiveEnumValue(value) {
2190
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2191
- }
2192
- function normalizeConstraint(value) {
2193
- if (!isRecord(value) || typeof value.kind !== "string") return null;
2194
- if (value.kind === "enum") {
2195
- if (!Array.isArray(value.values) || value.values.length === 0 || !value.values.every(isPrimitiveEnumValue)) return null;
2196
- return {
2197
- kind: "enum",
2198
- values: value.values
2199
- };
2200
- }
2201
- if (value.kind === "number" || value.kind === "integer") {
2202
- const result = { kind: value.kind };
2203
- if (typeof value.min === "number" && Number.isFinite(value.min)) result.min = value.min;
2204
- if (typeof value.max === "number" && Number.isFinite(value.max)) result.max = value.max;
2205
- if (result.min !== void 0 && result.max !== void 0 && result.min > result.max) return null;
2206
- if (Array.isArray(value.values) && value.values.every((item) => typeof item === "number" && Number.isFinite(item))) {
2207
- if (value.kind === "integer" && !value.values.every(Number.isInteger)) return null;
2208
- const min = result.min;
2209
- const max = result.max;
2210
- if (min !== void 0 && value.values.some((item) => item < min)) return null;
2211
- if (max !== void 0 && value.values.some((item) => item > max)) return null;
2212
- result.values = value.values;
2213
- }
2214
- return result;
2215
- }
2216
- if (value.kind === "boolean") return typeof value.value === "boolean" ? {
2217
- kind: "boolean",
2218
- value: value.value
2219
- } : { kind: "boolean" };
2220
- return null;
2221
- }
2222
- function normalizeGenerationPolicy(value) {
2223
- if (!isRecord(value) || value.version !== 1) return null;
2224
- if (value.mode === "auto") return {
2225
- version: 1,
2226
- mode: "auto"
2227
- };
2228
- if (value.mode !== "limited" || !Array.isArray(value.models) || value.models.length === 0) return null;
2229
- const models = [];
2230
- for (const item of value.models) {
2231
- if (!isRecord(item) || typeof item.model !== "string" || !item.model.trim()) return null;
2232
- const modelPolicy = { model: item.model.trim() };
2233
- if (item.parameters !== void 0) {
2234
- if (!isRecord(item.parameters)) return null;
2235
- const parameters = {};
2236
- for (const [key, rawConstraint] of Object.entries(item.parameters)) {
2237
- if (!key.trim()) return null;
2238
- const constraint = normalizeConstraint(rawConstraint);
2239
- if (!constraint) return null;
2240
- parameters[key] = constraint;
2241
- }
2242
- if (Object.keys(parameters).length > 0) modelPolicy.parameters = parameters;
2243
- }
2244
- models.push(modelPolicy);
2245
- }
2246
- return {
2247
- version: 1,
2248
- mode: "limited",
2249
- models
2250
- };
2251
- }
2252
- function bytesToBase64(bytes) {
2253
- let binary = "";
2254
- for (const byte of bytes) binary += String.fromCharCode(byte);
2255
- return btoa(binary);
2256
- }
2257
- function base64ToBytes(value) {
2258
- const binary = atob(value);
2259
- const bytes = new Uint8Array(binary.length);
2260
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
2261
- return bytes;
2262
- }
2263
- function toBase64Url(value) {
2264
- return bytesToBase64(new TextEncoder().encode(value)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
2265
- }
2266
- function fromBase64Url(value) {
2267
- const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
2268
- const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
2269
- return new TextDecoder().decode(base64ToBytes(padded));
2270
- }
2271
- function encodeGenerationPolicy(policy) {
2272
- return toBase64Url(JSON.stringify(policy));
2273
- }
2274
- function decodeGenerationPolicy(value) {
2275
- try {
2276
- return normalizeGenerationPolicy(JSON.parse(fromBase64Url(value)));
2277
- } catch {
2278
- return null;
2279
- }
2280
- }
2281
- function parseGenerationPolicyFromEnv(env) {
2282
- const value = env[GENERATION_POLICY_ENV_KEY]?.trim();
2283
- return value ? decodeGenerationPolicy(value) : null;
2284
- }
2285
- function getAllowedGenerationModelIds(policy) {
2286
- if (!policy || policy.mode === "auto") return null;
2287
- return [...new Set((policy.models ?? []).map((item) => item.model))];
2288
- }
2289
- function findGenerationModelPolicy(policy, model) {
2290
- if (!policy || policy.mode === "auto") return null;
2291
- return policy.models?.find((item) => item.model === model) ?? null;
2292
- }
2293
- function formatList(values) {
2294
- return values.map((value) => `- ${String(value)}`).join("\n");
2295
- }
2296
- function modelNotSupportedMessage(allowedModels) {
2297
- return [
2298
- "This turn supports the following generation models:",
2299
- "",
2300
- formatList(allowedModels),
2301
- "",
2302
- "Choose one of these models or update Generation settings in the Models panel."
2303
- ].join("\n");
2304
- }
2305
- function valuesNotSupportedMessage(model, name, values, received) {
2306
- return [
2307
- `This turn supports selected values for ${model}.${name}:`,
2308
- "",
2309
- formatList(values),
2310
- "",
2311
- "Received:",
2312
- `- ${String(received)}`
2313
- ].join("\n");
2314
- }
2315
- function rangeNotSupportedMessage(model, name, min, max, received) {
2316
- return [
2317
- `This turn supports ${model}.${name} in the range ${min !== void 0 && max !== void 0 ? `${min}–${max}` : min !== void 0 ? `at least ${min}` : `at most ${max}`}.`,
2318
- "",
2319
- "Received:",
2320
- `- ${String(received)}`
2321
- ].join("\n");
2322
- }
2323
- function booleanNotSupportedMessage(model, name, expected, received) {
2324
- return [
2325
- `This turn supports ${model}.${name}=${String(expected)}.`,
2326
- "",
2327
- "Received:",
2328
- `- ${String(received)}`
2329
- ].join("\n");
2330
- }
2331
- function sameEnumValue(a, b) {
2332
- return typeof a === typeof b && a === b;
2333
- }
2334
- function validateConstraint(input) {
2335
- const { model, name, value, constraint } = input;
2336
- if (constraint.kind === "enum") {
2337
- if (!constraint.values.some((item) => sameEnumValue(item, value))) throw new GenerationPolicyError(valuesNotSupportedMessage(model, name, constraint.values, value));
2338
- return;
2339
- }
2340
- if (constraint.kind === "number" || constraint.kind === "integer") {
2341
- if (typeof value !== "number" || !Number.isFinite(value)) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
2342
- if (constraint.kind === "integer" && !Number.isInteger(value)) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
2343
- if (constraint.values?.length && !constraint.values.includes(value)) throw new GenerationPolicyError(valuesNotSupportedMessage(model, name, constraint.values, value));
2344
- if (constraint.min !== void 0 && value < constraint.min) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
2345
- if (constraint.max !== void 0 && value > constraint.max) throw new GenerationPolicyError(rangeNotSupportedMessage(model, name, constraint.min, constraint.max, value));
2346
- return;
2347
- }
2348
- if (constraint.kind === "boolean" && constraint.value !== void 0 && value !== constraint.value) throw new GenerationPolicyError(booleanNotSupportedMessage(model, name, constraint.value, value));
2349
- }
2350
- function assertGenerationRequestAllowedByPolicy(input) {
2351
- const allowedModels = getAllowedGenerationModelIds(input.policy);
2352
- if (!allowedModels) return;
2353
- const modelPolicy = findGenerationModelPolicy(input.policy, input.model);
2354
- if (!modelPolicy) throw new GenerationPolicyError(modelNotSupportedMessage(allowedModels));
2355
- const parameters = input.parameters ?? {};
2356
- for (const [name, value] of Object.entries(parameters)) {
2357
- const constraint = modelPolicy.parameters?.[name];
2358
- if (!constraint) continue;
2359
- validateConstraint({
2360
- model: input.model,
2361
- name,
2362
- value,
2363
- constraint
2364
- });
2365
- }
2366
- }
2367
- function cloneDeclaration(declaration) {
2368
- return {
2369
- ...declaration,
2370
- content: {
2371
- ...declaration.content,
2372
- input: [...declaration.content.input]
2373
- },
2374
- parameters: declaration.parameters ? Object.fromEntries(Object.entries(declaration.parameters).map(([key, value]) => [key, { ...value }])) : declaration.parameters,
2375
- examples: declaration.examples ? [...declaration.examples] : declaration.examples
2376
- };
2377
- }
2378
- function narrowParameterSpec(spec, constraint) {
2379
- const next = { ...spec };
2380
- if (constraint.kind === "enum") {
2381
- if ("enum" in next && Array.isArray(next.enum)) next.enum = next.enum.filter((value) => constraint.values.some((allowed) => sameEnumValue(allowed, value)));
2382
- return next;
2383
- }
2384
- if (constraint.kind === "number" || constraint.kind === "integer") {
2385
- if ("min" in next && typeof next.min === "number" && constraint.min !== void 0) next.min = Math.max(next.min, constraint.min);
2386
- else if ("min" in next && constraint.min !== void 0) next.min = constraint.min;
2387
- if ("max" in next && typeof next.max === "number" && constraint.max !== void 0) next.max = Math.min(next.max, constraint.max);
2388
- else if ("max" in next && constraint.max !== void 0) next.max = constraint.max;
2389
- if (constraint.values?.length && "enum" in next && Array.isArray(next.enum)) next.enum = next.enum.filter((value) => constraint.values?.some((allowed) => sameEnumValue(allowed, value)));
2390
- }
2391
- return next;
2392
- }
2393
- function filterGenerationDeclarationsByPolicy(declarations, policy) {
2394
- const allowedModels = getAllowedGenerationModelIds(policy);
2395
- if (!allowedModels) return declarations;
2396
- const allowedSet = new Set(allowedModels);
2397
- return declarations.filter((declaration) => allowedSet.has(declaration.model)).map((declaration) => {
2398
- const cloned = cloneDeclaration(declaration);
2399
- const modelPolicy = findGenerationModelPolicy(policy, declaration.model);
2400
- if (!modelPolicy?.parameters || !cloned.parameters) return cloned;
2401
- for (const [name, constraint] of Object.entries(modelPolicy.parameters)) {
2402
- const spec = cloned.parameters[name];
2403
- if (spec) cloned.parameters[name] = narrowParameterSpec(spec, constraint);
2404
- }
2405
- return cloned;
2406
- });
2407
- }
2408
- //#endregion
2409
- //#region ../protocol/dist/board.js
2410
- const BOARD_MANIFEST_KIND = "cohub.board.manifest";
2411
- const idSchema = z.string().min(1).max(160);
2412
- const extensionIdSchema = z.string().regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/).max(160);
2413
- const jsonObjectSchema = z.record(z.string(), z.unknown());
2414
- const finiteSchema = z.number().finite();
2415
- z.object({
2416
- kind: z.literal(BOARD_MANIFEST_KIND),
2417
- version: z.literal(1),
2418
- boardId: z.string().uuid(),
2419
- title: z.string().min(1).max(255)
2420
- });
2421
- const BoardTargetSchema = z.discriminatedUnion("type", [
2422
- z.object({
2423
- type: z.literal("node"),
2424
- nodeId: idSchema
2425
- }),
2426
- z.object({
2427
- type: z.literal("effect"),
2428
- effectId: idSchema
2429
- }),
2430
- z.object({ type: z.literal("board") }),
2431
- z.object({ type: z.literal("camera") })
2432
- ]);
2433
- const BoardAssetRefSchema = z.object({
2434
- type: z.enum(["space-file", "extension"]),
2435
- ref: z.string().min(1).max(4096),
2436
- digest: z.string().min(16).max(160).optional()
2437
- });
2438
- const BoardKeyframeSchema = z.object({
2439
- at: finiteSchema.nonnegative(),
2440
- value: z.unknown(),
2441
- easing: z.string().min(1).max(80).optional()
2442
- });
2443
- const BoardClipSchema = z.object({
2444
- id: idSchema,
2445
- sequenceId: idSchema,
2446
- kind: extensionIdSchema,
2447
- kindVersion: z.number().int().positive(),
2448
- target: BoardTargetSchema,
2449
- start: finiteSchema.nonnegative(),
2450
- duration: finiteSchema.positive(),
2451
- layer: z.enum([
2452
- "behind",
2453
- "content",
2454
- "front",
2455
- "screen"
2456
- ]).default("content"),
2457
- fill: z.enum([
2458
- "none",
2459
- "backwards",
2460
- "forwards",
2461
- "both"
2462
- ]).default("none"),
2463
- easing: z.string().min(1).max(80).default("linear"),
2464
- params: jsonObjectSchema.default({}),
2465
- keyframes: z.array(BoardKeyframeSchema).default([]),
2466
- assetRefs: z.array(BoardAssetRefSchema).default([]),
2467
- seed: z.string().min(1).max(160),
2468
- metadata: jsonObjectSchema.default({})
2469
- });
2470
- const BoardEffectSchema = z.object({
2471
- id: idSchema,
2472
- boardId: z.string().uuid(),
2473
- target: z.discriminatedUnion("type", [z.object({
2474
- type: z.literal("node"),
2475
- nodeId: idSchema
2476
- }), z.object({ type: z.literal("board") })]),
2477
- kind: extensionIdSchema,
2478
- kindVersion: z.number().int().positive(),
2479
- enabled: z.boolean().default(true),
2480
- lifecycle: z.enum([
2481
- "persistent",
2482
- "when-visible",
2483
- "manual"
2484
- ]),
2485
- timeOrigin: z.enum([
2486
- "board",
2487
- "visible",
2488
- "activation"
2489
- ]),
2490
- layer: z.enum([
2491
- "behind",
2492
- "front",
2493
- "screen"
2494
- ]).default("front"),
2495
- seed: z.string().min(1).max(160),
2496
- params: jsonObjectSchema.default({}),
2497
- assetRefs: z.array(BoardAssetRefSchema).default([]),
2498
- metadata: jsonObjectSchema.default({}),
2499
- revision: z.number().int().nonnegative()
2500
- });
2501
- const BoardSequenceSchema = z.object({
2502
- id: idSchema,
2503
- boardId: z.string().uuid(),
2504
- name: z.string().min(1).max(255),
2505
- duration: finiteSchema.nonnegative(),
2506
- seed: z.string().min(1).max(160),
2507
- restPose: jsonObjectSchema.default({}),
2508
- metadata: jsonObjectSchema.default({}),
2509
- revision: z.number().int().nonnegative()
2510
- });
2511
- const BoardNodeInputSchema = z.object({
2512
- nodeId: idSchema,
2513
- type: z.string().min(1).max(40),
2514
- parentId: idSchema.nullable(),
2515
- orderKey: z.string().max(4096).nullable(),
2516
- x: finiteSchema,
2517
- y: finiteSchema,
2518
- width: finiteSchema.positive(),
2519
- height: finiteSchema.positive(),
2520
- rotation: finiteSchema,
2521
- refKind: z.string().max(40).nullable(),
2522
- refPath: z.string().max(4096).nullable(),
2523
- refUrl: z.string().max(4096).nullable(),
2524
- view: jsonObjectSchema,
2525
- style: jsonObjectSchema,
2526
- data: jsonObjectSchema
2527
- });
2528
- z.object({
2529
- path: z.string().min(1),
2530
- /** Reused by clients when board creation is interrupted and retried. */
2531
- mutationId: z.string().max(128).optional(),
2532
- title: z.string().min(1).max(255).optional(),
2533
- metadata: jsonObjectSchema.optional(),
2534
- nodes: z.array(BoardNodeInputSchema).max(5e4).optional(),
2535
- connections: z.array(BoardConnectionSchema).max(5e4).optional(),
2536
- effects: z.array(BoardEffectSchema.omit({
2537
- boardId: true,
2538
- revision: true
2539
- })).optional(),
2540
- sequences: z.array(z.object({
2541
- sequence: BoardSequenceSchema.omit({
2542
- boardId: true,
2543
- revision: true
2544
- }),
2545
- clips: z.array(BoardClipSchema.omit({ sequenceId: true }))
2546
- })).optional()
2547
- });
2548
- z.object({
2549
- include: z.array(z.enum([
2550
- "nodes",
2551
- "connections",
2552
- "effects",
2553
- "sequences",
2554
- "clips",
2555
- "playback"
2556
- ])).optional(),
2557
- viewport: z.object({
2558
- x: finiteSchema,
2559
- y: finiteSchema,
2560
- width: finiteSchema.positive(),
2561
- height: finiteSchema.positive()
2562
- }).optional()
2563
- });
2564
- /** Persisted on `boards.metadata.playback`: how a Board plays when opened. */
2565
- const BoardPlaybackPolicySchema = z.object({
2566
- sequenceId: idSchema,
2567
- /** Delay before the first local playback after opening the Board, in milliseconds. */
2568
- delayMs: finiteSchema.nonnegative().default(0),
2569
- loop: z.boolean().default(false)
2570
- });
2571
- function parseBoardPlaybackPolicy(metadata) {
2572
- const parsed = BoardPlaybackPolicySchema.safeParse(metadata.playback);
2573
- return parsed.success ? parsed.data : null;
2574
- }
2575
- z.discriminatedUnion("type", [
2576
- z.object({
2577
- commandId: idSchema,
2578
- type: z.literal("play"),
2579
- sequenceId: idSchema,
2580
- position: finiteSchema.nonnegative().optional(),
2581
- timeScale: finiteSchema.positive().max(4).optional(),
2582
- shared: z.boolean().optional(),
2583
- seed: idSchema.optional()
2584
- }),
2585
- z.object({
2586
- commandId: idSchema,
2587
- type: z.literal("pause"),
2588
- playbackId: z.string().uuid()
2589
- }),
2590
- z.object({
2591
- commandId: idSchema,
2592
- type: z.literal("seek"),
2593
- playbackId: z.string().uuid(),
2594
- position: finiteSchema.nonnegative()
2595
- }),
2596
- z.object({
2597
- commandId: idSchema,
2598
- type: z.literal("stop"),
2599
- playbackId: z.string().uuid()
2600
- })
2601
- ]);
2602
- //#endregion
2603
2253
  //#region src/board/animation.ts
2604
2254
  const ZERO_COST = {
2605
2255
  particles: 0,
@@ -2951,4 +2601,4 @@ function createBoardExtensionRegistry(input = {}) {
2951
2601
  return registry;
2952
2602
  }
2953
2603
  //#endregion
2954
- export { BILLING_ACCESS_BLOCKED_ERROR_CODE, BillingApi, BoardClient, BoardExtensionRegistry, BoardPlaybackPolicySchema, BoardTransactionError, 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, 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, clip, compileSequence, 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, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAssistantMessageCommit, parseBoardPlaybackPolicy, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseUiCommand, parseWorkRef, parseWorkSurfaceReady, parseWorkSurfaceResponse, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveCohubEnvironment, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, resolveWorkTransport, sanitizeAccessToken, setGrantedWorkScopes, timeline };
2604
+ export { BILLING_ACCESS_BLOCKED_ERROR_CODE, BOARD_COLOR_IDS, BOARD_GEO_KINDS, BOARD_NATIVE_NODE_TYPES, BOARD_NODE_CONTRACT, BillingApi, BoardClient, BoardExtensionRegistry, BoardInputError, BoardPlaybackPolicySchema, BoardTransactionError, 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, assertBoardNodes, assertGenerationRequestAllowedByPolicy, buildSpaceInvitePath, buildSpacePath, buildWorkSurfaceRequest, clearGrantedWorkScopes, clip, compileSequence, createBoardExtensionRegistry, createBoardNode, 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, isWorkId, joinApiUrl, matchesUnauthorizedErrorToken, mergeRequestSourceIntoMeta, normalizeBaseUrl, normalizeGenerationPolicy, normalizeRequestSource, normalizeVoiceInputWebsocketUrl, normalizeWebsocketUrl, parseAssistantMessageCommit, parseBoardPlaybackPolicy, parseGenerationPolicyFromEnv, parseRequestSourceFromHeaders, parseUiCommand, parseWorkRef, parseWorkSurfaceReady, parseWorkSurfaceResponse, readRequestSourceFromEnv, requestSourceToHeaders, resolveApiBaseUrl, resolveCohubEnvironment, resolveRequestSourceChannel, resolveVoiceInputWebsocketUrl, resolveWebsocketUrl, resolveWorkTransport, sanitizeAccessToken, setGrantedWorkScopes, timeline, validateBoardNodeInput, validateBoardNodes };