> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chappiesdk.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Let the Model Call Swift Functions with Host Tools

> Register Swift handlers as host tools so the model can invoke your app's own functions mid-conversation, with full control over approval and permissions.

Host tools are Swift functions your app registers with the Chappie client so the model can call them during a conversation. When the model decides a tool is relevant, Chappie invokes your handler, collects the result, and seamlessly feeds it back into the transcript — all without any extra orchestration code on your part. The two-step setup keeps the model's declaration separate from the Swift implementation: first describe the tool in a `ChappieHarness`, then attach a `ChappieHostTool` handler to the client.

## Register a host tool

<Steps>
  ### Declare the tool in the harness

  Create a `ChappieHarness` and list every tool you want the model to know about using `ChappieHarnessTool`. The `inputSchema` field accepts a `ChappieJSONValue` tree that maps directly to a JSON Schema object — the model uses it to construct well-formed arguments.

  ```swift theme={null}
  let harness = ChappieHarness(
      name: "Inventory Harness",
      summary: "Native iOS surface for inventory questions.",
      capabilities: ["multi-turn transcript"],
      tools: [
          ChappieHarnessTool(
              name: "lookup_item",
              description: "Looks up one inventory item by SKU.",
              inputSchema: .object([
                  "type": .string("object"),
                  "properties": .object([
                      "sku": .object(["type": .string("string")])
                  ]),
                  "required": .array([.string("sku")]),
                  "additionalProperties": .bool(false)
              ])
          )
      ]
  )
  ```

  ### Register the Swift handler

  Create a matching `ChappieHostTool` with the same `name` and an async handler closure. Pass your tools to `Chappie.client(harness:tools:)` — the client matches incoming tool calls by name and dispatches to your handler automatically.

  ```swift theme={null}
  let lookupItem = ChappieHostTool(
      name: "lookup_item",
      description: "Looks up one inventory item by SKU.",
      inputSchema: .object([
          "type": .string("object"),
          "properties": .object([
              "sku": .object(["type": .string("string")])
          ]),
          "required": .array([.string("sku")]),
          "additionalProperties": .bool(false)
      ])
  ) { call in
      // Decode the arguments from the call
      guard case .object(let args) = call.argumentsJSON,
            case .string(let sku) = args["sku"] else {
          return ChappieToolResult(output: #"{"error":"missing sku"}"#)
      }
      // Call your own data layer
      let stock = await InventoryStore.shared.stock(for: sku)
      return ChappieToolResult(output: #"{"sku":"\#(sku)","stock":\#(stock)}"#)
  }

  let client = Chappie.client(harness: harness, tools: [lookupItem])
  let reply = try await client.send("Find alternatives for SKU-123.")
  ```
</Steps>

## Inspect tool call arguments

Every handler receives a `ChappieToolCall` value. Use `argumentsJSON` to get a parsed `ChappieJSONValue?` rather than decoding the raw JSON string yourself.

<Accordion title="ChappieToolCall fields">
  <ParamField path="id" type="String?">
    The response-level output item ID assigned by the backend. May be `nil` for some transport paths.
  </ParamField>

  <ParamField path="callID" type="String">
    The unique call identifier used to correlate the result with the request. Always present.
  </ParamField>

  <ParamField path="name" type="String">
    The tool name as declared in the harness — used by the registry to route to your handler.
  </ParamField>

  <ParamField path="arguments" type="String">
    The raw JSON string the model produced for this call's input arguments.
  </ParamField>

  <ParamField path="argumentsJSON" type="ChappieJSONValue?">
    A convenience computed property that parses `arguments` into a `ChappieJSONValue` tree. Returns `nil` if the string is not valid JSON.
  </ParamField>
</Accordion>

Return your result as a `ChappieToolResult`. Because `ChappieToolResult` conforms to `ExpressibleByStringLiteral`, you can return a plain string literal from your handler when you don't need the `output:` label:

```swift theme={null}
let echoTool = ChappieHostTool(
    name: "echo",
    description: "Echoes the input back."
) { call in
    return "{\"echo\":true}"   // String literal works directly
}
```

## ChappieJSONValue — schema building and argument parsing

`ChappieJSONValue` is the enum used both for defining input schemas and for parsing the arguments the model sends back. Its cases map one-to-one with JSON primitives:

| Case                                  | Swift type |
| ------------------------------------- | ---------- |
| `.string(String)`                     | String     |
| `.number(Double)`                     | Number     |
| `.bool(Bool)`                         | Boolean    |
| `.object([String: ChappieJSONValue])` | Object     |
| `.array([ChappieJSONValue])`          | Array      |
| `.null`                               | Null       |

