refactor: neutral engineering terminology in tool schemas and device profiles

Ported from the Android edition (its 'reworded device and tool schemas
in neutral terminology' change):

- Tool descriptions and devices.json now describe hardware in neutral
  actuator/engineering terms. Content filters on some LLM platforms
  refused to call tools whose schemas contained explicit anatomical
  language; the reworded schemas work across providers.
- list_devices now surfaces capability descriptions next to each output
  channel (e.g. 'vibrate (two independent eccentric-mass actuators…)')
  so the model learns what each channel does from the profile itself.
- All output/pattern tools now declare required=["device"]; pattern
  schema defaults adjusted to match the Android edition (intensity 0.5,
  duration 60).
- Dual-motor device profiles (Edge, Dolce) document feature_index usage.
- Fixed a stray ')' in nora's rotate capability (typo in the Android
  copy of devices.json).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Aletheia
2026-07-07 20:11:49 +02:00
parent 136920b717
commit 07e5f9c38c
2 changed files with 62 additions and 46 deletions

View File

@@ -7,7 +7,7 @@ All tools that Claude can call to control devices. Each tool:
3. Routes it through the session registry to the user's phone
4. Returns the result to Claude
Expanded to support ALL Buttplug output types:
Expanded to support ALL output types:
vibrate, rotate, oscillate, constrict, temperature, led, position, spray
And sensor input types:
@@ -103,7 +103,7 @@ async def _send(command: dict, intensity: float = 0.0) -> str:
@_register_tool(
"list_devices",
"List all connected devices with their capabilities, intensity floors, and notes.",
"List all connected devices and their supported outputs, operating ranges, and metadata.",
{},
)
async def list_devices(**kwargs) -> str:
@@ -139,7 +139,11 @@ async def list_devices(**kwargs) -> str:
lines = []
for d in devices:
caps = ", ".join(d.get("capabilities", {}).keys())
caps_dict = d.get("capabilities", {})
# Surface capability values when present so the model knows what each
# output channel actually does; fall back to bare keys for terse entries.
caps_parts = [f"{k} ({v})" if v else k for k, v in caps_dict.items()]
caps = ", ".join(caps_parts)
notes = d.get("notes", "")
floor = d.get("intensity_floor", 0)
lines.append(
@@ -148,7 +152,7 @@ async def list_devices(**kwargs) -> str:
+ (f" | {notes}" if notes else "")
)
# Append governor state so Claude knows the session budget
# Append governor state so AI knows the session budget
gov = governor.get_state(user_id)
heat = gov["heat_pct"]
if gov["in_cooldown"]:
@@ -177,7 +181,7 @@ async def scan_devices(**kwargs) -> str:
_OUTPUT_PARAMS = {
"device": {
"type": "string",
"description": "Device short name (e.g. 'ferri', 'lush', 'gravity') or 'all'",
"description": "Device short name or 'all'",
"default": "all",
},
"intensity": {
@@ -222,30 +226,35 @@ def _make_output_handler(output_type: OutputType):
# Standard outputs (available on most devices)
_register_tool(
"vibrate",
"Send vibration to a device. Most common output type.",
"Send vibration to a device. Most common output type. "
"For dual-motor devices (Dolce, Edge), use feature_index to target a "
"specific motor (e.g. Dolce: 0 = internal, 1 = external).",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.VIBRATE))
_register_tool(
"rotate",
"Send rotation/sonic pulse output. Device-specific — some devices use this "
"for sonic clitoral stimulation rather than physical rotation.",
"Control rotational or oscillatory high-frequency actuator output. "
"Interpretation depends on device firmware.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.ROTATE))
_register_tool(
"oscillate",
"Send oscillation/thrusting output. Device-specific — typically linear "
"thrusting motion.",
"Control linear reciprocating actuator output. Intensity controls stroke amplitude/speed "
"depending on hardware.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.OSCILLATE))
# Extended outputs (device-specific, may not be available on all hardware)
_register_tool(
"constrict",
"Send constriction/compression output. Device-specific — available on "
"devices with squeeze or compression mechanisms.",
"Send constriction/compression output. Device-specific.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.CONSTRICT))
_register_tool(
@@ -253,12 +262,14 @@ _register_tool(
"Set temperature output. Device-specific — available on devices with "
"heating or cooling elements. Intensity maps to temperature range.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.TEMPERATURE))
_register_tool(
"led",
"Control LED light output. Device-specific — intensity controls brightness.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.LED))
_register_tool(
@@ -266,12 +277,14 @@ _register_tool(
"Set linear position. Device-specific — intensity maps to position "
"along the device's range of motion (0.0 = retracted, 1.0 = extended).",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.POSITION))
_register_tool(
"spray",
"Trigger spray/liquid output. Device-specific.",
_OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.SPRAY))
@@ -313,13 +326,13 @@ _PATTERN_PARAMS = {
},
"intensity": {
"type": "number",
"description": "Peak intensity (0.0–1.0)",
"default": 0.6,
"description": "Set intensity (0.0–1.0)",
"default": 0.5,
},
"duration": {
"type": "number",
"description": "Duration in seconds",
"default": 10,
"default": 60,
},
"feature_index": {
"type": "integer",
@@ -360,18 +373,20 @@ _register_tool(
"Rhythmic on/off pattern. 0.5s on at intensity, 0.3s off, repeating. "
"Works with any output type (default: vibrate).",
_PATTERN_PARAMS,
required=["device"],
)(_make_pattern_handler("pulse"))
_register_tool(
"wave",
"Smooth sine-wave intensity modulation. Rises and falls continuously. "
"Smooth continuous sine-wave amplitude modulation. "
"Works with any output type (default: vibrate).",
_PATTERN_PARAMS,
required=["device"],
)(_make_pattern_handler("wave"))
_register_tool(
"escalate",
"Gradual ramp from 0% to peak intensity over the duration, then hold at peak. "
"Apply linear interpolation from current output level to target level over duration; optionally maintain target after transition. "
"Use hold_seconds to auto-stop after holding (0 = hold indefinitely until stop command). "
"Works with any output type (default: vibrate).",
{k: v for k, v in _PATTERN_PARAMS.items() if k != "intensity"}
@@ -383,6 +398,7 @@ _register_tool(
"default": 0,
},
},
required=["device"],
)(_make_pattern_handler("escalate"))