Files
Elydora-Node-SDK/src/plugins/letta-contract.ts
T
雷电芽衣 3b5cbd6e17 chore(node-sdk): audit providers kimi grok qwen letta
Command files use shell-command and managedScriptReference; runtime checks use managed-runtime-status; jsonc-edit.ts and parseCommentedJsonObject replace copied JSONC helpers.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
2026-09-02 22:07:30 -04:00

281 lines
8.5 KiB
TypeScript

import { sameAgentId, samePath } from './common.js';
import {
buildLettaCommand,
lettaLegacyRuntimeReference,
lettaRuntimeReference,
type LettaRuntimeReference,
} from './letta-command.js';
import { isObject, type JsonObject } from './strict-json.js';
export const AGENT_KEY = 'letta';
export const GUARD_SCRIPT = 'guard.js';
export const AUDIT_SCRIPT = 'hook.js';
export const HOOK_TIMEOUT_MS = 10_000;
export const LETTA_AUDIT_OPTIONS = {
nativePayload: true,
failClosed: true,
} as const;
export const MANAGED_EVENTS = [
'PreToolUse',
'PostToolUse',
'PostToolUseFailure',
] as const;
const TOOL_EVENTS = new Set([
'PreToolUse',
'PostToolUse',
'PostToolUseFailure',
'PermissionRequest',
]);
const SIMPLE_EVENTS = new Set([
'UserPromptSubmit',
'Notification',
'Stop',
'SubagentStop',
'PreCompact',
'SessionStart',
'SessionEnd',
]);
const KNOWN_EVENTS = new Set([...TOOL_EVENTS, ...SIMPLE_EVENTS]);
export type ManagedLettaEvent = (typeof MANAGED_EVENTS)[number];
export interface LettaHandler extends JsonObject {
readonly type: 'command' | 'prompt';
readonly command?: string;
readonly prompt?: string;
readonly model?: string;
readonly timeout?: number;
readonly quiet?: boolean;
}
export interface LettaGroup extends JsonObject {
readonly matcher?: string;
readonly hooks: LettaHandler[];
}
export interface LettaHooks extends JsonObject {
readonly disabled?: boolean;
readonly [event: string]: LettaGroup[] | boolean | undefined;
}
export interface LettaRuntimeContract {
readonly agentId: string;
readonly guardPath: string;
readonly auditPath: string;
}
export interface ManagedLettaRemoval {
readonly event: ManagedLettaEvent;
readonly groupIndex: number;
readonly handlerIndexes: readonly number[];
readonly removeGroup: boolean;
}
function validateTimeout(value: unknown, label: string): void {
if (value === undefined) return;
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
throw new Error(`${label} timeout must be a non-negative finite number`);
}
}
function validateHandler(value: unknown, label: string): LettaHandler {
if (!isObject(value)) throw new Error(`${label} must be an object`);
if (value.type !== 'command' && value.type !== 'prompt') {
throw new Error(`${label} has unsupported type "${String(value.type)}"`);
}
validateTimeout(value.timeout, label);
if (value.quiet !== undefined && typeof value.quiet !== 'boolean') {
throw new Error(`${label} quiet must be a boolean`);
}
if (value.type === 'command') {
if (typeof value.command !== 'string' || value.command.length === 0) {
throw new Error(`${label} requires a non-empty command`);
}
} else {
if (typeof value.prompt !== 'string' || value.prompt.length === 0) {
throw new Error(`${label} requires a non-empty prompt`);
}
if (value.model !== undefined && typeof value.model !== 'string') {
throw new Error(`${label} model must be a string`);
}
}
return value as LettaHandler;
}
function validateGroup(
value: unknown,
event: string,
index: number,
): LettaGroup {
const label = `Letta Code settings group hooks.${event}[${index}]`;
if (!isObject(value)) throw new Error(`${label} must be an object`);
if (TOOL_EVENTS.has(event)) {
if (typeof value.matcher !== 'string') {
throw new Error(`${label} matcher must be a string`);
}
} else if (value.matcher !== undefined) {
throw new Error(`${label} matcher is unsupported for ${event}`);
}
if (!Array.isArray(value.hooks)) throw new Error(`${label} must contain a hooks array`);
return {
...value,
hooks: value.hooks.map((handler, handlerIndex) => (
validateHandler(handler, `${label}.hooks[${handlerIndex}]`)
)),
} as LettaGroup;
}
export function readLettaHooks(value: unknown): LettaHooks {
if (value === undefined) return {};
if (!isObject(value)) throw new Error('Letta Code settings field "hooks" must be an object');
if (value.disabled !== undefined && typeof value.disabled !== 'boolean') {
throw new Error('Letta Code settings field "hooks.disabled" must be a boolean');
}
const hooks: JsonObject = {};
for (const [event, groups] of Object.entries(value)) {
if (event === 'disabled') {
hooks.disabled = groups;
continue;
}
if (!KNOWN_EVENTS.has(event)) {
hooks[event] = groups;
continue;
}
if (!Array.isArray(groups)) {
throw new Error(`Letta Code settings field "hooks.${event}" must be an array`);
}
hooks[event] = groups.map((group, index) => validateGroup(group, event, index));
}
return hooks as LettaHooks;
}
export function buildLettaGroup(scriptPath: string): LettaGroup {
return {
matcher: '*',
hooks: [{
type: 'command',
command: buildLettaCommand(scriptPath),
timeout: HOOK_TIMEOUT_MS,
}],
};
}
function exactGroup(group: LettaGroup): boolean {
return Object.keys(group).sort().join('|') === 'hooks|matcher'
&& group.matcher === '*';
}
function exactCurrentReference(
handler: LettaHandler,
scriptName: string,
): LettaRuntimeReference | undefined {
return Object.keys(handler).sort().join('|') === 'command|timeout|type'
&& handler.type === 'command'
&& handler.timeout === HOOK_TIMEOUT_MS
&& typeof handler.command === 'string'
? lettaRuntimeReference(handler.command, scriptName)
: undefined;
}
function exactLegacyReference(
handler: LettaHandler,
scriptName: string,
): LettaRuntimeReference | undefined {
return Object.keys(handler).sort().join('|') === 'command|type'
&& handler.type === 'command'
&& typeof handler.command === 'string'
? lettaLegacyRuntimeReference(handler.command, scriptName)
: undefined;
}
function managedReference(
handler: LettaHandler,
scriptName: string,
): LettaRuntimeReference | undefined {
return exactCurrentReference(handler, scriptName)
?? exactLegacyReference(handler, scriptName);
}
const EVENT_CONTRACTS = [
['PreToolUse', GUARD_SCRIPT],
['PostToolUse', AUDIT_SCRIPT],
['PostToolUseFailure', AUDIT_SCRIPT],
] as const;
function eventGroups(hooks: LettaHooks, event: ManagedLettaEvent): LettaGroup[] {
const value = hooks[event];
return Array.isArray(value) ? value : [];
}
export function managedLettaRemovals(
hooks: LettaHooks,
agentId?: string,
): ManagedLettaRemoval[] {
const removals: ManagedLettaRemoval[] = [];
for (const [event, scriptName] of EVENT_CONTRACTS) {
eventGroups(hooks, event).forEach((group, groupIndex) => {
const handlerIndexes = group.hooks.flatMap((handler, handlerIndex) => {
const reference = managedReference(handler, scriptName);
return reference && (agentId === undefined || sameAgentId(reference.agentId, agentId))
? [handlerIndex]
: [];
});
if (handlerIndexes.length > 0) {
removals.push({
event,
groupIndex,
handlerIndexes,
removeGroup: exactGroup(group) && handlerIndexes.length === group.hooks.length,
});
}
});
}
return removals;
}
function referencesForEvent(
groups: readonly LettaGroup[],
scriptName: string,
): Map<string, LettaRuntimeReference[]> {
const references = new Map<string, LettaRuntimeReference[]>();
for (const group of groups) {
if (!exactGroup(group)) continue;
for (const handler of group.hooks) {
const reference = exactCurrentReference(handler, scriptName);
if (!reference
|| !reference.executablePath
|| !samePath(reference.executablePath, process.execPath)) continue;
const key = process.platform === 'win32'
? reference.agentId.toLowerCase()
: reference.agentId;
references.set(key, [...(references.get(key) ?? []), reference]);
}
}
return references;
}
export function lettaRuntimeContracts(hooks: LettaHooks): LettaRuntimeContract[] {
const guards = referencesForEvent(eventGroups(hooks, 'PreToolUse'), GUARD_SCRIPT);
const posts = referencesForEvent(eventGroups(hooks, 'PostToolUse'), AUDIT_SCRIPT);
const failures = referencesForEvent(
eventGroups(hooks, 'PostToolUseFailure'),
AUDIT_SCRIPT,
);
const contracts: LettaRuntimeContract[] = [];
for (const [key, guard] of guards) {
const post = posts.get(key);
const failure = failures.get(key);
if (guard.length !== 1 || post?.length !== 1 || failure?.length !== 1) continue;
if (!samePath(post[0].scriptPath, failure[0].scriptPath)) continue;
contracts.push({
agentId: guard[0].agentId,
guardPath: guard[0].scriptPath,
auditPath: post[0].scriptPath,
});
}
return contracts;
}