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