Pattern · Agents

New

Tool 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
Get SwiftPieces Pro

$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.Call has a stable id, the tool's name, an SF Symbol, a one-line summary, a Status (.running, .done, .failed(error)), an optional duration in seconds, its arguments and an optional result. Call.samples are the four calls in the preview.
  • Call.Payload is .fields([Field]), .json(String) or .text(String). Call.Field is a key and a value.
  • Pass a new array whenever a call starts or finishes. Durations add up into the summary line.
  • isExpanded lets your screen own whether the list is open. Leave it nil and the block opens while calls run and folds once they finish.
  • onRetry receives a failed call; update the call in your array as it runs again. A thrown error shows under it. onCopy replaces UIPasteboard for 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.
  • ToolCallsBlockExample at the end of ToolCallsBlock.swift is the reply in the preview: a question about invoices, four calls arriving and finishing one by one, then the answer. Pass wrapsInNavigationStack: false to push it in your own stack.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • The card is color.surface with radius.lg and SPHairline dividers in separator, inset to the text. The JSON box is fill at radius.sm.
  • The summary and tool names are type.subhead at medium weight, argument summaries and keys type.footnote, durations and section labels type.caption. Values and JSON are the system monospaced design at the footnote size.
  • Text is textPrimary and textSecondary; tool symbols are textSecondary, the folded symbols and chevrons textTertiary. Failures are danger.
  • The block has no accent: it is a record, not an action.
  • Opening and folding use motion.smooth, rows and Copy motion.snappy; both become short fades under Reduce Motion.