# AiToolCallView
**Also known as:** FunctionCall
## Description
BitAiToolCallView shows a tool call as one card: what is being called, what it was given, how it is going and what came back. The arguments are shown as the model writes them, the progress a running tool reports as a bar, the result as text or a JSON tree, a failure as a compact error, and a call waiting for a human decision closes with a BitAiToolApproval. The card starts collapsed and opens by itself when something needs attention. A renderer registered for a tool replaces the card wherever that tool is called.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Call | `BitAiToolCallPart` | | The tool call to render (required): its tool, arguments, status, progress, result and approval. The card subscribes to the call and re-renders itself whenever it changes - at most once per BitAiUiOptions.RenderInterval - so nothing has to re-render it while the call streams or runs. Its Definition names the tool (Title, else the tool name) and picks the icon of the header (Icon) - until a session sets it, the definition registered for the tool does - and the Risk of the Definition decides how an approval prompt looks. |
| Classes | `BitAiToolCallViewClassStyles?` | null | Custom CSS classes for different parts of the tool call view. |
| Message | `BitAiMessage?` | null | The message the call belongs to. Only a custom tool renderer reads it (BitAiToolCallContext.Message); left unset, the renderer is handed an empty assistant message. |
| Session | `BitAiChatSession?` | null | The session driving the conversation. The approval prompt of a call awaiting a decision hands the decision to it (BitAiChatSession.Approve), which is what lets the paused turn carry on; without one the decision is only recorded on the call. A custom tool renderer is handed it as well (BitAiToolCallContext.Session). |
| Styles | `BitAiToolCallViewClassStyles?` | null | Custom CSS styles for different parts of the tool call view. |
| AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. |
| Disabled | `bool` | false | Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction. |
| ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name. |
| Id | `string?` | null | Gets or sets the unique identifier for the component's root element. |
| Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
## Public Members
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. |
## Enums
### BitAiToolCallStatus Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Streaming | 0 | The model has named the tool and is still writing the arguments. The card is outlined as running and shows the busy dots. |
| Pending | 1 | The arguments are complete and the call has not started running. |
| AwaitingApproval | 2 | The call waits for a human to approve or decline it. The card is outlined in the warning color, opens by itself and shows the approval prompt. |
| Rejected | 3 | A human declined the call, so it will not run. The card is outlined in the error color. |
| Running | 4 | The tool is running. The card is outlined as running and shows the busy dots and the progress the tool reports. |
| Completed | 5 | The tool produced a result. The header shows how long it took. |
| Failed | 6 | The tool threw or returned a failure. The card is outlined in the error color and opens by itself. |
| Cancelled | 7 | The turn was stopped before the tool produced a result. |
### BitVisibility Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
### BitDir Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
## Sub Classes
### BitAiToolCallViewClassStyles Properties
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiToolCallView. |
| Header | `string?` | null | Custom CSS classes/styles for the header (the button that expands and collapses the card) of the BitAiToolCallView. |
| ExpanderIcon | `string?` | null | Custom CSS classes/styles for the expander chevron of the BitAiToolCallView. |
| Icon | `string?` | null | Custom CSS classes/styles for the tool icon of the BitAiToolCallView. |
| Title | `string?` | null | Custom CSS classes/styles for the title of the BitAiToolCallView. |
| Status | `string?` | null | Custom CSS classes/styles for the status (the busy dots, or the duration of a finished call) of the BitAiToolCallView. |
| Spinner | `BitLoadingClassStyles?` | null | Custom CSS classes/styles for the busy dots (a BitBouncingDotsLoading) of a running call of the BitAiToolCallView. |
| Progress | `BitProgressClassStyles?` | null | Custom CSS classes/styles for the progress bar (a BitProgress) of the BitAiToolCallView. |
| Body | `string?` | null | Custom CSS classes/styles for the body of the BitAiToolCallView. |
| ProgressMessage | `string?` | null | Custom CSS classes/styles for the progress message in the body of the BitAiToolCallView. |
| Section | `string?` | null | Custom CSS classes/styles for each section (the arguments, the result) of the body of the BitAiToolCallView. |
| Label | `string?` | null | Custom CSS classes/styles for the label (the heading) of each section of the BitAiToolCallView. |
| RawText | `string?` | null | Custom CSS classes/styles for the raw text of the arguments (before they parse) or of a textual result of the BitAiToolCallView. |
| Approval | `BitAiToolApprovalClassStyles?` | null | Custom CSS classes/styles for the approval prompt (a BitAiToolApproval) of the BitAiToolCallView. |
### BitAiToolCallPart Properties
A tool call and everything that happens to it - streaming arguments, an approval decision, progress and finally a result - as one message part (Kind "tool-call"). A session creates and drives it from the events of its transport; a page or a test can drive one by hand with the methods below. Every change raises the part's Changed event, which is what the card listens to. Thread-safe: the arguments may stream in on the transport's thread while the card reads them.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| BitAiToolCallPart(toolCallId, toolName, partId?) | `constructor` | | Creates a call in the Streaming status, with no arguments yet. toolCallId is the provider's identifier of the call (a result is matched back to the call by it), toolName the tool being called; partId is generated when left out. |
| ToolCallId | `string` | | The call identifier used by the provider. |
| ToolName | `string` | | The tool being called, which is also what a custom tool renderer is registered against. |
| Definition | `BitAiToolDefinition?` | null | The definition of the tool, resolved from the tool registry when the session starts running the call. The card is headed by its DisplayName and draws its Icon, and its Risk decides how the approval prompt looks. Until it is set, the card reads the definition registered for the tool with AddBitBlazorUIAIServices (IBitAiToolRegistry, see its table); a tool the host does not know is shown by its raw name, under the wrench. |
| Status | `BitAiToolCallStatus` | BitAiToolCallStatus.Streaming | Where the call is in its lifecycle, which decides the heading, the outline and whether the card opens by itself. |
| IsStreaming | `bool` | true | Whether the arguments are still streaming (Status is Streaming). |
| RawArguments | `string` | "" | The raw JSON argument text received so far. |
| Arguments | `JsonElement?` | null | The parsed arguments, available once CompleteArguments has been called. |
| DisplayArguments | `JsonElement?` | null | The best view of the arguments there is: the parsed ones once complete, else a lenient parse of the partial JSON received so far - which is how the card shows city: "Os..." while the model is still writing. Recomputed at most once per change, and only when read. |
| Approval | `BitAiApprovalDecision` | BitAiApprovalDecision.Undecided | The human decision on the call, when one was asked for. |
| ApprovalReason | `string?` | null | Why the approval was asked for. The approval prompt shows it in place of its own question. |
| Progress | `BitAiToolProgress?` | null | The latest progress the running tool reported, drawn as a bar under the header. Cleared when the result arrives. |
| Result | `BitAiToolOutcome?` | null | The result, once the tool has produced one. |
| StartedAt | `DateTimeOffset?` | null | When the call first moved to Running. |
| CompletedAt | `DateTimeOffset?` | null | When the result was recorded. |
| Duration | `TimeSpan?` | null | How long the call took: the Duration of its Result, else the time between StartedAt and CompletedAt. The header of a finished call shows it. |
| AppendArguments(delta) | `void` | | Appends a fragment of the raw JSON arguments. An empty fragment is ignored. |
| CompleteArguments(arguments?) | `void` | | Marks the arguments complete - the parsed value given, or a parse of the text received so far - and moves a Streaming call to Pending. |
| SetStatus(status) | `void` | | Moves the call to a status. Moving it to Running records StartedAt. |
| SetApproval(decision) | `void` | | Records a human decision: Rejected moves the call to Rejected, Approved and ApprovedAlways to Pending. A session records it through BitAiChatSession.Approve, which also resumes the turn waiting for it. |
| SetProgress(progress) | `void` | | Records a progress report of the running tool. A tool reports through BitAiToolInvocation.Progress, which the session forwards here. |
| SetResult(outcome) | `void` | | Records the result, clears the progress and moves the call to Completed or, for a failed outcome, Failed. |
| PartId | `string` | | The identifier of the part (inherited from BitAiMessagePart, like the four members below): the partId given to the constructor, else a generated one. A good @key for the card. |
| Kind | `string` | "tool-call" | The kind of the part (BitAiMessagePartKinds.ToolCall), which BitAiMessagePartView renders with this card. |
| Version | `int` | 0 | Increments on every change, for a cheap ShouldRender of a view of the app's own. |
| Message | `BitAiMessage?` | null | The message the call belongs to, once it has been added to one. |
| Changed | `event Action<BitAiMessagePart>` | | Raised after every change of the call, synchronously on the thread that made it. The card subscribes to it, so it follows its call without the page re-rendering it. |
### BitAiToolProgress Properties
One progress report of a running tool (a readonly record struct), sent through BitAiToolInvocation.Progress.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Fraction | `double?` | null | How much is done, from 0 to 1 (clamped). Null when the tool cannot measure it, which the bar shows by sweeping. |
| Message | `string?` | null | A short status line, such as the current sub-step. It is the accessible name of the bar and leads the body of an open card. |
### BitAiToolOutcome Properties
The result of one tool call: the structured Value that goes back to the model and the optional Text the card shows instead of it. Created through its factory methods.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| IsSuccess | `bool` | | Whether the call succeeded. |
| Value | `JsonElement?` | null | The structured result, sent back to the model. The card shows it as a JSON tree when there is no Text. |
| Text | `string?` | null | A human-readable rendering of the result, which the card shows in place of the Value, as it was written. |
| Error | `BitAiError?` | null | The failure, when IsSuccess is false. The card shows it as a compact BitAiErrorView. |
| Citations | `IReadOnlyList<BitAiCitation>?` | null | The sources the tool consulted, merged into the citations of the message. |
| Duration | `TimeSpan?` | null | How long the call took. The session fills it in when the tool does not. |
| Metadata | `IReadOnlyDictionary<string, object?>?` | null | Values of the application's own, carried through to custom renderers. |
| Success(value, text?) | `BitAiToolOutcome` | | A successful outcome carrying a JsonElement, and optionally the text to show for it. |
| Success<T>(value, typeInfo) | `BitAiToolOutcome` | | A successful outcome serialized with the JsonTypeInfo<T> of the app's own JsonSerializerContext, which is safe to trim. An overload taking JsonSerializerOptions serializes by reflection instead. |
| FromText(text) | `BitAiToolOutcome` | | A successful outcome carrying only text (sent to the model as a JSON string). |
| Failure(error) | `BitAiToolOutcome` | | A failed outcome carrying a BitAiError; Failure(message) makes one of the Tool kind from a message. |
| GetDisplayText() | `string?` | | The text to display for the outcome: its Text, else the message of its Error, else its Value (a string as it is, anything else as JSON). |
### IBitAiToolRegistry Properties
The set of tools available to a conversation, found by the name the model calls them by (case does not matter). AddBitBlazorUIAIServices registers BitAiToolRegistry as a singleton, filled with the tools added through ai.AddTool(...) (BitAiTool.Create makes one); a session runs the tools it finds in it, and a card whose call carries no Definition yet reads the definition of its tool from it.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Tools | `IReadOnlyCollection<IBitAiTool>` | | Every registered tool, each with its Definition. |
| Find(name) | `IBitAiTool?` | | The tool registered under that name, or null - in which case a card with no Definition on its call shows the raw name of the tool under the wrench. |
| Register(tool) | `void` | | Adds a tool, or replaces the one registered under its name - at runtime too, which is how an app enables a capability for some users or conversations only. |
| Unregister(name) | `bool` | | Removes a tool; true when one was removed. |
### BitAiRendererRegistry Properties
Where an app registers its own renderers, which the components resolve before their built-in views. A singleton: register at startup (AddBitBlazorUIAIServices(ai => ai.ConfigureRenderers(renderers => ...))) and treat the entries as fixed afterwards. These are its tool members; the part members are on the AiMessagePartView page.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| RegisterTool(toolName, renderer) | `void` | | Registers a BitAiToolCallRenderer for one tool, matched by the name the model calls it by (case does not matter). Every BitAiToolCallView of that tool renders it in place of its card, inside a BitAiRendererBoundary; a later registration replaces an earlier one. |
| RegisterTool<TComponent>(toolName) | `void` | | Registers a component as the renderer of one tool. The component is handed the BitAiToolCallPart as a parameter named Call. |
| FindTool(toolName) | `BitAiToolCallRenderer?` | | The renderer registered for a tool, or null when the built-in card draws it. |
### BitAiToolCallContext Properties
What a custom tool renderer is handed (a sealed record).
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Call | `BitAiToolCallPart` | | The tool call, with its arguments, status, progress and result. |
| Message | `BitAiMessage` | | The message the call belongs to - the card's Message, or an empty assistant message when it has none. |
| Session | `BitAiChatSession?` | null | The session driving the conversation, when there is one - so a renderer of its own can approve, decline or retry as well as display. |
### BitAiToolCallRenderer Properties
The delegate a custom tool renderer is: public delegate RenderFragment BitAiToolCallRenderer(BitAiToolCallContext context). It is called each time the card's parameters are set, and the markup it returns is rendered again on every change of the call - so read what the markup needs off the call as it renders, rather than when the delegate runs.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| context | `BitAiToolCallContext` | | The call and its surroundings. |
| (return value) | `RenderFragment` | | The markup drawn in place of the card. |
## CSS Variables
Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves.
| Name | Default Value | Description |
| :--- | :------------ | :---------- |
| `--bit-AiToolCallView-background` | var(--bit-clr-bg-sec) | Surface of the card. |
| `--bit-AiToolCallView-border-color` | var(--bit-clr-brd-sec) | Outline of the card at rest, and the rules under its header and above its approval prompt. The outline of a running, failed or waiting call wins over it. |
| `--bit-AiToolCallView-running-border-color` | var(--bit-clr-pri) | Outline of the card of a call that is streaming its arguments or running. |
| `--bit-AiToolCallView-failed-border-color` | var(--bit-clr-err) | Outline of the card of a call that failed or was declined. |
| `--bit-AiToolCallView-approval-border-color` | var(--bit-clr-wrn) | Outline of the card of a call awaiting a decision. |
| `--bit-AiToolCallView-radius` | var(--bit-shp-radius-surface) | Corner radius of the card. |
| `--bit-AiToolCallView-header-color` | var(--bit-clr-fg-sec) | Text and icons of the header. |
| `--bit-AiToolCallView-header-hover-color` | var(--bit-clr-fg-pri) | Text and icons of the header under the pointer. |
| `--bit-AiToolCallView-header-hover-background` | var(--bit-clr-bg-sec-hover) | Surface of the header under the pointer. |
| `--bit-AiToolCallView-body-background` | var(--bit-clr-bg-pri) | Surface of the expanded body. |
| `--bit-AiToolCallView-label-color` | var(--bit-clr-fg-ter) | Labels of the arguments and the result sections. |
## Examples
\n**Basic**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiToolCallPart weatherCall = CreateWeatherCall();
private readonly BitAiUiOptions expandedOptions = BitAiUiOptions.Default with { CollapseToolCalls = false };
private static BitAiToolCallPart CreateWeatherCall()
{
var call = new BitAiToolCallPart("call_weather", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Oslo"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Success(Json("""{"city":"Oslo","degrees":-3,"condition":"Snow","humidity":81}""")) with
{
Duration = TimeSpan.FromMilliseconds(700),
});
return call;
}
```
\n**Tool definition**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
// A tool nobody defined: the card shows its raw name, under the wrench.
private readonly BitAiToolCallPart unknownToolCall = CreateCompletedCall("get_stock_price", null,
"""{"symbol":"MSFT"}""", "MSFT: 431.20 USD");
// A definition of the call's own, which names an icon.
private readonly BitAiToolCallPart iconToolCall = CreateCompletedCall("book_meeting",
new BitAiToolDefinition("book_meeting") { Title = "Book a meeting", Icon = BitIconName.Calendar },
"""{"with":"Ana","on":"Tuesday 10:00"}""", "Booked with Ana for Tuesday at 10:00.");
// No definition on the call: the card reads the one registered for get_weather in Program.cs -
// ai.AddTool(BitAiTool.Create("get_weather", ..., definition => definition with { Title = "Weather", Icon = BitIconName.Globe }))
private readonly BitAiToolCallPart registeredToolCall = CreateCompletedCall("get_weather", null,
"""{"city":"Lisbon"}""", "Lisbon: 17 degrees, clear");
private static BitAiToolCallPart CreateCompletedCall(string toolName, BitAiToolDefinition? definition, string arguments, string result)
{
var call = new BitAiToolCallPart(BitAiId.NewToolCallId(), toolName) { Definition = definition };
call.CompleteArguments(Json(arguments));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.FromText(result) with { Duration = TimeSpan.FromMilliseconds(400) });
return call;
}
```
\n**Lifecycle**:
```razor
Run the tool
@if (indexCall is not null)
{
}
```
```csharp
private BitAiToolCallPart? indexCall;
private bool isIndexing;
private readonly BitAiUiOptions expandedOptions = BitAiUiOptions.Default with { CollapseToolCalls = false };
private async Task RunIndexAsync()
{
isIndexing = true;
indexCall = new BitAiToolCallPart(BitAiId.NewToolCallId(), "index_docs")
{
Definition = new BitAiToolDefinition("index_docs") { Title = "Index the docs" },
};
StateHasChanged();
// The model writes the arguments a few characters at a time; the card shows them as they arrive.
foreach (var slice in new[] { """{"fol""", """der":"do""", """cs/","rec""", """ursive":""", "true}" })
{
await Task.Delay(250);
indexCall.AppendArguments(slice);
}
indexCall.CompleteArguments();
await Task.Delay(400);
indexCall.SetStatus(BitAiToolCallStatus.Running);
indexCall.SetProgress(new BitAiToolProgress(null, "Listing the files"));
await Task.Delay(1200);
indexCall.SetProgress(new BitAiToolProgress(0.4, "Reading 12 of 30 files"));
await Task.Delay(1200);
indexCall.SetProgress(new BitAiToolProgress(0.8, "Reading 24 of 30 files"));
await Task.Delay(1200);
indexCall.SetResult(BitAiToolOutcome.FromText("Indexed 30 files of docs/ (412 passages)."));
isIndexing = false;
}
```
\n**Failure & cancellation**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiToolCallPart failedCall = CreateFailedCall();
private readonly BitAiToolCallPart declinedCall = CreateDeclinedCall();
private static BitAiToolCallPart CreateFailedCall()
{
var call = new BitAiToolCallPart("call_atlantis", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Atlantis"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Failure(new BitAiError("No weather station reports for Atlantis.")
{
Code = "unknown_city",
Kind = BitAiErrorKind.Tool,
}) with
{
Duration = TimeSpan.FromMilliseconds(1200),
});
return call;
}
// What a session records for a call the user declined.
private static BitAiToolCallPart CreateDeclinedCall()
{
var call = new BitAiToolCallPart("call_delete", "delete_records")
{
Definition = new BitAiToolDefinition("delete_records") { Title = "Delete records", Risk = BitAiToolRisk.Destructive },
};
call.CompleteArguments(Json("""{"filter":"status = 'archived'"}"""));
call.SetStatus(BitAiToolCallStatus.AwaitingApproval);
call.SetApproval(BitAiApprovalDecision.Rejected);
call.SetResult(BitAiToolOutcome.Failure(new BitAiError("The user declined this tool call.")
{
Code = "rejected",
Kind = BitAiErrorKind.Tool,
}));
call.SetStatus(BitAiToolCallStatus.Rejected);
return call;
}
// What a session records for a call that was running when the user stopped the turn.
private readonly BitAiToolCallPart cancelledCall = CreateCancelledCall();
private static BitAiToolCallPart CreateCancelledCall()
{
var call = new BitAiToolCallPart("call_export", "export_report")
{
Definition = new BitAiToolDefinition("export_report") { Title = "Export the report" },
};
call.CompleteArguments(Json("""{"format":"pdf","pages":"all"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetStatus(BitAiToolCallStatus.Cancelled);
return call;
}
```
\n**Approval**:
```razor
@implements IAsyncDisposable
Delete the archived records
@if (approvalSession.Conversation.Messages.LastOrDefault(m => m.Role.IsAssistant) is { } approvalMessage)
{
foreach (var call in approvalMessage.PartsOf())
{
}
}
```
```csharp
[Inject] private IBitAiToolRegistry ToolRegistry { get; set; } = default!;
private BitAiRenderScheduler sessionRenders = default!;
private BitAiChatSession approvalSession = default!;
protected override void OnInitialized()
{
// The session raises StateChanged on every event it applies; the scheduler turns those into at most one render
// of the page per frame interval. The cards follow their own calls in between.
sessionRenders = new BitAiRenderScheduler(() => InvokeAsync(StateHasChanged));
// A scripted model that asks for delete_records, then answers once the tool has returned. The tool itself is the
// one registered with AddBitBlazorUIAIServices, declared Destructive; a policy of its own per session keeps an
// "Always allow" to this page.
approvalSession = new BitAiChatSession(
AiDemoToolTransports.CallsTool(
"delete_records",
"""{"filter":"status = 'archived'"}""",
"Deleted 128 archived records."),
tools: ToolRegistry,
approvalPolicy: new BitAiDefaultToolApprovalPolicy());
approvalSession.StateChanged += sessionRenders.Request;
}
private Task AskToDeleteAsync() => approvalSession.SubmitAsync("Delete the archived records");
public async ValueTask DisposeAsync()
{
await approvalSession.DisposeAsync();
await sessionRenders.DisposeAsync();
}
```
\n**Custom renderer**:
```razor
@implements IAsyncDisposable
Search the docs
@if (searchSession.Conversation.Messages.LastOrDefault(m => m.Role.IsAssistant) is { } searchMessage)
{
foreach (var call in searchMessage.PartsOf())
{
}
}
```
```csharp
[Inject] private IBitAiToolRegistry ToolRegistry { get; set; } = default!;
private BitAiRenderScheduler sessionRenders = default!;
private BitAiChatSession searchSession = default!;
protected override void OnInitialized()
{
sessionRenders = new BitAiRenderScheduler(() => InvokeAsync(StateHasChanged));
searchSession = new BitAiChatSession(
AiDemoToolTransports.CallsTool(
"search_docs",
"""{"query":"render modes"}""",
"Two passages matched."),
tools: ToolRegistry);
searchSession.StateChanged += sessionRenders.Request;
}
private Task SearchDocsAsync() => searchSession.SubmitAsync("Find the docs on render modes");
public async ValueTask DisposeAsync()
{
await searchSession.DisposeAsync();
await sessionRenders.DisposeAsync();
}
```
`Program.cs`:
```csharp
builder.Services.AddBitBlazorUIAIServices(ai =>
{
ai.UseTransport(AiDemoTransports.Echo());
// The tool the model calls. Its progress reports reach the card (or the renderer) while it runs.
ai.AddTool(BitAiTool.Create(
"search_docs",
"Search the product documentation and return matching passages.",
AiDemoJsonContext.Default.SearchArgs,
async (args, invocation, token) =>
{
invocation.Progress.Report(new BitAiToolProgress(0.2, "Querying the index"));
await Task.Delay(500, token);
invocation.Progress.Report(new BitAiToolProgress(0.7, "Ranking results"));
await Task.Delay(500, token);
return BitAiToolOutcome.Success(AiDemoCatalog.SearchHits(args.Query), AiDemoJsonContext.Default.SearchResult) with
{
Text = $"2 passages matched “{args.Query}”.",
};
},
definition => definition with { Title = "Search docs", Icon = BitIconName.Search }));
// Wherever search_docs is called, SearchResultCard draws it instead of the default card. The component is handed
// the call as its Call parameter; RegisterTool(name, context => builder => ...) takes a delegate instead.
ai.ConfigureRenderers(renderers => renderers.RegisterTool("search_docs"));
});
```
`SearchResultCard.razor`:
```razor
@using System.Globalization
@using System.Text.Json
@(Call.Status is BitAiToolCallStatus.Completed ? "Searched the documentation" : "Searching the documentation")
@if (Call.Duration is { } duration)
{
}
@code {
// Handed over by the registry, which renders the component with the call as its Call parameter.
[Parameter, EditorRequired] public BitAiToolCallPart Call { get; set; } = default!;
// Checked rather than assumed: a custom renderer is handed data a model produced.
private JsonElement? Hits => Call.Result?.Value is { } value
&& value.ValueKind is JsonValueKind.Object
&& value.TryGetProperty("hits", out var hits)
&& hits.ValueKind is JsonValueKind.Array
? hits
: null;
private string? Query => Call.DisplayArguments is { } args
&& args.ValueKind is JsonValueKind.Object
&& args.TryGetProperty("query", out var value)
? value.GetString()
: null;
}
```
\n**Disabled**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiToolCallPart awaitingCall = CreateAwaitingCall();
private static BitAiToolCallPart CreateAwaitingCall()
{
var call = new BitAiToolCallPart("call_email", "send_email")
{
Definition = new BitAiToolDefinition("send_email") { Title = "Send email", Risk = BitAiToolRisk.Write, RequiresApproval = true },
ApprovalReason = "Send the summary to the 3 people on the thread?",
};
call.CompleteArguments(Json("""{"to":["[email protected]","[email protected]","[email protected]"],"subject":"Weekly summary"}"""));
call.SetStatus(BitAiToolCallStatus.AwaitingApproval);
return call;
}
```
\n**Cascading parameters**:
```razor
```
```csharp
private readonly BitAiToolCallViewParams[] toolCallViewParams =
[
new() { Disabled = true, Styles = new() { Root = "border-style: dashed", Title = "font-style: italic" } },
];
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiToolCallPart weatherCall = CreateWeatherCall();
private readonly BitAiToolCallPart failedCall = CreateFailedCall();
private static BitAiToolCallPart CreateWeatherCall()
{
var call = new BitAiToolCallPart("call_weather", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Oslo"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Success(Json("""{"city":"Oslo","degrees":-3,"condition":"Snow","humidity":81}""")) with
{
Duration = TimeSpan.FromMilliseconds(700),
});
return call;
}
private static BitAiToolCallPart CreateFailedCall()
{
var call = new BitAiToolCallPart("call_atlantis", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Atlantis"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Failure(new BitAiError("No weather station reports for Atlantis.")
{
Code = "unknown_city",
Kind = BitAiErrorKind.Tool,
}) with
{
Duration = TimeSpan.FromMilliseconds(1200),
});
return call;
}
```
\n**Style & Class**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiToolCallPart weatherCall = CreateWeatherCall();
private readonly BitAiToolCallPart failedCall = CreateFailedCall();
private static BitAiToolCallPart CreateWeatherCall()
{
var call = new BitAiToolCallPart("call_weather", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Oslo"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Success(Json("""{"city":"Oslo","degrees":-3,"condition":"Snow","humidity":81}""")) with
{
Duration = TimeSpan.FromMilliseconds(700),
});
return call;
}
private static BitAiToolCallPart CreateFailedCall()
{
var call = new BitAiToolCallPart("call_atlantis", "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "Weather" },
};
call.CompleteArguments(Json("""{"city":"Atlantis"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(BitAiToolOutcome.Failure(new BitAiError("No weather station reports for Atlantis.")
{
Code = "unknown_city",
Kind = BitAiErrorKind.Tool,
}) with
{
Duration = TimeSpan.FromMilliseconds(1200),
});
return call;
}
```
\n**RTL**:
```razor
```
```csharp
private static JsonElement Json(string json) => JsonDocument.Parse(json).RootElement;
private readonly BitAiUiOptions persianOptions = BitAiUiOptions.Default with { Strings = new PersianAiStrings() };
private readonly BitAiToolCallPart rtlCall = CreateRtlCall(succeeded: true);
private readonly BitAiToolCallPart rtlFailedCall = CreateRtlCall(succeeded: false);
private static BitAiToolCallPart CreateRtlCall(bool succeeded)
{
var call = new BitAiToolCallPart(BitAiId.NewToolCallId(), "get_weather")
{
Definition = new BitAiToolDefinition("get_weather") { Title = "آب و هوا" },
};
call.CompleteArguments(Json(succeeded ? """{"city":"تهران"}""" : """{"city":"آتلانتیس"}"""));
call.SetStatus(BitAiToolCallStatus.Running);
call.SetResult(succeeded
? BitAiToolOutcome.FromText("تهران: ۱۸ درجه، آفتابی") with { Duration = TimeSpan.FromMilliseconds(650) }
: BitAiToolOutcome.Failure("هیچ ایستگاه هواشناسیای برای آتلانتیس گزارشی ندارد.") with { Duration = TimeSpan.FromMilliseconds(1200) });
return call;
}
// The words of the card, in Persian.
private sealed class PersianAiStrings : BitAiStrings
{
public override string CallingTool(string tool) => $"در حال اجرای {tool}";
public override string CalledTool(string tool) => $"{tool} اجرا شد";
public override string ToolFailed(string tool) => $"{tool} ناموفق بود";
public override string ToolCancelled(string tool) => $"{tool} لغو شد";
public override string ToolRejected(string tool) => $"{tool} رد شد";
public override string ApproveTool(string tool) => $"اجازهی اجرای {tool} داده شود؟";
public override string ToolAwaitingApproval(string tool) => $"{tool} منتظر اجازهی شماست";
public override string Approve => "اجازه";
public override string ApproveAlways => "همیشه اجازه";
public override string Reject => "رد";
public override string Arguments => "ورودیها";
public override string Result => "نتیجه";
public override string FormatDuration(TimeSpan duration) => $"{duration.TotalSeconds:F1} ثانیه";
}
```
@if (Query is { Length: > 0 } query)
{
}
@query
}
@if (Hits is { } hits)
{
-
@foreach (var hit in hits.EnumerateArray())
{
-
@(hit.TryGetProperty("title", out var title) ? title.GetString() : null)