Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
158 lines
4.1 KiB
TypeScript
158 lines
4.1 KiB
TypeScript
import { mergeAttributes, Node } from "@tiptap/core";
|
|
|
|
export interface AgentCapabilityTokenOption {
|
|
capability: string;
|
|
label: string;
|
|
name: string;
|
|
trigger: string;
|
|
}
|
|
|
|
export interface AgentCapabilityTokenAttrs {
|
|
capability: string;
|
|
label: string;
|
|
name: string;
|
|
trigger: string;
|
|
}
|
|
|
|
export interface AgentCapabilityTokenMatch {
|
|
attrs: AgentCapabilityTokenAttrs;
|
|
end: number;
|
|
}
|
|
|
|
export interface AgentCapabilityTokenExtensionOptions {
|
|
capabilities?: readonly AgentCapabilityTokenOption[];
|
|
}
|
|
|
|
export function createAgentCapabilityTokenExtension(
|
|
options: AgentCapabilityTokenExtensionOptions = {}
|
|
): Node {
|
|
return Node.create({
|
|
name: "agentCapabilityToken",
|
|
group: "inline",
|
|
inline: true,
|
|
atom: true,
|
|
selectable: true,
|
|
|
|
addOptions() {
|
|
return options;
|
|
},
|
|
|
|
addAttributes() {
|
|
return {
|
|
capability: { default: "" },
|
|
label: { default: "" },
|
|
name: { default: "" },
|
|
trigger: { default: "" }
|
|
};
|
|
},
|
|
|
|
parseHTML() {
|
|
return [{ tag: "span[data-agent-capability-token]" }];
|
|
},
|
|
|
|
renderHTML({ HTMLAttributes }) {
|
|
const attrs = attrsToCapabilityTokenAttrs(HTMLAttributes);
|
|
const displayLabel = capabilityTokenDisplayLabel(attrs);
|
|
return [
|
|
"span",
|
|
mergeAttributes(HTMLAttributes, {
|
|
"aria-label": attrs.label,
|
|
contentEditable: "false",
|
|
"data-agent-capability-token": "true",
|
|
"data-agent-capability-trigger": attrs.trigger,
|
|
"data-agent-mention-kind": "capability",
|
|
class: "tsh-agent-object-token tsh-agent-object-token--entity"
|
|
}),
|
|
[
|
|
"span",
|
|
{
|
|
class: "tsh-agent-object-token__kind",
|
|
"aria-hidden": "true"
|
|
},
|
|
[
|
|
"span",
|
|
{
|
|
class: "tsh-agent-object-token__kind-icon",
|
|
"aria-hidden": "true"
|
|
},
|
|
""
|
|
]
|
|
],
|
|
["span", { class: "tsh-agent-object-token__main" }, displayLabel]
|
|
];
|
|
},
|
|
|
|
renderText({ node }) {
|
|
return attrsToCapabilityTokenAttrs(node.attrs ?? {}).trigger;
|
|
}
|
|
});
|
|
}
|
|
|
|
export function parseAgentCapabilityToken(
|
|
text: string,
|
|
start: number,
|
|
capabilities: readonly AgentCapabilityTokenOption[] = []
|
|
): AgentCapabilityTokenMatch | null {
|
|
if (!isCapabilityTokenBoundary(text[start - 1] ?? "")) {
|
|
return null;
|
|
}
|
|
for (const candidate of capabilityTokenCandidates(capabilities)) {
|
|
if (!text.startsWith(candidate.trigger, start)) {
|
|
continue;
|
|
}
|
|
const end = start + candidate.trigger.length;
|
|
if (!isCapabilityTokenBoundary(text[end] ?? "")) {
|
|
continue;
|
|
}
|
|
return {
|
|
attrs: candidate,
|
|
end
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function capabilityTokenCandidates(
|
|
capabilities: readonly AgentCapabilityTokenOption[]
|
|
): AgentCapabilityTokenAttrs[] {
|
|
const candidates = new Map<string, AgentCapabilityTokenAttrs>();
|
|
for (const capability of capabilities) {
|
|
const trigger = capability.trigger.trim();
|
|
if (!trigger || candidates.has(trigger)) {
|
|
continue;
|
|
}
|
|
candidates.set(trigger, {
|
|
capability: capability.capability.trim(),
|
|
label: capability.label.trim(),
|
|
name: capability.name.trim(),
|
|
trigger
|
|
});
|
|
}
|
|
return [...candidates.values()].sort(
|
|
(left, right) => right.trigger.length - left.trigger.length
|
|
);
|
|
}
|
|
|
|
function isCapabilityTokenBoundary(value: string): boolean {
|
|
return value === "" || value === " " || value === "\n" || value === "\t";
|
|
}
|
|
|
|
function attrsToCapabilityTokenAttrs(
|
|
attrs: Record<string, unknown>
|
|
): AgentCapabilityTokenAttrs {
|
|
return {
|
|
capability: typeof attrs.capability === "string" ? attrs.capability : "",
|
|
label: typeof attrs.label === "string" ? attrs.label : "",
|
|
name: typeof attrs.name === "string" ? attrs.name : "",
|
|
trigger: typeof attrs.trigger === "string" ? attrs.trigger : ""
|
|
};
|
|
}
|
|
|
|
function capabilityTokenDisplayLabel(attrs: AgentCapabilityTokenAttrs): string {
|
|
const label = attrs.label.trim() || attrs.trigger.trim();
|
|
if (!label || label.startsWith("/") || label.startsWith("$")) {
|
|
return label;
|
|
}
|
|
return `/${label}`;
|
|
}
|