Resources
API index
Every export of each entry point, read from the published types: a link to the component page that documents it, or its signature and description.
Every export of signoff-ui, by entry point, read from the package's published types when the site builds, so the page lists exactly what the package exports.
The components, the hooks and the types they take each have a component page, with props in full, defaults, examples, keyboard and accessibility, and each entry point links them there. Every other export is here, with its declaration and its description as your editor shows them. Import an export from the entry point it is listed under. The main entry, signoff-ui, also exports everything in signoff-ui/core.
signoff-ui
The components and hooks. They are client modules, each with its own 'use client' directive. For the pure helpers in server code, import from signoff-ui/core.
It also exports everything in signoff-ui/core.
On the component pages: AgentMessage, AgentStatus, ApprovalCard, DiffReview, Markdown, MarkdownRendererProvider, Reasoning, RunMeter, Sources, ToolApprovalBatch, ToolApprovalCard, ToolCallTimeline, useApprovalPolicy, useDiffReview, useRunTiming, AgentMessageProps, AgentStatusProps, ApprovalAuditEvent, ApprovalCardProps, ApprovalChoice, ApprovalOutcome, ApprovalPolicy, ApprovalRuleStorage, DiffReviewDraft, DiffReviewFileState, DiffReviewProps, DiffReviewSelection, DiffViewMode, MarkdownProps, MarkdownRenderer, MarkdownRendererProps, MarkdownRendererProviderProps, ReasoningProps, RunMeterProps, RunTiming, SourcesProps, ToolApprovalBatchProps, ToolApprovalCardProps, ToolApprovalResponse, ToolCallTimelineProps, ToolMeta, UseApprovalPolicyOptions, UseDiffReviewOptions, UseDiffReviewResult.
AgentStatusDot
function AgentStatusDot(props: {
state: AgentState;
className?: string;
}): ReactNode;JsonView
function JsonView(props: JsonViewProps): JSX.Element;
Read-only JSON with light syntax tinting, a copy button and long-output folding.
In the docs: Introduction.
SignoffLabelsProvider
function SignoffLabelsProvider(props: {
labels: SignoffLabelsInput;
children: ReactNode;
}): JSX.Element;Labels for every component inside: a translation of defaultLabels, whole or in part. Providers
nest, the inner one winning key by key. Labels hold functions, so render the provider from a
Client Component (a module with 'use client') and keep the object stable: at module scope, or memoized.
In the docs: Introduction, Labels.
ToolCallDetails
function ToolCallDetails(props: {
part: ToolPart;
meta?: ToolMeta | undefined;
labels?: SignoffLabelsInput | undefined;
}): JSX.Element;The expanded body of a tool call: input, output or error, and approval outcome.
useActivityWindow
function useActivityWindow(active: boolean): ActivityWindow;
Records when active last turned on and off. Restarts when it turns on again.
useHydrated
function useHydrated(): boolean;
False during SSR and hydration, true afterwards. Clock-derived values (live durations, waterfall widths) render only once hydrated so server and client markup match.
usePrefersReducedMotion
function usePrefersReducedMotion(): boolean;
Tracks the user's prefers-reduced-motion setting.
useSignoffLabels
function useSignoffLabels(overrides?: SignoffLabelsInput): SignoffLabels;
Every label in effect here: the English, the providers' over it, and overrides over both.
In the docs: Labels.
useToolTimings
function useToolTimings(parts: readonly AnyUIPart[]): ToolTimings;
Measures tool-call timings client-side by watching AI SDK state transitions, a sub-agent's calls included. Calls that are already settled when first observed (e.g. restored history) get no timing rather than a misleading zero.
In the docs: Introduction.
approvalRequestOf
function approvalRequestOf(part: ToolPart): ApprovalRequest | undefined;
The request a tool part makes, for a policy.
memoryRuleStorage
function memoryRuleStorage(initial?: readonly ApprovalRule[]): ApprovalRuleStorage;
Rules kept in memory, for this page only: the default.
webStorageRules
function webStorageRules(key?: string, storage?: () => Storage | undefined): ApprovalRuleStorage;
Rules kept in localStorage (or another Storage) as JSON under key, and the ids of default
rules the person removed under <key>:removed-defaults. A rule with a when is not kept: JSON
can't hold the code. Nothing is read or written on the server.
In the docs: useApprovalPolicy.
DiffWorkerFactory
type DiffWorkerFactory = () => Worker;
Starts a worker that answers DiffReview's diff requests: see lib/diff-worker.ts.
JsonViewProps
interface JsonViewProps {
value: unknown;
label: string;
collapseAfter?: number;
className?: string;
labels?: SignoffLabelsInput | undefined;
}RunTimingMessage
interface RunTimingMessage {
id: string;
role: string;
}What useRunTiming reads of a message. AI SDK UIMessages and useAgUiAgent's messages fit.
ShownContext
interface ShownContext {
start: number;
end: number;
}Unchanged lines shown in a gap: from its start (after the hunk above) and from its end.
signoff-ui/core
The pure helpers, with no React and no 'use client', so Server Components, Route Handlers and plain Node can call them: approval rules, the review's result and what goes back to the agent, tool and source parts, usage and cost, untrusted text and URLs, and the labels.
On the component pages: AgentState, ApprovalDecision, ApprovalRequest, ApprovalRule, ApprovalRuleLimits, ApprovalStatus, DiffReviewComment, DiffReviewFileResult, DiffReviewRejectedHunk, DiffReviewResult, DiffReviewResultComment, FileChange, FileDecision, HunkDecision, ModelPricing, ReviewItem, RiskLevel, RunUsage, SourceItem, SubagentData, SubagentPart, SubagentStatus, ToolTiming, ToToolApprovalOptions.
addUsage
function addUsage(a: RunUsage | undefined, b: RunUsage | undefined): LanguageModelUsage;
Add two usages field by field. A run that continues after an approval or a
client-side tool is a new request with its own totalUsage, and useChat
replaces metadata.usage rather than adding to it, so sum on the server:
addUsage(lastMessage.metadata?.usage, part.totalUsage).
In the docs: Introduction, Getting started, Performance, RunMeter.
applyHunks
function applyHunks(file: Pick<ParsedFileDiff, 'hunks' | 'oldContent' | 'newContent'>, accepted: ReadonlySet<string> | readonly string[]): string;
Apply only the accepted hunks to the original contents.
Accepting every hunk yields newContent; rejecting every hunk yields oldContent.
In the docs: Introduction, Getting started, Performance, DiffReview.
cn
function cn(...inputs: ClassValue[]): string;
Merge class names, resolving Tailwind utility conflicts (last one wins).
computeReviewResult
function computeReviewResult(files: readonly ParsedFileDiff[], decisions: Readonly<Record<string, HunkDecision>>, options?: {
comments?: readonly DiffReviewComment[];
viewed?: Readonly<Record<string, boolean>>;
}): DiffReviewResult;Compute the review result for a set of parsed files, decisions, comments and viewed files.
In the docs: DiffReview.
contextGaps
function contextGaps(file: ParsedFileDiff): ContextGap[];
The unchanged lines around a file's hunks. Only with both contents: a patch alone has none.
decisionEffect
function decisionEffect(decision: ApprovalDecision): {
approved: boolean;
scope: 'once' | 'session' | 'always';
};What a decision does now, and the rule it leaves, if any.
decisionsFromAcpOptions
function decisionsFromAcpOptions(options: readonly AcpPermissionOption[]): ApprovalDecision[];
The decisions to offer for an ACP request: one per kind of option the agent sent, plus
allow-session, which the client keeps as a rule of its own (ACP has no session option). A kind
the agent sent several options of is left out, since a button can't say which one it picks:
Claude Code's ExitPlanMode sends up to three allow_always options, one of them clearing the
context and switching to auto mode, so it gets Approve and Deny but no Always. Deny stays
whenever the agent sent a reject_once, and answers its first one.
In the docs: ACP, useApprovalPolicy.
decisionsFromToolOutput
function decisionsFromToolOutput(output: unknown): Record<string, HunkDecision>;
The decisions a review's tool output records, keyed as DiffReview's decisions: to show a
review again from its saved result, read-only, with the same files. Anything that is not such
an output, or one made before it recorded decisions, gives none.
In the docs: CopilotKit, DiffReview.
deriveAgentState
function deriveAgentState(options: {
status: ChatStatus;
message?: Pick<UIMessage, 'role' | 'parts'> | undefined;
pendingClientTools?: readonly string[];
clientTools?: readonly string[];
writingResponse?: string;
}): DerivedAgentState;Map useChat status plus the latest assistant message to a single agent state.
Human-in-the-loop waits (approvals, client-side tools awaiting output) take
precedence over transport status, because the run cannot progress without the user.
A run that ended (ready) with work still unfinished in the message is stopped, not done:
text or reasoning still streaming, a tool call still preparing, running or partial, or a
sub-agent still running. That is what stop() leaves behind, and what an AG-UI run cancelled or
aborted mid-way does. A client-side tool the app runs itself is the exception: name it in
clientTools.
In the docs: Introduction, Getting started, AG-UI, Labels.
describeRule
function describeRule(rule: Pick<ApprovalRule, 'tool' | 'args'>): string;
A rule in words: "run_command with command npm test*".
In the docs: Labels, useApprovalPolicy.
estimateCost
function estimateCost(usage: RunUsage | undefined, pricing: ModelPricing): CostBreakdown;
Estimate the cost of a run from AI SDK usage and per-million-token pricing.
In the docs: Introduction, Performance.
evaluateRules
function evaluateRules(rules: readonly ApprovalRule[], toolName: string, input: unknown): RuleMatch | undefined;
The rule that decides a call, if any. A deny rule wins over an allow rule, whatever their
order; among rules of the same effect the most specific is the one reported. Each rule's matching
takes a bounded amount of work, in proportion to the arguments it reads, an array's item counting
as one character at least: far more than common patterns need, however long the arguments. The
bound is each rule's, so a call's work grows with the number of rules times the arguments' length,
and a deny rule reads a string up to twice (whole, then by command) and an array up to four times
(all its items in one pass, each once however often it comes, then the items joined, each both
ways): cap the arguments where a client sends rules. A rule that runs out before it can tell
counts against the call: an allow rule allows nothing, and a deny rule denies, reported as
unchecked when no other deny rule covers the call.
formatCost
function formatCost(usd: number | undefined | null): string;
formatDuration
function formatDuration(ms: number | undefined | null): string;
Formatting helpers shared by the components. All output is locale-stable.
formatTokens
function formatTokens(n: number | undefined | null): string;
fromAcpPermissionRequest
function fromAcpPermissionRequest(request: AcpRequestPermissionRequest): ApprovalRequest & {
title?: string;
};An ACP permission request as the card's request: the tool call's id, name, and raw input.
In the docs: useApprovalPolicy.
fromAcpPermissionResponse
function fromAcpPermissionResponse(response: AcpRequestPermissionResponse, options: readonly AcpPermissionOption[]): ApprovalDecision | 'cancelled';
An ACP response as the decision it records, or cancelled.
getAllToolParts
function getAllToolParts(parts: readonly AnyUIPart[]): ToolPart[];
Every tool call in parts, with those of its sub-agents, at any depth.
In the docs: useApprovalPolicy.
getApprovalStatus
function getApprovalStatus(part: ToolPart): ApprovalStatus | undefined;
The approval state of a tool part, or undefined for parts outside the approval flow.
getSourceParts
function getSourceParts(parts: readonly AnyUIPart[]): SourcePart[];
Collect sources, de-duplicated by URL (or source id for documents).
getToolPartName
function getToolPartName(part: ToolPart): string;
Same semantics as the SDK's getToolName, without the runtime import.
In the docs: Introduction, Getting started, Chat UIs, useApprovalPolicy.
getToolParts
function getToolParts(parts: readonly AnyUIPart[]): ToolPart[];
getToolPhase
function getToolPhase(part: Pick<ToolPart, 'state'> & {
approval?: {
approved?: boolean;
isAutomatic?: boolean;
} | undefined;
preliminary?: boolean | undefined;
}): ToolPhase;globToRegExp
function globToRegExp(pattern: string): RegExp;
A glob as a regular expression over the whole value:
*matches any run of characters but shell control characters (;,&,|,`,<,>, line breaks) and$(;?matches one such character;**matches any run of characters at all;\makes the next character literal; everything else is literal.
The expression backtracks: with a few * and a long value it doesn't match, a test can take
seconds. matchesGlob gives the same answers without backtracking.
humanizeToolName
function humanizeToolName(name: string): string;
read_file / readFile / read-file → "Read file".
In the docs: Getting started.
inferLanguage
function inferLanguage(path: string): string;
isSourcePart
function isSourcePart(part: {
type: string;
}): part is SourcePart;isSubagentPart
function isSubagentPart(part: {
type: string;
}): part is SubagentPart;isToolPart
function isToolPart(part: {
type: string;
}): part is ToolPart;In the docs: Introduction, Chat UIs.
matchesGlob
function matchesGlob(value: string, pattern: string): boolean;
Whether a whole value matches a glob (see globToRegExp). It reads the value once, keeping the
places in the pattern that match so far, so its time grows with the value's length times the
pattern's, and no more: a long argument written by a model can't hold up the server or the page.
mergeLabels
function mergeLabels<T extends SignoffLabelsInput>(base: T, ...overrides: (SignoffLabelsInput | undefined)[]): T;
Labels with some replaced: plain objects merge key by key, strings and functions replace.
In the docs: Labels.
observeToolTimings
function observeToolTimings(prev: ToolTimings, tools: readonly ToolPart[], now: number): ToolTimings;
Pure reducer behind useToolTimings, exported for tests and custom stores.
parseApprovalRules
function parseApprovalRules(value: unknown, options?: ApprovalRuleLimits): ApprovalRule[];
The rules a client sent, such as a request body's rules, checked on the server: each field of
the right type and within limits, other fields dropped. A rule that fails a check is left out,
and so is everything when value is not an array. A deny rule left out takes with it the allow
rules it could overrule: those on a tool it names, or all of them when its tool is missing, not
a string or too long, when it comes after the first maxRules, or when the deny rules left out
name more than 10 tools. A rule left out still holds on the page, which answers the call when the
server asks.
In the docs: Introduction, Getting started, Performance, Comparison.
parseFileChange
function parseFileChange(change: FileChange, options?: ParseFileChangeOptions): ParsedFileDiff;
Turn a FileChange into hunks with line numbers and word-level segments. Contents are diffed;
past maxEditLength the changed region is one replacing hunk (fallback: 'replace').
In the docs: Getting started, Performance, DiffReview.
revealText
function revealText(text: string, options?: RevealTextOptions): RevealedPart[];
The text in parts, with marks in place of what would make it read as something it is not:
characters that do not show, such as a bidi override (U+202E), which shows the text after it
reversed, or a zero-width space, and with blanks, long runs of spaces or of blank lines. Text
with none of them is one part, the text itself. The joiners and variation selectors in an emoji
are left as they are.
In the docs: Performance.
reviewItems
function reviewItems(file: ParsedFileDiff): ReviewItem[];
What a file asks to be decided: its hunks, or the file itself when it has none but changed.
reviewResumeEntry
function reviewResumeEntry(interruptId: string, result: DiffReviewResult, options?: DiffReviewToolOutputOptions): DiffReviewResumeEntry;
The review as the answer to an AG-UI interrupt: pass it to useAgUiAgent's resolve, or put it
in RunAgentInput.resume yourself. The payload is reviewToolOutput(result, options).
In the docs: DiffReview.
reviewToolOutput
function reviewToolOutput(result: DiffReviewResult, options?: DiffReviewToolOutputOptions): DiffReviewToolOutput;
A review result shaped for the agent: a one-line summary, each file's decision (with its contents
by default, and viewed: true when the reviewer marked it viewed), the rejected hunks with their
lines, every comment with its lines, and each decided hunk by id, so the review can be shown
again from it (decisionsFromToolOutput). JSON, so it is an AI SDK tool output as it is:
addToolOutput({ tool, toolCallId, output: reviewToolOutput(result) }).
The summary is written in English for the model, whatever language the UI is in.
In the docs: Introduction, Getting started, Chat UIs, CopilotKit.
riskFromMcpAnnotations
function riskFromMcpAnnotations(annotations: unknown): RiskLevel | undefined;
A risk level from an MCP tool's annotations, for ToolMeta.risk or the card's risk. AI SDK 7's
MCP client puts them on each tool's metadata.annotations, and on its calls' parts as
toolMetadata.annotations. The first that applies: 'high' for a tool its server marks
destructive (destructiveHint: true), 'low' for one it marks read-only (readOnlyHint: true),
and 'medium' for one it marks as changing things (readOnlyHint: false) or as reaching outside
systems, such as the web (openWorldHint: true).
Only hints the server set count. The MCP spec reads a missing destructiveHint or
openWorldHint as true, which would rate every tool with no annotations as risky: a tool with
none of these hints gets undefined, and its card shows no risk. A value other than true or
false counts as missing.
A hint, not a guarantee: a server describes its own tools, and one you don't trust can mark a tool that deletes as read-only. Show it to a person; don't let it decide an approval.
In the docs: useApprovalPolicy.
ruleFromDecision
function ruleFromDecision(decision: ApprovalDecision, request: Pick<ApprovalRequest, 'toolName'>, options?: {
args?: Record<string, string> | undefined;
reason?: string;
id?: string;
now?: number;
}): ApprovalRule | undefined;The rule a decision leaves: none for allow-once and deny-once. args narrows it to some
arguments; without it, it covers every call of the tool.
ruleMatches
function ruleMatches(rule: ApprovalRule, toolName: string, input: unknown): boolean;
Whether a rule applies to a call: its tool, every argument pattern, and its own test. A deny rule
reads more than an allow rule, to hold when a command is written another way: each command of a
shell line, from its first word past NAME=value settings, a path and a redirect, or after sudo,
env, sh -c, find -exec… (see commands); an array by its items; and an object, or a when
that throws, as covered. Matching takes a bounded amount of work (see evaluateRules), and what
it can't finish counts against the call: an allow rule doesn't apply, a deny rule does.
safeUrl
function safeUrl(url: string | null | undefined, options?: SafeUrlOptions): string | undefined;
The URL when a link may use it: http, https, mailto, a relative URL, and with files, data:
and blob:. Anything else, javascript: first, is undefined, to render as text instead: React
18 renders a javascript: link as it is, and React 19 only replaces it with one that throws.
In the docs: Performance.
setToolInput
function setToolInput<M extends {
parts: readonly unknown[];
}>(messages: readonly M[], toolCallId: string, input: unknown): M[];Messages with one tool call's input replaced, for approving edited arguments with AI SDK 7: set
them with setMessages (from useChat), then answer the approval. The server runs the call with the
input in the messages, after checking it against the tool's schema and toolApproval again.
When the tool's schema changed what the model sent (a default filled in, a value coerced, a key
dropped), the approval keeps what the model sent as inputSchemaInput, and the server checks the
call against it, so it gets the edit too. The server runs only an edit that comes out of the
schema unchanged, and out of experimental_refineToolInput too when that is set: keep the fields
the schema fills in (the card starts an edit from the input it shows, defaults included), and use
transforms that give the same result when run twice.
With experimental_toolApprovalSecret set, the server signs the input it asked about and refuses
any other, so edits fail there by design.
In the docs: useApprovalPolicy.
suggestArgs
function suggestArgs(input: unknown): Record<string, string> | undefined;
The argument patterns a rule made from this call would start with: for a shell command, its
program and subcommand (npm test*), or the whole command when it has no subcommand or runs what
its arguments say (rm -rf build, node -e "…"), since rm* would allow rm -rf ~; otherwise
each string, number and boolean argument as it is. Globs are escaped. The person can widen them
before saving.
toAcpPermissionResponse
function toAcpPermissionResponse(decision: ApprovalDecision, options: readonly AcpPermissionOption[]): AcpRequestPermissionResponse;
A decision as ACP's response: the agent's one option of the same kind. allow-session selects
allow_once: the session rule stays on the client, so it never grants more than the agent's
"once". With no option of that kind, or several, the request is cancelled, as ACP asks of a
client that can't answer: it never picks one of several grants for the person. deny-once
answers the first of several reject_once options, since a denial grants nothing.
In the docs: ACP, useApprovalPolicy.
toPatch
function toPatch(files: readonly ParsedFileDiff[], decisions: Readonly<Record<string, HunkDecision>>): string;
The accepted changes as a git-style unified diff, from the original files: git apply and
patch -p1 read it. Rejected and undecided changes are left out, and the hunks after them are
renumbered to match. A binary file accepted is listed as changed, without its contents.
In the docs: Performance, DiffReview, useDiffReview.
toPlainRules
function toPlainRules(rules: readonly ApprovalRule[]): ApprovalRule[];
Rules as plain data, to send with a request: copies, as JSON carries them. A rule with a when
is left out, since when is code and without it the rule would cover more calls than it does on
the page. For a deny rule with a when, so are the allow rules on the tools it covers, which it
would overrule on the page: the server asks about those calls, and the page answers them with
every rule.
In the docs: useApprovalPolicy.
toSourceItem
function toSourceItem(source: SourcePart | SourceItem): SourceItem;
toToolApproval
function toToolApproval(rules: readonly ApprovalRule[] | (() => readonly ApprovalRule[]), options?: ToToolApprovalOptions): (options: {
toolCall: {
toolName: string;
input: unknown;
};
}) => ToolApprovalStatusLike;Rules as AI SDK 7's generic toolApproval function: a matching rule approves or denies the call
on the server, with the rule in the reason (reason words it), and any other call gets
otherwise (by default 'user-approval', a person decides). The SDK checks it again on the
approved call before it runs, edited arguments included.
Rules a client sends are the choices of the person using it: take them for that person's own runs only, and keep rules that protect others on the server.
In the docs: Getting started, Labels, Performance, useApprovalPolicy.
toToolApprovalResponse
function toToolApprovalResponse(id: string, decision: ApprovalDecision, reason?: string): {
id: string;
approved: boolean;
reason?: string;
};A decision as the response addToolApprovalResponse (from useChat) takes.
AGENT_STATE_LABEL
const AGENT_STATE_LABEL: Record<AgentState, string>;
The English state names. AgentStatus reads them from labels.agentStatus.states.
APPROVAL_DECISIONS
const APPROVAL_DECISIONS: readonly ApprovalDecision[];
DEFAULT_MAX_EDIT_LENGTH
const DEFAULT_MAX_EDIT_LENGTH = 2000;
The most lines added plus lines removed a file's diff may take before it is shown as one hunk that replaces the changed region. Diffing costs about the square of this number: 2,000 is about 0.2 s on a laptop, in a worker for large files, while 10,000 (a 5,000-line rewrite) was 2.7 s.
defaultLabels
const defaultLabels: SignoffLabels;
The English labels, as the components read without any.
TOOL_PHASE_LABEL
const TOOL_PHASE_LABEL: Record<ToolPhase, string>;
The English phase names. The timeline reads them from labels.toolCallTimeline.phases.
TOOL_STATES
const TOOL_STATES: readonly [
"input-streaming",
"input-available",
"approval-requested",
"approval-responded",
"output-available",
"output-error",
"output-denied"
];AcpPermissionOption
interface AcpPermissionOption {
optionId: string;
name: string;
kind: AcpPermissionOptionKind;
}ACP PermissionOption: a choice the agent offers.
AcpPermissionOptionKind
type AcpPermissionOptionKind = 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always';
ACP PermissionOptionKind.
AcpRequestPermissionRequest
interface AcpRequestPermissionRequest {
sessionId: string;
toolCall: {
toolCallId: string;
title?: string | null | undefined;
name?: string | null | undefined;
kind?: string | null | undefined;
rawInput?: unknown;
};
options: readonly AcpPermissionOption[];
}What toAcpPermissionResponse reads of ACP's RequestPermissionRequest.
AcpRequestPermissionResponse
interface AcpRequestPermissionResponse {
outcome: {
outcome: 'cancelled';
} | {
outcome: 'selected';
optionId: string;
};
}ACP RequestPermissionResponse.
AnyUIPart
type AnyUIPart = UIMessage['parts'][number];
Any part of any UIMessage.
CommentRange
interface CommentRange extends LineRange {
oldStartLine?: number | undefined;
oldEndLine?: number | undefined;
}The lines a comment records: their range, and with removed lines among lines of the proposed file, their range in the original too.
CommentSide
type CommentSide = 'new' | 'old';
ContextGap
interface ContextGap {
before: number;
oldStart: number;
oldEnd: number;
delta: number;
}A gap of unchanged lines between hunks (or before the first, or after the last) that can be shown.
CostBreakdown
interface CostBreakdown {
input: number;
cachedInput: number;
cacheWrite: number;
output: number;
total: number;
}DerivedAgentState
interface DerivedAgentState {
state: AgentState;
detail?: string | undefined;
}DiffHunk
interface DiffHunk {
id: string;
index: number;
oldStart: number;
oldLines: number;
newStart: number;
newLines: number;
header: string;
lines: DiffLine[];
additions: number;
deletions: number;
}DiffLine
interface DiffLine {
type: DiffLineType;
content: string;
oldNumber?: number | undefined;
newNumber?: number | undefined;
segments?: DiffSegment[] | undefined;
noNewline?: true | undefined;
}DiffReviewResumeEntry
interface DiffReviewResumeEntry {
interruptId: string;
status: 'resolved';
payload: DiffReviewToolOutput;
}An AG-UI ResumeEntry answering an interrupt with a review (AgUiResumeEntry, typed here).
DiffReviewToolOutput
interface DiffReviewToolOutput { … }
The review as the agent reads it, for addToolOutput (AI SDK) or an AG-UI resume payload.
In the docs: Getting started.
DiffReviewToolOutputOptions
interface DiffReviewToolOutputOptions {
contents?: boolean;
patch?: string;
}FileStatus
type FileStatus = 'added' | 'deleted' | 'modified' | 'renamed';
HeadingLevel
type HeadingLevel = 2 | 3 | 4 | 5 | 6;
Heading levels a component title can render as.
OfferedChoices
interface OfferedChoices {
approve: boolean;
session: boolean;
always: boolean;
deny: boolean;
alwaysDeny: boolean;
}Which choices an approval card offers, for its keyboard hint.
ParsedFileDiff
interface ParsedFileDiff {
id: string;
path: string;
oldPath?: string | undefined;
status: FileStatus;
language: string;
hunks: DiffHunk[];
additions: number;
deletions: number;
oldContent?: string | undefined;
newContent?: string | undefined;
binary?: true | undefined;
fallback?: 'replace' | undefined;
}ParseFileChangeOptions
interface ParseFileChangeOptions {
context?: number | undefined;
id?: string | undefined;
maxEditLength?: number | undefined;
}RangeLabel
interface RangeLabel {
side: CommentSide;
startLine: number;
endLine: number;
}Lines of a hunk, as a range in one of the files.
RevealedMark
interface RevealedMark {
kind: 'invisible' | 'blank';
text: string;
count: number;
lines: number;
codes: string[];
shown: string;
}A run of characters to show as a mark, in place of the characters.
RevealedPart
type RevealedPart = string | RevealedMark;
A part of the text: as it is, or a mark.
RevealTextOptions
interface RevealTextOptions {
blanks?: boolean | undefined;
}Text and URLs from the agent, shown to the person who decides on them. A prompt injection can write them too, so the components show what would make them read as something other than what runs or opens: characters that do not show, a long run of spaces or of blank lines, and a link whose text names another host. No React here, so servers can call these too.
RuleMatch
interface RuleMatch {
effect: 'allow' | 'deny';
rule: ApprovalRule;
unchecked?: boolean | undefined;
}The rule that decides a call.
SafeUrlOptions
interface SafeUrlOptions {
files?: boolean | undefined;
}SignoffLabels
interface SignoffLabels { … }
In the docs: Labels.
SignoffLabelsInput
type SignoffLabelsInput = {
[K in keyof SignoffLabels]?: DeepPartial<SignoffLabels[K]>;
};A part of the labels: any section, any key, at any depth.
SourcePart
type SourcePart = SourceUrlUIPart | SourceDocumentUIPart;
ToolApprovalStatusLike
type ToolApprovalStatusLike = 'not-applicable' | 'approved' | 'denied' | 'user-approval' | {
type: 'approved' | 'denied' | 'user-approval';
reason?: string;
};AI SDK 7's ToolApprovalStatus, as toToolApproval returns it.
ToolPart
type ToolPart = ToolUIPart<UITools> | DynamicToolUIPart;
A static (tool-${name}) or dynamic tool part.
In the docs: CopilotKit.
ToolPhase
type ToolPhase = 'streaming' | 'running' | 'awaiting-approval' | 'success' | 'error' | 'denied';
A UI-level phase derived from the SDK tool state. Several SDK states collapse into one visual phase (e.g. an approved call that has not returned yet is "running").
ToolState
type ToolState = ToolPart['state'];
The AI SDK v7 tool part states.
ToolTimings
type ToolTimings = Readonly<Record<string, ToolTiming>>;
signoff-ui/ag-ui
AG-UI agents: useAgUiAgent, and the converters and the run reducer it wires to an @ag-ui/client agent. Those are pure, for a store of your own.
On the component pages: useAgUiAgent, AgUiAgentLike, AgUiApprovalResponse, AgUiInterrupt, AgUiResumeEntry, UseAgUiAgentResult.
answerAgUiInterrupt
function answerAgUiInterrupt(run: AgUiRunState, answer: AgUiApprovalResponse | AgUiResumeEntry): AgUiRunState;
Record an answer to an open interrupt. A tool approval becomes the conventional
{ approved, reason? } payload, with editedArgs when the person edited the arguments: AG-UI's
approve-with-edits field, a full replacement of the call's arguments. The same arguments go as
input too, the name earlier versions sent, which is deprecated. Pass a resume entry for other
interrupts.
In the docs: AG-UI.
createAgUiRun
function createAgUiRun(interrupts?: readonly AgUiInterrupt[]): AgUiRunState;
fromAgUiMessages
function fromAgUiMessages(messages: readonly AgUiMessage[], run?: AgUiRunState): UIMessage[];
AG-UI messages as AI SDK UIMessages: each user message, then the assistant, reasoning, tool and
activity messages after it grouped into one assistant message, the way the AI SDK groups a
multi-step run. Tool calls become dynamic tool parts with their results; with the run state,
streaming flags and tool-call interrupts (as approvals) are applied too. Activity messages become
data-${activityType} parts. System and developer messages are left out.
A sub-agent's messages become one data-signoff-subagent part, after the tool call that started
it (SUBAGENT_STARTED's parentToolCallId), which may be another sub-agent's, or else where its
first message was. ToolCallTimeline nests its calls under that call.
In the docs: AG-UI.
fromAgUiUsage
function fromAgUiUsage(usage: AgUiTokenUsage): RunUsage;
An AG-UI TokenUsage entry as AI SDK usage.
getAgUiResume
function getAgUiResume(run: AgUiRunState): AgUiResumeEntry[] | undefined;
The resume entries for the next run, once every open interrupt has an answer.
In the docs: AG-UI.
parsePartialJson
function parsePartialJson(text: string): unknown;
Parse JSON that may be cut off mid-stream, such as tool arguments as they arrive: open strings,
arrays and objects are closed, and a trailing key or partial literal is dropped.
{"query": "stre gives { query: 'stre' }. Returns undefined when nothing parses.
reduceAgUiRun
function reduceAgUiRun(run: AgUiRunState, event: AgUiEvent): AgUiRunState;
Fold one AG-UI event into the run state. Pure, so it fits useReducer or any store;
useAgUiAgent uses it with an @ag-ui/client agent.
In the docs: AG-UI.
AgUiApproval
interface AgUiApproval {
id: string;
approved?: boolean | undefined;
reason?: string | undefined;
requestReason?: string | undefined;
}A decision on a tool-call interrupt, as the tool part shows it.
AgUiContentPart
interface AgUiContentPart {
type: string;
text?: string;
source?: {
type: string;
value: string;
mimeType?: string;
};
}Text, image, audio, video or document content (AG-UI ContentPart).
AgUiEvent
interface AgUiEvent {
type: string;
}An AG-UI event. Only type is required here; the fields each event type carries are read as needed.
AgUiMessage
interface AgUiMessage {
id: string;
role: string;
content?: unknown;
toolCalls?: readonly AgUiToolCall[];
toolCallId?: string;
error?: string;
activityType?: string;
subagentRunId?: string;
}Any AG-UI message: user, assistant, reasoning, tool, activity, system or developer.
AgUiRunState
interface AgUiRunState {
status: ChatStatus;
error?: {
message: string;
code?: string | undefined;
} | undefined;
usage?: RunUsage | undefined;
step?: string | undefined;
interrupts: AgUiInterrupt[];
answers: Record<string, AgUiResumeEntry>;
approvals: Record<string, AgUiApproval>;
streaming: {
messages: string[];
toolCalls: string[];
};
subagents?: AgUiSubagent[] | undefined;
}What the event stream says about runs, beyond the messages themselves.
AgUiSubagent
interface AgUiSubagent {
subagentRunId: string;
name?: string | undefined;
parentToolCallId?: string | undefined;
parentSubagentRunId?: string | undefined;
status: SubagentStatus;
error?: {
message: string;
code?: string | undefined;
} | undefined;
}A sub-agent the runs started (SUBAGENT_STARTED), and how it ended.
AgUiSubscriber
interface AgUiSubscriber {
onRunInitialized?(params: {
messages: readonly AgUiMessage[];
}): void;
onEvent?(params: {
event: AgUiEvent;
}): void;
onMessagesChanged?(params: {
messages: readonly AgUiMessage[];
}): void;
onRunFailed?(params: {
error: Error;
}): void;
onRunFinalized?(params: {
messages: readonly AgUiMessage[];
}): void;
}The subscriber callbacks useAgUiAgent registers (a subset of @ag-ui/client's AgentSubscriber).
AgUiTokenUsage
interface AgUiTokenUsage {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
reasoningTokens?: number;
cachedInputTokens?: number;
cacheWriteInputTokens?: number;
}AG-UI TokenUsage. Totals include cached and reasoning tokens, as in the AI SDK.
AgUiToolCall
interface AgUiToolCall {
id: string;
function: {
name: string;
arguments: string;
};
}signoff-ui/acp
The Agent Client Protocol, with no React: a prompt turn's session/update notifications and permission requests folded into a UIMessage, a tool call's diff content as DiffReview files, and the answer to session/request_permission from the approval card's decision.
It also exports these, listed under signoff-ui/core: AcpPermissionOption, AcpPermissionOptionKind, AcpRequestPermissionRequest, AcpRequestPermissionResponse, decisionsFromAcpOptions, fromAcpPermissionRequest, fromAcpPermissionResponse, toAcpPermissionResponse.
acpToolName
function acpToolName(toolCall: {
name?: string | null | undefined;
kind?: string | null | undefined;
title?: string | null | undefined;
}): string;The tool name rules and cards use for an ACP tool call: its programmatic name when the agent
sends one, else its kind (edit, execute…), else its title.
In the docs: ACP.
answerAcpPermission
function answerAcpPermission(turn: AcpTurn, toolCallId: string, decision: ApprovalDecision | 'cancelled'): AcpTurn;
Record the answer to a tool call's permission request, as the card shows it. Send the agent
toAcpPermissionResponse(decision, options) as the request's response.
In the docs: ACP.
createAcpTurn
function createAcpTurn(): AcpTurn;
A turn that has just been prompted.
In the docs: ACP.
endAcpTurn
function endAcpTurn(turn: AcpTurn, result: {
stopReason: string;
} | {
error: {
message: string;
};
}): AcpTurn;The turn's end: session/prompt's response, or the error it returned.
In the docs: ACP.
fromAcpDiffs
function fromAcpDiffs(content: readonly AcpToolCallContent[] | null | undefined, options?: {
root?: string | undefined;
}): FileChange[];A tool call's diff content as DiffReview files. ACP paths are absolute: pass the session's
cwd as root to show them relative to it. A diff with no oldText is a new file.
In the docs: ACP.
reduceAcpTurn
function reduceAcpTurn(turn: AcpTurn, update: AcpSessionNotification | AcpSessionUpdate): AcpTurn;
Fold one session/update (the notification's params, or its update) into the turn. Message and
thought chunks append to the reply, tool calls and their updates merge by id; plans, commands, mode
and usage updates, and the user's own message chunks, leave it as it was. Pure, for useReducer or
any store.
In the docs: ACP.
requestAcpPermission
function requestAcpPermission(turn: AcpTurn, request: AcpRequestPermissionRequest): AcpTurn;
A session/request_permission request: its tool call waits on a person, with the options offered.
In the docs: ACP.
toAcpDiffs
function toAcpDiffs(files: readonly Pick<FileChange, 'path' | 'oldContent' | 'newContent'>[], options?: {
root?: string | undefined;
}): Extract<AcpToolCallContent, {
type: 'diff';
}>[];Files as ACP diff content, { type: 'diff', path, oldText, newText }: a new file has no
oldText. With root, relative paths are made absolute under it.
In the docs: ACP.
toAcpMessage
function toAcpMessage(turn: AcpTurn, id?: string): UIMessage;
The turn as an assistant UIMessage: text and reasoning parts, and a dynamic tool part per call.
A call waiting on its permission request is approval-requested, with the tool call id as the
approval id; one denied (or whose request was cancelled) is output-denied. The tool's name is
its name, else its kind, else its title; the title is the part's title.
In the docs: ACP.
AcpContentBlock
interface AcpContentBlock {
type: string;
text?: string;
}An ACP ContentBlock. Text is read; images, audio and resources are left out of the message.
AcpPermission
interface AcpPermission {
options: readonly AcpPermissionOption[];
decision?: ApprovalDecision | 'cancelled' | undefined;
}A permission the agent asked for: the options it offered, and once answered, the decision.
AcpSessionNotification
interface AcpSessionNotification {
sessionId: string;
update: AcpSessionUpdate;
}The params of an ACP session/update notification.
AcpSessionUpdate
interface AcpSessionUpdate {
sessionUpdate: string;
}An ACP SessionUpdate. Only sessionUpdate is required here; each kind's fields are read as needed.
AcpToolCall
interface AcpToolCall {
toolCallId: string;
title?: string | null | undefined;
name?: string | null | undefined;
kind?: string | null | undefined;
status?: AcpToolCallStatus | null | undefined;
content?: readonly AcpToolCallContent[] | null | undefined;
locations?: readonly {
path: string;
line?: number | null | undefined;
}[] | null | undefined;
rawInput?: unknown;
rawOutput?: unknown;
}An ACP ToolCall, or a ToolCallUpdate (every field but the id optional, null for unchanged).
AcpToolCallContent
type AcpToolCallContent = {
type: 'content';
content: AcpContentBlock;
} | {
type: 'diff';
path: string;
oldText?: string | null | undefined;
newText: string;
} | {
type: 'terminal';
terminalId: string;
};ACP ToolCallContent: content, a diff, or a terminal.
AcpToolCallStatus
type AcpToolCallStatus = 'pending' | 'in_progress' | 'completed' | 'failed';
ACP ToolCallStatus.
AcpTurn
interface AcpTurn {
status: ChatStatus;
stopReason?: string | undefined;
error?: {
message: string;
} | undefined;
items: readonly AcpTurnItem[];
toolCalls: Readonly<Record<string, AcpToolCall>>;
permissions: Readonly<Record<string, AcpPermission>>;
}One prompt turn of an ACP session, from session/prompt until its response.
In the docs: ACP.
AcpTurnItem
type AcpTurnItem = {
type: 'text' | 'reasoning';
text: string;
} | {
type: 'tool';
toolCallId: string;
};A piece of the agent's reply, in the order it arrived.
signoff-ui/assistant-ui
Tool UIs for assistant-ui: signoffTools() goes in MessagePrimitive.Parts' components.tools. @assistant-ui/react is an optional peer, and only its types are used.
ApprovalToolUI
function ApprovalToolUI(props: ToolCallMessagePartProps): JSX.Element | null;
The approval card for a call at an approval gate (approval on the part), answered through
respondToApproval. Renders nothing for other calls, for a request that is not a plain decision
(a question, a choice to select), and for one cancelled or expired before anyone decided.
In the docs: Chat UIs.
ReviewToolUI
function ReviewToolUI(props: ToolCallMessagePartProps): JSX.Element | null;
A call whose arguments carry files (FileChange[]), reviewed in a DiffReview once its
arguments are in. The review goes back as the call's result, reviewToolOutput(review), and the
review stays on the page, read-only, with the decisions that result records: a thread loaded
again shows each hunk as it was decided. Its viewed marks and comments are not shown again.
In the docs: Chat UIs, DiffReview.
SignoffToolsProvider
function SignoffToolsProvider(props: SignoffToolsProviderProps): JSX.Element;
Options for the tool UIs below it: approval rules, tool labels and risk, review props. Optional.
In the docs: Chat UIs.
signoffTools
function signoffTools(options?: SignoffToolsOptions): {
by_name: Record<string, ToolCallMessagePartComponent>;
Fallback: ToolCallMessagePartComponent;
};components.tools for MessagePrimitive.Parts: a DiffReview for the review tools, and for
every other call the approval card when it has an approval gate, else Fallback. The components
are the same on every call, so it can run on every render.
In the docs: Introduction, Chat UIs, Performance.
SignoffToolsOptions
interface SignoffToolsOptions {
review?: readonly string[] | undefined;
Fallback?: ToolCallMessagePartComponent | undefined;
}SignoffToolsProviderProps
interface SignoffToolsProviderProps {
policy?: ApprovalPolicy | undefined;
tools?: Record<string, ToolMeta> | undefined;
review?: Omit<DiffReviewProps, 'files' | 'onSubmit' | 'readOnly'> | undefined;
approval?: Omit<ToolApprovalCardProps, 'part' | 'onRespond' | 'policy' | 'meta'> | undefined;
output?: DiffReviewToolOutputOptions | undefined;
children?: ReactNode;
}signoff-ui/custom-markdown
AgentMessage and Reasoning for a Markdown renderer of your own, such as Streamdown, without react-markdown: pass renderMarkdown, or set one with MarkdownRendererProvider. Without one, text shows as it is.
It also exports these, listed under signoff-ui: AgentMessageProps, MarkdownRenderer, MarkdownRendererProps, MarkdownRendererProvider, MarkdownRendererProviderProps, ReasoningProps.
On the component pages: AgentMessage, Reasoning.