@womp/spark-agent 6.5.0 → 8.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +7 -4
  3. package/dist/agent.d.ts +3 -6
  4. package/dist/agent.js +41 -23
  5. package/dist/capabilities/geometry.d.ts +17 -5
  6. package/dist/capabilities/geometry.js +11 -12
  7. package/dist/compaction.d.ts +41 -8
  8. package/dist/compaction.js +241 -77
  9. package/dist/context-window.d.ts +23 -0
  10. package/dist/context-window.js +141 -0
  11. package/dist/generated/geometry-declarations.d.ts +1 -1
  12. package/dist/generated/geometry-declarations.js +1 -1
  13. package/dist/geometry/backendRegistry.d.ts +1 -0
  14. package/dist/geometry/common.d.ts +5 -2
  15. package/dist/geometry/common.js +18 -3
  16. package/dist/geometry/compiler.js +2 -2
  17. package/dist/geometry/diagnostics.d.ts +2 -7
  18. package/dist/geometry/diagnostics.js +3 -51
  19. package/dist/geometry/graph.d.ts +1 -1
  20. package/dist/geometry/graph.js +1 -0
  21. package/dist/geometry/index.d.ts +0 -1
  22. package/dist/geometry/index.js +0 -3
  23. package/dist/geometry/lift.js +105 -113
  24. package/dist/geometry/lower.js +29 -49
  25. package/dist/geometry/schemas/flowir-dialect.json +117 -117
  26. package/dist/geometry/writeParamData.js +4 -4
  27. package/dist/geometry.d.ts +2 -5
  28. package/dist/geometry.js +7 -11
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -1
  31. package/dist/intent-bench.js +1 -1
  32. package/dist/sdk-geometry-host.d.ts +1 -1
  33. package/dist/sdk-geometry-host.js +253 -251
  34. package/dist/ui.d.ts +7 -0
  35. package/dist/ui.js +1 -0
  36. package/dist/womp-scene-api.d.ts +9 -1
  37. package/dist/womp-scene-api.js +41 -32
  38. package/package.json +2 -2
  39. package/dist/context-budget.d.ts +0 -65
  40. package/dist/context-budget.js +0 -364
@@ -1,4 +1,5 @@
1
- import type { BackendPort, BackendRegistry, BackendValue } from "./backendRegistry.js";
1
+ import type * as flowir from "@womp/flowir";
2
+ import type { BackendPort, BackendRegistry, BackendValue, GraphExt } from "./backendRegistry.js";
2
3
  /** Scalar backend storage is zero-initialized. Empty arrays have node-specific semantics. */
3
4
  export declare function defaultScalar(port: BackendPort): BackendValue | null;
