@vettly/shared 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -0
- package/dist/index.cjs +362 -0
- package/dist/index.d.cts +795 -0
- package/dist/index.d.ts +795 -0
- package/dist/index.js +299 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var ContentTypeSchema = z.enum(["text", "image", "video"]);
|
|
4
|
+
var UseCaseTypeSchema = z.enum([
|
|
5
|
+
"social_post",
|
|
6
|
+
"comment",
|
|
7
|
+
"profile",
|
|
8
|
+
"message",
|
|
9
|
+
"review",
|
|
10
|
+
"listing",
|
|
11
|
+
"bio",
|
|
12
|
+
"other"
|
|
13
|
+
]);
|
|
14
|
+
var CategorySchema = z.enum([
|
|
15
|
+
"hate_speech",
|
|
16
|
+
"harassment",
|
|
17
|
+
"violence",
|
|
18
|
+
"self_harm",
|
|
19
|
+
"sexual",
|
|
20
|
+
"spam",
|
|
21
|
+
"profanity",
|
|
22
|
+
"scam",
|
|
23
|
+
"illegal"
|
|
24
|
+
]);
|
|
25
|
+
var ProviderNameSchema = z.enum([
|
|
26
|
+
"openai",
|
|
27
|
+
"perspective",
|
|
28
|
+
"hive",
|
|
29
|
+
"azure",
|
|
30
|
+
"bot_detection",
|
|
31
|
+
"gemini_vision",
|
|
32
|
+
"mock",
|
|
33
|
+
"fallback"
|
|
34
|
+
]);
|
|
35
|
+
var ActionSchema = z.enum(["block", "warn", "flag", "allow"]);
|
|
36
|
+
var RuleSchema = z.object({
|
|
37
|
+
category: CategorySchema,
|
|
38
|
+
threshold: z.number().min(0).max(1),
|
|
39
|
+
provider: ProviderNameSchema,
|
|
40
|
+
action: ActionSchema,
|
|
41
|
+
priority: z.number().optional().default(0),
|
|
42
|
+
// Custom prompt-based moderation (Pro+ tier only)
|
|
43
|
+
// When set, uses Gemini Vision for semantic image analysis
|
|
44
|
+
customPrompt: z.string().min(1).max(500).optional(),
|
|
45
|
+
customCategory: z.string().min(1).max(100).optional()
|
|
46
|
+
}).refine(
|
|
47
|
+
(data) => {
|
|
48
|
+
const hasPrompt = !!data.customPrompt;
|
|
49
|
+
const hasCategory = !!data.customCategory;
|
|
50
|
+
return hasPrompt === hasCategory;
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
message: "customPrompt and customCategory must both be set, or both be omitted",
|
|
54
|
+
path: ["customPrompt"]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
var OverrideSchema = z.object({
|
|
58
|
+
locale: z.string().optional(),
|
|
59
|
+
region: z.string().optional(),
|
|
60
|
+
data_residency: z.string().optional(),
|
|
61
|
+
provider: ProviderNameSchema.optional()
|
|
62
|
+
});
|
|
63
|
+
var FallbackConfigSchema = z.object({
|
|
64
|
+
provider: ProviderNameSchema,
|
|
65
|
+
on_timeout: z.boolean().optional().default(true),
|
|
66
|
+
timeout_ms: z.number().optional().default(5e3)
|
|
67
|
+
});
|
|
68
|
+
var PolicySchema = z.object({
|
|
69
|
+
name: z.string(),
|
|
70
|
+
version: z.string(),
|
|
71
|
+
rules: z.array(RuleSchema),
|
|
72
|
+
overrides: z.array(OverrideSchema).optional(),
|
|
73
|
+
fallback: FallbackConfigSchema.optional()
|
|
74
|
+
});
|
|
75
|
+
var DecisionSchema = z.object({
|
|
76
|
+
id: z.string().uuid(),
|
|
77
|
+
content: z.string(),
|
|
78
|
+
contentHash: z.string(),
|
|
79
|
+
contentType: ContentTypeSchema,
|
|
80
|
+
policy: z.object({
|
|
81
|
+
id: z.string(),
|
|
82
|
+
version: z.string()
|
|
83
|
+
}),
|
|
84
|
+
result: z.object({
|
|
85
|
+
safe: z.boolean(),
|
|
86
|
+
flagged: z.boolean(),
|
|
87
|
+
action: ActionSchema,
|
|
88
|
+
categories: z.array(
|
|
89
|
+
z.object({
|
|
90
|
+
category: CategorySchema,
|
|
91
|
+
score: z.number(),
|
|
92
|
+
threshold: z.number(),
|
|
93
|
+
triggered: z.boolean()
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
}),
|
|
97
|
+
provider: z.object({
|
|
98
|
+
name: ProviderNameSchema,
|
|
99
|
+
latency: z.number(),
|
|
100
|
+
cost: z.number()
|
|
101
|
+
}),
|
|
102
|
+
metadata: z.record(z.unknown()).optional(),
|
|
103
|
+
timestamp: z.string().datetime(),
|
|
104
|
+
requestId: z.string().optional()
|
|
105
|
+
});
|
|
106
|
+
var MultiModalCheckRequestSchema = z.object({
|
|
107
|
+
// Multi-modal content inputs
|
|
108
|
+
text: z.string().max(1e5, "Text exceeds maximum size of 100KB").optional(),
|
|
109
|
+
images: z.array(
|
|
110
|
+
z.string().refine(
|
|
111
|
+
(val) => {
|
|
112
|
+
return val.startsWith("http://") || val.startsWith("https://") || val.startsWith("data:image/");
|
|
113
|
+
},
|
|
114
|
+
{ message: "Images must be URLs or base64 data URIs" }
|
|
115
|
+
)
|
|
116
|
+
).max(10, "Maximum 10 images per request").optional(),
|
|
117
|
+
video: z.string().url("Video must be a valid URL").optional(),
|
|
118
|
+
// Moderation context
|
|
119
|
+
context: z.object({
|
|
120
|
+
useCase: UseCaseTypeSchema.default("other"),
|
|
121
|
+
userId: z.string().optional(),
|
|
122
|
+
userReputation: z.number().min(0).max(1).optional(),
|
|
123
|
+
// 0-1 score
|
|
124
|
+
locale: z.string().optional(),
|
|
125
|
+
region: z.string().optional()
|
|
126
|
+
}).optional(),
|
|
127
|
+
// Policy and metadata
|
|
128
|
+
policyId: z.string(),
|
|
129
|
+
metadata: z.record(z.unknown()).optional(),
|
|
130
|
+
requestId: z.string().optional()
|
|
131
|
+
// For idempotency
|
|
132
|
+
}).refine(
|
|
133
|
+
(data) => {
|
|
134
|
+
return data.text || data.images?.length || data.video;
|
|
135
|
+
},
|
|
136
|
+
{ message: "At least one of text, images, or video must be provided" }
|
|
137
|
+
);
|
|
138
|
+
var CheckRequestSchema = z.object({
|
|
139
|
+
content: z.string().min(1).max(1e5, "Content exceeds maximum size of 100KB"),
|
|
140
|
+
policyId: z.string(),
|
|
141
|
+
contentType: ContentTypeSchema.optional().default("text"),
|
|
142
|
+
metadata: z.record(z.unknown()).optional(),
|
|
143
|
+
requestId: z.string().optional()
|
|
144
|
+
// For idempotency
|
|
145
|
+
});
|
|
146
|
+
var ContentItemResultSchema = z.object({
|
|
147
|
+
contentType: ContentTypeSchema,
|
|
148
|
+
contentRef: z.string().optional(),
|
|
149
|
+
// URL or identifier for images/video
|
|
150
|
+
contentItemId: z.string().uuid().optional(),
|
|
151
|
+
// Database ID for linking evidence
|
|
152
|
+
safe: z.boolean(),
|
|
153
|
+
flagged: z.boolean(),
|
|
154
|
+
action: ActionSchema,
|
|
155
|
+
categories: z.array(
|
|
156
|
+
z.object({
|
|
157
|
+
category: CategorySchema,
|
|
158
|
+
score: z.number(),
|
|
159
|
+
triggered: z.boolean()
|
|
160
|
+
})
|
|
161
|
+
),
|
|
162
|
+
provider: ProviderNameSchema,
|
|
163
|
+
latency: z.number(),
|
|
164
|
+
cost: z.number(),
|
|
165
|
+
evidence: z.object({
|
|
166
|
+
url: z.string().url().optional(),
|
|
167
|
+
// Signed URL to evidence (screenshot, frame)
|
|
168
|
+
expiresAt: z.string().datetime().optional()
|
|
169
|
+
}).optional()
|
|
170
|
+
});
|
|
171
|
+
var MultiModalCheckResponseSchema = z.object({
|
|
172
|
+
decisionId: z.string().uuid(),
|
|
173
|
+
safe: z.boolean(),
|
|
174
|
+
// Overall safe if ALL content items are safe
|
|
175
|
+
flagged: z.boolean(),
|
|
176
|
+
// Overall flagged if ANY content item is flagged
|
|
177
|
+
action: ActionSchema,
|
|
178
|
+
// Most severe action across all content
|
|
179
|
+
results: z.array(ContentItemResultSchema),
|
|
180
|
+
// Per-content-type results
|
|
181
|
+
totalLatency: z.number(),
|
|
182
|
+
totalCost: z.number(),
|
|
183
|
+
requestId: z.string().optional()
|
|
184
|
+
});
|
|
185
|
+
var CheckResponseSchema = z.object({
|
|
186
|
+
decisionId: z.string().uuid(),
|
|
187
|
+
safe: z.boolean(),
|
|
188
|
+
flagged: z.boolean(),
|
|
189
|
+
action: ActionSchema,
|
|
190
|
+
categories: z.array(
|
|
191
|
+
z.object({
|
|
192
|
+
category: CategorySchema,
|
|
193
|
+
score: z.number(),
|
|
194
|
+
triggered: z.boolean()
|
|
195
|
+
})
|
|
196
|
+
),
|
|
197
|
+
provider: ProviderNameSchema,
|
|
198
|
+
latency: z.number(),
|
|
199
|
+
cost: z.number(),
|
|
200
|
+
requestId: z.string().optional()
|
|
201
|
+
});
|
|
202
|
+
var ReplayRequestSchema = z.object({
|
|
203
|
+
decisionId: z.string().uuid(),
|
|
204
|
+
policyId: z.string()
|
|
205
|
+
});
|
|
206
|
+
var ModerationError = class extends Error {
|
|
207
|
+
constructor(message, code, statusCode = 500, details) {
|
|
208
|
+
super(message);
|
|
209
|
+
this.code = code;
|
|
210
|
+
this.statusCode = statusCode;
|
|
211
|
+
this.details = details;
|
|
212
|
+
this.name = "ModerationError";
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
var PolicyValidationError = class extends ModerationError {
|
|
216
|
+
constructor(message, details) {
|
|
217
|
+
super(message, "POLICY_VALIDATION_ERROR", 400, details);
|
|
218
|
+
this.name = "PolicyValidationError";
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var ProviderError = class extends ModerationError {
|
|
222
|
+
constructor(message, provider, details) {
|
|
223
|
+
super(message, "PROVIDER_ERROR", 502, { provider, ...details });
|
|
224
|
+
this.name = "ProviderError";
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
var WebhookEventTypeSchema = z.enum([
|
|
228
|
+
"decision.created",
|
|
229
|
+
"decision.flagged",
|
|
230
|
+
"decision.blocked",
|
|
231
|
+
"policy.created",
|
|
232
|
+
"policy.updated"
|
|
233
|
+
]);
|
|
234
|
+
var WebhookEndpointSchema = z.object({
|
|
235
|
+
url: z.string().url().refine(
|
|
236
|
+
(url) => {
|
|
237
|
+
if (process.env.NODE_ENV === "production") {
|
|
238
|
+
return !url.includes("localhost") && !url.includes("127.0.0.1");
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
},
|
|
242
|
+
"Localhost webhooks are not allowed in production"
|
|
243
|
+
),
|
|
244
|
+
events: z.array(WebhookEventTypeSchema),
|
|
245
|
+
description: z.string().max(500).optional()
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// src/utils.ts
|
|
249
|
+
import crypto from "crypto";
|
|
250
|
+
function hashContent(content) {
|
|
251
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
252
|
+
}
|
|
253
|
+
function generateUUID() {
|
|
254
|
+
return crypto.randomUUID();
|
|
255
|
+
}
|
|
256
|
+
function generateRequestId() {
|
|
257
|
+
return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
258
|
+
}
|
|
259
|
+
function calculatePolicyVersion(yaml) {
|
|
260
|
+
return hashContent(yaml).substring(0, 16);
|
|
261
|
+
}
|
|
262
|
+
function formatCost(cost) {
|
|
263
|
+
return `$${cost.toFixed(6)}`;
|
|
264
|
+
}
|
|
265
|
+
function formatLatency(ms) {
|
|
266
|
+
if (ms < 1e3) {
|
|
267
|
+
return `${ms.toFixed(0)}ms`;
|
|
268
|
+
}
|
|
269
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
ActionSchema,
|
|
273
|
+
CategorySchema,
|
|
274
|
+
CheckRequestSchema,
|
|
275
|
+
CheckResponseSchema,
|
|
276
|
+
ContentItemResultSchema,
|
|
277
|
+
ContentTypeSchema,
|
|
278
|
+
DecisionSchema,
|
|
279
|
+
FallbackConfigSchema,
|
|
280
|
+
ModerationError,
|
|
281
|
+
MultiModalCheckRequestSchema,
|
|
282
|
+
MultiModalCheckResponseSchema,
|
|
283
|
+
OverrideSchema,
|
|
284
|
+
PolicySchema,
|
|
285
|
+
PolicyValidationError,
|
|
286
|
+
ProviderError,
|
|
287
|
+
ProviderNameSchema,
|
|
288
|
+
ReplayRequestSchema,
|
|
289
|
+
RuleSchema,
|
|
290
|
+
UseCaseTypeSchema,
|
|
291
|
+
WebhookEndpointSchema,
|
|
292
|
+
WebhookEventTypeSchema,
|
|
293
|
+
calculatePolicyVersion,
|
|
294
|
+
formatCost,
|
|
295
|
+
formatLatency,
|
|
296
|
+
generateRequestId,
|
|
297
|
+
generateUUID,
|
|
298
|
+
hashContent
|
|
299
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vettly/shared",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Shared TypeScript types for Vettly protection SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
21
|
+
"test": "bun test",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"vettly",
|
|
26
|
+
"moderation",
|
|
27
|
+
"protection",
|
|
28
|
+
"types",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "Vettly",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/brian-nextaura/vettly-docs.git",
|
|
36
|
+
"directory": "packages/shared"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://vettly.dev",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public",
|
|
41
|
+
"name": "@nextauralabs/vettly-shared"
|
|
42
|
+
},
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/brian-nextaura/vettly-docs/issues"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"zod": "^3.22.4"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/bun": "latest",
|
|
51
|
+
"tsup": "^8.0.0",
|
|
52
|
+
"typescript": "^5"
|
|
53
|
+
}
|
|
54
|
+
}
|