dirac-browser 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/chatbox.d.ts +1 -0
- package/dist/chatbox.js +81 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +39 -0
- package/dist/runtime/braket-parser.d.ts +44 -0
- package/dist/runtime/braket-parser.js +208 -0
- package/dist/runtime/interpreter.d.ts +6 -0
- package/dist/runtime/interpreter.js +110 -0
- package/dist/runtime/parser.d.ts +11 -0
- package/dist/runtime/parser.js +135 -0
- package/dist/runtime/session.d.ts +43 -0
- package/dist/runtime/session.js +171 -0
- package/dist/tags/assign.d.ts +6 -0
- package/dist/tags/assign.js +32 -0
- package/dist/tags/call.d.ts +6 -0
- package/dist/tags/call.js +130 -0
- package/dist/tags/defvar.d.ts +6 -0
- package/dist/tags/defvar.js +27 -0
- package/dist/tags/eval.d.ts +6 -0
- package/dist/tags/eval.js +55 -0
- package/dist/tags/execute.d.ts +6 -0
- package/dist/tags/execute.js +45 -0
- package/dist/tags/expr.d.ts +8 -0
- package/dist/tags/expr.js +107 -0
- package/dist/tags/if.d.ts +6 -0
- package/dist/tags/if.js +46 -0
- package/dist/tags/llm.d.ts +6 -0
- package/dist/tags/llm.js +73 -0
- package/dist/tags/loop.d.ts +6 -0
- package/dist/tags/loop.js +31 -0
- package/dist/tags/output.d.ts +6 -0
- package/dist/tags/output.js +19 -0
- package/dist/tags/parameters.d.ts +6 -0
- package/dist/tags/parameters.js +63 -0
- package/dist/tags/subroutine.d.ts +6 -0
- package/dist/tags/subroutine.js +41 -0
- package/dist/tags/variable.d.ts +6 -0
- package/dist/tags/variable.js +20 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/llm-adapter.d.ts +22 -0
- package/dist/utils/llm-adapter.js +91 -0
- package/examples/llm-reflection-test.di +18 -0
- package/package.json +21 -0
- package/src/chatbox.ts +89 -0
- package/src/index.ts +47 -0
- package/src/runtime/braket-parser.ts +234 -0
- package/src/runtime/interpreter.ts +129 -0
- package/src/runtime/parser.ts +151 -0
- package/src/runtime/session.ts +209 -0
- package/src/tags/assign.ts +37 -0
- package/src/tags/call.ts +166 -0
- package/src/tags/defvar.ts +32 -0
- package/src/tags/eval.ts +65 -0
- package/src/tags/execute.ts +52 -0
- package/src/tags/expr.ts +128 -0
- package/src/tags/if.ts +58 -0
- package/src/tags/llm.ts +88 -0
- package/src/tags/loop.ts +43 -0
- package/src/tags/output.ts +23 -0
- package/src/tags/parameters.ts +77 -0
- package/src/tags/subroutine.ts +52 -0
- package/src/tags/variable.ts +27 -0
- package/src/types/index.ts +103 -0
- package/src/utils/llm-adapter.ts +118 -0
- package/tsconfig.json +18 -0
package/src/tags/if.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <if> tag - conditional execution
|
|
3
|
+
* Maps to mask_tag_if in MASK
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { substituteVariables, getVariable } from '../runtime/session.js';
|
|
8
|
+
import { integrateChildren } from '../runtime/interpreter.js';
|
|
9
|
+
|
|
10
|
+
export async function executeIf(session: DiracSession, element: DiracElement): Promise<void> {
|
|
11
|
+
const test = element.attributes.test;
|
|
12
|
+
|
|
13
|
+
if (!test) {
|
|
14
|
+
throw new Error('<if> requires test attribute');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const condition = evaluateCondition(session, test);
|
|
18
|
+
|
|
19
|
+
if (condition) {
|
|
20
|
+
await integrateChildren(session, element);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function evaluateCondition(session: DiracSession, test: string): boolean {
|
|
25
|
+
// Substitute variables first
|
|
26
|
+
const substituted = substituteVariables(session, test);
|
|
27
|
+
|
|
28
|
+
// Simple condition evaluation (can be enhanced later)
|
|
29
|
+
// Supports: ==, !=, <, >, <=, >=
|
|
30
|
+
|
|
31
|
+
const operators = ['==', '!=', '<=', '>=', '<', '>'];
|
|
32
|
+
|
|
33
|
+
for (const op of operators) {
|
|
34
|
+
const parts = substituted.split(op);
|
|
35
|
+
if (parts.length === 2) {
|
|
36
|
+
const left = parts[0].trim();
|
|
37
|
+
const right = parts[1].trim();
|
|
38
|
+
|
|
39
|
+
switch (op) {
|
|
40
|
+
case '==':
|
|
41
|
+
return left === right;
|
|
42
|
+
case '!=':
|
|
43
|
+
return left !== right;
|
|
44
|
+
case '<':
|
|
45
|
+
return parseFloat(left) < parseFloat(right);
|
|
46
|
+
case '>':
|
|
47
|
+
return parseFloat(left) > parseFloat(right);
|
|
48
|
+
case '<=':
|
|
49
|
+
return parseFloat(left) <= parseFloat(right);
|
|
50
|
+
case '>=':
|
|
51
|
+
return parseFloat(left) >= parseFloat(right);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If no operator, treat as boolean (non-empty = true)
|
|
57
|
+
return substituted.trim() !== '' && substituted.trim() !== '0' && substituted.trim() !== 'false';
|
|
58
|
+
}
|
package/src/tags/llm.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <llm> tag - Call LLM via backend service
|
|
3
|
+
* Browser edition - makes HTTP calls to dirac-server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { setVariable, emit } from '../runtime/session.js';
|
|
8
|
+
|
|
9
|
+
export async function executeLLM(session: DiracSession, element: DiracElement): Promise<void> {
|
|
10
|
+
const model = element.attributes.model || 'claude-sonnet-4-20250514';
|
|
11
|
+
const userPrompt = element.attributes.prompt || element.text || '';
|
|
12
|
+
const name = element.attributes.name;
|
|
13
|
+
const temperature = parseFloat(element.attributes.temperature || '0');
|
|
14
|
+
const maxTokens = parseInt(element.attributes['max-tokens'] || '100', 10);
|
|
15
|
+
|
|
16
|
+
if (!userPrompt) {
|
|
17
|
+
throw new Error('<llm> requires prompt attribute or text content');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Reflect subroutines for system prompt
|
|
21
|
+
// Use getAvailableSubroutines from session
|
|
22
|
+
// (imported from ../runtime/session.js)
|
|
23
|
+
// @ts-ignore
|
|
24
|
+
const { getAvailableSubroutines } = await import('../runtime/session.js');
|
|
25
|
+
const subroutines = getAvailableSubroutines(session);
|
|
26
|
+
let systemPrompt = 'You are an expert Dirac command generator.\nAvailable subroutines:';
|
|
27
|
+
for (const sub of subroutines) {
|
|
28
|
+
systemPrompt += `\n- <${sub.name} />: ${sub.description || ''}`;
|
|
29
|
+
if (sub.parameters && sub.parameters.length > 0) {
|
|
30
|
+
systemPrompt += ' Parameters: ' + sub.parameters.map(p => `${p.name} (${p.type || 'string'})`).join(', ');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
systemPrompt += '\nInstructions: Output only valid Dirac XML tags. Do not include explanations or extra text.';
|
|
34
|
+
|
|
35
|
+
// Example (optional):
|
|
36
|
+
// systemPrompt += '\nExample: User: Show me my inventory\nOutput: <inventory />';
|
|
37
|
+
|
|
38
|
+
// Final prompt
|
|
39
|
+
const prompt = systemPrompt + '\nUser: ' + userPrompt + '\nOutput:';
|
|
40
|
+
|
|
41
|
+
// Get backend URL from config
|
|
42
|
+
const backendUrl = session.config.llmBackendUrl || 'http://localhost:3000';
|
|
43
|
+
|
|
44
|
+
if (session.debug) {
|
|
45
|
+
console.error(`[LLM] Calling backend: ${backendUrl}/api/llm/complete`);
|
|
46
|
+
console.error(`[LLM] Model: ${model}, Prompt length: ${prompt.length}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Call backend LLM API
|
|
51
|
+
const response = await fetch(`${backendUrl}/api/llm/complete`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
model,
|
|
58
|
+
prompt,
|
|
59
|
+
temperature,
|
|
60
|
+
maxTokens
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new Error(`LLM API error: ${response.status} ${response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
const result = data.response || '';
|
|
70
|
+
|
|
71
|
+
if (session.debug) {
|
|
72
|
+
console.error(`[LLM] Response length: ${result.length}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Store result if name provided
|
|
76
|
+
if (name) {
|
|
77
|
+
setVariable(session, name, result, false);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Emit result
|
|
81
|
+
emit(session, result);
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
85
|
+
console.error('[LLM] Error:', errorMsg);
|
|
86
|
+
throw new Error(`LLM call failed: ${errorMsg}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/tags/loop.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <loop> tag - iteration
|
|
3
|
+
* Maps to mask_tag_loop in MASK
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { setVariable, substituteVariables, getVariable } from '../runtime/session.js';
|
|
8
|
+
import { integrateChildren } from '../runtime/interpreter.js';
|
|
9
|
+
|
|
10
|
+
export async function executeLoop(session: DiracSession, element: DiracElement): Promise<void> {
|
|
11
|
+
const countAttr = element.attributes.count;
|
|
12
|
+
const varName = element.attributes.var || 'i';
|
|
13
|
+
|
|
14
|
+
if (!countAttr) {
|
|
15
|
+
throw new Error('<loop> requires count attribute');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const count = parseInt(substituteVariables(session, countAttr), 10);
|
|
19
|
+
|
|
20
|
+
if (isNaN(count) || count < 0) {
|
|
21
|
+
throw new Error(`Invalid loop count: ${countAttr}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const wasBreak = session.isBreak;
|
|
25
|
+
session.isBreak = false;
|
|
26
|
+
|
|
27
|
+
for (let i = 0; i < count; i++) {
|
|
28
|
+
setVariable(session, varName, i, false);
|
|
29
|
+
|
|
30
|
+
await integrateChildren(session, element);
|
|
31
|
+
|
|
32
|
+
if (session.isBreak) {
|
|
33
|
+
session.isBreak = false;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (session.isReturn) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
session.isBreak = wasBreak;
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <output> tag - emit content
|
|
3
|
+
* Maps to mask_tag_output in MASK
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { emit, substituteVariables } from '../runtime/session.js';
|
|
8
|
+
import { integrateChildren } from '../runtime/interpreter.js';
|
|
9
|
+
|
|
10
|
+
export async function executeOutput(session: DiracSession, element: DiracElement): Promise<void> {
|
|
11
|
+
// If has children, process them (handles mixed content)
|
|
12
|
+
if (element.children && element.children.length > 0) {
|
|
13
|
+
await integrateChildren(session, element);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// If only text content, use it (with variable substitution)
|
|
18
|
+
if (element.text) {
|
|
19
|
+
const content = substituteVariables(session, element.text);
|
|
20
|
+
emit(session, content);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <parameters> tag - Access parameters passed to subroutine
|
|
3
|
+
* Maps to MASK parameter selection syntax
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { getCurrentParameters, emit, setVariable } from '../runtime/session.js';
|
|
8
|
+
import { integrate } from '../runtime/interpreter.js';
|
|
9
|
+
|
|
10
|
+
export async function executeParameters(session: DiracSession, element: DiracElement): Promise<void> {
|
|
11
|
+
const select = element.attributes.select;
|
|
12
|
+
|
|
13
|
+
if (!select) {
|
|
14
|
+
throw new Error('<parameters> requires select attribute');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Get parameters from current call context
|
|
18
|
+
const params = getCurrentParameters(session);
|
|
19
|
+
|
|
20
|
+
if (!params || params.length === 0) {
|
|
21
|
+
if (session.debug) {
|
|
22
|
+
console.error(`[PARAMETERS] No parameters available`);
|
|
23
|
+
}
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The caller element is params[0] (the calling tag itself)
|
|
28
|
+
const caller = params[0];
|
|
29
|
+
|
|
30
|
+
if (select === '*') {
|
|
31
|
+
// Select all child elements - execute them
|
|
32
|
+
if (session.debug) {
|
|
33
|
+
console.error(`[PARAMETERS] Selecting all children (${caller.children.length} elements)`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const child of caller.children) {
|
|
37
|
+
await integrate(session, child);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
} else if (select.startsWith('@')) {
|
|
41
|
+
// Select attribute(s)
|
|
42
|
+
const attrName = select.slice(1); // Remove '@'
|
|
43
|
+
|
|
44
|
+
if (attrName === '*') {
|
|
45
|
+
// Select all attributes
|
|
46
|
+
if (session.debug) {
|
|
47
|
+
console.error(`[PARAMETERS] Selecting all attributes`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const attrs = Object.entries(caller.attributes)
|
|
51
|
+
.map(([key, val]) => `${key}="${val}"`)
|
|
52
|
+
.join(' ');
|
|
53
|
+
emit(session, attrs);
|
|
54
|
+
|
|
55
|
+
} else {
|
|
56
|
+
// Select specific attribute - automatically create variable with that name
|
|
57
|
+
const value = caller.attributes[attrName];
|
|
58
|
+
|
|
59
|
+
if (session.debug) {
|
|
60
|
+
console.error(`[PARAMETERS] Setting variable '${attrName}' = '${value}'`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (value !== undefined) {
|
|
64
|
+
// Automatically create variable (like defvar)
|
|
65
|
+
setVariable(session, attrName, value, false);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Execute children if any (for additional processing)
|
|
69
|
+
for (const child of element.children) {
|
|
70
|
+
await integrate(session, child);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} else {
|
|
75
|
+
throw new Error(`<parameters> invalid select: ${select}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <subroutine> tag - define reusable code block
|
|
3
|
+
* Maps to mask_tag_subroutine in MASK
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement, ParameterMetadata } from '../types/index.js';
|
|
7
|
+
import { registerSubroutine } from '../runtime/session.js';
|
|
8
|
+
|
|
9
|
+
export function executeSubroutine(session: DiracSession, element: DiracElement): void {
|
|
10
|
+
const name = element.attributes.name;
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
throw new Error('<subroutine> requires name attribute');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Extract metadata from attributes (no structural changes!)
|
|
17
|
+
const description = element.attributes.description;
|
|
18
|
+
const parameters: ParameterMetadata[] = [];
|
|
19
|
+
|
|
20
|
+
// Parse param- prefixed attributes for metadata
|
|
21
|
+
// e.g., param-color="string:required:Color name:red|blue|green"
|
|
22
|
+
for (const [attrName, attrValue] of Object.entries(element.attributes)) {
|
|
23
|
+
if (attrName.startsWith('param-')) {
|
|
24
|
+
const paramName = attrName.substring(6); // Remove "param-" prefix
|
|
25
|
+
|
|
26
|
+
// Parse format: "type:required:description:enum1|enum2|..."
|
|
27
|
+
const parts = attrValue.split(':');
|
|
28
|
+
const paramMeta: ParameterMetadata = {
|
|
29
|
+
name: paramName,
|
|
30
|
+
type: parts[0] || 'string',
|
|
31
|
+
required: parts[1] === 'required',
|
|
32
|
+
description: parts[2] || undefined,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Parse enum if present (after 3rd colon)
|
|
36
|
+
if (parts[3]) {
|
|
37
|
+
paramMeta.enum = parts[3].split('|');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
parameters.push(paramMeta);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Store subroutine exactly as before (preserves nesting and extends)
|
|
45
|
+
const subroutine: DiracElement = {
|
|
46
|
+
tag: 'subroutine',
|
|
47
|
+
attributes: { ...element.attributes },
|
|
48
|
+
children: element.children,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
registerSubroutine(session, name, subroutine, description, parameters.length > 0 ? parameters : undefined);
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <variable> tag - retrieve variable value
|
|
3
|
+
* Outputs the stored variable content (text or tags)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession, DiracElement } from '../types/index.js';
|
|
7
|
+
import { getVariable, emit } from '../runtime/session.js';
|
|
8
|
+
|
|
9
|
+
export function executeVariable(session: DiracSession, element: DiracElement): void {
|
|
10
|
+
const name = element.attributes.name;
|
|
11
|
+
|
|
12
|
+
if (!name) {
|
|
13
|
+
throw new Error('<variable> requires name attribute');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const value = getVariable(session, name);
|
|
17
|
+
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
if (session.debug) {
|
|
20
|
+
console.error(`[Warning] Variable '${name}' is undefined`);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Output the variable value
|
|
26
|
+
emit(session, String(value));
|
|
27
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for Dirac interpreter
|
|
3
|
+
* Directly based on MASK C implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface DiracElement {
|
|
7
|
+
tag: string;
|
|
8
|
+
attributes: Record<string, string>;
|
|
9
|
+
children: DiracElement[];
|
|
10
|
+
text?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Variable info - maps to VarInfo in MASK
|
|
15
|
+
*/
|
|
16
|
+
export interface Variable {
|
|
17
|
+
name: string;
|
|
18
|
+
value: any;
|
|
19
|
+
visible: boolean;
|
|
20
|
+
boundary: number; // scope boundary marker
|
|
21
|
+
passby: 'value' | 'ref';
|
|
22
|
+
refName?: string; // for pass-by-reference
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Subroutine info - maps to Subroutine in MASK
|
|
27
|
+
*/
|
|
28
|
+
export interface Subroutine {
|
|
29
|
+
name: string;
|
|
30
|
+
element: DiracElement;
|
|
31
|
+
boundary: number; // scope boundary marker
|
|
32
|
+
extends?: string; // parent subroutine
|
|
33
|
+
description?: string; // For LLM reflection
|
|
34
|
+
parameters?: ParameterMetadata[]; // For LLM reflection
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Parameter metadata for reflection API
|
|
39
|
+
*/
|
|
40
|
+
export interface ParameterMetadata {
|
|
41
|
+
name: string;
|
|
42
|
+
type: string;
|
|
43
|
+
required: boolean;
|
|
44
|
+
description?: string;
|
|
45
|
+
enum?: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execution context - maps to MaskSession in MASK
|
|
50
|
+
*/
|
|
51
|
+
export interface DiracSession {
|
|
52
|
+
// Variable stack (all variables are on stack)
|
|
53
|
+
variables: Variable[];
|
|
54
|
+
|
|
55
|
+
// Subroutine registry
|
|
56
|
+
subroutines: Subroutine[];
|
|
57
|
+
|
|
58
|
+
// Scope boundaries (for cleanup)
|
|
59
|
+
varBoundary: number;
|
|
60
|
+
subBoundary: number;
|
|
61
|
+
|
|
62
|
+
// Parameter stack (for subroutine calls)
|
|
63
|
+
parameterStack: DiracElement[][];
|
|
64
|
+
|
|
65
|
+
// Output buffer
|
|
66
|
+
output: string[];
|
|
67
|
+
|
|
68
|
+
// LLM client
|
|
69
|
+
llmClient?: any;
|
|
70
|
+
|
|
71
|
+
// Execution limits
|
|
72
|
+
limits: {
|
|
73
|
+
maxLLMCalls: number;
|
|
74
|
+
currentLLMCalls: number;
|
|
75
|
+
maxDepth: number;
|
|
76
|
+
currentDepth: number;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Control flow
|
|
80
|
+
isReturn: boolean;
|
|
81
|
+
isBreak: boolean;
|
|
82
|
+
|
|
83
|
+
// Debugging
|
|
84
|
+
debug: boolean;
|
|
85
|
+
|
|
86
|
+
// Import tracking
|
|
87
|
+
currentFile?: string;
|
|
88
|
+
importedFiles?: Set<string>;
|
|
89
|
+
|
|
90
|
+
// Configuration reference
|
|
91
|
+
config: DiracConfig;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface DiracConfig {
|
|
95
|
+
apiKey?: string;
|
|
96
|
+
model?: string;
|
|
97
|
+
debug?: boolean;
|
|
98
|
+
maxLLMCalls?: number;
|
|
99
|
+
maxDepth?: number;
|
|
100
|
+
filePath?: string; // Current file path for imports
|
|
101
|
+
customContext?: Record<string, any>; // Custom context for browser integration
|
|
102
|
+
llmBackendUrl?: string; // Backend URL for LLM calls (browser edition)
|
|
103
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Adapter - Auto-generate prompts and JSON→XML converters
|
|
3
|
+
* from Dirac subroutine definitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DiracSession } from '../types/index.js';
|
|
7
|
+
import { getAvailableSubroutines } from '../runtime/session.js';
|
|
8
|
+
|
|
9
|
+
export interface LLMPromptGenerator {
|
|
10
|
+
generatePrompt(userInput: string): string;
|
|
11
|
+
intentToXML(intent: any): string | null;
|
|
12
|
+
getAvailableActions(): string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create LLM adapter from session's registered subroutines
|
|
17
|
+
*/
|
|
18
|
+
export function createLLMAdapter(session: DiracSession): LLMPromptGenerator {
|
|
19
|
+
const subroutines = getAvailableSubroutines(session);
|
|
20
|
+
|
|
21
|
+
// Build JSON schema from subroutines
|
|
22
|
+
const actions = subroutines.map(s => `"${s.name}"`).join('|');
|
|
23
|
+
|
|
24
|
+
// Build examples
|
|
25
|
+
const examples = subroutines.slice(0, 3).map(sub => {
|
|
26
|
+
if (!sub.parameters || sub.parameters.length === 0) {
|
|
27
|
+
return `"${sub.name}" → {"action":"${sub.name}","params":{}}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const firstParam = sub.parameters[0];
|
|
31
|
+
const exampleValue = firstParam.enum?.[0] || 'value';
|
|
32
|
+
return `"call ${sub.name}" → {"action":"${sub.name}","params":{"${firstParam.name}":"${exampleValue}"}}`;
|
|
33
|
+
}).join('\n');
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
generatePrompt(userInput: string): string {
|
|
37
|
+
return `You are a command parser. Convert user input to JSON.
|
|
38
|
+
Return ONLY valid JSON, no other text.
|
|
39
|
+
|
|
40
|
+
Format: {"action": ${actions}, "params": {}}
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
${examples}
|
|
44
|
+
|
|
45
|
+
User: ${userInput}
|
|
46
|
+
JSON:`;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
intentToXML(intent: any): string | null {
|
|
50
|
+
if (!intent || !intent.action) return null;
|
|
51
|
+
|
|
52
|
+
const sub = subroutines.find(s => s.name === intent.action);
|
|
53
|
+
if (!sub) return null;
|
|
54
|
+
|
|
55
|
+
const attrs: string[] = [`name="${sub.name}"`];
|
|
56
|
+
|
|
57
|
+
// Map params to XML attributes
|
|
58
|
+
if (sub.parameters) {
|
|
59
|
+
for (const param of sub.parameters) {
|
|
60
|
+
const value = intent.params?.[param.name];
|
|
61
|
+
|
|
62
|
+
if (value != null) {
|
|
63
|
+
// Validate enum if present
|
|
64
|
+
if (param.enum && !param.enum.includes(value)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
attrs.push(`${param.name}="${value}"`);
|
|
68
|
+
} else if (param.required) {
|
|
69
|
+
return null; // Missing required parameter
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return `<call ${attrs.join(' ')}/>`;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
getAvailableActions(): string[] {
|
|
78
|
+
return subroutines.map(s => s.name);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Higher-level helper: execute user command via LLM
|
|
85
|
+
*/
|
|
86
|
+
export async function executeUserCommand(
|
|
87
|
+
session: DiracSession,
|
|
88
|
+
userInput: string,
|
|
89
|
+
llmExecuteFn: (prompt: string) => Promise<string>
|
|
90
|
+
): Promise<{ success: boolean; xml?: string; error?: string }> {
|
|
91
|
+
try {
|
|
92
|
+
const adapter = createLLMAdapter(session);
|
|
93
|
+
const prompt = adapter.generatePrompt(userInput);
|
|
94
|
+
|
|
95
|
+
// Call LLM
|
|
96
|
+
const llmResponse = await llmExecuteFn(prompt);
|
|
97
|
+
|
|
98
|
+
// Parse JSON
|
|
99
|
+
let jsonStr = llmResponse.trim();
|
|
100
|
+
jsonStr = jsonStr.replace(/```json\s*/g, '').replace(/```\s*/g, '');
|
|
101
|
+
|
|
102
|
+
const intent = JSON.parse(jsonStr);
|
|
103
|
+
|
|
104
|
+
// Convert to XML
|
|
105
|
+
const xml = adapter.intentToXML(intent);
|
|
106
|
+
|
|
107
|
+
if (!xml) {
|
|
108
|
+
return { success: false, error: 'Could not convert intent to valid command' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { success: true, xml };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: error instanceof Error ? error.message : String(error)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"moduleResolution": "node",
|
|
14
|
+
"resolveJsonModule": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|