@womp/spark-agent 3.0.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -33
- package/dist/agent-run-cli.d.ts +2 -0
- package/dist/agent-run-cli.js +30 -0
- package/dist/agent-run-input.d.ts +11 -0
- package/dist/agent-run-input.js +59 -0
- package/dist/agent-run.d.ts +81 -0
- package/dist/agent-run.js +234 -0
- package/dist/agent-tools.js +3 -2
- package/dist/agent.d.ts +0 -2
- package/dist/agent.js +4 -36
- package/dist/capabilities/account.js +15 -15
- package/dist/capabilities/asset-imports.js +50 -50
- package/dist/capabilities/assets.js +10 -10
- package/dist/capabilities/flow.js +15 -15
- package/dist/capabilities/geometry.js +12 -12
- package/dist/capabilities/knowledge.js +4 -4
- package/dist/capabilities/openscad-instructions.d.ts +1 -1
- package/dist/capabilities/openscad-instructions.js +83 -80
- package/dist/capabilities/scene.d.ts +11 -3
- package/dist/capabilities/scene.js +88 -27
- package/dist/capabilities.d.ts +1 -2
- package/dist/capabilities.js +0 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.js +22 -0
- package/dist/coordinates.d.ts +0 -7
- package/dist/coordinates.js +8 -47
- package/dist/generated/scene-declarations.d.ts +1 -1
- package/dist/generated/scene-declarations.js +1 -1
- package/dist/geometry/schemas/flowir-dialect.json +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/models.d.ts +7 -0
- package/dist/models.js +10 -0
- package/dist/prompt.js +18 -18
- package/dist/request-limits.d.ts +1 -0
- package/dist/request-limits.js +1 -0
- package/dist/run-metrics.d.ts +15 -0
- package/dist/run-metrics.js +30 -0
- package/dist/runtime.d.ts +3 -5
- package/dist/scene-metadata.js +1 -1
- package/dist/sdk-scene-host.d.ts +11 -13
- package/dist/sdk-scene-host.js +1 -0
- package/dist/sdk-scene-screenshot.d.ts +4 -6
- package/dist/sdk-scene-screenshot.js +14 -15
- package/dist/server/artifacts.js +1 -5
- package/dist/server/executor.d.ts +0 -1
- package/dist/server/executor.js +0 -1
- package/dist/server/express.js +17 -5
- package/dist/server/scene-actions.d.ts +2 -0
- package/dist/server/scene-actions.js +20 -0
- package/dist/server/service.d.ts +2 -0
- package/dist/server/service.js +10 -0
- package/dist/server/sqlite-store.d.ts +3 -1
- package/dist/server/sqlite-store.js +26 -3
- package/dist/server/standalone.d.ts +2 -0
- package/dist/server/standalone.js +54 -3
- package/dist/server/store.d.ts +3 -1
- package/dist/server/store.js +21 -1
- package/dist/server/types.d.ts +5 -0
- package/dist/simple-bench-cli.d.ts +2 -0
- package/dist/simple-bench-cli.js +64 -0
- package/dist/simple-bench-prompts.d.ts +28 -0
- package/dist/simple-bench-prompts.js +11 -0
- package/dist/simple-bench.d.ts +15 -0
- package/dist/simple-bench.js +175 -0
- package/dist/types.d.ts +4 -13
- package/dist/ui.d.ts +3 -8
- package/dist/womp-scene-api.d.ts +14 -3
- package/dist/womp-scene-api.js +91 -72
- package/package.json +7 -3
- package/dist/capabilities/health.d.ts +0 -9
- package/dist/capabilities/health.js +0 -27
- package/dist/context.d.ts +0 -15
- package/dist/context.js +0 -266
- package/dist/generated/health-declarations.d.ts +0 -1
- package/dist/generated/health-declarations.js +0 -3
package/dist/womp-scene-api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { xyzRotationFromMatrix, MAX_BLEND_RADIUS_MM, MAX_MIRROR_BLEND_RADIUS_MM } from "@womp/kakapo-sdk";
|
|
1
2
|
import { metadataObject, readMaterialMetadata, removeMaterialMetadata, shiftCurvePointMetadata, writeMaterialMetadata, } from "./scene-metadata.js";
|
|
2
3
|
import { createSceneEnvironment } from "./scene-environment.js";
|
|
3
|
-
import { convertRotationBasis, sceneUnitMultiplier, fromEngineDimensions,
|
|
4
|
+
import { convertRotationBasis, sceneUnitMultiplier, fromEngineDimensions, fromEngineMirrorRotation, fromEnginePosition, toEngineDimensions, toEngineMirrorRotation, toEnginePosition, } from "./coordinates.js";
|
|
4
5
|
export const NodeType = {
|
|
5
6
|
Primitive: 0,
|
|
6
7
|
Union: 1,
|
|
@@ -143,12 +144,25 @@ function convertAABB(box, unitMultiplier) {
|
|
|
143
144
|
},
|
|
144
145
|
};
|
|
145
146
|
}
|
|
147
|
+
/** Align local +Z toward target. Both points are in the same parent-local coordinates. */
|
|
146
148
|
function lookAt(origin, target) {
|
|
147
|
-
|
|
149
|
+
if (![origin.x, origin.y, origin.z, target.x, target.y, target.z].every(Number.isFinite)) {
|
|
150
|
+
throw new Error("lookAt requires finite points in the same parent coordinate system.");
|
|
151
|
+
}
|
|
152
|
+
if (Math.hypot(target.x - origin.x, target.y - origin.y, target.z - origin.z) === 0) {
|
|
153
|
+
throw new Error("lookAt requires distinct origin and target points.");
|
|
154
|
+
}
|
|
155
|
+
return alignAxis({
|
|
148
156
|
x: target.x - origin.x,
|
|
149
157
|
y: target.y - origin.y,
|
|
150
158
|
z: target.z - origin.z,
|
|
151
|
-
});
|
|
159
|
+
}, "z");
|
|
160
|
+
}
|
|
161
|
+
/** XYZ degrees aligning the selected positive local axis to a parent-local direction. */
|
|
162
|
+
function alignAxis(direction, axis) {
|
|
163
|
+
if (!["x", "y", "z"].includes(axis))
|
|
164
|
+
throw new Error("Alignment axis must be x, y, or z.");
|
|
165
|
+
const forward = normalize(direction);
|
|
152
166
|
let up = { x: 0, y: 1, z: 0 };
|
|
153
167
|
let right = cross(up, forward);
|
|
154
168
|
if (Math.hypot(right.x, right.y, right.z) < 1e-6) {
|
|
@@ -157,17 +171,19 @@ function lookAt(origin, target) {
|
|
|
157
171
|
}
|
|
158
172
|
right = normalize(right);
|
|
159
173
|
const correctedUp = cross(forward, right);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
const [x, y, z] = axis === "x" ? [forward, right, correctedUp]
|
|
175
|
+
: axis === "y" ? [right, forward, scale(correctedUp, -1)] : [right, correctedUp, forward];
|
|
176
|
+
return xyzRotationFromMatrix([
|
|
177
|
+
[x.x, y.x, z.x],
|
|
178
|
+
[x.y, y.y, z.y],
|
|
179
|
+
[x.z, y.z, z.z],
|
|
180
|
+
]);
|
|
165
181
|
}
|
|
166
182
|
function normalize(value) {
|
|
167
183
|
const length = Math.hypot(value.x, value.y, value.z);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
184
|
+
if (!Number.isFinite(length) || length === 0)
|
|
185
|
+
throw new Error("Cannot orient a zero-length or non-finite direction.");
|
|
186
|
+
return { x: value.x / length, y: value.y / length, z: value.z / length };
|
|
171
187
|
}
|
|
172
188
|
function cross(left, right) {
|
|
173
189
|
return {
|
|
@@ -286,12 +302,6 @@ export function createWompSceneApi(context) {
|
|
|
286
302
|
rotation: { ...plane.rotation },
|
|
287
303
|
...(plane.customRotation ? { customRotation: { ...plane.customRotation } } : {}),
|
|
288
304
|
}));
|
|
289
|
-
const curvePoints = (id) => requireCurve(id).points.map((point) => ({
|
|
290
|
-
...point,
|
|
291
|
-
position: { ...point.position },
|
|
292
|
-
rotation: { ...point.rotation },
|
|
293
|
-
scale: { ...point.scale },
|
|
294
|
-
}));
|
|
295
305
|
const requirePoint = (curveId, pointIndex) => {
|
|
296
306
|
const index = Math.round(finite(pointIndex, "pointIndex"));
|
|
297
307
|
const count = requireCurve(curveId).points.length;
|
|
@@ -338,7 +348,18 @@ export function createWompSceneApi(context) {
|
|
|
338
348
|
},
|
|
339
349
|
}]);
|
|
340
350
|
};
|
|
341
|
-
const
|
|
351
|
+
const addCurvePoint = (curveId, point, pointIndex) => {
|
|
352
|
+
const curve = requireCurve(curveId);
|
|
353
|
+
const count = curve.points.length;
|
|
354
|
+
if (count >= MAX_CURVE_POINTS)
|
|
355
|
+
throw new Error("A curve cannot exceed 100 points");
|
|
356
|
+
const index = pointIndex === undefined ? count : pointIndex;
|
|
357
|
+
if (!Number.isInteger(index) || index < 0 || index > count)
|
|
358
|
+
throw new Error(`Curve ${curveId}: index must be an integer from 0 through ${count}`);
|
|
359
|
+
const input = {
|
|
360
|
+
pos: { x: 0, y: 0, z: 0 }, size: { x: 1, y: 1, z: 1 },
|
|
361
|
+
materialId: curve.materialId ?? context.getDefaultMaterialId(), ...point,
|
|
362
|
+
};
|
|
342
363
|
const patch = {};
|
|
343
364
|
const parentScale = ancestorScale(curveId);
|
|
344
365
|
if (input.pos) {
|
|
@@ -358,10 +379,11 @@ export function createWompSceneApi(context) {
|
|
|
358
379
|
context.requireMaterial(materialId);
|
|
359
380
|
patch.materialId = materialId;
|
|
360
381
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
setPointName(curveId, index,
|
|
382
|
+
context.call("addCurvePoint", [curveId, patch, index], () => scene.addCurvePoint(curveId, patch, index));
|
|
383
|
+
shiftCurvePointMetadata(scene, curveId, index, 1);
|
|
384
|
+
if (point.name !== undefined)
|
|
385
|
+
setPointName(curveId, index, point.name);
|
|
386
|
+
return index;
|
|
365
387
|
};
|
|
366
388
|
const setNodeSize = (id, sizeValue) => {
|
|
367
389
|
const node = requireEditableTransform(id);
|
|
@@ -374,8 +396,12 @@ export function createWompSceneApi(context) {
|
|
|
374
396
|
update(id, { size: { x: converted.z, y: converted.y } });
|
|
375
397
|
return;
|
|
376
398
|
}
|
|
377
|
-
if (!("z" in sizeValue))
|
|
378
|
-
|
|
399
|
+
if (!("z" in sizeValue)) {
|
|
400
|
+
if (node.kind !== "primitive" ||
|
|
401
|
+
!["cylinder", "cone", "torus", "pyramid"].includes(node.primitive))
|
|
402
|
+
throw new Error("size.z must be a finite number");
|
|
403
|
+
sizeValue = { ...sizeValue, z: sizeValue.x };
|
|
404
|
+
}
|
|
379
405
|
const requestedSize = vector(sizeValue, "size");
|
|
380
406
|
const size = node.kind === "primitive"
|
|
381
407
|
? normalizePrimitiveSize(node.primitive, requestedSize)
|
|
@@ -404,21 +430,6 @@ export function createWompSceneApi(context) {
|
|
|
404
430
|
}
|
|
405
431
|
return converted;
|
|
406
432
|
};
|
|
407
|
-
const insertCurvePoint = (curveId, pointIndex, point = {}) => {
|
|
408
|
-
const points = curvePoints(curveId);
|
|
409
|
-
if (points.length >= MAX_CURVE_POINTS)
|
|
410
|
-
throw new Error("A curve cannot exceed 100 points");
|
|
411
|
-
const index = Math.round(finite(pointIndex, "pointIndex"));
|
|
412
|
-
if (index < 0 || index > points.length)
|
|
413
|
-
throw new Error("Curve insertion index is out of range");
|
|
414
|
-
if (points.length === 0)
|
|
415
|
-
throw new Error("Cannot add to an empty curve");
|
|
416
|
-
const sourceIndex = Math.max(0, index - 1);
|
|
417
|
-
context.call("cloneCurvePoint", [curveId, sourceIndex, index], () => scene.cloneCurvePoint(curveId, sourceIndex, index));
|
|
418
|
-
shiftCurvePointMetadata(scene, curveId, index, 1);
|
|
419
|
-
applyPointInput(curveId, index, point);
|
|
420
|
-
return index;
|
|
421
|
-
};
|
|
422
433
|
const api = {
|
|
423
434
|
...createSceneEnvironment(scene, context.hdrs, context.print),
|
|
424
435
|
listSuperMaterials: () => {
|
|
@@ -471,29 +482,12 @@ export function createWompSceneApi(context) {
|
|
|
471
482
|
closed: false,
|
|
472
483
|
primitive: "sphere",
|
|
473
484
|
operation: "union",
|
|
474
|
-
blend:
|
|
485
|
+
blend: 0,
|
|
475
486
|
density: 80,
|
|
476
487
|
roundness: 2,
|
|
477
488
|
smoothing: 0.02,
|
|
478
489
|
materialId: context.getDefaultMaterialId(),
|
|
479
|
-
points: [
|
|
480
|
-
{
|
|
481
|
-
position: scale(toEnginePosition({ x: 0, y: 2, z: 0 }), 1 / parentScale),
|
|
482
|
-
rotation: { x: 0, y: 0, z: 0 },
|
|
483
|
-
scale: scale(toEngineDimensions({ x: 4, y: 4, z: 4 }), 1 / parentScale),
|
|
484
|
-
materialId: context.getDefaultMaterialId(),
|
|
485
|
-
fixed: false,
|
|
486
|
-
round: 0,
|
|
487
|
-
},
|
|
488
|
-
{
|
|
489
|
-
position: scale(toEnginePosition({ x: 6, y: 2, z: 0 }), 1 / parentScale),
|
|
490
|
-
rotation: { x: 0, y: 0, z: 0 },
|
|
491
|
-
scale: scale(toEngineDimensions({ x: 4, y: 4, z: 4 }), 1 / parentScale),
|
|
492
|
-
materialId: context.getDefaultMaterialId(),
|
|
493
|
-
fixed: false,
|
|
494
|
-
round: 0,
|
|
495
|
-
},
|
|
496
|
-
],
|
|
490
|
+
points: [],
|
|
497
491
|
}
|
|
498
492
|
: kind === "text"
|
|
499
493
|
? {
|
|
@@ -533,6 +527,27 @@ export function createWompSceneApi(context) {
|
|
|
533
527
|
context.trackNode(clone.id);
|
|
534
528
|
return clone.id;
|
|
535
529
|
},
|
|
530
|
+
/** Deep clone; preserves sibling order and remaps internal references. Await before other edits. */
|
|
531
|
+
cloneSubtree: async (id, options = {}) => {
|
|
532
|
+
if (id === context.rootId)
|
|
533
|
+
throw new Error("The scene root cannot be cloned");
|
|
534
|
+
const check = (nodeId) => {
|
|
535
|
+
requireNode(nodeId);
|
|
536
|
+
for (const child of scene.getNodeChildren(nodeId))
|
|
537
|
+
check(child.id);
|
|
538
|
+
};
|
|
539
|
+
check(id);
|
|
540
|
+
if (options.parentId !== undefined)
|
|
541
|
+
requireNode(options.parentId);
|
|
542
|
+
const clone = await context.callAsync("cloneSubtree", [id, options], () => scene.cloneSubtree(id, options));
|
|
543
|
+
const track = (nodeId) => {
|
|
544
|
+
context.trackNode(nodeId);
|
|
545
|
+
for (const child of scene.getNodeChildren(nodeId))
|
|
546
|
+
track(child.id);
|
|
547
|
+
};
|
|
548
|
+
track(clone.id);
|
|
549
|
+
return clone.id;
|
|
550
|
+
},
|
|
536
551
|
nodeList: () => scene
|
|
537
552
|
.listNodes()
|
|
538
553
|
.filter((node) => node.id !== context.rootId)
|
|
@@ -642,11 +657,13 @@ export function createWompSceneApi(context) {
|
|
|
642
657
|
}
|
|
643
658
|
context.call("setNodeOperation", [id, value], () => scene.setNodeOperation(id, value));
|
|
644
659
|
},
|
|
660
|
+
/** Blend radius in millimetres before parent scaling, independent of project units. */
|
|
645
661
|
nodeGetBlend: (id) => {
|
|
646
662
|
const node = requireNode(id);
|
|
647
|
-
return "blend" in node ?
|
|
663
|
+
return "blend" in node ? node.blend : 0;
|
|
648
664
|
},
|
|
649
|
-
|
|
665
|
+
/** Set blend radius in millimetres before parent scaling; clamps to 0–500. */
|
|
666
|
+
nodeSetBlend: (id, blend) => update(id, { blend: Math.min(MAX_BLEND_RADIUS_MM, Math.max(0, finite(blend, "Blend radius (mm)"))) }),
|
|
650
667
|
nodeGetRound: (id) => {
|
|
651
668
|
const node = requireNode(id);
|
|
652
669
|
if (node.kind !== "primitive" && node.kind !== "text")
|
|
@@ -673,11 +690,11 @@ export function createWompSceneApi(context) {
|
|
|
673
690
|
throw new Error("Resolution is only valid for Unions");
|
|
674
691
|
return node.resolution;
|
|
675
692
|
},
|
|
676
|
-
|
|
677
|
-
const node = requireNode(
|
|
693
|
+
nodeSetUnionResolution: (unionId, resolution) => {
|
|
694
|
+
const node = requireNode(unionId);
|
|
678
695
|
if (node.kind !== "union")
|
|
679
|
-
throw new Error(
|
|
680
|
-
update(
|
|
696
|
+
throw new Error(`Node ${unionId} (${node.kind}): resolution belongs to an Area/Union, not this node`);
|
|
697
|
+
update(unionId, { resolution: clamp(finite(resolution, "resolution"), 0.01, 0.65) });
|
|
681
698
|
},
|
|
682
699
|
nodeGetLightType: (id) => LIGHT_TYPES.indexOf(requireLight(id).lightType),
|
|
683
700
|
nodeSetLightType: (id, lightType) => {
|
|
@@ -687,7 +704,12 @@ export function createWompSceneApi(context) {
|
|
|
687
704
|
update(id, { lightType: value });
|
|
688
705
|
},
|
|
689
706
|
nodeGetPower: (id) => requireLight(id).power,
|
|
690
|
-
nodeSetPower: (id, power) =>
|
|
707
|
+
nodeSetPower: (id, power) => {
|
|
708
|
+
requireLight(id);
|
|
709
|
+
if (!Number.isFinite(power) || power < 0 || power > 100)
|
|
710
|
+
throw new Error(`Light ${id}: power must be a finite number from 0 through 100`);
|
|
711
|
+
update(id, { power });
|
|
712
|
+
},
|
|
691
713
|
nodeGetText: (id) => requireText(id).text,
|
|
692
714
|
nodeSetText: (id, text) => update(id, { text: String(text) }),
|
|
693
715
|
nodeGetFont: (id) => requireText(id).fontFamily,
|
|
@@ -768,7 +790,7 @@ export function createWompSceneApi(context) {
|
|
|
768
790
|
return {
|
|
769
791
|
axis: axis ?? "custom",
|
|
770
792
|
planePosition: axis ? position[axis] : 0,
|
|
771
|
-
goop:
|
|
793
|
+
goop: plane.value,
|
|
772
794
|
};
|
|
773
795
|
},
|
|
774
796
|
nodeSetMirror: (id, slot, axis, planePosition = 0, goop = 0) => {
|
|
@@ -790,7 +812,7 @@ export function createWompSceneApi(context) {
|
|
|
790
812
|
mirrors[index] = {
|
|
791
813
|
position: scale(toEnginePosition(publicPosition, unitMultiplier), 1 / ancestorScale(id)),
|
|
792
814
|
rotation,
|
|
793
|
-
value:
|
|
815
|
+
value: Math.min(MAX_MIRROR_BLEND_RADIUS_MM, Math.max(0, finite(goop, "Mirror radius (mm)"))),
|
|
794
816
|
hide: 0,
|
|
795
817
|
hideMirrorPlane: true,
|
|
796
818
|
constraint,
|
|
@@ -815,12 +837,8 @@ export function createWompSceneApi(context) {
|
|
|
815
837
|
curvePointSetHandleIn: (curveId, pointIndex, value) => setPointHandle(curveId, pointIndex, "handleIn", value),
|
|
816
838
|
curvePointSetHandleOut: (curveId, pointIndex, value) => setPointHandle(curveId, pointIndex, "handleOut", value),
|
|
817
839
|
curvePointCount: (curveId) => requireCurve(curveId).points.length,
|
|
818
|
-
curvePointAdd:
|
|
819
|
-
curvePointInsert: insertCurvePoint,
|
|
840
|
+
curvePointAdd: addCurvePoint,
|
|
820
841
|
curvePointRemove: (curveId, pointIndex) => {
|
|
821
|
-
const points = curvePoints(curveId);
|
|
822
|
-
if (points.length <= 1)
|
|
823
|
-
throw new Error("A curve must keep at least one point");
|
|
824
842
|
const index = requirePoint(curveId, pointIndex);
|
|
825
843
|
context.call("deleteCurvePoint", [curveId, index], () => scene.deleteCurvePoint(curveId, index));
|
|
826
844
|
shiftCurvePointMetadata(scene, curveId, index, -1);
|
|
@@ -969,6 +987,7 @@ export function createWompSceneApi(context) {
|
|
|
969
987
|
}),
|
|
970
988
|
aabbRadius: (box) => Math.hypot(box.max.x - box.min.x, box.max.y - box.min.y, box.max.z - box.min.z) / 2,
|
|
971
989
|
lookAt,
|
|
990
|
+
alignAxis,
|
|
972
991
|
};
|
|
973
992
|
return api;
|
|
974
993
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@womp/spark-agent",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Portable skills-based AI agent with local and durable chat transports for Womp",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
|
-
"spark-agent": "dist/cli.js"
|
|
9
|
+
"spark-agent": "dist/cli.js",
|
|
10
|
+
"simple-bench": "dist/simple-bench-cli.js",
|
|
11
|
+
"agent-run": "dist/agent-run-cli.js"
|
|
10
12
|
},
|
|
11
13
|
"exports": {
|
|
12
14
|
"./browser": {
|
|
@@ -92,6 +94,8 @@
|
|
|
92
94
|
"lint": "yarn typecheck",
|
|
93
95
|
"dev": "yarn build && concurrently --kill-others \"yarn dev:server\" \"yarn dev:web\"",
|
|
94
96
|
"dev:server": "tsx src/cli.ts",
|
|
97
|
+
"simple-bench": "tsx src/simple-bench-cli.ts",
|
|
98
|
+
"agent-run": "tsx src/agent-run-cli.ts",
|
|
95
99
|
"dev:demo-account": "yarn build && concurrently --kill-others \"yarn dev:server --profile demo-account\" \"yarn dev:web\"",
|
|
96
100
|
"dev:web": "vite --config web/vite.config.ts",
|
|
97
101
|
"start": "node dist/cli.js",
|
|
@@ -106,7 +110,7 @@
|
|
|
106
110
|
"@ai-sdk/openai": "4.0.63",
|
|
107
111
|
"@jitl/quickjs-wasmfile-release-sync": "0.32.0",
|
|
108
112
|
"@womp/flowir": "0.3.0",
|
|
109
|
-
"@womp/kakapo-sdk": "0.
|
|
113
|
+
"@womp/kakapo-sdk": "0.4.0",
|
|
110
114
|
"ai": "7.0.95",
|
|
111
115
|
"eventsource-parser": "^3.0.6",
|
|
112
116
|
"express": "^5.2.1",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { SparkCapability, SparkInvocationContext } from "../capabilities.js";
|
|
2
|
-
import type { SceneHealth } from "../scene-health.js";
|
|
3
|
-
export type WompHealthApi = {
|
|
4
|
-
read(objectIds: number[]): Promise<SceneHealth>;
|
|
5
|
-
};
|
|
6
|
-
export interface SceneHealthHost {
|
|
7
|
-
read(objectIds: number[], context: SparkInvocationContext): Promise<SceneHealth>;
|
|
8
|
-
}
|
|
9
|
-
export declare function createSceneHealthCapability(host: SceneHealthHost): SparkCapability<WompHealthApi>;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { WOMP_HEALTH_DECLARATIONS } from "../generated/health-declarations.js";
|
|
3
|
-
export function createSceneHealthCapability(host) {
|
|
4
|
-
return {
|
|
5
|
-
skills: [
|
|
6
|
-
{
|
|
7
|
-
name: "scene-health",
|
|
8
|
-
version: "1",
|
|
9
|
-
description: "Required before scene checks or procedural edits: engine limits, empty Areas, fresh geometry feedback, and bounded repairs.",
|
|
10
|
-
instructions: `Before checking or editing a model, call womp.health.read([]) for current scene health. A health read is a snapshot and does not prove that the latest edit finished evaluating. Inspect scene edit receipts before claiming success.
|
|
11
|
-
For confirmed primitive or depth overflow, repair the same target while preserving design, dimensions, active parameter overrides, and unrelated objects. At most two geometry repairs are allowed per target in one logical run. Unverified feedback must not trigger blind regeneration or a claim that the model passed. Empty Areas may be pending, intentionally empty, mesh-only, or failed. Overflow flags take precedence over possibly stale counts. max_total_depth is a scene hierarchy limit, not measured Boolean depth.`,
|
|
12
|
-
declarations: WOMP_HEALTH_DECLARATIONS,
|
|
13
|
-
examples: ["print(await womp.health.read([]));"],
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
createApi: (context) => ({
|
|
17
|
-
read: (objectIds) => {
|
|
18
|
-
const parsed = z
|
|
19
|
-
.array(z.number().int().positive())
|
|
20
|
-
.safeParse(objectIds);
|
|
21
|
-
if (parsed.success === false)
|
|
22
|
-
return Promise.reject(parsed.error);
|
|
23
|
-
return host.read(parsed.data, context);
|
|
24
|
-
},
|
|
25
|
-
}),
|
|
26
|
-
};
|
|
27
|
-
}
|
package/dist/context.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ModelMessage } from "ai";
|
|
2
|
-
/** Host input budget, not a claim about a provider's maximum context window. */
|
|
3
|
-
export type SparkContextPolicy = {
|
|
4
|
-
maxInputTokens: number;
|
|
5
|
-
};
|
|
6
|
-
export declare class SparkContextLimitError extends Error {
|
|
7
|
-
constructor();
|
|
8
|
-
}
|
|
9
|
-
/** Heuristic, not provider tokenization. Encoded media bytes are not text tokens. */
|
|
10
|
-
export declare function estimateSparkContextTokens(value: unknown): number;
|
|
11
|
-
/**
|
|
12
|
-
* Project model input only. Never mutate persisted messages, remove call/result
|
|
13
|
-
* pairs, summarize user requests, or discard receipts, references, and failures.
|
|
14
|
-
*/
|
|
15
|
-
export declare function prepareSparkContext(messages: ModelMessage[], system: string, policy: SparkContextPolicy): ModelMessage[];
|
package/dist/context.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
export class SparkContextLimitError extends Error {
|
|
2
|
-
constructor() {
|
|
3
|
-
super("This conversation is too large to continue without losing required context. Completed work and full history are saved. Start a new conversation with the current target references and remaining task; inspect existing results before retrying any changes.");
|
|
4
|
-
this.name = "SparkContextLimitError";
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
function record(value) {
|
|
8
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
9
|
-
? value
|
|
10
|
-
: undefined;
|
|
11
|
-
}
|
|
12
|
-
/** Heuristic, not provider tokenization. Encoded media bytes are not text tokens. */
|
|
13
|
-
export function estimateSparkContextTokens(value) {
|
|
14
|
-
let mediaTokens = 0;
|
|
15
|
-
const json = JSON.stringify(value, (_key, item) => {
|
|
16
|
-
const part = record(item);
|
|
17
|
-
if (part &&
|
|
18
|
-
(part.type === "image" || part.type === "file" || part.type === "reasoning-file")) {
|
|
19
|
-
const isImage = part.type === "image" ||
|
|
20
|
-
(typeof part.mediaType === "string" && part.mediaType.startsWith("image/"));
|
|
21
|
-
mediaTokens += isImage ? 512 : 8192;
|
|
22
|
-
return { type: part.type, mediaType: part.mediaType };
|
|
23
|
-
}
|
|
24
|
-
return item;
|
|
25
|
-
}) ?? "";
|
|
26
|
-
return Math.ceil(new TextEncoder().encode(json).byteLength / 3) + mediaTokens;
|
|
27
|
-
}
|
|
28
|
-
function executionOutput(part) {
|
|
29
|
-
if (part.toolName !== "execute_javascript" || part.output.type !== "content")
|
|
30
|
-
return;
|
|
31
|
-
const first = part.output.value[0];
|
|
32
|
-
if (first?.type !== "text")
|
|
33
|
-
return;
|
|
34
|
-
try {
|
|
35
|
-
const value = record(JSON.parse(first.text));
|
|
36
|
-
if (value?.version === 2 && typeof value.ok === "boolean")
|
|
37
|
-
return value;
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// Legacy and unrecognized results remain verbatim.
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function shortenSkills(messages, keepLatest) {
|
|
44
|
-
let retained = 0;
|
|
45
|
-
const result = [...messages];
|
|
46
|
-
for (let index = result.length - 1; index >= 0; index--) {
|
|
47
|
-
const message = result[index];
|
|
48
|
-
if (message.role !== "tool")
|
|
49
|
-
continue;
|
|
50
|
-
result[index] = {
|
|
51
|
-
...message,
|
|
52
|
-
content: message.content.map((part) => {
|
|
53
|
-
if (part.type !== "tool-result" ||
|
|
54
|
-
part.toolName !== "read_skill" ||
|
|
55
|
-
part.output.type !== "json")
|
|
56
|
-
return part;
|
|
57
|
-
const skill = record(part.output.value);
|
|
58
|
-
if (skill?.ok !== true ||
|
|
59
|
-
typeof skill.name !== "string" ||
|
|
60
|
-
(typeof skill.instructions !== "string" && typeof skill.declarations !== "string"))
|
|
61
|
-
return part;
|
|
62
|
-
if (retained++ < keepLatest)
|
|
63
|
-
return part;
|
|
64
|
-
const summary = {
|
|
65
|
-
ok: true,
|
|
66
|
-
name: skill.name,
|
|
67
|
-
version: typeof skill.version === "string" ? skill.version : "",
|
|
68
|
-
contextNote: "This older skill body was removed from model context. Call read_skill again before using its API; do not guess its declarations.",
|
|
69
|
-
};
|
|
70
|
-
if (JSON.stringify(summary).length >= JSON.stringify(skill).length)
|
|
71
|
-
return part;
|
|
72
|
-
return { ...part, output: { type: "json", value: summary } };
|
|
73
|
-
}),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Project model input only. Never mutate persisted messages, remove call/result
|
|
80
|
-
* pairs, summarize user requests, or discard receipts, references, and failures.
|
|
81
|
-
*/
|
|
82
|
-
export function prepareSparkContext(messages, system, policy) {
|
|
83
|
-
if (!Number.isSafeInteger(policy.maxInputTokens) || policy.maxInputTokens < 1)
|
|
84
|
-
throw new Error("maxInputTokens must be a positive integer.");
|
|
85
|
-
// Reserve room for the two tool schemas and provider message framing.
|
|
86
|
-
const fits = (input) => estimateSparkContextTokens({ system, messages: input }) + 2048 <= policy.maxInputTokens;
|
|
87
|
-
if (fits(messages))
|
|
88
|
-
return messages;
|
|
89
|
-
const seenSkills = new Map();
|
|
90
|
-
const seenOutputs = new Map();
|
|
91
|
-
const compactableCalls = new Set();
|
|
92
|
-
const lastUser = messages.reduce((last, message, index) => (message.role === "user" ? index : last), -1);
|
|
93
|
-
const prepared = messages.map((message) => ({ ...message }));
|
|
94
|
-
for (let index = prepared.length - 1; index >= 0; index--) {
|
|
95
|
-
const message = prepared[index];
|
|
96
|
-
if (message.role !== "tool")
|
|
97
|
-
continue;
|
|
98
|
-
prepared[index] = {
|
|
99
|
-
...message,
|
|
100
|
-
content: message.content.map((part) => {
|
|
101
|
-
if (part.type !== "tool-result")
|
|
102
|
-
return part;
|
|
103
|
-
if (part.toolName === "read_skill" && part.output.type === "json") {
|
|
104
|
-
const skill = record(part.output.value);
|
|
105
|
-
if (skill?.ok !== true || typeof skill.name !== "string")
|
|
106
|
-
return part;
|
|
107
|
-
const key = JSON.stringify(skill);
|
|
108
|
-
const later = seenSkills.get(key);
|
|
109
|
-
if (later === undefined) {
|
|
110
|
-
seenSkills.set(key, part.toolCallId);
|
|
111
|
-
return part;
|
|
112
|
-
}
|
|
113
|
-
const summary = {
|
|
114
|
-
ok: true,
|
|
115
|
-
name: skill.name,
|
|
116
|
-
version: typeof skill.version === "string" ? skill.version : "",
|
|
117
|
-
contextNote: `Identical skill content is retained in tool result ${later}. Read the skill again if needed.`,
|
|
118
|
-
};
|
|
119
|
-
if (JSON.stringify(summary).length >= key.length)
|
|
120
|
-
return part;
|
|
121
|
-
return {
|
|
122
|
-
...part,
|
|
123
|
-
output: { type: "json", value: summary },
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const output = executionOutput(part);
|
|
127
|
-
if (!output || part.output.type !== "content")
|
|
128
|
-
return part;
|
|
129
|
-
const receipts = Array.isArray(output.receipts) ? output.receipts : [];
|
|
130
|
-
const settled = output.ok === true &&
|
|
131
|
-
receipts.every((receipt) => record(receipt)?.status === "committed");
|
|
132
|
-
// Preserve code for unresolved work and for the current user turn.
|
|
133
|
-
if (index < lastUser && settled && receipts.length > 0)
|
|
134
|
-
compactableCalls.add(part.toolCallId);
|
|
135
|
-
if (!settled)
|
|
136
|
-
return part;
|
|
137
|
-
const key = JSON.stringify({ value: output.value, logs: output.logs });
|
|
138
|
-
const later = seenOutputs.get(key);
|
|
139
|
-
if (later === undefined) {
|
|
140
|
-
seenOutputs.set(key, part.toolCallId);
|
|
141
|
-
return part;
|
|
142
|
-
}
|
|
143
|
-
const abbreviated = {
|
|
144
|
-
...output,
|
|
145
|
-
value: null,
|
|
146
|
-
logs: [`Identical returned value and logs are retained in tool result ${later}.`],
|
|
147
|
-
contextNote: "Only duplicate observations were omitted. This call's own receipts and artifacts remain below.",
|
|
148
|
-
};
|
|
149
|
-
const text = JSON.stringify(abbreviated);
|
|
150
|
-
if (text.length >= JSON.stringify(output).length)
|
|
151
|
-
return part;
|
|
152
|
-
return {
|
|
153
|
-
...part,
|
|
154
|
-
output: {
|
|
155
|
-
...part.output,
|
|
156
|
-
value: [{ type: "text", text }, ...part.output.value.slice(1)],
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
if (fits(prepared))
|
|
163
|
-
return prepared;
|
|
164
|
-
// Prefer keeping the latest workflow and reference together while there is room.
|
|
165
|
-
const withRecentSkills = shortenSkills(prepared, 2);
|
|
166
|
-
if (fits(withRecentSkills))
|
|
167
|
-
return withRecentSkills;
|
|
168
|
-
const withoutOldCode = withRecentSkills.map((message) => {
|
|
169
|
-
if (message.role !== "assistant" ||
|
|
170
|
-
typeof message.content === "string" ||
|
|
171
|
-
message.providerOptions)
|
|
172
|
-
return message;
|
|
173
|
-
if (message.content.some((part) => part.type === "reasoning" ||
|
|
174
|
-
part.type === "reasoning-file" ||
|
|
175
|
-
("providerOptions" in part && part.providerOptions)))
|
|
176
|
-
return message;
|
|
177
|
-
return {
|
|
178
|
-
...message,
|
|
179
|
-
content: message.content.map((part) => {
|
|
180
|
-
// Signed/provider-owned tool inputs must remain byte-for-byte intact.
|
|
181
|
-
if (part.type !== "tool-call" ||
|
|
182
|
-
part.toolName !== "execute_javascript" ||
|
|
183
|
-
part.providerOptions ||
|
|
184
|
-
!compactableCalls.has(part.toolCallId))
|
|
185
|
-
return part;
|
|
186
|
-
const input = record(part.input);
|
|
187
|
-
if (typeof input?.code !== "string")
|
|
188
|
-
return part;
|
|
189
|
-
const code = "// Historical code omitted from model context. This completed call must not be replayed; inspect its preserved result and receipts.";
|
|
190
|
-
if (input.code.length <= code.length)
|
|
191
|
-
return part;
|
|
192
|
-
return {
|
|
193
|
-
...part,
|
|
194
|
-
input: { ...input, code },
|
|
195
|
-
};
|
|
196
|
-
}),
|
|
197
|
-
};
|
|
198
|
-
});
|
|
199
|
-
if (fits(withoutOldCode))
|
|
200
|
-
return withoutOldCode;
|
|
201
|
-
// Older visual observations can be recovered from their retained artifact references.
|
|
202
|
-
// Keep the latest visual result and all user/provider-owned media verbatim.
|
|
203
|
-
// SDK call metadata can also appear on local tool results; preserve that metadata
|
|
204
|
-
// without treating the host's subsequently produced output as signed model content.
|
|
205
|
-
let latestVisualCall;
|
|
206
|
-
for (const message of withoutOldCode) {
|
|
207
|
-
if (message.role !== "tool")
|
|
208
|
-
continue;
|
|
209
|
-
for (const part of message.content) {
|
|
210
|
-
if (part.type !== "tool-result" || part.output.type !== "content")
|
|
211
|
-
continue;
|
|
212
|
-
if (part.output.value.some((item) => item.type === "file"))
|
|
213
|
-
latestVisualCall = part.toolCallId;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
for (let index = 0; index < withoutOldCode.length; index++) {
|
|
217
|
-
const message = withoutOldCode[index];
|
|
218
|
-
if (message.role !== "tool" || message.providerOptions)
|
|
219
|
-
continue;
|
|
220
|
-
withoutOldCode[index] = {
|
|
221
|
-
...message,
|
|
222
|
-
content: message.content.map((part) => {
|
|
223
|
-
if (part.type !== "tool-result" ||
|
|
224
|
-
part.toolCallId === latestVisualCall ||
|
|
225
|
-
part.output.type !== "content")
|
|
226
|
-
return part;
|
|
227
|
-
const output = executionOutput(part);
|
|
228
|
-
if (output?.ok !== true ||
|
|
229
|
-
!Array.isArray(output.artifacts) ||
|
|
230
|
-
output.artifacts.length === 0)
|
|
231
|
-
return part;
|
|
232
|
-
if (output.artifacts.some((item) => typeof record(record(item)?.artifact)?.id !== "string"))
|
|
233
|
-
return part;
|
|
234
|
-
const receipts = Array.isArray(output.receipts) ? output.receipts : [];
|
|
235
|
-
if (receipts.some((receipt) => record(receipt)?.status !== "committed"))
|
|
236
|
-
return part;
|
|
237
|
-
if (!part.output.value.some((item) => item.type === "file"))
|
|
238
|
-
return part;
|
|
239
|
-
if (part.output.value.some((item) => "providerOptions" in item && item.providerOptions))
|
|
240
|
-
return part;
|
|
241
|
-
const abbreviated = {
|
|
242
|
-
...output,
|
|
243
|
-
contextNote: "Older image/file bytes were omitted from model context. The original artifact references remain in this result and full stored history. Use emitImage or emitFile with those references when you need to inspect them again.",
|
|
244
|
-
};
|
|
245
|
-
return {
|
|
246
|
-
...part,
|
|
247
|
-
output: {
|
|
248
|
-
type: "content",
|
|
249
|
-
value: [
|
|
250
|
-
{ type: "text", text: JSON.stringify(abbreviated) },
|
|
251
|
-
...part.output.value.slice(1).filter((item) => item.type !== "file"),
|
|
252
|
-
],
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}),
|
|
256
|
-
};
|
|
257
|
-
if (fits(withoutOldCode))
|
|
258
|
-
return withoutOldCode;
|
|
259
|
-
}
|
|
260
|
-
// Documentation can be reread; keep current observations and receipts instead
|
|
261
|
-
// of stopping solely to retain a second skill body.
|
|
262
|
-
const withLatestSkill = shortenSkills(withoutOldCode, 1);
|
|
263
|
-
if (fits(withLatestSkill))
|
|
264
|
-
return withLatestSkill;
|
|
265
|
-
throw new SparkContextLimitError();
|
|
266
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const WOMP_HEALTH_DECLARATIONS = "declare const womp: { health: {\n\"read\": ((objectIds: Array<number>) => Promise<{\n\"status\": \"within_limits\" | \"overflow\" | \"unverified\";\n\"freshness\": \"unverified\" | \"snapshot\" | \"post_ack_event\";\n\"revisionVerified\": false;\n\"affectedAreaIds\": Array<string>;\n\"emptyAreaIds\"?: undefined | Array<string>;\n\"thermometer\"?: undefined | {\n\"objects\": {\n[key: string]: {\n\"num_prims\": number;\n\"flag_prim_overflow\": number;\n\"flag_depth_overflow\": number;\n\"flag_vox_overflow\": number;\n\"voxels\": number;\n};\n};\n\"max_num_prims\": number;\n\"max_total_depth\": number;\n\"max_per_object\": number;\n\"total\": number;\n\"max_total\": number;\n\"assets_budget_used\": number;\n\"assets_budget_total\": number;\n\"mesh_instances_used\"?: undefined | number;\n\"mesh_instances_total\"?: undefined | number;\n\"decal_instances_used\"?: undefined | number;\n\"decal_instances_total\"?: undefined | number;\n\"field_instances_used\"?: undefined | number;\n\"field_instances_total\"?: undefined | number;\n};\n\"reason\"?: undefined | string;\n}>);\n} };\ndeclare function print(...values: unknown[]): void;\ndeclare function emitImage(ref: { id: string; mediaType: string; name?: string }): Promise<void>;\ndeclare function emitFile(ref: { id: string; mediaType: string; name?: string }): Promise<void>;\n";
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// Generated by scripts/generate-skill-declarations.mjs.
|
|
2
|
-
// prettier-ignore
|
|
3
|
-
export const WOMP_HEALTH_DECLARATIONS = "declare const womp: { health: {\n\"read\": ((objectIds: Array<number>) => Promise<{\n\"status\": \"within_limits\" | \"overflow\" | \"unverified\";\n\"freshness\": \"unverified\" | \"snapshot\" | \"post_ack_event\";\n\"revisionVerified\": false;\n\"affectedAreaIds\": Array<string>;\n\"emptyAreaIds\"?: undefined | Array<string>;\n\"thermometer\"?: undefined | {\n\"objects\": {\n[key: string]: {\n\"num_prims\": number;\n\"flag_prim_overflow\": number;\n\"flag_depth_overflow\": number;\n\"flag_vox_overflow\": number;\n\"voxels\": number;\n};\n};\n\"max_num_prims\": number;\n\"max_total_depth\": number;\n\"max_per_object\": number;\n\"total\": number;\n\"max_total\": number;\n\"assets_budget_used\": number;\n\"assets_budget_total\": number;\n\"mesh_instances_used\"?: undefined | number;\n\"mesh_instances_total\"?: undefined | number;\n\"decal_instances_used\"?: undefined | number;\n\"decal_instances_total\"?: undefined | number;\n\"field_instances_used\"?: undefined | number;\n\"field_instances_total\"?: undefined | number;\n};\n\"reason\"?: undefined | string;\n}>);\n} };\ndeclare function print(...values: unknown[]): void;\ndeclare function emitImage(ref: { id: string; mediaType: string; name?: string }): Promise<void>;\ndeclare function emitFile(ref: { id: string; mediaType: string; name?: string }): Promise<void>;\n";
|