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

@@ -3,113 +3,113 @@
"short_name": "ferri", "short_name": "ferri",
"match_strings": ["Ferri"], "match_strings": ["Ferri"],
"capabilities": { "capabilities": {
"vibrate": "external clitoral vibration" "vibrate": "compact eccentric-mass actuator, high response at low duty cycle"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Small wearable. Intense even at low settings." "notes": "Small form factor. High effective output at low values; recommend starting below 0.3."
}, },
{ {
"short_name": "lush", "short_name": "lush",
"match_strings": ["Lush"], "match_strings": ["Lush"],
"capabilities": { "capabilities": {
"vibrate": "internal egg vibration" "vibrate": "single eccentric-mass actuator, low-frequency dominant"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Insertable egg. Strong deep vibration." "notes": "Compact ovoid form factor. High output efficiency at low command levels."
}, },
{ {
"short_name": "gravity", "short_name": "gravity",
"match_strings": ["Gravity"], "match_strings": ["Gravity"],
"capabilities": { "capabilities": {
"vibrate": "shaft vibration", "vibrate": "eccentric-mass actuator along main axis",
"oscillate": "thrusting motion" "oscillate": "linear reciprocating actuator; stroke rate scales with intensity"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Vibration + thrusting. Use 0.05+ intensity for slow strokes." "notes": "Dual-mode device. Oscillate requires intensity >= 0.05 to engage; low values map to slow cycle rate."
}, },
{ {
"short_name": "enigma", "short_name": "enigma",
"match_strings": ["Enigma"], "match_strings": ["Enigma"],
"capabilities": { "capabilities": {
"vibrate": "G-spot thumping stimulation", "vibrate": "high-amplitude pulsed actuator (impact-style, not continuous waveform)",
"rotate": "clitoral sonic pulse" "rotate": "sonic-frequency acoustic actuator (note: maps to acoustic output, NOT mechanical rotation)"
}, },
"intensity_floor": 0.4, "intensity_floor": 0.4,
"notes": "Dual stimulation. 'rotate' = sonic pulse, NOT rotation. Needs 40%+ to feel." "notes": "Two independent actuators. Threshold response: effective activation threshold ≈0.4. Rotate channel is acoustic, not rotational."
}, },
{ {
"short_name": "max", "short_name": "max",
"match_strings": ["Max"], "match_strings": ["Max"],
"capabilities": { "capabilities": {
"vibrate": "internal vibration", "vibrate": "eccentric-mass actuator",
"constrict": "air pump compression" "constrict": "pneumatic compression actuator; intensity maps to chamber pressure"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Vibration + air pump constriction. Constrict controls squeeze pressure." "notes": "Two independent actuators. Constrict is air-pump driven; intensity = compression force."
}, },
{ {
"short_name": "nora", "short_name": "nora",
"match_strings": ["Nora"], "match_strings": ["Nora"],
"capabilities": { "capabilities": {
"vibrate": "internal vibration", "vibrate": "eccentric-mass actuator",
"rotate": "internal rotation" "rotate": "rotational actuator"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Vibration + actual physical rotation (unlike Enigma)." "notes": "Two independent actuators. Rotate is mechanical, not acoustic."
}, },
{ {
"short_name": "edge", "short_name": "edge",
"match_strings": ["Edge"], "match_strings": ["Edge"],
"capabilities": { "capabilities": {
"vibrate": "dual motor vibration" "vibrate": "two independent eccentric-mass actuators; address individually via feature_index"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Prostate massager. Two independent vibration motors." "notes": "Dual-actuator device. Use feature_index 0 or 1 to drive motors separately."
}, },
{ {
"short_name": "hush", "short_name": "hush",
"match_strings": ["Hush"], "match_strings": ["Hush"],
"capabilities": { "capabilities": {
"vibrate": "vibration" "vibrate": "single eccentric-mass actuator"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Vibrating plug. Simple single-motor vibration." "notes": "Single-actuator device, no quirks."
}, },
{ {
"short_name": "domi", "short_name": "domi",
"match_strings": ["Domi"], "match_strings": ["Domi"],
"capabilities": { "capabilities": {
"vibrate": "powerful wand vibration" "vibrate": "high-output eccentric-mass actuator, broad-spectrum"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Mini wand. Very powerful. Start low." "notes": "High maximum output relative to other devices in this set. Typical operating range: 0.05–0.30."
}, },
{ {
"short_name": "osci", "short_name": "osci",
"match_strings": ["Osci"], "match_strings": ["Osci"],
"capabilities": { "capabilities": {
"oscillate": "oscillating stimulation" "oscillate": "linear reciprocating actuator (no vibrate channel)"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Oscillating G-spot stimulator. Uses oscillate, not vibrate." "notes": "Single-channel device. Responds only to oscillate, not vibrate."
}, },
{ {
"short_name": "dolce", "short_name": "dolce",
"match_strings": ["Dolce"], "match_strings": ["Dolce"],
"capabilities": { "capabilities": {
"vibrate": "dual vibration" "vibrate": "two independent eccentric-mass actuators; address individually via feature_index"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Couples' vibrator. Dual motors." "notes": "Dual-actuator device. Use feature_index 0 or 1 to drive motors separately."
}, },
{ {
"short_name": "flexer", "short_name": "flexer",
"match_strings": ["Flexer"], "match_strings": ["Flexer"],
"capabilities": { "capabilities": {
"vibrate": "vibration", "vibrate": "eccentric-mass actuator",
"oscillate": "come-hither motion" "oscillate": "articulated curling actuator (pivots rather than reciprocates linearly)"
}, },
"intensity_floor": 0.0, "intensity_floor": 0.0,
"notes": "Vibration + finger-like come-hither oscillation pattern." "notes": "Two independent actuators. Oscillate uses curling articulation, not linear stroke."
} }
] ]

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 3. Routes it through the session registry to the user's phone
4. Returns the result to Claude 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 vibrate, rotate, oscillate, constrict, temperature, led, position, spray
And sensor input types: And sensor input types:
@@ -103,7 +103,7 @@ async def _send(command: dict, intensity: float = 0.0) -> str:
@_register_tool( @_register_tool(
"list_devices", "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: async def list_devices(**kwargs) -> str:
@@ -139,7 +139,11 @@ async def list_devices(**kwargs) -> str:
lines = [] lines = []
for d in devices: 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", "") notes = d.get("notes", "")
floor = d.get("intensity_floor", 0) floor = d.get("intensity_floor", 0)
lines.append( lines.append(
@@ -148,7 +152,7 @@ async def list_devices(**kwargs) -> str:
+ (f" | {notes}" if notes else "") + (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) gov = governor.get_state(user_id)
heat = gov["heat_pct"] heat = gov["heat_pct"]
if gov["in_cooldown"]: if gov["in_cooldown"]:
@@ -177,7 +181,7 @@ async def scan_devices(**kwargs) -> str:
_OUTPUT_PARAMS = { _OUTPUT_PARAMS = {
"device": { "device": {
"type": "string", "type": "string",
"description": "Device short name (e.g. 'ferri', 'lush', 'gravity') or 'all'", "description": "Device short name or 'all'",
"default": "all", "default": "all",
}, },
"intensity": { "intensity": {
@@ -222,30 +226,35 @@ def _make_output_handler(output_type: OutputType):
# Standard outputs (available on most devices) # Standard outputs (available on most devices)
_register_tool( _register_tool(
"vibrate", "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, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.VIBRATE)) )(_make_output_handler(OutputType.VIBRATE))
_register_tool( _register_tool(
"rotate", "rotate",
"Send rotation/sonic pulse output. Device-specific — some devices use this " "Control rotational or oscillatory high-frequency actuator output. "
"for sonic clitoral stimulation rather than physical rotation.", "Interpretation depends on device firmware.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.ROTATE)) )(_make_output_handler(OutputType.ROTATE))
_register_tool( _register_tool(
"oscillate", "oscillate",
"Send oscillation/thrusting output. Device-specific — typically linear " "Control linear reciprocating actuator output. Intensity controls stroke amplitude/speed "
"thrusting motion.", "depending on hardware.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.OSCILLATE)) )(_make_output_handler(OutputType.OSCILLATE))
# Extended outputs (device-specific, may not be available on all hardware) # Extended outputs (device-specific, may not be available on all hardware)
_register_tool( _register_tool(
"constrict", "constrict",
"Send constriction/compression output. Device-specific — available on " "Send constriction/compression output. Device-specific.",
"devices with squeeze or compression mechanisms.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.CONSTRICT)) )(_make_output_handler(OutputType.CONSTRICT))
_register_tool( _register_tool(
@@ -253,12 +262,14 @@ _register_tool(
"Set temperature output. Device-specific — available on devices with " "Set temperature output. Device-specific — available on devices with "
"heating or cooling elements. Intensity maps to temperature range.", "heating or cooling elements. Intensity maps to temperature range.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.TEMPERATURE)) )(_make_output_handler(OutputType.TEMPERATURE))
_register_tool( _register_tool(
"led", "led",
"Control LED light output. Device-specific — intensity controls brightness.", "Control LED light output. Device-specific — intensity controls brightness.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.LED)) )(_make_output_handler(OutputType.LED))
_register_tool( _register_tool(
@@ -266,12 +277,14 @@ _register_tool(
"Set linear position. Device-specific — intensity maps to position " "Set linear position. Device-specific — intensity maps to position "
"along the device's range of motion (0.0 = retracted, 1.0 = extended).", "along the device's range of motion (0.0 = retracted, 1.0 = extended).",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.POSITION)) )(_make_output_handler(OutputType.POSITION))
_register_tool( _register_tool(
"spray", "spray",
"Trigger spray/liquid output. Device-specific.", "Trigger spray/liquid output. Device-specific.",
_OUTPUT_PARAMS, _OUTPUT_PARAMS,
required=["device"],
)(_make_output_handler(OutputType.SPRAY)) )(_make_output_handler(OutputType.SPRAY))
@@ -313,13 +326,13 @@ _PATTERN_PARAMS = {
}, },
"intensity": { "intensity": {
"type": "number", "type": "number",
"description": "Peak intensity (0.0–1.0)", "description": "Set intensity (0.0–1.0)",
"default": 0.6, "default": 0.5,
}, },
"duration": { "duration": {
"type": "number", "type": "number",
"description": "Duration in seconds", "description": "Duration in seconds",
"default": 10, "default": 60,
}, },
"feature_index": { "feature_index": {
"type": "integer", "type": "integer",
@@ -360,18 +373,20 @@ _register_tool(
"Rhythmic on/off pattern. 0.5s on at intensity, 0.3s off, repeating. " "Rhythmic on/off pattern. 0.5s on at intensity, 0.3s off, repeating. "
"Works with any output type (default: vibrate).", "Works with any output type (default: vibrate).",
_PATTERN_PARAMS, _PATTERN_PARAMS,
required=["device"],
)(_make_pattern_handler("pulse")) )(_make_pattern_handler("pulse"))
_register_tool( _register_tool(
"wave", "wave",
"Smooth sine-wave intensity modulation. Rises and falls continuously. " "Smooth continuous sine-wave amplitude modulation. "
"Works with any output type (default: vibrate).", "Works with any output type (default: vibrate).",
_PATTERN_PARAMS, _PATTERN_PARAMS,
required=["device"],
)(_make_pattern_handler("wave")) )(_make_pattern_handler("wave"))
_register_tool( _register_tool(
"escalate", "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). " "Use hold_seconds to auto-stop after holding (0 = hold indefinitely until stop command). "
"Works with any output type (default: vibrate).", "Works with any output type (default: vibrate).",
{k: v for k, v in _PATTERN_PARAMS.items() if k != "intensity"} {k: v for k, v in _PATTERN_PARAMS.items() if k != "intensity"}
@@ -383,6 +398,7 @@ _register_tool(
"default": 0, "default": 0,
}, },
}, },
required=["device"],
)(_make_pattern_handler("escalate")) )(_make_pattern_handler("escalate"))