JRK.JevRunner 0.5.0

dotnet add package JRK.JevRunner --version 0.5.0
                    
NuGet\Install-Package JRK.JevRunner -Version 0.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="JRK.JevRunner" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="JRK.JevRunner" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="JRK.JevRunner" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add JRK.JevRunner --version 0.5.0
                    
#r "nuget: JRK.JevRunner, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package JRK.JevRunner@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=JRK.JevRunner&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=JRK.JevRunner&version=0.5.0
                    
Install as a Cake Tool

JRK.JevRunner

NuGet

An .NET client for Jev and the TypeSafe System One API. Submit application state and named questions, and consume typed responses.

Requirements

  • .NET 11 or later
  • A TypeSafe API key

Installation

dotnet add package JRK.JevRunner

The package includes its source generators automatically; no separate generator package or analyzer reference is needed when consuming it through NuGet. The generator assembly targets netstandard2.0 for compiler-host compatibility, while the client targets net11.0.

For repository development, analyzer project references are not transitive. See the sample project's explicit analyzer reference. Build and pack the client with:

dotnet pack JRK.JevRunner/JRK.JevRunner.csproj -c Release
dotnet build JRK.JevRunner.SourceGenerators/JRK.JevRunner.SourceGenerators.Sample/JRK.JevRunner.SourceGenerators.Sample.csproj -c Release -p:UsePackedJevRunner=true

The second command is a build-only packaging smoke check: it consumes the locally packed NuGet without an explicit generator reference and compiles code that requires generated types.

Package restores audit both direct and transitive dependencies and fail on known vulnerabilities or audit-feed failures. To repeat the advisory check:

dotnet restore all.slnx --force-evaluate
dotnet package list --project all.slnx --vulnerable --include-transitive --no-restore

Quick start

Set the TYPESAFE_API_KEY environment variable to your API key. Do not commit API keys to source control.

using JRK.JevRunner;
using JRK.JevRunner.Requests;

var apiKey = Environment.GetEnvironmentVariable("TYPESAFE_API_KEY")
    ?? throw new InvalidOperationException("Set TYPESAFE_API_KEY before running.");

var runner = JevRunner.Init(apiKey);
var request = new Request("The brown fox jumped over the lazy dog.", JevModel.Latest)
    .AddQuestion(new NoulQuestionBuilder(
        "dog_was_jumped_over",
        "Did any animal jump over a lazy dog?"));

var response = await runner.Execute(request);
Console.WriteLine(response.GetRequiredNoulAnswer("dog_was_jumped_over"));

Images (experimental)

Jev's image API is dreamer-ready, not production-ready: request formats, limits, and accuracy may change. Images can be used with Noul, Choice, Score, and Box questions. Box answers contain normalized Top, Bottom, Left, and Right edges and answer-level Confidence.

Use AssetState to associate a state field with an image. AddImageAsset reads the file asynchronously, base64-encodes its bytes, and uses its extension as the image format. It accepts an optional IFileSystem for file access.

Using the runner initialized in the quick start:

using JRK.JevRunner;
using JRK.JevRunner.Infrastructure;
using JRK.JevRunner.Requests;
using TruePath;

var state = new AssetState();
await state.AddImageAsset(
    "photo",
    Guid.NewGuid().ToString(),
    AbsolutePath.Create(Path.GetFullPath("photo.jpg")));

var request = new Request(state, JevModel.Latest)
    .AddQuestion(new NoulQuestionBuilder(
        "person_present",
        "Is there a person in `photo`?"));

var response = await runner.Execute(request);
Console.WriteLine(response.GetRequiredNoulAnswer("person_present").Noul);

Populate the state before constructing the request: its assets are collected by the constructor. A generated [JevQuery] can likewise expose an AssetState as its State property; populate it before calling GetRequest() or Execute().

For manual configuration, add an ImageAsset(reference, format, base64) to request.Assets and place the same reference in the state, instructions, or criteria. Use unique references such as UUIDs to avoid accidental text matches. Base64 must contain only the encoded bytes, without a data: prefix.

Requests containing assets automatically send TypeSafe-Danger-Zone-Images: true. Images are sent in the JSON assets map, not as multipart uploads. A filename or image URL alone does not upload an image.

Prepare PNG or JPEG images before sending: each dimension must be 280–1568 pixels, each file at most 16 MiB, and the request body at most 32 MiB. Base64 increases the body size. Rotate the pixels upright; the API does not apply EXIF orientation. The helper does not resize, rotate, convert, or validate image data. Use a lowercase .png, .jpg, or .jpeg extension that matches the file contents. For an explicit per-file check before upload, use ImageValidator from the optional JRK.JevRunner.Image package. Or see Validate and prepare images. See the TypeSafe image documentation for all limits.

