UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

150 lines (149 loc) • 5.66 kB
/** * Per-session skill activation state. * * A skill activated via use_skill is pinned to its session: the tracker * holds the pinned instruction message until the turn is persisted * (drainPending → StoreConversationTurnOptions.skillMessages) and answers * "is this skill already loaded?" so re-invocations return a cheap * already_loaded note instead of the full body. * * The stored session history is the source of truth — pinned messages * carry `metadata.isSkill`. hydrate() rebuilds a session's active set from * stored messages on every activation attempt (use_skill is rare, one * history read per attempt), so dedup stays correct across process * restarts, multiple instances sharing a Redis memory backend, and failed * persistence: a pin that never reached storage simply re-activates on * the next turn instead of being falsely reported as loaded. */ import { randomUUID } from "node:crypto"; /** Upper bound on tracked sessions; oldest are evicted first (insertion order). */ const MAX_TRACKED_SESSIONS = 1000; /** Render the pinned history message for an activated skill. */ export function buildSkillActivationMessage(skill) { const version = skill.version ?? 1; return { id: `skill-${randomUUID()}`, role: "user", content: `[Skill loaded: ${skill.name} v${version}]\n\n${skill.instructions}`, timestamp: new Date().toISOString(), metadata: { isSkill: true, skillId: skill.id, skillName: skill.name, skillVersion: version, }, }; } /** Activation record derived from a pinned history message, when it is one. */ function recordFromMessage(message) { const meta = message.metadata; if (!meta?.isSkill || !meta.skillId || !meta.skillName) { return null; } return { skillId: meta.skillId, name: meta.skillName, version: meta.skillVersion ?? 1, activatedAt: message.timestamp ?? new Date(0).toISOString(), }; } export class SkillSessionTracker { /** sessionId → skillId → activation record. */ active = new Map(); /** sessionId → pinned messages awaiting persistence into the session turn. */ pending = new Map(); /** Whether the skill is already active (loaded) in this session. */ isActive(sessionId, skillId, name) { return Boolean(this.getActivation(sessionId, skillId, name)); } /** Activation record for an active skill (by id, falling back to name). */ getActivation(sessionId, skillId, name) { const records = this.active.get(sessionId); if (!records) { return undefined; } const byId = records.get(skillId); if (byId || !name) { return byId; } for (const record of records.values()) { if (record.name === name) { return record; } } return undefined; } /** * Record an activation: marks the skill active and queues its pinned * message for persistence when the turn is stored. */ recordActivation(sessionId, skill) { const message = buildSkillActivationMessage(skill); const record = recordFromMessage(message); this.recordsFor(sessionId).set(skill.id, record); const queue = this.pending.get(sessionId); if (queue) { queue.push(message); } else { this.pending.set(sessionId, [message]); } return message; } /** * Rebuild the active set from stored session history, then merge the * in-process pending activations (this turn's pins, not yet stored). * Called before every dedup check — the rebuild keeps state truthful * when other instances pinned skills or when a past pin never reached * storage. */ hydrate(sessionId, storedMessages) { const records = new Map(); for (const message of storedMessages) { const record = recordFromMessage(message); if (record && !records.has(record.skillId)) { records.set(record.skillId, record); } } for (const message of this.pending.get(sessionId) ?? []) { const record = recordFromMessage(message); if (record) { records.set(record.skillId, record); } } this.recordsFor(sessionId); // reserve the slot (applies the session cap) this.active.set(sessionId, records); } /** * Remove and return the pinned messages queued for this session. Called * once per turn by the memory-store path; an empty result means nothing * was activated this turn. */ drainPending(sessionId) { const queue = this.pending.get(sessionId); if (!queue || queue.length === 0) { return []; } this.pending.delete(sessionId); return queue; } /** Active skill records for a session (listing/debugging). */ listActive(sessionId) { return Array.from(this.active.get(sessionId)?.values() ?? []); } recordsFor(sessionId) { let records = this.active.get(sessionId); if (!records) { if (this.active.size >= MAX_TRACKED_SESSIONS) { const oldest = this.active.keys().next().value; if (oldest !== undefined) { this.active.delete(oldest); this.pending.delete(oldest); } } records = new Map(); this.active.set(sessionId, records); } return records; } }