@nodetool-ai/agents 0.8.2 → 0.8.3
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/dist/.nodetool-build-stamp +1 -1
- package/dist/apify/assets.d.ts.map +1 -1
- package/dist/apify/assets.js +4 -0
- package/dist/apify/assets.js.map +1 -1
- package/dist/capabilities/agent-activity.js +1 -1
- package/dist/capabilities/assets.d.ts.map +1 -1
- package/dist/capabilities/assets.js +40 -10
- package/dist/capabilities/assets.js.map +1 -1
- package/dist/capabilities/collections.d.ts +4 -6
- package/dist/capabilities/collections.d.ts.map +1 -1
- package/dist/capabilities/collections.js +16 -11
- package/dist/capabilities/collections.js.map +1 -1
- package/dist/capabilities/derive-shot.d.ts +45 -0
- package/dist/capabilities/derive-shot.d.ts.map +1 -0
- package/dist/capabilities/derive-shot.js +82 -0
- package/dist/capabilities/derive-shot.js.map +1 -0
- package/dist/capabilities/documents.d.ts +2 -1
- package/dist/capabilities/documents.d.ts.map +1 -1
- package/dist/capabilities/documents.js +12 -6
- package/dist/capabilities/documents.js.map +1 -1
- package/dist/capabilities/email.d.ts +2 -1
- package/dist/capabilities/email.d.ts.map +1 -1
- package/dist/capabilities/email.js +5 -3
- package/dist/capabilities/email.js.map +1 -1
- package/dist/capabilities/game-asset-browser.d.ts +11 -0
- package/dist/capabilities/game-asset-browser.d.ts.map +1 -0
- package/dist/capabilities/game-asset-browser.js +152 -0
- package/dist/capabilities/game-asset-browser.js.map +1 -0
- package/dist/capabilities/game.d.ts +30 -0
- package/dist/capabilities/game.d.ts.map +1 -1
- package/dist/capabilities/game.js +215 -137
- package/dist/capabilities/game.js.map +1 -1
- package/dist/capabilities/game.specs.d.ts +2 -0
- package/dist/capabilities/game.specs.d.ts.map +1 -1
- package/dist/capabilities/game.specs.js +20 -5
- package/dist/capabilities/game.specs.js.map +1 -1
- package/dist/capabilities/game3d.d.ts.map +1 -1
- package/dist/capabilities/game3d.js +4 -3
- package/dist/capabilities/game3d.js.map +1 -1
- package/dist/capabilities/generations.d.ts.map +1 -1
- package/dist/capabilities/generations.js +7 -21
- package/dist/capabilities/generations.js.map +1 -1
- package/dist/capabilities/lazy-tool-core.d.ts +23 -0
- package/dist/capabilities/lazy-tool-core.d.ts.map +1 -0
- package/dist/capabilities/lazy-tool-core.js +56 -0
- package/dist/capabilities/lazy-tool-core.js.map +1 -0
- package/dist/capabilities/lazy-tool.d.ts +2 -21
- package/dist/capabilities/lazy-tool.d.ts.map +1 -1
- package/dist/capabilities/lazy-tool.js +3 -55
- package/dist/capabilities/lazy-tool.js.map +1 -1
- package/dist/capabilities/legacy-tools.js +1 -1
- package/dist/capabilities/legacy-tools.js.map +1 -1
- package/dist/capabilities/media.d.ts.map +1 -1
- package/dist/capabilities/media.js +13 -1
- package/dist/capabilities/media.js.map +1 -1
- package/dist/capabilities/media.specs.d.ts.map +1 -1
- package/dist/capabilities/media.specs.js +17 -0
- package/dist/capabilities/media.specs.js.map +1 -1
- package/dist/capabilities/metadata.d.ts +62 -0
- package/dist/capabilities/metadata.d.ts.map +1 -0
- package/dist/capabilities/metadata.js +114 -0
- package/dist/capabilities/metadata.js.map +1 -0
- package/dist/capabilities/model3d.d.ts.map +1 -1
- package/dist/capabilities/model3d.js +4 -0
- package/dist/capabilities/model3d.js.map +1 -1
- package/dist/capabilities/registry.d.ts +4 -19
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js +48 -117
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/capabilities/run-agent.js +1 -1
- package/dist/capabilities/settings.d.ts.map +1 -1
- package/dist/capabilities/settings.js +4 -2
- package/dist/capabilities/settings.js.map +1 -1
- package/dist/capabilities/storyboards.d.ts +11 -1
- package/dist/capabilities/storyboards.d.ts.map +1 -1
- package/dist/capabilities/storyboards.js +466 -4
- package/dist/capabilities/storyboards.js.map +1 -1
- package/dist/capabilities/storyboards.specs.d.ts +6 -0
- package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
- package/dist/capabilities/storyboards.specs.js +131 -2
- package/dist/capabilities/storyboards.specs.js.map +1 -1
- package/dist/capabilities/threads.js +1 -1
- package/dist/capabilities/threads.js.map +1 -1
- package/dist/capabilities/timeline-bridge.d.ts +9 -0
- package/dist/capabilities/timeline-bridge.d.ts.map +1 -1
- package/dist/capabilities/timeline-bridge.js +60 -68
- package/dist/capabilities/timeline-bridge.js.map +1 -1
- package/dist/capabilities/timeline-operations.d.ts +52 -0
- package/dist/capabilities/timeline-operations.d.ts.map +1 -0
- package/dist/capabilities/timeline-operations.js +413 -0
- package/dist/capabilities/timeline-operations.js.map +1 -0
- package/dist/capabilities/timelines.d.ts +3 -38
- package/dist/capabilities/timelines.d.ts.map +1 -1
- package/dist/capabilities/timelines.js +30 -413
- package/dist/capabilities/timelines.js.map +1 -1
- package/dist/capabilities/timelines.specs.d.ts.map +1 -1
- package/dist/capabilities/timelines.specs.js +5 -1
- package/dist/capabilities/timelines.specs.js.map +1 -1
- package/dist/capabilities/web.d.ts.map +1 -1
- package/dist/capabilities/web.js +4 -0
- package/dist/capabilities/web.js.map +1 -1
- package/dist/capabilities/workflows.d.ts +3 -1
- package/dist/capabilities/workflows.d.ts.map +1 -1
- package/dist/capabilities/workflows.js +192 -14
- package/dist/capabilities/workflows.js.map +1 -1
- package/dist/capabilities/workflows.specs.d.ts +2 -0
- package/dist/capabilities/workflows.specs.d.ts.map +1 -1
- package/dist/capabilities/workflows.specs.js +65 -1
- package/dist/capabilities/workflows.specs.js.map +1 -1
- package/dist/codeact/sandbox-packages.d.ts +0 -7
- package/dist/codeact/sandbox-packages.d.ts.map +1 -1
- package/dist/codeact/sandbox-packages.js +1 -10
- package/dist/codeact/sandbox-packages.js.map +1 -1
- package/dist/evals/surfaces/game.d.ts +8 -2
- package/dist/evals/surfaces/game.d.ts.map +1 -1
- package/dist/evals/surfaces/game.js +460 -2
- package/dist/evals/surfaces/game.js.map +1 -1
- package/dist/game-authoring.d.ts +4895 -59
- package/dist/game-authoring.d.ts.map +1 -1
- package/dist/game-build.d.ts +3 -0
- package/dist/game-build.d.ts.map +1 -0
- package/dist/game-build.js +3 -0
- package/dist/game-build.js.map +1 -0
- package/dist/game-examples.d.ts +1355 -2
- package/dist/game-examples.d.ts.map +1 -1
- package/dist/generation-cancel.d.ts +31 -0
- package/dist/generation-cancel.d.ts.map +1 -0
- package/dist/generation-cancel.js +28 -0
- package/dist/generation-cancel.js.map +1 -0
- package/dist/host-access-gate.d.ts +29 -0
- package/dist/host-access-gate.d.ts.map +1 -0
- package/dist/host-access-gate.js +39 -0
- package/dist/host-access-gate.js.map +1 -0
- package/dist/host-modules/dispatcher.d.ts +7 -1
- package/dist/host-modules/dispatcher.d.ts.map +1 -1
- package/dist/host-modules/dispatcher.js +13 -2
- package/dist/host-modules/dispatcher.js.map +1 -1
- package/dist/host-modules/docx.d.ts.map +1 -1
- package/dist/host-modules/docx.js +2 -2
- package/dist/host-modules/docx.js.map +1 -1
- package/dist/host-modules/epub.d.ts.map +1 -1
- package/dist/host-modules/epub.js +2 -2
- package/dist/host-modules/epub.js.map +1 -1
- package/dist/host-modules/fabric.d.ts.map +1 -1
- package/dist/host-modules/fabric.js +1 -1
- package/dist/host-modules/fabric.js.map +1 -1
- package/dist/host-modules/limits.d.ts +10 -8
- package/dist/host-modules/limits.d.ts.map +1 -1
- package/dist/host-modules/limits.js +14 -9
- package/dist/host-modules/limits.js.map +1 -1
- package/dist/host-modules/mammoth.d.ts.map +1 -1
- package/dist/host-modules/mammoth.js +2 -2
- package/dist/host-modules/mammoth.js.map +1 -1
- package/dist/host-modules/ocr.d.ts.map +1 -1
- package/dist/host-modules/ocr.js +1 -1
- package/dist/host-modules/ocr.js.map +1 -1
- package/dist/host-modules/pdf.d.ts.map +1 -1
- package/dist/host-modules/pdf.js +2 -2
- package/dist/host-modules/pdf.js.map +1 -1
- package/dist/host-modules/pdflib.d.ts.map +1 -1
- package/dist/host-modules/pdflib.js +2 -2
- package/dist/host-modules/pdflib.js.map +1 -1
- package/dist/host-modules/pptx.d.ts.map +1 -1
- package/dist/host-modules/pptx.js +2 -2
- package/dist/host-modules/pptx.js.map +1 -1
- package/dist/host-modules/pptxgen.d.ts.map +1 -1
- package/dist/host-modules/pptxgen.js +2 -2
- package/dist/host-modules/pptxgen.js.map +1 -1
- package/dist/host-modules/tfjs.d.ts.map +1 -1
- package/dist/host-modules/tfjs.js +4 -4
- package/dist/host-modules/tfjs.js.map +1 -1
- package/dist/host-modules/xlsx.d.ts.map +1 -1
- package/dist/host-modules/xlsx.js +2 -2
- package/dist/host-modules/xlsx.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/js-sandbox.d.ts +7 -1
- package/dist/js-sandbox.d.ts.map +1 -1
- package/dist/js-sandbox.js +20 -4
- package/dist/js-sandbox.js.map +1 -1
- package/dist/timeline-code-bake.js +2 -2
- package/dist/timeline-code-bake.js.map +1 -1
- package/dist/tools/builtin-tools.d.ts +4 -3
- package/dist/tools/builtin-tools.d.ts.map +1 -1
- package/dist/tools/builtin-tools.js +11 -3
- package/dist/tools/builtin-tools.js.map +1 -1
- package/package.json +122 -41
|
@@ -2,7 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createLogger, getManagedWorkspaceDir, managedWorkspaceKey, workspaceStorageKind } from "@nodetool-ai/config";
|
|
5
|
-
import { AmbiguousGameIdError, Asset, Game, Prediction, Project, Workspace } from "@nodetool-ai/models";
|
|
5
|
+
import { AmbiguousGameIdError, MissingGameDraftSourceError, Asset, Game, Prediction, Project, Workspace } from "@nodetool-ai/models";
|
|
6
6
|
import { anyGameAssetBinding as gameAssetBinding, gameAssetBinding as legacyAssetBinding, gameAssetBinding3D, anyGameDocument as gameDocument, gameInputFrame, shortResourceId } from "@nodetool-ai/protocol";
|
|
7
7
|
import { autoplayNativeGame, MAX_GAME_ROUTE_TICKS, createScriptedGameSession, createTopDownRoomGame, createNative3DGame, decodePreparedGameCollider3D, validateAnyGame, trackGameAuthoringEdits, anyGameDocumentOp as gameDocumentOp, GameOpError } from "@nodetool-ai/game-runtime";
|
|
8
8
|
import { workspaceFromRow } from "@nodetool-ai/execution/service";
|
|
@@ -13,7 +13,8 @@ import { persistOutput } from "../tools/asset-persist.js";
|
|
|
13
13
|
import { getExampleGameBundle, installExampleGameAssets, listExampleGames } from "../game-examples.js";
|
|
14
14
|
import { previewGameAuthoring, applyGameAuthoring } from "../game-authoring.js";
|
|
15
15
|
import { gameDocumentDigest } from "../game-code-bake.js";
|
|
16
|
-
import { previewGameAuthoringSpec, applyGameAuthoringSpec } from "./game.specs.js";
|
|
16
|
+
import { previewGameAuthoringSpec, applyGameAuthoringSpec, browseGameAssetsSpec } from "./game.specs.js";
|
|
17
|
+
import { browseGameAssets } from "./game-asset-browser.js";
|
|
17
18
|
const log = createLogger("capabilities.game");
|
|
18
19
|
const MAX_PLAYTEST_TICKS = MAX_GAME_ROUTE_TICKS;
|
|
19
20
|
const MAX_CAPTURE_FRAMES = 8;
|
|
@@ -121,7 +122,8 @@ function outline(document) {
|
|
|
121
122
|
inputActions: document.inputActions,
|
|
122
123
|
collisionLayers: document.collisionLayers,
|
|
123
124
|
renderEffects: document.renderEffects,
|
|
124
|
-
hudEffectOrder: document.hudEffectOrder
|
|
125
|
+
hudEffectOrder: document.hudEffectOrder,
|
|
126
|
+
audio: document.audio
|
|
125
127
|
},
|
|
126
128
|
scenes: document.scenes.map((scene) => ({
|
|
127
129
|
id: scene.id,
|
|
@@ -184,6 +186,8 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
|
|
|
184
186
|
return cancelled || wallTimeLimited;
|
|
185
187
|
};
|
|
186
188
|
const { captureGameFrame } = await import("@nodetool-ai/game-renderer/node");
|
|
189
|
+
const { GameParticles2D } = await import("@nodetool-ai/game-renderer");
|
|
190
|
+
const particles = new GameParticles2D(document.tickRate);
|
|
187
191
|
const diagnostics = [];
|
|
188
192
|
const storage = run.context.assetStorage;
|
|
189
193
|
const assetCache = new Map();
|
|
@@ -221,7 +225,9 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
|
|
|
221
225
|
if (budgetReached()) {
|
|
222
226
|
break;
|
|
223
227
|
}
|
|
224
|
-
|
|
228
|
+
const step = session.step(inputs[simulatedTicks] ?? { pressed: [], justPressed: [] });
|
|
229
|
+
particles.tick(step.frame, session.takePresentationEvents());
|
|
230
|
+
simulatedTicks = step.tick;
|
|
225
231
|
}
|
|
226
232
|
if (budgetReached()) {
|
|
227
233
|
break;
|
|
@@ -238,11 +244,11 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
|
|
|
238
244
|
const start = diagnostics.length;
|
|
239
245
|
let png;
|
|
240
246
|
try {
|
|
241
|
-
png = await captureGameFrame(frame, { resolveAsset, scale, effects: document.renderEffects, hudEffectOrder: document.hudEffectOrder, backend: document.renderEffects?.length ? "webgpu" : "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
|
|
247
|
+
png = await captureGameFrame(frame, { resolveAsset, scale, particles, effects: document.renderEffects, hudEffectOrder: document.hudEffectOrder, backend: document.renderEffects?.length ? "webgpu" : "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
|
|
242
248
|
}
|
|
243
249
|
catch (error) {
|
|
244
250
|
diagnostics.push(`WebGPU capture failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
245
|
-
png = await captureGameFrame(frame, { resolveAsset, scale, backend: "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
|
|
251
|
+
png = await captureGameFrame(frame, { resolveAsset, scale, particles, backend: "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
|
|
246
252
|
}
|
|
247
253
|
if (budgetReached()) {
|
|
248
254
|
break;
|
|
@@ -415,7 +421,7 @@ export async function createNativeGame(user, projectId, name, source, dimension
|
|
|
415
421
|
return { error: "Project workspace is unavailable" };
|
|
416
422
|
const id = randomUUID().replace(/-/g, "");
|
|
417
423
|
const revision = randomUUID().replace(/-/g, "");
|
|
418
|
-
const document = validateSource(source ?? (dimension === "3d" ? createNative3DGame(id) : { ...createTopDownRoomGame(id), schemaVersion:
|
|
424
|
+
const document = validateSource(source ?? (dimension === "3d" ? createNative3DGame(id) : { ...createTopDownRoomGame(id), schemaVersion: 4, engineVersion: "3" }), id, revision);
|
|
419
425
|
if ("error" in document)
|
|
420
426
|
return document;
|
|
421
427
|
if (source && ((document.schemaVersion === 3) !== (dimension === "3d"))) {
|
|
@@ -559,6 +565,9 @@ const install = {
|
|
|
559
565
|
if (parsed.data.mediaKind === "font" && !parsed.data.fontFormat) {
|
|
560
566
|
return { error: "Font binding needs a TrueType or OpenType format" };
|
|
561
567
|
}
|
|
568
|
+
if (parsed.data.mediaKind === "hdri") {
|
|
569
|
+
return { error: "HDRI candidates cannot be installed until HDRI preparation verifies their bytes and dimensions" };
|
|
570
|
+
}
|
|
562
571
|
const workspace = await workspaceOf(user, game);
|
|
563
572
|
if (!workspace)
|
|
564
573
|
return { error: "Game workspace is unavailable" };
|
|
@@ -1085,7 +1094,7 @@ const generateAsset = {
|
|
|
1085
1094
|
if (prepared.baseline !== undefined && frames?.[0]) {
|
|
1086
1095
|
binding.pivot = { x: 0.5, y: (prepared.baseline + 1) / frames[0].height };
|
|
1087
1096
|
}
|
|
1088
|
-
if (draft.document.schemaVersion
|
|
1097
|
+
if (draft.document.schemaVersion !== 1) {
|
|
1089
1098
|
binding.preparation = imagePreparationMetadata(settings.data);
|
|
1090
1099
|
binding.originalDimensions = { width: prepared.originalWidth, height: prepared.originalHeight };
|
|
1091
1100
|
if (prepared.trim) {
|
|
@@ -1123,7 +1132,7 @@ const generateAsset = {
|
|
|
1123
1132
|
binding.digest = digest;
|
|
1124
1133
|
binding.width = width;
|
|
1125
1134
|
binding.height = height;
|
|
1126
|
-
if (draft.document.schemaVersion
|
|
1135
|
+
if (draft.document.schemaVersion !== 1 && draft.document.schemaVersion !== 3) {
|
|
1127
1136
|
binding.provenance = typeof inputFile === "string" ? `import:${inputFile}`
|
|
1128
1137
|
: makeLut ? "generated:color-cube" : kind === "sfx" ? `node:${args["node_type"]}` : `${provider}:${model}:${generated.generation_id ?? ""}`;
|
|
1129
1138
|
}
|
|
@@ -1139,6 +1148,25 @@ const generateAsset = {
|
|
|
1139
1148
|
return { game_id: shortResourceId(game.id), slot, binding: parsedBinding.data, candidate_workspace_id: game.workspace_id,
|
|
1140
1149
|
draft_updated_at: draft.game.draft_updated_at, installed: false, next: "Call install_native_game_asset with this binding to install the candidate." };
|
|
1141
1150
|
}
|
|
1151
|
+
if (args["install"] === false) {
|
|
1152
|
+
const parsedBinding = legacyAssetBinding.safeParse(binding);
|
|
1153
|
+
if (!parsedBinding.success) {
|
|
1154
|
+
return { error: "Invalid asset binding", diagnostics: parsedBinding.error.issues };
|
|
1155
|
+
}
|
|
1156
|
+
const staged = { game_id: shortResourceId(game.id), slot, binding: parsedBinding.data, candidate_workspace_id: game.workspace_id,
|
|
1157
|
+
draft_updated_at: draft.game.draft_updated_at, installed: false, generation_id: generated.generation_id,
|
|
1158
|
+
next: "Call install_native_game_asset with this binding to install the candidate." };
|
|
1159
|
+
if (frames) {
|
|
1160
|
+
staged.frames = frames;
|
|
1161
|
+
}
|
|
1162
|
+
if (tiles) {
|
|
1163
|
+
staged.tiles = tiles;
|
|
1164
|
+
}
|
|
1165
|
+
if (lutSize) {
|
|
1166
|
+
staged.lut_size = lutSize;
|
|
1167
|
+
}
|
|
1168
|
+
return staged;
|
|
1169
|
+
}
|
|
1142
1170
|
const installed = await install.impl(run, { game_id: game.id, slot, binding, base_updated_at: draft.game.draft_updated_at });
|
|
1143
1171
|
if (typeof installed !== "object" || installed === null) {
|
|
1144
1172
|
return { generation_id: generated.generation_id, result: installed };
|
|
@@ -1165,141 +1193,177 @@ const generateAsset = {
|
|
|
1165
1193
|
return { generation_id: generated.generation_id, ...installed, ...extras };
|
|
1166
1194
|
}
|
|
1167
1195
|
};
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1196
|
+
const BUILD_CONTENT_TYPES = {
|
|
1197
|
+
js: "text/javascript", css: "text/css", json: "application/json", html: "text/html", png: "image/png", jpg: "image/jpeg",
|
|
1198
|
+
webp: "image/webp", wav: "audio/wav", ogg: "audio/ogg", mp3: "audio/mpeg", ttf: "font/ttf", otf: "font/otf",
|
|
1199
|
+
glb: "model/gltf-binary", wasm: "application/wasm", md: "text/markdown", toml: "text/plain", rs: "text/plain",
|
|
1200
|
+
ico: "image/vnd.microsoft.icon", icns: "image/icns"
|
|
1201
|
+
};
|
|
1202
|
+
/**
|
|
1203
|
+
* Builds an owned, published game revision into its project workspace: a static web player under
|
|
1204
|
+
* `game-builds/<game-id>/<revision>`, or a Tauri desktop project under `game-builds/<game-id>/<revision>-tauri-<settings>`.
|
|
1205
|
+
* The capability and the editor's export download share this path.
|
|
1206
|
+
*/
|
|
1207
|
+
export async function buildOwnedNativeGame(request) {
|
|
1208
|
+
const user = request.userId;
|
|
1209
|
+
const target = request.target ?? "web";
|
|
1210
|
+
const game = await ownedGame(user, request.gameId);
|
|
1211
|
+
if (!game)
|
|
1212
|
+
return { error: "Game not found" };
|
|
1213
|
+
const workspace = await workspaceOf(user, game);
|
|
1214
|
+
if (!workspace)
|
|
1215
|
+
return { error: "Game workspace is unavailable" };
|
|
1216
|
+
const revision = request.revision ?? game.current_revision;
|
|
1217
|
+
const document = await readDocument(workspace, game, revision);
|
|
1218
|
+
if (!document)
|
|
1219
|
+
return { error: "Game revision not found" };
|
|
1220
|
+
if (Object.keys(document.assets).length > MAX_BUILD_ASSETS) {
|
|
1221
|
+
return { error: `A standalone build supports at most ${MAX_BUILD_ASSETS} assets` };
|
|
1222
|
+
}
|
|
1223
|
+
const expectedDigests = new Map();
|
|
1224
|
+
for (const binding of Object.values(document.assets)) {
|
|
1225
|
+
if (binding.assetId.startsWith("builtin:"))
|
|
1226
|
+
continue;
|
|
1227
|
+
const digests = expectedDigests.get(binding.assetId) ?? new Set();
|
|
1228
|
+
digests.add(binding.digest);
|
|
1229
|
+
expectedDigests.set(binding.assetId, digests);
|
|
1230
|
+
}
|
|
1231
|
+
if ([...expectedDigests.values()].some((digests) => digests.size !== 1)) {
|
|
1232
|
+
return { error: "One source asset has conflicting digests in this revision" };
|
|
1233
|
+
}
|
|
1234
|
+
let buildPath = `game-builds/${game.id}/${revision}`;
|
|
1235
|
+
let entryPath = `${buildPath}/index.html`;
|
|
1236
|
+
let tauriApp;
|
|
1237
|
+
let metadata;
|
|
1238
|
+
if (target === "tauri") {
|
|
1239
|
+
tauriApp = { name: game.name };
|
|
1240
|
+
const { tauriAppMetadata } = await import("@nodetool-ai/game-renderer/build-tauri");
|
|
1241
|
+
try {
|
|
1242
|
+
metadata = tauriAppMetadata(document, tauriApp);
|
|
1187
1243
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
if (binding.assetId.startsWith("builtin:"))
|
|
1191
|
-
continue;
|
|
1192
|
-
const digests = expectedDigests.get(binding.assetId) ?? new Set();
|
|
1193
|
-
digests.add(binding.digest);
|
|
1194
|
-
expectedDigests.set(binding.assetId, digests);
|
|
1244
|
+
catch (error) {
|
|
1245
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1195
1246
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1247
|
+
// The app name comes from the game row, which can change without a new revision.
|
|
1248
|
+
const settings = createHash("sha256").update(JSON.stringify(metadata)).digest("hex").slice(0, 12);
|
|
1249
|
+
buildPath = `game-builds/${game.id}/${revision}-tauri-${settings}`;
|
|
1250
|
+
entryPath = `${buildPath}/src-tauri/tauri.conf.json`;
|
|
1251
|
+
}
|
|
1252
|
+
const done = { game_id: game.id, revision, target, build_path: buildPath, entry_path: entryPath, workspace, app: metadata ?? null };
|
|
1253
|
+
if (await workspace.exists(entryPath)) {
|
|
1254
|
+
return { ...done, cached: true };
|
|
1255
|
+
}
|
|
1256
|
+
const storage = request.storage;
|
|
1257
|
+
const cache = new Map();
|
|
1258
|
+
let totalAssetBytes = 0;
|
|
1259
|
+
const resolveAsset = async (assetId) => {
|
|
1260
|
+
const cached = cache.get(assetId);
|
|
1261
|
+
if (cached)
|
|
1262
|
+
return cached;
|
|
1263
|
+
if (!/^[a-f0-9]{32}$/.test(assetId))
|
|
1264
|
+
return null;
|
|
1265
|
+
const asset = await Asset.find(user, assetId);
|
|
1266
|
+
if (!asset || !storage)
|
|
1267
|
+
return null;
|
|
1268
|
+
const extension = MEDIA_EXTENSIONS[asset.content_type];
|
|
1269
|
+
if (!extension || (asset.size !== null && asset.size > MAX_BUILD_ASSET_BYTES))
|
|
1270
|
+
return null;
|
|
1271
|
+
const keys = assetKeyCandidates(user, `${asset.id}.${extension}`);
|
|
1272
|
+
let bytes = null;
|
|
1273
|
+
for (const key of keys) {
|
|
1274
|
+
bytes = await storage.retrieve(storage.uriForKey(key));
|
|
1275
|
+
if (bytes)
|
|
1276
|
+
break;
|
|
1198
1277
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1278
|
+
if (!bytes || bytes.byteLength > MAX_BUILD_ASSET_BYTES)
|
|
1279
|
+
return null;
|
|
1280
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
1281
|
+
if (!expectedDigests.get(assetId)?.has(digest)) {
|
|
1282
|
+
throw new Error(`Asset digest changed for ${assetId}`);
|
|
1203
1283
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
if (document.schemaVersion === 3) {
|
|
1244
|
-
const { buildStandaloneGame3D } = await import("@nodetool-ai/game-renderer/build3d");
|
|
1245
|
-
await buildStandaloneGame3D({ document, outputDir: join(staging, "player"), resolveAsset, signal: run.context.signal });
|
|
1246
|
-
}
|
|
1247
|
-
else {
|
|
1248
|
-
const { buildStandaloneGame } = await import("@nodetool-ai/game-renderer/build");
|
|
1249
|
-
await buildStandaloneGame({ document, outputDir: join(staging, "player"), resolveAsset });
|
|
1250
|
-
}
|
|
1251
|
-
const files = [];
|
|
1252
|
-
const pending = [""];
|
|
1253
|
-
let totalBytes = 0;
|
|
1254
|
-
while (pending.length > 0) {
|
|
1255
|
-
const directory = pending.pop() ?? "";
|
|
1256
|
-
for (const entry of await readdir(join(staging, "player", directory), { withFileTypes: true })) {
|
|
1257
|
-
const path = directory ? `${directory}/${entry.name}` : entry.name;
|
|
1258
|
-
if (entry.isDirectory()) {
|
|
1259
|
-
pending.push(path);
|
|
1260
|
-
}
|
|
1261
|
-
else if (entry.isFile()) {
|
|
1262
|
-
const bytes = await readFile(join(staging, "player", path));
|
|
1263
|
-
totalBytes += bytes.byteLength;
|
|
1264
|
-
if (files.length >= MAX_BUILD_FILES || totalBytes > MAX_BUILD_BYTES) {
|
|
1265
|
-
throw new Error("Standalone build exceeds file or byte limits");
|
|
1266
|
-
}
|
|
1267
|
-
files.push({ path, bytes });
|
|
1284
|
+
totalAssetBytes += bytes.byteLength;
|
|
1285
|
+
if (totalAssetBytes > MAX_BUILD_BYTES) {
|
|
1286
|
+
throw new Error(`A standalone build supports at most ${MAX_BUILD_BYTES} asset bytes`);
|
|
1287
|
+
}
|
|
1288
|
+
const resolved = { bytes, mimeType: asset.content_type };
|
|
1289
|
+
cache.set(assetId, resolved);
|
|
1290
|
+
return resolved;
|
|
1291
|
+
};
|
|
1292
|
+
const scratch = await workspace.scratchDir();
|
|
1293
|
+
const staging = await mkdtemp(join(scratch, ".native-game-build-"));
|
|
1294
|
+
try {
|
|
1295
|
+
const output = join(staging, "player");
|
|
1296
|
+
if (tauriApp) {
|
|
1297
|
+
const { buildTauriGameProject } = await import("@nodetool-ai/game-renderer/build-tauri");
|
|
1298
|
+
await buildTauriGameProject({ document, outputDir: output, resolveAsset, app: tauriApp, signal: request.signal });
|
|
1299
|
+
}
|
|
1300
|
+
else if (document.schemaVersion === 3) {
|
|
1301
|
+
const { buildStandaloneGame3D } = await import("@nodetool-ai/game-renderer/build3d");
|
|
1302
|
+
await buildStandaloneGame3D({ document, outputDir: output, resolveAsset, signal: request.signal });
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
const { buildStandaloneGame } = await import("@nodetool-ai/game-renderer/build");
|
|
1306
|
+
await buildStandaloneGame({ document, outputDir: output, resolveAsset });
|
|
1307
|
+
}
|
|
1308
|
+
const files = [];
|
|
1309
|
+
const pending = [""];
|
|
1310
|
+
let totalBytes = 0;
|
|
1311
|
+
while (pending.length > 0) {
|
|
1312
|
+
const directory = pending.pop() ?? "";
|
|
1313
|
+
for (const entry of await readdir(join(output, directory), { withFileTypes: true })) {
|
|
1314
|
+
const path = directory ? `${directory}/${entry.name}` : entry.name;
|
|
1315
|
+
if (entry.isDirectory()) {
|
|
1316
|
+
pending.push(path);
|
|
1317
|
+
}
|
|
1318
|
+
else if (entry.isFile()) {
|
|
1319
|
+
const bytes = await readFile(join(output, path));
|
|
1320
|
+
totalBytes += bytes.byteLength;
|
|
1321
|
+
if (files.length >= MAX_BUILD_FILES || totalBytes > MAX_BUILD_BYTES) {
|
|
1322
|
+
throw new Error("Standalone build exceeds file or byte limits");
|
|
1268
1323
|
}
|
|
1324
|
+
files.push({ path, bytes });
|
|
1269
1325
|
}
|
|
1270
1326
|
}
|
|
1271
|
-
if (!files.some((file) => file.path === "index.html")) {
|
|
1272
|
-
throw new Error("Standalone player entry is missing");
|
|
1273
|
-
}
|
|
1274
|
-
for (const file of files.filter((item) => item.path !== "index.html")) {
|
|
1275
|
-
const contentType = file.path.endsWith(".js") ? "text/javascript"
|
|
1276
|
-
: file.path.endsWith(".css") ? "text/css"
|
|
1277
|
-
: file.path.endsWith(".json") ? "application/json"
|
|
1278
|
-
: file.path.endsWith(".png") ? "image/png"
|
|
1279
|
-
: file.path.endsWith(".jpg") ? "image/jpeg"
|
|
1280
|
-
: file.path.endsWith(".webp") ? "image/webp"
|
|
1281
|
-
: file.path.endsWith(".wav") ? "audio/wav"
|
|
1282
|
-
: file.path.endsWith(".ogg") ? "audio/ogg"
|
|
1283
|
-
: file.path.endsWith(".mp3") ? "audio/mpeg"
|
|
1284
|
-
: file.path.endsWith(".ttf") ? "font/ttf"
|
|
1285
|
-
: file.path.endsWith(".otf") ? "font/otf"
|
|
1286
|
-
: file.path.endsWith(".glb") ? "model/gltf-binary"
|
|
1287
|
-
: file.path.endsWith(".wasm") ? "application/wasm"
|
|
1288
|
-
: "application/octet-stream";
|
|
1289
|
-
await workspace.write(`${buildPath}/${file.path}`, file.bytes, contentType);
|
|
1290
|
-
}
|
|
1291
|
-
const index = files.find((file) => file.path === "index.html");
|
|
1292
|
-
if (!index)
|
|
1293
|
-
throw new Error("Standalone player entry is missing");
|
|
1294
|
-
await workspace.write(indexPath, index.bytes, "text/html");
|
|
1295
|
-
return { game_id: game.id, revision, build_path: buildPath, entry_path: indexPath, cached: false };
|
|
1296
|
-
}
|
|
1297
|
-
catch (error) {
|
|
1298
|
-
return { error: error instanceof Error ? error.message : String(error) };
|
|
1299
|
-
}
|
|
1300
|
-
finally {
|
|
1301
|
-
await rm(staging, { recursive: true, force: true });
|
|
1302
1327
|
}
|
|
1328
|
+
const entryFile = entryPath.slice(buildPath.length + 1);
|
|
1329
|
+
const entry = files.find((file) => file.path === entryFile);
|
|
1330
|
+
if (!entry)
|
|
1331
|
+
throw new Error("Standalone build entry is missing");
|
|
1332
|
+
const contentType = (path) => BUILD_CONTENT_TYPES[path.slice(path.lastIndexOf(".") + 1)] ?? "application/octet-stream";
|
|
1333
|
+
// The entry file is written last, so its presence marks a complete build for the cache check above.
|
|
1334
|
+
for (const file of files.filter((item) => item !== entry)) {
|
|
1335
|
+
await workspace.write(`${buildPath}/${file.path}`, file.bytes, contentType(file.path));
|
|
1336
|
+
}
|
|
1337
|
+
await workspace.write(entryPath, entry.bytes, contentType(entry.path));
|
|
1338
|
+
return { ...done, cached: false };
|
|
1339
|
+
}
|
|
1340
|
+
catch (error) {
|
|
1341
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
1342
|
+
}
|
|
1343
|
+
finally {
|
|
1344
|
+
await rm(staging, { recursive: true, force: true });
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const buildGame = {
|
|
1348
|
+
spec: gameSpecs[5],
|
|
1349
|
+
impl: async (run, args) => {
|
|
1350
|
+
const user = userId(run);
|
|
1351
|
+
const id = args["game_id"];
|
|
1352
|
+
if (!user || typeof id !== "string")
|
|
1353
|
+
return { error: "game_id is required in a user session" };
|
|
1354
|
+
const target = args["target"] ?? "web";
|
|
1355
|
+
if (target !== "web" && target !== "tauri")
|
|
1356
|
+
return { error: "target must be web or tauri" };
|
|
1357
|
+
const revision = args["revision"];
|
|
1358
|
+
const result = await buildOwnedNativeGame({ userId: user, gameId: id, revision: typeof revision === "string" ? revision : undefined,
|
|
1359
|
+
target, storage: run.context.assetStorage, signal: run.context.signal });
|
|
1360
|
+
if ("error" in result)
|
|
1361
|
+
return { error: result.error };
|
|
1362
|
+
const report = { game_id: result.game_id, revision: result.revision, target: result.target, build_path: result.build_path,
|
|
1363
|
+
entry_path: result.entry_path, cached: result.cached };
|
|
1364
|
+
return target === "tauri"
|
|
1365
|
+
? { ...report, next: "The project builds desktop installers with `npm install && npm run build` in a checkout that has Rust and the Tauri prerequisites." }
|
|
1366
|
+
: report;
|
|
1303
1367
|
}
|
|
1304
1368
|
};
|
|
1305
1369
|
const listExamples = {
|
|
@@ -1423,10 +1487,24 @@ const autoplay = {
|
|
|
1423
1487
|
return { game_id: shortResourceId(game.id), revision: document.revision, ...route, win_tick: winTick, reached_targets: reachedTargets, level_stats: levelStats, state: snapshot };
|
|
1424
1488
|
}
|
|
1425
1489
|
};
|
|
1490
|
+
function withDraftRecovery(capability) {
|
|
1491
|
+
return { ...capability, impl: async (run, args) => {
|
|
1492
|
+
try {
|
|
1493
|
+
return await capability.impl(run, args);
|
|
1494
|
+
}
|
|
1495
|
+
catch (error) {
|
|
1496
|
+
if (error instanceof MissingGameDraftSourceError) {
|
|
1497
|
+
return { error: error.message, code: "game_draft_source_unavailable" };
|
|
1498
|
+
}
|
|
1499
|
+
throw error;
|
|
1500
|
+
}
|
|
1501
|
+
} };
|
|
1502
|
+
}
|
|
1426
1503
|
export const module = {
|
|
1427
1504
|
module: "game",
|
|
1428
1505
|
exports: [create, get, save, install, playtest, buildGame, edit, capture, generateAsset, listExamples, getExample, installExample, autoplay,
|
|
1429
1506
|
{ spec: previewGameAuthoringSpec, impl: previewGameAuthoring },
|
|
1430
|
-
{ spec: applyGameAuthoringSpec, impl: applyGameAuthoring }
|
|
1507
|
+
{ spec: applyGameAuthoringSpec, impl: applyGameAuthoring },
|
|
1508
|
+
{ spec: browseGameAssetsSpec, impl: browseGameAssets }].map(withDraftRecovery)
|
|
1431
1509
|
};
|
|
1432
1510
|
//# sourceMappingURL=game.js.map
|