refactor(frontend): rename project to s2s-frontend
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