@instruktlabs/kiln 1.1.0 → 1.2.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/agent-run.mjs +220 -150
- package/dist/build.json +22 -22
- package/dist/cli.mjs +225 -150
- package/dist/mcp-engine.mjs +398 -144
- package/dist/mcp-server.mjs +1 -1
- package/docs/sdk.md +23 -0
- package/docs/tools-operations.md +1529 -0
- package/lib/discovery/index.d.ts +1 -1
- package/lib/discovery/index.js +2 -2
- package/lib/discovery/service.d.ts +3 -1
- package/lib/discovery/service.js +4 -2
- package/lib/engine-identity.d.ts +1 -1
- package/lib/engine-identity.js +1 -1
- package/lib/mcp-core.d.ts +2 -1
- package/lib/mcp-core.js +1 -0
- package/lib/mcp-engine.d.ts +2 -0
- package/lib/mcp-engine.js +8 -3
- package/lib/tools/discovery.d.ts +1 -1
- package/lib/tools/discovery.js +11 -9
- package/lib/tools/materials.d.ts +1 -1
- package/lib/tools/materials.js +44 -14
- package/lib/tools/operation-contract.d.ts +8 -0
- package/lib/tools/operation-contract.js +37 -0
- package/lib/tools/operations.d.ts +3 -0
- package/lib/tools/operations.js +154 -0
- package/lib/tools/registry.d.ts +6 -2
- package/lib/tools/registry.js +71 -23
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
- package/plugins/kiln-engine/README.md +3 -3
- package/plugins/kiln-engine/package-provenance.json +6 -6
- package/plugins/kiln-engine/plugin.json +1 -1
- package/plugins/kiln-engine/runtime.json +1 -1
- package/src/discovery/index.ts +2 -0
- package/src/discovery/service.ts +3 -1
- package/src/engine-identity.ts +1 -1
- package/src/mcp-core.ts +3 -0
- package/src/mcp-engine.ts +11 -1
- package/src/tools/discovery.ts +20 -10
- package/src/tools/materials.ts +59 -15
- package/src/tools/operation-contract.ts +51 -0
- package/src/tools/operations.ts +177 -0
- package/src/tools/registry.ts +100 -20
package/lib/discovery/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export type { DiscoveryRequest } from "./query-schema.js";
|
|
|
6
6
|
export { createDiscoveryService } from "./service.js";
|
|
7
7
|
export type { DiscoveryIndex, DiscoveryResponse } from "./service.js";
|
|
8
8
|
/** Share one immutable catalog/index per process; host capabilities stay live. */
|
|
9
|
-
export declare function createDiscovery(capabilities?: () => Promise<unknown>, notes?: () => Promise<string[]
|
|
9
|
+
export declare function createDiscovery(capabilities?: () => Promise<unknown>, notes?: () => Promise<string[]>, capabilitiesCall?: string): (input: unknown) => Promise<import("./service.js").DiscoveryResponse>;
|
package/lib/discovery/index.js
CHANGED
|
@@ -11,10 +11,10 @@ let snapshot;
|
|
|
11
11
|
export function createDiscovery(capabilities = async () => ({
|
|
12
12
|
host: 'No host capability provider was supplied.',
|
|
13
13
|
discovery: { mode: 'lexical', offline: true, requiresModel: false },
|
|
14
|
-
}), notes = async () => []) {
|
|
14
|
+
}), notes = async () => [], capabilitiesCall = 'capabilities:true') {
|
|
15
15
|
snapshot ??= (() => {
|
|
16
16
|
const entries = listDiscoveryEntries();
|
|
17
17
|
return { entries, index: createLexicalDiscoveryIndex(entries) };
|
|
18
18
|
})();
|
|
19
|
-
return createDiscoveryService(snapshot.entries, snapshot.index, capabilities, REMOVED_AUTHORING_HELPERS, notes);
|
|
19
|
+
return createDiscoveryService(snapshot.entries, snapshot.index, capabilities, REMOVED_AUTHORING_HELPERS, notes, capabilitiesCall);
|
|
20
20
|
}
|
|
@@ -53,5 +53,7 @@ export interface DiscoveryResponse {
|
|
|
53
53
|
}
|
|
54
54
|
export declare function createDiscoveryService(source: unknown, index: DiscoveryIndex, capabilities: () => Promise<unknown>, retirements?: Readonly<Record<string, string>>,
|
|
55
55
|
/** Host notes for the overview, such as the project an omitted `projectId` selects. */
|
|
56
|
-
notes?: () => Promise<string[]
|
|
56
|
+
notes?: () => Promise<string[]>,
|
|
57
|
+
/** Host-selected capability entrypoint, never a model-provided instruction. */
|
|
58
|
+
capabilitiesCall?: string): (input: unknown) => Promise<DiscoveryResponse>;
|
|
57
59
|
export {};
|
package/lib/discovery/service.js
CHANGED
|
@@ -7,7 +7,9 @@ const DETAIL_RESULT_BUDGET = 15_000;
|
|
|
7
7
|
const compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
8
8
|
export function createDiscoveryService(source, index, capabilities, retirements = {},
|
|
9
9
|
/** Host notes for the overview, such as the project an omitted `projectId` selects. */
|
|
10
|
-
notes = async () => []
|
|
10
|
+
notes = async () => [],
|
|
11
|
+
/** Host-selected capability entrypoint, never a model-provided instruction. */
|
|
12
|
+
capabilitiesCall = 'capabilities:true') {
|
|
11
13
|
const entries = parseCatalog(source);
|
|
12
14
|
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
13
15
|
const byName = new Map(entries.filter((entry) => entry.kind !== 'recipe').map((entry) => [entry.name, entry]));
|
|
@@ -34,7 +36,7 @@ notes = async () => []) {
|
|
|
34
36
|
'Parts auto-add to opts.parent; do not wrap createPart in parent.add. Part rotation uses degrees.',
|
|
35
37
|
'Custom THREE.BufferGeometry and ordinary JavaScript functions remain available. Recipes offer guidance and never restrict modeling.',
|
|
36
38
|
'Send source once and reuse programRef for reads, edits, inspection and rendering. Review geometry, materials and destination constraints separately.',
|
|
37
|
-
|
|
39
|
+
`Search for the operation you need in ordinary language, then fetch exact contracts with ids. Use ${capabilitiesCall} for the current host.`,
|
|
38
40
|
],
|
|
39
41
|
};
|
|
40
42
|
const error = (mode, code, message) => ({
|
package/lib/engine-identity.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
|
|
27
27
|
* or with any plugin manifest, which is what keeps the literal honest.
|
|
28
28
|
*/
|
|
29
|
-
export declare const ENGINE_VERSION = "1.
|
|
29
|
+
export declare const ENGINE_VERSION = "1.2.0";
|
|
30
30
|
/**
|
|
31
31
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
|
32
32
|
*
|
package/lib/engine-identity.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* `src/__tests__/package-contents.test.ts` fails if it disagrees with the manifest
|
|
27
27
|
* or with any plugin manifest, which is what keeps the literal honest.
|
|
28
28
|
*/
|
|
29
|
-
export const ENGINE_VERSION = '1.
|
|
29
|
+
export const ENGINE_VERSION = '1.2.0';
|
|
30
30
|
/**
|
|
31
31
|
* Where this engine was loaded from, as a `file://` URL of the package root.
|
|
32
32
|
*
|
package/lib/mcp-core.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { type ServeStdioOptions, type StdioServerHandle } from '@modelcontextpro
|
|
|
23
23
|
import type { AssetLink } from "./assets-resources.js";
|
|
24
24
|
/** Server identity reported in the handshake and stamped into every 2026-07-28 result. */
|
|
25
25
|
export declare const MCP_SERVER_NAME = "kiln";
|
|
26
|
-
export declare const MCP_SERVER_VERSION = "1.
|
|
26
|
+
export declare const MCP_SERVER_VERSION = "1.2.0";
|
|
27
27
|
/**
|
|
28
28
|
* The spec's optional `instructions` field, returned by `initialize` and
|
|
29
29
|
* `server/discover`.
|
|
@@ -39,6 +39,7 @@ export declare const MCP_SERVER_VERSION = "1.1.0";
|
|
|
39
39
|
* same bytes serve every installation.
|
|
40
40
|
*/
|
|
41
41
|
export declare const MCP_SERVER_INSTRUCTIONS = "Kiln turns JavaScript you write into GLB assets and returns rendered views. Work by reference: send a program once to kiln_validate or kiln_render, keep the returned programRef exactly, and use it for every later render, inspect, kiln_source and kiln_edit call; never resend a program to change part of it. Call kiln_discover first for helper contracts, with capabilities:true for the host's runtime, project and camera facts. Read viewFidelity before judging materials: a CPU view shows shape, not material; GPU views need the render service in render-service/. Agent Skills for authoring, refining, QA and scenes ship in skills/ beside this server's dist/; read the matching SKILL.md first.";
|
|
42
|
+
export declare const MCP_OPERATION_INSTRUCTIONS = "Kiln turns authored JavaScript into editable GLB assets and rendered views. Send source once to kiln_validate or kiln_render, then preserve the exact programRef for later reads, edits and renders. Use kiln_discover for helper contracts and full input shapes; use kiln_capabilities for host configuration and limits. Read viewFidelity before judging materials: CPU geometry views are not material evidence. Search saved assets with kiln_assets_search; reopen exact source with kiln_assets_restore. Browse materials with kiln_material_search and create with kiln_material_create. Save only when requested. Pin collection, asset and revision IDs for reuse and export.";
|
|
42
43
|
/** One MCP content block. Mirrors the SDK's `CallToolResult['content']` element. */
|
|
43
44
|
export type KilnContentBlock = AssetLink | {
|
|
44
45
|
type: 'text';
|
package/lib/mcp-core.js
CHANGED
|
@@ -43,6 +43,7 @@ export const MCP_SERVER_VERSION = ENGINE_VERSION;
|
|
|
43
43
|
* same bytes serve every installation.
|
|
44
44
|
*/
|
|
45
45
|
export const MCP_SERVER_INSTRUCTIONS = "Kiln turns JavaScript you write into GLB assets and returns rendered views. Work by reference: send a program once to kiln_validate or kiln_render, keep the returned programRef exactly, and use it for every later render, inspect, kiln_source and kiln_edit call; never resend a program to change part of it. Call kiln_discover first for helper contracts, with capabilities:true for the host's runtime, project and camera facts. Read viewFidelity before judging materials: a CPU view shows shape, not material; GPU views need the render service in render-service/. Agent Skills for authoring, refining, QA and scenes ship in skills/ beside this server's dist/; read the matching SKILL.md first.";
|
|
46
|
+
export const MCP_OPERATION_INSTRUCTIONS = 'Kiln turns authored JavaScript into editable GLB assets and rendered views. Send source once to kiln_validate or kiln_render, then preserve the exact programRef for later reads, edits and renders. Use kiln_discover for helper contracts and full input shapes; use kiln_capabilities for host configuration and limits. Read viewFidelity before judging materials: CPU geometry views are not material evidence. Search saved assets with kiln_assets_search; reopen exact source with kiln_assets_restore. Browse materials with kiln_material_search and create with kiln_material_create. Save only when requested. Pin collection, asset and revision IDs for reuse and export.';
|
|
46
47
|
/** Presentation hints for the asset viewer app, hidden from the language model. */
|
|
47
48
|
export const KILN_WIDGET_META = {
|
|
48
49
|
ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: [] } },
|
package/lib/mcp-engine.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { type KilnToolContext, type KilnToolDef } from "./tools/registry.js";
|
|
|
14
14
|
export type { KilnToolResult } from "./mcp-core.js";
|
|
15
15
|
/** Optional wire features whose support is not negotiated by core MCP. */
|
|
16
16
|
export type KilnMcpCompatibilityOptions = {
|
|
17
|
+
/** Opt-in domain operation presentation generated from the shared registry. */
|
|
18
|
+
toolPresentation?: 'grouped' | 'operations';
|
|
17
19
|
/**
|
|
18
20
|
* Add core `resource_link` blocks for saved artifact files.
|
|
19
21
|
*
|
package/lib/mcp-engine.js
CHANGED
|
@@ -14,12 +14,12 @@ import { buildRenderPort, resolveRenderMode } from "./cli-render-mode.js";
|
|
|
14
14
|
import { FileLiveReview } from "./live-review-node.js";
|
|
15
15
|
import { createPackagedLocalToolContext } from "./local-runtime.js";
|
|
16
16
|
import { LOOPBACK_HOSTNAMES } from "./loopback.js";
|
|
17
|
-
import { createKilnServer, errorMessage, withoutLocalPaths, } from "./mcp-core.js";
|
|
17
|
+
import { createKilnServer, errorMessage, withoutLocalPaths, MCP_OPERATION_INSTRUCTIONS, } from "./mcp-core.js";
|
|
18
18
|
import { buildMcpManifest } from "./mcp-manifest.js";
|
|
19
19
|
import { localProgramStore } from "./program-store-node.js";
|
|
20
20
|
import { validateRequirementsBinding } from "./requirements-context.js";
|
|
21
21
|
import { describeInputError, schemaFields } from "./tools/actions.js";
|
|
22
|
-
import { createKilnProgramToolRegistry, } from "./tools/registry.js";
|
|
22
|
+
import { createKilnProgramToolRegistry, createKilnOperationToolRegistry, } from "./tools/registry.js";
|
|
23
23
|
import { localWorkspaceRoot } from "./workspace-node.js";
|
|
24
24
|
/** Use the shared program-aware definitions, including standalone validation. */
|
|
25
25
|
export function kilnMcpToolDefs(context = {}) {
|
|
@@ -112,7 +112,9 @@ export function createKilnToolHost(context = {}, options = {}) {
|
|
|
112
112
|
};
|
|
113
113
|
},
|
|
114
114
|
};
|
|
115
|
-
const defs =
|
|
115
|
+
const defs = options.toolPresentation === 'operations'
|
|
116
|
+
? createKilnOperationToolRegistry(requestContext)
|
|
117
|
+
: kilnMcpToolDefs(requestContext);
|
|
116
118
|
const byName = new Map(defs.map((def) => [def.name, def]));
|
|
117
119
|
return {
|
|
118
120
|
defs,
|
|
@@ -200,6 +202,9 @@ function resourceText(name, bytes) {
|
|
|
200
202
|
export function createKilnMcpServer(context = {}, options = {}) {
|
|
201
203
|
const host = createKilnToolHost(context, options);
|
|
202
204
|
return createKilnServer({
|
|
205
|
+
...(options.toolPresentation === 'operations'
|
|
206
|
+
? { instructions: MCP_OPERATION_INSTRUCTIONS }
|
|
207
|
+
: {}),
|
|
203
208
|
manifest: buildMcpManifest(host.defs, context),
|
|
204
209
|
host: async () => host,
|
|
205
210
|
});
|
package/lib/tools/discovery.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { KilnToolContext, KilnToolDef } from "./registry.js";
|
|
2
2
|
/** Shared native tool definition; all transports use the same Discovery service. */
|
|
3
|
-
export declare function createKilnDiscoveryDef(context: KilnToolContext): KilnToolDef;
|
|
3
|
+
export declare function createKilnDiscoveryDef(context: KilnToolContext, presentation?: 'grouped' | 'operations'): KilnToolDef;
|
package/lib/tools/discovery.js
CHANGED
|
@@ -8,8 +8,8 @@ import { geometryExportAttributes } from "../geometry-export.js";
|
|
|
8
8
|
import { resolveGltfExporter } from "../community-exporter.js";
|
|
9
9
|
import { approvedTextureCatalogV1 } from "../material-resources.js";
|
|
10
10
|
/** Shared native tool definition; all transports use the same Discovery service. */
|
|
11
|
-
export function createKilnDiscoveryDef(context) {
|
|
12
|
-
const run = createDiscovery(() => currentCapabilities(context), async () => projectNotes(context));
|
|
11
|
+
export function createKilnDiscoveryDef(context, presentation = 'grouped') {
|
|
12
|
+
const run = createDiscovery(() => currentCapabilities(context, presentation), async () => projectNotes(context), presentation === 'operations' ? 'kiln_capabilities({})' : 'capabilities:true');
|
|
13
13
|
return {
|
|
14
14
|
name: 'kiln_discover',
|
|
15
15
|
description: 'Discover Kiln operations, assemblies, recipes, tool-input shapes and current host capabilities. Omit arguments for a compact overview. Search with ordinary modeling language using query; refine with family, kind or tags. Fetch complete contracts/examples with ids (up to six exact IDs, executable names or shape: ids). Overview/search pages default to six summaries. Recipes guide construction without restricting the asset. Search runs locally without models or network calls.',
|
|
@@ -39,7 +39,7 @@ function projectNotes(context) {
|
|
|
39
39
|
]
|
|
40
40
|
: [];
|
|
41
41
|
}
|
|
42
|
-
async function currentCapabilities(context) {
|
|
42
|
+
async function currentCapabilities(context, presentation) {
|
|
43
43
|
const externalEvaluator = Boolean(context.evaluatorPort) || context.evaluatorProfile === 'evaluator-required';
|
|
44
44
|
const approvedTextures = context.approvedTextureResources
|
|
45
45
|
? context.approvedTextureResources()
|
|
@@ -91,11 +91,13 @@ async function currentCapabilities(context) {
|
|
|
91
91
|
// you -- so the workspace guide's "do not substitute it silently" had
|
|
92
92
|
// nothing to check against. Compare with `runtime` in .kiln/workspace.json.
|
|
93
93
|
engine: engineIdentity(),
|
|
94
|
-
project:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
project: presentation === 'operations' && !context.projectStore
|
|
95
|
+
? { available: false }
|
|
96
|
+
: {
|
|
97
|
+
configured: configuredProject(context),
|
|
98
|
+
select: 'projectId per call; omitted uses the configured project, null selects standalone work',
|
|
99
|
+
read: 'kiln_project { action: "get", projectId } returns the brief, design, inventory and material pins',
|
|
100
|
+
},
|
|
99
101
|
renderer,
|
|
100
102
|
execution: context.localExecution ??
|
|
101
103
|
(context.evaluatorPort
|
|
@@ -115,7 +117,7 @@ async function currentCapabilities(context) {
|
|
|
115
117
|
available: Boolean(context.assetLibrary),
|
|
116
118
|
collections: context.assetLibrary?.collections() ?? [],
|
|
117
119
|
save: 'kiln_save persists exact GLB, source and provenance; draft renders do not populate collections',
|
|
118
|
-
resume: 'kiln_assets action=restore imports a saved revision into the current program store
|
|
120
|
+
resume: `${presentation === 'operations' ? 'kiln_assets_restore' : 'kiln_assets action=restore'} imports a saved revision into the current program store`,
|
|
119
121
|
downloads: 'kiln_export returns GLB/source/ZIP resource links; client presentation varies',
|
|
120
122
|
viewer: 'kiln_present opens a saved revision in supporting chat clients with 3D viewing and downloads; kiln view opens a local collection or standalone GLB/ZIP',
|
|
121
123
|
},
|
package/lib/tools/materials.d.ts
CHANGED
|
@@ -26,4 +26,4 @@ export declare const materialToolInput: z.ZodObject<{
|
|
|
26
26
|
draft: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
27
27
|
payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
28
28
|
}, z.core.$strict>;
|
|
29
|
-
export declare function createKilnMaterialDef(library: MaterialLibrary): KilnToolDef;
|
|
29
|
+
export declare function createKilnMaterialDef(library: MaterialLibrary, presentation?: 'grouped' | 'operations'): KilnToolDef;
|
package/lib/tools/materials.js
CHANGED
|
@@ -11,13 +11,39 @@ const MATERIAL_ACTIONS = [
|
|
|
11
11
|
'create-procedural',
|
|
12
12
|
'import',
|
|
13
13
|
];
|
|
14
|
-
const
|
|
15
|
-
presets: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
const MATERIAL_CONTRACT = {
|
|
15
|
+
presets: {
|
|
16
|
+
fields: ['tag'],
|
|
17
|
+
required: [],
|
|
18
|
+
description: 'List shipped material recipes, optionally filtered by tag.',
|
|
19
|
+
},
|
|
20
|
+
'create-preset': {
|
|
21
|
+
fields: ['presetId', 'seed', 'creator', 'license', 'size', 'materialId'],
|
|
22
|
+
required: ['presetId', 'seed', 'creator', 'license'],
|
|
23
|
+
description: 'Create an immutable material from a shipped preset with explicit seed, creator and license.',
|
|
24
|
+
},
|
|
25
|
+
list: {
|
|
26
|
+
fields: ['tag'],
|
|
27
|
+
required: [],
|
|
28
|
+
description: 'List saved immutable material revisions, optionally filtered by tag.',
|
|
29
|
+
},
|
|
30
|
+
get: {
|
|
31
|
+
fields: ['materialId', 'revisionId'],
|
|
32
|
+
required: ['materialId'],
|
|
33
|
+
description: 'Read a material revision, provenance and portable specification. Omit revisionId only when exactly one revision exists.',
|
|
34
|
+
},
|
|
35
|
+
'create-procedural': {
|
|
36
|
+
fields: ['draft'],
|
|
37
|
+
required: ['draft'],
|
|
38
|
+
shapes: { draft: 'shape:material-draft' },
|
|
39
|
+
description: 'Create an immutable material from a bounded procedural draft. Does not fetch URLs or execute source.',
|
|
40
|
+
},
|
|
41
|
+
import: {
|
|
42
|
+
fields: ['payload'],
|
|
43
|
+
required: ['payload'],
|
|
44
|
+
shapes: { payload: 'shape:material-import' },
|
|
45
|
+
description: 'Import complete normalized material records and embedded PNGs. Does not fetch URLs or execute source.',
|
|
46
|
+
},
|
|
21
47
|
};
|
|
22
48
|
const record = (description) => z.record(z.string(), z.unknown()).optional().describe(description);
|
|
23
49
|
const tag = z.string().min(1).max(80).optional().describe('presets, list: keep one tag.');
|
|
@@ -49,14 +75,14 @@ const materialResult = (material) => ({
|
|
|
49
75
|
material,
|
|
50
76
|
portableSpec: materialLibraryPortableSpec(material),
|
|
51
77
|
});
|
|
52
|
-
const unknownMaterial = (materialId, revisionId) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ''}.
|
|
78
|
+
const unknownMaterial = (materialId, revisionId, listCall) => new Error(`Unknown material ${materialId}${revisionId ? ` at ${revisionId}` : ''}. ${listCall} lists the materials in this workspace with their revision IDs.`);
|
|
53
79
|
/**
|
|
54
80
|
* The revision a `get` without `revisionId` means: the material's only one.
|
|
55
81
|
* Revisions are content hashes with no order, so several are named for the
|
|
56
82
|
* caller to choose (two live sessions of 1 October 2026 called get with the
|
|
57
83
|
* id alone and were refused).
|
|
58
84
|
*/
|
|
59
|
-
async function onlyRevision(library, materialId) {
|
|
85
|
+
async function onlyRevision(library, materialId, listCall) {
|
|
60
86
|
const revisions = (await library.list())
|
|
61
87
|
.filter((material) => material.materialId === materialId)
|
|
62
88
|
.map((material) => material.revisionId)
|
|
@@ -64,7 +90,7 @@ async function onlyRevision(library, materialId) {
|
|
|
64
90
|
if (revisions.length === 1)
|
|
65
91
|
return revisions[0];
|
|
66
92
|
if (revisions.length === 0)
|
|
67
|
-
throw unknownMaterial(materialId);
|
|
93
|
+
throw unknownMaterial(materialId, undefined, listCall);
|
|
68
94
|
throw new Error(`Material ${materialId} has ${revisions.length} revisions: ${revisions.join(', ')}. Pass revisionId to get one.`);
|
|
69
95
|
}
|
|
70
96
|
/**
|
|
@@ -83,11 +109,15 @@ function aliasMaterialId(raw) {
|
|
|
83
109
|
throw new Error(`kiln_material: resourceId (${String(resourceId)}) and materialId (${String(rest.materialId)}) name different materials; pass materialId alone.`);
|
|
84
110
|
return { ...rest, materialId: resourceId };
|
|
85
111
|
}
|
|
86
|
-
export function createKilnMaterialDef(library) {
|
|
112
|
+
export function createKilnMaterialDef(library, presentation = 'grouped') {
|
|
113
|
+
const listCall = presentation === 'operations'
|
|
114
|
+
? 'kiln_material_search({ scope: "saved" })'
|
|
115
|
+
: "kiln_material { action: 'list' }";
|
|
87
116
|
return {
|
|
88
117
|
name: 'kiln_material',
|
|
89
118
|
description: 'Manage optional immutable material resources in this workspace. presets discovers shipped architecture, wood, metal, fabric and ground recipes; create-preset bakes one with an explicit seed, creator and license; list returns compact material/revision summaries; get returns full provenance, hashes, map conventions, physical repeat scale and a code-ready portable material spec; create-procedural bakes bounded editable layer recipes including optional height-derived normals; import accepts complete normalized records with embedded PNG bytes. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before authored evaluation resolves the resources; no project is required.',
|
|
90
119
|
inputSchema: materialToolInput,
|
|
120
|
+
actionContract: MATERIAL_CONTRACT,
|
|
91
121
|
annotations: {
|
|
92
122
|
readOnlyHint: false,
|
|
93
123
|
destructiveHint: false,
|
|
@@ -97,7 +127,7 @@ export function createKilnMaterialDef(library) {
|
|
|
97
127
|
async run(raw) {
|
|
98
128
|
assertMaterialJson(raw);
|
|
99
129
|
const input = materialToolInput.parse(aliasMaterialId(raw));
|
|
100
|
-
requireActionFields('kiln_material', input.action, input,
|
|
130
|
+
requireActionFields('kiln_material', input.action, input, MATERIAL_CONTRACT[input.action]);
|
|
101
131
|
switch (input.action) {
|
|
102
132
|
case 'presets':
|
|
103
133
|
return {
|
|
@@ -135,14 +165,14 @@ export function createKilnMaterialDef(library) {
|
|
|
135
165
|
};
|
|
136
166
|
}
|
|
137
167
|
case 'get': {
|
|
138
|
-
const revisionId = input.revisionId ?? (await onlyRevision(library, input.materialId));
|
|
168
|
+
const revisionId = input.revisionId ?? (await onlyRevision(library, input.materialId, listCall));
|
|
139
169
|
try {
|
|
140
170
|
return materialResult((await library.read(input.materialId, revisionId)).manifest);
|
|
141
171
|
}
|
|
142
172
|
catch (error) {
|
|
143
173
|
// Rule 9: an unknown id names the call that lists the known ones.
|
|
144
174
|
if (error.code === 'ENOENT')
|
|
145
|
-
throw unknownMaterial(input.materialId, revisionId);
|
|
175
|
+
throw unknownMaterial(input.materialId, revisionId, listCall);
|
|
146
176
|
throw error;
|
|
147
177
|
}
|
|
148
178
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ActionRequirements } from "./actions.js";
|
|
3
|
+
/** Domain action metadata shared by grouped execution and named presentations. */
|
|
4
|
+
export interface KilnActionContract extends ActionRequirements {
|
|
5
|
+
readonly fields: readonly string[];
|
|
6
|
+
readonly description: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function projectActionSchemas<A extends string>(name: string, input: z.ZodObject, actions: Readonly<Record<A, KilnActionContract>>): Record<A, z.ZodObject>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function projectActionSchemas(name, input, actions) {
|
|
3
|
+
if (input.def.checks?.length)
|
|
4
|
+
throw new Error(`${name}: cross-field constraints require an explicit operation schema`);
|
|
5
|
+
const json = z.toJSONSchema(input, { io: 'input' });
|
|
6
|
+
const selector = json.properties?.action;
|
|
7
|
+
const expected = Object.keys(actions).sort();
|
|
8
|
+
if (!Array.isArray(selector?.enum) ||
|
|
9
|
+
JSON.stringify([...selector.enum].sort()) !== JSON.stringify(expected))
|
|
10
|
+
throw new Error(`${name}: unclassified actions`);
|
|
11
|
+
const classified = new Set([
|
|
12
|
+
'action',
|
|
13
|
+
...Object.values(actions).flatMap((contract) => [...contract.fields]),
|
|
14
|
+
]);
|
|
15
|
+
const unclassified = Object.keys(input.shape).filter((field) => !classified.has(field));
|
|
16
|
+
if (unclassified.length)
|
|
17
|
+
throw new Error(`${name}: Unclassified fields: ${unclassified.join(', ')}`);
|
|
18
|
+
const schemas = [];
|
|
19
|
+
for (const [action, contract] of Object.entries(actions)) {
|
|
20
|
+
for (const field of contract.required)
|
|
21
|
+
if (!contract.fields.includes(field))
|
|
22
|
+
throw new Error(`${name}.${action}: unmapped required ${field}`);
|
|
23
|
+
for (const field of json.required ?? [])
|
|
24
|
+
if (field !== 'action' && !contract.fields.includes(field))
|
|
25
|
+
throw new Error(`${name}.${action}: would omit required ${field}`);
|
|
26
|
+
const shape = {};
|
|
27
|
+
for (const field of contract.fields) {
|
|
28
|
+
if (field === 'action' || !Object.hasOwn(input.shape, field))
|
|
29
|
+
throw new Error(`${name}.${action}: missing or reserved field ${field}`);
|
|
30
|
+
classified.add(field);
|
|
31
|
+
const source = input.shape[field];
|
|
32
|
+
shape[field] = contract.required.includes(field) ? source.nonoptional() : source;
|
|
33
|
+
}
|
|
34
|
+
schemas.push([action, z.strictObject(shape)]);
|
|
35
|
+
}
|
|
36
|
+
return Object.fromEntries(schemas);
|
|
37
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { projectActionSchemas } from "./operation-contract.js";
|
|
3
|
+
/** Adapt domain operations in the engine, never in a transport-specific skin. */
|
|
4
|
+
export function projectOperationDefs(defs, context) {
|
|
5
|
+
return defs.flatMap((def) => {
|
|
6
|
+
if (!context.assetLibrary &&
|
|
7
|
+
['kiln_save', 'kiln_assets', 'kiln_present', 'kiln_export', 'kiln_import'].includes(def.name))
|
|
8
|
+
return [];
|
|
9
|
+
if (def.name === 'kiln_renderer')
|
|
10
|
+
return rendererOperations(def, context);
|
|
11
|
+
if (def.name === 'kiln_discover')
|
|
12
|
+
return discoveryOperations(def);
|
|
13
|
+
if (def.name === 'kiln_material')
|
|
14
|
+
return materialOperations(def);
|
|
15
|
+
if (def.name === 'kiln_assets')
|
|
16
|
+
return assetOperations(def, context);
|
|
17
|
+
return [def];
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function actionTools(def) {
|
|
21
|
+
if (!(def.inputSchema instanceof z.ZodObject) || !def.actionContract)
|
|
22
|
+
throw new Error(`${def.name}: missing shared action contract`);
|
|
23
|
+
const schemas = projectActionSchemas(def.name, def.inputSchema, def.actionContract);
|
|
24
|
+
return Object.fromEntries(Object.entries(def.actionContract).map(([action, contract]) => [
|
|
25
|
+
action,
|
|
26
|
+
{
|
|
27
|
+
...def,
|
|
28
|
+
actionContract: undefined,
|
|
29
|
+
name: `${def.name}_${action.replaceAll('-', '_')}`,
|
|
30
|
+
description: contract.description,
|
|
31
|
+
inputSchema: schemas[action],
|
|
32
|
+
run: async (raw) => def.run({ ...schemas[action].parse(raw), action }),
|
|
33
|
+
},
|
|
34
|
+
]));
|
|
35
|
+
}
|
|
36
|
+
function assetOperations(def, context) {
|
|
37
|
+
const tools = actionTools(def);
|
|
38
|
+
const list = tools.list;
|
|
39
|
+
const listSchema = list.inputSchema;
|
|
40
|
+
// Remove only the legacy default: omission means all collections in this operation.
|
|
41
|
+
const collection = listSchema.shape.collection
|
|
42
|
+
.unwrap()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe('Collection ID from kiln_assets_collections; omitted searches all configured collections.');
|
|
45
|
+
const inputSchema = listSchema.extend({ collection });
|
|
46
|
+
const search = {
|
|
47
|
+
...list,
|
|
48
|
+
name: 'kiln_assets_search',
|
|
49
|
+
description: 'Search saved assets with pagination. Omit collection for all configured collections, or select one explicitly. Results identify exact collection, asset and revision; partial read failures are reported.',
|
|
50
|
+
inputSchema,
|
|
51
|
+
run: async (raw) => {
|
|
52
|
+
const input = inputSchema.parse(raw);
|
|
53
|
+
const { collection: _collection, ...filters } = input;
|
|
54
|
+
if (input.collection === undefined)
|
|
55
|
+
return tools.catalog.run(filters);
|
|
56
|
+
if (!context.assetLibrary?.collections().some((item) => item.id === input.collection))
|
|
57
|
+
throw new Error('Unknown collection. Use kiln_assets_collections to list available destinations.');
|
|
58
|
+
const result = (await list.run(input));
|
|
59
|
+
return {
|
|
60
|
+
errors: [],
|
|
61
|
+
total: result.total,
|
|
62
|
+
nextOffset: result.nextOffset,
|
|
63
|
+
assets: result.assets.map((asset) => ({ ...asset, collection: input.collection })),
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
return [tools.collections, search, tools.get, tools.restore];
|
|
68
|
+
}
|
|
69
|
+
function materialOperations(def) {
|
|
70
|
+
const tools = actionTools(def);
|
|
71
|
+
const presets = tools.presets;
|
|
72
|
+
const inputSchema = presets.inputSchema.extend({
|
|
73
|
+
scope: z.enum(['all', 'presets', 'saved']).default('all'),
|
|
74
|
+
});
|
|
75
|
+
const search = {
|
|
76
|
+
...presets,
|
|
77
|
+
name: 'kiln_material_search',
|
|
78
|
+
description: 'Browse shipped material recipes and saved material revisions, optionally filtered by exact tag. Recipe IDs and saved material IDs are distinct. Use scope to select presets, saved or both.',
|
|
79
|
+
inputSchema,
|
|
80
|
+
run: async (raw) => {
|
|
81
|
+
const { scope, ...input } = inputSchema.parse(raw);
|
|
82
|
+
const recipes = scope === 'saved'
|
|
83
|
+
? { presets: [] }
|
|
84
|
+
: (await presets.run(input));
|
|
85
|
+
const saved = scope === 'presets'
|
|
86
|
+
? { materials: [] }
|
|
87
|
+
: (await tools.list.run(input));
|
|
88
|
+
return {
|
|
89
|
+
ok: true,
|
|
90
|
+
presets: recipes.presets.map((item) => ({ ...item, kind: 'preset' })),
|
|
91
|
+
materials: saved.materials.map((item) => ({ ...item, kind: 'saved' })),
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
const preset = tools['create-preset'];
|
|
96
|
+
const procedural = tools['create-procedural'];
|
|
97
|
+
const createInput = z.strictObject({
|
|
98
|
+
definition: z.discriminatedUnion('kind', [
|
|
99
|
+
preset.inputSchema.extend({ kind: z.literal('preset') }),
|
|
100
|
+
procedural.inputSchema.extend({ kind: z.literal('procedural') }),
|
|
101
|
+
]),
|
|
102
|
+
});
|
|
103
|
+
const create = {
|
|
104
|
+
...preset,
|
|
105
|
+
name: 'kiln_material_create',
|
|
106
|
+
description: 'Create and retain one immutable material from a preset or procedural definition. Presets require explicit seed, creator and license. Pin the returned materialId/revisionId for rendering. Does not fetch URLs or execute source.',
|
|
107
|
+
inputSchema: createInput,
|
|
108
|
+
run: async (raw) => {
|
|
109
|
+
const { kind, ...input } = createInput.parse(raw).definition;
|
|
110
|
+
return (kind === 'preset' ? preset : procedural).run(input);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
return [search, tools.get, create, tools.import];
|
|
114
|
+
}
|
|
115
|
+
function rendererOperations(def, context) {
|
|
116
|
+
if (!context.renderCapabilities)
|
|
117
|
+
return [];
|
|
118
|
+
return ['status', ...(context.reprobeRenderer ? ['reprobe'] : [])].map((action) => {
|
|
119
|
+
const inputSchema = z.strictObject({});
|
|
120
|
+
return {
|
|
121
|
+
...def,
|
|
122
|
+
name: `kiln_renderer_${action}`,
|
|
123
|
+
inputSchema,
|
|
124
|
+
run: async (raw) => {
|
|
125
|
+
inputSchema.parse(raw);
|
|
126
|
+
return def.run({ action });
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function discoveryOperations(def) {
|
|
132
|
+
const original = def.inputSchema;
|
|
133
|
+
const { capabilities: _capabilities, ...fields } = original.shape;
|
|
134
|
+
const inputSchema = z.strictObject(fields);
|
|
135
|
+
const empty = z.strictObject({});
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
...def,
|
|
139
|
+
description: 'Discover geometry helpers, assemblies, recipes and complete input shapes. Search with modeling language or fetch up to six exact IDs. Use kiln_capabilities for current host facts.',
|
|
140
|
+
inputSchema,
|
|
141
|
+
run: async (raw) => def.run(inputSchema.parse(raw)),
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
...def,
|
|
145
|
+
name: 'kiln_capabilities',
|
|
146
|
+
description: 'Inspect declared host capabilities, execution limits and available storage. Observed rendering fidelity is returned by each render.',
|
|
147
|
+
inputSchema: empty,
|
|
148
|
+
run: async (raw) => {
|
|
149
|
+
empty.parse(raw);
|
|
150
|
+
return def.run({ capabilities: true });
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
}
|
package/lib/tools/registry.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ import { type BackdropId } from "../views/background.js";
|
|
|
19
19
|
import type { TextureUsage } from "../textures.js";
|
|
20
20
|
export declare const KILN_ASSET_WIDGET_URI = "ui://kiln/asset-v5.html";
|
|
21
21
|
export interface KilnToolDef {
|
|
22
|
+
/** Shared action ownership and requirements; never hand-maintained by transports. */
|
|
23
|
+
actionContract?: Readonly<Record<string, import("./operation-contract.js").KilnActionContract>>;
|
|
22
24
|
outputSchema?: z.ZodType;
|
|
23
25
|
/** Optional MCP App presentation; metadata is hidden from the language model. */
|
|
24
26
|
ui?: {
|
|
@@ -770,6 +772,8 @@ export declare function createKilnEditDef(context?: KilnToolContext): KilnToolDe
|
|
|
770
772
|
/** Neutral compatibility export. */
|
|
771
773
|
export declare const kilnEditDef: KilnToolDef;
|
|
772
774
|
export declare function createKilnProgramToolRegistry(suppliedContext?: KilnToolContext): KilnToolDef[];
|
|
775
|
+
/** Opt-in named domain operations; grouped local/native contracts remain unchanged. */
|
|
776
|
+
export declare function createKilnOperationToolRegistry(context?: KilnToolContext): KilnToolDef[];
|
|
773
777
|
/** Canonical native workflow: shared definitions plus one host-owned terminal. */
|
|
774
778
|
export declare function createKilnNativeToolRegistry(suppliedContext: KilnToolContext, completion: NativeCompletion): KilnToolDef[];
|
|
775
779
|
/**
|
|
@@ -784,9 +788,9 @@ export declare function newestRevision(target: {
|
|
|
784
788
|
parentRevision?: string | null;
|
|
785
789
|
createdAt: string;
|
|
786
790
|
}[]>;
|
|
787
|
-
}, collection: string, assetId: string): Promise<string>;
|
|
791
|
+
}, collection: string, assetId: string, listCall?: string): Promise<string>;
|
|
788
792
|
/** Build one current-policy revision for ordinary save or explicit migration. */
|
|
789
793
|
export declare function buildProgramAssetDraft(code: string, context: KilnToolContext, backdrop?: BackdropId): Promise<Pick<import("../assets.js").AssetDraft, 'code' | 'glb' | 'preview' | 'previewInfo' | 'build'>>;
|
|
790
794
|
/** All asset operation schemas live here, alongside the existing tool definitions. */
|
|
791
|
-
export declare function createKilnAssetDefs(context: KilnToolContext): KilnToolDef[];
|
|
795
|
+
export declare function createKilnAssetDefs(context: KilnToolContext, presentation?: 'grouped' | 'operations'): KilnToolDef[];
|
|
792
796
|
export {};
|