refactor: rename API and frontend projects to Vocero
Rename the project directories, Python distribution and import package, and npm package. Update launcher, Docker, tooling, tests, lockfiles, and documentation while preserving existing API contracts. Make AGENTS.md self-contained and remove external skill and workflow references.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { recordSchema, type DynamicTool, type FunctionTool, type Profile, type ServerConfig } from "./contracts";
|
||||
import type { Settings } from "./settings";
|
||||
|
||||
export function profileName(profile: Profile | null): string {
|
||||
if (!profile) {
|
||||
return "";
|
||||
}
|
||||
for (const key of ["name", "fullName", "full_name", "displayName", "display_name", "username"]) {
|
||||
const value = profile[key];
|
||||
if (typeof value === "string" && value.trim()) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
const full = [profile.firstName, profile.lastName]
|
||||
.filter((part): part is string => typeof part === "string")
|
||||
.join(" ")
|
||||
.trim();
|
||||
if (full) {
|
||||
return full;
|
||||
}
|
||||
for (const key of ["user", "customer", "profile", "contact"]) {
|
||||
const nested = recordSchema.safeParse(profile[key]);
|
||||
if (nested.success) {
|
||||
const name = profileName(nested.data);
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
export function initials(name: string) {
|
||||
const words = name.trim().split(/\s+/);
|
||||
return (words.length > 1 ? `${words[0]?.[0] ?? ""}${words.at(-1)?.[0] ?? ""}` : name.slice(0, 2)).toUpperCase();
|
||||
}
|
||||
export function profileContext(profile: Profile | null) {
|
||||
if (!profile) {
|
||||
return "";
|
||||
}
|
||||
const serialized = JSON.stringify(profile);
|
||||
return `Caller profile resolved from the page phoneNumber: ${serialized.length > 1600 ? `${serialized.slice(0, 1600)}...` : serialized}. Use this as trusted caller context when relevant.`;
|
||||
}
|
||||
export function ragContext(text: string) {
|
||||
return text.trim() ? `<<<CUSTOM_RAG_BEGIN>>>\n${text.trim()}\n<<<CUSTOM_RAG_END>>>` : "";
|
||||
}
|
||||
export const TOOL_MARKER = "<<<APIPILOT_TOOLS_LIST_BEGIN>>>";
|
||||
export function toolContext(tools: DynamicTool[]) {
|
||||
if (!tools.length) {
|
||||
return "";
|
||||
}
|
||||
const entries = tools.slice(0, 100).map((tool) => {
|
||||
const required = Array.isArray(tool.parameters.required)
|
||||
? tool.parameters.required.filter((key): key is string => typeof key === "string")
|
||||
: [];
|
||||
return `- name: ${tool.name}\n requires_auth: ${String(tool.requires_auth)}${required.length ? `\n required_parameters: [${required.slice(0, 40).join(", ")}]` : ""}\n description: ${tool.description.slice(0, 500)}`;
|
||||
});
|
||||
return `${TOOL_MARKER}\n### Available dynamic tools (ApiPilot AquaPortal)\nEach tool name is a unique identifier: DO NOT change, camelCase or translate it.\nMANDATORY EXECUTION RULES:\n1. If the user request matches a tool description, output ONLY one line as the FIRST line of your response:\n<<<APIPILOT_CALL:NAME=tool_name,ARGUMENTS={"parameter1":"value1"}>>>\n2. ARGUMENTS must be valid JSON. Include ONLY fields listed in required_parameters, if any.\n3. Do NOT add text after the marker. The engine executes the tool and supplies its result in a second call.\n4. If no description matches, respond normally in Spanish without a marker.\n5. If multiple descriptions match, choose the most specific.\n${entries.join("\n")}\n<<<APIPILOT_TOOLS_LIST_END>>>`;
|
||||
}
|
||||
export function buildInstructions(
|
||||
config: ServerConfig,
|
||||
settings: Settings,
|
||||
profile: Profile | null,
|
||||
rag: string,
|
||||
tools: DynamicTool[],
|
||||
) {
|
||||
const base = config.primaryPrompt || settings.instructions;
|
||||
return [
|
||||
base,
|
||||
config.injectProfile ? profileContext(profile) : "",
|
||||
ragContext(rag),
|
||||
base.includes(TOOL_MARKER) ? "" : toolContext(tools),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n");
|
||||
}
|
||||
export function speechInstructions(instructions: string, config: ServerConfig) {
|
||||
return config.reasoningType === "OPENAI"
|
||||
? `${instructions}\nDo not answer ordinary user speech directly. Stay silent unless the latest user message starts with [VOCERO_RENDER_REPLY]. When it does, speak only the content after the marker naturally. Do not call tools while rendering that message.`
|
||||
: instructions;
|
||||
}
|
||||
export const WEB_SEARCH: FunctionTool = {
|
||||
type: "function",
|
||||
name: "web_search",
|
||||
description: "Search the web for current or factual information. Returns results with titles, snippets and URLs.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: { query: { type: "string", description: "The search query." } },
|
||||
required: ["query"],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user