breakpoint-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/bridge.js +189 -0
- package/dist/config.js +71 -0
- package/dist/confirm.js +45 -0
- package/dist/csdap.js +331 -0
- package/dist/cslsp.js +217 -0
- package/dist/dap.js +341 -0
- package/dist/framing.js +125 -0
- package/dist/index.js +110 -0
- package/dist/logger.js +11 -0
- package/dist/lsp.js +219 -0
- package/dist/paths.js +28 -0
- package/dist/schemas.js +587 -0
- package/dist/stdio.js +101 -0
- package/dist/subscriptions.js +141 -0
- package/dist/tasks.js +146 -0
- package/dist/tools/assetgen.js +360 -0
- package/dist/tools/backend.js +532 -0
- package/dist/tools/cli.js +130 -0
- package/dist/tools/csdap.js +377 -0
- package/dist/tools/cslsp.js +285 -0
- package/dist/tools/dap.js +504 -0
- package/dist/tools/editor.js +1919 -0
- package/dist/tools/knowledge.js +517 -0
- package/dist/tools/lsp-common.js +121 -0
- package/dist/tools/lsp.js +576 -0
- package/dist/tools/netcode.js +411 -0
- package/dist/tools/processes.js +103 -0
- package/dist/tools/resources.js +27 -0
- package/dist/tools/runtime.js +125 -0
- package/package.json +57 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { gate } from "../confirm.js";
|
|
5
|
+
import { toFsPath } from "../paths.js";
|
|
6
|
+
/**
|
|
7
|
+
* Group J — AI asset generation.
|
|
8
|
+
*
|
|
9
|
+
* MCP-native framing: the server NEVER bundles or calls a model. Each generator
|
|
10
|
+
* writes an asset to a res:// path, triggers an import (via the editor bridge),
|
|
11
|
+
* and returns a schema'd result — but WHERE the bytes come from is delegated:
|
|
12
|
+
*
|
|
13
|
+
* backend "none" (default) : the tool DEGRADES to a clear "no generation
|
|
14
|
+
* backend configured" and returns a `request` spec (kind / prompt / path /
|
|
15
|
+
* size / format) the connected multimodal client can fulfil itself. No file
|
|
16
|
+
* is written; not an error — degradation is a documented outcome.
|
|
17
|
+
* backend "placeholder" : deterministic, in-engine procedural stand-ins
|
|
18
|
+
* (a hashed-colour PNG sprite/texture/icon, an AudioStreamWAV blip, a
|
|
19
|
+
* BoxMesh) — no model, fully reproducible, so CI can assert them.
|
|
20
|
+
* backend "command" : delegate to a configured local backend. The argv
|
|
21
|
+
* TEMPLATE's tokens {kind} {prompt} {output} {width} {height} {format} are
|
|
22
|
+
* substituted per-argument (no shell) and the command writes the file; the
|
|
23
|
+
* host then imports it through the bridge. Bring-your-own-tool, same trust
|
|
24
|
+
* model as the OmniSharp / netcoredbg commands.
|
|
25
|
+
*
|
|
26
|
+
* The single always-on tool is asset_gen_placeholder (deterministic, ignores the
|
|
27
|
+
* backend); the five typed generators (sprite/texture/icon/audio_sfx/model)
|
|
28
|
+
* branch on the backend, and asset_gen_configure inspects/sets it for the session.
|
|
29
|
+
*/
|
|
30
|
+
const KINDS = ["sprite", "texture", "icon", "audio_sfx", "model"];
|
|
31
|
+
const KIND_ENUM = z.enum(KINDS);
|
|
32
|
+
/**
|
|
33
|
+
* Per-kind placeholder output: the in-engine generator writes NATIVE Godot
|
|
34
|
+
* resources (.tres) — an ImageTexture / AudioStreamWAV / primitive mesh — so
|
|
35
|
+
* they load synchronously with no async import pipeline. (A real `command`
|
|
36
|
+
* backend may instead write an external format like .png; that goes through
|
|
37
|
+
* asset.import.)
|
|
38
|
+
*/
|
|
39
|
+
const KIND_FORMAT = {
|
|
40
|
+
sprite: { ext: ".tres", format: "ImageTexture", allowed: [".tres", ".res"] },
|
|
41
|
+
texture: { ext: ".tres", format: "ImageTexture", allowed: [".tres", ".res"] },
|
|
42
|
+
icon: { ext: ".tres", format: "ImageTexture", allowed: [".tres", ".res"] },
|
|
43
|
+
audio_sfx: { ext: ".tres", format: "AudioStreamWAV", allowed: [".tres", ".res"] },
|
|
44
|
+
model: { ext: ".tres", format: "BoxMesh", allowed: [".tres", ".res"] },
|
|
45
|
+
};
|
|
46
|
+
function normalizeBackend(v) {
|
|
47
|
+
return v === "placeholder" || v === "command" ? v : "none";
|
|
48
|
+
}
|
|
49
|
+
// ---- result envelopes (mirror the ok()/fail() shape used across the tools) ----
|
|
50
|
+
function ok(obj) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
53
|
+
structuredContent: obj,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function fail(message) {
|
|
57
|
+
return {
|
|
58
|
+
isError: true,
|
|
59
|
+
content: [{ type: "text", text: `Asset generation error: ${message}` }],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function extOf(p) {
|
|
63
|
+
const slash = p.lastIndexOf("/");
|
|
64
|
+
const dot = p.lastIndexOf(".");
|
|
65
|
+
return dot > slash ? p.slice(dot).toLowerCase() : "";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the destination for placeholder mode: append the kind's default
|
|
69
|
+
* extension when none is given, or reject a mismatched one. (The command backend
|
|
70
|
+
* owns its own format, so this only guards the in-engine placeholder path.)
|
|
71
|
+
*/
|
|
72
|
+
function resolvePlaceholderPath(kind, toPath) {
|
|
73
|
+
if (!toPath.startsWith("res://"))
|
|
74
|
+
return { error: "'to_path' must be a res:// path" };
|
|
75
|
+
const spec = KIND_FORMAT[kind];
|
|
76
|
+
const ext = extOf(toPath);
|
|
77
|
+
if (!ext)
|
|
78
|
+
return { path: toPath + spec.ext };
|
|
79
|
+
if (!spec.allowed.includes(ext)) {
|
|
80
|
+
return { error: `asset_gen placeholder for kind '${kind}' writes ${spec.allowed.join(" / ")}; got '${ext}'` };
|
|
81
|
+
}
|
|
82
|
+
return { path: toPath };
|
|
83
|
+
}
|
|
84
|
+
/** Substitute {tokens} in one argv template argument. */
|
|
85
|
+
function subToken(arg, tokens) {
|
|
86
|
+
return arg.replace(/\{(kind|prompt|output|width|height|format)\}/g, (_m, k) => tokens[k] ?? "");
|
|
87
|
+
}
|
|
88
|
+
/** Run the configured backend command (no shell). Resolves when it exits 0. */
|
|
89
|
+
function runCommand(template, tokens, timeoutMs) {
|
|
90
|
+
const parts = template.split(/\s+/).filter(Boolean);
|
|
91
|
+
if (parts.length === 0)
|
|
92
|
+
return Promise.resolve({ ok: false, message: "empty command template" });
|
|
93
|
+
const argv = parts.map((a) => subToken(a, tokens));
|
|
94
|
+
const [cmd, ...args] = argv;
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
let done = false;
|
|
97
|
+
const finish = (r) => {
|
|
98
|
+
if (done)
|
|
99
|
+
return;
|
|
100
|
+
done = true;
|
|
101
|
+
resolve(r);
|
|
102
|
+
};
|
|
103
|
+
let stderr = "";
|
|
104
|
+
let child;
|
|
105
|
+
try {
|
|
106
|
+
child = spawn(cmd, args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
finish({ ok: false, message: `could not spawn backend '${cmd}': ${err.message}` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
child.kill("SIGKILL");
|
|
114
|
+
finish({ ok: false, message: `backend command timed out after ${timeoutMs}ms` });
|
|
115
|
+
}, timeoutMs);
|
|
116
|
+
timer.unref?.();
|
|
117
|
+
child.stderr?.on("data", (d) => {
|
|
118
|
+
if (stderr.length < 4000)
|
|
119
|
+
stderr += String(d);
|
|
120
|
+
});
|
|
121
|
+
child.on("error", (err) => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
finish({ ok: false, message: `backend '${cmd}' failed to start: ${err.message}` });
|
|
124
|
+
});
|
|
125
|
+
child.on("close", (code) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
if (code === 0)
|
|
128
|
+
finish({ ok: true });
|
|
129
|
+
else
|
|
130
|
+
finish({ ok: false, message: `backend '${cmd}' exited ${code}${stderr ? `: ${stderr.trim().slice(0, 400)}` : ""}` });
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export function registerAssetGenTools(server, bridge, config) {
|
|
135
|
+
// Mutable session state, seeded from the environment (the "flag").
|
|
136
|
+
const state = {
|
|
137
|
+
backend: normalizeBackend(config.assetGenBackend),
|
|
138
|
+
command: config.assetGenCommand || null,
|
|
139
|
+
provider: config.assetGenProvider || null,
|
|
140
|
+
};
|
|
141
|
+
const backendNote = (b) => b === "none"
|
|
142
|
+
? "No generation backend configured — asset_gen_* generators degrade to a request spec (asset_gen_placeholder still works)."
|
|
143
|
+
: b === "placeholder"
|
|
144
|
+
? "Placeholder backend — generators write deterministic in-engine stand-ins."
|
|
145
|
+
: "Command backend — generators delegate to the configured command.";
|
|
146
|
+
// ------------------------------------------------------ asset_gen_configure ----
|
|
147
|
+
server.registerTool("asset_gen_configure", {
|
|
148
|
+
title: "Configure asset-generation backend",
|
|
149
|
+
description: "Inspect or set the asset-generation backend for this session (Group J's feature flag). " +
|
|
150
|
+
"backend 'none' (default) makes the generators degrade to a clear 'no backend configured' request spec; " +
|
|
151
|
+
"'placeholder' writes deterministic in-engine stand-ins; 'command' delegates to a configured local command " +
|
|
152
|
+
"(argv template with {kind} {prompt} {output} {width} {height} {format} tokens; it must write the file). " +
|
|
153
|
+
"Call with no arguments to just report the current configuration. Session-only (does not persist to disk).",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
backend: z.enum(["none", "placeholder", "command"]).optional().describe("Backend to select (omit to leave unchanged)"),
|
|
156
|
+
command: z.string().optional().describe("argv template for the 'command' backend, tokens {kind} {prompt} {output} {width} {height} {format}"),
|
|
157
|
+
provider: z.string().optional().describe("Free-form provider label recorded in results (e.g. \"local-sd\", \"my-gen.py\")"),
|
|
158
|
+
},
|
|
159
|
+
}, async ({ backend, command, provider }) => {
|
|
160
|
+
if (command !== undefined)
|
|
161
|
+
state.command = command || null;
|
|
162
|
+
if (provider !== undefined)
|
|
163
|
+
state.provider = provider || null;
|
|
164
|
+
if (backend !== undefined) {
|
|
165
|
+
if (backend === "command" && !state.command) {
|
|
166
|
+
return fail("backend 'command' needs a command template — pass `command` in the same call (or set BREAKPOINT_ASSETGEN_CMD)");
|
|
167
|
+
}
|
|
168
|
+
state.backend = backend;
|
|
169
|
+
}
|
|
170
|
+
return ok({
|
|
171
|
+
backend: state.backend,
|
|
172
|
+
provider: state.provider,
|
|
173
|
+
command: state.command,
|
|
174
|
+
configured: state.backend !== "none",
|
|
175
|
+
supported_kinds: [...KINDS],
|
|
176
|
+
note: backendNote(state.backend),
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// The shared generation flow for the 5 typed generators + the always-on placeholder.
|
|
180
|
+
async function generate(kind, args) {
|
|
181
|
+
const { prompt, to_path, width, height, duration_ms, shape, placeholder, confirm, forcePlaceholder } = args;
|
|
182
|
+
const spec = KIND_FORMAT[kind];
|
|
183
|
+
const usePlaceholder = forcePlaceholder === true || placeholder === true || state.backend === "placeholder";
|
|
184
|
+
const effectiveBackend = usePlaceholder ? "placeholder" : state.backend;
|
|
185
|
+
// Degrade path — no file written, no confirmation needed.
|
|
186
|
+
if (effectiveBackend === "none") {
|
|
187
|
+
return ok({
|
|
188
|
+
status: "no_backend",
|
|
189
|
+
kind,
|
|
190
|
+
backend: "none",
|
|
191
|
+
path: null,
|
|
192
|
+
prompt: prompt ?? null,
|
|
193
|
+
format: spec.format,
|
|
194
|
+
request: { kind, prompt: prompt ?? null, to_path, width: width ?? null, height: height ?? null, format: spec.format },
|
|
195
|
+
message: `No generation backend configured. Configure one with asset_gen_configure (backend "command" or "placeholder"), ` +
|
|
196
|
+
`pass placeholder:true for a deterministic stand-in, or have your multimodal client generate the ${kind} and write it to ${to_path}.`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// Everything below writes a file → gate it.
|
|
200
|
+
const blocked = await gate(server, confirm, `Generate ${kind} asset at ${to_path}`);
|
|
201
|
+
if (blocked)
|
|
202
|
+
return blocked;
|
|
203
|
+
if (effectiveBackend === "placeholder") {
|
|
204
|
+
const resolved = resolvePlaceholderPath(kind, to_path);
|
|
205
|
+
if (resolved.error)
|
|
206
|
+
return fail(resolved.error);
|
|
207
|
+
const path = resolved.path;
|
|
208
|
+
try {
|
|
209
|
+
const r = (await bridge.request("asset.gen_placeholder", {
|
|
210
|
+
kind,
|
|
211
|
+
to_path: path,
|
|
212
|
+
prompt: prompt ?? "",
|
|
213
|
+
...(width !== undefined ? { width } : {}),
|
|
214
|
+
...(height !== undefined ? { height } : {}),
|
|
215
|
+
...(duration_ms !== undefined ? { duration_ms } : {}),
|
|
216
|
+
...(shape !== undefined ? { shape } : {}),
|
|
217
|
+
}));
|
|
218
|
+
return ok({
|
|
219
|
+
status: "placeholder",
|
|
220
|
+
kind,
|
|
221
|
+
backend: "placeholder",
|
|
222
|
+
path: r.path ?? path,
|
|
223
|
+
prompt: prompt ?? null,
|
|
224
|
+
imported_type: r.imported_type ?? null,
|
|
225
|
+
...(r.width !== undefined ? { width: r.width } : {}),
|
|
226
|
+
...(r.height !== undefined ? { height: r.height } : {}),
|
|
227
|
+
...(r.bytes !== undefined ? { bytes: r.bytes } : {}),
|
|
228
|
+
format: r.format ?? spec.format,
|
|
229
|
+
message: `Wrote a deterministic ${kind} placeholder to ${r.path ?? path}.`,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
return fail(err.message ?? String(err));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// command backend
|
|
237
|
+
if (!to_path.startsWith("res://"))
|
|
238
|
+
return fail("'to_path' must be a res:// path");
|
|
239
|
+
if (!state.command)
|
|
240
|
+
return fail("backend 'command' has no command template configured");
|
|
241
|
+
const output = toFsPath(to_path, config.projectPath);
|
|
242
|
+
const tokens = {
|
|
243
|
+
kind,
|
|
244
|
+
prompt: prompt ?? "",
|
|
245
|
+
output,
|
|
246
|
+
width: String(width ?? ""),
|
|
247
|
+
height: String(height ?? ""),
|
|
248
|
+
format: spec.format,
|
|
249
|
+
};
|
|
250
|
+
const run = await runCommand(state.command, tokens, config.assetGenTimeoutMs);
|
|
251
|
+
if (!run.ok)
|
|
252
|
+
return fail(run.message);
|
|
253
|
+
let bytes = 0;
|
|
254
|
+
try {
|
|
255
|
+
bytes = fs.statSync(output).size;
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return fail(`backend command exited 0 but did not write ${to_path}`);
|
|
259
|
+
}
|
|
260
|
+
if (bytes === 0)
|
|
261
|
+
return fail(`backend command wrote an empty file at ${to_path}`);
|
|
262
|
+
// Import the freshly-written file through the editor bridge.
|
|
263
|
+
let importedType = null;
|
|
264
|
+
try {
|
|
265
|
+
const r = (await bridge.request("asset.import", { path: to_path }));
|
|
266
|
+
importedType = r.imported_type ?? null;
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
return fail(`generated ${to_path} (${bytes} bytes) but the editor import failed: ${err.message ?? String(err)}`);
|
|
270
|
+
}
|
|
271
|
+
return ok({
|
|
272
|
+
status: "generated",
|
|
273
|
+
kind,
|
|
274
|
+
backend: "command",
|
|
275
|
+
provider: state.provider,
|
|
276
|
+
path: to_path,
|
|
277
|
+
prompt: prompt ?? null,
|
|
278
|
+
imported_type: importedType,
|
|
279
|
+
bytes,
|
|
280
|
+
message: `Generated ${kind} via the configured backend${state.provider ? ` (${state.provider})` : ""} at ${to_path}.`,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
// ----------------------------------------------------- asset_gen_placeholder ----
|
|
284
|
+
server.registerTool("asset_gen_placeholder", {
|
|
285
|
+
title: "Generate a placeholder asset",
|
|
286
|
+
description: "Write a deterministic, in-engine PROCEDURAL placeholder asset to a res:// path — no model, fully " +
|
|
287
|
+
"reproducible (colour/frequency/size derive from a hash of the prompt). Always available regardless of the configured " +
|
|
288
|
+
"backend. kind picks the asset (all native .tres resources that load synchronously): sprite/texture/icon → an ImageTexture; " +
|
|
289
|
+
"audio_sfx → an AudioStreamWAV; model → a BoxMesh/primitive mesh. " +
|
|
290
|
+
"DESTRUCTIVE (writes a file) — gated by confirmation.",
|
|
291
|
+
inputSchema: {
|
|
292
|
+
kind: KIND_ENUM.describe("Asset kind: sprite | texture | icon | audio_sfx | model"),
|
|
293
|
+
to_path: z.string().describe("Destination res:// path; the correct extension is appended if omitted"),
|
|
294
|
+
prompt: z.string().optional().describe("Seed text — deterministically colours/tunes/sizes the placeholder"),
|
|
295
|
+
width: z.number().int().positive().optional().describe("Image width for sprite/texture/icon (default per-kind)"),
|
|
296
|
+
height: z.number().int().positive().optional().describe("Image height for sprite/texture/icon (default per-kind)"),
|
|
297
|
+
duration_ms: z.number().int().positive().optional().describe("Length in ms for audio_sfx (default 300)"),
|
|
298
|
+
shape: z.enum(["box", "sphere", "cylinder", "prism"]).optional().describe("Primitive for model (default box)"),
|
|
299
|
+
confirm: z.boolean().optional().describe("Auto-approve this destructive action (skip the confirmation prompt)"),
|
|
300
|
+
},
|
|
301
|
+
}, async ({ kind, to_path, prompt, width, height, duration_ms, shape, confirm }) => generate(kind, { prompt, to_path, width, height, duration_ms, shape, confirm, forcePlaceholder: true }));
|
|
302
|
+
// The five typed generators share `generate`; they differ only in the fixed
|
|
303
|
+
// `kind` and the per-kind input fields exposed.
|
|
304
|
+
const imageInput = {
|
|
305
|
+
prompt: z.string().describe("What to generate (delegated to the backend; seeds the placeholder)"),
|
|
306
|
+
to_path: z.string().describe("Destination res:// path (a .tres ImageTexture for the placeholder backend)"),
|
|
307
|
+
width: z.number().int().positive().optional().describe("Image width (default 64 sprite / 128 texture,icon)"),
|
|
308
|
+
height: z.number().int().positive().optional().describe("Image height (default matches width)"),
|
|
309
|
+
placeholder: z.boolean().optional().describe("Force a deterministic in-engine stand-in even if a real backend is configured"),
|
|
310
|
+
confirm: z.boolean().optional().describe("Auto-approve this destructive action (skip the confirmation prompt)"),
|
|
311
|
+
};
|
|
312
|
+
server.registerTool("asset_gen_sprite", {
|
|
313
|
+
title: "Generate a sprite",
|
|
314
|
+
description: "Generate a 2D sprite texture from a prompt and import it at a res:// path. Delegates the pixels to the configured " +
|
|
315
|
+
"backend (asset_gen_configure); with no backend it degrades to a request spec, or pass placeholder:true for a " +
|
|
316
|
+
"deterministic stand-in. DESTRUCTIVE (writes a file when a backend/placeholder is used) — gated by confirmation.",
|
|
317
|
+
inputSchema: imageInput,
|
|
318
|
+
}, async ({ prompt, to_path, width, height, placeholder, confirm }) => generate("sprite", { prompt, to_path, width, height, placeholder, confirm }));
|
|
319
|
+
server.registerTool("asset_gen_texture", {
|
|
320
|
+
title: "Generate a texture",
|
|
321
|
+
description: "Generate a (tileable-intent) material texture from a prompt and import it at a res:// path. Delegates to the configured " +
|
|
322
|
+
"backend; degrades to a request spec with no backend, or pass placeholder:true for a deterministic stand-in. " +
|
|
323
|
+
"DESTRUCTIVE (writes a file when a backend/placeholder is used) — gated by confirmation.",
|
|
324
|
+
inputSchema: imageInput,
|
|
325
|
+
}, async ({ prompt, to_path, width, height, placeholder, confirm }) => generate("texture", { prompt, to_path, width, height, placeholder, confirm }));
|
|
326
|
+
server.registerTool("asset_gen_icon", {
|
|
327
|
+
title: "Generate an icon",
|
|
328
|
+
description: "Generate a square icon from a prompt and import it at a res:// path. Delegates to the configured backend; degrades to a " +
|
|
329
|
+
"request spec with no backend, or pass placeholder:true for a deterministic stand-in. DESTRUCTIVE (writes a file when a " +
|
|
330
|
+
"backend/placeholder is used) — gated by confirmation.",
|
|
331
|
+
inputSchema: imageInput,
|
|
332
|
+
}, async ({ prompt, to_path, width, height, placeholder, confirm }) => generate("icon", { prompt, to_path, width, height, placeholder, confirm }));
|
|
333
|
+
server.registerTool("asset_gen_audio_sfx", {
|
|
334
|
+
title: "Generate a sound effect",
|
|
335
|
+
description: "Generate a short sound effect from a prompt and import it at a res:// path (an AudioStreamWAV for the placeholder " +
|
|
336
|
+
"backend). Delegates to the configured backend; degrades to a request spec with no backend, or pass placeholder:true " +
|
|
337
|
+
"for a deterministic stand-in. DESTRUCTIVE (writes a file when a backend/placeholder is used) — gated by confirmation.",
|
|
338
|
+
inputSchema: {
|
|
339
|
+
prompt: z.string().describe("What the sound should be (delegated to the backend; seeds the placeholder)"),
|
|
340
|
+
to_path: z.string().describe("Destination res:// path (a .tres AudioStreamWAV for the placeholder backend)"),
|
|
341
|
+
duration_ms: z.number().int().positive().optional().describe("Length in milliseconds (default 300)"),
|
|
342
|
+
placeholder: z.boolean().optional().describe("Force a deterministic in-engine stand-in even if a real backend is configured"),
|
|
343
|
+
confirm: z.boolean().optional().describe("Auto-approve this destructive action (skip the confirmation prompt)"),
|
|
344
|
+
},
|
|
345
|
+
}, async ({ prompt, to_path, duration_ms, placeholder, confirm }) => generate("audio_sfx", { prompt, to_path, duration_ms, placeholder, confirm }));
|
|
346
|
+
server.registerTool("asset_gen_model", {
|
|
347
|
+
title: "Generate a 3D model",
|
|
348
|
+
description: "Generate a 3D mesh from a prompt and import it at a res:// path (a BoxMesh/primitive for the placeholder backend). " +
|
|
349
|
+
"Delegates to the configured backend; degrades to a request spec with no backend, or pass placeholder:true for a " +
|
|
350
|
+
"deterministic stand-in. DESTRUCTIVE (writes a file when a backend/placeholder is used) — gated by confirmation.",
|
|
351
|
+
inputSchema: {
|
|
352
|
+
prompt: z.string().describe("What to model (delegated to the backend; seeds the placeholder)"),
|
|
353
|
+
to_path: z.string().describe("Destination res:// path (a .tres mesh resource for the placeholder backend)"),
|
|
354
|
+
shape: z.enum(["box", "sphere", "cylinder", "prism"]).optional().describe("Primitive shape for the placeholder (default box)"),
|
|
355
|
+
placeholder: z.boolean().optional().describe("Force a deterministic in-engine stand-in even if a real backend is configured"),
|
|
356
|
+
confirm: z.boolean().optional().describe("Auto-approve this destructive action (skip the confirmation prompt)"),
|
|
357
|
+
},
|
|
358
|
+
}, async ({ prompt, to_path, shape, placeholder, confirm }) => generate("model", { prompt, to_path, shape, placeholder, confirm }));
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=assetgen.js.map
|