@molecule/api-ai-decisions-jev
Provider bond · ai-decisions · API (Node) · v1.0.0 · Apache-2.0
Jev decisions provider for molecule.dev — typed choice/score/yes-no answers from TypeSafe's hosted Jev System One API
npm install @molecule/api-ai-decisions-jevnpm · Source on GitHub · Implements @molecule/api-ai-decisions
How it works
@molecule/api-ai-decisions-jev is a provider bond on the API (Node) side: it implements the ai-decisions core interface (@molecule/api-ai-decisions) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
import { setProvider, requireProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-jev'
setProvider(provider) // reads TYPESAFE_API_KEY on first use
const { answers } = await requireProvider().decide({
state: { from: 'ana@example.com', body: 'Your app deleted my notes!!' },
questions: {
severity: {
type: 'score',
instructions: 'How severe is the problem?',
criteria: ['cosmetic', 'annoying', 'blocking', 'data loss'],
},
churnRisk: { type: 'yesNo', instructions: 'The customer is likely to cancel.' },
},
})
answers.severity.level // 3
answers.churnRisk.probability // 0.81Works with: @molecule/api-ai-decisions, @molecule/api-secrets
Secrets: TYPESAFE_API_KEY
Reference
Jev decisions provider for molecule.dev — typed decisions from TypeSafe AI's hosted Jev "System One" model.
Jev answers choice, score and yes/no questions about text or JSON with a
probability distribution instead of generated text. This bond calls
POST https://api.typesafe.ai/v1/systemone with your TYPESAFE_API_KEY.
The open-weights Laya server speaks the same protocol, so
@molecule/api-ai-decisions-laya is a drop-in self-hosted swap.
Quick Start
import { setProvider, requireProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-jev'
setProvider(provider) // reads TYPESAFE_API_KEY on first use
const { answers } = await requireProvider().decide({
state: { from: 'ana@example.com', body: 'Your app deleted my notes!!' },
questions: {
severity: {
type: 'score',
instructions: 'How severe is the problem?',
criteria: ['cosmetic', 'annoying', 'blocking', 'data loss'],
},
churnRisk: { type: 'yesNo', instructions: 'The customer is likely to cancel.' },
},
})
answers.severity.level // 3
answers.churnRisk.probability // 0.81
Type
provider
Installation
npm install @molecule/api-ai-decisions-jev @molecule/api-ai-decisions @molecule/api-secrets
API
Interfaces
JevConfig
Configuration for the Jev decisions provider.
interface JevConfig {
/** TypeSafe API key. Defaults to the `TYPESAFE_API_KEY` env var (the name TypeSafe's own SDKs read). */
apiKey?: string
/**
* API base URL. Defaults to `TYPESAFE_BASE_URL`, then `https://api.typesafe.ai`.
* Point it at a gateway that relays `/v1/systemone` (or at a `laya-serve`
* host — same protocol) without changing code.
*/
baseUrl?: string
/** Default model id. Defaults to `'jev-latest'`. */
model?: string
}
PostOptions
Options for postSystemOne.
interface PostOptions {
/** Full endpoint URL. */
url: string
/** Bearer token, if any. */
apiKey?: string
/** Request body. */
body: Record<string, unknown>
/** Abort signal. */
signal?: AbortSignal
/** Label for error messages (`'Laya'`, `'Jev'`). */
label: string
/** Max retries on a retryable status (default 3). */
maxRetries?: number
}
WireAnswer
One answer as received on the wire (only the fields we read).
interface WireAnswer {
type?: string
choice?: string
score?: number
noul?: number
probabilities?: Record<string, number>
}
WireQuestion
One question as sent on the wire.
interface WireQuestion {
type: 'choice' | 'score' | 'noul'
instructions: string
criteria?: Record<string, string> | string[]
}
WireResponse
The response body (only the fields we read).
interface WireResponse {
model?: string
answers?: Record<string, WireAnswer>
usage?: { input_tokens?: number; output_tokens?: number }
}
Functions
createProvider(config)
Creates a Jev decisions provider.
function createProvider(config?: JevConfig): AIDecisionsProvider
config— API key, base URL and default model.
Returns: An AIDecisionsProvider backed by TypeSafe's Jev API.
fromWireAnswer(id, question, wire, minConfidence)
Converts one wire answer into the core's answer for question.
function fromWireAnswer(
id: string,
question: DecisionQuestion,
wire: WireAnswer | undefined,
minConfidence?: number,
): DecisionAnswer
id— The question id (for error messages).question— The question that was asked.wire— The wire answer.minConfidence— Optional low-confidence threshold.
Returns: The typed answer.
fromWireResponse(questions, body, minConfidence)
Converts a whole wire response into the core's result.
function fromWireResponse(questions: Q, body: WireResponse, minConfidence?: number): DecideResult<Q>
questions— The questions that were asked.body— The parsed response body.minConfidence— Optional low-confidence threshold.
Returns: The typed result.
postSystemOne(opts)
POSTs a /v1/systemone request with retry on 429/5xx-busy, honouring
Retry-After. Throws an Error carrying status on a non-2xx response.
function postSystemOne(opts: PostOptions): Promise<WireResponse>
opts— Request options.
Returns: The parsed response body.
toWireQuestions(questions)
Converts the core's questions into wire questions (yesNo → noul).
function toWireQuestions(questions: Record<string, DecisionQuestion>): Record<string, WireQuestion>
questions— The questions keyed by id.
Returns: The wire questions object.
Constants
aiDecisionsJevSecretDefinitions
Secret definitions required by the Jev decisions bond.
const aiDecisionsJevSecretDefinitions: SecretDefinition[]
DEFAULT_JEV_MODEL
TypeSafe's flagship model id.
const DEFAULT_JEV_MODEL: 'jev-latest'
DEFAULT_JEV_URL
TypeSafe's API host.
const DEFAULT_JEV_URL: 'https://api.typesafe.ai'
provider
The provider implementation — lazy, so env vars are read on first use.
const provider: AIDecisionsProvider
Core Interface
Implements @molecule/api-ai-decisions interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-jev'
export function setupAiDecisionsJev(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-ai-decisions^1.0.0@molecule/api-secrets^1.0.1
Environment Variables
TYPESAFE_API_KEY(required) — TypeSafe API key- Setup: Create an API key in your TypeSafe AI account; it is sent as a bearer token to the Jev API.
- Get it here: https://docs.typesafe.ai/api
- Example:
ts-...
Runtime Dependencies
-
@molecule/api-ai-decisions -
@molecule/api-secrets -
Config:
TYPESAFE_API_KEY(required — sent asAuthorization: Bearer …),TYPESAFE_BASE_URL(optional; a gateway, or alaya-servehost), andcreateProvider({ model })(default'jev-latest', or passmodelper call). -
Limits (TypeSafe docs): up to 255 options per
choice, 2–10 levels perscore. 429 (rate limit) and 529 (overloaded) are retried up to 3 times with backoff; 401 and 422 throw immediately with the API's message and astatusproperty. -
English-first. TypeSafe describes English as Jev's primary training language; for other languages evaluate carefully or use Laya's multilingual checkpoint.
-
Your data leaves your servers (TypeSafe processes the
state). For data that must stay in your environment, bond the Laya provider instead. -
confidencein the answers is the probability of the reported answer, NOT Jev's ownconfidencefield (see the core's remarks). -
Use the core's
setProvider, notbond('ai-decisions', …)directly.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Each flow that makes a decision (routing, triage, moderation, a guardrail) runs it from the real UI, and the answer DRIVES what happens next (the item lands in the chosen queue, the badge shows, the action is blocked) — not just printed.
- Both directions: a clearly-billing input routes to billing AND a clearly-technical one routes elsewhere. One label for every input is a broken integration.
- A low-confidence answer takes the app's fallback path (human review, "unsure" state) instead of being acted on.
- Provider errors (service down, bad key) show a visible, recoverable state — never a blank screen or an unhandled rejection.
- The call runs server-side: no provider request or key in the browser's Network tab.