Skip to content
中文

Presentation ​

KIT · constant · Runtime value

Import ​

ts
import { Presentation } from '@jangts/agent-core/kits/presentation';
js
import { Presentation } from '@jangts/agent-core/kits/presentation';

Other public entry points:@jangts/agent-core/types, @jangts/agent-core/kits/presentation, @jangts/agent-core/kits.

Definition ​

Field structure ​

FieldTypeRequiredDescription
replyFormatChecksReadonly<{ bubbles(totalLimit: number): ReplyFormatCheck; textBudget(maxChars: number): ReplyFormatCheck; }>RequiredSee the type definition and usage context.
REPLY_BUBBLE_BOUNDARY"\u001E"RequiredSee the type definition and usage context.

Member index ​

MemberPurpose
defineOutputCatalogSee the overload below for full inputs and outputs.
defineReplyFormatsSee the overload below for full inputs and outputs.
mergeReplyBubblesSee the overload below for full inputs and outputs.
defineTextTemplateSee the overload below for full inputs and outputs.
defineSemanticSlotsSee the overload below for full inputs and outputs.
selectVariantSee the overload below for full inputs and outputs.
resolvePreferenceSee the overload below for full inputs and outputs.
replyBubbleTextsSee the overload below for full inputs and outputs.
publishReplyEventsSee the overload below for full inputs and outputs.
serializeOutputEventsSee the overload below for full inputs and outputs.
textOutputEventsSee the overload below for full inputs and outputs.

Member details ​

defineOutputCatalog ​

ts
Presentation.defineOutputCatalog<App = unknown>(definitions: readonly OutputDefinition[], options?: OutputCatalogOptions<App>): Readonly<{ toModelSchema: (id: string) => { type: string; properties: { [k: string]: Readonly<Record<string, unknown>>; }; required: string[]; additionalProperties: boolean; }; check(request: { outputId: string; value: unknown; }): { status: "passed" | "failed"; value: Record<string, unknown>; issues: OutputIssue[]; }; compose(request: { outputId: string; input: OutputComposeInput; }, context: OperationContext<App>): OutputResult; definitions: readonly Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>[]; ids(): readonly string[]; values(): IterableIterator<Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>>; entries(): IterableIterator<readonly [string, Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>]>; has(id: string): boolean; get(id: string): Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }> | undefined; require(id: string): Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>; }>

Compile output definitions into one catalog.

Compilation is where most mistakes surface: group membership is checked for cycles, master templates must have exactly one slot per field, and every bound/semantic field must name a registered provider. Composition then trusts the compiled catalog.

Type parameterConstraintDefault type
App—unknown
ParameterTypePassingDescription
definitionsreadonly OutputDefinition[]Required—
optionsOutputCatalogOptions<App>Optional—
Fields of options
FieldTypeRequiredDescription
providersreadonly OutputProvider<App>[] | undefinedOptionalRead-only. Providers referenced by bound and semantic fields.
observe((result: OutputResult) => void | Promise<void>) | undefinedOptionalRead-only. Observer invoked with every composed result.

Returns: Readonly<{ toModelSchema: (id: string) => { type: string; properties: { [k: string]: Readonly<Record<string, unknown>>; }; required: string[]; additionalProperties: boolean; }; check(request: { outputId: string; value: unknown; }): { status: "passed" \| "failed"; value: Record<string, unknown>; issues: OutputIssue[]; }; compose(request: { outputId: string; input: OutputComposeInput; }, context: OperationContext<App>): OutputResult; definitions: readonly Readonly<{ id: string; kind: "text" \| "master" \| "record" \| "group"; fields?: readonly OutputField[] \| undefined; master?: string \| undefined; allowedOutputIds?: readonly string[] \| undefined; maxItems?: number \| undefined; }>[]; ids(): readonly string[]; values(): IterableIterator<Readonly<{ id: string; kind: "text" \| "master" \| "record" \| "group"; fields?: readonly OutputField[] \| undefined; master?: string \| undefined; allowedOutputIds?: readonly string[] \| undefined; maxItems?: number \| undefined; }>>; entries(): IterableIterator<readonly [string, Readonly<{ id: string; kind: "text" \| "master" \| "record" \| "group"; fields?: readonly OutputField[] \| undefined; master?: string \| undefined; allowedOutputIds?: readonly string[] \| undefined; maxItems?: number \| undefined; }>]>; has(id: string): boolean; get(id: string): Readonly<{ id: string; kind: "text" \| "master" \| "record" \| "group"; fields?: readonly OutputField[] \| undefined; master?: string \| undefined; allowedOutputIds?: readonly string[] \| undefined; maxItems?: number \| undefined; }> \| undefined; require(id: string): Readonly<{ id: string; kind: "text" \| "master" \| "record" \| "group"; fields?: readonly OutputField[] \| undefined; master?: string \| undefined; allowedOutputIds?: readonly string[] \| undefined; maxItems?: number \| undefined; }>; }>.

