← Public packages
@kentcdodds/jev
Call TypeSafe Jev evaluations via Cloudflare AI Gateway (same token/gateway as @kentcdodds/ai).
src/evaluate.ts
126 lines · 3.6 KB · TypeScript/**
* Evaluate shared state with TypeSafe Jev through Cloudflare AI Gateway.
* Use for typed noul / choice / score answers with calibrated probabilities.
*
* @param input.state - Shared state (string or object) to evaluate.
* @param input.questions - Named noul / choice / score questions.
* @param input.accountId - Optional Cloudflare account id override.
* @param input.gatewayId - Optional AI Gateway id override.
* @param input.model - Optional model id (default `typesafe/jev`).
* @returns Parsed `{ ok, answers, model, usage }` — never logs secrets.
*
* @example
* import evaluate from 'kody:@kentcdodds/jev/evaluate'
*
* const result = await evaluate({
* state: 'hello',
* questions: {
* greeting: { type: 'noul', instructions: 'Is this a greeting?' },
* },
* })
*/
import jevRequest from './request.ts'
import type {
Answer,
EvaluateInput,
EvaluateResult,
JevResult,
JevUsage,
} from './types.ts'
import { resolveJevConfig } from './resolve.ts'
function asRecord(value: unknown): Record<string, unknown> | null {
return value && typeof value === 'object' && !Array.isArray(value)
? (value as Record<string, unknown>)
: null
}
function unwrapJevPayload(data: unknown): JevResult | null {
const root = asRecord(data)
if (!root) return null
// Cloudflare envelope: { success, result: { model, answers, usage } }
const nested = asRecord(root.result)
const candidate = nested && (nested.answers || nested.model) ? nested : root
const answersRaw = asRecord(candidate.answers)
if (!answersRaw) return null
const answers: Record<string, Answer> = {}
for (const [key, value] of Object.entries(answersRaw)) {
const answer = asRecord(value)
if (!answer || typeof answer.type !== 'string') continue
answers[key] = answer as Answer
}
return {
model: typeof candidate.model === 'string' ? candidate.model : 'typesafe/jev',
answers,
usage: asRecord(candidate.usage) as JevUsage | undefined,
}
}
/**
* Evaluate shared state with TypeSafe Jev through Cloudflare AI Gateway.
* Use for typed noul / choice / score answers with calibrated probabilities.
*
* @param input.state - Shared state (string or object) to evaluate.
* @param input.questions - Named noul / choice / score questions.
* @param input.accountId - Optional Cloudflare account id override.
* @param input.gatewayId - Optional AI Gateway id override.
* @param input.model - Optional model id (default `typesafe/jev`).
* @returns Parsed `{ ok, answers, model, usage }` — never logs secrets.
*
* @example
* import evaluate from 'kody:@kentcdodds/jev/evaluate'
*
* const result = await evaluate({
* state: 'hello',
* questions: {
* greeting: { type: 'noul', instructions: 'Is this a greeting?' },
* },
* })
*/
export default async function evaluate(
input: EvaluateInput,
): Promise<EvaluateResult> {
const config = await resolveJevConfig({
accountId: input?.accountId,
gatewayId: input?.gatewayId,
model: input?.model,
})
const raw = await jevRequest(input)
if (!raw.ok) {
return {
ok: false,
status: raw.status,
error: raw.error || 'Jev request failed',
setup: raw.setup,
}
}
const parsed = unwrapJevPayload(raw.data)
if (!parsed || Object.keys(parsed.answers).length === 0) {
return {
ok: false,
status: raw.status,
error:
'Unexpected Jev response shape (missing answers). Use ./request for the raw envelope.',
}
}
return {
ok: true,
status: raw.status,
answers: parsed.answers,
model: parsed.model,
usage: parsed.usage,
meta: {
accountIdSet: Boolean(config.accountId),
gatewayId: config.gatewayId,
model: config.model,
},
}
}