mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-10-19 13:30:37 +00:00
feat(providers): add TypeSafe System One client for Jev routing (#15278)
Adds the non-chat `typesafe` credential provider, a bounded fail-open System One client (`evaluateSystemOneChoice`) and a key validator that probes System One. Credential + client only: no public /v1/systemone route, no chat models (#15276 prerequisite slice of #13987 Part 1). ⚠️ base-red inherited: #15306
This commit is contained in:
parent
e80dfd00ae
commit
4e348e8a45
6 changed files with 598 additions and 1 deletions
276
open-sse/services/typesafe/systemOne.ts
Normal file
276
open-sse/services/typesafe/systemOne.ts
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* TypeSafe System One client — typed evaluation against api.typesafe.ai.
|
||||
*
|
||||
* Used by the jev combo strategy (#15276) to ask Jev which configured model
|
||||
* should handle a request. Not a chat executor: System One answers noul /
|
||||
* choice / score questions and returns calibrated probabilities.
|
||||
*
|
||||
* Docs: https://docs.typesafe.ai/api
|
||||
*/
|
||||
|
||||
import { sanitizeErrorMessage } from "../../utils/error.ts";
|
||||
|
||||
export const TYPESAFE_PROVIDER_ID = "typesafe";
|
||||
export const TYPESAFE_SYSTEM_ONE_BASE_URL = "https://api.typesafe.ai";
|
||||
export const TYPESAFE_SYSTEM_ONE_PATH = "/v1/systemone";
|
||||
export const TYPESAFE_DEFAULT_MODEL = "jev-latest";
|
||||
|
||||
/** Hard ceiling so a hung TypeSafe call cannot stall combo dispatch. */
|
||||
export const TYPESAFE_SYSTEM_ONE_TIMEOUT_MS = 8_000;
|
||||
/** TypeSafe choice questions accept at most 255 options. */
|
||||
export const TYPESAFE_MAX_CHOICE_OPTIONS = 255;
|
||||
/** Cap on `state` so a long transcript cannot be forwarded wholesale. */
|
||||
export const TYPESAFE_MAX_STATE_CHARS = 8_000;
|
||||
|
||||
type FetchLike = typeof fetch;
|
||||
|
||||
export type SystemOneChoiceCriteria = Readonly<Record<string, string | null>>;
|
||||
|
||||
export type SystemOneChoiceSuccess = {
|
||||
ok: true;
|
||||
choice: string;
|
||||
probabilities: Record<string, number>;
|
||||
confidence: number;
|
||||
model: string | null;
|
||||
inputTokens: number | null;
|
||||
outputTokens: number | null;
|
||||
};
|
||||
|
||||
export type SystemOneUnavailableReason =
|
||||
| "missing_api_key"
|
||||
| "empty_criteria"
|
||||
| "timeout"
|
||||
| "http_error"
|
||||
| "invalid_response"
|
||||
| "network_error";
|
||||
|
||||
export type SystemOneChoiceUnavailable = {
|
||||
ok: false;
|
||||
reason: SystemOneUnavailableReason;
|
||||
status: number | null;
|
||||
/** Sanitized, safe to log or put in a decision-trace detail field. */
|
||||
detail: string;
|
||||
};
|
||||
|
||||
export type SystemOneChoiceResult = SystemOneChoiceSuccess | SystemOneChoiceUnavailable;
|
||||
|
||||
export type EvaluateSystemOneChoiceOptions = {
|
||||
apiKey: string | null | undefined;
|
||||
state: string;
|
||||
criteria: SystemOneChoiceCriteria;
|
||||
/** Defaults to a routing-oriented instruction. */
|
||||
instructions?: string;
|
||||
model?: string;
|
||||
timeoutMs?: number;
|
||||
fetchImpl?: FetchLike;
|
||||
baseUrl?: string;
|
||||
/** Aborted when the caller cancels; combined with the timeout. */
|
||||
signal?: AbortSignal | null;
|
||||
};
|
||||
|
||||
const DEFAULT_INSTRUCTIONS =
|
||||
"Which of these configured models is the most appropriate for this request?";
|
||||
|
||||
function unavailable(
|
||||
reason: SystemOneUnavailableReason,
|
||||
detail: string,
|
||||
status: number | null = null
|
||||
): SystemOneChoiceUnavailable {
|
||||
return {
|
||||
ok: false,
|
||||
reason,
|
||||
status,
|
||||
detail: sanitizeErrorMessage(detail) || reason,
|
||||
};
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function toFiniteNumber(value: unknown): number | null {
|
||||
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
||||
return Number.isFinite(n) ? n : null;
|
||||
}
|
||||
|
||||
function parseProbabilities(value: unknown): Record<string, number> | null {
|
||||
if (!isRecord(value)) return null;
|
||||
const out: Record<string, number> = {};
|
||||
for (const [key, raw] of Object.entries(value)) {
|
||||
const n = toFiniteNumber(raw);
|
||||
if (n === null || n < 0) return null;
|
||||
out[key] = n;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a System One response body into a choice result. Pure — no I/O.
|
||||
* Exported for unit tests.
|
||||
*/
|
||||
export function parseSystemOneChoiceResponse(body: unknown): SystemOneChoiceResult {
|
||||
if (!isRecord(body)) {
|
||||
return unavailable("invalid_response", "System One response was not a JSON object");
|
||||
}
|
||||
|
||||
const answers = body.answers;
|
||||
if (!isRecord(answers)) {
|
||||
return unavailable("invalid_response", "System One response missing answers");
|
||||
}
|
||||
|
||||
const route = answers.route;
|
||||
if (!isRecord(route)) {
|
||||
return unavailable("invalid_response", "System One response missing answers.route");
|
||||
}
|
||||
|
||||
if (route.type !== "choice") {
|
||||
return unavailable(
|
||||
"invalid_response",
|
||||
`System One answers.route.type was ${String(route.type)}, expected choice`
|
||||
);
|
||||
}
|
||||
|
||||
const choice = typeof route.choice === "string" ? route.choice.trim() : "";
|
||||
if (!choice) {
|
||||
return unavailable("invalid_response", "System One choice answer had an empty choice");
|
||||
}
|
||||
|
||||
const probabilities = parseProbabilities(route.probabilities);
|
||||
if (!probabilities) {
|
||||
return unavailable("invalid_response", "System One choice answer missing probabilities");
|
||||
}
|
||||
|
||||
const confidence = toFiniteNumber(route.confidence);
|
||||
if (confidence === null || confidence < 0 || confidence > 1) {
|
||||
return unavailable("invalid_response", "System One choice answer missing confidence");
|
||||
}
|
||||
|
||||
const usage = isRecord(body.usage) ? body.usage : null;
|
||||
return {
|
||||
ok: true,
|
||||
choice,
|
||||
probabilities,
|
||||
confidence,
|
||||
model: typeof body.model === "string" ? body.model : null,
|
||||
inputTokens: usage ? toFiniteNumber(usage.input_tokens) : null,
|
||||
outputTokens: usage ? toFiniteNumber(usage.output_tokens) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask System One (Jev) to pick one option from `criteria`.
|
||||
*
|
||||
* Never throws. Missing key, timeout, HTTP errors, and malformed bodies all
|
||||
* return `{ ok: false, reason, ... }` so combo dispatch can fall open.
|
||||
*/
|
||||
export async function evaluateSystemOneChoice(
|
||||
options: EvaluateSystemOneChoiceOptions
|
||||
): Promise<SystemOneChoiceResult> {
|
||||
const apiKey = typeof options.apiKey === "string" ? options.apiKey.trim() : "";
|
||||
if (!apiKey) {
|
||||
return unavailable("missing_api_key", "No TypeSafe API key configured");
|
||||
}
|
||||
|
||||
const criteriaEntries = Object.entries(options.criteria || {}).filter(
|
||||
([key]) => typeof key === "string" && key.trim().length > 0
|
||||
);
|
||||
if (criteriaEntries.length === 0) {
|
||||
return unavailable("empty_criteria", "System One choice requires at least one criterion");
|
||||
}
|
||||
if (criteriaEntries.length > TYPESAFE_MAX_CHOICE_OPTIONS) {
|
||||
return unavailable(
|
||||
"invalid_response",
|
||||
`System One choice accepts at most ${TYPESAFE_MAX_CHOICE_OPTIONS} options`
|
||||
);
|
||||
}
|
||||
|
||||
const criteria: Record<string, string | null> = {};
|
||||
for (const [key, value] of criteriaEntries) {
|
||||
criteria[key.trim()] =
|
||||
typeof value === "string"
|
||||
? value
|
||||
: value === null || value === undefined
|
||||
? null
|
||||
: String(value);
|
||||
}
|
||||
|
||||
const rawState = typeof options.state === "string" ? options.state : "";
|
||||
const state =
|
||||
rawState.length > TYPESAFE_MAX_STATE_CHARS
|
||||
? rawState.slice(0, TYPESAFE_MAX_STATE_CHARS)
|
||||
: rawState;
|
||||
const instructions =
|
||||
typeof options.instructions === "string" && options.instructions.trim().length > 0
|
||||
? options.instructions.trim()
|
||||
: DEFAULT_INSTRUCTIONS;
|
||||
const model =
|
||||
typeof options.model === "string" && options.model.trim().length > 0
|
||||
? options.model.trim()
|
||||
: TYPESAFE_DEFAULT_MODEL;
|
||||
const timeoutMs =
|
||||
typeof options.timeoutMs === "number" &&
|
||||
Number.isFinite(options.timeoutMs) &&
|
||||
options.timeoutMs > 0
|
||||
? options.timeoutMs
|
||||
: TYPESAFE_SYSTEM_ONE_TIMEOUT_MS;
|
||||
const baseUrl = (options.baseUrl || TYPESAFE_SYSTEM_ONE_BASE_URL).replace(/\/+$/, "");
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
|
||||
if (options.signal?.aborted) {
|
||||
return unavailable("timeout", "System One call aborted before it started");
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const onAbort = () => controller.abort();
|
||||
options.signal?.addEventListener("abort", onAbort, { once: true });
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetchImpl(`${baseUrl}${TYPESAFE_SYSTEM_ONE_PATH}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
state,
|
||||
questions: {
|
||||
route: {
|
||||
type: "choice",
|
||||
instructions,
|
||||
criteria,
|
||||
},
|
||||
},
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Status only. Upstream bodies can echo the state or the key.
|
||||
return unavailable(
|
||||
"http_error",
|
||||
`System One returned HTTP ${response.status}`,
|
||||
response.status
|
||||
);
|
||||
}
|
||||
|
||||
let json: unknown;
|
||||
try {
|
||||
json = await response.json();
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "invalid JSON";
|
||||
return unavailable("invalid_response", `System One response was not JSON: ${message}`);
|
||||
}
|
||||
|
||||
return parseSystemOneChoiceResponse(json);
|
||||
} catch (err) {
|
||||
if (err instanceof Error && err.name === "AbortError") {
|
||||
return unavailable("timeout", `System One timed out after ${timeoutMs}ms`);
|
||||
}
|
||||
return unavailable("network_error", "System One request failed");
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
options.signal?.removeEventListener("abort", onAbort);
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@ import {
|
|||
validateNousResearchProvider,
|
||||
validatePoeProvider,
|
||||
} from "./validation/audioMiscProviders";
|
||||
import { validateTypesafeProvider } from "./validation/typesafe";
|
||||
import { validateZaiWebProvider } from "./validation/zaiWeb";
|
||||
import { validateSearchProvider, SEARCH_VALIDATOR_CONFIGS } from "./validation/searchProviders";
|
||||
import {
|
||||
|
|
@ -326,6 +327,7 @@ export async function validateProviderApiKey({
|
|||
nlpcloud: validateNlpCloudProvider,
|
||||
oneminai: validateOneMinAiProvider,
|
||||
runwayml: validateRunwayProvider,
|
||||
typesafe: ({ apiKey }: any) => validateTypesafeProvider({ apiKey }),
|
||||
snowflake: validateSnowflakeProvider,
|
||||
gigachat: validateGigachatProvider,
|
||||
"deepseek-web": validateDeepSeekWebProvider,
|
||||
|
|
|
|||
67
src/lib/providers/validation/typesafe.ts
Normal file
67
src/lib/providers/validation/typesafe.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* TypeSafe AI (System One / Jev) API-key validation.
|
||||
*
|
||||
* Probes POST /v1/systemone with a tiny choice question. A 2xx with a parseable
|
||||
* choice means the key works; 401/403 means invalid; everything else is treated
|
||||
* as unavailable (credentials may still be fine).
|
||||
*/
|
||||
|
||||
import {
|
||||
evaluateSystemOneChoice,
|
||||
type SystemOneChoiceUnavailable,
|
||||
} from "@omniroute/open-sse/services/typesafe/systemOne.ts";
|
||||
|
||||
export async function validateTypesafeProvider({
|
||||
apiKey,
|
||||
fetchImpl,
|
||||
}: {
|
||||
apiKey?: string | null;
|
||||
fetchImpl?: typeof fetch;
|
||||
}) {
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey,
|
||||
state: "ping",
|
||||
instructions: "Pick any option.",
|
||||
criteria: {
|
||||
ok: "A valid routing destination",
|
||||
other: "Any other destination",
|
||||
},
|
||||
timeoutMs: 10_000,
|
||||
fetchImpl,
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
return { valid: true, error: null, method: "typesafe_systemone" };
|
||||
}
|
||||
// `strict: false` does not narrow the union by the boolean `ok`; past the early return it is the failure arm.
|
||||
const failure = result as SystemOneChoiceUnavailable;
|
||||
|
||||
if (failure.reason === "missing_api_key") {
|
||||
return { valid: false, error: "API key is required" };
|
||||
}
|
||||
|
||||
if (failure.reason === "http_error" && (failure.status === 401 || failure.status === 403)) {
|
||||
return { valid: false, error: "Invalid API key" };
|
||||
}
|
||||
|
||||
if (failure.reason === "http_error" && failure.status === 429) {
|
||||
return {
|
||||
valid: true,
|
||||
error: null,
|
||||
method: "typesafe_systemone",
|
||||
warning: "Rate limited, but credentials appear valid",
|
||||
};
|
||||
}
|
||||
|
||||
if (failure.reason === "timeout" || failure.reason === "network_error") {
|
||||
return {
|
||||
valid: false,
|
||||
error: `TypeSafe unreachable (${failure.reason})`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
error: failure.detail || `TypeSafe validation failed (${failure.reason})`,
|
||||
};
|
||||
}
|
||||
|
|
@ -166,6 +166,23 @@ export const APIKEY_PROVIDERS_SPECIALTY = {
|
|||
hasFree: true,
|
||||
freeNote: "10M free tokens on signup (non-commercial), no credit card required",
|
||||
},
|
||||
// Decision / evaluation API (System One / Jev). Not a chat provider — no
|
||||
// chat models in the registry. Used by the jev combo strategy (#15276) and
|
||||
// by any future System One passthrough (#13987).
|
||||
typesafe: {
|
||||
id: "typesafe",
|
||||
serviceKinds: [],
|
||||
alias: "typesafe",
|
||||
name: "TypeSafe AI",
|
||||
icon: "psychology",
|
||||
color: "#4F46E5",
|
||||
textIcon: "TS",
|
||||
website: "https://docs.typesafe.ai",
|
||||
authHint:
|
||||
"Bearer API key for api.typesafe.ai. OmniRoute uses it for System One evaluation (Jev) — not chat completions. Store the key here; the jev combo strategy reads it at request time.",
|
||||
apiHint:
|
||||
"TypeSafe System One answers typed questions (noul / choice / score) against a state. OmniRoute does not expose TypeSafe models in chat pickers or combos.",
|
||||
},
|
||||
"fal-ai": {
|
||||
id: "fal-ai",
|
||||
serviceKinds: [],
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
// Unifically (gateways, #14182) adds one apikey entry — 244.
|
||||
// BeatAPI (#14875) extracts one aggregator gateway into apikey/beatapi.ts and spreads it
|
||||
// from the barrel — 245.
|
||||
// TypeSafe System One (#15278, non-chat `typesafe` credential provider) adds one specialty-media entry — 249.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ const P = await import("../../src/shared/constants/providers.ts");
|
|||
|
||||
// Apmix (#14821) adds one apikey/regional entry — 246.
|
||||
// Token Market (#13191) adds one apikey/gateways entry — measured 248 on the #13191 branch.
|
||||
const APIKEY_PROVIDER_COUNT = 248;
|
||||
const APIKEY_PROVIDER_COUNT = 249;
|
||||
|
||||
test("barrel still exports every catalog + key helpers", () => {
|
||||
for (const name of [
|
||||
|
|
|
|||
234
tests/unit/typesafe-systemone.test.ts
Normal file
234
tests/unit/typesafe-systemone.test.ts
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* TypeSafe System One client + provider registration tests (#15276 PR 1).
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
evaluateSystemOneChoice,
|
||||
parseSystemOneChoiceResponse,
|
||||
TYPESAFE_MAX_CHOICE_OPTIONS,
|
||||
TYPESAFE_MAX_STATE_CHARS,
|
||||
TYPESAFE_PROVIDER_ID,
|
||||
TYPESAFE_SYSTEM_ONE_PATH,
|
||||
} from "../../open-sse/services/typesafe/systemOne.ts";
|
||||
import { APIKEY_PROVIDERS } from "../../src/shared/constants/providers.ts";
|
||||
import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
|
||||
import { getStaticModelsForProvider } from "../../src/lib/providers/staticModels.ts";
|
||||
import { getProviderModels } from "../../open-sse/config/providerModels.ts";
|
||||
import { filterChatSelectableModels } from "../../open-sse/services/modelEndpointPolicy.ts";
|
||||
import { validateTypesafeProvider } from "../../src/lib/providers/validation/typesafe.ts";
|
||||
|
||||
test("typesafe is registered as an API-key specialty provider with no chat serviceKinds", () => {
|
||||
const entry = APIKEY_PROVIDERS.typesafe;
|
||||
assert.ok(entry, "typesafe must exist in APIKEY_PROVIDERS");
|
||||
assert.equal(entry.id, TYPESAFE_PROVIDER_ID);
|
||||
assert.deepEqual(entry.serviceKinds, []);
|
||||
assert.match(String(entry.authHint || ""), /Bearer/i);
|
||||
});
|
||||
|
||||
test("typesafe has no chat registry models and no static catalog", () => {
|
||||
assert.equal(getRegistryEntry(TYPESAFE_PROVIDER_ID), null);
|
||||
assert.equal(getStaticModelsForProvider(TYPESAFE_PROVIDER_ID), undefined);
|
||||
assert.deepEqual(getProviderModels(TYPESAFE_PROVIDER_ID), []);
|
||||
});
|
||||
|
||||
test("typesafe models are absent from chat-selectable filtering", () => {
|
||||
// Even if a stray model row were imported, empty serviceKinds + no registry
|
||||
// means operators should never see typesafe in combo chat pickers. An empty
|
||||
// catalog filters to empty.
|
||||
const filtered = filterChatSelectableModels(TYPESAFE_PROVIDER_ID, []);
|
||||
assert.deepEqual(filtered, []);
|
||||
});
|
||||
|
||||
test("parseSystemOneChoiceResponse accepts a valid choice answer", () => {
|
||||
const result = parseSystemOneChoiceResponse({
|
||||
model: "jev-1.13.0",
|
||||
answers: {
|
||||
route: {
|
||||
type: "choice",
|
||||
choice: "step-a",
|
||||
probabilities: { "step-a": 0.72, "step-b": 0.2, "step-c": 0.08 },
|
||||
confidence: 0.84,
|
||||
},
|
||||
},
|
||||
usage: { input_tokens: 120, output_tokens: 18 },
|
||||
});
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
assert.equal(result.choice, "step-a");
|
||||
assert.equal(result.confidence, 0.84);
|
||||
assert.equal(result.probabilities["step-a"], 0.72);
|
||||
assert.equal(result.model, "jev-1.13.0");
|
||||
assert.equal(result.inputTokens, 120);
|
||||
assert.equal(result.outputTokens, 18);
|
||||
});
|
||||
|
||||
test("parseSystemOneChoiceResponse rejects a malformed body", () => {
|
||||
const result = parseSystemOneChoiceResponse({ answers: {} });
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.equal(result.reason, "invalid_response");
|
||||
});
|
||||
|
||||
test("evaluateSystemOneChoice returns missing_api_key without calling fetch", async () => {
|
||||
let called = false;
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey: " ",
|
||||
state: "hi",
|
||||
criteria: { a: "A" },
|
||||
fetchImpl: async () => {
|
||||
called = true;
|
||||
return new Response("{}", { status: 200 });
|
||||
},
|
||||
});
|
||||
assert.equal(called, false);
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.equal(result.reason, "missing_api_key");
|
||||
});
|
||||
|
||||
test("evaluateSystemOneChoice posts a choice question and parses the answer", async () => {
|
||||
const calls: Array<{ url: string; init: RequestInit }> = [];
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey: "ts_test_key",
|
||||
state: "rewrite this email",
|
||||
criteria: { opus: "Claude Opus", glm: "GLM" },
|
||||
fetchImpl: async (url, init) => {
|
||||
calls.push({ url: String(url), init: init || {} });
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
model: "jev-1.13.0",
|
||||
answers: {
|
||||
route: {
|
||||
type: "choice",
|
||||
choice: "glm",
|
||||
probabilities: { opus: 0.1, glm: 0.9 },
|
||||
confidence: 0.91,
|
||||
},
|
||||
},
|
||||
usage: { input_tokens: 40, output_tokens: 10 },
|
||||
}),
|
||||
{ status: 200, headers: { "Content-Type": "application/json" } }
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(calls.length, 1);
|
||||
assert.ok(calls[0].url.endsWith(TYPESAFE_SYSTEM_ONE_PATH));
|
||||
const headers = calls[0].init.headers as Record<string, string>;
|
||||
assert.equal(headers.Authorization, "Bearer ts_test_key");
|
||||
const body = JSON.parse(String(calls[0].init.body));
|
||||
assert.equal(body.model, "jev-latest");
|
||||
assert.equal(body.state, "rewrite this email");
|
||||
assert.equal(body.questions.route.type, "choice");
|
||||
assert.deepEqual(body.questions.route.criteria, { opus: "Claude Opus", glm: "GLM" });
|
||||
|
||||
assert.equal(result.ok, true);
|
||||
if (!result.ok) return;
|
||||
assert.equal(result.choice, "glm");
|
||||
assert.equal(result.confidence, 0.91);
|
||||
});
|
||||
|
||||
test("evaluateSystemOneChoice maps HTTP 401 to unavailable without throwing", async () => {
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey: "bad",
|
||||
state: "hi",
|
||||
criteria: { a: "A" },
|
||||
fetchImpl: async () => new Response(JSON.stringify({ error: "unauthorized" }), { status: 401 }),
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.equal(result.reason, "http_error");
|
||||
assert.equal(result.status, 401);
|
||||
assert.equal(result.detail, "System One returned HTTP 401");
|
||||
assert.doesNotMatch(result.detail, /unauthorized|Bearer |sk-|ts_test/);
|
||||
});
|
||||
|
||||
test("evaluateSystemOneChoice maps AbortError to timeout without throwing", async () => {
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey: "key",
|
||||
state: "hi",
|
||||
criteria: { a: "A" },
|
||||
timeoutMs: 5,
|
||||
fetchImpl: async (_url, init) => {
|
||||
const signal = init?.signal;
|
||||
return await new Promise((_resolve, reject) => {
|
||||
const onAbort = () => {
|
||||
const err = new Error("aborted");
|
||||
err.name = "AbortError";
|
||||
reject(err);
|
||||
};
|
||||
if (signal?.aborted) onAbort();
|
||||
else signal?.addEventListener("abort", onAbort, { once: true });
|
||||
});
|
||||
},
|
||||
});
|
||||
assert.equal(result.ok, false);
|
||||
if (result.ok) return;
|
||||
assert.equal(result.reason, "timeout");
|
||||
});
|
||||
|
||||
test("evaluateSystemOneChoice truncates state and refuses more than 255 options", async () => {
|
||||
let posted = "";
|
||||
const long = "x".repeat(TYPESAFE_MAX_STATE_CHARS + 50);
|
||||
const result = await evaluateSystemOneChoice({
|
||||
apiKey: "key",
|
||||
state: long,
|
||||
criteria: { a: "A" },
|
||||
fetchImpl: async (_url, init) => {
|
||||
posted = String(init?.body);
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
answers: {
|
||||
route: { type: "choice", choice: "a", probabilities: { a: 1 }, confidence: 1 },
|
||||
},
|
||||
}),
|
||||
{ status: 200 }
|
||||
);
|
||||
},
|
||||
});
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(JSON.parse(posted).state.length, TYPESAFE_MAX_STATE_CHARS);
|
||||
|
||||
let called = false;
|
||||
const criteria: Record<string, string> = {};
|
||||
for (let i = 0; i < TYPESAFE_MAX_CHOICE_OPTIONS + 1; i++) criteria[`k${i}`] = `L${i}`;
|
||||
const tooMany = await evaluateSystemOneChoice({
|
||||
apiKey: "key",
|
||||
state: "hi",
|
||||
criteria,
|
||||
fetchImpl: async () => {
|
||||
called = true;
|
||||
return new Response("no", { status: 500 });
|
||||
},
|
||||
});
|
||||
assert.equal(called, false);
|
||||
assert.equal(tooMany.ok, false);
|
||||
});
|
||||
|
||||
test("validateTypesafeProvider maps a probe without sending the key to the error", async () => {
|
||||
const valid = await validateTypesafeProvider({
|
||||
apiKey: "ts_live_key",
|
||||
fetchImpl: async () =>
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
answers: {
|
||||
route: { type: "choice", choice: "ok", probabilities: { ok: 1 }, confidence: 1 },
|
||||
},
|
||||
}),
|
||||
{ status: 200 }
|
||||
),
|
||||
});
|
||||
assert.equal(valid.valid, true);
|
||||
|
||||
const invalid = await validateTypesafeProvider({
|
||||
apiKey: "ts_live_key",
|
||||
fetchImpl: async () =>
|
||||
new Response(JSON.stringify({ error: "nope ts_live_key" }), { status: 401 }),
|
||||
});
|
||||
assert.equal(invalid.valid, false);
|
||||
assert.equal(invalid.error, "Invalid API key");
|
||||
assert.doesNotMatch(JSON.stringify(invalid), /ts_live_key/);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue