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.
Files changed (157) hide show
  1. namel3ss/__init__.py +4 -0
  2. namel3ss/ast/__init__.py +5 -0
  3. namel3ss/ast/agents.py +13 -0
  4. namel3ss/ast/ai.py +23 -0
  5. namel3ss/ast/base.py +10 -0
  6. namel3ss/ast/expressions.py +55 -0
  7. namel3ss/ast/nodes.py +86 -0
  8. namel3ss/ast/pages.py +43 -0
  9. namel3ss/ast/program.py +22 -0
  10. namel3ss/ast/records.py +27 -0
  11. namel3ss/ast/statements.py +107 -0
  12. namel3ss/ast/tool.py +11 -0
  13. namel3ss/cli/__init__.py +2 -0
  14. namel3ss/cli/actions_mode.py +39 -0
  15. namel3ss/cli/app_loader.py +22 -0
  16. namel3ss/cli/commands/action.py +27 -0
  17. namel3ss/cli/commands/run.py +43 -0
  18. namel3ss/cli/commands/ui.py +26 -0
  19. namel3ss/cli/commands/validate.py +23 -0
  20. namel3ss/cli/format_mode.py +30 -0
  21. namel3ss/cli/io/json_io.py +19 -0
  22. namel3ss/cli/io/read_source.py +16 -0
  23. namel3ss/cli/json_io.py +21 -0
  24. namel3ss/cli/lint_mode.py +29 -0
  25. namel3ss/cli/main.py +135 -0
  26. namel3ss/cli/new_mode.py +146 -0
  27. namel3ss/cli/runner.py +28 -0
  28. namel3ss/cli/studio_mode.py +22 -0
  29. namel3ss/cli/ui_mode.py +14 -0
  30. namel3ss/config/__init__.py +4 -0
  31. namel3ss/config/dotenv.py +33 -0
  32. namel3ss/config/loader.py +83 -0
  33. namel3ss/config/model.py +49 -0
  34. namel3ss/errors/__init__.py +2 -0
  35. namel3ss/errors/base.py +34 -0
  36. namel3ss/errors/render.py +22 -0
  37. namel3ss/format/__init__.py +3 -0
  38. namel3ss/format/formatter.py +18 -0
  39. namel3ss/format/rules.py +97 -0
  40. namel3ss/ir/__init__.py +3 -0
  41. namel3ss/ir/lowering/__init__.py +4 -0
  42. namel3ss/ir/lowering/agents.py +42 -0
  43. namel3ss/ir/lowering/ai.py +45 -0
  44. namel3ss/ir/lowering/expressions.py +49 -0
  45. namel3ss/ir/lowering/flow.py +21 -0
  46. namel3ss/ir/lowering/pages.py +48 -0
  47. namel3ss/ir/lowering/program.py +34 -0
  48. namel3ss/ir/lowering/records.py +25 -0
  49. namel3ss/ir/lowering/statements.py +122 -0
  50. namel3ss/ir/lowering/tools.py +16 -0
  51. namel3ss/ir/model/__init__.py +50 -0
  52. namel3ss/ir/model/agents.py +33 -0
  53. namel3ss/ir/model/ai.py +31 -0
  54. namel3ss/ir/model/base.py +20 -0
  55. namel3ss/ir/model/expressions.py +50 -0
  56. namel3ss/ir/model/pages.py +43 -0
  57. namel3ss/ir/model/program.py +28 -0
  58. namel3ss/ir/model/statements.py +76 -0
  59. namel3ss/ir/model/tools.py +11 -0
  60. namel3ss/ir/nodes.py +88 -0
  61. namel3ss/lexer/__init__.py +2 -0
  62. namel3ss/lexer/lexer.py +152 -0
  63. namel3ss/lexer/tokens.py +98 -0
  64. namel3ss/lint/__init__.py +4 -0
  65. namel3ss/lint/engine.py +125 -0
  66. namel3ss/lint/semantic.py +45 -0
  67. namel3ss/lint/text_scan.py +70 -0
  68. namel3ss/lint/types.py +22 -0
  69. namel3ss/parser/__init__.py +3 -0
  70. namel3ss/parser/agent.py +78 -0
  71. namel3ss/parser/ai.py +113 -0
  72. namel3ss/parser/constraints.py +37 -0
  73. namel3ss/parser/core.py +166 -0
  74. namel3ss/parser/expressions.py +105 -0
  75. namel3ss/parser/flow.py +37 -0
  76. namel3ss/parser/pages.py +76 -0
  77. namel3ss/parser/program.py +45 -0
  78. namel3ss/parser/records.py +66 -0
  79. namel3ss/parser/statements/__init__.py +27 -0
  80. namel3ss/parser/statements/control_flow.py +116 -0
  81. namel3ss/parser/statements/core.py +66 -0
  82. namel3ss/parser/statements/data.py +17 -0
  83. namel3ss/parser/statements/letset.py +22 -0
  84. namel3ss/parser/statements.py +1 -0
  85. namel3ss/parser/tokens.py +35 -0
  86. namel3ss/parser/tool.py +29 -0
  87. namel3ss/runtime/__init__.py +3 -0
  88. namel3ss/runtime/ai/http/client.py +24 -0
  89. namel3ss/runtime/ai/mock_provider.py +5 -0
  90. namel3ss/runtime/ai/provider.py +29 -0
  91. namel3ss/runtime/ai/providers/__init__.py +18 -0
  92. namel3ss/runtime/ai/providers/_shared/errors.py +20 -0
  93. namel3ss/runtime/ai/providers/_shared/parse.py +18 -0
  94. namel3ss/runtime/ai/providers/anthropic.py +55 -0
  95. namel3ss/runtime/ai/providers/gemini.py +50 -0
  96. namel3ss/runtime/ai/providers/mistral.py +51 -0
  97. namel3ss/runtime/ai/providers/mock.py +23 -0
  98. namel3ss/runtime/ai/providers/ollama.py +39 -0
  99. namel3ss/runtime/ai/providers/openai.py +55 -0
  100. namel3ss/runtime/ai/providers/registry.py +38 -0
  101. namel3ss/runtime/ai/trace.py +18 -0
  102. namel3ss/runtime/executor/__init__.py +3 -0
  103. namel3ss/runtime/executor/agents.py +91 -0
  104. namel3ss/runtime/executor/ai_runner.py +90 -0
  105. namel3ss/runtime/executor/api.py +54 -0
  106. namel3ss/runtime/executor/assign.py +40 -0
  107. namel3ss/runtime/executor/context.py +31 -0
  108. namel3ss/runtime/executor/executor.py +77 -0
  109. namel3ss/runtime/executor/expr_eval.py +110 -0
  110. namel3ss/runtime/executor/records_ops.py +64 -0
  111. namel3ss/runtime/executor/result.py +13 -0
  112. namel3ss/runtime/executor/signals.py +6 -0
  113. namel3ss/runtime/executor/statements.py +99 -0
  114. namel3ss/runtime/memory/manager.py +52 -0
  115. namel3ss/runtime/memory/profile.py +17 -0
  116. namel3ss/runtime/memory/semantic.py +20 -0
  117. namel3ss/runtime/memory/short_term.py +18 -0
  118. namel3ss/runtime/records/service.py +105 -0
  119. namel3ss/runtime/store/__init__.py +2 -0
  120. namel3ss/runtime/store/memory_store.py +62 -0
  121. namel3ss/runtime/tools/registry.py +13 -0
  122. namel3ss/runtime/ui/__init__.py +2 -0
  123. namel3ss/runtime/ui/actions.py +124 -0
  124. namel3ss/runtime/validators/__init__.py +2 -0
  125. namel3ss/runtime/validators/constraints.py +126 -0
  126. namel3ss/schema/__init__.py +2 -0
  127. namel3ss/schema/records.py +52 -0
  128. namel3ss/studio/__init__.py +4 -0
  129. namel3ss/studio/api.py +115 -0
  130. namel3ss/studio/edit/__init__.py +3 -0
  131. namel3ss/studio/edit/ops.py +80 -0
  132. namel3ss/studio/edit/selectors.py +74 -0
  133. namel3ss/studio/edit/transform.py +39 -0
  134. namel3ss/studio/server.py +175 -0
  135. namel3ss/studio/session.py +11 -0
  136. namel3ss/studio/web/app.js +248 -0
  137. namel3ss/studio/web/index.html +44 -0
  138. namel3ss/studio/web/styles.css +42 -0
  139. namel3ss/templates/__init__.py +3 -0
  140. namel3ss/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  141. namel3ss/templates/ai_assistant/.gitignore +1 -0
  142. namel3ss/templates/ai_assistant/README.md +10 -0
  143. namel3ss/templates/ai_assistant/app.ai +30 -0
  144. namel3ss/templates/crud/.gitignore +1 -0
  145. namel3ss/templates/crud/README.md +10 -0
  146. namel3ss/templates/crud/app.ai +26 -0
  147. namel3ss/templates/multi_agent/.gitignore +1 -0
  148. namel3ss/templates/multi_agent/README.md +10 -0
  149. namel3ss/templates/multi_agent/app.ai +43 -0
  150. namel3ss/ui/__init__.py +2 -0
  151. namel3ss/ui/manifest.py +220 -0
  152. namel3ss/utils/__init__.py +2 -0
  153. namel3ss-0.1.0a0.dist-info/METADATA +123 -0
  154. namel3ss-0.1.0a0.dist-info/RECORD +157 -0
  155. namel3ss-0.1.0a0.dist-info/WHEEL +5 -0
  156. namel3ss-0.1.0a0.dist-info/entry_points.txt +2 -0
  157. namel3ss-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Set
