Export template source from 9b2406f9e421926e4eda0380785418cbaae47f91:quixos-instance/packages/agent-runtime
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
import { QxReject, canonicalJson, type Json } from "@quixos/camino-replica-core";
|
||||
|
||||
export function boundedText(value: string, limit = 65536, allowEmpty = true): string {
|
||||
if (typeof value !== "string" || (!allowEmpty && !value.trim()) || new TextEncoder().encode(value).length > limit)
|
||||
throw new QxReject("AGENT_INVALID_TEXT");
|
||||
return value;
|
||||
}
|
||||
export function json(value: string, limit = 65536): Json {
|
||||
boundedText(value, limit, false);
|
||||
let parsed: Json;
|
||||
try {
|
||||
parsed = JSON.parse(value);
|
||||
} catch {
|
||||
throw new QxReject("AGENT_INVALID_JSON");
|
||||
}
|
||||
let nodes = 0;
|
||||
function walk(v: Json, depth: number) {
|
||||
if (++nodes > 8192 || depth > 24) throw new QxReject("AGENT_JSON_LIMIT");
|
||||
if (Array.isArray(v)) for (const child of v) walk(child, depth + 1);
|
||||
else if (v && typeof v === "object") for (const child of Object.values(v)) walk(child, depth + 1);
|
||||
}
|
||||
walk(parsed, 0);
|
||||
return parsed;
|
||||
}
|
||||
export function object(v: Json): Record<string, Json> {
|
||||
if (!v || Array.isArray(v) || typeof v !== "object") throw new QxReject("AGENT_EXPECTED_OBJECT");
|
||||
return v as Record<string, Json>;
|
||||
}
|
||||
export function timestamp(v: string) {
|
||||
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(v) || !Number.isFinite(Date.parse(v)))
|
||||
throw new QxReject("AGENT_INVALID_TIME");
|
||||
}
|
||||
const schemaKeys = new Set([
|
||||
"type",
|
||||
"properties",
|
||||
"required",
|
||||
"items",
|
||||
"enum",
|
||||
"additionalProperties",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"minItems",
|
||||
"maxItems",
|
||||
"description",
|
||||
"title",
|
||||
]);
|
||||
export function schema(v: Json, depth = 0, budget = { nodes: 0 }): void {
|
||||
if (++budget.nodes > 256 || depth > 12) throw new QxReject("AGENT_SCHEMA_LIMIT");
|
||||
const s = object(v);
|
||||
for (const k of Object.keys(s)) if (!schemaKeys.has(k)) throw new QxReject("AGENT_SCHEMA_UNSUPPORTED");
|
||||
if (!["string", "number", "integer", "boolean", "null", "array", "object"].includes(String(s.type)))
|
||||
throw new QxReject("AGENT_SCHEMA_TYPE");
|
||||
if (s.enum !== undefined && (!Array.isArray(s.enum) || !s.enum.length || s.enum.length > 128))
|
||||
throw new QxReject("AGENT_SCHEMA_ENUM");
|
||||
for (const k of ["minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems"]) {
|
||||
if (s[k] !== undefined && (typeof s[k] !== "number" || !Number.isFinite(s[k])))
|
||||
throw new QxReject("AGENT_SCHEMA_BOUND");
|
||||
if (
|
||||
k !== "minimum" &&
|
||||
k !== "maximum" &&
|
||||
s[k] !== undefined &&
|
||||
(Number(s[k]) < 0 || !Number.isInteger(s[k]) || Number(s[k]) > 65536)
|
||||
)
|
||||
throw new QxReject("AGENT_SCHEMA_BOUND");
|
||||
}
|
||||
for (const [min, max] of [
|
||||
["minimum", "maximum"],
|
||||
["minLength", "maxLength"],
|
||||
["minItems", "maxItems"],
|
||||
])
|
||||
if (s[min] !== undefined && s[max] !== undefined && Number(s[min]) > Number(s[max]))
|
||||
throw new QxReject("AGENT_SCHEMA_BOUND");
|
||||
if (s.type === "object") {
|
||||
const p = object(s.properties ?? {});
|
||||
if (Object.keys(p).length > 64) throw new QxReject("AGENT_SCHEMA_LIMIT");
|
||||
if (s.additionalProperties !== undefined && typeof s.additionalProperties !== "boolean")
|
||||
throw new QxReject("AGENT_SCHEMA_UNSUPPORTED");
|
||||
if (
|
||||
s.required !== undefined &&
|
||||
(!Array.isArray(s.required) ||
|
||||
s.required.some((k) => typeof k !== "string" || !Object.hasOwn(p, k)) ||
|
||||
new Set(s.required).size !== s.required.length)
|
||||
)
|
||||
throw new QxReject("AGENT_SCHEMA_REQUIRED");
|
||||
for (const child of Object.values(p)) schema(child, depth + 1, budget);
|
||||
}
|
||||
if (s.type === "array") schema(s.items ?? null, depth + 1, budget);
|
||||
}
|
||||
export function validateResult(supplied: Json, value: Json): void {
|
||||
schema(supplied);
|
||||
let nodes = 0;
|
||||
function check(shape: Json, v: Json, depth: number) {
|
||||
if (++nodes > 8192 || depth > 24) throw new QxReject("AGENT_RESULT_LIMIT");
|
||||
const s = object(shape),
|
||||
type = s.type;
|
||||
const actual = v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
|
||||
if (!(type === actual || (type === "integer" && typeof v === "number" && Number.isInteger(v))))
|
||||
throw new QxReject("AGENT_RESULT_TYPE");
|
||||
if (s.enum && !(s.enum as Json[]).some((e) => canonicalJson(e) === canonicalJson(v)))
|
||||
throw new QxReject("AGENT_RESULT_ENUM");
|
||||
if (
|
||||
typeof v === "number" &&
|
||||
((s.minimum !== undefined && v < Number(s.minimum)) || (s.maximum !== undefined && v > Number(s.maximum)))
|
||||
)
|
||||
throw new QxReject("AGENT_RESULT_BOUND");
|
||||
if (typeof v === "string") {
|
||||
const n = Array.from(v).length;
|
||||
if (n < Number(s.minLength ?? 0) || n > Number(s.maxLength ?? 65536)) throw new QxReject("AGENT_RESULT_BOUND");
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
if (v.length < Number(s.minItems ?? 0) || v.length > Number(s.maxItems ?? 8192))
|
||||
throw new QxReject("AGENT_RESULT_BOUND");
|
||||
for (const e of v) check(s.items, e, depth + 1);
|
||||
}
|
||||
if (type === "object") {
|
||||
const p = object(s.properties ?? {}),
|
||||
o = object(v);
|
||||
for (const k of (s.required ?? []) as string[])
|
||||
if (!Object.hasOwn(o, k)) throw new QxReject("AGENT_RESULT_REQUIRED");
|
||||
for (const [k, e] of Object.entries(o)) {
|
||||
if (Object.hasOwn(p, k)) check(p[k], e, depth + 1);
|
||||
else if (s.additionalProperties === false) throw new QxReject("AGENT_RESULT_PROPERTY");
|
||||
}
|
||||
}
|
||||
}
|
||||
check(supplied, value, 0);
|
||||
}
|
||||
export function settings(value: string): Record<string, Json> {
|
||||
const s = object(json(value));
|
||||
const keys = new Set([
|
||||
"profile",
|
||||
"response",
|
||||
"capabilities",
|
||||
"notifications",
|
||||
"retry",
|
||||
"limits",
|
||||
"interactive",
|
||||
"systemInstructions",
|
||||
]);
|
||||
for (const k of Object.keys(s)) if (!keys.has(k)) throw new QxReject("AGENT_SETTINGS_UNKNOWN");
|
||||
if (s.systemInstructions !== undefined) boundedText(s.systemInstructions as string);
|
||||
if (s.profile !== undefined) {
|
||||
if (typeof s.profile !== "string") throw new QxReject("AGENT_SETTINGS_TYPE");
|
||||
boundedText(s.profile, 80, false);
|
||||
}
|
||||
const response = object(s.response ?? { kind: "text" });
|
||||
for (const k of Object.keys(response))
|
||||
if (!["kind", "schema"].includes(k)) throw new QxReject("AGENT_RESPONSE_UNKNOWN");
|
||||
if (response.kind !== "json" && response.schema !== undefined) throw new QxReject("AGENT_UNEXPECTED_SCHEMA");
|
||||
if (!["text", "json", "unit"].includes(String(response.kind))) throw new QxReject("AGENT_RESPONSE_MODE");
|
||||
if (response.kind === "json") schema(response.schema ?? null);
|
||||
if (
|
||||
s.capabilities !== undefined &&
|
||||
(!Array.isArray(s.capabilities) ||
|
||||
s.capabilities.length > 6 ||
|
||||
new Set(s.capabilities).size !== s.capabilities.length ||
|
||||
s.capabilities.some(
|
||||
(c) => !["web-search", "graph-read", "graph-write", "eval", "source-read", "source-write"].includes(String(c)),
|
||||
))
|
||||
)
|
||||
throw new QxReject("AGENT_CAPABILITY");
|
||||
if (s.retry !== undefined && !["never", "inference-only"].includes(String(s.retry)))
|
||||
throw new QxReject("AGENT_RETRY");
|
||||
if (
|
||||
s.retry === "inference-only" &&
|
||||
Array.isArray(s.capabilities) &&
|
||||
s.capabilities.some((c) => ["graph-write", "eval", "source-write"].includes(String(c)))
|
||||
)
|
||||
throw new QxReject("AGENT_UNSAFE_RETRY");
|
||||
for (const k of ["interactive", "notifications"])
|
||||
if (s[k] !== undefined && typeof s[k] !== "boolean") throw new QxReject("AGENT_SETTINGS_TYPE");
|
||||
if (s.limits !== undefined) {
|
||||
const limits = object(s.limits);
|
||||
for (const [k, v] of Object.entries(limits))
|
||||
if (
|
||||
!Object.hasOwn({ toolSteps: 128, activeMs: 1800000, outputBytes: 262144, retries: 1 }, k) ||
|
||||
!Number.isInteger(v) ||
|
||||
Number(v) < 0 ||
|
||||
Number(v) >
|
||||
({ toolSteps: 128, activeMs: 1800000, outputBytes: 262144, retries: 1 } as Record<string, number>)[k]
|
||||
)
|
||||
throw new QxReject("AGENT_LIMIT");
|
||||
}
|
||||
return {
|
||||
profile: "balanced",
|
||||
response: { kind: "text" },
|
||||
capabilities: [],
|
||||
notifications: false,
|
||||
retry: "never",
|
||||
interactive: false,
|
||||
...s,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user