4
5
  export type Edge = {
@@ -14,11 +15,13 @@ export type Edge = {
14
15
  export declare const VALUE_BACKEND_NAMES: Record<string, string>;
15
16
  /** FlowIR scalar type → backend node name (inverse of VALUE_BACKEND_NAMES). */
16
17
  export declare function valueBackendName(typeName: string): string | undefined;
17
- export declare function titleToAstName(title: string): string;
18
18
  export declare function astNameToTitle(s: string): string;
19
+ export declare function storedSymbol(graphExt: GraphExt, bid: string): string | undefined;
20
+ export declare function paramName(graphExt: GraphExt, valueBid: string, inputBid: string): string | undefined;
19
21
  export declare function isArrayPortType(type: string): boolean;
20
22
  export declare function parseConnection(key: string): Edge | null;
21
23
  export declare function buildIncomingMap(connections: Record<string, 0>): Map<string, Edge[]>;
24
+ export declare function backendTypeId(flowirRegistry: flowir.Registry, name: string): number;
22
25
  /** Resolve the GIP and value-node type IDs from the backend registry. */
23
26
  export declare function resolveParamTypeIds(backendRegistry: BackendRegistry): {
24
27
  gipTypeId: number | null;
@@ -4,7 +4,7 @@ export function defaultScalar(port) {
4
4
  return 0;
5
5
  if (port.type === "Bool")
6
6
  return false;
7
- if (port.type === "Vec" || port.type === "Vec3")
7
+ if (port.type === "Vec")
8
8
  return { x: 0, y: 0, z: 0 };
9
9
  return null;
10
10
  }
@@ -25,7 +25,7 @@ export function valueBackendName(typeName) {
25
25
  }
26
26
  return undefined;
27
27
  }
28
- export function titleToAstName(title) {
28
+ function titleToAstName(title) {
29
29
  return title
30
30
  .trim()
31
31
  .replace(/\s+(.)/g, (_, c) => "_" + c.toLowerCase())
@@ -36,8 +36,15 @@ export function astNameToTitle(s) {
36
36
  .replace(/_./g, (m) => " " + m[1].toUpperCase())
37
37
  .replace(/^./, (m) => m.toUpperCase());
38
38
  }
39
+ export function storedSymbol(graphExt, bid) {
40
+ return graphExt.nodes?.[bid]?.flowirSymbol;
41
+ }
42
+ export function paramName(graphExt, valueBid, inputBid) {
43
+ const label = graphExt.nodes?.[inputBid]?.name;
44
+ return storedSymbol(graphExt, valueBid) ?? (label ? titleToAstName(label) : undefined);
45
+ }
39
46
  export function isArrayPortType(type) {
40
- return type.endsWith("[]") || type.endsWith("Array");
47
+ return type.endsWith("Array");
41
48
  }
42
49
  export function parseConnection(key) {
43
50
  const m = key.match(/^(\d+):(\d+)-(\d+):(\d+)$/);
@@ -65,6 +72,14 @@ export function buildIncomingMap(connections) {
65
72
  }
66
73
  return incoming;
67
74
  }
75
+ export function backendTypeId(flowirRegistry, name) {
76
+ const node = flowirRegistry.getNode(name);
77
+ if (!node)
78
+ throw new Error(`Unknown dialect node ${name}.`);
79
+ if (!/^\d+$/.test(node.type))
80
+ throw new Error(`Dialect node ${name} has type ${JSON.stringify(node.type)}; the geometry backend needs a decimal type id.`);
81
+ return Number(node.type);
82
+ }
68
83
  /** Resolve the GIP and value-node type IDs from the backend registry. */
69
84
  export function resolveParamTypeIds(backendRegistry) {
70
85
  const gipTypeId = backendRegistry.getSchemaByName("GroupInputParam")?.typeId ?? null;
@@ -16,9 +16,9 @@ export function compileGeometry(source) {
16
16
  // of the text, not by whoever forwards it.
17
17
  if (!parsed.ok)
18
18
  throw new Error(formatBoundedDiagnostics(source, parsed.diagnostics));
19
- const result = flowir.compileFromProgram(parsed.program, source, flowirRegistry);
19
+ const result = flowir.compileFromProgram(parsed.program, flowirRegistry);
20
20
  if (!result.ok)
21
- throw new Error(formatBoundedDiagnostics(result.source, result.diagnostics));
21
+ throw new Error(formatBoundedDiagnostics(source, result.diagnostics));
22
22
  const compiled = lower(parsed.program, flowirRegistry, backendRegistry);
23
23
  for (const statement of parsed.program.statements) {
24
24
  const name = statement.kind === "node"
@@ -1,11 +1,7 @@
1
- import type { Diagnostic } from "@womp/flowir";
1
+ import * as flowir from "@womp/flowir";
2
2
  export declare const MAX_REPORTED_DIAGNOSTICS = 5;
3
3
  export declare const MAX_DIAGNOSTIC_BYTES: number;
4
- type DiagnosticLike = Pick<Diagnostic, "code" | "message" | "line" | "column" | "hint">;
5
- export declare function formatBoundedDiagnostics(source: string, diagnostics: readonly DiagnosticLike[], limits?: {
6
- count?: number;
7
- bytes?: number;
8
- }): string;
4
+ export declare function formatBoundedDiagnostics(source: string, diagnostics: readonly flowir.Diagnostic[]): string;
9
5
  /**
10
6
  * A host-assembled diagnostic list, bounded the same way: each entry to
11
7
  * `MAX_DIAGNOSTIC_BYTES`, the list to `MAX_REPORTED_DIAGNOSTICS` entries, with
@@ -15,4 +11,3 @@ export declare function boundedDiagnostics(diagnostics: readonly string[], limit
15
11
  count?: number;
16
12
  bytes?: number;
17
13
  }): string[];
18
- export {};
@@ -1,57 +1,9 @@
1
+ import * as flowir from "@womp/flowir";
1
2
  import { boundedText } from "../bounded-output.js";
2
- // WHY THIS FILE EXISTS. `@womp/flowir`'s own `formatDiagnostics` emits one
3
- // four-line block per diagnostic -- header, source line, caret, hint -- and
4
- // joins them, so a compiler that reports the same one-character mistake five
5
- // times repeats the offending source line five times and its hint five times.
6
- // That string is what `compileGeometry` throws and what a rejected
7
- // `geometry.write` hands the model as a diagnostic; on staging one bad enum
8
- // value cost a whole turn. The compiler holds the structured diagnostics
9
- // already, so the bound belongs here rather than behind a package this one
10
- // only consumes.
11
- //
12
- // What is kept: every diagnostic's header (code, line, column, message), the
13
- // source line once per distinct line, its caret, and each distinct hint once.
14
- // What is dropped is only repetition and the tail past the limits, and the
15
- // message says so.
16
3
  export const MAX_REPORTED_DIAGNOSTICS = 5;
17
4
  export const MAX_DIAGNOSTIC_BYTES = 4 * 1024;
18
- export function formatBoundedDiagnostics(source, diagnostics, limits = {}) {
19
- const count = limits.count ?? MAX_REPORTED_DIAGNOSTICS;
20
- const bytes = limits.bytes ?? MAX_DIAGNOSTIC_BYTES;
21
- if (diagnostics.length === 0)
22
- return "";
23
- const lines = source.replace(/\r\n?/gu, "\n").split("\n");
24
- const gutterWidth = String(lines.length || 1).length;
25
- const pointerPrefix = `${" ".repeat(gutterWidth)} | `;
26
- const shownLines = new Set();
27
- const shownCarets = new Set();
28
- const shownHints = new Set();
29
- const out = [];
30
- for (const diagnostic of diagnostics.slice(0, count)) {
31
- out.push(`error[${diagnostic.code}] L${diagnostic.line}:${diagnostic.column} ${diagnostic.message}`);
32
- const sourceLine = lines[diagnostic.line - 1];
33
- if (sourceLine !== undefined) {
34
- // A line already quoted is not quoted again -- that repetition is the
35
- // defect this bounds. Its caret still is, once per column, so every
36
- // diagnostic keeps a marker against the quoted line above it.
37
- if (shownLines.has(diagnostic.line) === false) {
38
- shownLines.add(diagnostic.line);
39
- out.push(`${String(diagnostic.line).padStart(gutterWidth, " ")} | ${sourceLine}`);
40
- }
41
- const caret = `${pointerPrefix}${" ".repeat(Math.max(diagnostic.column - 1, 0))}^`;
42
- if (shownCarets.has(`${diagnostic.line}:${caret}`) === false) {
43
- shownCarets.add(`${diagnostic.line}:${caret}`);
44
- out.push(caret);
45
- }
46
- }
47
- if (shownHints.has(diagnostic.hint) === false) {
48
- shownHints.add(diagnostic.hint);
49
- out.push(`${pointerPrefix}hint: ${diagnostic.hint}`);
50
- }
51
- }
52
- if (diagnostics.length > count)
53
- out.push(`${diagnostics.length - count} more diagnostics were omitted (${count} of ${diagnostics.length} shown). Fix these, then compile again.`);
54
- return boundedText(out.join("\n"), bytes, "Diagnostics");
5
+ export function formatBoundedDiagnostics(source, diagnostics) {
6
+ return boundedText(flowir.formatDiagnostics(source, diagnostics, MAX_REPORTED_DIAGNOSTICS), MAX_DIAGNOSTIC_BYTES, "Diagnostics");
55
7
  }
56
8
  /**
57
9
  * A host-assembled diagnostic list, bounded the same way: each entry to
@@ -31,9 +31,9 @@ export declare const graphExtSchema: z.ZodObject<{
31
31
  nodes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
32
32
  name: z.ZodOptional<z.ZodString>;
33
33
  position: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
34
+ flowirSymbol: z.ZodOptional<z.ZodString>;
34
35
  }, z.core.$catchall<z.ZodJSONSchema>>>>;
35
36
  }, z.core.$catchall<z.ZodJSONSchema>>;
36
37
  export type GeometryGraph = z.infer<typeof geometryGraphSchema>;
37
38
  export type GraphData = z.infer<typeof graphDataSchema>;
38
- /** FlowIR statement roles map to backend IDs only for the current conversion. */
39
39
  export type StatementNodes = Record<string, string>;
@@ -25,6 +25,7 @@ export const graphExtSchema = z
25
25
  .object({
26
26
  name: z.string().optional(),
27
27
  position: z.array(z.number()).length(2).optional(),
28
+ flowirSymbol: z.string().optional(),
28
29
  })
29
30
  .catchall(z.json()))
30
31
  .default({}),
@@ -5,5 +5,4 @@ export { lower } from "./lower.js";
5
5
  export type { LowerResult } from "./lower.js";
6
6
  export { lift } from "./lift.js";
7
7
  export { writeParamData } from "./writeParamData.js";
8
- export declare function loadGeoNodesDialect(): flowir.DialectSchema;
9
8
  export declare function createGeoNodesRegistry(): flowir.Registry;
@@ -4,9 +4,6 @@ export { BackendRegistry } from "./backendRegistry.js";
4
4
  export { lower } from "./lower.js";
5
5
  export { lift } from "./lift.js";
6
6
  export { writeParamData } from "./writeParamData.js";
7
- export function loadGeoNodesDialect() {
8
- return dialectJson;
9
- }
10
7
  export function createGeoNodesRegistry() {
11
8
  return flowir.Registry.fromJsonObject(dialectJson);
12
9
  }
@@ -1,11 +1,42 @@
1
- import { VALUE_BACKEND_NAMES, titleToAstName, isArrayPortType, buildIncomingMap, defaultScalar, } from "./common.js";
2
- const POS = { line: 0, column: 0 };
3
- function decimal(value) {
4
- const text = value.toLocaleString("en-US", {
5
- useGrouping: false,
6
- maximumSignificantDigits: 21,
1
+ import { VALUE_BACKEND_NAMES, backendTypeId, isArrayPortType, paramName, storedSymbol, buildIncomingMap, defaultScalar, } from "./common.js";
2
+ const NO_SOURCE_POSITION = { line: 0, column: 0 };
3
+ function uniqueSymbol(prefix, start, taken) {
4
+ let index = start;
5
+ while (taken.has(`${prefix}${index}`))
6
+ index += 1;
7
+ taken.add(`${prefix}${index}`);
8
+ return `${prefix}${index}`;
9
+ }
10
+ function resolveNames(valueNodes, paramInputBid, dialectBids, graphExt) {
11
+ const names = new Map();
12
+ const taken = new Set();
13
+ const keep = (bid, name) => {
14
+ if (name === undefined)
15
+ return;
16
+ names.set(bid, name);
17
+ taken.add(name);
18
+ };
19
+ for (const vNode of valueNodes) {
20
+ const inputBid = paramInputBid(vNode);
21
+ keep(vNode.bid, inputBid
22
+ ? paramName(graphExt, vNode.bid, inputBid)
23
+ : storedSymbol(graphExt, vNode.bid));
24
+ }
25
+ for (const bid of dialectBids)
26
+ keep(bid, storedSymbol(graphExt, bid));
27
+ let paramIndex = 0;
28
+ let constIndex = 0;
29
+ for (const vNode of valueNodes) {
30
+ const isParam = paramInputBid(vNode) !== undefined;
31
+ const index = isParam ? paramIndex++ : constIndex++;
32
+ if (!names.has(vNode.bid))
33
+ names.set(vNode.bid, uniqueSymbol(isParam ? "p" : "c", index, taken));
34
+ }
35
+ dialectBids.forEach((bid, index) => {
36
+ if (!names.has(bid))
37
+ names.set(bid, uniqueSymbol("n", index, taken));
7
38
  });
8
- return text.includes(".") ? text : `${text}.0`;
39
+ return names;
9
40
  }
10
41
  function isVec3Object(v) {
11
42
  if (!v || typeof v !== "object")
@@ -105,70 +136,47 @@ function decodeScalarValue(raw, typeName) {
105
136
  return {
106
137
  kind: "vec3",
107
138
  value: [vec.x, vec.y, vec.z],
108
- raw: `Vec3(${decimal(vec.x)}, ${decimal(vec.y)}, ${decimal(vec.z)})`,
109
- pos: POS,
139
+ ...NO_SOURCE_POSITION,
110
140
  };
111
141
  }
112
- if (typeName === "Bool" && typeof raw === "boolean") {
113
- return { kind: "bool", value: raw, pos: POS };
114
- }
115
142
  if (typeName === "Int" && typeof raw === "number") {
116
143
  const v = Math.trunc(raw);
117
- return { kind: "int", value: v, raw: String(v), pos: POS };
144
+ return { kind: "int", value: v, ...NO_SOURCE_POSITION };
118
145
  }
119
146
  const num = typeof raw === "number" ? raw : 0;
120
- return { kind: "float", value: num, raw: decimal(num), pos: POS };
147
+ return { kind: "float", value: num, ...NO_SOURCE_POSITION };
121
148
  }
122
149
  function decodeBackendValue(raw, backendPort, dialectPort, flowirRegistry) {
123
- if (dialectPort.enum && typeof raw === "number") {
124
- const values = flowirRegistry.getEnumValues(dialectPort.enum);
125
- const name = values[raw];
126
- if (name)
127
- return { kind: "ident", name, pos: POS };
128
- }
129
- if (backendPort.enumValues?.length && typeof raw === "number") {
130
- const name = backendPort.enumValues[raw];
131
- if (name)
132
- return { kind: "ident", name, pos: POS };
150
+ if (flowirRegistry.isEnum(dialectPort.type)) {
151
+ const name = typeof raw === "number"
152
+ ? flowirRegistry.getEnumValues(dialectPort.type)[raw]
153
+ : undefined;
154
+ return name ? { kind: "ident", name, ...NO_SOURCE_POSITION } : null;
133
155
  }
134
156
  const unwrapped = isArrayPortType(backendPort.type) && Array.isArray(raw) && raw.length === 1
135
157
  ? raw[0]
136
158
  : raw;
137
- if (isVec3Object(unwrapped)) {
138
- return {
139
- kind: "vec3",
140
- value: [unwrapped.x, unwrapped.y, unwrapped.z],
141
- raw: `Vec3(${decimal(unwrapped.x)}, ${decimal(unwrapped.y)}, ${decimal(unwrapped.z)})`,
142
- pos: POS,
143
- };
144
- }
159
+ if (isVec3Object(unwrapped))
160
+ return decodeScalarValue(unwrapped, "Vec3");
145
161
  if (typeof unwrapped === "boolean") {
146
- return { kind: "bool", value: unwrapped, pos: POS };
162
+ return { kind: "bool", value: unwrapped, ...NO_SOURCE_POSITION };
147
163
  }
148
164
  if (typeof unwrapped === "number") {
149
165
  if (dialectPort.type === "Int" || dialectPort.type === "Int[]") {
150
166
  const v = Math.trunc(unwrapped);
151
- return { kind: "int", value: v, raw: String(v), pos: POS };
167
+ return { kind: "int", value: v, ...NO_SOURCE_POSITION };
152
168
  }
153
- return {
154
- kind: "float",
155
- value: unwrapped,
156
- raw: decimal(unwrapped),
157
- pos: POS,
158
- };
169
+ return { kind: "float", value: unwrapped, ...NO_SOURCE_POSITION };
159
170
  }
160
171
  return null;
161
172
  }
162
- function liftParam(vNode, gipBid, paramCounter, ctx) {
173
+ function liftParam(vNode, gipBid, name, ctx) {
163
174
  const typeName = vNode.valueTypeName ?? "Float";
164
- const extName = ctx.graphExt.nodes?.[gipBid]?.name;
165
- const name = extName ? titleToAstName(extName) : `p${paramCounter}`;
166
175
  const rawDefault = getData(ctx.graph, vNode.bid, "0");
167
- const paramTypeName = typeName === "Bool" ? "Float" : typeName;
168
176
  return {
169
177
  kind: "param",
170
178
  name,
171
- typeName: paramTypeName,
179
+ typeName,
172
180
  value: rawDefault != null
173
181
  ? decodeScalarValue(rawDefault, typeName)
174
182
  : decodeScalarValue(typeName === "Vec3" ? { x: 0, y: 0, z: 0 } : 0, typeName),
@@ -177,53 +185,50 @@ function liftParam(vNode, gipBid, paramCounter, ctx) {
177
185
  max: getDataAsNumber(ctx.graph, gipBid, "1"),
178
186
  step: getDataAsNumber(ctx.graph, gipBid, "2"),
179
187
  },
180
- pos: POS,
188
+ ...NO_SOURCE_POSITION,
181
189
  };
182
190
  }
183
- function liftConst(vNode, constCounter, ctx) {
191
+ function liftConst(vNode, name, ctx) {
184
192
  const typeName = vNode.valueTypeName ?? "Float";
185
193
  const rawDefault = getData(ctx.graph, vNode.bid, "0");
186
194
  return {
187
195
  kind: "const",
188
- name: `c${constCounter}`,
189
- typeName: typeName,
196
+ name,
197
+ typeName,
190
198
  value: rawDefault != null
191
199
  ? decodeScalarValue(rawDefault, typeName)
192
200
  : decodeScalarValue(typeName === "Vec3" ? { x: 0, y: 0, z: 0 } : 0, typeName),
193
- pos: POS,
201
+ ...NO_SOURCE_POSITION,
194
202
  };
195
203
  }
196
204
  function resolveInputBinding(bid, idx, dialectPort, backendPort, ctx) {
197
205
  const connEdge = ctx.incoming.get(bid)?.find((e) => e.toPort === idx);
198
206
  if (connEdge) {
199
207
  const sourceNode = ctx.classMap.get(connEdge.fromBid);
200
- if (!sourceNode)
208
+ const sourceName = ctx.names.get(connEdge.fromBid);
209
+ if (!sourceNode || !sourceName)
201
210
  return null;
202
- const paramName = ctx.paramBids.get(connEdge.fromBid);
203
- if (paramName) {
211
+ if (sourceNode.cls === "value") {
204
212
  return {
205
213
  name: dialectPort.name,
206
- value: { kind: "ident", name: paramName, pos: POS },
207
- pos: POS,
214
+ value: { kind: "ident", name: sourceName, ...NO_SOURCE_POSITION },
215
+ ...NO_SOURCE_POSITION,
208
216
  };
209
217
  }
210
218
  if (sourceNode.cls === "dialect" && sourceNode.dialectName) {
211
219
  const sourceDialect = ctx.flowirRegistry.getNode(sourceNode.dialectName);
212
- const sourceStmtId = ctx.dialectStmtIds.get(connEdge.fromBid);
213
- if (sourceDialect && sourceStmtId) {
214
- const outputPort = sourceDialect.outputs[connEdge.fromPort];
215
- if (outputPort) {
216
- return {
217
- name: dialectPort.name,
218
- value: {
219
- kind: "ref",
220
- nodeId: sourceStmtId,
221
- port: outputPort.name,
222
- pos: POS,
223
- },
224
- pos: POS,
225
- };
226
- }
220
+ const outputPort = sourceDialect?.outputs[connEdge.fromPort];
221
+ if (outputPort) {
222
+ return {
223
+ name: dialectPort.name,
224
+ value: {
225
+ kind: "ref",
226
+ nodeId: sourceName,
227
+ port: outputPort.name,
228
+ ...NO_SOURCE_POSITION,
229
+ },
230
+ ...NO_SOURCE_POSITION,
231
+ };
227
232
  }
228
233
  }
229
234
  return null;
@@ -234,11 +239,11 @@ function resolveInputBinding(bid, idx, dialectPort, backendPort, ctx) {
234
239
  const decoded = decodeBackendValue(rawData, backendPort, dialectPort, ctx.flowirRegistry);
235
240
  if (!decoded)
236
241
  return null;
237
- return { name: dialectPort.name, value: decoded, pos: POS };
242
+ return { name: dialectPort.name, value: decoded, ...NO_SOURCE_POSITION };
238
243
  }
239
244
  function liftNodeStmt(bid, cNode, ctx) {
240
245
  const dialectName = cNode.dialectName;
241
- const stmtId = ctx.dialectStmtIds.get(bid);
246
+ const stmtId = ctx.names.get(bid);
242
247
  const dialectNode = ctx.flowirRegistry.getNode(dialectName);
243
248
  if (!dialectNode)
244
249
  return null;
@@ -254,7 +259,13 @@ function liftNodeStmt(bid, cNode, ctx) {
254
259
  if (binding)
255
260
  inputs.push(binding);
256
261
  }
257
- return { kind: "node", id: stmtId, nodeType: dialectName, inputs, pos: POS };
262
+ return {
263
+ kind: "node",
264
+ id: stmtId,
265
+ nodeType: dialectName,
266
+ inputs,
267
+ ...NO_SOURCE_POSITION,
268
+ };
258
269
  }
259
270
  function liftOutStmt(oNode, ctx) {
260
271
  const geoEdge = ctx.incoming.get(oNode.bid)?.find((e) => e.toPort === 1);
@@ -264,7 +275,7 @@ function liftOutStmt(oNode, ctx) {
264
275
  if (!sourceNode || sourceNode.cls !== "dialect" || !sourceNode.dialectName)
265
276
  return null;
266
277
  const sourceDialect = ctx.flowirRegistry.getNode(sourceNode.dialectName);
267
- const sourceStmtId = ctx.dialectStmtIds.get(geoEdge.fromBid);
278
+ const sourceStmtId = ctx.names.get(geoEdge.fromBid);
268
279
  if (!sourceDialect || !sourceStmtId)
269
280
  return null;
270
281
  const outputPort = sourceDialect.outputs[geoEdge.fromPort];
@@ -277,20 +288,16 @@ function liftOutStmt(oNode, ctx) {
277
288
  kind: "ref",
278
289
  nodeId: sourceStmtId,
279
290
  port: outputPort.name,
280
- pos: POS,
291
+ ...NO_SOURCE_POSITION,
281
292
  },
282
- pos: POS,
293
+ ...NO_SOURCE_POSITION,
283
294
  };
284
295
  }
285
296
  export function lift(graph, graphExt, flowirRegistry, backendRegistry) {
286
297
  const reverseMap = new Map();
287
298
  for (const name of flowirRegistry.listNodeNames()) {
288
- const id = flowirRegistry.getNodeTypeId(name);
289
- if (id == null)
290
- continue;
299
+ const id = backendTypeId(flowirRegistry, name);
291
300
  const outputs = backendRegistry.getSchema(id)?.outputs;
292
- // Dialect aliases can share a backend ID. Lift the native output type,
293
- // so vector math is not accidentally narrowed to its scalar alias.
294
301
  const matches = flowirRegistry
295
302
  .getNode(name)
296
303
  ?.outputs.every((port, index) => port.type ===
@@ -313,52 +320,37 @@ export function lift(graph, graphExt, flowirRegistry, backendRegistry) {
313
320
  const classMap = new Map();
314
321
  for (const node of classified)
315
322
  classMap.set(node.bid, node);
323
+ const valueNodes = classified.filter((n) => n.cls === "value");
324
+ const paramInputBid = (vNode) => incoming
325
+ .get(vNode.bid)
326
+ ?.find((e) => classMap.get(e.fromBid)?.cls === "gip" && e.toPort === 0)
327
+ ?.fromBid;
328
+ const dialectBids = toposort(classified.filter((n) => n.cls === "dialect").map((n) => n.bid), incoming);
316
329
  const ctx = {
317
330
  graph,
318
- graphExt,
319
331
  flowirRegistry,
320
332
  backendRegistry,
321
333
  incoming,
322
334
  classMap,
323
- paramBids: new Map(),
324
- dialectStmtIds: new Map(),
335
+ names: resolveNames(valueNodes, paramInputBid, dialectBids, graphExt),
325
336
  };
326
337
  const statements = [];
327
338
  const nodeIds = {};
328
- let paramCounter = 0;
329
- let constCounter = 0;
330
- const valueNodes = classified.filter((n) => n.cls === "value");
331
339
  for (const vNode of valueNodes) {
332
- const edges = incoming.get(vNode.bid);
333
- const gipEdge = edges?.find((e) => classMap.get(e.fromBid)?.cls === "gip" && e.toPort === 0);
334
- if (gipEdge) {
335
- statements.push(liftParam(vNode, gipEdge.fromBid, paramCounter, ctx));
336
- const stmt = statements[statements.length - 1];
337
- ctx.paramBids.set(vNode.bid, stmt.name);
338
- nodeIds[`param:${stmt.name}`] = vNode.bid;
339
- nodeIds[`param-input:${stmt.name}`] = gipEdge.fromBid;
340
- paramCounter++;
340
+ const name = ctx.names.get(vNode.bid);
341
+ const inputBid = paramInputBid(vNode);
342
+ if (inputBid) {
343
+ statements.push(liftParam(vNode, inputBid, name, ctx));
344
+ nodeIds[`param:${name}`] = vNode.bid;
345
+ nodeIds[`param-input:${name}`] = inputBid;
341
346
  }
342
347
  else {
343
- statements.push(liftConst(vNode, constCounter, ctx));
344
- const stmt = statements[statements.length - 1];
345
- ctx.paramBids.set(vNode.bid, stmt.name);
346
- nodeIds[`const:${stmt.name}`] = vNode.bid;
347
- constCounter++;
348
+ statements.push(liftConst(vNode, name, ctx));
349
+ nodeIds[`const:${name}`] = vNode.bid;
348
350
  }
349
351
  }
350
- const dialectBids = classified
351
- .filter((n) => n.cls === "dialect")
352
- .map((n) => n.bid);
353
- const sortedBids = toposort(dialectBids, incoming);
354
- let nodeCounter = 0;
355
- for (const bid of sortedBids) {
356
- ctx.dialectStmtIds.set(bid, `n${nodeCounter}`);
357
- nodeCounter++;
358
- }
359
- for (const bid of sortedBids) {
360
- const cNode = classMap.get(bid);
361
- const stmt = liftNodeStmt(bid, cNode, ctx);
352
+ for (const bid of dialectBids) {
353
+ const stmt = liftNodeStmt(bid, classMap.get(bid), ctx);
362
354
  if (stmt) {
363
355
  statements.push(stmt);
364
356
  nodeIds[`node:${stmt.id}`] = bid;