Studio previewSelf-host Studio locally.Run it

TypeSafe Jev through OpenRouter

Updated 3 hours ago • September 22, 2026

TypeSafe Jev is a structured decision model. It returns constrained choices, scores, and yes/no probabilities rather than generated prose.

Jev still uses the normal Kortyx model and hook shape:

const result = await useReason({ model: openrouter("typesafe/jev-1.13"), input: ticket, outputSchema: TicketDecision, });

The difference is TicketDecision: Jev needs native System One questions, so define it with jevOutputSchema(...) instead of a general-purpose Zod object.

1. Install and import

Jev support is included in @kortyx/openrouter:

pnpm add @kortyx/openrouter
import { jevOutputSchema, openrouter } from "@kortyx/openrouter"; import { useReason } from "kortyx";

It uses the same OPENROUTER_API_KEY or KORTYX_OPENROUTER_API_KEY credentials as other OpenRouter models.

2. Define native Jev questions

const TicketDecision = jevOutputSchema({ queue: { type: "choice", instructions: "Which team should handle this ticket?", criteria: { billing: "Invoices, charges, and refunds", technical: "Bugs, outages, and login failures", }, }, urgency: { type: "score", instructions: "How urgent is this ticket?", criteria: [ "Can wait", "Important but work can continue", "Customer is blocked", ], }, needsHuman: { type: "noul", instructions: "Does this require a human response?", criteria: { true: "A person must respond or make a judgment", false: "Documentation or an automated answer is sufficient", }, }, });

jevOutputSchema(...) is deliberately thin. Its question types come from the official OpenRouter SDK contract, and the returned value is a regular schema accepted by useReason({ outputSchema }).

3. Call Jev through useReason(...)

const result = await useReason({ id: "triage-ticket", model: openrouter("typesafe/jev-1.13"), input: ticket, outputSchema: TicketDecision, }); result.output?.queue; // "billing" | "technical" result.output?.urgency; // number from 0 to 2, including fractional values result.output?.needsHuman; // probability from 0 to 1

There is no separate openrouter.decide(...) API. Jev is selected through the same openrouter(modelId) helper used for chat models, and it participates in the normal Kortyx result, tracing, usage, and telemetry flow.

4. How question types map to output

Jev questionDefinitionresult.output
choicenamed criteria objectselected criterion key, inferred as a string union
scoreordered array of 2–10 levelsnumeric position from 0 to criteria.length - 1; may be fractional
noulyes/no instruction with optional criteriaprobability of yes from 0 to 1

noul is intentionally not converted to a boolean. Choose an application threshold based on the cost of false positives and false negatives:

const HUMAN_THRESHOLD = 0.8; const needsHuman = (result.output?.needsHuman ?? 0) >= HUMAN_THRESHOLD;

5. Native details and normalized result fields

The simplified typed decision lives in result.output. Additional native details remain available for decisions that need uncertainty handling:

result.output; result.usage; result.providerMetadata?.cost; result.providerMetadata?.answers; result.raw;

providerMetadata.answers retains native choice probabilities, score probabilities and legends, and confidence when returned. providerMetadata.cost retains OpenRouter's reported cost. result.usage contains normalized input, output, and total token counts.

Use result.output for ordinary application branching. Read probabilities and confidence when the application has an explicit uncertainty or review policy.

6. Unsupported fields fail or warn explicitly

Known hard incompatibilities fail before the network request:

  • tools are not supported by Jev
  • omitting jevOutputSchema(...) fails configuration validation
  • supplying an explicit responseFormat that replaces the helper metadata fails configuration validation

Do not add responseFormat manually:

// Correct: the helper provides the native wire contract. await useReason({ model: openrouter("typesafe/jev-1.13"), input: ticket, outputSchema: TicketDecision, });

Chat-only controls such as temperature, maxOutputTokens, stopSequences, and reasoning settings are not sent to System One. Kortyx reports them through result.warnings instead of silently pretending they changed the decision.

7. Streaming behavior

OpenRouter System One is non-streaming. If a caller requests a stream, Kortyx performs one decision request and emits the completed serialized output as one final result. Do not expect token-by-token text from Jev.

For normal use, omit stream and consume the typed result.output after the hook resolves.

8. OpenRouter options supported by Jev

These OpenRouter-native controls are forwarded to System One when provided:

  • provider
  • sessionId
  • trace
  • user
const result = await useReason({ model: openrouter("typesafe/jev-1.13"), input: ticket, outputSchema: TicketDecision, providerOptions: { openrouter: { sessionId: ticketId, user: accountId, provider: { zdr: true }, }, }, });

Other OpenRouter chat-only provider options produce an unsupported-option warning and are not included in the System One request.

9. Model ids

Use a current OpenRouter Jev model id such as:

  • typesafe/jev-1.13
  • ~typesafe/jev-latest
  • jev-latest

Namespaced ids make the upstream provider explicit. Alias availability can change, so use the model id chosen by the application rather than hard-coding a catalog in shared framework code.

Next steps

  • Return to the OpenRouter provider for chat models, routing, tools, and structured output
  • See Hooks for the complete useReason(...) contract
  • See Provider API for normalized result fields and warnings