@instruktlabs/kiln 1.1.0 → 1.2.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.
Files changed (56) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +28 -0
  4. package/dist/agent-run.mjs +224 -151
  5. package/dist/build.json +22 -22
  6. package/dist/cli.mjs +229 -151
  7. package/dist/mcp-engine.mjs +400 -147
  8. package/dist/mcp-server.mjs +6 -6
  9. package/dist/viewer/app.js +180 -180
  10. package/dist/viewer/chat.html +183 -183
  11. package/docs/sdk.md +23 -0
  12. package/docs/tools-operations.md +1529 -0
  13. package/lib/asset-widget-uri.d.ts +1 -1
  14. package/lib/asset-widget-uri.js +1 -1
  15. package/lib/discovery/index.d.ts +1 -1
  16. package/lib/discovery/index.js +2 -2
  17. package/lib/discovery/service.d.ts +3 -1
  18. package/lib/discovery/service.js +4 -2
  19. package/lib/engine-identity.d.ts +1 -1
  20. package/lib/engine-identity.js +1 -1
  21. package/lib/mcp-core.d.ts +2 -1
  22. package/lib/mcp-core.js +1 -0
  23. package/lib/mcp-engine.d.ts +2 -0
  24. package/lib/mcp-engine.js +8 -3
  25. package/lib/tools/discovery.d.ts +1 -1
  26. package/lib/tools/discovery.js +11 -9
  27. package/lib/tools/materials.d.ts +1 -1
  28. package/lib/tools/materials.js +44 -14
  29. package/lib/tools/operation-contract.d.ts +8 -0
  30. package/lib/tools/operation-contract.js +37 -0
  31. package/lib/tools/operations.d.ts +3 -0
  32. package/lib/tools/operations.js +154 -0
  33. package/lib/tools/registry.d.ts +7 -4
  34. package/lib/tools/registry.js +73 -24
  35. package/package.json +2 -1
  36. package/plugin.json +1 -1
  37. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  38. package/plugins/kiln-engine/README.md +3 -3
  39. package/plugins/kiln-engine/package-provenance.json +6 -6
  40. package/plugins/kiln-engine/plugin.json +1 -1
  41. package/plugins/kiln-engine/runtime.json +1 -1
  42. package/src/asset-widget-uri.ts +1 -1
  43. package/src/discovery/index.ts +2 -0
  44. package/src/discovery/service.ts +3 -1
  45. package/src/engine-identity.ts +1 -1
  46. package/src/generated/mcp-manifest.json +4 -4
  47. package/src/mcp-core.ts +3 -0
  48. package/src/mcp-engine.ts +11 -1
  49. package/src/tools/discovery.ts +20 -10
  50. package/src/tools/materials.ts +59 -15
  51. package/src/tools/operation-contract.ts +51 -0
  52. package/src/tools/operations.ts +177 -0
  53. package/src/tools/registry.ts +102 -21
  54. package/src/viewer/chat-app.ts +6 -2
  55. package/src/viewer/glb-loader.ts +78 -0
  56. package/src/viewer/scene.ts +9 -26
