@@ -8,6 +8,10 @@ import {
88import { defineRouteContract } from '@/lib/api/contracts/types'
99import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages'
1010import { PRIVATE_SECRET_PROVENANCE_FIELD } from '@/lib/execution/private-tool-metadata'
11+ import {
12+ MAX_PII_VALIDATION_DETECTED_ENTITIES ,
13+ MAX_PII_VALIDATION_TEXT_CHARACTERS ,
14+ } from '@/lib/guardrails/pii-limits'
1115
1216const guardrailsMaskBatchBodySchema = z . object ( {
1317 texts : z . array ( z . string ( ) ) . max ( 100_000 ) ,
@@ -20,6 +24,38 @@ const guardrailsMaskBatchResponseSchema = z.object({
2024 masked : z . array ( z . string ( ) ) ,
2125} )
2226
27+ export const guardrailsPiiValidateBodySchema = z
28+ . object ( {
29+ text : z . string ( ) . max ( MAX_PII_VALIDATION_TEXT_CHARACTERS , 'Text is too long' ) ,
30+ entityTypes : z . array ( z . string ( ) . min ( 1 , 'Entity type cannot be empty' ) ) . max ( 200 ) ,
31+ mode : z . enum ( [ 'block' , 'mask' ] ) ,
32+ language : z . string ( ) . min ( 1 , 'Language cannot be empty' ) . max ( 20 ) . optional ( ) ,
33+ customPatterns : z . array ( customPatternSchema ) . max ( 20 ) . optional ( ) ,
34+ } )
35+ . strict ( )
36+
37+ export const detectedPiiEntitySchema = z
38+ . object ( {
39+ type : z . string ( ) . min ( 1 , 'Entity type cannot be empty' ) . max ( 100 ) ,
40+ start : z . number ( ) . int ( ) . nonnegative ( ) ,
41+ end : z . number ( ) . int ( ) . nonnegative ( ) ,
42+ score : z . number ( ) . min ( 0 ) . max ( 1 ) ,
43+ text : z . string ( ) . max ( MAX_PII_VALIDATION_TEXT_CHARACTERS , 'Detected text is too long' ) ,
44+ } )
45+ . strict ( )
46+
47+ export const guardrailsPiiValidateResponseSchema = z
48+ . object ( {
49+ passed : z . boolean ( ) ,
50+ error : z . string ( ) . max ( 1_000 ) . optional ( ) ,
51+ detectedEntities : z . array ( detectedPiiEntitySchema ) . max ( MAX_PII_VALIDATION_DETECTED_ENTITIES ) ,
52+ maskedText : z
53+ . string ( )
54+ . max ( MAX_PII_VALIDATION_TEXT_CHARACTERS , 'Masked text is too long' )
55+ . optional ( ) ,
56+ } )
57+ . strict ( )
58+
2359/**
2460 * Internal batch PII masking. Called server-to-server (internal JWT) from the
2561 * log-redaction persist path so Presidio always runs in the app container,
@@ -38,6 +74,23 @@ export const guardrailsMaskBatchContract = defineRouteContract({
3874export type GuardrailsMaskBatchBody = z . input < typeof guardrailsMaskBatchBodySchema >
3975export type GuardrailsMaskBatchResult = z . output < typeof guardrailsMaskBatchResponseSchema >
4076
77+ /**
78+ * Internal single-text PII validation. The workflow executor can run outside
79+ * the app network, while only the app task can reach the Presidio service.
80+ */
81+ export const guardrailsPiiValidateContract = defineRouteContract ( {
82+ method : 'POST' ,
83+ path : '/api/guardrails/pii/validate' ,
84+ body : guardrailsPiiValidateBodySchema ,
85+ response : {
86+ mode : 'json' ,
87+ schema : guardrailsPiiValidateResponseSchema ,
88+ } ,
89+ } )
90+
91+ export type GuardrailsPiiValidateBody = z . input < typeof guardrailsPiiValidateBodySchema >
92+ export type GuardrailsPiiValidateResult = z . output < typeof guardrailsPiiValidateResponseSchema >
93+
4194const chatMessageSchema = z . object ( {
4295 role : z . enum ( [ 'user' , 'assistant' , 'system' ] ) ,
4396 content : z . string ( ) ,
0 commit comments