{"version":3,"sources":["../../tracing/index.ts","../../tracing/core.ts","../../tracing/interaction.ts","../../tracing/otel.ts","../../tracing/reflex.ts"],"sourcesContent":["/**\n * Morph Tracing — instrument the top AI SDKs and ship traces to Morph.\n *\n * @example\n * ```ts\n * import { morphTracing } from \"@morphllm/morphsdk/tracing\";\n *\n * const morph = morphTracing({ apiKey: process.env.MORPH_API_KEY });\n * // OpenAI / Anthropic / Vercel AI SDK calls are now traced automatically.\n *\n * const it = morph.begin({ userId: \"u1\", convoId: \"c1\", event: \"chat\" });\n * it.setInput(\"what's the weather?\");\n * const answer = await it.withTool({ name: \"get_weather\" }, () => getWeather());\n * await it.finish({ output: answer });\n * ```\n *\n * For the Vercel AI SDK, also see `@morphllm/morphsdk/tracing/otel`'s `metadata()` helper.\n */\nexport { morphTracing, MorphTracing } from './core.js';\nexport type { Interaction, Tracer } from './interaction.js';\nexport { metadata } from './otel.js';\nexport {\n  REFLEX_RUN_ATTRIBUTE,\n  normalizeEvals,\n  serializeEvals,\n} from './reflex.js';\nexport type { EvalRunEntry } from './reflex.js';\nexport type {\n  MorphTracingConfig,\n  InstrumentModules,\n  TraceContext,\n  EvalSelection,\n  SpanParams,\n  ToolParams,\n  ToolSpan,\n  TrackToolParams,\n  FinishOptions,\n  MetadataOptions,\n} from './types.js';\n\nexport * as otel from './otel.js';\n","/**\n * Morph Tracing — core initialization.\n *\n * Thin Morph layer over OpenLLMetry / Traceloop. `morphTracing()` initializes\n * Traceloop with a JSON OTLP exporter pointed at Morph's ingest endpoint\n * (`${baseUrl}/v1/traces`, `Authorization: Bearer <apiKey>`) and returns a\n * `MorphTracing` handle for interactions and tools.\n *\n * We deliberately override Traceloop's default protobuf exporter with the\n * JSON-over-HTTP exporter so the ingest service can parse plain OTLP/JSON.\n */\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport * as traceloop from '@traceloop/node-server-sdk';\n\nimport {\n  createInteractionApi,\n  createNoopInteraction,\n  type Interaction,\n  type Tracer,\n} from './interaction.js';\nimport { metadata as buildMetadata } from './otel.js';\nimport type {\n  EvalSelection,\n  MetadataOptions,\n  MorphTracingConfig,\n  TraceContext,\n} from './types.js';\n\nconst DEFAULT_BASE_URL = 'https://api.morphllm.com';\n\nexport interface ResolvedConfig {\n  apiKey: string;\n  baseUrl: string;\n  appName: string;\n  disableBatching: boolean;\n  traceContent: boolean;\n  headers?: Record<string, string>;\n  debug: boolean;\n  evals?: EvalSelection;\n}\n\nfunction log(debug: boolean, ...args: unknown[]): void {\n  if (debug) console.log('[morph-tracing]', ...args);\n}\n\n/**\n * Handle returned by {@link morphTracing}. Create interactions with `begin()`,\n * get a non-interactive `tracer()`, and flush/shutdown the exporter.\n */\nexport class MorphTracing {\n  private readonly cfg: ResolvedConfig;\n  private readonly active = new Map<string, Interaction>();\n  readonly enabled: boolean;\n\n  constructor(config: MorphTracingConfig = {}) {\n    const debug = config.debug ?? process.env.MORPH_TRACING_DEBUG === '1';\n    const apiKey = config.apiKey ?? process.env.MORPH_API_KEY ?? '';\n    const baseUrlRaw =\n      config.baseUrl ?? process.env.MORPH_TRACES_URL ?? DEFAULT_BASE_URL;\n    const baseUrl = baseUrlRaw.replace(/\\/+$/, '');\n    const isProd = process.env.NODE_ENV === 'production';\n\n    this.cfg = {\n      apiKey,\n      baseUrl,\n      appName: config.appName ?? process.env.npm_package_name ?? 'morph-app',\n      disableBatching: config.disableBatching ?? !isProd,\n      traceContent: config.traceContent ?? true,\n      headers: config.headers,\n      debug,\n      evals: config.evals,\n    };\n\n    if (config.disabled) {\n      log(debug, 'disabled — no tracing initialized');\n      this.enabled = false;\n      return;\n    }\n    if (!apiKey) {\n      console.warn(\n        '[morph-tracing] No API key (set MORPH_API_KEY or pass { apiKey }). Tracing disabled.',\n      );\n      this.enabled = false;\n      return;\n    }\n\n    this.enabled = true;\n    if (config.useExternalOtel) {\n      // Customer drives their own NodeSDK; just register config, no SDK start.\n      traceloop.initialize({\n        baseUrl,\n        apiKey,\n        appName: this.cfg.appName,\n        tracingEnabled: false,\n        traceContent: this.cfg.traceContent,\n        silenceInitializationMessage: true,\n      });\n      log(debug, 'external OTEL mode — add createSpanProcessor() to your NodeSDK');\n      return;\n    }\n\n    const exporter = new OTLPTraceExporter({\n      url: `${baseUrl}/v1/traces`,\n      headers: { Authorization: `Bearer ${apiKey}`, ...this.cfg.headers },\n    });\n\n    traceloop.initialize({\n      baseUrl,\n      apiKey,\n      appName: this.cfg.appName,\n      exporter,\n      disableBatch: this.cfg.disableBatching,\n      traceContent: this.cfg.traceContent,\n      instrumentModules: config.instrumentModules as NonNullable<\n        Parameters<typeof traceloop.initialize>[0]\n      >['instrumentModules'],\n      tracingEnabled: true,\n      traceloopSyncEnabled: false,\n      silenceInitializationMessage: !debug,\n    });\n    log(\n      debug,\n      `initialized → ${baseUrl}/v1/traces (batching ${this.cfg.disableBatching ? 'off' : 'on'})`,\n    );\n  }\n\n  /**\n   * Begin a new traced interaction (a single user turn / agent run). On a\n   * disabled instance this returns an inert no-op interaction — callbacks still\n   * run, but no spans are created or shipped.\n   */\n  begin(ctx: TraceContext & { userId: string; event?: string }): Interaction {\n    if (!this.enabled) return createNoopInteraction(ctx);\n    // Per-interaction evals override the global default; omit both for none.\n    const resolved = { ...ctx, evals: ctx.evals ?? this.cfg.evals };\n    const interaction = createInteractionApi(resolved, this.cfg.traceContent, (eventId) =>\n      this.active.delete(eventId),\n    );\n    this.active.set(interaction.getEventId()!, interaction);\n    return interaction;\n  }\n\n  /** Look up an in-flight interaction by its eventId. */\n  getActiveInteraction(eventId: string): Interaction | undefined {\n    return this.active.get(eventId);\n  }\n\n  /**\n   * Non-interactive tracer for batch jobs where you only care about\n   * spans/token usage, not a user-facing interaction.\n   */\n  tracer(globalProperties: Record<string, string> = {}): Tracer {\n    const ctx = { userId: globalProperties.userId ?? 'batch', properties: globalProperties };\n    if (!this.enabled) return createNoopInteraction(ctx);\n    return createInteractionApi(ctx, this.cfg.traceContent);\n  }\n\n  /** Build Vercel AI SDK telemetry metadata (see `@morphllm/morphsdk/tracing/otel`). */\n  metadata(opts: MetadataOptions): Record<string, string> {\n    return buildMetadata(opts);\n  }\n\n  /** Span processor for `useExternalOtel: true` integrations. */\n  createSpanProcessor(\n    options?: Parameters<typeof traceloop.createSpanProcessor>[0],\n  ): ReturnType<typeof traceloop.createSpanProcessor> {\n    // Traceloop's default exporter here is OTLP/protobuf, but Morph's ingest\n    // parses OTLP/JSON only — supply the JSON exporter unless the caller\n    // brings their own.\n    const exporter =\n      options?.exporter ??\n      new OTLPTraceExporter({\n        url: `${this.cfg.baseUrl}/v1/traces`,\n        headers: { Authorization: `Bearer ${this.cfg.apiKey}`, ...this.cfg.headers },\n      });\n    return traceloop.createSpanProcessor({\n      apiKey: this.cfg.apiKey,\n      baseUrl: this.cfg.baseUrl,\n      ...options,\n      exporter,\n    });\n  }\n\n  /** Flush any batched spans immediately. Safe to call when idle. */\n  async forceFlush(): Promise<void> {\n    if (!this.enabled) return;\n    try {\n      await traceloop.forceFlush();\n    } catch (err) {\n      log(this.cfg.debug, 'forceFlush error (ignored):', err);\n    }\n  }\n\n  /** Flush and stop tracing. */\n  async shutdown(): Promise<void> {\n    await this.forceFlush();\n  }\n}\n\n/**\n * Initialize Morph Tracing and auto-instrument supported AI SDKs.\n *\n * Requires the optional OpenTelemetry / Traceloop backends (see this module's\n * README). They install automatically with the SDK unless optional deps are\n * skipped; importing `/tracing` without them throws a module-not-found error\n * naming the missing package.\n */\nexport function morphTracing(config: MorphTracingConfig = {}): MorphTracing {\n  return new MorphTracing(config);\n}\n","/**\n * Morph Tracing — interactions, tools, and manual spans.\n *\n * An `Interaction` is one user turn / agent run. It threads association\n * properties (user_id / convo_id / event_id) onto every span created inside it —\n * including the auto-instrumented LLM spans — so a whole conversation stitches\n * together in the Morph UI. Built on Traceloop's `withTask` / `withTool` and a\n * manual tracer for already-completed tool spans.\n */\nimport { context, SpanStatusCode, trace, type Span } from '@opentelemetry/api';\nimport * as traceloop from '@traceloop/node-server-sdk';\n\nimport { metadata as buildMetadata } from './otel.js';\nimport { REFLEX_RUN_ATTRIBUTE, serializeEvals } from './reflex.js';\nimport type {\n  FinishOptions,\n  SpanParams,\n  ToolParams,\n  ToolSpan,\n  TrackToolParams,\n  TraceContext,\n} from './types.js';\n\n// Traceloop semantic-convention attribute keys.\nconst ASSOC = 'traceloop.association.properties.';\nconst ENTITY_INPUT = 'traceloop.entity.input';\nconst ENTITY_OUTPUT = 'traceloop.entity.output';\nconst ENTITY_NAME = 'traceloop.entity.name';\nconst SPAN_KIND = 'traceloop.span.kind';\n\nfunction uuid(): string {\n  const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n  if (c?.randomUUID) return c.randomUUID();\n  return 'xxxxxxxxxxxx4xxxyxxx'.replace(/[xy]/g, (ch) => {\n    const r = (Math.random() * 16) | 0;\n    return (ch === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n  });\n}\n\nfunction asString(v: unknown): string {\n  if (v == null) return '';\n  return typeof v === 'string' ? v : JSON.stringify(v);\n}\n\nexport interface Interaction {\n  getEventId(): string | undefined;\n  setInput(input: string): void;\n  setProperty(key: string, value: string): void;\n  setProperties(props: Record<string, string>): void;\n  /**\n   * Run `fn` with this interaction's association properties active — required so\n   * auto-instrumented OpenAI/Anthropic spans inherit user_id / convo_id / tags.\n   */\n  run<T>(fn: () => Promise<T> | T): Promise<T>;\n  /** Run `fn` inside a traced task span; LLM calls within inherit attribution. */\n  withSpan<T>(params: SpanParams | string, fn: () => Promise<T> | T): Promise<T>;\n  /** Run `fn` inside a traced tool span. */\n  withTool<T>(params: ToolParams | string, fn: () => Promise<T> | T): Promise<T>;\n  /** Start a tool span you end manually. */\n  startToolSpan(params: ToolParams | string): ToolSpan;\n  /** Record an already-completed tool invocation. */\n  trackTool(params: TrackToolParams): void;\n  /** Metadata for the Vercel AI SDK `experimental_telemetry.metadata`. */\n  vercelAiSdkMetadata(): Record<string, string>;\n  /** End the interaction with its final output. */\n  finish(opts: FinishOptions | string): Promise<void>;\n}\n\nexport type Tracer = Pick<Interaction, 'withSpan' | 'withTool' | 'startToolSpan' | 'trackTool'>;\n\n/** Build the association-property bag Traceloop propagates onto child spans. */\nfunction associationProps(\n  ctx: TraceContext & { userId?: string },\n  extra: Record<string, string>,\n): Record<string, string> {\n  const props: Record<string, string> = { ...extra };\n  if (ctx.userId) props.user_id = ctx.userId;\n  if (ctx.convoId) props.convo_id = ctx.convoId;\n  if (ctx.eventId) props.event_id = ctx.eventId;\n  if (ctx.event) props.event_name = ctx.event;\n  return props;\n}\n\nexport function createInteractionApi(\n  initial: TraceContext & { userId?: string },\n  traceContent: boolean,\n  onClose?: (eventId: string) => void,\n): Interaction {\n  const ctx: TraceContext & { userId?: string } = {\n    ...initial,\n    eventId: initial.eventId ?? uuid(),\n  };\n  const properties: Record<string, string> = { ...(initial.properties ?? {}) };\n  let input = initial.input;\n  let workflowSpan: Span | null = null;\n  let finished = false;\n  // Serialized once. Set as a raw attribute on the workflow span only — NOT an\n  // association property, so it is not copied onto every child LLM span. Ingest\n  // reads `morph.reflex.run` off the workflow-span to enqueue the evals.\n  const reflexRun = serializeEvals(initial.evals);\n\n  const withAssoc = <T>(fn: () => Promise<T> | T): Promise<T> | T =>\n    traceloop.withAssociationProperties(associationProps(ctx, properties), fn);\n\n  const workflowName = () => ctx.event ?? 'interaction';\n\n  /** Open the interaction workflow span once; stays active until finish(). */\n  function ensureWorkflowSpan(): Span {\n    if (workflowSpan) return workflowSpan;\n    const span = traceloop.getTraceloopTracer().startSpan(workflowName());\n    span.setAttribute(SPAN_KIND, 'workflow');\n    for (const [k, v] of Object.entries(associationProps(ctx, properties))) {\n      span.setAttribute(ASSOC + k, v);\n    }\n    if (traceContent && input) span.setAttribute(ENTITY_INPUT, input);\n    if (reflexRun) span.setAttribute(REFLEX_RUN_ATTRIBUTE, reflexRun);\n    workflowSpan = span;\n    return span;\n  }\n\n  /** Run fn with association props and the workflow span as the active parent. */\n  function withWorkflowContext<T>(fn: () => Promise<T> | T): Promise<T> | T {\n    return withAssoc(() => {\n      const span = ensureWorkflowSpan();\n      return context.with(trace.setSpan(context.active(), span), fn);\n    });\n  }\n\n  /** Close the workflow span exactly once, applying final attributes. */\n  function closeWorkflowSpan(apply: (span: Span) => void): void {\n    if (finished) return;\n    withAssoc(() => {\n      const span = ensureWorkflowSpan();\n      apply(span);\n      span.end();\n    });\n    workflowSpan = null;\n    finished = true;\n    onClose?.(ctx.eventId!);\n  }\n\n  /** Record an exception + ERROR status on the workflow span, then close it. */\n  function failWorkflowSpan(err: unknown): void {\n    const e = err instanceof Error ? err : new Error(String(err));\n    closeWorkflowSpan((span) => {\n      span.recordException(e);\n      span.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n      if (traceContent) span.setAttribute(ENTITY_OUTPUT, `ERROR: ${e.message}`);\n    });\n  }\n\n  const toolName = (p: ToolParams | string) => (typeof p === 'string' ? p : p.name);\n\n  function startToolSpan(params: ToolParams | string): ToolSpan {\n    const name = toolName(params);\n    const span: Span = traceloop.getTraceloopTracer().startSpan(name);\n    span.setAttribute(SPAN_KIND, 'tool');\n    span.setAttribute(ENTITY_NAME, name);\n    for (const [k, v] of Object.entries(associationProps(ctx, properties))) {\n      span.setAttribute(ASSOC + k, v);\n    }\n    if (typeof params !== 'string' && params.properties) {\n      for (const [k, v] of Object.entries(params.properties)) span.setAttribute(k, v);\n    }\n    return {\n      setInput(value: unknown) {\n        if (traceContent) span.setAttribute(ENTITY_INPUT, asString(value));\n      },\n      setOutput(value: unknown) {\n        if (traceContent) span.setAttribute(ENTITY_OUTPUT, asString(value));\n      },\n      setError(error: Error | string) {\n        const e = typeof error === 'string' ? new Error(error) : error;\n        span.recordException(e);\n        span.setStatus({ code: SpanStatusCode.ERROR, message: e.message });\n      },\n      end() {\n        span.end();\n      },\n    };\n  }\n\n  return {\n    getEventId: () => ctx.eventId,\n    setInput(value: string) {\n      input = value;\n    },\n    setProperty(key: string, value: string) {\n      properties[key] = value;\n    },\n    setProperties(props: Record<string, string>) {\n      Object.assign(properties, props);\n    },\n    vercelAiSdkMetadata() {\n      return buildMetadata({\n        userId: ctx.userId ?? 'unknown',\n        convoId: ctx.convoId,\n        eventName: ctx.event,\n        eventId: ctx.eventId,\n        properties,\n      });\n    },\n    async run(fn) {\n      try {\n        return await Promise.resolve(withWorkflowContext(fn));\n      } catch (err) {\n        // Natively record the failure on the workflow span and ship it, so a\n        // throwing interaction still lands in the trace as an errored span.\n        failWorkflowSpan(err);\n        throw err;\n      }\n    },\n    withSpan(params, fn) {\n      const name = typeof params === 'string' ? params : params.name;\n      return Promise.resolve(withWorkflowContext(() => traceloop.withTask({ name }, fn)));\n    },\n    withTool(params, fn) {\n      const name = toolName(params);\n      const version = typeof params === 'string' ? undefined : params.version;\n      return Promise.resolve(withWorkflowContext(() => traceloop.withTool({ name, version }, fn)));\n    },\n    startToolSpan,\n    trackTool(params: TrackToolParams) {\n      const span = startToolSpan({ name: params.name, properties: params.properties });\n      if (params.input !== undefined) span.setInput(params.input);\n      if (params.output !== undefined) span.setOutput(params.output);\n      if (params.error) span.setError(params.error);\n      span.end();\n    },\n    async finish(opts) {\n      const output = typeof opts === 'string' ? opts : opts.output;\n      if (typeof opts !== 'string' && opts.properties) Object.assign(properties, opts.properties);\n      // No-op if run() already closed the span on error; otherwise end it now.\n      // (Creates one if finish() is called alone, i.e. begin() → finish().)\n      closeWorkflowSpan((span) => {\n        if (traceContent) span.setAttribute(ENTITY_OUTPUT, output);\n      });\n    },\n  };\n}\n\n/**\n * Inert Interaction for disabled instances. Preserves control flow — run() /\n * withTool() still execute their callback and rethrow errors — but never touches\n * the tracer. Required because the OTel provider is a process-wide singleton: a\n * disabled instance that created real spans would ship them through whichever\n * enabled instance registered the provider.\n */\nexport function createNoopInteraction(initial: TraceContext & { userId?: string }): Interaction {\n  const ctx: TraceContext & { userId?: string } = {\n    ...initial,\n    eventId: initial.eventId ?? uuid(),\n  };\n  const properties: Record<string, string> = { ...(initial.properties ?? {}) };\n  const noopToolSpan: ToolSpan = { setInput() {}, setOutput() {}, setError() {}, end() {} };\n\n  return {\n    getEventId: () => ctx.eventId,\n    setInput() {},\n    setProperty(key: string, value: string) {\n      properties[key] = value;\n    },\n    setProperties(props: Record<string, string>) {\n      Object.assign(properties, props);\n    },\n    vercelAiSdkMetadata() {\n      return buildMetadata({\n        userId: ctx.userId ?? 'unknown',\n        convoId: ctx.convoId,\n        eventName: ctx.event,\n        eventId: ctx.eventId,\n        properties,\n      });\n    },\n    async run(fn) {\n      return await fn();\n    },\n    async withSpan(_params, fn) {\n      return await fn();\n    },\n    async withTool(_params, fn) {\n      return await fn();\n    },\n    startToolSpan: () => noopToolSpan,\n    trackTool() {},\n    async finish() {},\n  };\n}\n","/**\n * Morph Tracing — Vercel AI SDK helper.\n *\n * The Vercel AI SDK emits its own OpenTelemetry spans when you pass\n * `experimental_telemetry`. There is nothing to monkey-patch; instead you tag\n * each call with `metadata()` so Morph can attribute the resulting spans to a\n * user/conversation/event.\n *\n * @example\n * ```ts\n * import { generateText } from \"ai\";\n * import { metadata } from \"@morphllm/morphsdk/tracing/otel\";\n *\n * const res = await generateText({\n *   model: openai(\"gpt-4o\"),\n *   prompt: \"Hello!\",\n *   experimental_telemetry: {\n *     isEnabled: true,\n *     metadata: metadata({ userId: \"user-123\", convoId: \"convo-456\" }),\n *   },\n * });\n * ```\n */\nimport type { MetadataOptions } from './types.js';\n\n/**\n * Reserved metadata keys Morph owns. Custom `properties` can't overwrite these,\n * so attribution (user_id / convo_id / event_id) stays intact.\n *\n * The AI SDK stores metadata as `ai.telemetry.metadata.<key>`; Traceloop's span\n * processor copies them to `traceloop.association.properties.<key>`, which is\n * what ClickHouse views read. Use snake_case names (user_id, convo_id, …).\n */\nconst RESERVED_KEYS = new Set(['user_id', 'convo_id', 'event_id', 'event_name']);\n\nfunction uuid(): string {\n  // Node 18+ and modern runtimes expose globalThis.crypto.randomUUID.\n  const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n  if (c?.randomUUID) return c.randomUUID();\n  // Fallback: RFC4122-ish without crypto.\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {\n    const r = (Math.random() * 16) | 0;\n    const v = ch === 'x' ? r : (r & 0x3) | 0x8;\n    return v.toString(16);\n  });\n}\n\n/**\n * Build the metadata object for the Vercel AI SDK's\n * `experimental_telemetry.metadata`. The values are propagated to every span the\n * AI SDK creates for that call. Generate a fresh `eventId` per call for grouping.\n */\nexport function metadata(opts: MetadataOptions): Record<string, string> {\n  const result: Record<string, string> = {\n    user_id: opts.userId,\n    event_id: opts.eventId ?? uuid(),\n  };\n  if (opts.convoId) result.convo_id = opts.convoId;\n  if (opts.eventName) result.event_name = opts.eventName;\n  if (opts.properties) {\n    for (const [key, value] of Object.entries(opts.properties)) {\n      if (!RESERVED_KEYS.has(key)) result[key] = value;\n    }\n  }\n  return result;\n}\n\nexport default { metadata };\n","/**\n * Morph Tracing — eval selection (which Reflexes to run on a trace).\n *\n * The public API is `evals` (see {@link EvalSelection}): `{ user, assistant }` choosing which role\n * each model classifies. We deliberately keep the public surface to plain nouns and hide the wire\n * detail: the selection is serialized onto a single\n * `morph.reflex.run` attribute on the interaction's workflow span (NOT an association property,\n * so it is not copied onto child LLM spans), and the per-role choice maps to the backend's\n * transform ids. Morph's ingest reads that attribute and runs the classifications async; results\n * land in the traces dashboard already labeled.\n */\nimport type { EvalSelection } from './types.js';\n\n/** Raw span attribute key carrying the serialized selection (internal wire detail). */\nexport const REFLEX_RUN_ATTRIBUTE = 'morph.reflex.run';\n\n// Public role → backend transform id. `user` classifies the user's message, `assistant` the\n// agent's output. (Whole-conversation evals are not exposed yet.)\nconst USER_TRANSFORM = 'user_message';\nconst ASSISTANT_TRANSFORM = 'assistant_message';\n\n/** One serialized entry on the wire: the model alias and the transform to run it on. */\nexport interface EvalRunEntry {\n  model: string;\n  transform: string;\n}\n\n/**\n * Normalize a public `evals` selection into deduped `{ model, transform }` wire entries.\n * `user` models run on the user turn, `assistant` models on the agent turn. Drops blank models;\n * preserves first-seen order. Exported for tests and so callers can see exactly what runs.\n */\nexport function normalizeEvals(evals: EvalSelection | undefined): EvalRunEntry[] {\n  if (!evals) return [];\n  const out: EvalRunEntry[] = [];\n  const seen = new Set<string>();\n  const add = (models: string[] | undefined, transform: string): void => {\n    // Guard against a non-array (e.g. an untyped JS caller passing { user: \"jailbreak\" }) — iterating\n    // a string would emit one bogus entry per character. Types forbid this; this is runtime defense.\n    if (!Array.isArray(models)) return;\n    for (const raw of models) {\n      const model = typeof raw === 'string' ? raw.trim() : '';\n      if (!model) continue;\n      const key = `${model} ${transform}`;\n      if (seen.has(key)) continue;\n      seen.add(key);\n      out.push({ model, transform });\n    }\n  };\n  add(evals.user, USER_TRANSFORM);\n  add(evals.assistant, ASSISTANT_TRANSFORM);\n  return out;\n}\n\n/**\n * Serialize an `evals` selection to the `morph.reflex.run` attribute string, or `null` when\n * there is nothing to run (so the attribute is simply omitted and no classification is enqueued).\n */\nexport function serializeEvals(evals: EvalSelection | undefined): string | null {\n  const entries = normalizeEvals(evals);\n  return entries.length ? JSON.stringify(entries) : null;\n}\n"],"mappings":"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","names":["traceloop","uuid"]}