use polyc_llm::ToolSpec;
use serde_json::json;
pub const TOOL_NAME: &str = "unlink_self";
pub const ALL: &[&str] = &[TOOL_NAME];
pub const ARG_TARGET: &str = "target";
pub const TARGET_EMAIL: &str = "email";
pub const TARGET_WALLET: &str = "wallet";
#[must_use]
pub fn all_specs() -> Vec<ToolSpec> {
vec![unlink_self_spec()]
}
#[must_use]
pub fn unlink_self_spec() -> ToolSpec {
ToolSpec::new(
TOOL_NAME,
"Remove ONE of the caller's own linked identifiers: their email address or their \
spending wallet. Use it ONLY when they ask to unlink, disconnect, remove, or stop \
using their OWN email or wallet specifically — never for a request to unlink a \
Slack/Telegram/platform identity (that's unlink_identity, admin-only, and it \
targets a NAMED account, never the caller's own). Set `target` to \"email\" to \
remove their linked email — to replace an email, call this first, then \
link_email with the new address, since this tool never links a new one itself — \
or \"wallet\" to disconnect their spending wallet, which is reversible; they can \
link again anytime. Refuses if removing the named target would leave the caller \
with zero identities, or if nothing is linked for the target named.",
json!({
"type": "object",
"properties": {
ARG_TARGET: {
"type": "string",
"enum": [TARGET_EMAIL, TARGET_WALLET],
"description": "Which of the caller's own linked identifiers to remove."
}
},
"required": [ARG_TARGET],
"additionalProperties": false
}),
)
.destructive()
.approval_required()
.titled("Remove a linked email or wallet")
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use super::*;
#[test]
fn all_specs_match_all_names() {
let specs = all_specs();
assert_eq!(specs.len(), ALL.len());
for name in ALL {
assert!(specs.iter().any(|s| s.name == *name), "{name} has no spec");
}
}
#[test]
fn unlink_self_spec_requires_a_target_enum_and_is_gated() {
let spec = unlink_self_spec();
assert_eq!(spec.name, TOOL_NAME);
assert!(spec.destructive, "removes a real linked identifier");
assert!(
spec.needs_approval,
"unlinking must always pause for a human check, regardless of reversibility"
);
let props = spec.schema_json["properties"].as_object().unwrap();
let target = props.get(ARG_TARGET).expect("target property");
let enum_values = target["enum"].as_array().unwrap();
assert_eq!(enum_values, &[json!(TARGET_EMAIL), json!(TARGET_WALLET)]);
let required = spec.schema_json["required"].as_array().unwrap();
assert_eq!(required, &[json!(ARG_TARGET)]);
}
}