@agentix-e/nl2spel 1.3.0 → 1.4.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.
package/dist/index.d.cts CHANGED
@@ -334,6 +334,25 @@ interface SlotDefinition {
334
334
  }
335
335
  type SlotTransform = 'toNumber' | 'toBoolean' | 'toString' | 'trim' | 'lowercase' | 'normalize';
336
336
 
337
+ /**
338
+ * How to treat a field word the dictionary does not know.
339
+ *
340
+ * `passthrough` emits the word verbatim, which is legal Spring — `Character.isLetter`
341
+ * accepts any Unicode letter — and is the default, because a caller whose schema
342
+ * uses Chinese property names depends on it. `strict` refuses instead, for callers
343
+ * who would rather see an error than an unresolved name.
344
+ */
345
+ type FieldPolicy = 'passthrough' | 'strict';
346
+ /** Thrown under `strict` when a field word has no dictionary entry. */
347
+ declare class UnmappedFieldError extends Error {
348
+ /** The field word that could not be resolved. */
349
+ readonly field: string;
350
+ constructor(field: string);
351
+ }
352
+ interface PatternMatcherOptions {
353
+ /** How to treat a field word with no dictionary entry. Default `passthrough`. */
354
+ fieldPolicy?: FieldPolicy;
355
+ }
337
356
  interface PatternMatchResult {
338
357
  /** Whether matched */
339
358
  matched: boolean;
@@ -347,13 +366,17 @@ interface PatternMatchResult {
347
366
  latencyMs: number;
348
367
  /** Extracted slot values */
349
368
  slots?: Record<string, string>;
369
+ /**
370
+ * Field words emitted verbatim because the dictionary had no entry, in
371
+ * encounter order. Populated under either policy, so a caller can always see
372
+ * what was not resolved rather than having to infer it.
373
+ */
374
+ unmappedFields?: string[];
350
375
  }
351
- /**
352
- * PatternMatcher — Layer 0 pattern matching core.
353
- */
354
376
  declare class PatternMatcher {
355
377
  private _patterns;
356
- constructor(patterns?: PatternDefinition[]);
378
+ private readonly fieldPolicy;
379
+ constructor(patterns?: PatternDefinition[], options?: PatternMatcherOptions);
357
380
  get patternCount(): number;
358
381
  register(pattern: PatternDefinition): void;
359
382
  registerAll(patterns: PatternDefinition[]): void;
@@ -374,18 +397,29 @@ declare class PatternMatcher {
374
397
  * Infer SpEL root object name from Chinese input
375
398
  */
376
399
  private inferRoot;
400
+ /**
401
+ * The first whitespace- or punctuation-delimited chunk of `input`.
402
+ */
403
+ private firstWord;
377
404
  /**
378
405
  * Extract Chinese field names from input and map to SpEL fields
379
406
  */
380
407
  private extractChineseField;
381
408
  /**
382
- * Template filling and value transformation
409
+ * Resolve a captured field word into the identifier to emit, and report whether
410
+ * the dictionary recognised it.
411
+ *
412
+ * An ASCII word is already an identifier and is emitted unchanged. Anything else
413
+ * is looked up: a word the dictionary knows becomes its English identifier, and a
414
+ * word it does not know is emitted verbatim and reported as unmapped. Emitting it
415
+ * verbatim is legal Spring, but it is a guess about the caller's schema, so the
416
+ * guess is never silent.
383
417
  */
384
- private fillTemplate;
418
+ private resolveField;
385
419
  /**
386
- * Infer SpEL field name from capture group
420
+ * Template filling and value transformation
387
421
  */
388
- private inferFieldFromCapture;
422
+ private fillTemplate;
389
423
  }
390
424
 
391
425
  /**
@@ -399,6 +433,60 @@ declare class PatternMatcher {
399
433
  */
400
434
  declare const BUILTIN_PATTERNS: PatternDefinition[];
401
435
 
436
+ /**
437
+ * Clause decomposition for compound natural-language rules.
438
+ *
439
+ * A single comparison pattern matches a prefix and ignores the rest, so a
440
+ * sentence that joins two conditions is silently answered with only the first
441
+ * one: `金额大于1000且订单已确认` used to produce `#amount > 1000`, dropping the
442
+ * confirmation requirement entirely. The result parses, so nothing downstream
443
+ * notices — a materially weaker rule than the one that was asked for.
444
+ *
445
+ * This module splits such a sentence on its top-level logical connectors, has the
446
+ * caller convert each clause independently, and joins the results with SpEL's
447
+ * `and`/`or`. A clause that cannot be converted is reported rather than dropped:
448
+ * a partial rule is never emitted.
449
+ */
450
+ interface Clause {
451
+ /** Clause text, trimmed. */
452
+ text: string;
453
+ /**
454
+ * How this clause attaches to the previous one. The first clause carries
455
+ * `'and'`, which is never used because a single clause needs no join.
456
+ */
457
+ connector: 'and' | 'or';
458
+ }
459
+ /** Raised when a compound sentence contains a clause that cannot be converted. */
460
+ declare class UnconvertibleClauseError extends Error {
461
+ /** The clause texts that could not be converted. */
462
+ readonly unconvertible: readonly string[];
463
+ /** The full input that was being decomposed. */
464
+ readonly input: string;
465
+ constructor(input: string, unconvertible: readonly string[]);
466
+ }
467
+ /**
468
+ * Split `input` on its top-level logical connectors.
469
+ *
470
+ * Returns a single clause when there is no top-level connector, so the caller can
471
+ * treat "one clause" and "no decomposition needed" identically.
472
+ */
473
+ declare function splitClauses(input: string): Clause[];
474
+ interface Decomposition {
475
+ /** The joined expression, with every operand parenthesised. */
476
+ expression: string;
477
+ /** The clause texts, in order. */
478
+ clauses: string[];
479
+ }
480
+ /**
481
+ * Decompose `input` and convert every clause with `convert`.
482
+ *
483
+ * Returns `null` when the input has no top-level connector — the caller should
484
+ * run its ordinary single-pass conversion instead. Throws
485
+ * {@link UnconvertibleClauseError} when any clause cannot be converted, so a
486
+ * partially understood sentence is never answered with a partial rule.
487
+ */
488
+ declare function decompose(input: string, convert: (clause: string) => string | null): Decomposition | null;
489
+
402
490
  /**
403
491
  * NLIntent — Natural language intent classification system.
404
492
  *
@@ -514,6 +602,22 @@ declare class TemplateEngine {
514
602
  generate(input: string, intentResult: IntentResult): TemplateResult | null;
515
603
  private selectBestTemplate;
516
604
  private fillTemplate;
605
+ /**
606
+ * Resolve the SpEL root name for `input`: the configured schema root when one
607
+ * exists, otherwise a keyword heuristic over the well-known roots.
608
+ */
609
+ private resolveRootName;
610
+ /**
611
+ * Resolve the field name for `input`.
612
+ *
613
+ * The previous implementation only compared schema *keys* and otherwise left
614
+ * the literal placeholder default in place, which is how "#order.field == null"
615
+ * reached callers. The lookup now falls back in order: schema key, schema
616
+ * field description (Chinese inputs name the field by its description),
617
+ * Chinese field word, first English identifier, and finally "value" — the
618
+ * neutral default the pattern layer already uses for an unknown field.
619
+ */
620
+ private resolveFieldName;
517
621
  }
518
622
 
519
623
  interface PromptBuilderOptions {
@@ -534,6 +638,10 @@ declare class PromptBuilder {
534
638
  private buildExamples;
535
639
  }
536
640
 
641
+ /** Stage a diagnostic was produced by. */
642
+ type ValidationStage = 'parse' | 'type' | 'semantic' | 'context' | 'final';
643
+ /** Whether a diagnostic rejects the expression or merely observes it. */
644
+ type ValidationSeverity = 'error' | 'warning';
537
645
  interface ValidationResult {
538
646
  /** Whether all validation stages passed */
539
647
  valid: boolean;
@@ -543,6 +651,7 @@ interface ValidationResult {
543
651
  type: StageResult;
544
652
  semantic: StageResult;
545
653
  context: StageResult;
654
+ final: StageResult;
546
655
  };
547
656
  /** All errors */
548
657
  errors: ValidationError[];
@@ -557,28 +666,38 @@ interface StageResult {
557
666
  interface ValidationError {
558
667
  code: string;
559
668
  message: string;
669
+ /** Severity of this diagnostic — always `error`. */
670
+ severity?: ValidationSeverity;
560
671
  /** Error position in expression */
561
672
  position?: number;
562
673
  /** Stage the error belongs to */
563
- stage: 'parse' | 'type' | 'semantic' | 'context';
674
+ stage: ValidationStage;
564
675
  /** Whether LLM regeneration is needed */
565
676
  requiresLLM?: boolean;
566
677
  }
567
678
  interface ValidationWarning {
568
679
  code: string;
569
680
  message: string;
681
+ /** Severity of this diagnostic — always `warning`. */
682
+ severity?: ValidationSeverity;
570
683
  /** Warning position in expression */
571
684
  position?: number;
572
685
  /** Stage the warning belongs to */
573
- stage: 'parse' | 'type' | 'semantic' | 'context';
686
+ stage: ValidationStage;
574
687
  }
575
688
  /**
576
- * ValidationPipeline — four-stage validation pipeline.
689
+ * ValidationPipeline — five-stage validation pipeline.
577
690
  *
578
- * 1. Parse: syntax validity (depends on SpelEvaluator)
579
- * 2. Type: type checking (operator-operand type matching)
580
- * 3. Semantic: semantic reasonableness (is the expression meaningful)
581
- * 4. Context: context references (do all references exist in ContextSchema)
691
+ * 1. Parse: syntax validity (empty, delimiter balance, JS operators, evaluator)
692
+ * 2. Type: type checking (operator-operand type matching) — advisory
693
+ * 3. Semantic: semantic reasonableness advisory
694
+ * 4. Context: context references (do all references exist in ContextSchema)
695
+ * 5. Final: the expression must parse — structural completeness gate
696
+ *
697
+ * Stages 1, 4 and 5 can reject an expression; the type and semantic stages only
698
+ * observe. Reference checks are only meaningful when a schema is supplied,
699
+ * because without one an undeclared `#unknownRef` and a legitimate runtime
700
+ * variable are indistinguishable.
582
701
  */
583
702
  declare class ValidationPipeline {
584
703
  private evaluator;
@@ -590,36 +709,103 @@ declare class ValidationPipeline {
590
709
  validate(expression: string, contextSchema?: ContextSchema): Promise<ValidationResult>;
591
710
  /**
592
711
  * Stage 1: Parse Check — syntax validation
712
+ *
713
+ * Delimiter balance and JavaScript operators are checked against a copy of
714
+ * the expression with string literals blanked out, so literal text can never
715
+ * be mistaken for syntax.
593
716
  */
594
717
  private validateParse;
595
718
  /**
596
- * Stage 2: Type Check — type validation
719
+ * Stage 2: Type Check — advisory type validation
597
720
  */
598
721
  private validateTypes;
599
722
  /**
600
- * Stage 3: Semantic Check — semantic reasonableness validation
723
+ * Stage 3: Semantic Check — advisory semantic validation
601
724
  */
602
725
  private validateSemantic;
603
726
  /**
604
727
  * Stage 4: Context Check — context reference validation
728
+ *
729
+ * A supplied schema turns this stage into a real gate: an undeclared bean,
730
+ * a missing root field or an undeclared variable is an error. Without a
731
+ * schema nothing can be judged, so the stage stays advisory.
605
732
  */
606
733
  private validateContext;
607
734
  /**
608
- * Check if parentheses are balanced
735
+ * Stage 5: Final Check the generated expression must parse.
736
+ *
737
+ * When an evaluator is configured the parse stage already performed a real
738
+ * parse; this mandatory final gate adds the structural check that a truncated
739
+ * tail (an expression ending on an operator) is never accepted, even when no
740
+ * engine is wired in. It is deliberately conservative: only a trailing
741
+ * operator or an unterminated literal fails, so no well-formed expression is
742
+ * rejected.
743
+ */
744
+ private validateFinal;
745
+ /**
746
+ * Whether the expression is obviously incomplete.
747
+ *
748
+ * The last token is obtained from spel-ts's tokenizer, so a trailing
749
+ * operator keyword or an unterminated string literal is detected exactly,
750
+ * and text inside a literal is never read as a trailing operator.
751
+ */
752
+ private isManifestlyIncomplete;
753
+ /**
754
+ * Check if parentheses are balanced. Expects a literal-masked expression.
609
755
  */
610
756
  private hasBalancedParentheses;
611
757
  /**
612
- * Extract all identifier references from expression
758
+ * Extract all identifier references from expression.
759
+ *
760
+ * A bare `#x` that is only the head of a dotted reference (`#x.y`) is not
761
+ * emitted on its own: a dotted reference names a property of some object,
762
+ * not a variable of that name.
613
763
  */
614
764
  private extractReferences;
615
765
  }
616
766
 
617
767
  /**
618
- * AutoFixer — automatically fixes common SpEL syntax errors from LLM output.
768
+ * AutoFixer — rewrites the JavaScript spellings an LLM commonly emits into the
769
+ * SpEL equivalents, without ever touching the contents of a string literal.
770
+ *
771
+ * Literal boundaries come from a quote-state scan. In SpEL a quote character is
772
+ * always a string-literal delimiter — the language has no comments and no other
773
+ * construct that uses `'` or `"` — so the scan is exact by construction rather
774
+ * than an approximation of the parser. It is also independent of the engine
775
+ * build: deriving the boundaries from the lexer made every structural check
776
+ * depend on the lexer being able to tokenize the *whole* expression, which fails
777
+ * for input the engine cannot lex at all, such as a field name in Chinese on a
778
+ * build without Unicode identifier support. A whole-string regex (the previous
779
+ * implementation) rewrote literal contents and appended closers for delimiters
780
+ * it could only see inside literals, turning valid expressions into broken
781
+ * ones.
619
782
  */
620
783
  declare class AutoFixer {
621
784
  fix(expression: string): AutoFixResult;
622
- private fixAllBrackets;
785
+ /**
786
+ * Apply one global replacement rule to every unprotected chunk.
787
+ *
788
+ * The replacement is only reported when it actually matched, and the count
789
+ * is the number of matches across all chunks, so the human-readable change
790
+ * log is unchanged from the previous implementation.
791
+ */
792
+ private applyRule;
793
+ /**
794
+ * Normalise the Elvis operator only when it was written with whitespace
795
+ * between `?` and `:`. A correctly written `?:` must be left untouched so
796
+ * that `fix()` is a no-op on already-valid input.
797
+ */
798
+ private applyElvisRule;
799
+ /**
800
+ * Split the expression into alternating protected (string literal) and
801
+ * unprotected chunks. Ranges come from spel-ts's tokenizer so they agree
802
+ * exactly with what the parser considers opaque.
803
+ */
804
+ private split;
805
+ /**
806
+ * Turn protected spans into a chunk list covering the whole expression.
807
+ */
808
+ private toChunks;
623
809
  }
624
810
  interface AutoFixResult {
625
811
  expression: string;
@@ -685,6 +871,8 @@ interface StrategyResult {
685
871
  interface StrategyMetadata {
686
872
  /** Matched pattern ID (pattern strategy only) */
687
873
  patternId?: string;
874
+ /** Clause texts, when a compound sentence was decomposed */
875
+ clauses?: string[];
688
876
  /** Intent type (template strategy only) */
689
877
  intent?: string;
690
878
  /** Template name (template strategy only) */
@@ -698,6 +886,15 @@ interface StrategyMetadata {
698
886
  /** Raw LLM output (llm strategy only) */
699
887
  rawOutput?: string;
700
888
  }
889
+ /** A compound sentence converted clause by clause. */
890
+ interface DecomposedConversion {
891
+ /** The joined expression, with every operand parenthesised. */
892
+ expression: string;
893
+ /** The clause texts, in order. */
894
+ clauses: string[];
895
+ /** The lowest confidence among the converted clauses. */
896
+ confidence: number;
897
+ }
701
898
  interface StrategyRouterConfig {
702
899
  /** Pattern confidence threshold (default 0.7) */
703
900
  patternMinConfidence?: number;
@@ -727,6 +924,15 @@ declare class StrategyRouter {
727
924
  /**
728
925
  * Get PatternMatcher (for external testing/debugging)
729
926
  */
927
+ /**
928
+ * Convert a sentence that joins its clauses with a logical connector.
929
+ *
930
+ * Returns `null` when `nl` has no top-level connector, in which case the caller
931
+ * should use its ordinary single-pass conversion. Throws
932
+ * {@link UnconvertibleClauseError} when a clause cannot be converted, so the
933
+ * caller can refuse instead of emitting a partial rule.
934
+ */
935
+ decomposeClauses(nl: string): DecomposedConversion | null;
730
936
  getPatternMatcher(): PatternMatcher;
731
937
  /**
732
938
  * Get TemplateEngine
@@ -914,4 +1120,4 @@ declare class ChineseNumberParser {
914
1120
  static parseSafe(text: string): number | null;
915
1121
  }
916
1122
 
917
- export { type AutoFixResult, AutoFixer, BUILTIN_PATTERNS, ChineseNumberParser, ContextExtractor, type CorrectionLog, type DebugInfo, type ExplainResult, type FewShotExample, type GenerateOptions, type GenerateResult, IntentClassifier, type IntentEntity, type IntentResult, type LLMCapabilities, type LLMGenerateOptions, type LLMPrompt, type LLMProvider, type LLMResponse, type LLMStreamChunk, type LLMUsage, NL2SpelEngine, NLIntent, type PatternDefinition, type PatternMatchResult, PatternMatcher, PromptBuilder, type PromptBuilderOptions, ProviderRegistry, SchemaFormatter, type SelfCorrectionConfig, SelfCorrectionLoop, type SelfCorrectionResult, type SlotDefinition, type SlotTransform, type StageResult, type StrategyMetadata, type StrategyResult, StrategyRouter, type StrategyRouterConfig, type StrategyType, TemplateEngine, type TemplateResult, type ValidationError, ValidationPipeline, type ValidationResult, type ValidationWarning };
1123
+ export { type AutoFixResult, AutoFixer, BUILTIN_PATTERNS, ChineseNumberParser, type Clause, ContextExtractor, type CorrectionLog, type DebugInfo, type Decomposition, type ExplainResult, type FewShotExample, type FieldPolicy, type GenerateOptions, type GenerateResult, IntentClassifier, type IntentEntity, type IntentResult, type LLMCapabilities, type LLMGenerateOptions, type LLMPrompt, type LLMProvider, type LLMResponse, type LLMStreamChunk, type LLMUsage, NL2SpelEngine, NLIntent, type PatternDefinition, type PatternMatchResult, PatternMatcher, type PatternMatcherOptions, PromptBuilder, type PromptBuilderOptions, ProviderRegistry, SchemaFormatter, type SelfCorrectionConfig, SelfCorrectionLoop, type SelfCorrectionResult, type SlotDefinition, type SlotTransform, type StageResult, type StrategyMetadata, type StrategyResult, StrategyRouter, type StrategyRouterConfig, type StrategyType, TemplateEngine, type TemplateResult, UnconvertibleClauseError, UnmappedFieldError, type ValidationError, ValidationPipeline, type ValidationResult, type ValidationWarning, decompose, splitClauses };