dirac-lang 0.1.14 → 0.1.16

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/dist/index.d.ts CHANGED
@@ -79,6 +79,7 @@ interface DiracSession {
79
79
  isReturn: boolean;
80
80
  isBreak: boolean;
81
81
  skipSubroutineRegistration: boolean;
82
+ currentSubroutineName?: string;
82
83
  debug: boolean;
83
84
  currentFile?: string;
84
85
  importedFiles?: Set<string>;
package/dist/index.js CHANGED
@@ -2,11 +2,13 @@ import {
2
2
  createLLMAdapter,
3
3
  execute,
4
4
  executeUserCommand
5
- } from "./chunk-SOHSXT3M.js";
5
+ } from "./chunk-FEOBVBSI.js";
6
6
  import {
7
- DiracParser,
8
7
  integrate
9
- } from "./chunk-VOD5GGHB.js";
8
+ } from "./chunk-CP4YCRBG.js";
9
+ import {
10
+ DiracParser
11
+ } from "./chunk-HRHAMPOB.js";
10
12
  import "./chunk-E7PWEMZA.js";
11
13
  import {
12
14
  createSession,
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  integrate,
3
3
  integrateChildren
4
- } from "./chunk-VOD5GGHB.js";
4
+ } from "./chunk-CP4YCRBG.js";
5
+ import "./chunk-HRHAMPOB.js";
5
6
  import "./chunk-E7PWEMZA.js";
6
7
  import "./chunk-52ED23DR.js";
