Pattern · Agents
NewTool Calls SwiftUI Block
The tools an agent used in one reply, folded to "Used 4 tools · 3.2s" and opening to each call's arguments and result.
A small, inline record for the top of an assistant message. Folded it is one quiet row; opened, each call shows its symbol, name, argument summary and duration or status, and opens to its arguments and result as key and value pairs or monospaced JSON with Copy. It opens itself while calls run and folds once they finish.
- SwiftUI
- iOS 17.0+
- tool-calls
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- One summary row: tools used, total time and any failures
- A row per call with its symbol, arguments in one line and a spinner, duration or Failed
- Arguments and results as aligned pairs or monospaced JSON with Copy
- Failed calls show the error and Retry
- Opens while calls run and folds once the last one finishes
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
The block sits inside an assistant reply and stays small. Folded, it is one row: a tool symbol, "Used 4 tools · 3.2s", the tools' symbols in a quiet row and a chevron; a failed call adds "· 1 failed" in the danger colour. Opened, it lists a row per call with the tool's symbol, its name, a one-line summary of the arguments and where it stands: a spinner while it runs, its duration once done, or Failed. Each row opens to its arguments as aligned key and value pairs and its result, either the same pairs, plain text, or monospaced JSON that scrolls sideways with Copy, which answers "Copied". A failed call shows its error and, when you pass onRetry, Retry. While calls run the block opens itself and reads "Using tools · 2 of 4 done"; once the last one finishes it folds back to the summary. An empty list reads "No tools used", and nil shows a grey summary row.
Notes
ToolCallsBlock(calls:isExpanded:onRetry:onCopy:)takes the reply's calls in the order they started. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your message column above the reply's text.ToolCallsBlock.Callhas a stableid, the tool'sname, an SF Symbol, a one-linesummary, aStatus(.running,.done,.failed(error)), an optionaldurationin seconds, itsargumentsand an optionalresult.Call.samplesare the four calls in the preview.Call.Payloadis.fields([Field]),.json(String)or.text(String).Call.Fieldis a key and a value.- Pass a new array whenever a call starts or finishes. Durations add up into the summary line.
isExpandedlets your screen own whether the list is open. Leave it nil and the block opens while calls run and folds once they finish.onRetryreceives a failed call; update the call in your array as it runs again. A thrown error shows under it.onCopyreplacesUIPasteboardfor Copy.- Copy plays a success haptic and VoiceOver announces "Copied".
- VoiceOver reads the summary as one button ("Used 4 tools · 3.2s", collapsed) and each call as its name and summary with its state ("Done in 1.4s", "Failed. The file is open in another app"). Argument pairs stack under each other at accessibility sizes, and values and JSON can be selected.
- Reduce Motion turns opening and folding into short fades.
ToolCallsBlockExampleat the end ofToolCallsBlock.swiftis the reply in the preview: a question about invoices, four calls arriving and finishing one by one, then the answer. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - The card is
color.surfacewithradius.lgandSPHairlinedividers inseparator, inset to the text. The JSON box isfillatradius.sm. - The summary and tool names are
type.subheadat medium weight, argument summaries and keystype.footnote, durations and section labelstype.caption. Values and JSON are the system monospaced design at the footnote size. - Text is
textPrimaryandtextSecondary; tool symbols aretextSecondary, the folded symbols and chevronstextTertiary. Failures aredanger. - The block has no accent: it is a record, not an action.
- Opening and folding use
motion.smooth, rows and Copymotion.snappy; both become short fades under Reduce Motion.
03
Keep building
Pieces that work well next to this one.