Crux
API Reference@use-crux/core

PromptText

md composition, interpolation, inspection, Project Index evidence, and runtime preview.

import { md, type PromptText } from "@use-crux/core";

md builds an opaque, immutable PromptText value with native tagged-template syntax. Runtime composition does not parse or render Markdown. Resolution lowers the value to provider-neutral plain text before adapters, caches, and RPC boundaries.

Crux editor tooling may classify authored literal islands as CommonMark for presentation, navigation, previews, and diagnostics without changing runtime bytes.

Authored Fields

prompt-text.ts
import { md, prompt, type PromptText } from "@use-crux/core";
import { z } from "zod";

const rules: PromptText = md`
  ## Rules

  - Be concise.
`;

const answer = prompt({
  id: "answer",
  input: z.object({ question: z.string() }),
  system: rules,
  prompt: ({ input }) => md`
    ## Question

    ${input.question}
  `,
});

PromptText is structurally distinct from string and cannot be coerced to text directly. Crux accepts it only in these authored fields:

FieldDirect valueCallback result
prompt.systemExisting system union plus PromptTextExisting sync/async system-result union plus PromptText
prompt.promptstring | PromptTextSynchronous string | PromptText
context.systemExisting context-system union plus PromptTextExisting sync/async context-result union plus PromptText

Canonical Message and MessageContent do not accept PromptText. messages mode has no PromptText-specific lowering or runtime guard.

A direct PromptText Context contribution is static for lifecycle, memo, and provider-cache classification, like a direct string. A callback returning PromptText remains dynamic.

Interpolation Values

The recursive interpolation contract is:

type PromptTextValue =
  | string
  | number
  | PromptText
  | false
  | null
  | undefined
  | readonly PromptTextValue[];

Numbers must be finite. false, null, and undefined omit content. Arrays are snapshotted recursively and are valid only in block position, where items are joined with one newline. Nested PromptText values retain their structural boundaries until resolution. Mutating an input array later cannot change an already-created fragment.

General booleans, true, non-finite numbers, bigint, symbols, functions, Promises, and arbitrary objects are invalid. Use native .join() for an inline scalar list and md.json() for intentional object serialization. Asynchronous interpolation is unsupported; use an outer async system or context.system callback.

Whitespace and Position

Each tagged template is normalized independently:

  1. Leading and trailing blank lines are removed.
  2. The common source-whitespace prefix is removed from nonblank lines.
  3. Relative indentation and intentional internal blank lines remain.

An interpolation alone on its line is a block interpolation. Later lines of its value inherit the carrier line's indentation. An empty block removes its carrier without combining the blank-line runs around it; an equal-size tie keeps the earlier run exactly.

const evidence = md`
  1. First
  2. Second
`;

md`
  - Evidence:
    ${evidence}
`;

The result is:

- Evidence:
  1. First
  2. Second

Every other interpolation is inline. Inline scalar or fragment text is inserted verbatim, including multiline continuation lines. Inline arrays are rejected.

Strings do not pass through this normalization. Existing direct strings and string-returning callbacks preserve their current bytes and inspection shape.

Conditions, Sequences, and Fragments

Control flow stays in TypeScript:

const warning = input.warning && md`> Warning: ${input.warning}`;

const details = md`
  ## Details

  ${warning}

  ${input.events.map((event) => md`- ${event.summary}`)}
`;

Extract a named fragment or helper before deeply nested inline tags become difficult to read. V1 does not add filters, loops, includes, macros, md.join, or a fragment registry.

md.json(value)

md.json(value) snapshots JSON.stringify(value, null, 2) as PromptText. It adds no Markdown fence:

md`
  ```json
  ${md.json(input.account)}
  ```
`;

Native JSON treatment of unsupported object properties and array entries is preserved. A top-level value for which JSON.stringify returns undefined, a cycle, or bigint fails with a stable Crux error. md.json() is not a sanitizer, redactor, canonical key sorter, or security escape hatch.

Resolution and request evidence

.resolve() exposes plain system and prompt strings. preview() reports content-free whole-request fit and prospective adaptations:

import { preview } from "@use-crux/core";

const result = await preview(answer, {
  input: { question: "How do I rotate credentials?" },
  model,
});

result.status;
result.adaptations;

Executed request receipts retain redacted contribution, candidate, and token evidence without copying PromptText content.

Project Index Evidence

Compiler-proven md regions appear as source refs with this metadata:

metadata: {
  promptText: {
    tag: "md";
    language: "markdown";
    lifecycle: "static" | "dynamic";
    sourceKind: "owner" | "named-fragment" | "anonymous-fragment";
    fragmentJoins?: PromptTextFragmentJoinEvidence[];
  }
}

This is source-authoring evidence. It does not render Markdown or mark content trusted, sanitized, or safe. Consumers do not infer fragment identity from names or generic metadata.

Crux follows supported imports, aliases, and re-exports to the canonical md export. Local lookalike tags receive no evidence. If tsconfig.paths can intercept the exact package root, attribution fails closed. Runtime PromptText construction is unaffected.

Exact Runtime Preview Registration

configure() publishes one process-owned, revisioned Prompt catalogue for explicit exact preview:

import { configure } from "@use-crux/core";

const registry = configure({
  prompts: [answer],
});

registry.dispose();

Only explicit Preview or Retry dispatch invokes preview(). Request evidence does not generate with a provider, invoke tools, create an ordinary Run, or emit observability records. Authored callbacks are trusted application code and may perform their own side effects.

Inputs, rendered content, provenance, validation details, and results remain private in-memory request data. config() remains project and runtime policy; it is not a Prompt catalogue. Contexts are not exact-preview targets.

Errors

CodeConditionRemedy
CRUX_PROMPT_TEXT_INVALID_INTERPOLATIONUnsupported interpolation valueSelect a scalar, fragment, or intentional md.json() value
CRUX_PROMPT_TEXT_INLINE_SEQUENCEArray interpolated inlineMove it to a block line or join scalar values with native .join()
CRUX_PROMPT_TEXT_JSON_SERIALIZATIONmd.json() cannot produce JSON textRemove cycles or bigint, or serialize explicitly before interpolation

Errors include the zero-based interpolation index and nested array path. Formatting does not stringify rejected values or expose secret content.

Security and Non-goals

md composes text only. It does not mark content trusted, sanitize nested JSON, bypass schema validation or auto-escape, suppress diagnostics, or change rawFields. There is no md.raw, md.trusted, or md.safe.

V1 does not add runtime Markdown rendering, a Prompt DSL or compiler transform, standalone Prompt files, asynchronous interpolation, message or multimodal construction, semantic section tags, md.use, or Context-placement placeholders. Editor support is an additive source view and never changes PromptText resolution.

On this page