Return value structure
FieldTypeRequiredDescription
toModelSchema(id: string) => { type: string; properties: { [k: string]: Readonly<Record<string, unknown>>; }; required: string[]; additionalProperties: boolean; }RequiredJSON schema of the fields a model must generate for this output.
check(request: { outputId: string; value: unknown; }) => { status: "passed" | "failed"; value: Record<string, unknown>; issues: OutputIssue[]; }RequiredValidate a generated value without composing it.
compose(request: { outputId: string; input: OutputComposeInput; }, context: OperationContext<App>) => OutputResultRequiredCompose the final output value from generated fields, providers and constants.
definitionsreadonly Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>[]RequiredRead-only. Definitions in registration order; each entry is frozen by the implementation.
ids() => readonly string[]RequiredIds in the same order as definitions.
values() => IterableIterator<Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>>RequiredSee the type definition and usage context.
entries() => IterableIterator<readonly [string, Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>]>RequiredSee the type definition and usage context.
has(id: string) => booleanRequiredSee the type definition and usage context.
get(id: string) => Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }> | undefinedRequiredReturns the definition or undefined; the caller decides whether absence is an error.
require(id: string) => Readonly<{ id: string; kind: "text" | "master" | "record" | "group"; fields?: readonly OutputField[] | undefined; master?: string | undefined; allowedOutputIds?: readonly string[] | undefined; maxItems?: number | undefined; }>RequiredReturns the definition or throws; use when the id is a required reference.

defineReplyFormats ​

ts
Presentation.defineReplyFormats<Context = unknown>(options: ReplyFormatOptions<Context>): Readonly<{ resolve(context: Context): CompiledReplyFormat | undefined; }>

Compile reply formats so that checks run only for templates the current turn uses.

Type parameterConstraintDefault type
Context—unknown
ParameterTypePassingDescription
optionsReplyFormatOptions<Context>Required—
Fields of options
FieldTypeRequiredDescription
formatsreadonly ReplyFormatDefinition<Context>[]RequiredRead-only. See the type definition and usage context.
checksreadonly ReplyFormatCheck[]RequiredRead-only. See the type definition and usage context.

Returns: Readonly<{ resolve(context: Context): CompiledReplyFormat \| undefined; }>.

Return value structure
FieldTypeRequiredDescription
resolve(context: Context) => CompiledReplyFormat | undefinedRequiredThe template this turn uses, or undefined when it has no output-format requirement.

mergeReplyBubbles ​

ts
Presentation.mergeReplyBubbles(bubbles: readonly string[], limit: number): string[]

Normalize a reply that has no declared format: drop blank bubbles and merge overflow into the last allowed one. Text content is preserved.

ParameterTypePassingDescription
bubblesreadonly string[]Required—
limitnumberRequired—

Returns: string[].

defineTextTemplate ​

ts
Presentation.defineTextTemplate(master: string, options?: TextTemplateOptions): Readonly<{ slots: readonly string[]; render(values: Readonly<Record<string, string>>): string; }>

The program owns the master; inserted model text is a value and is substituted once only.

Substitution is single-pass String.replace: a value that itself contains {{slot}} is left as literal text, so a slot value cannot inject further substitutions.

ParameterTypePassingDescription
masterstringRequired—
optionsTextTemplateOptionsOptional—
Fields of options
FieldTypeRequiredDescription
observe((result: string) => void | Promise<void>) | undefinedOptionalRead-only. Observer invoked with every rendered text.
errors{ readonly missingField?: (key: string) => Error | string; } | undefinedOptionalRead-only. Builds the error for a slot the caller did not supply; receives the slot name.

