Friday's Party: Creating a Private, Agentic AI using Voice Assistant tools

A bunch of Tool updates to prep for File Tool

Script - Zen Index 2.6:
(REQUIRES Inspect)

alias: DojoTOOLS Zen Index
description: >-
  The Zen Indexer (2.6.0) is a DIRECT replacement and the next version of the Library
  ~INDEX~ Command.  ALWAYS prefer it over old index tools in place of THE
  LIBRARY ~INDEX~. Set logic on two lists of entity_ids (each resolved from a
  label or list of entities). - If only one side provided, returns that set. -
  If both, applies operator (AND, OR, NOT, XOR). - If neither, returns an error
  or (special case) full label index (operator = '*'). - Optional expand to
  resolve groups/areas. Returns matching entities, adjacent labels, expansion
  can include state and attribute list. result.simple ALWAYS returns a list (for
  chaining). If index_command is set, fires zen_indexer_request event and exits
  for recursive resolution.
fields:
  index_command:
    name: Zen Index Command
    description: >-
      Structured query string (if set, all other fields are ignored and this
      triggers recursive index resolution via event). If parsing fails or times
      out, will fall back to using the other fields.
    required: false
    selector:
      text: {}
  entities_1:
    name: Entities 1
    description: List of entity_ids for operand 1 (used if label_1 is blank)
    required: false
    selector:
      entity:
        multiple: true
  label_1:
    name: Label 1
    description: Label for operand 1 (used if entities_1 is empty)
    required: false
    selector:
      text: {}
  entities_2:
    name: Entities 2
    description: List of entity_ids for operand 2 (used if label_2 is blank)
    required: false
    selector:
      entity:
        multiple: true
  label_2:
    name: Label 2
    description: Label for operand 2 (used if entities_2 is empty)
    required: false
    selector:
      text: {}
  operator:
    name: Set Operator
    description: Logical set operator to apply between operand 1 and 2 (AND, OR, NOT, XOR)
    required: true
    default: AND
    selector:
      select:
        options:
          - AND
          - OR
          - NOT
          - XOR
  expand_entities:
    name: Expand results
    description: >-
      If true, expand each resulting entity to include its state and attribute
      keys (may produce a large output). Best practice: use labels to narrow
      results before expanding. Default: false.
    required: false
    default: false
    selector:
      boolean: {}
  timeout:
    name: Timeout
    description: >-
      Timeout (in seconds) to wait for index resolution via event if using
      index_command.  Default 2 seconds (max 5). If timeout occurs, script will
      fall back to using provided fields or full index.
    required: false
    default: 2
    selector:
      number:
        min: 0
        max: 5
        step: 0.25
