UNPKG

rhombus-node-mcp

Version:
180 lines (179 loc) 9.61 kB
import { z } from "zod"; import { ISOTimestampFormatDescription } from "../utils/timestampInput.js"; import { createUuidSchema } from "../types.js"; import { INCLUDE_FIELDS_ARG, FILTER_BY_ARG } from "../util.js"; export var RequestType; (function (RequestType) { RequestType["GET_FACE_EVENTS"] = "get-face-events"; RequestType["GET_REGISTERED_FACES"] = "get-registered-faces"; RequestType["GET_PERSON_LABELS"] = "get-person-labels"; RequestType["SEARCH_SIMILAR_FACES"] = "search-similar-faces"; RequestType["GET_FACE_MATCHMAKERS"] = "get-face-matchmakers"; RequestType["GET_FACE_EVENTS_BY_PERSON"] = "get-face-events-by-person"; })(RequestType || (RequestType = {})); export const GetRegisteredFacesArgsSchema = z.object({ requestType: z.literal(RequestType.GET_REGISTERED_FACES), }); export const GetFaceEventsArgs = z.object({ pageRequest: z .object({ lastEvaluatedKey: z .string() .nullable() .describe("The last evaluated key from a previous pagination request"), maxPageSize: z .number() .nullable() .describe("Maximum number of results to return per page. Default to around 200. Caution against setting this to a lower number, it may make you miss information."), }) .nullable() .describe("Pagination parameters for the request"), searchFilter: z .object({ // TODO: this is causing the AI to call the tool incorrectly and always pass in deviceUUIDs. We'll leave // the filtering of this to the AI for now. // deviceUuids: z // .array(z.string()) // .describe( // "Optional filter by a set of device UUIDs. Only face events from these devices will be returned. An empty array will be the same as omitting the filter. The strings are always 22 characters long." // ), faceNameContains: z .string() .nullable() .describe("Optional filter for face events where the detected face's name contains this substring. The search is performed only if the value is at least 3 characters long after trimming spaces. This takes precedence over 'faceNames' if both are specified. This is case-sensitive."), faceNames: z.array(z.string()).describe(`Optional filter by a set of specific person names. Only face events associated with these names will be returned. An empty array will be the same as omitting the filter. This is case-sensitive.`), hasEmbedding: z .boolean() .nullable() .describe("Optional filter by the presence (true) or absence (false) of a face embedding associated with the event."), hasName: z .boolean() .nullable() .describe("Optional filter by the presence (true) or absence (false) of a person name associated with the face event."), labels: z .array(z.string()) .describe("Optional filter by a set of labels associated with the face event. An empty array will be the same as omitting the filter."), locationUuids: z .array(createUuidSchema()) .describe("Optional filter by a set of location UUIDs. Only face events from these locations will be returned. An empty array will be the same as omitting the filter."), personUuids: z .array(createUuidSchema()) .describe("Optional filter by a set of person UUIDs. Only face events associated with these specific people will be returned. An empty array will be the same as omitting the filter."), timestampFilter: z .object({ rangeEnd: z .string() .datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true, }) .nullable() .describe("The end of the time range (inclusive) for filtering face events. If not specified, the filter defaults to the last 7 days." + ISOTimestampFormatDescription), rangeStart: z .string() .datetime({ message: "Invalid datetime string. Expected ISO 8601 format.", offset: true, }) .nullable() .describe("The start of the time range (inclusive) for filtering face events. If not specified, the filter defaults to the last 7 days." + ISOTimestampFormatDescription), }) .nullable() .describe("Time range filter for face events"), }) .nullable() .describe("Search criteria for filtering face events. Only applies to tool calls with requestType 'get-face-events'."), }); export const TOOL_ARGS = { requestType: z.nativeEnum(RequestType), faceEventFilter: GetFaceEventsArgs, timeZone: z .string() .describe("The timezone for formatting timestamps which should come from the location of the camera for the face event. This is necessary for the tool to produce accurate formatted timestamps."), faceEventUuid: z.string().nullable().describe("UUID of a face event to search similar faces from. Required for 'search-similar-faces'."), personUuid: z.string().nullable().describe("UUID of a person to get face events for. Required for 'get-face-events-by-person'."), includeFields: INCLUDE_FIELDS_ARG, filterBy: FILTER_BY_ARG, }; const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS); export const OUTPUT_SCHEMA = z.object({ requestType: z.nativeEnum(RequestType), getFaceEventsResponse: z .optional(z.array(z.object({ deviceUuid: z.optional(z.string()), eventTimestampMs: z.number().describe("The timestamp of the face event in milliseconds."), eventTimestamp: z .optional(z.string()) .describe("The timestamp of the face event in human readable format."), faceName: z .optional(z.string()) .describe("If the face matches somebody that has been registered in our system, this is the name of the person that was detected."), locationUuid: z .optional(z.string()) .describe("The UUID of the location where the face event occurred."), personUuid: z.optional(z.string()).describe("The UUID of the person that was detected."), // selectedPersonMatch: z.optional( // z.object({ // confidence: z.number(), // faceId: z.optional(z.string()), // name: z.optional(z.string()), // uuid: z.optional(z.string()), // }) // ), thumbnailS3Key: z .optional(z.string()) .describe("The S3 key of the thumbnail of the face event."), // topPersonMatches: z.optional( // z.array( // z.object({ // confidence: z.number(), // faceId: z.optional(z.string()), // name: z.optional(z.string()), // uuid: z.optional(z.string()), // }) // ) // ).describe("The top person matches found for this face event, in order of confidence."), uuid: z.optional(z.string()), }))) .describe("A list of all people seen over the given time period."), getSavedFacesResponse: z .optional(z.array(z.object({ createdOn: z.optional(z.number()), name: z.optional(z.string()), orgUuid: z.optional(z.string()), updatedOn: z.optional(z.number()), uuid: z.optional(z.string()), labels: z.optional(z.array(z.string())).describe("Labels assigned to this person, useful for grouping (e.g., 'Engineering', 'Visitors')."), }))) .describe("A list of all people (registered faces) currently known to the Rhombus system for your organization."), getPersonLabelsResponse: z .optional(z.record(z.string(), z.array(z.string()))) .describe("A map of person UUIDs to their assigned label arrays. Use this to discover what label groups exist and which people belong to them."), resolvedNames: z .optional(z.record(z.string(), z.string().nullable())) .describe("When faceNames are provided in a get-face-events request, this shows how each queried name was automatically resolved to a registered face. " + "Key is the queried name (e.g., 'Brandon'), value is the matched registered name (e.g., 'Brandon Salzberg') or null if no match was found. " + "Use these resolved names when reporting results to the user."), similarFaceEvents: z.array(z.object({ uuid: z.string().optional(), personUuid: z.string().optional(), similarity: z.number().optional(), eventTimestamp: z.string().optional(), })).optional().describe("Similar face event results"), faceMatchmakers: z.array(z.object({ uuid: z.string().optional(), personUuid: z.string().optional(), name: z.string().optional(), })).optional().describe("Face matchmaker records"), personFaceEvents: z.array(z.object({ uuid: z.string().optional(), personUuid: z.string().optional(), eventTimestamp: z.string().optional(), deviceUuid: z.string().optional(), })).optional().describe("Face events for a specific person"), lastEvaluatedKey: z.string().optional().describe("For paginated requests, this is the returned last evaluated key that can be passed back in on the next request to get the next page of results"), error: z.optional(z.string()), });