Recursive AI

Error reports: text, compact and JSON

All oneecma recipes

Get OneEcma’s report for a failed script in each of its forms: the full text report, the same with each frame’s local variables, a compact rendering sized for an LLM’s context, and versioned JSON for tools. All of them come from one Diagnostic.

The script reads order.items where the object holds lineItems.

C#
using RecursiveAI.OneEcma;
using RecursiveAI.OneEcma.Diagnostics;
 
const string checkout = """
    async function loadOrder(id) {
      return { id, lineItems: [{ price: 4 }, { price: 6 }], total: 10 };
    }
 
    async function main() {
      const order = await loadOrder(7);
      const total = order.items.reduce((sum, i) => sum + i.price, 0);
      return total;
    }
 
    main();
    """;
 
var engine = new ScriptEngine();
try
{
    engine.Evaluate(checkout, "checkout.js");              // the rejection is unhandled, so Evaluate throws
}
catch (JsException e)
{
    Diagnostic report = e.Report!;
 
    Console.WriteLine("--- e.Message");
    Console.WriteLine(e.Message);
 
    Console.WriteLine("--- the full report");
    Console.WriteLine(DiagnosticRenderer.Render(report));
 
    Console.WriteLine("--- with locals");
    Console.WriteLine(DiagnosticRenderer.Render(report, new RenderOptions { ShowLocals = true }));
 
    Console.WriteLine("--- compact, for a model's context");
    Console.WriteLine(DiagnosticRenderer.Render(report, new RenderOptions { Compact = true }));
 
    Console.WriteLine("--- JSON");
    Console.WriteLine(DiagnosticJson.Render([report], new JsonRenderOptions { Indented = true }));
 
    Console.WriteLine("--- the same facts as objects");
    Console.WriteLine($"{report.Kind} {report.Code} at {report.Position.Line}:{report.Position.Column}");
    foreach (DidYouMean d in report.Suggestions) Console.WriteLine($"did you mean {d.Name}: {d.Description}");
}
Text
--- e.Message
TypeError: Cannot read properties of undefined (reading 'reduce')
--- the full report
error[TypeError]: Cannot read properties of undefined (reading 'reduce')
 --> checkout.js:7:23
  |
6 |   const order = await loadOrder(7);
  |                 ------------------ `order` comes from here: { id, lineItems, total }
7 |   const total = order.items.reduce((sum, i) => sum + i.price, 0);
  |                       ^^^^^ `order.items` is undefined
  |
  = help: did you mean `order.lineItems`? (Array, 2 items)
  = stack: main (checkout.js:7)
 
--- with locals
error[TypeError]: Cannot read properties of undefined (reading 'reduce')
 --> checkout.js:7:23
  |
6 |   const order = await loadOrder(7);
  |                 ------------------ `order` comes from here: { id, lineItems, total }
7 |   const total = order.items.reduce((sum, i) => sum + i.price, 0);
  |                       ^^^^^ `order.items` is undefined
  |
  = help: did you mean `order.lineItems`? (Array, 2 items)
  = stack: main (checkout.js:7)
  = locals in main: order = { id, lineItems, total }, total = <uninitialized>
 
--- compact, for a model's context
error[TypeError]: Cannot read properties of undefined (reading 'reduce')
 --> checkout.js:7:23
6 |   const order = await loadOrder(7);
  |                 ------------------ `order` comes from here: { id, lineItems, total }
7 |   const total = order.items.reduce((sum, i) => sum + i.price, 0);
  |                       ^^^^^ `order.items` is undefined
  = help: did you mean `order.lineItems`? (Array, 2 items)
 
