namel3ss 0.1.0a0__py3-none-any.whl
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.
- namel3ss/__init__.py +4 -0
- namel3ss/ast/__init__.py +5 -0
- namel3ss/ast/agents.py +13 -0
- namel3ss/ast/ai.py +23 -0
- namel3ss/ast/base.py +10 -0
- namel3ss/ast/expressions.py +55 -0
- namel3ss/ast/nodes.py +86 -0
- namel3ss/ast/pages.py +43 -0
- namel3ss/ast/program.py +22 -0
- namel3ss/ast/records.py +27 -0
- namel3ss/ast/statements.py +107 -0
- namel3ss/ast/tool.py +11 -0
- namel3ss/cli/__init__.py +2 -0
- namel3ss/cli/actions_mode.py +39 -0
- namel3ss/cli/app_loader.py +22 -0
- namel3ss/cli/commands/action.py +27 -0
- namel3ss/cli/commands/run.py +43 -0
- namel3ss/cli/commands/ui.py +26 -0
- namel3ss/cli/commands/validate.py +23 -0
- namel3ss/cli/format_mode.py +30 -0
- namel3ss/cli/io/json_io.py +19 -0
- namel3ss/cli/io/read_source.py +16 -0
- namel3ss/cli/json_io.py +21 -0
- namel3ss/cli/lint_mode.py +29 -0
- namel3ss/cli/main.py +135 -0
- namel3ss/cli/new_mode.py +146 -0
- namel3ss/cli/runner.py +28 -0
- namel3ss/cli/studio_mode.py +22 -0
- namel3ss/cli/ui_mode.py +14 -0
- namel3ss/config/__init__.py +4 -0
- namel3ss/config/dotenv.py +33 -0
- namel3ss/config/loader.py +83 -0
- namel3ss/config/model.py +49 -0
- namel3ss/errors/__init__.py +2 -0
- namel3ss/errors/base.py +34 -0
- namel3ss/errors/render.py +22 -0
- namel3ss/format/__init__.py +3 -0
- namel3ss/format/formatter.py +18 -0
- namel3ss/format/rules.py +97 -0
- namel3ss/ir/__init__.py +3 -0
- namel3ss/ir/lowering/__init__.py +4 -0
- namel3ss/ir/lowering/agents.py +42 -0
- namel3ss/ir/lowering/ai.py +45 -0
- namel3ss/ir/lowering/expressions.py +49 -0
- namel3ss/ir/lowering/flow.py +21 -0
- namel3ss/ir/lowering/pages.py +48 -0
- namel3ss/ir/lowering/program.py +34 -0
- namel3ss/ir/lowering/records.py +25 -0
- namel3ss/ir/lowering/statements.py +122 -0
- namel3ss/ir/lowering/tools.py +16 -0
- namel3ss/ir/model/__init__.py +50 -0
- namel3ss/ir/model/agents.py +33 -0
- namel3ss/ir/model/ai.py +31 -0
- namel3ss/ir/model/base.py +20 -0
- namel3ss/ir/model/expressions.py +50 -0
- namel3ss/ir/model/pages.py +43 -0
- namel3ss/ir/model/program.py +28 -0
- namel3ss/ir/model/statements.py +76 -0
- namel3ss/ir/model/tools.py +11 -0
- namel3ss/ir/nodes.py +88 -0
- namel3ss/lexer/__init__.py +2 -0
- namel3ss/lexer/lexer.py +152 -0
- namel3ss/lexer/tokens.py +98 -0
- namel3ss/lint/__init__.py +4 -0
- namel3ss/lint/engine.py +125 -0
- namel3ss/lint/semantic.py +45 -0
- namel3ss/lint/text_scan.py +70 -0
- namel3ss/lint/types.py +22 -0
- namel3ss/parser/__init__.py +3 -0
- namel3ss/parser/agent.py +78 -0
- namel3ss/parser/ai.py +113 -0
- namel3ss/parser/constraints.py +37 -0
- namel3ss/parser/core.py +166 -0
- namel3ss/parser/expressions.py +105 -0
- namel3ss/parser/flow.py +37 -0
- namel3ss/parser/pages.py +76 -0
- namel3ss/parser/program.py +45 -0
- namel3ss/parser/records.py +66 -0
- namel3ss/parser/statements/__init__.py +27 -0
- namel3ss/parser/statements/control_flow.py +116 -0
- namel3ss/parser/statements/core.py +66 -0
- namel3ss/parser/statements/data.py +17 -0
- namel3ss/parser/statements/letset.py +22 -0
- namel3ss/parser/statements.py +1 -0
- namel3ss/parser/tokens.py +35 -0
- namel3ss/parser/tool.py +29 -0
- namel3ss/runtime/__init__.py +3 -0
- namel3ss/runtime/ai/http/client.py +24 -0
- namel3ss/runtime/ai/mock_provider.py +5 -0
- namel3ss/runtime/ai/provider.py +29 -0
- namel3ss/runtime/ai/providers/__init__.py +18 -0
- namel3ss/runtime/ai/providers/_shared/errors.py +20 -0
- namel3ss/runtime/ai/providers/_shared/parse.py +18 -0
- namel3ss/runtime/ai/providers/anthropic.py +55 -0
- namel3ss/runtime/ai/providers/gemini.py +50 -0
- namel3ss/runtime/ai/providers/mistral.py +51 -0
- namel3ss/runtime/ai/providers/mock.py +23 -0
- namel3ss/runtime/ai/providers/ollama.py +39 -0
- namel3ss/runtime/ai/providers/openai.py +55 -0
- namel3ss/runtime/ai/providers/registry.py +38 -0
- namel3ss/runtime/ai/trace.py +18 -0
- namel3ss/runtime/executor/__init__.py +3 -0
- namel3ss/runtime/executor/agents.py +91 -0
- namel3ss/runtime/executor/ai_runner.py +90 -0
- namel3ss/runtime/executor/api.py +54 -0
- namel3ss/runtime/executor/assign.py +40 -0
- namel3ss/runtime/executor/context.py +31 -0
- namel3ss/runtime/executor/executor.py +77 -0
- namel3ss/runtime/executor/expr_eval.py +110 -0
- namel3ss/runtime/executor/records_ops.py +64 -0
- namel3ss/runtime/executor/result.py +13 -0
- namel3ss/runtime/executor/signals.py +6 -0
- namel3ss/runtime/executor/statements.py +99 -0
- namel3ss/runtime/memory/manager.py +52 -0
- namel3ss/runtime/memory/profile.py +17 -0
- namel3ss/runtime/memory/semantic.py +20 -0
- namel3ss/runtime/memory/short_term.py +18 -0
- namel3ss/runtime/records/service.py +105 -0
- namel3ss/runtime/store/__init__.py +2 -0
- namel3ss/runtime/store/memory_store.py +62 -0
- namel3ss/runtime/tools/registry.py +13 -0
- namel3ss/runtime/ui/__init__.py +2 -0
- namel3ss/runtime/ui/actions.py +124 -0
- namel3ss/runtime/validators/__init__.py +2 -0
- namel3ss/runtime/validators/constraints.py +126 -0
- namel3ss/schema/__init__.py +2 -0
- namel3ss/schema/records.py +52 -0
- namel3ss/studio/__init__.py +4 -0
- namel3ss/studio/api.py +115 -0
- namel3ss/studio/edit/__init__.py +3 -0
- namel3ss/studio/edit/ops.py +80 -0
- namel3ss/studio/edit/selectors.py +74 -0
- namel3ss/studio/edit/transform.py +39 -0
- namel3ss/studio/server.py +175 -0
- namel3ss/studio/session.py +11 -0
- namel3ss/studio/web/app.js +248 -0
- namel3ss/studio/web/index.html +44 -0
- namel3ss/studio/web/styles.css +42 -0
- namel3ss/templates/__init__.py +3 -0
- namel3ss/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- namel3ss/templates/ai_assistant/.gitignore +1 -0
- namel3ss/templates/ai_assistant/README.md +10 -0
- namel3ss/templates/ai_assistant/app.ai +30 -0
- namel3ss/templates/crud/.gitignore +1 -0
- namel3ss/templates/crud/README.md +10 -0
- namel3ss/templates/crud/app.ai +26 -0
- namel3ss/templates/multi_agent/.gitignore +1 -0
- namel3ss/templates/multi_agent/README.md +10 -0
- namel3ss/templates/multi_agent/app.ai +43 -0
- namel3ss/ui/__init__.py +2 -0
- namel3ss/ui/manifest.py +220 -0
- namel3ss/utils/__init__.py +2 -0
- namel3ss-0.1.0a0.dist-info/METADATA +123 -0
- namel3ss-0.1.0a0.dist-info/RECORD +157 -0
- namel3ss-0.1.0a0.dist-info/WHEEL +5 -0
- namel3ss-0.1.0a0.dist-info/entry_points.txt +2 -0
- namel3ss-0.1.0a0.dist-info/top_level.txt +1 -0
namel3ss/format/rules.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def migrate_buttons(lines: List[str]) -> List[str]:
|
|
8
|
+
migrated: List[str] = []
|
|
9
|
+
pattern = re.compile(r'(\s*)button\s+"([^"]+)"\s+calls\s+flow\s+"([^"]+)"\s*$', re.IGNORECASE)
|
|
10
|
+
for line in lines:
|
|
11
|
+
m = pattern.match(line)
|
|
12
|
+
if m:
|
|
13
|
+
indent = m.group(1)
|
|
14
|
+
label = m.group(2)
|
|
15
|
+
flow = m.group(3)
|
|
16
|
+
migrated.append(f'{indent}button "{label}":')
|
|
17
|
+
migrated.append(f"{indent} calls flow \"{flow}\"")
|
|
18
|
+
continue
|
|
19
|
+
migrated.append(line)
|
|
20
|
+
return migrated
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_spacing(line: str) -> str:
|
|
24
|
+
indent_len = len(line) - len(line.lstrip(" "))
|
|
25
|
+
indent = " " * indent_len
|
|
26
|
+
rest = line.strip()
|
|
27
|
+
if rest == "":
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
# headers with names
|
|
31
|
+
m = re.match(r'^(flow|page|record|ai|agent|tool)\s+"([^"]+)"\s*:?\s*$', rest)
|
|
32
|
+
if m:
|
|
33
|
+
return f'{indent}{m.group(1)} "{m.group(2)}":'
|
|
34
|
+
|
|
35
|
+
if rest.startswith("button "):
|
|
36
|
+
m = re.match(r'^button\s+"([^"]+)"\s*:$', rest)
|
|
37
|
+
if m:
|
|
38
|
+
return f'{indent}button "{m.group(1)}":'
|
|
39
|
+
|
|
40
|
+
# property with "is"
|
|
41
|
+
m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)\s+is\s+(.+)$', rest)
|
|
42
|
+
if m:
|
|
43
|
+
rest = f"{m.group(1)} is {m.group(2)}"
|
|
44
|
+
|
|
45
|
+
# ask ai pattern
|
|
46
|
+
m = re.match(
|
|
47
|
+
r'^ask\s+ai\s+"([^"]+)"\s+with\s+input\s*:?\s*(.+?)\s+as\s+([A-Za-z_][A-Za-z0-9_]*)$',
|
|
48
|
+
rest,
|
|
49
|
+
)
|
|
50
|
+
if m:
|
|
51
|
+
rest = f'ask ai "{m.group(1)}" with input: {m.group(2)} as {m.group(3)}'
|
|
52
|
+
|
|
53
|
+
# calls flow line
|
|
54
|
+
m = re.match(r'^calls\s+flow\s+"([^"]+)"\s*$', rest)
|
|
55
|
+
if m:
|
|
56
|
+
rest = f'calls flow "{m.group(1)}"'
|
|
57
|
+
|
|
58
|
+
rest = re.sub(r'\s+:', ":", rest)
|
|
59
|
+
return f"{indent}{rest}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def normalize_indentation(lines: List[str]) -> List[str]:
|
|
63
|
+
result: List[str] = []
|
|
64
|
+
indent_stack = [0]
|
|
65
|
+
for line in lines:
|
|
66
|
+
if line.strip() == "":
|
|
67
|
+
result.append("")
|
|
68
|
+
continue
|
|
69
|
+
leading = len(line) - len(line.lstrip(" "))
|
|
70
|
+
if leading > indent_stack[-1]:
|
|
71
|
+
indent_stack.append(leading)
|
|
72
|
+
else:
|
|
73
|
+
while indent_stack and leading < indent_stack[-1]:
|
|
74
|
+
indent_stack.pop()
|
|
75
|
+
if leading != indent_stack[-1]:
|
|
76
|
+
indent_stack.append(leading)
|
|
77
|
+
depth = max(0, len(indent_stack) - 1)
|
|
78
|
+
content = line.lstrip(" ")
|
|
79
|
+
result.append(" " * depth + content)
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def collapse_blank_lines(lines: List[str]) -> List[str]:
|
|
84
|
+
cleaned: List[str] = []
|
|
85
|
+
for line in lines:
|
|
86
|
+
if line.strip() == "":
|
|
87
|
+
if cleaned and cleaned[-1] == "":
|
|
88
|
+
continue
|
|
89
|
+
cleaned.append("")
|
|
90
|
+
else:
|
|
91
|
+
cleaned.append(line)
|
|
92
|
+
# trim leading/trailing blanks
|
|
93
|
+
while cleaned and cleaned[0] == "":
|
|
94
|
+
cleaned.pop(0)
|
|
95
|
+
while cleaned and cleaned[-1] == "":
|
|
96
|
+
cleaned.pop()
|
|
97
|
+
return cleaned
|
namel3ss/ir/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from namel3ss.ast import nodes as ast
|
|
6
|
+
from namel3ss.errors.base import Namel3ssError
|
|
7
|
+
from namel3ss.ir.model.agents import AgentDecl, ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
|
|
8
|
+
from namel3ss.ir.model.ai import AIDecl
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _lower_agents(agents: List[ast.AgentDecl], ais: Dict[str, AIDecl]) -> Dict[str, AgentDecl]:
|
|
12
|
+
agent_map: Dict[str, AgentDecl] = {}
|
|
13
|
+
for agent in agents:
|
|
14
|
+
if agent.name in agent_map:
|
|
15
|
+
raise Namel3ssError(f"Duplicate agent declaration '{agent.name}'", line=agent.line, column=agent.column)
|
|
16
|
+
if agent.ai_name not in ais:
|
|
17
|
+
raise Namel3ssError(f"Agent '{agent.name}' references unknown AI '{agent.ai_name}'", line=agent.line, column=agent.column)
|
|
18
|
+
agent_map[agent.name] = AgentDecl(
|
|
19
|
+
name=agent.name,
|
|
20
|
+
ai_name=agent.ai_name,
|
|
21
|
+
system_prompt=agent.system_prompt,
|
|
22
|
+
line=agent.line,
|
|
23
|
+
column=agent.column,
|
|
24
|
+
)
|
|
25
|
+
return agent_map
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _validate_agent_reference(agent_name: str, agents: Dict[str, AgentDecl], line, column) -> None:
|
|
29
|
+
if agent_name not in agents:
|
|
30
|
+
raise Namel3ssError(f"Unknown agent '{agent_name}'", line=line, column=column)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def validate_agent_statement(stmt: RunAgentStmt | RunAgentsParallelStmt, agents: Dict[str, AgentDecl]) -> None:
|
|
34
|
+
if isinstance(stmt, RunAgentStmt):
|
|
35
|
+
_validate_agent_reference(stmt.agent_name, agents, stmt.line, stmt.column)
|
|
36
|
+
return
|
|
37
|
+
if isinstance(stmt, RunAgentsParallelStmt):
|
|
38
|
+
if not stmt.entries:
|
|
39
|
+
raise Namel3ssError("Parallel agent block requires at least one entry", line=stmt.line, column=stmt.column)
|
|
40
|
+
for entry in stmt.entries:
|
|
41
|
+
_validate_agent_reference(entry.agent_name, agents, entry.line, entry.column)
|
|
42
|
+
return
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from namel3ss.ast import nodes as ast
|
|
6
|
+
from namel3ss.errors.base import Namel3ssError
|
|
7
|
+
from namel3ss.ir.model.ai import AIDecl, AIMemory
|
|
8
|
+
from namel3ss.ir.model.tools import ToolDecl
|
|
9
|
+
from namel3ss.runtime.ai.providers.registry import is_supported_provider
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _lower_ai_decls(ais: List[ast.AIDecl], tools: Dict[str, ToolDecl]) -> Dict[str, AIDecl]:
|
|
13
|
+
ai_map: Dict[str, AIDecl] = {}
|
|
14
|
+
for ai in ais:
|
|
15
|
+
if ai.name in ai_map:
|
|
16
|
+
raise Namel3ssError(f"Duplicate AI declaration '{ai.name}'", line=ai.line, column=ai.column)
|
|
17
|
+
if not ai.model:
|
|
18
|
+
raise Namel3ssError(f"AI '{ai.name}' must specify a model", line=ai.line, column=ai.column)
|
|
19
|
+
provider = (ai.provider or "mock").lower()
|
|
20
|
+
if not is_supported_provider(provider):
|
|
21
|
+
raise Namel3ssError(f"Unknown AI provider '{provider}'", line=ai.line, column=ai.column)
|
|
22
|
+
exposed: List[str] = []
|
|
23
|
+
for tool in ai.exposed_tools:
|
|
24
|
+
if tool not in tools:
|
|
25
|
+
raise Namel3ssError(f"AI '{ai.name}' exposes unknown tool '{tool}'", line=ai.line, column=ai.column)
|
|
26
|
+
if tool in exposed:
|
|
27
|
+
raise Namel3ssError(f"Duplicate tool exposure '{tool}' in AI '{ai.name}'", line=ai.line, column=ai.column)
|
|
28
|
+
exposed.append(tool)
|
|
29
|
+
ai_map[ai.name] = AIDecl(
|
|
30
|
+
name=ai.name,
|
|
31
|
+
model=ai.model,
|
|
32
|
+
provider=provider,
|
|
33
|
+
system_prompt=ai.system_prompt,
|
|
34
|
+
exposed_tools=exposed,
|
|
35
|
+
memory=AIMemory(
|
|
36
|
+
short_term=ai.memory.short_term,
|
|
37
|
+
semantic=ai.memory.semantic,
|
|
38
|
+
profile=ai.memory.profile,
|
|
39
|
+
line=ai.memory.line,
|
|
40
|
+
column=ai.memory.column,
|
|
41
|
+
),
|
|
42
|
+
line=ai.line,
|
|
43
|
+
column=ai.column,
|
|
44
|
+
)
|
|
45
|
+
return ai_map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from namel3ss.ast import nodes as ast
|
|
4
|
+
from namel3ss.ir.model.expressions import AttrAccess, BinaryOp, Comparison, Literal, StatePath, UnaryOp, VarReference, Assignable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _lower_assignable(expr: ast.Assignable) -> Assignable:
|
|
8
|
+
if isinstance(expr, ast.VarReference):
|
|
9
|
+
return VarReference(name=expr.name, line=expr.line, column=expr.column)
|
|
10
|
+
if isinstance(expr, ast.StatePath):
|
|
11
|
+
return StatePath(path=list(expr.path), line=expr.line, column=expr.column)
|
|
12
|
+
raise TypeError(f"Unhandled assignable type: {type(expr)}")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _lower_expression(expr: ast.Expression | None):
|
|
16
|
+
if expr is None:
|
|
17
|
+
return None
|
|
18
|
+
if isinstance(expr, ast.Literal):
|
|
19
|
+
return Literal(value=expr.value, line=expr.line, column=expr.column)
|
|
20
|
+
if isinstance(expr, ast.VarReference):
|
|
21
|
+
return VarReference(name=expr.name, line=expr.line, column=expr.column)
|
|
22
|
+
if isinstance(expr, ast.AttrAccess):
|
|
23
|
+
return AttrAccess(base=expr.base, attrs=list(expr.attrs), line=expr.line, column=expr.column)
|
|
24
|
+
if isinstance(expr, ast.StatePath):
|
|
25
|
+
return StatePath(path=list(expr.path), line=expr.line, column=expr.column)
|
|
26
|
+
if isinstance(expr, ast.UnaryOp):
|
|
27
|
+
return UnaryOp(
|
|
28
|
+
op=expr.op,
|
|
29
|
+
operand=_lower_expression(expr.operand),
|
|
30
|
+
line=expr.line,
|
|
31
|
+
column=expr.column,
|
|
32
|
+
)
|
|
33
|
+
if isinstance(expr, ast.BinaryOp):
|
|
34
|
+
return BinaryOp(
|
|
35
|
+
op=expr.op,
|
|
36
|
+
left=_lower_expression(expr.left),
|
|
37
|
+
right=_lower_expression(expr.right),
|
|
38
|
+
line=expr.line,
|
|
39
|
+
column=expr.column,
|
|
40
|
+
)
|
|
41
|
+
if isinstance(expr, ast.Comparison):
|
|
42
|
+
return Comparison(
|
|
43
|
+
kind=expr.kind,
|
|
44
|
+
left=_lower_expression(expr.left),
|
|
45
|
+
right=_lower_expression(expr.right),
|
|
46
|
+
line=expr.line,
|
|
47
|
+
column=expr.column,
|
|
48
|
+
)
|
|
49
|
+
raise TypeError(f"Unhandled expression type: {type(expr)}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from namel3ss.ast import nodes as ast
|
|
4
|
+
from namel3ss.ir.lowering.statements import _lower_statement
|
|
5
|
+
from namel3ss.ir.model.agents import AgentDecl
|
|
6
|
+
from namel3ss.ir.model.program import Flow
|
|
7
|
+
from namel3ss.ir.lowering.agents import validate_agent_statement
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def lower_flow(flow: ast.Flow, agents: dict[str, AgentDecl]) -> Flow:
|
|
11
|
+
ir_body = []
|
|
12
|
+
for stmt in flow.body:
|
|
13
|
+
lowered = _lower_statement(stmt, agents)
|
|
14
|
+
validate_agent_statement(lowered, agents)
|
|
15
|
+
ir_body.append(lowered)
|
|
16
|
+
return Flow(
|
|
17
|
+
name=flow.name,
|
|
18
|
+
body=ir_body,
|
|
19
|
+
line=flow.line,
|
|
20
|
+
column=flow.column,
|
|
21
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from namel3ss.ast import nodes as ast
|
|
4
|
+
from namel3ss.errors.base import Namel3ssError
|
|
5
|
+
from namel3ss.ir.model.pages import ButtonItem, FormItem, Page, PageItem, TableItem, TextItem, TitleItem
|
|
6
|
+
from namel3ss.schema import records as schema
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _lower_page(page: ast.PageDecl, record_map: dict[str, schema.RecordSchema], flow_names: set[str]) -> Page:
|
|
10
|
+
items = [_lower_page_item(item, record_map, flow_names, page.name) for item in page.items]
|
|
11
|
+
return Page(name=page.name, items=items, line=page.line, column=page.column)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _lower_page_item(
|
|
15
|
+
item: ast.PageItem,
|
|
16
|
+
record_map: dict[str, schema.RecordSchema],
|
|
17
|
+
flow_names: set[str],
|
|
18
|
+
page_name: str,
|
|
19
|
+
) -> PageItem:
|
|
20
|
+
if isinstance(item, ast.TitleItem):
|
|
21
|
+
return TitleItem(value=item.value, line=item.line, column=item.column)
|
|
22
|
+
if isinstance(item, ast.TextItem):
|
|
23
|
+
return TextItem(value=item.value, line=item.line, column=item.column)
|
|
24
|
+
if isinstance(item, ast.FormItem):
|
|
25
|
+
if item.record_name not in record_map:
|
|
26
|
+
raise Namel3ssError(
|
|
27
|
+
f"Page '{page_name}' references unknown record '{item.record_name}'",
|
|
28
|
+
line=item.line,
|
|
29
|
+
column=item.column,
|
|
30
|
+
)
|
|
31
|
+
return FormItem(record_name=item.record_name, line=item.line, column=item.column)
|
|
32
|
+
if isinstance(item, ast.TableItem):
|
|
33
|
+
if item.record_name not in record_map:
|
|
34
|
+
raise Namel3ssError(
|
|
35
|
+
f"Page '{page_name}' references unknown record '{item.record_name}'",
|
|
36
|
+
line=item.line,
|
|
37
|
+
column=item.column,
|
|
38
|
+
)
|
|
39
|
+
return TableItem(record_name=item.record_name, line=item.line, column=item.column)
|
|
40
|
+
if isinstance(item, ast.ButtonItem):
|
|
41
|
+
if item.flow_name not in flow_names:
|
|
42
|
+
raise Namel3ssError(
|
|
43
|
+
f"Page '{page_name}' references unknown flow '{item.flow_name}'",
|
|
44
|
+
line=item.line,
|
|
45
|
+
column=item.column,
|
|
46
|
+
)
|
|
47
|
+
return ButtonItem(label=item.label, flow_name=item.flow_name, line=item.line, column=item.column)
|
|
48
|
+
raise TypeError(f"Unhandled page item type: {type(item)}")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from namel3ss.ast import nodes as ast
|
|
6
|
+
from namel3ss.ir.lowering.agents import _lower_agents
|
|
7
|
+
from namel3ss.ir.lowering.ai import _lower_ai_decls
|
|
8
|
+
from namel3ss.ir.lowering.flow import lower_flow
|
|
9
|
+
from namel3ss.ir.lowering.pages import _lower_page
|
|
10
|
+
from namel3ss.ir.lowering.records import _lower_record
|
|
11
|
+
from namel3ss.ir.lowering.tools import _lower_tools
|
|
12
|
+
from namel3ss.ir.model.program import Flow, Program
|
|
13
|
+
from namel3ss.schema import records as schema
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lower_program(program: ast.Program) -> Program:
|
|
17
|
+
record_schemas = [_lower_record(record) for record in program.records]
|
|
18
|
+
tool_map = _lower_tools(program.tools)
|
|
19
|
+
ai_map = _lower_ai_decls(program.ais, tool_map)
|
|
20
|
+
agent_map = _lower_agents(program.agents, ai_map)
|
|
21
|
+
flow_irs: List[Flow] = [lower_flow(flow, agent_map) for flow in program.flows]
|
|
22
|
+
record_map: Dict[str, schema.RecordSchema] = {rec.name: rec for rec in record_schemas}
|
|
23
|
+
flow_names = {flow.name for flow in flow_irs}
|
|
24
|
+
pages = [_lower_page(page, record_map, flow_names) for page in program.pages]
|
|
25
|
+
return Program(
|
|
26
|
+
records=record_schemas,
|
|
27
|
+
flows=flow_irs,
|
|
28
|
+
pages=pages,
|
|
29
|
+
ais=ai_map,
|
|
30
|
+
tools=tool_map,
|
|
31
|
+
agents=agent_map,
|
|
32
|
+
line=program.line,
|
|
33
|
+
column=program.column,
|
|
34
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from namel3ss.ast import nodes as ast
|
|
4
|
+
from namel3ss.ir.lowering.expressions import _lower_expression
|
|
5
|
+
from namel3ss.schema import records as schema
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _lower_record(record: ast.RecordDecl) -> schema.RecordSchema:
|
|
9
|
+
fields = []
|
|
10
|
+
for field in record.fields:
|
|
11
|
+
constraint = None
|
|
12
|
+
if field.constraint:
|
|
13
|
+
constraint = schema.FieldConstraint(
|
|
14
|
+
kind=field.constraint.kind,
|
|
15
|
+
expression=_lower_expression(field.constraint.expression) if field.constraint.expression else None,
|
|
16
|
+
pattern=field.constraint.pattern,
|
|
17
|
+
)
|
|
18
|
+
fields.append(
|
|
19
|
+
schema.FieldSchema(
|
|
20
|
+
name=field.name,
|
|
21
|
+
type_name=field.type_name,
|
|
22
|
+
constraint=constraint,
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
return schema.RecordSchema(name=record.name, fields=fields)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from namel3ss.ast import nodes as ast
|
|
4
|
+
from namel3ss.ir.lowering.expressions import _lower_assignable, _lower_expression
|
|
5
|
+
from namel3ss.ir.model.agents import ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
|
|
6
|
+
from namel3ss.ir.model.ai import AskAIStmt
|
|
7
|
+
from namel3ss.ir.model.statements import (
|
|
8
|
+
Find,
|
|
9
|
+
ForEach,
|
|
10
|
+
If,
|
|
11
|
+
Let,
|
|
12
|
+
Match,
|
|
13
|
+
MatchCase,
|
|
14
|
+
Repeat,
|
|
15
|
+
Return,
|
|
16
|
+
Save,
|
|
17
|
+
Set,
|
|
18
|
+
TryCatch,
|
|
19
|
+
)
|
|
20
|
+
from namel3ss.ir.model.statements import Statement as IRStatement
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _lower_statement(stmt: ast.Statement, agents) -> IRStatement:
|
|
24
|
+
if isinstance(stmt, ast.Let):
|
|
25
|
+
return Let(
|
|
26
|
+
name=stmt.name,
|
|
27
|
+
expression=_lower_expression(stmt.expression),
|
|
28
|
+
constant=stmt.constant,
|
|
29
|
+
line=stmt.line,
|
|
30
|
+
column=stmt.column,
|
|
31
|
+
)
|
|
32
|
+
if isinstance(stmt, ast.Set):
|
|
33
|
+
return Set(
|
|
34
|
+
target=_lower_assignable(stmt.target),
|
|
35
|
+
expression=_lower_expression(stmt.expression),
|
|
36
|
+
line=stmt.line,
|
|
37
|
+
column=stmt.column,
|
|
38
|
+
)
|
|
39
|
+
if isinstance(stmt, ast.If):
|
|
40
|
+
return If(
|
|
41
|
+
condition=_lower_expression(stmt.condition),
|
|
42
|
+
then_body=[_lower_statement(s, agents) for s in stmt.then_body],
|
|
43
|
+
else_body=[_lower_statement(s, agents) for s in stmt.else_body],
|
|
44
|
+
line=stmt.line,
|
|
45
|
+
column=stmt.column,
|
|
46
|
+
)
|
|
47
|
+
if isinstance(stmt, ast.Return):
|
|
48
|
+
return Return(
|
|
49
|
+
expression=_lower_expression(stmt.expression),
|
|
50
|
+
line=stmt.line,
|
|
51
|
+
column=stmt.column,
|
|
52
|
+
)
|
|
53
|
+
if isinstance(stmt, ast.Repeat):
|
|
54
|
+
return Repeat(
|
|
55
|
+
count=_lower_expression(stmt.count),
|
|
56
|
+
body=[_lower_statement(s, agents) for s in stmt.body],
|
|
57
|
+
line=stmt.line,
|
|
58
|
+
column=stmt.column,
|
|
59
|
+
)
|
|
60
|
+
if isinstance(stmt, ast.ForEach):
|
|
61
|
+
return ForEach(
|
|
62
|
+
name=stmt.name,
|
|
63
|
+
iterable=_lower_expression(stmt.iterable),
|
|
64
|
+
body=[_lower_statement(s, agents) for s in stmt.body],
|
|
65
|
+
line=stmt.line,
|
|
66
|
+
column=stmt.column,
|
|
67
|
+
)
|
|
68
|
+
if isinstance(stmt, ast.AskAIStmt):
|
|
69
|
+
return AskAIStmt(
|
|
70
|
+
ai_name=stmt.ai_name,
|
|
71
|
+
input_expr=_lower_expression(stmt.input_expr),
|
|
72
|
+
target=stmt.target,
|
|
73
|
+
line=stmt.line,
|
|
74
|
+
column=stmt.column,
|
|
75
|
+
)
|
|
76
|
+
if isinstance(stmt, ast.Match):
|
|
77
|
+
return Match(
|
|
78
|
+
expression=_lower_expression(stmt.expression),
|
|
79
|
+
cases=[
|
|
80
|
+
MatchCase(
|
|
81
|
+
pattern=_lower_expression(case.pattern),
|
|
82
|
+
body=[_lower_statement(s, agents) for s in case.body],
|
|
83
|
+
line=case.line,
|
|
84
|
+
column=case.column,
|
|
85
|
+
)
|
|
86
|
+
for case in stmt.cases
|
|
87
|
+
],
|
|
88
|
+
otherwise=[_lower_statement(s, agents) for s in stmt.otherwise] if stmt.otherwise is not None else None,
|
|
89
|
+
line=stmt.line,
|
|
90
|
+
column=stmt.column,
|
|
91
|
+
)
|
|
92
|
+
if isinstance(stmt, ast.TryCatch):
|
|
93
|
+
return TryCatch(
|
|
94
|
+
try_body=[_lower_statement(s, agents) for s in stmt.try_body],
|
|
95
|
+
catch_var=stmt.catch_var,
|
|
96
|
+
catch_body=[_lower_statement(s, agents) for s in stmt.catch_body],
|
|
97
|
+
line=stmt.line,
|
|
98
|
+
column=stmt.column,
|
|
99
|
+
)
|
|
100
|
+
if isinstance(stmt, ast.Save):
|
|
101
|
+
return Save(record_name=stmt.record_name, line=stmt.line, column=stmt.column)
|
|
102
|
+
if isinstance(stmt, ast.Find):
|
|
103
|
+
return Find(record_name=stmt.record_name, predicate=_lower_expression(stmt.predicate), line=stmt.line, column=stmt.column)
|
|
104
|
+
if isinstance(stmt, ast.RunAgentStmt):
|
|
105
|
+
return RunAgentStmt(
|
|
106
|
+
agent_name=stmt.agent_name,
|
|
107
|
+
input_expr=_lower_expression(stmt.input_expr),
|
|
108
|
+
target=stmt.target,
|
|
109
|
+
line=stmt.line,
|
|
110
|
+
column=stmt.column,
|
|
111
|
+
)
|
|
112
|
+
if isinstance(stmt, ast.RunAgentsParallelStmt):
|
|
113
|
+
return RunAgentsParallelStmt(
|
|
114
|
+
entries=[
|
|
115
|
+
ParallelAgentEntry(agent_name=e.agent_name, input_expr=_lower_expression(e.input_expr), line=e.line, column=e.column)
|
|
116
|
+
for e in stmt.entries
|
|
117
|
+
],
|
|
118
|
+
target=stmt.target,
|
|
119
|
+
line=stmt.line,
|
|
120
|
+
column=stmt.column,
|
|
121
|
+
)
|
|
122
|
+
raise TypeError(f"Unhandled statement type: {type(stmt)}")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
from namel3ss.ast import nodes as ast
|
|
6
|
+
from namel3ss.errors.base import Namel3ssError
|
|
7
|
+
from namel3ss.ir.model.tools import ToolDecl
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _lower_tools(tools: List[ast.ToolDecl]) -> Dict[str, ToolDecl]:
|
|
11
|
+
tool_map: Dict[str, ToolDecl] = {}
|
|
12
|
+
for tool in tools:
|
|
13
|
+
if tool.name in tool_map:
|
|
14
|
+
raise Namel3ssError(f"Duplicate tool declaration '{tool.name}'", line=tool.line, column=tool.column)
|
|
15
|
+
tool_map[tool.name] = ToolDecl(name=tool.name, kind=tool.kind, line=tool.line, column=tool.column)
|
|
16
|
+
return tool_map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from namel3ss.ir.model.base import Expression, Node, Statement
|
|
2
|
+
from namel3ss.ir.model.program import Flow, Program
|
|
3
|
+
from namel3ss.ir.model.statements import ForEach, If, Let, Match, MatchCase, Repeat, Return, Save, Set, TryCatch, Find
|
|
4
|
+
from namel3ss.ir.model.expressions import AttrAccess, BinaryOp, Comparison, Literal, StatePath, UnaryOp, VarReference, Assignable
|
|
5
|
+
from namel3ss.ir.model.ai import AIDecl, AIMemory, AskAIStmt
|
|
6
|
+
from namel3ss.ir.model.agents import AgentDecl, ParallelAgentEntry, RunAgentStmt, RunAgentsParallelStmt
|
|
7
|
+
from namel3ss.ir.model.pages import ButtonItem, FormItem, Page, PageItem, TableItem, TextItem, TitleItem
|
|
8
|
+
from namel3ss.ir.model.tools import ToolDecl
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Expression",
|
|
12
|
+
"Node",
|
|
13
|
+
"Statement",
|
|
14
|
+
"Flow",
|
|
15
|
+
"Program",
|
|
16
|
+
"ForEach",
|
|
17
|
+
"If",
|
|
18
|
+
"Let",
|
|
19
|
+
"Match",
|
|
20
|
+
"MatchCase",
|
|
21
|
+
"Repeat",
|
|
22
|
+
"Return",
|
|
23
|
+
"Save",
|
|
24
|
+
"Set",
|
|
25
|
+
"TryCatch",
|
|
26
|
+
"Find",
|
|
27
|
+
"AttrAccess",
|
|
28
|
+
"BinaryOp",
|
|
29
|
+
"Comparison",
|
|
30
|
+
"Literal",
|
|
31
|
+
"StatePath",
|
|
32
|
+
"UnaryOp",
|
|
33
|
+
"VarReference",
|
|
34
|
+
"Assignable",
|
|
35
|
+
"AIDecl",
|
|
36
|
+
"AIMemory",
|
|
37
|
+
"AskAIStmt",
|
|
38
|
+
"AgentDecl",
|
|
39
|
+
"ParallelAgentEntry",
|
|
40
|
+
"RunAgentStmt",
|
|
41
|
+
"RunAgentsParallelStmt",
|
|
42
|
+
"ButtonItem",
|
|
43
|
+
"FormItem",
|
|
44
|
+
"Page",
|
|
45
|
+
"PageItem",
|
|
46
|
+
"TableItem",
|
|
47
|
+
"TextItem",
|
|
48
|
+
"TitleItem",
|
|
49
|
+
"ToolDecl",
|
|
50
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from namel3ss.ir.model.base import Node, Statement
|
|
7
|
+
from namel3ss.ir.model.expressions import Expression
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AgentDecl(Node):
|
|
12
|
+
name: str
|
|
13
|
+
ai_name: str
|
|
14
|
+
system_prompt: Optional[str]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ParallelAgentEntry(Node):
|
|
19
|
+
agent_name: str
|
|
20
|
+
input_expr: Expression
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class RunAgentStmt(Statement):
|
|
25
|
+
agent_name: str
|
|
26
|
+
input_expr: Expression
|
|
27
|
+
target: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class RunAgentsParallelStmt(Statement):
|
|
32
|
+
entries: List[ParallelAgentEntry]
|
|
33
|
+
target: str
|
namel3ss/ir/model/ai.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from namel3ss.ir.model.base import Node, Statement
|
|
7
|
+
from namel3ss.ir.model.expressions import Expression
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class AIMemory(Node):
|
|
12
|
+
short_term: int = 0
|
|
13
|
+
semantic: bool = False
|
|
14
|
+
profile: bool = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class AIDecl(Node):
|
|
19
|
+
name: str
|
|
20
|
+
model: str
|
|
21
|
+
provider: str
|
|
22
|
+
system_prompt: Optional[str]
|
|
23
|
+
exposed_tools: List[str]
|
|
24
|
+
memory: AIMemory
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class AskAIStmt(Statement):
|
|
29
|
+
ai_name: str
|
|
30
|
+
input_expr: Expression
|
|
31
|
+
target: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Node:
|
|
9
|
+
line: Optional[int]
|
|
10
|
+
column: Optional[int]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Statement(Node):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Expression(Node):
|
|
20
|
+
pass
|