7
8
  export {
@@ -0,0 +1,6 @@
1
+ import {
2
+ DiracParser
3
+ } from "./chunk-HRHAMPOB.js";
4
+ export {
5
+ DiracParser
6
+ };
@@ -4,21 +4,23 @@ import yaml from "js-yaml";
4
4
  var SIMILARITY_CUTOFF = 0.75;
5
5
  function getEmbeddingServerConfig() {
6
6
  try {
7
- const config = yaml.load(fs.readFileSync("config.yml", "utf8"));
7
+ const configPath = process.env.DIRAC_CONFIG || "config.yml";
8
+ const config = yaml.load(fs.readFileSync(configPath, "utf8"));
8
9
  const host = config.embeddingServer?.host || "localhost";
9
- const port = config.embeddingServer?.port || 11435;
10
- return { host, port };
11
- } catch {
12
- return { host: "localhost", port: 11435 };
10
+ const port = config.embeddingServer?.port || 11434;
11
+ const model = config.embeddingServer?.model || "embeddinggemma";
12
+ return { host, port, model };
13
+ } catch (e) {
14
+ return { host: "localhost", port: 11434, model: "embeddinggemma" };
13
15
  }
14
16
  }
15
17
  async function getEmbeddings(tags) {
16
- const { host, port } = getEmbeddingServerConfig();
18
+ const { host, port, model } = getEmbeddingServerConfig();
17
19
  return await Promise.all(tags.map(async (tag) => {
18
20
  const response = await fetch(`http://${host}:${port}/api/embeddings`, {
19
21
  method: "POST",
20
22
  headers: { "Content-Type": "application/json" },
21
- body: JSON.stringify({ model: "embeddinggemma", prompt: tag })
23
+ body: JSON.stringify({ model, prompt: tag })
22
24
  });
23
25
  const data = await response.json();
24
26
  return data.embedding;
@@ -111,7 +113,7 @@ async function validateDiracCode(session, ast, options = {}) {
111
113
  const results = [];
112
114
  const errorMessages = [];
113
115
  async function validateElement(element) {
114
- if (element.tag && element.tag !== "dirac" && element.tag !== "") {
116
+ if (element.tag && element.tag !== "dirac" && element.tag.trim() !== "") {
115
117
  const result = await validateTag(session, element, options);
116
118
  results.push(result);
117
119
  if (!result.valid) {
@@ -1,7 +1,9 @@
1
1
  import {
2
- DiracParser,
3
2
  integrate
4
- } from "./chunk-VOD5GGHB.js";
3
+ } from "./chunk-CP4YCRBG.js";
4
+ import {
5
+ DiracParser
6
+ } from "./chunk-HRHAMPOB.js";
5
7
  import "./chunk-E7PWEMZA.js";
6
8
  import {
7
9
  createSession,
@@ -17,9 +19,8 @@ var TestRunner = class {
17
19
  config;
18
20
  constructor(testDir = "tests", configPath) {
19
21
  this.testDir = testDir;
20
- const defaultConfigPath = configPath || path.resolve(process.cwd(), "config.yml");
21
- if (fs.existsSync(defaultConfigPath)) {
22
- this.config = yaml.load(fs.readFileSync(defaultConfigPath, "utf8")) || {};
22
+ if (configPath && fs.existsSync(configPath)) {
23
+ this.config = yaml.load(fs.readFileSync(configPath, "utf8")) || {};
23
24
  } else {
24
25
  this.config = { llmProvider: "ollama", llmModel: "llama2" };
25
26
  }
@@ -0,0 +1,6 @@
1
+ <dirac>
2
+ <subroutine name="background" description="set the background color." param-color="string:required:color name" >
3
+ this is my color: <variable name="color" />
4
+ </subroutine>
5
+ <llm execute="true">what is the color of red mix with yellow and set the background color to that color</llm>
6
+ </dirac>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dirac-lang",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "LLM-Augmented Declarative Execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,6 +27,8 @@ import { executeCatch } from '../tags/catch.js';
27
27
  import { executeException } from '../tags/exception.js';
28
28
  import { executeTestIf } from '../tags/test-if.js';
29
29
  import { executeAvailableSubroutines } from '../tags/available-subroutines.js';
30
+ import { executeForeach } from '../tags/foreach.js';
31
+ import { executeAttr } from '../tags/attr.js';
30
32
 
31
33
  export async function integrate(session: DiracSession, element: DiracElement): Promise<void> {
32
34
  // Check execution limits
@@ -142,6 +144,14 @@ export async function integrate(session: DiracSession, element: DiracElement): P
142
144
  case 'available-subroutines':
143
145
  await executeAvailableSubroutines(session, element);
144
146
  break;
147
+
148
+ case 'foreach':
149
+ await executeForeach(session, element);
150
+ break;
151
+
152
+ case 'attr':
153
+ await executeAttr(session, element);
154
+ break;
145
155
 
146
156
  case 'require_module':
147
157
  await executeRequireModule(session, element);
@@ -0,0 +1,64 @@
1
+ /**
2
+ * <attr> tag - extract attribute value from XML item
3
+ * Usage: <attr name="description" from="$sub" />
4
+ * or: <attr name="param-format" from="$sub" />
5
+ */
6
+
7
+ import type { DiracSession, DiracElement } from '../types/index.js';
8
+ import { getVariable, substituteVariables } from '../runtime/session.js';
9
+
10
+ export async function executeAttr(session: DiracSession, element: DiracElement): Promise<void> {
11
+ const nameAttr = element.attributes.name;
12
+ const fromAttr = element.attributes.from;
13
+
14
+ if (!nameAttr) {
15
+ throw new Error('<attr> requires "name" attribute');
16
+ }
17
+
18
+ if (!fromAttr) {
19
+ throw new Error('<attr> requires "from" attribute');
20
+ }
21
+
22
+ // Get the XML item from variable
23
+ let item: any;
24
+
25
+ if (fromAttr.startsWith('$')) {
26
+ const varName = fromAttr.substring(1);
27
+ item = getVariable(session, varName);
28
+ } else {
29
+ // Try substitution
30
+ const substituted = substituteVariables(session, fromAttr);
31
+ item = getVariable(session, substituted);
32
+ }
33
+
34
+ if (!item) {
35
+ // No output if item not found
36
+ return;
37
+ }
38
+
39
+ // Extract the attribute value
40
+ let value = '';
41
+
42
+ if (typeof item === 'object') {
43
+ // If it's a structured XML item (from foreach)
44
+ if (item.attributes && typeof item.attributes === 'object') {
45
+ value = item.attributes[nameAttr] || '';
46
+ }
47
+ // If it's a direct object with the attribute
48
+ else if (item[nameAttr] !== undefined) {
49
+ value = String(item[nameAttr]);
50
+ }
51
+ } else if (typeof item === 'string') {
52
+ // If it's a string, try to parse as XML and extract
53
+ value = extractAttrFromXml(item, nameAttr);
54
+ }
55
+
56
+ session.output.push(value);
57
+ }
58
+
59
+ function extractAttrFromXml(xml: string, attrName: string): string {
60
+ // Simple regex-based extraction for single-element XML
61
+ const regex = new RegExp(`${attrName}="([^"]*)"`, 'i');
62
+ const match = xml.match(regex);
63
+ return match ? match[1] : '';
64
+ }
@@ -12,20 +12,32 @@ export async function executeAvailableSubroutines(
12
12
  // Get all subroutines from current boundary to top of stack
13
13
  const availableSubroutines = new Map<string, DiracElement>();
14
14
 
15
+ // Get the name of the currently executing subroutine from the boundary
16
+ const currentSubroutineName = session.subBoundary < session.subroutines.length
17
+ ? session.subroutines[session.subBoundary].name
18
+ : null;
19
+
15
20
  // Read from top of stack (most recent) backwards to boundary
16
21
  // This ensures we get the latest definition (handles extends override)
17
22
  for (let i = session.subroutines.length - 1; i >= session.subBoundary; i--) {
18
23
  const sub = session.subroutines[i];
19
24
 
25
+ // Skip the currently executing subroutine itself
26
+ if (sub.name === currentSubroutineName) {
27
+ continue;
28
+ }
29
+
20
30
  // Only add if not already seen (first occurrence wins)
21
31
  if (!availableSubroutines.has(sub.name)) {
22
32
  availableSubroutines.set(sub.name, sub.element);
23
33
  }
24
34
  }
25
35
 
26
- // Generate output for each subroutine
36
+ // Generate structured output with container
37
+ session.output.push('<subroutines>');
38
+
27
39
  for (const [name, subElement] of availableSubroutines) {
28
- const attrs: string[] = [];
40
+ const attrs: string[] = [`name="${escapeXml(name)}"`];
29
41
 
30
42
  // Add description if available
31
43
  const description = subElement.attributes.description;
@@ -36,15 +48,16 @@ export async function executeAvailableSubroutines(
36
48
  // Add all param-* attributes with their definitions
37
49
  for (const [attrName, attrValue] of Object.entries(subElement.attributes)) {
38
50
  if (attrName.startsWith('param-')) {
39
- const paramName = attrName.substring(6);
40
- attrs.push(`${paramName}="${escapeXml(attrValue)}"`);
51
+ attrs.push(`${attrName}="${escapeXml(attrValue)}"`);
41
52
  }
42
53
  }
43
54
 
44
55
  // Build the output tag
45
- const attrString = attrs.length > 0 ? ' ' + attrs.join(' ') : '';
46
- session.output.push(`<${name}${attrString} />`);
56
+ const attrString = attrs.join(' ');
57
+ session.output.push(` <subroutine ${attrString} />`);
47
58
  }
59
+
60
+ session.output.push('</subroutines>');
48
61
  }
49
62
 
50
63
  function escapeXml(text: string): string {
package/src/tags/call.ts CHANGED
@@ -135,6 +135,10 @@ async function executeCallInternal(
135
135
  const wasReturn = session.isReturn;
136
136
  session.isReturn = false;
137
137
 
138
+ // Track current subroutine name for available-subroutines
139
+ const oldSubroutineName = session.currentSubroutineName;
140
+ session.currentSubroutineName = callElement.tag;
141
+
138
142
  // For extend execution, skip subroutine registration during body execution
139
143
  const oldSkipFlag = session.skipSubroutineRegistration;
140
144
  if (isExtendExecution) {
@@ -27,8 +27,10 @@ export async function executeDefvar(session: DiracSession, element: DiracElement
27
27
  if (valueAttr !== undefined) {
28
28
  value = substituteVariables(session, valueAttr);
29
29
  } else if (literal) {
30
- // Literal mode: take text or XML as-is
31
- if (element.text) {
30
+ // Literal mode: serialize children to XML text without executing
31
+ if (element.children && element.children.length > 0) {
32
+ value = serializeToXml(element.children);
33
+ } else if (element.text) {
32
34
  value = substituteVariables(session, element.text);
33
35
  } else {
34
36
  value = '';
@@ -60,3 +62,52 @@ export async function executeDefvar(session: DiracSession, element: DiracElement
60
62
 
61
63
  setVariable(session, name, value, visible);
62
64
  }
65
+
66
+ function serializeToXml(children: DiracElement[]): string {
67
+ let xml = '';
68
+
69
+ for (const child of children) {
70
+ if (!child.tag || child.tag === '') {
71
+ // Text node
72
+ xml += child.text || '';
73
+ } else {
74
+ // Element node
75
+ xml += `<${child.tag}`;
76
+
77
+ // Add attributes
78
+ for (const [attrName, attrValue] of Object.entries(child.attributes)) {
79
+ xml += ` ${attrName}="${escapeXml(attrValue)}"`;
80
+ }
81
+
82
+ // Self-closing or with children
83
+ if (child.children.length === 0 && !child.text) {
84
+ xml += ' />';
85
+ } else {
86
+ xml += '>';
87
+
88
+ // Add text content if present
89
+ if (child.text) {
90
+ xml += escapeXml(child.text);
91
+ }
92
+
93
+ // Add children
94
+ if (child.children.length > 0) {
95
+ xml += serializeToXml(child.children);
96
+ }
97
+
98
+ xml += `</${child.tag}>`;
99
+ }
100
+ }
101
+ }
102
+
103
+ return xml;
104
+ }
105
+
106
+ function escapeXml(text: string): string {
107
+ return text
108
+ .replace(/&/g, '&amp;')
109
+ .replace(/</g, '&lt;')
110
+ .replace(/>/g, '&gt;')
111
+ .replace(/"/g, '&quot;')
112
+ .replace(/'/g, '&apos;');
113
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * <foreach> tag - iterate over XML elements
3
+ * Usage: <foreach from="<available-subroutines />" as="sub">...</foreach>
4
+ * or: <foreach from="$varname" as="item">...</foreach>
5
+ */
6
+
7
+ import type { DiracSession, DiracElement } from '../types/index.js';
8
+ import { setVariable, getVariable, substituteVariables } from '../runtime/session.js';
9
+ import { integrateChildren } from '../runtime/interpreter.js';
10
+ import { XMLParser } from 'fast-xml-parser';
11
+
12
+ export async function executeForeach(session: DiracSession, element: DiracElement): Promise<void> {
13
+ const fromAttr = element.attributes.from;
14
+ const asVar = element.attributes.as || 'item';
15
+ const xpath = element.attributes.xpath; // Optional: filter like "//subroutine"
16
+
17
+ if (!fromAttr) {
18
+ throw new Error('<foreach> requires "from" attribute');
19
+ }
20
+
21
+ // Get the XML content (either from variable or inline content)
22
+ let xmlContent: string;
23
+
24
+ if (fromAttr.startsWith('$')) {
25
+ // Get from variable
26
+ const varName = fromAttr.substring(1);
27
+ xmlContent = getVariable(session, varName) || '';
28
+ } else if (fromAttr.startsWith('<')) {
29
+ // Evaluate the "from" attribute as inline XML (e.g., <available-subroutines />)
30
+ const savedOutput = session.output;
31
+ session.output = [];
32
+
33
+ // Parse and execute the from content
34
+ const { DiracParser } = await import('../runtime/parser.js');
35
+ const parser = new DiracParser();
36
+ try {
37
+ const fromElement = parser.parse(fromAttr);
38
+ const { integrate } = await import('../runtime/interpreter.js');
39
+ await integrate(session, fromElement);
40
+ } catch (e) {
41
+ session.output = savedOutput;
42
+ throw new Error(`Failed to evaluate from="${fromAttr}": ${e}`);
43
+ }
44
+
45
+ xmlContent = session.output.join('');
46
+ session.output = savedOutput;
47
+ } else {
48
+ // Treat as literal XML string
49
+ xmlContent = substituteVariables(session, fromAttr);
50
+ }
51
+
52
+ if (!xmlContent || xmlContent.trim() === '') {
53
+ return; // Nothing to iterate over
54
+ }
55
+
56
+ // Parse the XML content
57
+ const parser = new XMLParser({
58
+ ignoreAttributes: false,
59
+ attributeNamePrefix: '@_',
60
+ parseAttributeValue: false,
61
+ preserveOrder: true,
62
+ });
63
+
64
+ let parsed;
65
+ try {
66
+ parsed = parser.parse(`<root>${xmlContent}</root>`);
67
+ } catch (e) {
68
+ throw new Error(`Failed to parse XML in <foreach>: ${e}`);
69
+ }
70
+
71
+ // Extract elements to iterate over
72
+ const items = extractElements(parsed, xpath);
73
+
74
+ // Iterate over each item
75
+ for (const item of items) {
76
+ // Store the item as a variable (as an object with tag, attributes, text)
77
+ setVariable(session, asVar, item, false);
78
+
79
+ // Execute the loop body
80
+ await integrateChildren(session, element);
81
+
82
+ if (session.isBreak) {
83
+ session.isBreak = false;
84
+ break;
85
+ }
86
+
87
+ if (session.isReturn) {
88
+ break;
89
+ }
90
+ }
91
+ }
92
+
93
+ interface XmlItem {
94
+ tag: string;
95
+ attributes: Record<string, string>;
96
+ text?: string;
97
+ }
98
+
99
+ function extractElements(parsed: any, xpath?: string): XmlItem[] {
100
+ const items: XmlItem[] = [];
101
+
102
+ // parsed is array with preserveOrder
103
+ if (!Array.isArray(parsed)) {
104
+ return items;
105
+ }
106
+
107
+ // Recursively extract all elements matching xpath
108
+ const extractFromNode = (node: any) => {
109
+ if (!node) return;
110
+
111
+ const tagName = Object.keys(node).find(k => k !== ':@' && k !== '#text' && k !== '#comment');
112
+ if (!tagName) return;
113
+
114
+ const item: XmlItem = {
115
+ tag: tagName,
116
+ attributes: {},
117
+ };
118
+
119
+ // Extract attributes
120
+ if (node[':@']) {
121
+ for (const [key, value] of Object.entries(node[':@'])) {
122
+ if (key.startsWith('@_')) {
123
+ item.attributes[key.slice(2)] = value as string;
124
+ }
125
+ }
126
+ }
127
+
128
+ // Extract text content and recurse into children
129
+ const children = node[tagName];
130
+ if (Array.isArray(children)) {
131
+ for (const child of children) {
132
+ if (child['#text']) {
133
+ item.text = (item.text || '') + child['#text'];
134
+ } else {
135
+ // Recurse into child elements
136
+ extractFromNode(child);
137
+ }
138
+ }
139
+ }
140
+
141
+ // Add this item if it matches xpath
142
+ if (!xpath || matchesXPath(item, xpath)) {
143
+ items.push(item);
144
+ }
145
+ };
146
+
147
+ // Start extraction from root
148
+ for (const rootItem of parsed) {
149
+ if (rootItem['root']) {
150
+ const rootChildren = rootItem['root'];
151
+ if (Array.isArray(rootChildren)) {
152
+ for (const child of rootChildren) {
153
+ extractFromNode(child);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ return items;
160
+ }
161
+
162
+ function matchesXPath(item: XmlItem, xpath: string): boolean {
163
+ // Simple xpath matching - just tag name for now
164
+ // Supports: "//tagname" or "tagname"
165
+ const tagMatch = xpath.match(/\/{0,2}(\w+)/);
166
+ if (tagMatch) {
167
+ return item.tag === tagMatch[1];
168
+ }
169
+ return true;
170
+ }
@@ -15,20 +15,27 @@ const SIMILARITY_CUTOFF = 0.75;
15
15
 
16
16
  // Helper: get embedding server config from config.yml
17
17
  function getEmbeddingServerConfig() {
18
- const config = yaml.load(fs.readFileSync('config.yml', 'utf8')) as any;
19
- const host = config.embeddingServer?.host || 'localhost';
20
- const port = config.embeddingServer?.port || 11435;
21
- return { host, port };
18
+ try {
19
+ const configPath = process.env.DIRAC_CONFIG || 'config.yml';
20
+ const config = yaml.load(fs.readFileSync(configPath, 'utf8')) as any;
21
+ const host = config.embeddingServer?.host || 'localhost';
22
+ const port = config.embeddingServer?.port || 11434;
23
+ const model = config.embeddingServer?.model || 'embeddinggemma';
24
+ return { host, port, model };
25
+ } catch (e) {
26
+ // Fallback to defaults if config file not found
27
+ return { host: 'localhost', port: 11434, model: 'embeddinggemma' };
28
+ }
22
29
  }
23
30
 
24
31
  // Helper: call Ollama embedding API directly
25
32
  async function getEmbeddings(tags: string[]): Promise<number[][]> {
26
- const { host, port } = getEmbeddingServerConfig();
33
+ const { host, port, model } = getEmbeddingServerConfig();
27
34
  return await Promise.all(tags.map(async tag => {
28
35
  const response = await fetch(`http://${host}:${port}/api/embeddings`, {
29
36
  method: 'POST',
30
37
  headers: { 'Content-Type': 'application/json' },
31
- body: JSON.stringify({ model: 'embeddinggemma', prompt: tag })
38
+ body: JSON.stringify({ model, prompt: tag })
32
39
  });
33
40
  const data = await response.json();
34
41
  return data.embedding;
@@ -67,6 +74,12 @@ export async function executeTagCheck(session: DiracSession, element: DiracEleme
67
74
 
68
75
  for (const child of element.children) {
69
76
  const tagName = child.tag;
77
+
78
+ // Skip text nodes (empty tag names) and whitespace
79
+ if (!tagName || tagName.trim() === '') {
80
+ continue;
81
+ }
82
+
70
83
  console.error(`[tag-check] Checking tag: <${tagName}>`);
71
84
  let allValid = false;
72
85
  let correctedTag: string | null = null;
@@ -33,10 +33,10 @@ export class TestRunner {
33
33
  constructor(testDir: string = 'tests', configPath?: string) {
34
34
  this.testDir = testDir;
35
35
 
36
- // Load default config
37
- const defaultConfigPath = configPath || path.resolve(process.cwd(), 'config.yml');
38
- if (fs.existsSync(defaultConfigPath)) {
39
- this.config = yaml.load(fs.readFileSync(defaultConfigPath, 'utf8')) || {};
36
+ // For tests, always use ollama as the default provider (no API keys needed)
37
+ // Unless a specific test config is provided
38
+ if (configPath && fs.existsSync(configPath)) {
39
+ this.config = yaml.load(fs.readFileSync(configPath, 'utf8')) || {};
40
40
  } else {
41
41
  this.config = { llmProvider: 'ollama', llmModel: 'llama2' };
42
42
  }
@@ -106,6 +106,7 @@ export interface DiracSession {
106
106
 
107
107
  // Extend mechanism
108
108
  skipSubroutineRegistration: boolean;
109
+ currentSubroutineName?: string; // Track currently executing subroutine for available-subroutines
109
110
 
110
111
  // Debugging
111
112
  debug: boolean;
@@ -23,23 +23,26 @@ export interface ValidationResult {
23
23
  // Helper: get embedding server config from config.yml
24
24
  function getEmbeddingServerConfig() {
25
25
  try {
26
- const config = yaml.load(fs.readFileSync('config.yml', 'utf8')) as any;
26
+ const configPath = process.env.DIRAC_CONFIG || 'config.yml';
27
+ const config = yaml.load(fs.readFileSync(configPath, 'utf8')) as any;
27
28
  const host = config.embeddingServer?.host || 'localhost';
28
- const port = config.embeddingServer?.port || 11435;
29
- return { host, port };
30
- } catch {
31
- return { host: 'localhost', port: 11435 };
29
+ const port = config.embeddingServer?.port || 11434;
30
+ const model = config.embeddingServer?.model || 'embeddinggemma';
31
+ return { host, port, model };
32
+ } catch (e) {
33
+ // Fallback to defaults if config file not found
34
+ return { host: 'localhost', port: 11434, model: 'embeddinggemma' };
32
35
  }
33
36
  }
34
37
 
35
38
  // Helper: call Ollama embedding API directly
36
39
  async function getEmbeddings(tags: string[]): Promise<number[][]> {
37
- const { host, port } = getEmbeddingServerConfig();
40
+ const { host, port, model } = getEmbeddingServerConfig();
38
41
  return await Promise.all(tags.map(async tag => {
39
42
  const response = await fetch(`http://${host}:${port}/api/embeddings`, {
40
43
  method: 'POST',
41
44
  headers: { 'Content-Type': 'application/json' },
42
- body: JSON.stringify({ model: 'embeddinggemma', prompt: tag })
45
+ body: JSON.stringify({ model, prompt: tag })
43
46
  });
44
47
  const data = await response.json();
45
48
  return data.embedding;
@@ -179,7 +182,8 @@ export async function validateDiracCode(
179
182
 
180
183
  // Recursively validate all elements
181
184
  async function validateElement(element: DiracElement) {
182
- if (element.tag && element.tag !== 'dirac' && element.tag !== '') {
185
+ // Skip text nodes and whitespace-only tags
186
+ if (element.tag && element.tag !== 'dirac' && element.tag.trim() !== '') {
183
187
  const result = await validateTag(session, element, options);
184
188
  results.push(result);
185
189
 
@@ -0,0 +1,32 @@
1
+ <!-- TEST: available-subroutines with foreach and attr -->
2
+ <!-- EXPECT: - show-subroutines:
3
+ - greet: Greets someone
4
+ - gender: Returns gender identity
5
+ - age: Returns the person age -->
6
+ <dirac>
7
+ <subroutine name="TestSuite">
8
+ <subroutine name="age" description="Returns the person age">
9
+ <output>42</output>
10
+ </subroutine>
11
+
12
+ <subroutine name="gender" description="Returns gender identity">
13
+ <output>Male</output>
14
+ </subroutine>
15
+
16
+ <subroutine name="greet" description="Greets someone">
17
+ <output>Hello!</output>
18
+ </subroutine>
19
+
20
+ <subroutine name="show-subroutines">
21
+ <defvar name="subs"><available-subroutines /></defvar>
22
+ <foreach from="$subs" as="sub" xpath="//subroutine">
23
+ <output>- <attr name="name" from="$sub" />: <attr name="description" from="$sub" />
24
+ </output>
25
+ </foreach>
26
+ </subroutine>
27
+
28
+ <show-subroutines />
29
+ </subroutine>
30
+
31
+ <TestSuite />
32
+ </dirac>