--- JSON
{
  "version": 1,
  "format": "oneecma.report",
  "summary": {
    "errors": 1,
    "warnings": 0,
    "repairs": 0
  },
  "diagnostics": [
    {
      "kind": "TypeError",
      "code": "runtime",
      "severity": "error",
      "headline": "Cannot read properties of undefined (reading 'reduce')",
      "source": {
        "name": "checkout.js",
        "length": 255
      },
      "span": {
        "start": 185,
        "end": 190,
        "line": 7,
        "column": 23,
        "endLine": 7,
        "endColumn": 28
      },
      "label": "`order.items` is undefined",
      "secondary": [
        {
          "span": {
            "start": 143,
            "end": 161,
            "line": 6,
            "column": 17,
            "endLine": 6,
            "endColumn": 35
          },
          "message": "`order` comes from here: { id, lineItems, total }"
        }
      ],
      "notes": [],
      "help": [
        "did you mean `order.lineItems`? (Array, 2 items)"
      ],
      "fixes": [],
      "recovery": [],
      "stack": [
        {
          "function": "main",
          "source": {
            "name": "checkout.js",
            "length": 255
          },
          "span": {
            "start": 179,
            "end": 190,
            "line": 7,
            "column": 17,
            "endLine": 7,
            "endColumn": 28
          },
          "async": false,
          "locals": [
            {
              "name": "order",
              "value": "{ id, lineItems, total }"
            },
            {
              "name": "total",
              "value": "<uninitialized>"
            }
          ]
        }
      ],
      "provenance": [
        {
          "source": {
            "name": "checkout.js",
            "length": 255
          },
          "span": {
            "start": 143,
            "end": 161,
            "line": 6,
            "column": 17,
            "endLine": 6,
            "endColumn": 35
          },
          "description": "`order` comes from here: { id, lineItems, total }"
        }
      ],
      "didYouMean": [
        {
          "name": "order.lineItems",
          "description": "Array, 2 items"
        }
      ]
    }
  ]
}
--- the same facts as objects
TypeError runtime at 7:23
did you mean order.lineItems: Array, 2 items

Reading the report

  • The headline is V8’s wording, word for word, and err.message and err.stack inside the script are what V8 prints too. Code that parses them keeps working.
  • The primary underline marks the failing expression and says what was wrong with it.
  • Secondary labels (- underlines) show where the failing value came from, including across await.
  • help: suggests a fix. A “did you mean” candidate is always something the object or scope actually holds, never a guess.
  • stack: lists the frames, innermost first. Frames stitched across await are marked async, and .NET frames of host functions sit between script frames.
  • locals in lines (with ShowLocals) preview each frame’s bindings, at most 24 per frame and 60 characters per value.

The explanation comes from replaying the failed run with tracing on: the normal run only records what it took from outside (see Record a failed run and replay it). A run that succeeds pays nothing for it.

Which form to use

  • Full text (DiagnosticRenderer.Render) for logs and people.
  • Compact (RenderOptions.Compact) for a model’s context: headline, location, the source line with its underline and label, the origin label, the single most useful help, and a one-line stack only when there is more than one frame. Long lines are cut to about 100 columns.
  • JSON (DiagnosticJson.Render) for tools. It is compact on one line by default; Indented is shown here for reading.
  • Objects: Diagnostic has Kind, Code, Headline, PrimarySpan, SecondaryLabels, Help, Notes, Suggestions, Provenance and Stack, if you’d rather build your own message.

The JSON contract

The format is versioned: "version": 1, and within a version fields are only added, so a consumer ignores properties it doesn’t know. Offsets are UTF-16 code units, half-open; lines and columns are 1-based, with columns in UTF-16 code units as in err.stack. Core arrays (secondary, notes, help, fixes, recovery) are always present; the runtime members (stack, provenance, didYouMean) appear only when they have something to say. The same bytes come out for the same diagnostics.

The schema is diagnostic-report.schema.json (JSON Schema 2020-12), and every field is described in the JSON report.

Good to know

  • Syntax errors come in the same forms, with every error of one parse in one report: DiagnosticRenderer.RenderAll and DiagnosticJson.Render take a list.
  • oneecma run --compact, --locals and oneecma check --format json print the same forms from the command line: see The oneecma command.
  • How the help: line is chosen is in How help lines are chosen; the err.stack format is in stack traces.