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

  1. Normalize before calling: map 'tool'/'function' messages to user (prefix content with tool name) or assistant, and drop roles you cannot represent
  2. Filter or fold unsupported roles in your adapter layer so only system|user|assistant reach the formatter
  3. Validate roles at the boundary with a Set of the three supported values
  4. 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

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


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)