Returns: Readonly<{ slots: readonly string[]; render(values: Readonly<Record<string, string>>): string; }>.

Return value structure
FieldTypeRequiredDescription
slotsreadonly string[]RequiredSee the type definition and usage context.
render(values: Readonly<Record<string, string>>) => stringRequiredSee the type definition and usage context.

defineSemanticSlots ​

ts
Presentation.defineSemanticSlots(definition: SemanticSlotDefinition): Readonly<{ namespace: string; isValid(text: string): boolean; }>

Models select public semantic keys; hosts retain private values and selection policy.

A text is valid when it uses at most maxOccurrences namespaced slots, every name is a declared key, and no malformed {{namespace remnant is left behind — the last check stops partial or misspelled placeholders from reaching the template engine.

ParameterTypePassingDescription
definitionSemanticSlotDefinitionRequired—
Fields of definition
FieldTypeRequiredDescription
namespacestringRequiredRead-only. Slot namespace; must match ^[a-z][a-z0-9-]*$.
keysreadonly string[]RequiredRead-only. Allowed keys after the namespace; any other key makes the text invalid.
maxOccurrencesnumberRequiredRead-only. Maximum total occurrences across the text; must be a non-negative integer.

Returns: Readonly<{ namespace: string; isValid(text: string): boolean; }>.

Return value structure
FieldTypeRequiredDescription
namespacestringRequiredSee the type definition and usage context.
isValid(text: string) => booleanRequiredSee the type definition and usage context.

selectVariant ​

ts
Presentation.selectVariant<Value>(values: readonly Value[], options: { previous?: Value; randomIndex(limit: number): number; }): Value | undefined

Pick one variant, avoiding an immediate repeat when alternatives exist.

When every candidate equals previous, the full list is used again, so a single-value set still yields a result instead of failing.

Type parameterConstraintDefault type
Value——
ParameterTypePassingDescription
valuesreadonly Value[]Required—
options{ previous?: Value; randomIndex(limit: number): number; }Required—
Fields of options
FieldTypeRequiredDescription
previousValue | undefinedOptionalSee the type definition and usage context.
randomIndex(limit: number) => numberRequiredSee the type definition and usage context.

Returns: Value \| undefined.

Awaited value: Awaited<Value> \| undefined.

resolvePreference ​

ts
Presentation.resolvePreference<Value>(candidates: readonly { value: Value; priority: number; order: number; }[]): Value | undefined

Resolve the preferred candidate.

Highest priority wins; order breaks ties with the later entry first, so recency can be encoded without touching priority. The input array is copied before sorting.

Type parameterConstraintDefault type
Value——
ParameterTypePassingDescription
candidatesreadonly { value: Value; priority: number; order: number; }[]Required—

Returns: Value \| undefined.

Awaited value: Awaited<Value> \| undefined.

replyBubbleTexts ​

ts
Presentation.replyBubbleTexts(reply: ReplyBubbleInput): string[]

Collect the non-empty-text parts of a reply.

ParameterTypePassingDescription
replyReplyBubbleInputRequired—
Fields of reply
FieldTypeRequiredDescription
textunknownOptionalPrimary reply text.
bubblesunknownOptionalAdditional bubbles appended after text, when the value is an array.

Returns: string[].

publishReplyEvents ​

ts
Presentation.publishReplyEvents(request: { text: string; delivery?: ReplyDeliveryOptions; }, context: ExecutionContext): AsyncIterable<AgentOutputEvent>

Stream one reply as paced text chunks and bubble separators.

Publish a validated reply without exposing its internal boundary to consumers. The pacing options are upper bounds, not exact timings: the pause and interval are clamped against maxDurationMs, so added delay never exceeds the budget while every chunk is still emitted.

ParameterTypePassingDescription
request{ text: string; delivery?: ReplyDeliveryOptions; }Required—
contextExecutionContextRequired—
Fields of request
FieldTypeRequiredDescription
textstringRequiredSee the type definition and usage context.
deliveryReplyDeliveryOptions | undefinedOptionalSee the type definition and usage context.
Fields of context
FieldTypeRequiredDescription
loggerSubFlowLog | undefinedOptionalRead-only. Explicit optional recorder for this operation; never inferred from diagnostics.
signalAbortSignalRequiredRead-only. Non-optional cancellation signal; never undefined once execution has started.
diagnosticsDiagnosticContext | undefinedOptionalRead-only. Diagnostic scope of the enclosing call, when recording is enabled.

Returns: AsyncIterable<AgentOutputEvent>.

serializeOutputEvents ​

ts
Presentation.serializeOutputEvents(events: AsyncIterable<AgentOutputEvent>): AsyncIterable<string>

Serialize the event protocol for callers using a text stream.

preview events are dropped because they are not final text; bubbles become the boundary character again, so a consumer can re-split the stream.

ParameterTypePassingDescription
eventsAsyncIterable<AgentOutputEvent>Required—

Returns: AsyncIterable<string>.

textOutputEvents ​

ts
Presentation.textOutputEvents(chunks: AsyncIterable<string>): AsyncIterable<AgentOutputEvent>

Wrap already-chunked text as text events.

ParameterTypePassingDescription
chunksAsyncIterable<string>Required—

Returns: AsyncIterable<AgentOutputEvent>.

Examples ​

First compose one piece of generated text ​

Beginner · Node.js

ts
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog([
  { id: 'reply', kind: 'text', fields: [{ id: 'text', owner: 'generated' }] },
]);
console.log(outputs.toModelSchema('reply'));
const output = outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可定义节点。' } } }, { app: {}, signal: new AbortController().signal });
console.log(output.value);
js
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog([
    { id: 'reply', kind: 'text', fields: [{ id: 'text', owner: 'generated' }] },
]);
console.log(outputs.toModelSchema('reply'));
const output = outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可定义节点。' } } }, { app: {}, signal: new AbortController().signal });
console.log(output.value);

Let the program own the explanatory prefix in the same compose ​

Configuration · Node.js

ts
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog([
  { id: 'reply', kind: 'master', master: '{{prefix}}{{text}}', fields: [
    { id: 'prefix', owner: 'constant', constantValue: '接口说明:' },
    { id: 'text', owner: 'generated' },
  ] },
]);
console.log(outputs.toModelSchema('reply')); // 只有 text 由模型填写
console.log(outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可定义节点。' } } }, { app: {}, signal: new AbortController().signal }).value);
js
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog([
    { id: 'reply', kind: 'master', master: '{{prefix}}{{text}}', fields: [
            { id: 'prefix', owner: 'constant', constantValue: '接口说明:' },
            { id: 'text', owner: 'generated' },
        ] },
]);
console.log(outputs.toModelSchema('reply')); // 只有 text 由模型填写
console.log(outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可定义节点。' } } }, { app: {}, signal: new AbortController().signal }).value);

Add bound fields and semantic keys while keeping the compose entry ​

Extension · Node.js

ts
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog<{ displayName: string }>([
  { id: 'reply', kind: 'record', fields: [
    { id: 'text', owner: 'generated' },
    { id: 'author', owner: 'bound', providerId: 'author' },
    { id: 'tone', owner: 'semantic', providerId: 'tone', semanticKeys: ['brief', 'detailed'] },
  ] },
], { providers: [
  { id: 'author', resolve: (_input, { app }) => app.displayName },
  { id: 'tone', resolve: ({ key }) => key === 'brief' ? '简洁说明' : '详细说明' },
] });
const result = outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可。', tone: 'brief' } } }, { app: { displayName: '示例助手' }, signal: new AbortController().signal });
console.log(result.value);
js
import { Presentation } from '@jangts/agent-core/kits/presentation';
const outputs = Presentation.defineOutputCatalog([
    { id: 'reply', kind: 'record', fields: [
            { id: 'text', owner: 'generated' },
            { id: 'author', owner: 'bound', providerId: 'author' },
            { id: 'tone', owner: 'semantic', providerId: 'tone', semanticKeys: ['brief', 'detailed'] },
        ] },
], { providers: [
        { id: 'author', resolve: (_input, { app }) => app.displayName },
        { id: 'tone', resolve: ({ key }) => key === 'brief' ? '简洁说明' : '详细说明' },
    ] });
const result = outputs.compose({ outputId: 'reply', input: { generated: { text: '普通对象即可。', tone: 'brief' } } }, { app: { displayName: '示例助手' }, signal: new AbortController().signal });
console.log(result.value);

API reference, usage examples and developer guides.