Four values complete the vocabulary, so the opaque body is reached only by producers that genuinely promise no shape. `snapshot` — current state a later snapshot supersedes. system-prompt now exposes `renderContextSections()`, the named contributions `renderContextSnapshot()` already joins for the model, so the body attributes each part to the subsystem that produced it instead of re-splitting joined prose. The runtime snapshot, time-context, and tmux-context declare it. `notice` — a one-off account of what just happened, declared by tool-tasks, goal state changes, tool-goal wrap-up, plan-mode switches, and repeat-tool-guard. Its `summary` rides the COLLAPSED row: these five are the majority of shipped producers and none of them needs expanding to be read. The task summary bounds itself because its inputs are unbounded caller text. `relay` — a message another agent addressed to this one; both subagent sources declare it and the body names the sender above what it said. `recall` — material lifted from another session's log. session-reference needed no new field: its references already record retained and omitted counts and the truncation flag, which the body shows first, because recalled context is bounded on the way in. `ContextFormed` is now discriminated by `form`, so a producer cannot declare a shape without the facts that shape is presented from — a notice without its summary, or a snapshot without its sections, fails to compile. Only the two hook bridges stay opaque, by design: their content is whatever an external program printed, so no shape can be promised for it. Unknown kinds and unreadable records land there too.
71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
import { useState } from 'react'
|
|
import type { ContextMessageNode } from '@deepseek-ai/dsh-client-runtime/client'
|
|
import type { ChatViewSlotProps } from '../contract/slots.ts'
|
|
import { IconBrowseOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
import { DisclosureRow } from './DisclosureRow.tsx'
|
|
import { contextBody } from './ContextBody.tsx'
|
|
import css from './ContextInjectionRow.module.css'
|
|
|
|
/** Props for the logged non-user message presentation. */
|
|
export interface ContextInjectionRowProps {
|
|
content: ContextMessageNode['content']
|
|
source: ContextMessageNode['source']
|
|
/** Role and producer name projected from the durable source. */
|
|
provenance: ContextMessageNode['provenance']
|
|
/** Producer-declared information form; null renders the opaque body. */
|
|
form: ContextMessageNode['form']
|
|
/** The owning view's locale seat, passed down as a plain prop. */
|
|
t: ChatViewSlotProps['t']
|
|
}
|
|
|
|
/**
|
|
* Render logged context with the Tool calls disclosure chrome from Figma.
|
|
*
|
|
* The header names the role the context plays and, beside it, the producer the
|
|
* durable source identifies, so a reader can tell an injected skill catalog
|
|
* from a workspace instruction file or a recalled session without expanding.
|
|
* The expanded body follows the producer-declared form; an absent or unknown
|
|
* form renders the opaque body.
|
|
* @param props - Durable content, its projected provenance and form, and the locale seat.
|
|
* @returns A collapsed context row with a bounded, form-specific body.
|
|
*/
|
|
export function ContextInjectionRow({ content, source, provenance, form, t }: ContextInjectionRowProps) {
|
|
const [open, setOpen] = useState(false)
|
|
// Resolved rather than declared: a form whose fields are unreadable renders
|
|
// the opaque body, and the marker must say what the row actually shows.
|
|
const { rendered, summary, body } = contextBody(form, { content, source, t })
|
|
|
|
return (
|
|
<DisclosureRow
|
|
className={css.root}
|
|
icon={<IconBrowseOutline16 size={14} />}
|
|
chevronClassName={css.chevron}
|
|
title={t(provenance.role === 'recall' ? 'message.contextRecall' : 'message.contextInjection')}
|
|
collapsedContent={provenance.label === null ? undefined : (
|
|
/* ToolRow's separator shape: an aria-hidden dot, so the accessible name
|
|
stays the two readable parts and the two disclosure rows expose one
|
|
name shape. A source that names no producer drops the dot with it. */
|
|
<>
|
|
<span className={css.sep} aria-hidden />
|
|
<span className={css.source} data-context-source>{provenance.label}</span>
|
|
{summary !== null && (
|
|
<>
|
|
<span className={css.sep} aria-hidden />
|
|
<span className={css.summary} data-context-summary>{summary}</span>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
keepContentWhenOpen
|
|
open={open}
|
|
expandable
|
|
expandOnRowClick
|
|
onToggle={() => { setOpen(value => !value) }}
|
|
>
|
|
<div className={css.body} data-context-injection-body data-context-form={rendered ?? undefined}>
|
|
{body}
|
|
</div>
|
|
</DisclosureRow>
|
|
)
|
|
}
|