spl-lang 0.6.0 → 1.0.0

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.
@@ -1,4 +1,26 @@
1
+ import { type StatusCode } from './errors.js';
1
2
  import { type LlmBackend, type LlmRequest, type LlmResponse } from './llm.js';
3
+ interface TraceRequest {
4
+ kind: LlmRequest['kind'];
5
+ prompt: string;
6
+ model: string | null;
7
+ reasoningEffort: LlmRequest['reasoningEffort'] | null;
8
+ showReasoning: boolean;
9
+ naturalLanguage?: boolean;
10
+ }
11
+ type Outcome = {
12
+ response: LlmResponse;
13
+ } | {
14
+ error: {
15
+ status_code: StatusCode;
16
+ reason: string;
17
+ };
18
+ };
19
+ export declare function traceRequest(req: LlmRequest | TraceRequest): TraceRequest;
20
+ export declare function isResponse(value: unknown): value is LlmResponse;
21
+ export declare function isTraceError(value: unknown): value is Extract<Outcome, {
22
+ error: unknown;
23
+ }>['error'];
2
24
  export declare class RecordingBackend implements LlmBackend {
3
25
  private readonly backend;
4
26
  private readonly file;
@@ -23,3 +45,4 @@ export declare class ReplayBackend implements LlmBackend {
23
45
  call(req: LlmRequest): Promise<LlmResponse>;
24
46
  close(): Promise<void>;
25
47
  }
48
+ export {};
package/dist/recording.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import * as fs from 'node:fs';
2
2
  import { Failure } from './errors.js';
3
3
  import { LlmError } from './llm.js';
4
- function traceRequest(req) {
4
+ export function traceRequest(req) {
5
5
  return {
6
6
  kind: req.kind, prompt: req.prompt, model: req.model ?? null,
7
7
  reasoningEffort: req.reasoningEffort ?? null, showReasoning: req.showReasoning,
8
+ ...(req.naturalLanguage ? { naturalLanguage: true } : {}),
8
9
  };
9
10
  }
10
11
  function requestKey(req) {
@@ -13,17 +14,19 @@ function requestKey(req) {
13
14
  function isRecord(value) {
14
15
  return typeof value === 'object' && value !== null && !Array.isArray(value);
15
16
  }
16
- function isResponse(value) {
17
+ export function isResponse(value) {
17
18
  return isRecord(value) && typeof value.text === 'string' && isRecord(value.effective)
18
- && (value.reasoning === undefined || typeof value.reasoning === 'string');
19
+ && (value.reasoning === undefined || typeof value.reasoning === 'string')
20
+ && (value.spl === undefined || typeof value.spl === 'string');
19
21
  }
20
22
  function isTraceRequest(value) {
21
23
  return isRecord(value) && (value.kind === 'ask' || value.kind === 'think' || value.kind === 'agent')
22
24
  && typeof value.prompt === 'string' && (value.model === null || typeof value.model === 'string')
23
25
  && (value.reasoningEffort === null || value.reasoningEffort === 'low' || value.reasoningEffort === 'medium' || value.reasoningEffort === 'high')
24
- && typeof value.showReasoning === 'boolean';
26
+ && typeof value.showReasoning === 'boolean'
27
+ && (value.naturalLanguage === undefined || typeof value.naturalLanguage === 'boolean');
25
28
  }
26
- function isTraceError(value) {
29
+ export function isTraceError(value) {
27
30
  const codes = [
28
31
  'assertion_failed', 'explicit_failure', 'invalid_format', 'validation_failed',
29
32
  'restriction_violation', 'llm_error', 'tool_error', 'runtime_error',
@@ -51,7 +54,7 @@ export class RecordingBackend {
51
54
  fs.writeFileSync(file, JSON.stringify({ format: 'spl-generation', version: 1 }) + '\n', { flag: 'wx', mode: 0o600 });
52
55
  }
53
56
  async call(req) {
54
- if (req.kind === 'agent')
57
+ if (req.kind === 'agent' && !req.naturalLanguage)
55
58
  throw new LlmError('agent actions cannot be recorded; record generation-only workflows');
56
59
  const request = requestKey(req);
57
60
  const response = await this.backend.call(req);
@@ -143,7 +146,7 @@ export class ReplayBackend {
143
146
  });
144
147
  }
145
148
  async call(req) {
146
- if (req.kind === 'agent' && this.mode === 'replay')
149
+ if (req.kind === 'agent' && !req.naturalLanguage && this.mode === 'replay')
147
150
  throw new LlmError('agent actions cannot be replayed');
148
151
  const entry = this.entries[this.position];
149
152
  if (!entry)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spl-lang",
3
- "version": "0.6.0",
3
+ "version": "1.0.0",
4
4
  "description": "Semantic Prompt Language: a Python-like language with LLM calls built into the syntax",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -30,7 +30,9 @@
30
30
  "license": "ISC",
31
31
  "dependencies": {
32
32
  "@github/copilot-sdk": "^1.0.22",
33
- "ajv": "^8.17.1"
33
+ "ajv": "^8.17.1",
34
+ "marked": "^18.1.1",
35
+ "yaml": "^2.8.1"
34
36
  },
35
37
  "devDependencies": {
36
38
  "@types/node": "^26.6.5",
package/docs/syntax.ebnf DELETED
@@ -1,97 +0,0 @@
1
- (* SPL 0.3. { x } means repetition; [ x ] means optional.
2
- Statements end at newline, ';', '}' or EOF. Newlines inside () and [] are ignored.
3
- '#' starts a comment. Opening block braces must be on the keyword's line. *)
4
-
5
- program = { directive , separator } , { statement , separator } ;
6
- separator = NL | ";" | end_of_block | EOF ;
7
- directive = "@" , "{" , "model" , "=" , plain_string , [ "," ] , "}" ;
8
- block = "{" , { statement , separator } , "}" ;
9
-
10
- statement = declaration | assignment | if_stmt | for_stmt | while_stmt
11
- | def_stmt | try_stmt | guidance_stmt | restrict_stmt | block
12
- | "return" , [ expr ] | "success" , [ expr ]
13
- | "assert" , expr , [ expr ] | "fail" , expr
14
- | "break" | "continue" | "pass" | expr ;
15
- declaration = "let" , NAME , "=" , expr ;
16
- assignment = target , ( "=" | "+=" | "-=" | "*=" | "/=" | "//=" | "%=" ) , expr ;
17
- target = NAME | postfix , "." , NAME | postfix , "[" , expr , "]" ;
18
- if_stmt = "if" , expr , block , { NL* , "elif" , expr , block } , [ NL* , "else" , block ] ;
19
- for_stmt = "for" , NAME , { "," , NAME } , "in" , expr , block ;
20
- while_stmt = "while" , expr , block ;
21
- def_stmt = "def" , NAME , "(" , [ NAME , { "," , NAME } , [ "," ] ] , ")" , block ;
22
- try_stmt = "try" , block , NL* , "except" , NAME , block ;
23
- guidance_stmt = ( "enforce" | "forbid" ) , expr , block ;
24
- restrict_stmt = "restrict" , restrict_clause , { "," , restrict_clause } , block ;
25
- restrict_clause = [ "fs" | "net" | "exec" | "mcp" ] , expr ;
26
-
27
- (* Every block creates a lexical child scope. let declares/redeclares locally and shadows parents.
28
- Reads and assignments search outward for the first declaration; undeclared names are errors.
29
- Each loop iteration has fresh locals; caught failure names belong to the handler scope.
30
- Blocks/if yield their final expression, otherwise None. An unmatched if yields None.
31
- {} remains an empty dict; { pass } is a block yielding None. Functions need explicit return.
32
- return exits the innermost function, or the script at top level.
33
- success exits the script, including when used in a function. *)
34
-
35
- expr = or_expr , [ "if" , or_expr , "else" , expr ] ;
36
- or_expr = and_expr , { "or" , and_expr } ;
37
- and_expr = not_expr , { "and" , not_expr } ;
38
- not_expr = "not" , not_expr | comparison ;
39
- comparison = sum , { ( "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not" , "in" ) , sum } ;
40
- sum = term , { ( "+" | "-" ) , term } ;
41
- term = unary , { ( "*" | "/" | "//" | "%" ) , unary } ;
42
- unary = ( "-" | "+" ) , unary | "?" , [ modifiers ] , postfix | power ;
43
- power = postfix , [ "**" , unary ] ;
44
- postfix = primary , { call | index | slice | member } ;
45
- call = "(" , [ expr , { "," , expr } , [ "," ] ] , ")" ;
46
- index = "[" , expr , "]" ;
47
- slice = "[" , [ expr ] , ":" , [ expr ] , "]" ;
48
- member = "." , NAME ;
49
- primary = NUMBER | string | "True" | "False" | "None" | NAME | special_var
50
- | "(" , expr , ")" | list | dict | block | if_stmt | log_expr
51
- | ( "think" | "agent" ) , [ modifiers ] , postfix ;
52
- special_var = "$env" | "$args" ;
53
- list = "[" , [ expr , { "," , expr } , [ "," ] ] , "]" ;
54
- dict = "{" , [ expr , ":" , expr , { "," , expr , ":" , expr } , [ "," ] ] , "}" ;
55
- modifiers = "{" , [ modifier , { "," , modifier } , [ "," ] ] , "}" ;
56
- modifier = ( "len" | "fmt" | "reasoning" | "show" | "schema" | "repairs" | "model" ) , "=" , expr ;
57
- log_expr = "log" , "{" , [ "level" , "=" , log_level , [ "," ] ] , "}" , call ;
58
- log_level = "error" | "info" | "debug" | expr ;
59
-
60
- (* Unknown/duplicate modifier names are syntax errors. Values are expressions, validated at runtime:
61
- len: word/sentence/paragraph/long (guidance only)
62
- fmt: integer/float/array/object/text/boolean/json; schema implies json unless fmt is explicit
63
- Non-text formats require strict JSON of the selected type; float accepts finite numbers,
64
- object excludes arrays, and json accepts any JSON value.
65
- reasoning: low/medium/high; think and agent default to medium
66
- show: True/False; schema: synchronous strict JSON Schema draft-07 object or boolean
67
- repairs: integer 0..5 (additional generation attempts, default 0); agents require 0
68
- model: non-empty string; CLI override > per-call model > header > SDK default
69
- Generation (? and think) has no tools. agent explicitly enables restricted runtime/MCP tools.
70
- Raw tools are ordinary positional functions: read_file(path), write_file(path, content),
71
- web_fetch(url), web_search(query), shell(command). They never invoke a model.
72
- log{level=...}(args) selects error/info/debug severity; plain log(args) defaults to info.
73
- Level values are bare names, strings or expressions, validated before evaluating arguments.
74
- Debug messages are emitted only at debug log level; silent suppresses all logs.
75
- enforce/forbid are scoped prompt guidance.
76
- schema/assert validate data; restrict checks actions. *)
77
-
78
- string = [ "r" ] , quote , { char | escape | interpolation } , closing_quote ;
79
- quote = "'" | '"' | "'''" | '"""' ;
80
- plain_string = string_without_interpolation ;
81
- escape = "\" , ( "n" | "t" | "r" | "0" | "\" | "'" | '"' | "{" | "}" ) ;
82
- interpolation = "{" , expr , "}" ;
83
- NUMBER = digit+ , [ "." , digit+ ] , [ ( "e" | "E" ) , [ "+" | "-" ] , digit+ ] ;
84
- NAME = ( letter | "_" ) , { letter | digit | "_" } ;
85
- NL = newline ;
86
-
87
- (* "{{"/"}}" are literal braces; raw strings have no interpolation or escapes.
88
- $env is a read-only process-environment dict snapshot; missing keys return None.
89
- $args is a read-only list of repeated CLI --arg strings (empty by default);
90
- out-of-range integer indexes return None, and negative indexes count from the end.
91
- Both are available in every scope and interpolation, cannot be rebound or mutated
92
- (including through aliases), and are not automatically persisted.
93
- Other $-prefixed names are syntax errors. Ordinary dict/list missing reads still fail.
94
- Reserved words: let if elif else for in while def return try except assert fail success
95
- enforce forbid restrict think agent break continue pass and or not True False None.
96
- Removed syntax: think{level=...}, tool{param=...} 'instructions',
97
- let x += ... and let d.key = ... . *)