Validate and prepare images

The optional JRK.JevRunner.Image package provides ImageValidator, proportional ImageScaler.Upscale/Downscale, and experimental automatic asset adjustment. Validation returns typed dimension or string errors and a result-level SuggestedScaling; call ThrowIfInvalid() to reject a failed validation. Suggestions only describe dimension-error direction and do not guarantee that resizing will make an image valid. See the image preparation examples and limitations.

To register already-prepared bytes without writing another file:

byte[] imageBytes = new JRK.JevRunner.Image.ImageScaler().Downscale(
    AbsolutePath.Create(@"C:\images\photo.jpg"));
var state = new AssetState().AddAsset("photo",
    ImageAsset.FromByteArray(Guid.NewGuid().ToString(), "png", imageBytes));

ImageScaler returns PNG bytes. FromByteArray base64-encodes them without a data: prefix; the factory itself does not validate or convert the supplied format.

Locate objects with Box questions

Using the populated state above:

var request = new Request(state)
    .AddQuestion(new BoxQuestionBuilder("Head", "Find the head in `photo`."));
var response = await runner.Execute(request);
var head = response.GetRequiredBoxAnswer("Head");
Console.WriteLine($"Top: {head.Top}, left: {head.Left}, confidence: {head.Confidence.Value}");

Without an explicit model, execution selects JevModel.Preview for Box questions and JevModel.Latest otherwise, including questions added after construction. An explicitly supplied model is never overridden.

For generated queries, expose a GenericBoxQuestionDefinition<string> property or implement IBoxQuestionDefinition<TInstruction>. A question property named Head generates GetHeadAnswer(response) and a BoxAnswer property named Head on the response wrapper. See the generator's Box example.

Render bounding boxes

The optional JRK.JevRunner.Image package provides a SkiaSharp BoxRenderer. The core client does not depend on it. With the original image path and a Box answer:

using JRK.JevRunner.Image;

var imagePath = AbsolutePath.Create(Path.GetFullPath("photo.jpg"));
byte[] png = new BoxRenderer().DrawBox(imagePath, head);
await File.WriteAllBytesAsync("annotated.png", png);

DrawBox accepts one BoxAnswer or an IEnumerable<BoxAnswer> through AppliedBox, and returns PNG bytes without modifying the source. Multiple boxes share the supplied color and stroke width. See the image package documentation for styling, coordinate validation, native dependencies, and multi-box examples.

API errors

Known HTTP errors throw JevKnownException from JRK.JevRunner.Infrastructure. Its InnerException retains the original HttpRequestException, including the status code, and its nullable Content exposes the response body as an ErrorContent union (JsonNode or string). Empty or whitespace-only bodies produce null content.

using JRK.JevRunner.Infrastructure;

try
{
    var response = await runner.Execute(request);
}
catch (JevKnownException error)
{
    Console.Error.WriteLine(error.Message);
    if (error.InnerException is HttpRequestException httpError)
        Console.Error.WriteLine($"HTTP status: {httpError.StatusCode}");
    if (error.Content is { } content)
        Console.Error.WriteLine(content.Value);
}

Handled status codes are 400, 401, 413, 415, 422, 429, and 529, with 509 retained for compatibility. Other status codes pass through as HTTP exceptions regardless of whether their response body is JSON or plain text. Transport failures pass through without an HTTP response. The client does not automatically retry rate-limit or overload responses.

Non-empty error bodies are parsed as JSON when possible. Plain text, malformed JSON, and JSON null are retained as raw strings rather than masking the HTTP failure with a JSON parsing exception.

See the TypeSafe error-code documentation.

Samples

See the samples and run instructions for runnable examples, including:

  • AnimalQuiz: identify a hidden animal through yes-or-no questions in an interactive console game.
  • BlackJack (Halvtolv): play a Danish blackjack variant against a Jev-controlled dealer that reasons about visible cards and round history to decide whether to stop or draw.
  • FileGrouping: classify files with Jev and move them into category folders.

Generated queries