4
+
5
+ from namel3ss.ast import nodes as ast
6
+
7
+
8
+ def parse_flow(parser) -> ast.Flow:
9
+ flow_tok = parser._expect("FLOW", "Expected 'flow' declaration")
10
+ name_tok = parser._expect("STRING", "Expected flow name string")
11
+ parser._expect("COLON", "Expected ':' after flow name")
12
+ parser._expect("NEWLINE", "Expected newline after flow header")
13
+ parser._expect("INDENT", "Expected indented block for flow body")
14
+ body = parse_statements(parser, until={"DEDENT"})
15
+ parser._expect("DEDENT", "Expected block end")
16
+ while parser._match("NEWLINE"):
17
+ pass
18
+ return ast.Flow(name=name_tok.value, body=body, line=flow_tok.line, column=flow_tok.column)
19
+
20
+
21
+ def parse_statements(parser, until: Set[str]) -> List[ast.Statement]:
22
+ statements: List[ast.Statement] = []
23
+ while parser._current().type not in until:
24
+ if parser._match("NEWLINE"):
25
+ continue
26
+ statements.append(parser._parse_statement())
27
+ return statements
28
+
29
+
30
+ def parse_block(parser) -> List[ast.Statement]:
31
+ parser._expect("NEWLINE", "Expected newline before block")
32
+ parser._expect("INDENT", "Expected indented block")
33
+ stmts = parse_statements(parser, until={"DEDENT"})
34
+ parser._expect("DEDENT", "Expected end of block")
35
+ while parser._match("NEWLINE"):
36
+ pass
37
+ return stmts
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+
8
+
9
+ def parse_page(parser) -> ast.PageDecl:
10
+ page_tok = parser._advance()
11
+ name_tok = parser._expect("STRING", "Expected page name string")
12
+ parser._expect("COLON", "Expected ':' after page name")
13
+ parser._expect("NEWLINE", "Expected newline after page header")
14
+ parser._expect("INDENT", "Expected indented page body")
15
+ items: List[ast.PageItem] = []
16
+ while parser._current().type != "DEDENT":
17
+ if parser._match("NEWLINE"):
18
+ continue
19
+ items.append(parse_page_item(parser))
20
+ parser._expect("DEDENT", "Expected end of page body")
21
+ return ast.PageDecl(name=name_tok.value, items=items, line=page_tok.line, column=page_tok.column)
22
+
23
+
24
+ def parse_page_item(parser) -> ast.PageItem:
25
+ tok = parser._current()
26
+ if tok.type == "TITLE":
27
+ parser._advance()
28
+ parser._expect("IS", "Expected 'is' after 'title'")
29
+ value_tok = parser._expect("STRING", "Expected title string")
30
+ return ast.TitleItem(value=value_tok.value, line=tok.line, column=tok.column)
31
+ if tok.type == "TEXT":
32
+ parser._advance()
33
+ parser._expect("IS", "Expected 'is' after 'text'")
34
+ value_tok = parser._expect("STRING", "Expected text string")
35
+ return ast.TextItem(value=value_tok.value, line=tok.line, column=tok.column)
36
+ if tok.type == "FORM":
37
+ parser._advance()
38
+ parser._expect("IS", "Expected 'is' after 'form'")
39
+ value_tok = parser._expect("STRING", "Expected form record name")
40
+ return ast.FormItem(record_name=value_tok.value, line=tok.line, column=tok.column)
41
+ if tok.type == "TABLE":
42
+ parser._advance()
43
+ parser._expect("IS", "Expected 'is' after 'table'")
44
+ value_tok = parser._expect("STRING", "Expected table record name")
45
+ return ast.TableItem(record_name=value_tok.value, line=tok.line, column=tok.column)
46
+ if tok.type == "BUTTON":
47
+ parser._advance()
48
+ label_tok = parser._expect("STRING", "Expected button label string")
49
+ if parser._match("CALLS"):
50
+ raise Namel3ssError(
51
+ 'Buttons must use a block. Use: button "Run": NEWLINE indent calls flow "demo"',
52
+ line=tok.line,
53
+ column=tok.column,
54
+ )
55
+ parser._expect("COLON", "Expected ':' after button label")
56
+ parser._expect("NEWLINE", "Expected newline after button header")
57
+ parser._expect("INDENT", "Expected indented button body")
58
+ flow_tok = None
59
+ while parser._current().type != "DEDENT":
60
+ if parser._match("NEWLINE"):
61
+ continue
62
+ parser._expect("CALLS", "Expected 'calls' in button action")
63
+ parser._expect("FLOW", "Expected 'flow' keyword in button action")
64
+ flow_tok = parser._expect("STRING", "Expected flow name string")
65
+ if parser._match("NEWLINE"):
66
+ continue
67
+ break
68
+ parser._expect("DEDENT", "Expected end of button body")
69
+ if flow_tok is None:
70
+ raise Namel3ssError("Button body must include 'calls flow \"<name>\"'", line=tok.line, column=tok.column)
71
+ return ast.ButtonItem(label=label_tok.value, flow_name=flow_tok.value, line=tok.line, column=tok.column)
72
+ raise Namel3ssError(
73
+ f"Pages are declarative; unexpected item '{tok.type.lower()}'",
74
+ line=tok.line,
75
+ column=tok.column,
76
+ )
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+ from namel3ss.parser.agent import parse_agent_decl
8
+ from namel3ss.parser.ai import parse_ai_decl
9
+ from namel3ss.parser.flow import parse_flow
10
+ from namel3ss.parser.pages import parse_page
11
+ from namel3ss.parser.records import parse_record
12
+ from namel3ss.parser.tool import parse_tool
13
+
14
+
15
+ def parse_program(parser) -> ast.Program:
16
+ records: List[ast.RecordDecl] = []
17
+ flows: List[ast.Flow] = []
18
+ pages: List[ast.PageDecl] = []
19
+ ais: List[ast.AIDecl] = []
20
+ tools: List[ast.ToolDecl] = []
21
+ agents: List[ast.AgentDecl] = []
22
+ while parser._current().type != "EOF":
23
+ if parser._match("NEWLINE"):
24
+ continue
25
+ if parser._current().type == "TOOL":
26
+ tools.append(parse_tool(parser))
27
+ continue
28
+ if parser._current().type == "AGENT":
29
+ agents.append(parse_agent_decl(parser))
30
+ continue
31
+ if parser._current().type == "AI":
32
+ ais.append(parse_ai_decl(parser))
33
+ continue
34
+ if parser._current().type == "RECORD":
35
+ records.append(parse_record(parser))
36
+ continue
37
+ if parser._current().type == "FLOW":
38
+ flows.append(parse_flow(parser))
39
+ continue
40
+ if parser._current().type == "PAGE":
41
+ pages.append(parse_page(parser))
42
+ continue
43
+ tok = parser._current()
44
+ raise Namel3ssError("Unexpected top-level token", line=tok.line, column=tok.column)
45
+ return ast.Program(records=records, flows=flows, pages=pages, ais=ais, tools=tools, agents=agents, line=None, column=None)
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+ from namel3ss.lexer.tokens import Token
8
+ from namel3ss.parser.constraints import parse_field_constraint
9
+
10
+
11
+ def parse_record(parser) -> ast.RecordDecl:
12
+ rec_tok = parser._advance()
13
+ name_tok = parser._expect("STRING", "Expected record name string")
14
+ parser._expect("COLON", "Expected ':' after record name")
15
+ fields = parse_record_fields(parser)
16
+ return ast.RecordDecl(name=name_tok.value, fields=fields, line=rec_tok.line, column=rec_tok.column)
17
+
18
+
19
+ def parse_record_fields(parser) -> List[ast.FieldDecl]:
20
+ parser._expect("NEWLINE", "Expected newline after record header")
21
+ parser._expect("INDENT", "Expected indented record body")
22
+ fields: List[ast.FieldDecl] = []
23
+ while parser._current().type != "DEDENT":
24
+ if parser._match("NEWLINE"):
25
+ continue
26
+ name_tok = parser._current()
27
+ if name_tok.type not in {"IDENT", "TITLE", "TEXT", "FORM", "TABLE", "BUTTON", "PAGE"}:
28
+ raise Namel3ssError("Expected field name", line=name_tok.line, column=name_tok.column)
29
+ parser._advance()
30
+ type_tok = parser._current()
31
+ if not type_tok.type.startswith("TYPE_"):
32
+ raise Namel3ssError("Expected field type", line=type_tok.line, column=type_tok.column)
33
+ parser._advance()
34
+ type_name = type_from_token(type_tok)
35
+ constraint = None
36
+ if parser._match("MUST"):
37
+ constraint = parse_field_constraint(parser)
38
+ fields.append(
39
+ ast.FieldDecl(
40
+ name=name_tok.value,
41
+ type_name=type_name,
42
+ constraint=constraint,
43
+ line=name_tok.line,
44
+ column=name_tok.column,
45
+ )
46
+ )
47
+ if parser._match("NEWLINE"):
48
+ continue
49
+ parser._expect("DEDENT", "Expected end of record body")
50
+ while parser._match("NEWLINE"):
51
+ pass
52
+ return fields
53
+
54
+
55
+ def type_from_token(tok: Token) -> str:
56
+ if tok.type == "TYPE_STRING":
57
+ return "string"
58
+ if tok.type == "TYPE_INT":
59
+ return "int"
60
+ if tok.type == "TYPE_NUMBER":
61
+ return "number"
62
+ if tok.type == "TYPE_BOOLEAN":
63
+ return "boolean"
64
+ if tok.type == "TYPE_JSON":
65
+ return "json"
66
+ raise Namel3ssError("Invalid type", line=tok.line, column=tok.column)
@@ -0,0 +1,27 @@
1
+ from namel3ss.parser.statements.core import parse_statement, parse_target, validate_match_pattern
2
+ from namel3ss.parser.statements.control_flow import (
3
+ parse_for_each,
4
+ parse_if,
5
+ parse_match,
6
+ parse_repeat,
7
+ parse_return,
8
+ parse_try,
9
+ )
10
+ from namel3ss.parser.statements.data import parse_find, parse_save
11
+ from namel3ss.parser.statements.letset import parse_let, parse_set
12
+
13
+ __all__ = [
14
+ "parse_statement",
15
+ "parse_let",
16
+ "parse_set",
17
+ "parse_if",
18
+ "parse_return",
19
+ "parse_repeat",
20
+ "parse_for_each",
21
+ "parse_match",
22
+ "parse_try",
23
+ "parse_save",
24
+ "parse_find",
25
+ "parse_target",
26
+ "validate_match_pattern",
27
+ ]
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List
4
+
5
+ from namel3ss.ast import nodes as ast
6
+ from namel3ss.errors.base import Namel3ssError
7
+
8
+
9
+ def parse_if(parser) -> ast.If:
10
+ if_tok = parser._advance()
11
+ condition = parser._parse_expression()
12
+ parser._expect("COLON", "Expected ':' after condition")
13
+ parser._expect("NEWLINE", "Expected newline after condition")
14
+ parser._expect("INDENT", "Expected indented block for if body")
15
+ then_body = parser._parse_statements(until={"DEDENT"})
16
+ parser._expect("DEDENT", "Expected block end")
17
+ else_body: List[ast.Statement] = []
18
+ while parser._match("NEWLINE"):
19
+ pass
20
+ if parser._match("ELSE"):
21
+ parser._expect("COLON", "Expected ':' after else")
22
+ parser._expect("NEWLINE", "Expected newline after else")
23
+ parser._expect("INDENT", "Expected indented block for else body")
24
+ else_body = parser._parse_statements(until={"DEDENT"})
25
+ parser._expect("DEDENT", "Expected block end")
26
+ while parser._match("NEWLINE"):
27
+ pass
28
+ return ast.If(
29
+ condition=condition,
30
+ then_body=then_body,
31
+ else_body=else_body,
32
+ line=if_tok.line,
33
+ column=if_tok.column,
34
+ )
35
+
36
+
37
+ def parse_return(parser) -> ast.Return:
38
+ ret_tok = parser._advance()
39
+ expr = parser._parse_expression()
40
+ return ast.Return(expression=expr, line=ret_tok.line, column=ret_tok.column)
41
+
42
+
43
+ def parse_repeat(parser) -> ast.Repeat:
44
+ rep_tok = parser._advance()
45
+ parser._expect("UP", "Expected 'up' in repeat statement")
46
+ parser._expect("TO", "Expected 'to' in repeat statement")
47
+ count_expr = parser._parse_expression()
48
+ parser._expect("TIMES", "Expected 'times' after repeat count")
49
+ parser._expect("COLON", "Expected ':' after repeat header")
50
+ body = parser._parse_block()
51
+ return ast.Repeat(count=count_expr, body=body, line=rep_tok.line, column=rep_tok.column)
52
+
53
+
54
+ def parse_for_each(parser) -> ast.ForEach:
55
+ for_tok = parser._advance()
56
+ parser._expect("EACH", "Expected 'each' after 'for'")
57
+ name_tok = parser._expect("IDENT", "Expected loop variable name")
58
+ parser._expect("IN", "Expected 'in' in for-each loop")
59
+ iterable = parser._parse_expression()
60
+ parser._expect("COLON", "Expected ':' after for-each header")
61
+ body = parser._parse_block()
62
+ return ast.ForEach(name=name_tok.value, iterable=iterable, body=body, line=for_tok.line, column=for_tok.column)
63
+
64
+
65
+ def parse_match(parser) -> ast.Match:
66
+ match_tok = parser._advance()
67
+ expr = parser._parse_expression()
68
+ parser._expect("COLON", "Expected ':' after match expression")
69
+ parser._expect("NEWLINE", "Expected newline after match header")
70
+ parser._expect("INDENT", "Expected indented match body")
71
+ parser._expect("WITH", "Expected 'with' inside match")
72
+ parser._expect("COLON", "Expected ':' after 'with'")
73
+ parser._expect("NEWLINE", "Expected newline after 'with:'")
74
+ parser._expect("INDENT", "Expected indented match cases")
75
+ cases: List[ast.MatchCase] = []
76
+ otherwise_body: List[ast.Statement] | None = None
77
+ while parser._current().type not in {"DEDENT"}:
78
+ if parser._match("WHEN"):
79
+ pattern_expr = parser._parse_expression()
80
+ parser._validate_match_pattern(pattern_expr)
81
+ parser._expect("COLON", "Expected ':' after when pattern")
82
+ case_body = parser._parse_block()
83
+ if otherwise_body is not None:
84
+ raise Namel3ssError("Unreachable case after otherwise", line=pattern_expr.line, column=pattern_expr.column)
85
+ cases.append(ast.MatchCase(pattern=pattern_expr, body=case_body, line=pattern_expr.line, column=pattern_expr.column))
86
+ continue
87
+ if parser._match("OTHERWISE"):
88
+ if otherwise_body is not None:
89
+ tok = parser.tokens[parser.position - 1]
90
+ raise Namel3ssError("Duplicate otherwise in match", line=tok.line, column=tok.column)
91
+ parser._expect("COLON", "Expected ':' after otherwise")
92
+ otherwise_body = parser._parse_block()
93
+ continue
94
+ tok = parser._current()
95
+ raise Namel3ssError("Expected 'when' or 'otherwise' in match", line=tok.line, column=tok.column)
96
+ parser._expect("DEDENT", "Expected end of match cases")
97
+ parser._expect("DEDENT", "Expected end of match block")
98
+ while parser._match("NEWLINE"):
99
+ pass
100
+ if not cases and otherwise_body is None:
101
+ raise Namel3ssError("Match must have at least one case", line=match_tok.line, column=match_tok.column)
102
+ return ast.Match(expression=expr, cases=cases, otherwise=otherwise_body, line=match_tok.line, column=match_tok.column)
103
+
104
+
105
+ def parse_try(parser) -> ast.TryCatch:
106
+ try_tok = parser._advance()
107
+ parser._expect("COLON", "Expected ':' after try")
108
+ try_body = parser._parse_block()
109
+ if not parser._match("WITH"):
110
+ tok = parser._current()
111
+ raise Namel3ssError("Expected 'with' introducing catch", line=tok.line, column=tok.column)
112
+ parser._expect("CATCH", "Expected 'catch' after 'with'")
113
+ var_tok = parser._expect("IDENT", "Expected catch variable name")
114
+ parser._expect("COLON", "Expected ':' after catch clause")
115
+ catch_body = parser._parse_block()
116
+ return ast.TryCatch(try_body=try_body, catch_var=var_tok.value, catch_body=catch_body, line=try_tok.line, column=try_tok.column)
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.errors.base import Namel3ssError
5
+ from namel3ss.parser.agent import parse_run_agent_stmt, parse_run_agents_parallel
6
+ from namel3ss.parser.ai import parse_ask_stmt
7
+ from namel3ss.parser.statements.control_flow import (
8
+ parse_for_each,
9
+ parse_if,
10
+ parse_match,
11
+ parse_repeat,
12
+ parse_return,
13
+ parse_try,
14
+ )
15
+ from namel3ss.parser.statements.data import parse_find, parse_save
16
+ from namel3ss.parser.statements.letset import parse_let, parse_set
17
+
18
+
19
+ def parse_statement(parser) -> ast.Statement:
20
+ tok = parser._current()
21
+ if tok.type == "LET":
22
+ return parse_let(parser)
23
+ if tok.type == "SET":
24
+ return parse_set(parser)
25
+ if tok.type == "IF":
26
+ return parse_if(parser)
27
+ if tok.type == "RETURN":
28
+ return parse_return(parser)
29
+ if tok.type == "ASK":
30
+ return parse_ask_stmt(parser)
31
+ if tok.type == "RUN":
32
+ next_type = parser.tokens[parser.position + 1].type
33
+ if next_type == "AGENT":
34
+ return parse_run_agent_stmt(parser)
35
+ if next_type == "AGENTS":
36
+ return parse_run_agents_parallel(parser)
37
+ raise Namel3ssError("Expected 'agent' or 'agents' after run", line=tok.line, column=tok.column)
38
+ if tok.type == "REPEAT":
39
+ return parse_repeat(parser)
40
+ if tok.type == "FOR":
41
+ return parse_for_each(parser)
42
+ if tok.type == "MATCH":
43
+ return parse_match(parser)
44
+ if tok.type == "TRY":
45
+ return parse_try(parser)
46
+ if tok.type == "SAVE":
47
+ return parse_save(parser)
48
+ if tok.type == "FIND":
49
+ return parse_find(parser)
50
+ raise Namel3ssError(f"Unexpected token '{tok.type}' in statement", line=tok.line, column=tok.column)
51
+
52
+
53
+ def parse_target(parser) -> ast.Assignable:
54
+ tok = parser._current()
55
+ if tok.type == "STATE":
56
+ return parser._parse_state_path()
57
+ if tok.type == "IDENT":
58
+ name_tok = parser._advance()
59
+ return ast.VarReference(name=name_tok.value, line=name_tok.line, column=name_tok.column)
60
+ raise Namel3ssError("Expected assignment target", line=tok.line, column=tok.column)
61
+
62
+
63
+ def validate_match_pattern(parser, pattern: ast.Expression) -> None:
64
+ if isinstance(pattern, (ast.Literal, ast.VarReference, ast.StatePath)):
65
+ return
66
+ raise Namel3ssError("Match patterns must be literal or identifier", line=pattern.line, column=pattern.column)
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+
5
+
6
+ def parse_save(parser) -> ast.Save:
7
+ tok = parser._advance()
8
+ name_tok = parser._expect("IDENT", "Expected record name after 'save'")
9
+ return ast.Save(record_name=name_tok.value, line=tok.line, column=tok.column)
10
+
11
+
12
+ def parse_find(parser) -> ast.Find:
13
+ tok = parser._advance()
14
+ name_tok = parser._expect("IDENT", "Expected record name after 'find'")
15
+ parser._expect("WHERE", "Expected 'where' in find statement")
16
+ predicate = parser._parse_expression()
17
+ return ast.Find(record_name=name_tok.value, predicate=predicate, line=tok.line, column=tok.column)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+
5
+
6
+ def parse_let(parser) -> ast.Let:
7
+ let_tok = parser._advance()
8
+ name_tok = parser._expect("IDENT", "Expected identifier after 'let'")
9
+ parser._expect("IS", "Expected 'is' in declaration")
10
+ expr = parser._parse_expression()
11
+ constant = False
12
+ if parser._match("CONSTANT"):
13
+ constant = True
14
+ return ast.Let(name=name_tok.value, expression=expr, constant=constant, line=let_tok.line, column=let_tok.column)
15
+
16
+
17
+ def parse_set(parser) -> ast.Set:
18
+ set_tok = parser._advance()
19
+ target = parser._parse_target()
20
+ parser._expect("IS", "Expected 'is' in assignment")
21
+ expr = parser._parse_expression()
22
+ return ast.Set(target=target, expression=expr, line=set_tok.line, column=set_tok.column)
@@ -0,0 +1 @@
1
+ from namel3ss.parser.statements import * # noqa: F401,F403
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ from namel3ss.errors.base import Namel3ssError
6
+ from namel3ss.lexer.tokens import Token
7
+
8
+
9
+ def current(parser) -> Token:
10
+ return parser.tokens[parser.position]
11
+
12
+
13
+ def advance(parser) -> Token:
14
+ tok = parser.tokens[parser.position]
15
+ parser.position += 1
16
+ return tok
17
+
18
+
19
+ def match(parser, *types: str) -> bool:
20
+ if current(parser).type in types:
21
+ advance(parser)
22
+ return True
23
+ return False
24
+
25
+
26
+ def expect(parser, token_type: str, message: Optional[str] = None) -> Token:
27
+ tok = current(parser)
28
+ if tok.type != token_type:
29
+ raise Namel3ssError(
30
+ message or f"Expected {token_type}, got {tok.type}",
31
+ line=tok.line,
32
+ column=tok.column,
33
+ )
34
+ advance(parser)
35
+ return tok
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.ast import nodes as ast
4
+ from namel3ss.errors.base import Namel3ssError
5
+
6
+
7
+ def parse_tool(parser) -> ast.ToolDecl:
8
+ tool_tok = parser._advance()
9
+ name_tok = parser._expect("STRING", "Expected tool name string")
10
+ parser._expect("COLON", "Expected ':' after tool name")
11
+ parser._expect("NEWLINE", "Expected newline after tool header")
12
+ parser._expect("INDENT", "Expected indented tool body")
13
+ kind = None
14
+ while parser._current().type != "DEDENT":
15
+ if parser._match("NEWLINE"):
16
+ continue
17
+ key_tok = parser._current()
18
+ if key_tok.type == "KIND":
19
+ parser._advance()
20
+ parser._expect("IS", "Expected 'is' after kind")
21
+ kind_tok = parser._expect("STRING", "Expected kind string")
22
+ kind = kind_tok.value
23
+ else:
24
+ raise Namel3ssError("Unknown field in tool declaration", line=key_tok.line, column=key_tok.column)
25
+ parser._match("NEWLINE")
26
+ parser._expect("DEDENT", "Expected end of tool body")
27
+ if kind is None:
28
+ raise Namel3ssError("Tool declaration requires a kind", line=tool_tok.line, column=tool_tok.column)
29
+ return ast.ToolDecl(name=name_tok.value, kind=kind, line=tool_tok.line, column=tool_tok.column)
@@ -0,0 +1,3 @@
1
+ """Runtime components for executing Namel3ss programs."""
2
+
3
+ from namel3ss.runtime.executor import Executor, ExecutionResult, execute_flow, execute_program_flow # noqa: F401
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from urllib.error import HTTPError, URLError
5
+ from urllib.request import Request, urlopen
6
+
7
+ from namel3ss.errors.base import Namel3ssError
8
+ from namel3ss.runtime.ai.providers._shared.errors import map_http_error
9
+ from namel3ss.runtime.ai.providers._shared.parse import json_loads_or_error
10
+
11
+
12
+ def post_json(*, url: str, headers: dict[str, str], payload: dict, timeout_seconds: int, provider_name: str) -> dict:
13
+ data = json.dumps(payload).encode("utf-8")
14
+ request = Request(url, data=data, headers=headers)
15
+ try:
16
+ with urlopen(request, timeout=timeout_seconds) as response:
17
+ body = response.read()
18
+ except (HTTPError, URLError, TimeoutError) as err:
19
+ raise map_http_error(provider_name, err) from err
20
+ except Exception as err: # pragma: no cover - unexpected transport errors
21
+ if isinstance(err, Namel3ssError):
22
+ raise
23
+ raise map_http_error(provider_name, err) from err
24
+ return json_loads_or_error(provider_name, body)
@@ -0,0 +1,5 @@
1
+ from __future__ import annotations
2
+
3
+ from namel3ss.runtime.ai.providers.mock import MockProvider
4
+
5
+ __all__ = ["MockProvider"]
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Dict, List, Optional
5
+
6
+
7
+ @dataclass
8
+ class AIResponse:
9
+ output: str
10
+
11
+
12
+ class AIProvider:
13
+ def ask(
14
+ self,
15
+ *,
16
+ model: str,
17
+ system_prompt: Optional[str],
18
+ user_input: str,
19
+ tools: Optional[List[Dict[str, object]]] = None,
20
+ memory: Optional[Dict[str, object]] = None,
21
+ tool_results: Optional[List[Dict[str, object]]] = None,
22
+ ) -> AIResponse:
23
+ raise NotImplementedError
24
+
25
+
26
+ @dataclass
27
+ class AIToolCallResponse:
28
+ tool_name: str
29
+ args: Dict[str, object]
@@ -0,0 +1,18 @@
1
+ from namel3ss.runtime.ai.providers.mock import MockProvider
2
+ from namel3ss.runtime.ai.providers.ollama import OllamaProvider
3
+ from namel3ss.runtime.ai.providers.openai import OpenAIProvider
4
+ from namel3ss.runtime.ai.providers.anthropic import AnthropicProvider
5
+ from namel3ss.runtime.ai.providers.gemini import GeminiProvider
6
+ from namel3ss.runtime.ai.providers.mistral import MistralProvider
7
+ from namel3ss.runtime.ai.providers.registry import get_provider, is_supported_provider
8
+
9
+ __all__ = [
10
+ "MockProvider",
11
+ "OllamaProvider",
12
+ "OpenAIProvider",
13
+ "AnthropicProvider",
14
+ "GeminiProvider",
15
+ "MistralProvider",
16
+ "get_provider",
17
+ "is_supported_provider",
18
+ ]
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ from urllib.error import HTTPError, URLError
4
+
5
+ from namel3ss.errors.base import Namel3ssError
6
+
7
+
8
+ def require_env(provider_name: str, env_var: str, value: str | None) -> str:
9
+ if value is None or str(value).strip() == "":
10
+ short_var = env_var.replace("NAMEL3SS_", "")
11
+ raise Namel3ssError(f"Missing {short_var} (set it in .env or export it)")
12
+ return value
13
+
14
+
15
+ def map_http_error(provider_name: str, err: HTTPError | URLError | TimeoutError | Exception) -> Namel3ssError:
16
+ if isinstance(err, HTTPError) and err.code in {401, 403}:
17
+ return Namel3ssError(f"Provider '{provider_name}' authentication failed")
18
+ if isinstance(err, (URLError, TimeoutError)):
19
+ return Namel3ssError(f"Provider '{provider_name}' unreachable")
20
+ return Namel3ssError(f"Provider '{provider_name}' returned an invalid response")