Use nested `.object` and `.array` values to express any JSON Schema structure your tool requires. `ChappieJSONValue` is `Codable`, so you can also decode model-produced arguments directly.

## Tool approval policies

By default Chappie runs tools automatically. Use `ChappieToolPolicy` to require user approval or block a tool entirely.

<Accordion title="Policy factories">
  <ParamField path="ChappieToolPolicy.automatic" type="static property">
    Runs the tool without prompting. This is the default when you omit the `policy` parameter.
  </ParamField>

  <ParamField path="ChappieToolPolicy.ask(scopes:)" type="static factory">
    Emits an `.approvalRequested` stream event and calls your `toolApprovalHandler` before running the tool. Provide at least one `ChappieToolPermissionScope` to describe what the tool accesses.
  </ParamField>

  <ParamField path="ChappieToolPolicy.deny(scopes:)" type="static factory">
    Blocks the tool unconditionally. The handler is never called; the stream receives a `.toolCallCompleted` event with `status == .denied`.
  </ParamField>
</Accordion>

### Permission scopes

Pass one or more `ChappieToolPermissionScope` values to `.ask` or `.deny` so your approval UI can present a meaningful description of what the tool needs:

| Scope                | Meaning                                                      |
| -------------------- | ------------------------------------------------------------ |
| `.hostTool`          | Generic host-app function                                    |
| `.contacts`          | Access to contacts                                           |
| `.photos`            | Access to the photo library                                  |
| `.location`          | Access to device location                                    |
| `.files`             | Access to the filesystem                                     |
| `.network`           | Outbound network requests                                    |
| `.destructiveAction` | Permanently modifies or deletes data                         |
| `.openWorldAction`   | Reaches outside the app (e.g., sends a message, opens a URL) |

### Requiring approval before a destructive action

```swift theme={null}
let deleteItem = ChappieHostTool(
    descriptor: ChappieHarnessTool(
        name: "delete_item",
        description: "Permanently deletes a record from the database."
    ),
    policy: .ask(scopes: [.destructiveAction])
) { call in
    // Only executes after the user approves
    try await Database.shared.delete(id: call.arguments)
    return ChappieToolResult(output: #"{"deleted":true}"#)
}
```

### Providing an approval handler

When any tool uses `.ask`, supply a `toolApprovalHandler` closure on the client. Chappie calls it with a `ChappieApprovalRequest` describing the pending call — display your confirmation UI there, then return a `ChappieApprovalDecision`:

```swift theme={null}
let client = Chappie.client(
    harness: harness,
    tools: [deleteItem],
    toolApprovalHandler: { request in
        let confirmed = await showApprovalAlert(
            toolName: request.toolCall.name,
            scopes: request.scopes,
            reason: request.reason
        )
        if confirmed {
            return .approved()
        } else {
            return .denied(reason: "User cancelled the action.")
        }
    }
)
```

<Warning>
  If a tool's policy is `.ask` but you don't provide a `toolApprovalHandler`, Chappie denies the tool call automatically and throws `ChappieClientError.toolApprovalDenied`. Always pair `.ask` policies with a handler.
</Warning>

## Observing tool execution with stream events

When you use `client.stream(_:)` or `client.streamHandle(_:)`, Chappie emits stream events at each stage of tool execution so your UI can show real-time feedback:

| Event                                             | When it fires                                                                                                                |
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `.toolCallRequested(ChappieToolExecutionRequest)` | Immediately after the model requests a tool call, before the handler runs                                                    |
| `.approvalRequested(ChappieApprovalRequest)`      | When a tool's policy is `.ask` and the handler has been invoked                                                              |
| `.toolCallCompleted(ChappieToolExecutionResult)`  | After the handler returns (or is denied/cancelled), with `status` set to `.completed`, `.denied`, `.cancelled`, or `.failed` |

```swift theme={null}
for try await event in client.stream("Check stock levels for all SKUs.") {
    switch event {
    case .textDelta(let text):
        print(text, terminator: "")
    case .toolCallRequested(let request):
        print("\n⚙️ Calling tool: \(request.toolCall.name)")
    case .toolCallCompleted(let result):
        print("✅ Tool finished with status: \(result.status)")
    case .completed:
        break
    default:
        break
    }
}
```

## Tool call round limit

Chappie allows up to **6 tool-call rounds** per `send`, `stream`, or `response` call. If the model requests more consecutive rounds than that limit, the client throws `ChappieClientError.toolLoopLimitExceeded`. Design your tools to return complete, actionable results so the model can reach a final answer within a few rounds.

<Tip>
  Returning structured JSON from your handler (rather than plain prose) lets the model extract individual fields reliably and reduces the number of follow-up tool calls needed.
</Tip>