Alternatively you can mark a partial class or record class with [JevQuery] and supply readable State and JevModel properties. Note that State can be a string, array or any object. Question properties implement one of INoulQuestionDefinition<TInstruction>, IChoiceQuestionDefinition<TInstruction>, IScoreQuestionDefinition<TInstruction>, or IBoxQuestionDefinition<TInstruction> from JRK.JevRunner.Annotation. The generator adds GetRequest(), Execute(runner, cancellationToken), and named typed answer accessors such as GetBudgetAnswer(Response). Generated Execute returns Task<{QueryName}ResponseWrapper>. The wrapper exposes getter-only typed answer properties named after the query's questions and the original .Response.

For example, using the runner initialized in the quick start:

using JRK.JevRunner.Annotation;
using JRK.JevRunner.Requests;

var query = new AnimalQuery();
var result = await query.Execute(runner);
Console.WriteLine(result.DogJumpedOver);
var response = result.Response; // Original Response for direct runtime API access.

[JevQuery]
public partial class AnimalQuery
{
    public string State => "The brown fox jumped over the lazy dog.";
    public JevModel JevModel => global::JRK.JevRunner.Requests.JevModel.Latest;
    public JumpQuestion DogJumpedOver { get; } = new();
}

public class JumpQuestion : INoulQuestionDefinition<string>
{
    public string Instructions => "Did any animal jump over a lazy dog?";
    public string? Yes => "An animal jumped over a lazy dog.";
    public string? No => "No animal jumped over a lazy dog.";
}

Execute(runner, cancellationToken) builds the request and forwards the optional cancellation token to the runner. In this example it returns an AnimalQueryResponseWrapper with a NoulAnswer property named DogJumpedOver. GetRequest() and GetDogJumpedOverAnswer(Response) remain available for manual execution and answer lookup. Wrapper answer properties throw if an answer is missing or has a mismatched type. Criteria are generated only when both Yes and No are non-null; otherwise criteria remain unset.

Question attributes have been replaced by definition interfaces. Choice definitions provide ChoiceCriteria[] Choices; score definitions provide string[] Criterias and now have only the instruction generic parameter. Noul definitions provide optional Yes and No descriptions: both must be non-null to generate criteria. If either is null, the runtime omits the JSON criteria member.

See the generator contract and diagnostics and the mixed-query sample.

This package is targeting C# 15 and dotnet sdk which is currently in the time of writing in preview

Dependency notes

This library uses TruePath.TestableIO.System.IO, which builds on TruePath and TestableIO.System.IO.

License

MIT License. Copyright (c) 2026 Jeppe Roi Kristensen.

Product Compatible and additional computed target framework versions.
.NET net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on JRK.JevRunner:

Package Downloads
JRK.JevRunner.Image

Optional SkiaSharp image helpers for JRK.JevRunner. Validate image files, proportionally upscale or downscale them, and render normalized Jev bounding-box answers without modifying source files.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0 75 10/19/2026
0.5.0-preview-02 69 10/19/2026
0.5.0-preview-01 77 10/18/2026
0.4.0 83 10/17/2026
0.3.1 89 10/11/2026
0.3.0 85 10/11/2026
0.3.0-preview-01 83 10/11/2026
0.2.0 91 10/10/2026
0.2.0-preview-01 92 10/10/2026
0.1.0 88 10/9/2026
0.0.3 88 10/9/2026
0.0.2 88 10/9/2026
0.0.1 86 10/9/2026

Experimental image support
- Added base64-encoded request assets with automatic TypeSafe-Danger-Zone-Images opt-in.
- AssetState and its asynchronous AddImageAsset helper load images through TruePath with optional injectable file access.
- ImageAsset.FromByteArray(name, format, bytes) uses standard padded base64 without line breaks or a data URI prefix. Callers must supply the matching format.

Bounding-box questions and answers
- BoxQuestionBuilder and BoxAnswer support normalized bounding boxes with confidence.
- The bundled source generator recognizes IBoxQuestionDefinition and GenericBoxQuestionDefinition and generates requests, typed answer methods, and response wrapper properties.
- Requests without an explicit model re-evaluate model selection before execution: JevModel.Preview for Box questions, including those added after construction, and JevModel.Latest otherwise. Explicit model choices are preserved.
- Added AnswerExtensions helpers for typed answer filtering: OnlyNoul(), OnlyChoice(), OnlyScoreAnswer(), and OnlyBoxAnswer().

Error handling and fixes
- API error translation preserves JSON or raw response content and retains the HTTP failure description in exception messages.
- Added known-error handling for HTTP 400, 413, 415, and 529 while retaining 509 support.
- Duplicate asset fields no longer replace registered assets before throwing.

Documentation and coverage
- Updated documentation and regression coverage for image requests, generated Box queries, and API errors.