@compilr-dev/sdk 0.33.0 → 0.33.1

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.
@@ -18,6 +18,37 @@ import { type SceneFile } from '../../canvas/scene.js';
18
18
  export declare const SCENE_TOOL_NAMES: readonly ["scene_create", "scene_get", "scene_add_object", "scene_update_object", "scene_remove_object", "scene_set_camera", "scene_screenshot"];
19
19
  /** "sofa · box · Sofa" rows, indented under their group parent. */
20
20
  export declare function sceneOutline(scene: SceneFile): string;
21
+ /**
22
+ * The most JSON one `scene_get` may return.
23
+ *
24
+ * ⚠️ Sized to stay UNDER the context manager's 8,000-token delegation threshold
25
+ * (`delegationThreshold`, agents lib). A read that crosses it gets auto-summarised — and a
26
+ * summary of a scene loses the exact object ids, which is the one thing the agent reads a scene
27
+ * for. So an oversized read must be refused with instructions, never silently shortened.
28
+ */
29
+ export declare const SCENE_READ_MAX_CHARS = 20000;
30
+ /**
31
+ * What `scene_get` returns: the outline, named objects in full, or the whole scene.
32
+ *
33
+ * ⚠️ THE OUTLINE IS THE DEFAULT ON PURPOSE. This tool used to return
34
+ * `serializeScene(scene)` every time, and its own description told the agent to call it before
35
+ * every edit — about 4,500 tokens for a 51-object house, 11x the outline, on every read of a
36
+ * build loop. It also taught the model to answer in the same register: having just read a page
37
+ * of fully-specified pretty-printed JSON, it would emit one enormous `scene_add_object` and get
38
+ * cut off at the output limit mid-arguments.
39
+ *
40
+ * The outline carries ids, types, names and hierarchy — enough to address anything. Numbers come
41
+ * from `ids`, which is the common case for an edit (the roof, not the house).
42
+ *
43
+ * See 3d-canvas-spec §6.4 for the measurements. Exported for its tests.
44
+ */
45
+ export declare function sceneReadBody(scene: SceneFile, head: string, ids: unknown, full: unknown): {
46
+ ok: true;
47
+ text: string;
48
+ } | {
49
+ ok: false;
50
+ error: string;
51
+ };
21
52
  /**
22
53
  * Render a scene through the host's `renderSceneToImage` — shared by scene_screenshot and
23
54
  * canvas_screenshot (either tool works on a scene).
@@ -47,6 +78,10 @@ export declare function createSceneTools(config: PlatformToolsConfig, options?:
47
78
  title: string;
48
79
  scene?: unknown;
49
80
  project_id?: number;
81
+ }> | import("@compilr-dev/agents").Tool<{
82
+ canvas_id: number;
83
+ ids?: unknown;
84
+ full?: unknown;
50
85
  }> | import("@compilr-dev/agents").Tool<Record<string, unknown> & {
51
86
  canvas_id: number;
52
87
  }> | import("@compilr-dev/agents").Tool<{
@@ -72,6 +72,75 @@ export function sceneOutline(scene) {
72
72
  walk(undefined, 0);
73
73
  return rows.join('\n');
74
74
  }
75
+ /**
76
+ * The most JSON one `scene_get` may return.
77
+ *
78
+ * ⚠️ Sized to stay UNDER the context manager's 8,000-token delegation threshold
79
+ * (`delegationThreshold`, agents lib). A read that crosses it gets auto-summarised — and a
80
+ * summary of a scene loses the exact object ids, which is the one thing the agent reads a scene
81
+ * for. So an oversized read must be refused with instructions, never silently shortened.
82
+ */
83
+ export const SCENE_READ_MAX_CHARS = 20_000;
84
+ /** "roof, walls" — the first few of a list, for an error that has to name what is available. */
85
+ function someIds(ids, limit = 12) {
86
+ return ids.length <= limit
87
+ ? ids.join(', ')
88
+ : `${ids.slice(0, limit).join(', ')}, … (${String(ids.length)} in all)`;
89
+ }
90
+ /**
91
+ * What `scene_get` returns: the outline, named objects in full, or the whole scene.
92
+ *
93
+ * ⚠️ THE OUTLINE IS THE DEFAULT ON PURPOSE. This tool used to return
94
+ * `serializeScene(scene)` every time, and its own description told the agent to call it before
95
+ * every edit — about 4,500 tokens for a 51-object house, 11x the outline, on every read of a
96
+ * build loop. It also taught the model to answer in the same register: having just read a page
97
+ * of fully-specified pretty-printed JSON, it would emit one enormous `scene_add_object` and get
98
+ * cut off at the output limit mid-arguments.
99
+ *
100
+ * The outline carries ids, types, names and hierarchy — enough to address anything. Numbers come
101
+ * from `ids`, which is the common case for an edit (the roof, not the house).
102
+ *
103
+ * See 3d-canvas-spec §6.4 for the measurements. Exported for its tests.
104
+ */
105
+ export function sceneReadBody(scene, head, ids, full) {
106
+ const known = scene.objects.map((o) => o.id);
107
+ const guard = (json, over) => json.length > SCENE_READ_MAX_CHARS
108
+ ? { ok: false, error: over }
109
+ : { ok: true, text: `${head}\n\n${json}` };
110
+ if (ids !== undefined) {
111
+ if (!Array.isArray(ids) || ids.some((i) => typeof i !== 'string')) {
112
+ return { ok: false, error: 'ids must be an array of object id strings.' };
113
+ }
114
+ const wanted = ids;
115
+ if (wanted.length === 0)
116
+ return {
117
+ ok: false,
118
+ error: 'ids was empty. Omit it for the outline, or name the objects you need.',
119
+ };
120
+ const found = scene.objects.filter((o) => wanted.includes(o.id));
121
+ const missing = wanted.filter((i) => !known.includes(i));
122
+ if (found.length === 0) {
123
+ return {
124
+ ok: false,
125
+ error: `No object in this scene has ${missing.length === 1 ? 'that id' : 'those ids'} (${someIds(missing)}). The scene has: ${someIds(known)}. Call scene_get without ids for the outline.`,
126
+ };
127
+ }
128
+ const body = guard(JSON.stringify(found, null, 2), `Those ${String(wanted.length)} objects come to more than ${String(SCENE_READ_MAX_CHARS)} characters. Ask for fewer ids at a time.`);
129
+ // A partial hit still answers, but says what was not there — a silently short list reads as
130
+ // "those objects do not exist".
131
+ if (body.ok && missing.length > 0) {
132
+ return { ok: true, text: `${body.text}\n\nNot in this scene: ${someIds(missing)}.` };
133
+ }
134
+ return body;
135
+ }
136
+ if (full === true) {
137
+ return guard(serializeScene(scene), `This scene's JSON is over ${String(SCENE_READ_MAX_CHARS)} characters, which would be summarised before you saw it and you would lose the ids. Call scene_get without full for the outline, then with ids: [...] for the objects you need.`);
138
+ }
139
+ return {
140
+ ok: true,
141
+ text: `${head}\n\n--- objects (id · type · name) ---\n${sceneOutline(scene)}`,
142
+ };
143
+ }
75
144
  function clampShot(v, fallback) {
76
145
  if (typeof v !== 'number' || !Number.isFinite(v))
77
146
  return fallback;
@@ -231,7 +300,8 @@ export function createSceneTools(config, options) {
231
300
  name: 'scene_create',
232
301
  description: 'Create a 3D SCENE canvas — objects in metres the user can orbit, select, edit and export. ' +
233
302
  'Scenes are JSON data, not HTML/code (never use canvas_write for 3D). Pass only a title for an ' +
234
- 'empty scene, then build it with scene_add_object (up to 50 objects per call), or pass a whole ' +
303
+ 'empty scene, then build it with scene_add_object (up to 50 per call, but see its note on ' +
304
+ 'batch size), or pass a whole ' +
235
305
  'scene { objects: [...], camera?, lights? }. Returns the canvas ID used by every scene_* tool.',
236
306
  inputSchema: {
237
307
  type: 'object',
@@ -284,12 +354,26 @@ export function createSceneTools(config, options) {
284
354
  // ---------------------------------------------------------------------------
285
355
  const sceneGetTool = defineTool({
286
356
  name: 'scene_get',
287
- description: 'Read a 3D scene: the full JSON (pretty-printed) plus a one-line summary. Always read before ' +
288
- 'editing — the user edits scenes too, and object ids come from here.',
357
+ description: 'Read a 3D scene. By default returns a one-line summary and the OUTLINE — every object as ' +
358
+ '"id · type · name", indented by group — which is what you need to address objects. ' +
359
+ 'Always read before editing: the user edits scenes too, and object ids come from here. ' +
360
+ 'For an object\'s numbers (position, size, rotation, colour) pass ids: ["roof", "walls"] ' +
361
+ 'and you get those objects in full. full: true dumps the entire scene JSON and is rarely ' +
362
+ 'worth it — it costs about 11x the outline and is refused on a large scene.',
289
363
  inputSchema: {
290
364
  type: 'object',
291
365
  properties: {
292
366
  canvas_id: { type: 'number', description: 'The scene canvas id.' },
367
+ ids: {
368
+ type: 'array',
369
+ items: { type: 'string' },
370
+ description: 'Return these objects IN FULL (all their fields) instead of the outline. The ids come ' +
371
+ 'from a previous outline. This is the right way to read detail before an edit.',
372
+ },
373
+ full: {
374
+ type: 'boolean',
375
+ description: 'Return the whole scene as JSON. Only for a wholesale rewrite or an export; prefer ids.',
376
+ },
293
377
  },
294
378
  required: ['canvas_id'],
295
379
  },
@@ -298,9 +382,11 @@ export function createSceneTools(config, options) {
298
382
  const r = await writer.read(input.canvas_id);
299
383
  if (!r.ok)
300
384
  return createErrorResult(r.error);
301
- const body = `Scene "${r.title}" (canvas ${String(r.canvasId)}): ${describeScene(r.scene)} · rev ${String(r.rev)}\n\n` +
302
- serializeScene(r.scene);
303
- return { success: true, result: withNotice(r.canvasId, body) };
385
+ const head = `Scene "${r.title}" (canvas ${String(r.canvasId)}): ${describeScene(r.scene)} · rev ${String(r.rev)}`;
386
+ const body = sceneReadBody(r.scene, head, input.ids, input.full);
387
+ if (!body.ok)
388
+ return createErrorResult(body.error);
389
+ return { success: true, result: withNotice(r.canvasId, body.text) };
304
390
  }
305
391
  catch (error) {
306
392
  return createErrorResult(errorMessage('read scene', error));
@@ -314,7 +400,11 @@ export function createSceneTools(config, options) {
314
400
  "Units are metres; position.y is the object's BOTTOM (0 = on the floor); rotation in degrees about Y. " +
315
401
  'Build hierarchically: a type "group" with your own id (e.g. "house", then "walls", "roof" inside it), ' +
316
402
  'and its parts with group: "<id>" — a batch may name a group it creates itself. ' +
317
- 'Returns the new id(s) — yours, or generated from name when id is omitted.',
403
+ 'Returns the new id(s) — yours, or generated from name when id is omitted. ' +
404
+ 'BATCH SIZE: about 15-20 per call once objects carry names, colours, groups and rotations. ' +
405
+ 'Fifty of those is roughly 3,000 tokens of arguments, your reasoning is drawn from the same ' +
406
+ 'output budget, and a call cut off at that limit adds NOTHING and has to be redone — so two ' +
407
+ 'moderate calls finish sooner than one oversized one.',
318
408
  inputSchema: {
319
409
  type: 'object',
320
410
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.33.0",
3
+ "version": "0.33.1",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",