ruvnet/ruflo · error
Unknown role
Error message
Unknown role: ${m.role} What it means
When formatting a chat prompt, the wrapper maps each message role to a wasm constructor and supports exactly three: system, user, assistant. Any other role value falls through the switch and throws — the constructor set literally has no branch for it.
Solutions
- Normalize before calling: map 'tool'/'function' messages to user (prefix content with tool name) or assistant, and drop roles you cannot represent
- Filter or fold unsupported roles in your adapter layer so only system|user|assistant reach the formatter
- Validate roles at the boundary with a Set of the three supported values
- Upgrade the package if a newer version added the role you need
Example fix
// before
const text = await formatChat(tmpl, rawMessages); // rawMessages has {role:'tool'} → throws
// after
const SUPPORTED = new Set(['system', 'user', 'assistant']);
const msgs = rawMessages
.map(m => SUPPORTED.has(m.role) ? m : { role: 'user' as const, content: `[${m.role}] ${m.content}` })
.filter(m => m.role !== undefined);
const text = await formatChat(tmpl, msgs); Defensive patterns
Strategy: type-guard
Validate before calling
const SUPPORTED_ROLES = new Set(['system', 'user', 'assistant']);
const safeMessages = messages
.filter(m => m && typeof m.content === 'string')
.map(m => SUPPORTED_ROLES.has(m.role) ? m : { role: 'user' as const, content: `[${m.role}] ${m.content}` }); Type guard
type SupportedRole = 'system' | 'user' | 'assistant';
function isSupportedRole(m: { role: string }): m is { role: SupportedRole; content: string } {
return m.role === 'system' || m.role === 'user' || m.role === 'assistant';
} Try / catch
try {
text = await formatChat(tmpl, messages);
} catch (e) {
if (e instanceof Error && e.message.startsWith('Unknown role')) {
text = await formatChat(tmpl, normalizeRoles(messages)); // fold, then retry
} else throw e;
} Prevention
- Normalize at your adapter boundary: fold tool/function/developer roles into user or assistant before formatting
- Reject or log unknown roles at ingestion instead of inside the prompt builder
- Keep the supported-role Set in one shared constant so validation and formatting cannot drift
When it happens
Trigger: Passing OpenAI-style histories containing 'tool' or 'function' role messages; a 'developer' or 'system2' role from a newer chat API; role field undefined/misspelled ('Assistant', 'bot'); adapters forwarding raw provider payloads without role normalization.
Common situations: Replaying captured LLM conversations that include tool calls; converting chat logs between providers; schema drift when a downstream API adds a new role this wrapper version predates.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Unknown template preset
- batchProcess requires WASM kernel
- Circuit breaker open for provider
- detectDestructive not available in JS fallback; use…
- Failed to initialize @ruvector/ruvllm-wasm
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ee3452c4c28d6959.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/ruvector/ruvllm-wasm.ts:361
phi: () => mod.ChatTemplateWasm.phi(),
gemma: () => mod.ChatTemplateWasm.gemma(),
};
const factory = presets[template];
if (!factory) throw new Error(`Unknown template preset: ${template}. Use: ${Object.keys(presets).join(', ')}`);
tmpl = factory();
} else if ('custom' in template) {
tmpl = mod.ChatTemplateWasm.custom(template.custom);
} else if ('modelId' in template) {
tmpl = mod.ChatTemplateWasm.detectFromModelId(template.modelId);
}
// Build messages
const wasmMessages = messages.map(m => {
switch (m.role) {
case 'system': return mod.ChatMessageWasm.system(m.content);
case 'user': return mod.ChatMessageWasm.user(m.content);
case 'assistant': return mod.ChatMessageWasm.assistant(m.content);
default: throw new Error(`Unknown role: ${m.role}`);
}
});
return tmpl.format(wasmMessages);
}
// ── KV Cache ─────────────────────────────────────────────────
/**
* Create a KV cache for token management.
*/
export async function createKvCache(opts?: {
tailLength?: number;
maxTokens?: number;
numKvHeads?: number;
headDim?: number;
}): Promise<{
append: (keys: Float32Array, values: Float32Array) => void;View on GitHub (pinned to fa13ee4ad6)