import type { Message } from "../providers/types.js"; /** Matches both old (``) and new versioned * (``) marker formats. * Group 1 = skill ID, group 2 = version string (optional). */ const LOADED_SKILL_RE = //g; // --------------------------------------------------------------------------- // Public types // --------------------------------------------------------------------------- export interface ActiveSkillEntry { id: string; /** Present only when the marker includes a `version` attribute. */ version?: string; } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** * Scans conversation history for `` markers and returns an * ordered, deduplicated list of active skill entries (ID + optional version). * * Supports two marker formats: * - Legacy: `` * - Versioned: `` * * Only `tool_result` blocks whose corresponding `tool_use` has * `name === 'skill_load'` are considered. This prevents user messages or * arbitrary tool outputs from injecting fake skill activations. */ export function deriveActiveSkills(messages: Message[]): ActiveSkillEntry[] { // First pass: collect tool_use IDs that belong to skill_load calls. const skillLoadUseIds = new Set(); for (const msg of messages) { for (const block of msg.content) { if (block.type === "tool_use" && block.name === "skill_load") { skillLoadUseIds.add(block.id); } } } // Second pass: parse markers only from matching tool_result blocks. const seen = new Set(); const entries: ActiveSkillEntry[] = []; for (const msg of messages) { for (const block of msg.content) { if (block.type !== "tool_result") { continue; } if (!skillLoadUseIds.has(block.tool_use_id)) { continue; } const text = block.content; if (!text) { continue; } for (const match of text.matchAll(LOADED_SKILL_RE)) { const id = match[1]; if (!seen.has(id)) { seen.add(id); const entry: ActiveSkillEntry = { id }; if (match[2]) { entry.version = match[2]; } entries.push(entry); } } } } return entries; }