@@ -0,0 +1,78 @@
1
+ import {
2
+ LoadingManager,
3
+ TextureLoader,
4
+ Texture,
5
+ type Object3D,
6
+ type Mesh,
7
+ type BufferGeometry,
8
+ type Material,
9
+ } from 'three';
10
+ import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
11
+ import { validateAssetGlb } from '../assets';
12
+
13
+ export function disposeAssetModel(model: Object3D) {
14
+ const geometries = new Set<BufferGeometry>();
15
+ const materials = new Set<Material>();
16
+ const textures = new Set<Texture>();
17
+ model.traverse((object) => {
18
+ const mesh = object as Mesh;
19
+ if (!mesh.isMesh) return;
20
+ geometries.add(mesh.geometry);
21
+ for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
22
+ materials.add(material);
23
+ for (const value of Object.values(material))
24
+ if (value instanceof Texture) textures.add(value);
25
+ }
26
+ });
27
+ for (const value of [...geometries, ...materials, ...textures]) value.dispose();
28
+ }
29
+
30
+ /**
31
+ * Parse self-contained GLBs under MCP Apps' img-src data: / connect-src none policy.
32
+ * ImageBitmapLoader fetches even blob/data URLs. Use the documented image handler
33
+ * instead, preserving GLTFLoader's sampler, UV, color-space and extension logic.
34
+ * Only the parser's private JSON copy changes; downloaded GLB bytes remain exact.
35
+ */
36
+ export async function loadAssetGlb(bytes: Uint8Array) {
37
+ validateAssetGlb(bytes);
38
+ const manager = new LoadingManager();
39
+ let imageFailed = false;
40
+ manager.onError = () => {
41
+ imageFailed = true;
42
+ };
43
+ manager.addHandler(/^data:image\//, new TextureLoader(manager));
44
+ manager.setURLModifier((url) => {
45
+ if (!url.startsWith('blob:') && !url.startsWith('data:'))
46
+ throw new Error('External model resources are not loaded');
47
+ return url;
48
+ });
49
+ const loader = new GLTFLoader(manager);
50
+ loader.register((parser) => ({
51
+ name: 'KILN_embedded_images',
52
+ async beforeRoot() {
53
+ for (const image of parser.json.images ?? []) {
54
+ if (image.bufferView === undefined) continue;
55
+ const buffer = new Uint8Array(await parser.getDependency('bufferView', image.bufferView));
56
+ let binary = '';
57
+ for (let i = 0; i < buffer.length; i += 8192)
58
+ binary += String.fromCharCode(...buffer.subarray(i, i + 8192));
59
+ if (!/^image\/(png|jpeg|webp|avif)$/.test(image.mimeType ?? ''))
60
+ throw new Error('Unsupported embedded image type');
61
+ image.uri = `data:${image.mimeType};base64,${btoa(binary)}`;
62
+ delete image.bufferView;
63
+ }
64
+ },
65
+ }));
66
+ const gltf = await loader.parseAsync(Uint8Array.from(bytes).buffer, '');
67
+ if (imageFailed) {
68
+ for (const scene of gltf.scenes) disposeAssetModel(scene);
69
+ throw new Error('An embedded texture could not be decoded.');
70
+ }
71
+ // GLTFLoader deliberately converts failed images to null maps. Never commit a
72
+ // material-degraded scene, including resources loaded later for detached LODs.
73
+ return Object.assign(gltf, {
74
+ assertTexturesLoaded() {
75
+ if (imageFailed) throw new Error('An embedded texture could not be decoded.');
76
+ },
77
+ });
78
+ }
@@ -1,9 +1,8 @@
1
1
  /// <reference lib="dom" />
2
2
  import * as THREE from 'three';
3
- import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
4
3
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
5
4
  import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
6
- import { validateAssetGlb } from '../assets';
5
+ import { loadAssetGlb, disposeAssetModel as disposeModel } from './glb-loader';
7
6
  import { frameAssetBounds } from './framing';
8
7
  import {
9
8
  captureCameraView,
@@ -156,22 +155,6 @@ export function createAssetStage(container: HTMLElement) {
156
155
  ],
157
156
  };
158
157
  };
159
- const disposeModel = (model: THREE.Object3D) => {
160
- const geometries = new Set<THREE.BufferGeometry>();
161
- const materials = new Set<THREE.Material>();
162
- const textures = new Set<THREE.Texture>();
163
- model.traverse((object) => {
164
- const mesh = object as THREE.Mesh;
165
- if (!mesh.isMesh) return;
166
- geometries.add(mesh.geometry);
167
- for (const material of Array.isArray(mesh.material) ? mesh.material : [mesh.material]) {
168
- materials.add(material);
169
- for (const value of Object.values(material))
170
- if (value instanceof THREE.Texture) textures.add(value);
171
- }
172
- });
173
- for (const value of [...geometries, ...materials, ...textures]) value.dispose();
174
- };
175
158
  const reset = () => {
176
159
  if (!root) return;
177
160
  const box = drawnBounds(root);
@@ -263,17 +246,17 @@ export function createAssetStage(container: HTMLElement) {
263
246
  const loadStarted = performance.now();
264
247
  const current = ++generation;
265
248
  cancelMeasurement('The artifact changed during measurement. Measure the new revision again.');
266
- validateAssetGlb(bytes);
267
- const manager = new THREE.LoadingManager();
268
- manager.setURLModifier((url) => {
269
- if (!url.startsWith('blob:') && !url.startsWith('data:'))
270
- throw new Error('External model resources are not loaded');
271
- return url;
272
- });
273
- const parsed = await new GLTFLoader(manager).parseAsync(Uint8Array.from(bytes).buffer, '');
249
+ const parsed = await loadAssetGlb(bytes);
274
250
  // three's loader draws every node; hide what KHR_node_visibility hides before counting.
275
251
  applyNodeVisibility(parsed, [parsed.scene]);
276
252
  const parsedLevels = await loadViewerLevels(parsed);
253
+ try {
254
+ parsed.assertTexturesLoaded();
255
+ } catch (error) {
256
+ disposeModel(parsed.scene);
257
+ for (const level of detachedLevels(parsedLevels)) disposeModel(level);
258
+ throw error;
259
+ }
277
260
  const gltf = acceptLoadedResource(
278
261
  parsed,
279
262
  () => !disposed && current === generation && options.isCurrent?.() !== false,