Files
boocode/packages/ion/src/engine/output-ref.ts
indifferentketchup 02063072ab chore: add ion package, codesight wiki, work plans, ascli config
New @boocode/ion package (v0.0.1) for inference optimization network.
.codesight/ wiki artifacts for codebase documentation.
.omo/ work plans for openspec cleanup and enhanced file panel.
2026-06-07 22:16:45 +00:00

122 lines
4.1 KiB
TypeScript

/**
* Node output reference resolution for the Ion workflow engine.
*
* Resolves `$nodeId.field` references in workflow conditions and prompts,
* with strict schema-aware validation and descriptive errors.
*/
// ---------------------------------------------------------------------------
// Output reference result
// ---------------------------------------------------------------------------
export type OutputRefKind = 'value' | 'empty';
export interface OutputRefResult {
/** Whether the field had a value or was empty. */
kind: OutputRefKind;
/** The resolved value (empty string for missing optional fields). */
value: string;
}
// ---------------------------------------------------------------------------
// OutputRefError
// ---------------------------------------------------------------------------
export class OutputRefError extends Error {
public readonly nodeId: string;
public readonly field: string;
constructor(nodeId: string, field: string, message: string) {
super(`Output reference error for node "${nodeId}".${field}: ${message}`);
this.name = 'OutputRefError';
this.nodeId = nodeId;
this.field = field;
}
}
// ---------------------------------------------------------------------------
// Schema helpers
// ---------------------------------------------------------------------------
/**
* Extract declared field names from an output_format schema.
*
* The output_format can be:
* - A JSON Schema object with `properties` (standard)
* - A string describing the format (treated as having no declared fields)
* - Undefined (no schema)
*
* Returns a Set of field names that are declared in the schema.
*/
export function declaredFieldsFromSchema(
outputFormat: Record<string, unknown> | string | undefined,
): Set<string> {
if (!outputFormat || typeof outputFormat === 'string') {
return new Set();
}
const properties = outputFormat['properties'];
if (properties && typeof properties === 'object' && properties !== null) {
return new Set(Object.keys(properties as Record<string, unknown>));
}
return new Set();
}
// ---------------------------------------------------------------------------
// Node output resolution
// ---------------------------------------------------------------------------
/**
* Resolve a specific field from a node's output.
*
* Behavior:
* - If the field is present in the output, returns `{ kind: 'value', value }`.
* - If the field is declared in the schema but not present in the output,
* returns `{ kind: 'empty', value: '' }` (optional field not set).
* - If the field is NOT declared in the schema AND not in the output,
* throws `OutputRefError` (undeclared reference).
* - If the field is NOT declared in the schema but IS in the output,
* returns `{ kind: 'value', value }` (dynamic output).
*
* The `declaredFields` parameter should come from `declaredFieldsFromSchema()`.
*/
export function resolveNodeOutputField(
nodeOutput: Record<string, unknown>,
nodeId: string,
field: string,
declaredFields?: Set<string>,
): OutputRefResult {
// Check if the field exists in the output.
if (field in nodeOutput) {
const rawValue = nodeOutput[field];
// Convert the value to a string.
if (rawValue === null || rawValue === undefined) {
// Field key exists but value is nullish — treat as empty.
return { kind: 'empty', value: '' };
}
if (typeof rawValue === 'string') {
return { kind: 'value', value: rawValue };
}
// Non-string values are JSON-serialized.
return { kind: 'value', value: JSON.stringify(rawValue) };
}
// Field is not in the output. Check if it was declared in the schema.
const isDeclared = declaredFields?.has(field) ?? false;
if (isDeclared) {
// Declared but not present — optional field not set.
return { kind: 'empty', value: '' };
}
// Not declared and not present — this is an error.
throw new OutputRefError(
nodeId,
field,
`Field "${field}" is not declared in the output schema and is not present in the node output. Available fields: ${Object.keys(nodeOutput).join(', ') || '(none)'}`,
);
}