sequence:
  - variables:
      is_index_command: "{{ index_command is defined and index_command | length > 0 }}"
      correlation_id: |-
        {% if index_command is defined and index_command | length > 0 %}
          {{ now().isoformat() ~ '-' ~ (range(1000) | random) }}
        {% else %}
          ''
        {% endif %}
      timeout_seconds: "{{ timeout | default(2) }}"
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ is_index_command }}"
        sequence:
          - alias: Send Index Command to Parser
            event: zen_indexer_request
            event_data:
              index_command: "{{ index_command }}"
              correlation_id: "{{ correlation_id }}"
          - alias: Wait for Parser Response ({{ timeout_seconds }}s)
            wait_for_trigger:
              - event_type: zen_index_response
                event_data:
                  correlation_id: "{{ id }}"
                trigger: event
            timeout:
              seconds: "{{ timeout_seconds }}"
          - choose:
              - conditions:
                  - condition: template
                    value_template: "{{ wait.trigger is defined and wait.trigger is not none }}"
                sequence:
                  - variables:
                      simple: "{{ wait.trigger.event.data.response.result.simple }}"
                      index_timeout: false
              - conditions: []
                sequence:
                  - variables:
                      simple: []
                      index_timeout: true
                      timeout_error_msg: Indexer call timeout {{ timeout_seconds }}s
        alias: Send Index Command to Parser and resolve to fields
  - variables:
      index_command: ""
      op1: >-
        {% set ents = simple if (simple is defined and simple) else [] %} {% if
        ents | length == 0 %}
          {% set ents = entities_1 if (entities_1 is defined and entities_1) else [] %}
          {% if ents | length == 0 and label_1 %}
            {{ label_entities(label_1) }}    # If still empty and label_1 provided, retrieve entities by label_1
          {% else %}
            {{ ents }}
          {% endif %}
        {% else %}
          {{ ents }}
        {% endif %}
      op2: >-
        {% set ents = entities_2 if (entities_2 is defined and entities_2) else
        [] %} {% if ents | length == 0 and label_2 %}
          {{ label_entities(label_2) }}
        {% else %}
          {{ ents }}
        {% endif %}
      op1_empty: "{{ op1 | length == 0 }}"
      op2_empty: "{{ op2 | length == 0 }}"
      setop: "{{ operator | default('AND') | upper }}"
      entities_base: |-
        {% if not op1_empty and op2_empty %}
          {{ op1 }}
        {% elif not op2_empty and op1_empty %}
          {{ op2 }}
        {% elif not op1_empty and not op2_empty %}
          {% if setop == 'AND' %}
            {{ op1 | intersect(op2) }}
          {% elif setop == 'OR' %}
            {{ op1 + op2 | unique | list }}
          {% elif setop == 'NOT' %}
            {{ op1 | difference(op2) }}
          {% elif setop == 'XOR' %}
            {{ op1 | symmetric_difference(op2) }}
          {% else %}
            {{ op1 + op2 | unique | list }}
          {% endif %}
        {% else %}
          []   # Both op1 and op2 empty: no entities in base set (will trigger full index fallback below)
        {% endif %}
  - if:
      - condition: template
        value_template: "{{ expand_entities }}"
        alias: if expand_entities
    then:
      - action: script.dojotools_zen_inspect
        metadata: {}
        data:
          entity_id: "{{entities_base}}"
        response_variable: response
      - stop: Pass Variable
        response_variable: response
      - variables:
          expanded_entities: "{{ response }}"
    else:
      - variables:
          expanded_entities: "{{ entities_base | tojson }}"
  - variables:
      result_simple: "{{ entities_base | tojson }}"
      adjacent_labels: |-
        {%- set all_labels = [] -%} {%- for e in entities_base -%}
          {%- set all_labels = all_labels + (labels(e) | list) -%}
        {%- endfor -%} {{ all_labels | unique | list | tojson }}
      result_index: |-
        {%- set index_list = [] -%} {%- for e in entities_base -%}
          {%- set index_list = index_list + [[ e, (labels(e) | list) ]] -%}
        {%- endfor -%} {{ index_list | tojson }}
      error_msg: |-
        {% set msg = '' %} {% if index_timeout is defined and index_timeout %}
          {% set msg = 'Indexer call timeout ' ~ timeout_seconds ~ 's. Fallback path used.' %}
        {% elif op1_empty and op2_empty %}
          {% set msg = 'Both operands are empty. Returned full label index.' %}
        {% endif %} {{ msg }}  # msg will be empty string if no error
      label_entity_logic_result_raw: |-
        { 
          "result": {
            "simple": {{ result_simple }},
            "expanded": {{ expanded_entities }},
            "adjacent_labels": {{ adjacent_labels }},
            "index": {% if op1_empty and op2_empty %} {{ labels() | list | tojson }} {% else %} {{ result_index }} {% endif %}
          },
          "operator": "{{ "*" if (op1_empty and op2_empty) else setop }}",
          "inputs": {
            "entities_1": {{ (entities_1 | default([])) | tojson }},
            "label_1": {{ (label_1 | default('')) | tojson }},
            "entities_2": {{ (entities_2 | default([])) | tojson }},
            "label_2": {{ (label_2 | default('')) | tojson }},
            "expand_entities": {{ (expand_entities | default(false)) | tojson }}
          },
          "error": {{ (error_msg if error_msg != '' else none) | tojson }}
        }
      label_entity_logic_result: "{{ label_entity_logic_result_raw | from_json | default({}) }}"
  - stop: Zen Index Call Complete
    response_variable: label_entity_logic_result
mode: parallel
max: 10

Automation - Index Event Handler:

alias: DojoTools Zen Index Event Handler
description: >
  Event Listener (2.0.0) to recurse index calls.  REQUIRES DojoTOOLS Zen Index (2.0.0 or
  greater)
triggers:
  - event_type: zen_indexer_request
    trigger: event
