| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- /**
- * TranslationEngine - Orchestrates all mappers for agent conversion
- *
- * The TranslationEngine coordinates ToolMapper, PermissionMapper, ModelMapper,
- * ContextMapper, and CapabilityMatrix to provide complete agent translation
- * between OAC and other platforms.
- *
- * @example
- * ```ts
- * const engine = new TranslationEngine();
- * const result = engine.translate(agent, 'cursor');
- * // => { agent: translatedAgent, warnings: [...], compatible: true }
- * ```
- */
- import type {
- OpenAgent,
- AgentFrontmatter,
- ToolAccess,
- PermissionMap,
- ContextReference,
- SkillReference,
- } from "../types.js";
- import {
- mapToolAccessFromOAC,
- mapToolAccessToOAC,
- } from "../mappers/ToolMapper.js";
- import {
- mapPermissionsFromOAC,
- mapPermissionsToOAC,
- type DegradationStrategy,
- type BinaryPermissions,
- } from "../mappers/PermissionMapper.js";
- import {
- mapModelFromOAC,
- mapModelToOAC,
- } from "../mappers/ModelMapper.js";
- import {
- mapContextReferencesFromOAC,
- mapContextPathToOAC,
- mapSkillsToClaudeFormat,
- mapSkillsFromClaudeFormat,
- } from "../mappers/ContextMapper.js";
- import {
- analyzeCompatibility,
- getToolCapabilities,
- type Platform,
- type CompatibilityResult,
- } from "../core/CapabilityMatrix.js";
- // ============================================================================
- // Types
- // ============================================================================
- /**
- * Target platform for translation (excludes OAC since we translate TO/FROM OAC)
- */
- /**
- * The targets this engine can translate an {@link OpenAgent} to.
- *
- * `opencode` is excluded deliberately, and it is NOT an oversight to be fixed by widening this
- * later. This engine translates from `OpenAgent`, whose `permission` is an unordered `Record`;
- * OpenCode's permission semantics are ordered last-match-wins. Emitting OpenCode from an
- * unordered map would silently reorder a security-critical rule block — the exact corruption the
- * canonical refactor exists to remove. `OpenCodeAdapter.fromOAC()` therefore refuses outright
- * and directs callers to `fromCanonical(source)`, which the `oac build` path uses instead.
- *
- * Previously this read `Exclude<Platform, "oac">` and so tracked the matrix's platform list by
- * accident. Adding `opencode` to {@link Platform} on 2026-07-15 widened it silently and the
- * compiler caught it: this engine's mappers (`ToolMapper.ToolPlatform`) never knew OpenCode.
- * The coupling was the bug; this union is now stated outright.
- */
- export type TranslationTarget = Exclude<Platform, "oac" | "opencode">;
- /**
- * Configuration options for translation
- */
- export interface TranslationOptions {
- /** Strategy for handling 'ask' permissions (default: 'permissive') */
- permissionStrategy?: DegradationStrategy;
- /** Whether to include compatibility analysis (default: true) */
- analyzeCompatibility?: boolean;
- /** Whether to preserve unsupported features as comments (default: false) */
- preserveAsComments?: boolean;
- /** Custom model fallback if model not available on target */
- modelFallback?: string;
- }
- /**
- * Result of translating an agent
- */
- export interface TranslationResult {
- /** The translated agent frontmatter */
- frontmatter: Partial<AgentFrontmatter>;
- /** Translated tools (platform format) */
- tools?: Record<string, boolean>;
- /** Translated permissions (platform format) */
- permissions?: BinaryPermissions;
- /** Translated model ID */
- model?: string;
- /** Translated context paths */
- contextPaths?: string[];
- /** Translated skills (for Claude) */
- skills?: string[];
- /** All warnings generated during translation */
- warnings: string[];
- /** Compatibility analysis result */
- compatibility?: CompatibilityResult;
- /** Whether translation was successful */
- success: boolean;
- }
- /**
- * Result of translating from a platform back to OAC
- */
- export interface ReverseTranslationResult {
- /** Partial OAC agent that can be merged */
- agent: Partial<OpenAgent>;
- /** Warnings generated during translation */
- warnings: string[];
- /** Whether translation was successful */
- success: boolean;
- }
- // ============================================================================
- // Default Options
- // ============================================================================
- const DEFAULT_OPTIONS: Required<TranslationOptions> = {
- permissionStrategy: "permissive",
- analyzeCompatibility: true,
- preserveAsComments: false,
- modelFallback: "claude-sonnet-4",
- };
- // ============================================================================
- // TranslationEngine Class
- // ============================================================================
- /**
- * Engine that orchestrates all mappers for complete agent translation.
- */
- export class TranslationEngine {
- private options: Required<TranslationOptions>;
- constructor(options: TranslationOptions = {}) {
- this.options = { ...DEFAULT_OPTIONS, ...options };
- }
- // ==========================================================================
- // OAC → Platform Translation
- // ==========================================================================
- /**
- * Translate an OpenAgent to a target platform format.
- *
- * @param agent - The OpenAgent to translate
- * @param target - Target platform
- * @param options - Override default options
- * @returns Translation result
- */
- translate(
- agent: OpenAgent,
- target: TranslationTarget,
- options?: TranslationOptions
- ): TranslationResult {
- const opts = { ...this.options, ...options };
- const warnings: string[] = [];
- // Analyze compatibility first
- let compatibility: CompatibilityResult | undefined;
- if (opts.analyzeCompatibility) {
- compatibility = analyzeCompatibility(agent, target);
- warnings.push(...compatibility.warnings);
- }
- // Translate tools
- let tools: Record<string, boolean> | undefined;
- if (agent.frontmatter.tools) {
- const toolResult = mapToolAccessFromOAC(
- agent.frontmatter.tools,
- target
- );
- tools = toolResult.tools;
- warnings.push(...toolResult.warnings);
- }
- // Translate permissions
- let permissions: BinaryPermissions | undefined;
- if (agent.frontmatter.permission) {
- const permResult = mapPermissionsFromOAC(
- agent.frontmatter.permission,
- target,
- opts.permissionStrategy
- );
- permissions = permResult.permissions as BinaryPermissions;
- warnings.push(...permResult.warnings);
- }
- // Translate model
- let model: string | undefined;
- if (agent.frontmatter.model) {
- const modelResult = mapModelFromOAC(
- agent.frontmatter.model,
- target
- );
- model = modelResult.id;
- if (modelResult.warning) {
- warnings.push(modelResult.warning);
- }
- }
- // Translate contexts
- let contextPaths: string[] | undefined;
- if (agent.contexts && agent.contexts.length > 0) {
- const contextResult = mapContextReferencesFromOAC(
- agent.contexts,
- target
- );
- contextPaths = contextResult.paths;
- warnings.push(...contextResult.warnings);
- }
- // Translate skills (Claude-specific)
- let skills: string[] | undefined;
- if (target === "claude" && agent.frontmatter.skills) {
- const skillResult = mapSkillsToClaudeFormat(agent.frontmatter.skills);
- skills = skillResult.skills;
- warnings.push(...skillResult.warnings);
- }
- // Build translated frontmatter
- const frontmatter: Partial<AgentFrontmatter> = {
- name: agent.frontmatter.name,
- description: agent.frontmatter.description,
- mode: agent.frontmatter.mode,
- };
- // Include temperature if supported
- if (agent.frontmatter.temperature !== undefined) {
- const capabilities = getToolCapabilities(target);
- if (capabilities.supportsTemperature) {
- frontmatter.temperature = agent.frontmatter.temperature;
- }
- }
- return {
- frontmatter,
- tools,
- permissions,
- model,
- contextPaths,
- skills,
- warnings,
- compatibility,
- success: !compatibility || compatibility.compatible,
- };
- }
- // ==========================================================================
- // Platform → OAC Translation
- // ==========================================================================
- /**
- * Translate from a platform format back to OAC format.
- *
- * @param source - Platform-specific agent data
- * @param platform - Source platform
- * @returns Partial OpenAgent that can be merged
- */
- translateToOAC(
- source: {
- name?: string;
- description?: string;
- tools?: Record<string, boolean>;
- permissions?: BinaryPermissions;
- model?: string;
- contextPaths?: string[];
- skills?: string[];
- systemPrompt?: string;
- },
- platform: TranslationTarget
- ): ReverseTranslationResult {
- const warnings: string[] = [];
- // Translate tools
- let oacTools: ToolAccess | undefined;
- if (source.tools) {
- const toolResult = mapToolAccessToOAC(source.tools, platform);
- oacTools = toolResult.tools;
- warnings.push(...toolResult.warnings);
- }
- // Translate permissions
- let oacPermissions: PermissionMap | undefined;
- if (source.permissions) {
- const permResult = mapPermissionsToOAC(
- source.permissions,
- platform
- );
- oacPermissions = permResult.permissions as PermissionMap;
- warnings.push(...permResult.warnings);
- }
- // Translate model
- let oacModel: string | undefined;
- if (source.model) {
- const modelResult = mapModelToOAC(source.model, platform);
- oacModel = modelResult.id;
- if (modelResult.warning) {
- warnings.push(modelResult.warning);
- }
- }
- // Translate context paths
- let oacContexts: ContextReference[] | undefined;
- if (source.contextPaths && source.contextPaths.length > 0) {
- oacContexts = source.contextPaths.map((path) => {
- const result = mapContextPathToOAC(path, platform);
- if (result.warning) {
- warnings.push(result.warning);
- }
- return { path: result.path };
- });
- }
- // Translate skills (from Claude format)
- let oacSkills: SkillReference[] | undefined;
- if (platform === "claude" && source.skills) {
- oacSkills = mapSkillsFromClaudeFormat(source.skills);
- }
- // Build partial OpenAgent
- const agent: Partial<OpenAgent> = {
- frontmatter: {
- name: source.name || "Unnamed Agent",
- description: source.description || "Imported agent",
- mode: "primary",
- ...(oacTools && { tools: oacTools }),
- ...(oacPermissions && { permission: oacPermissions }),
- ...(oacModel && { model: oacModel }),
- ...(oacSkills && { skills: oacSkills }),
- },
- ...(source.systemPrompt && { systemPrompt: source.systemPrompt }),
- ...(oacContexts && { contexts: oacContexts }),
- };
- return {
- agent,
- warnings,
- success: true,
- };
- }
- // ==========================================================================
- // Batch Translation
- // ==========================================================================
- /**
- * Translate multiple agents to a target platform.
- *
- * @param agents - Array of OpenAgents to translate
- * @param target - Target platform
- * @returns Array of translation results
- */
- translateBatch(
- agents: OpenAgent[],
- target: TranslationTarget
- ): TranslationResult[] {
- return agents.map((agent) => this.translate(agent, target));
- }
- // ==========================================================================
- // Utility Methods
- // ==========================================================================
- /**
- * Get a preview of what will happen during translation without actually translating.
- *
- * @param agent - The agent to preview
- * @param target - Target platform
- * @returns Compatibility analysis
- */
- preview(agent: OpenAgent, target: TranslationTarget): CompatibilityResult {
- return analyzeCompatibility(agent, target);
- }
- /**
- * Check if an agent can be translated to a target with full fidelity.
- *
- * @param agent - The agent to check
- * @param target - Target platform
- * @returns True if no features will be lost
- */
- isFullyCompatible(agent: OpenAgent, target: TranslationTarget): boolean {
- const result = analyzeCompatibility(agent, target);
- return result.lost.length === 0 && result.degraded.length === 0;
- }
- /**
- * Get the current translation options.
- *
- * @returns Current options
- */
- getOptions(): Required<TranslationOptions> {
- return { ...this.options };
- }
- /**
- * Update translation options.
- *
- * @param options - Options to merge
- */
- setOptions(options: TranslationOptions): void {
- this.options = { ...this.options, ...options };
- }
- }
- // ============================================================================
- // Factory Functions
- // ============================================================================
- /**
- * Create a TranslationEngine with default options.
- *
- * @returns New TranslationEngine instance
- */
- export function createTranslationEngine(
- options?: TranslationOptions
- ): TranslationEngine {
- return new TranslationEngine(options);
- }
- /**
- * Quick translate function for one-off translations.
- *
- * @param agent - Agent to translate
- * @param target - Target platform
- * @param options - Translation options
- * @returns Translation result
- */
- export function translate(
- agent: OpenAgent,
- target: TranslationTarget,
- options?: TranslationOptions
- ): TranslationResult {
- const engine = new TranslationEngine(options);
- return engine.translate(agent, target);
- }
- /**
- * Quick preview function for one-off compatibility checks.
- *
- * @param agent - Agent to preview
- * @param target - Target platform
- * @returns Compatibility result
- */
- export function previewTranslation(
- agent: OpenAgent,
- target: TranslationTarget
- ): CompatibilityResult {
- return analyzeCompatibility(agent, target);
- }
|