actions:
  - variables:
      index_command: "{{ trigger.event.data.index_command | default('{}', true) }}"
      correlation_id: "{{ trigger.event.data.correlation_id }}"
      depth: "{{ trigger.event.data.depth if 'depth' in trigger.event.data else 1 }}"
      is_mapping: |-
        {% set cmd = index_command | trim %} {% if cmd.startswith('{') %}
          {{ (cmd | from_json) is mapping }}
        {% else %}
          false
        {% endif %}
      parsed_cmd: >
        {% set reserved = ['AND', 'OR', 'XOR', 'NOT'] %} {% set true_tokens =
        ['true', 't', 'y', 'yes', 'on', '1'] %} {% set false_tokens = ['false',
        'f', 'n', 'no', 'off', '0'] %} {% set raw = index_command | string |
        trim %} {% set is_json = raw.lstrip().startswith('{') %} {% if is_json
        %}
          {{ raw }}
        {% elif raw == "" or raw == "*" %}
          { "label_1": "*", "operator": "AND", "expand_entities": false }
        {% else %}
          {% set tokens = raw.split() %}
          {% set exp = false %}
          {% if tokens[-1] | lower in true_tokens %}
            {% set exp = true %}
            {% set tokens = tokens[:-1] %}
          {% elif tokens[-1] | lower in false_tokens %}
            {% set tokens = tokens[:-1] %}
          {% endif %}
          {% set op_index = namespace(idx = none) %}
          {% for i in range(tokens | length) %}
            {% if tokens[i] | upper in reserved %}
              {% set op_index.idx = i %}
              {% break %}
            {% endif %}
          {% endfor %}
          {% if op_index.idx is not none %}
            {% set label_1 = tokens[:op_index.idx] | join(" ") | trim("'\"") %}
            {% set op = tokens[op_index.idx] | upper %}
            {% set label_2 = tokens[op_index.idx+1:] | join(" ") | trim("'\"") %}
          {% else %}
            {% set label_1 = tokens | join(" ") | trim("'\"") %}
            {% set op = "AND" %}
            {% set label_2 = "" %}
          {% endif %}
          {
            "label_1": "{{ label_1 }}",
            "label_2": "{{ label_2 }}",
            "operator": "{{ op }}",
            "expand_entities": {{ exp }}
          }
        {% endif %}
      entities_1: >-
        {% set ents = parsed_cmd.entities_1 if parsed_cmd.entities_1 is defined
        and parsed_cmd.entities_1 else [] %} {% if ents is string %}
          {% set ents = ents | from_json | default([]) %}
        {% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
          {{ label_entities(parsed_cmd.label_1) | default([]) }}
        {% else %}
          {{ ents }}
        {% endif %}
      label_1: "{{ parsed_cmd.label_1 if parsed_cmd.label_1 is defined else '' }}"
      entities_2: >-
        {% set ents = parsed_cmd.entities_2 if parsed_cmd.entities_2 is defined
        and parsed_cmd.entities_2 else [] %} {% if ents is string %}
          {% set ents = ents | from_json | default([]) %}
        {% endif %} {% if ents | length == 0 and parsed_cmd.label_1 %}
          {{ label_entities(parsed_cmd.label_1) | default([]) }}
        {% else %}
          {{ ents }}
        {% endif %}
      label_2: "{{ parsed_cmd.label_2 if parsed_cmd.label_2 is defined else '' }}"
      operator: |-
        {% if parsed_cmd.operator is defined and parsed_cmd.operator %}
          {{ parsed_cmd.operator }}
        {% else %}
          AND
        {% endif %}
      expand_entities: >-
        {{ parsed_cmd.expand_entities if parsed_cmd.expand_entities is defined
        else false }}
  - alias: Dispatch Zen Index Command
    data:
      entities_1: "{{ entities_1 }}"
      label_1: "{{ label_1 }}"
      entities_2: "{{ entities_2 }}"
      label_2: "{{ label_2 }}"
      operator: "{{ operator }}"
      expand_entities: "{{ expand_entities }}"
    response_variable: zen_index_response
    action: script.dojotools_zen_index
  - alias: Return Zen Indexer Result
    event: zen_index_response
    event_data:
      correlation_id: "{{ correlation_id }}"
      response: "{{ zen_index_response }}"
mode: queued

Script - Inspect: (Attribute dumper, plus, note bug still outputs stringified JSON)

alias: DojoTOOLS Zen Inspect
mode: parallel
description: >-
  Inspect (1.2.0) Deep dive on one or more entities.  THis tool lprovides the expand function of
  the Zen Indexer. Accepts a list of entity_ids - dumps attributes Exposes
  volume headers for volumes in the library, Highlights device trackers for
  person domain entities and more! Optional inference support is coming soon for
  an deeper analysis on provided data.
fields:
  entity_id:
    required: true
    selector:
      entity:
        multiple: true
  infer:
    required: false
    selector:
      boolean: {}
    description: Use local inference to add context to the result (VERY EXPENSIVE call)
  timeout:
    selector:
      number:
        min: 30
        max: 300
        step: 0.25
    name: Timeout
    description: >-
      Timeout, in seconds to wait for the inference return in seconds (Default -
      60, Min - 30, Max - 300, Step - 5)
    default: 60
    required: false
sequence:
  - variables:
      entity_list: "{{ entity_id }}"
      results: []
  - repeat:
      for_each: "{{ entity_list }}"
      sequence:
        - variables:
            current_entity: "{{ repeat.item }}"
            labels_raw: "{{ labels(current_entity) }}"
            adjacent_labels: "{{ labels_raw | unique | sort | list | tojson }}"
            raw_volume: "{{ state_attr(current_entity, 'variables') }}"
            is_person: "{{ current_entity.startswith('person.') }}"
            user_id: "{{ state_attr(current_entity, 'user_id') if is_person else none }}"
            last_updated: >-
              {{ as_timestamp(states[current_entity].last_updated, default=0) |
              timestamp_local }}
            device_trackers: >-
              {{ (state_attr(current_entity, 'device_trackers') if is_person
              else [] ) | tojson }}
            parsed_volume: |
              {% if raw_volume is string and raw_volume.startswith('{') %}
                {{ raw_volume | from_json }}
              {% elif raw_volume is mapping %}
                {{ raw_volume }}
              {% else %}
                {} 
              {% endif %}
            cab_valid: |
              {{ raw_volume is mapping and
                 raw_volume.AI_Cabinet_VolumeInfo is defined and
                 raw_volume.AI_Cabinet_VolumeInfo.value is mapping and
                 raw_volume.AI_Cabinet_VolumeInfo.value.validation == "ALLYOURBASEAREBELONGTOUS" }}
            cab_response: |
              {% if cab_valid %}
                {{
                  {
                    "id": raw_volume.AI_Cabinet_VolumeInfo.value.id | default(""),
                    "friendly_name": raw_volume.AI_Cabinet_VolumeInfo.value.friendly_name | default(""),
                    "description": raw_volume.AI_Cabinet_VolumeInfo.value.description | default(""),
                    "timestamp": raw_volume.AI_Cabinet_VolumeInfo.timestamp | default(""),
                    "flags": raw_volume.AI_Cabinet_VolumeInfo.flags | default({}),
                    "acls": {
                      "owner": (
                        raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id
                        if raw_volume.AI_Cabinet_VolumeInfo.acls is defined and
                           raw_volume.AI_Cabinet_VolumeInfo.acls.owner is defined and
                           raw_volume.AI_Cabinet_VolumeInfo.acls.owner[0].entity_id is defined
                        else ""
                      )
                    },
                    "index": (
                      raw_volume._label_index.value
                      if raw_volume._label_index is defined and raw_volume._label_index.value is defined
                      else []
                    )
                  }
                }}
              {% else %}
                { "defined": false, "error": "Invalid or missing AI Cabinet volume." }
              {% endif %}
            attr_dict: >
              {%- set attrs = states[current_entity].attributes if
              states[current_entity] else {} %}

              {%- set keys = attrs.keys() | list %}

              {%- set ns = namespace(vals=[]) %}

              {%- for v in attrs.values() %}
                {%- if v is none %}
                  {%- set ns.vals = ns.vals + [none] %}
                {%- elif v is datetime %}
                  {%- set ns.vals = ns.vals + [v.isoformat()] %}
                {%- elif v is mapping %}
                  {%- set ns.vals = ns.vals + [v] %}
                {%- elif v is iterable and not v is string %}
                  {%- set ns.vals = ns.vals + [v | list] %}
                {%- elif v is string or v is number or v is boolean %}
                  {%- set ns.vals = ns.vals + [v] %}
                {%- else %}
                  {# If it's anything else (enum, object, who-knows-what): DROP IT! #}
                {%- endif %}
              {%- endfor %}

              {{ dict(zip(keys, ns.vals)) }}
            fallback_response: |-
              {{
                {
                  "entity": current_entity | default(''),
                  "state": states(current_entity) | default(''),
                  "last_updated": last_updated,
                  "labels": labels_raw | default([]),
                  "attributes": attr_dict,
                  "user_id": user_id | default(''),
                  "device_trackers": device_trackers | default([])
                } | tojson
              }}
        - variables:
            inference: {}
            result: |-
              {{
                {
                  "entity": current_entity,
                  "inspection": fallback_response,
                  "inference": {
                    "unsupported": "infer in development, coming soon",
                  }
                } | tojson
              }}
            results: "{{ results + [result] }}"
  - variables:
      final_response: |
        {{
          {
            "results": results
          } | tojson 
        }}
  - stop: Multi-entity investigate complete
    response_variable: final_response

Script - Calculator:

alias: Zen DojoTOOLS Calculator
mode: parallel
description: >-
  Ultimate calculator for AI use. Accepts two numbers and an operator. Supports
  add, subtract, multiply, divide, exponent, sqrt, cbrt, sin, cos, tan,
  degrees_to_radians, radians_to_degrees, log, ln, percent_of,
  increase_by_percent, decrease_by_percent, round, floor, ceil, modulus,
  fahrenheit_to_celsius, celsius_to_fahrenheit, and a GUID Generator! Returns
  JSON output.  Drops inputs as necessary if operator does not require one or
  both. (v.1.3.0)
fields:
  number_1:
    name: First Number
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        mode: box
  number_2:
    name: Second Number
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        mode: box
  operator:
    name: Operator
    required: true
    selector:
      select:
        options:
          - add
          - subtract
          - multiply
          - divide
          - exponent
          - sqrt
          - cbrt
          - sin
          - cos
          - tan
          - degrees_to_radians
          - radians_to_degrees
          - log
          - ln
          - percent_of
          - increase_by_percent
          - decrease_by_percent
          - round
          - floor
          - ceil
          - modulus
          - fahrenheit_to_celsius
          - celsius_to_fahrenheit
          - guidgen
        translation_key: operator
        custom_value: false
sequence:
  - variables:
      n1: "{{ number_1 | default(0) | float(0) }}"
      n2: "{{ number_2 | default(0) | float(0) }}"
      op: "{{ operator | lower }}"
      result: |
        {% if op == 'add' %}
          {{ n1 + n2 }}
        {% elif op == 'subtract' %}
          {{ n1 - n2 }}
        {% elif op == 'multiply' %}
          {{ n1 * n2 }}
        {% elif op == 'divide' %}
          {% if n2 != 0 %}
            {{ n1 / n2 }}
          {% else %}
            'Error: Division by zero'
          {% endif %}
        {% elif op == 'exponent' %}
          {{ n1 ** n2 }}
        {% elif op == 'sqrt' %}
          {% if n1 >= 0 %}
            {{ n1 ** 0.5 }}
          {% else %}
            'Error: Square root of negative'
          {% endif %}
        {% elif op == 'cbrt' %}
          {{ n1 ** (1/3) }}
        {% elif op == 'sin' %}
          {{ n1 | sin }}
        {% elif op == 'cos' %}
          {{ n1 | cos }}
        {% elif op == 'tan' %}
          {{ n1 | tan }}
        {% elif op == 'degrees_to_radians' %}
          {{ n1 * 3.141592653589793 / 180 }}
        {% elif op == 'radians_to_degrees' %}
          {{ n1 * 180 / 3.141592653589793 }}
        {% elif op == 'log' %}
          {% if n1 > 0 %}
            {{ n1 | log10 }}
          {% else %}
            'Error: Log undefined for n1 ≤ 0'
          {% endif %}
        {% elif op == 'ln' %}
          {% if n1 > 0 %}
            {{ n1 | log }}
          {% else %}
            'Error: Natural log undefined for n1 ≤ 0'
          {% endif %}
        {% elif op == 'percent_of' %}
          {{ (n1 * n2) / 100 }}
        {% elif op == 'increase_by_percent' %}
          {{ n1 * (1 + n2/100) }}
        {% elif op == 'decrease_by_percent' %}
          {{ n1 * (1 - n2/100) }}
        {% elif op == 'round' %}
          {{ n1 | round(n2 | int(0)) }}
        {% elif op == 'floor' %}
          {{ n1 | floor }}
        {% elif op == 'ceil' %}
          {{ n1 | ceil }}
        {% elif op == 'modulus' %}
          {% if n2 != 0 %}
            {{ n1 % n2 }}
          {% else %}
            'Error: Modulus by zero'
          {% endif %}
        {% elif op == 'fahrenheit_to_celsius' %}
          {{ (n1 - 32) * 5 / 9 }}
        {% elif op == 'celsius_to_fahrenheit' %}
          {{ (n1 * 9 / 5) + 32 }}
        {% elif op == 'guidgen' %}
          {% macro random_string(len) -%}{% for i in range(0,len) -%}{{ [0,1,2,3,4,5,6,7,8,9,"a","b","c","d","e","f"]|random }}{% endfor %}{%- endmacro -%}
          {% macro random_guid() -%} {{ random_string(8) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(4) + "-" + random_string(12) }}{%- endmacro -%}
          {% set myGUID = random_guid() %}
          {{ myGUID }}
        {% else %}
          'Error: Unknown operator'
        {% endif %}
      out: |
        {{
          {
            "number_1": n1,
            "number_2": n2,
            "operator": op,
            "result": result
          } | tojson
        }}
  - stop: Calculator done
    response_variable: out

Script - Die Roller:

alias: Zen DojoTOOLs Dice Roller (1.0.0)
mode: parallel
description: >-
  Rolls dice (DnD style), flips a coin, or generates a random number. Supports
  classic dice notation (e.g., '2d6', 'd20'), coin flip, random integer, and
  random float.
fields:
  mode:
    name: Mode
    required: true
    selector:
      select:
        options:
          - Dice
          - Random Integer
          - Random Float
          - Coin Flip
  dice_notation:
    name: Dice Notation (e.g. 3d6, d20)
    required: false
    selector:
      text: null
    description: >-
      Only used if Mode is 'Dice'. Format XdY or dY (e.g. 2d8, d20) only
      supports one dice type per roll (e.g., 3d6 or 2d20 but not 2d6+1d8)
  min:
    name: Minimum Value
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        step: 1
        mode: box
    description: Only used for random number modes.
  max:
    name: Maximum Value
    required: false
    selector:
      number:
        min: -1000000000000
        max: 1000000000000
        step: 1
        mode: box
    description: Only used for random number modes.
sequence:
  - variables:
      md: "{{ mode | lower }}"
      dice: "{{ dice_notation | default('d6') }}"
      minval: "{{ min | int(1) }}"
      maxval: "{{ max | int(6) }}"
      result: >
        {% set m = dice.lower().replace(' ', '') %} {% set parts = m.split('d')
        %} {% set n = 1 if parts[0] == '' else parts[0] | int(1) %} {% set die =
        parts[1] | int(6) %} {% if md == 'dice' %}
          {% set faces = range(1, die+1) %}
          {% set ns = namespace(rolls=[]) %}
          {% for _ in range(n) %}
            {% set ns.rolls = ns.rolls + [faces | random] %}
          {% endfor %}
          {{ {
            "mode": "dice",
            "dice": m,
            "individual_rolls": ns.rolls,
            "total": ns.rolls | sum
          } }}
        {% elif md == 'random integer' %}
          {% if maxval >= minval %}
            {{ {
              "mode": "random_integer",
              "min": minval,
              "max": maxval,
              "value": (range(minval, maxval+1) | random)
            } }}
          {% else %}
            {{ { "error": "Max must be >= min" } }}
          {% endif %}
        {% elif md == 'random float' %}
          {% if maxval >= minval %}
            {% set f = (range(0, 100000) | random) / 100000 %}
            {% set val = minval + (maxval - minval) * f %}
            {{ {
              "mode": "random_float",
              "min": minval,
              "max": maxval,
              "value": val
            } }}
          {% else %}
            {{ { "error": "Max must be >= min" } }}
          {% endif %}
        {% elif md == 'coin flip' %}
          {{ {
            "mode": "coin_flip",
            "result": ['heads', 'tails'] | random
          } }}
        {% else %}
          {{ { "error": "Unknown mode" } }}
        {% endif %}
      out: |
        {{ result }}
  - stop: Dice/Random complete
    response_variable: out
icon: mdi:dice-d20-outline
1 Like