@framers/agentos-ext-ml-classifiers 0.1.0 → 0.3.1
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/.github/workflows/ci.yml +20 -0
- package/.github/workflows/release.yml +37 -0
- package/.releaserc.json +9 -0
- package/LICENSE +96 -21
- package/README.md +72 -0
- package/dist/MLClassifierGuardrail.d.ts +88 -117
- package/dist/MLClassifierGuardrail.d.ts.map +1 -1
- package/dist/MLClassifierGuardrail.js +263 -264
- package/dist/MLClassifierGuardrail.js.map +1 -1
- package/dist/index.d.ts +16 -90
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -309
- package/dist/index.js.map +1 -1
- package/dist/keyword-classifier.d.ts +26 -0
- package/dist/keyword-classifier.d.ts.map +1 -0
- package/dist/keyword-classifier.js +113 -0
- package/dist/keyword-classifier.js.map +1 -0
- package/dist/llm-classifier.d.ts +27 -0
- package/dist/llm-classifier.d.ts.map +1 -0
- package/dist/llm-classifier.js +129 -0
- package/dist/llm-classifier.js.map +1 -0
- package/dist/tools/ClassifyContentTool.d.ts +53 -80
- package/dist/tools/ClassifyContentTool.d.ts.map +1 -1
- package/dist/tools/ClassifyContentTool.js +52 -103
- package/dist/tools/ClassifyContentTool.js.map +1 -1
- package/dist/types.d.ts +77 -277
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -55
- package/dist/types.js.map +1 -1
- package/package.json +10 -24
- package/scripts/fix-esm-imports.mjs +181 -0
- package/src/MLClassifierGuardrail.ts +306 -310
- package/src/index.ts +35 -339
- package/src/keyword-classifier.ts +130 -0
- package/src/llm-classifier.ts +163 -0
- package/src/tools/ClassifyContentTool.ts +75 -132
- package/src/types.ts +78 -325
- package/test/llm-tier.spec.ts +267 -0
- package/test/ml-classifiers.spec.ts +57 -0
- package/test/onnx-tier.spec.ts +255 -0
- package/test/tier-fallthrough.spec.ts +185 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +35 -0
- package/dist/ClassifierOrchestrator.d.ts +0 -126
- package/dist/ClassifierOrchestrator.d.ts.map +0 -1
- package/dist/ClassifierOrchestrator.js +0 -239
- package/dist/ClassifierOrchestrator.js.map +0 -1
- package/dist/IContentClassifier.d.ts +0 -117
- package/dist/IContentClassifier.d.ts.map +0 -1
- package/dist/IContentClassifier.js +0 -22
- package/dist/IContentClassifier.js.map +0 -1
- package/dist/SlidingWindowBuffer.d.ts +0 -213
- package/dist/SlidingWindowBuffer.d.ts.map +0 -1
- package/dist/SlidingWindowBuffer.js +0 -246
- package/dist/SlidingWindowBuffer.js.map +0 -1
- package/dist/classifiers/InjectionClassifier.d.ts +0 -126
- package/dist/classifiers/InjectionClassifier.d.ts.map +0 -1
- package/dist/classifiers/InjectionClassifier.js +0 -210
- package/dist/classifiers/InjectionClassifier.js.map +0 -1
- package/dist/classifiers/JailbreakClassifier.d.ts +0 -124
- package/dist/classifiers/JailbreakClassifier.d.ts.map +0 -1
- package/dist/classifiers/JailbreakClassifier.js +0 -208
- package/dist/classifiers/JailbreakClassifier.js.map +0 -1
- package/dist/classifiers/ToxicityClassifier.d.ts +0 -125
- package/dist/classifiers/ToxicityClassifier.d.ts.map +0 -1
- package/dist/classifiers/ToxicityClassifier.js +0 -212
- package/dist/classifiers/ToxicityClassifier.js.map +0 -1
- package/dist/classifiers/WorkerClassifierProxy.d.ts +0 -158
- package/dist/classifiers/WorkerClassifierProxy.d.ts.map +0 -1
- package/dist/classifiers/WorkerClassifierProxy.js +0 -268
- package/dist/classifiers/WorkerClassifierProxy.js.map +0 -1
- package/dist/worker/classifier-worker.d.ts +0 -49
- package/dist/worker/classifier-worker.d.ts.map +0 -1
- package/dist/worker/classifier-worker.js +0 -180
- package/dist/worker/classifier-worker.js.map +0 -1
- package/src/ClassifierOrchestrator.ts +0 -290
- package/src/IContentClassifier.ts +0 -124
- package/src/SlidingWindowBuffer.ts +0 -384
- package/src/classifiers/InjectionClassifier.ts +0 -261
- package/src/classifiers/JailbreakClassifier.ts +0 -259
- package/src/classifiers/ToxicityClassifier.ts +0 -263
- package/src/classifiers/WorkerClassifierProxy.ts +0 -366
- package/src/worker/classifier-worker.ts +0 -267
package/dist/types.js
CHANGED
|
@@ -1,62 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @file types.ts
|
|
3
|
+
* @description Core type definitions for the ML Classifiers extension pack.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (toxicity, prompt-injection, jailbreak) and emit structured results that
|
|
8
|
-
* feed into the AgentOS guardrail decision tree.
|
|
5
|
+
* Defines the shared interfaces used across the ML classification system:
|
|
6
|
+
* classifier categories, confidence results, option shapes, and the LLM
|
|
7
|
+
* invoker callback signature.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
* ----------------
|
|
12
|
-
* ```
|
|
13
|
-
* IUtilityAI ──── ClassificationResult, ClassificationScore
|
|
14
|
-
* IGuardrailService ── GuardrailAction
|
|
15
|
-
* │
|
|
16
|
-
* ▼
|
|
17
|
-
* types.ts (this file)
|
|
18
|
-
* │
|
|
19
|
-
* ▼
|
|
20
|
-
* IContentClassifier.ts / SlidingWindowBuffer.ts / …
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* @module agentos/extensions/packs/ml-classifiers/types
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* Sensible defaults for {@link ClassifierThresholds}.
|
|
27
|
-
*
|
|
28
|
-
* These values reflect a conservative-but-pragmatic policy:
|
|
29
|
-
* - block at 90 % confidence → very high bar, minimises false positives
|
|
30
|
-
* - flag at 70 % → surfaced for human review, not blocked
|
|
31
|
-
* - warn at 40 % → low-confidence signal, handled with a light touch
|
|
9
|
+
* @module ml-classifiers/types
|
|
32
10
|
*/
|
|
33
|
-
export const DEFAULT_THRESHOLDS = {
|
|
34
|
-
blockThreshold: 0.9,
|
|
35
|
-
flagThreshold: 0.7,
|
|
36
|
-
warnThreshold: 0.4,
|
|
37
|
-
};
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Service identifiers
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
11
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* These IDs follow the `agentos:<domain>:<name>` naming convention used
|
|
46
|
-
* throughout the AgentOS extension ecosystem. Use them to retrieve specific
|
|
47
|
-
* classifier services from the shared service registry.
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* ```typescript
|
|
51
|
-
* const toxicity = serviceRegistry.get(ML_CLASSIFIER_SERVICE_IDS.TOXICITY_PIPELINE);
|
|
52
|
-
* ```
|
|
12
|
+
* All supported classifier categories as a constant array, used for
|
|
13
|
+
* iteration and default configuration.
|
|
53
14
|
*/
|
|
54
|
-
export const
|
|
55
|
-
/** Classifier that detects toxic, hateful, or abusive language. */
|
|
56
|
-
TOXICITY_PIPELINE: 'agentos:ml-classifiers:toxicity-pipeline',
|
|
57
|
-
/** Classifier that detects prompt-injection attempts. */
|
|
58
|
-
INJECTION_PIPELINE: 'agentos:ml-classifiers:injection-pipeline',
|
|
59
|
-
/** Classifier that detects jailbreak / system-override attempts. */
|
|
60
|
-
JAILBREAK_PIPELINE: 'agentos:ml-classifiers:jailbreak-pipeline',
|
|
61
|
-
};
|
|
15
|
+
export const ALL_CATEGORIES = ['toxic', 'injection', 'nsfw', 'threat'];
|
|
62
16
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAyB,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-ml-classifiers",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "ML-based content
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "ML-based content classifiers for AgentOS — toxicity, prompt injection, and NSFW detection via ONNX models or LLM fallback",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -11,35 +11,21 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist",
|
|
16
|
-
"src",
|
|
17
|
-
"SKILL.md",
|
|
18
|
-
"manifest.json"
|
|
19
|
-
],
|
|
20
14
|
"peerDependencies": {
|
|
21
|
-
"@framers/agentos": "
|
|
15
|
+
"@framers/agentos": ">=0.7.0"
|
|
22
16
|
},
|
|
23
17
|
"optionalDependencies": {
|
|
24
18
|
"@huggingface/transformers": "^3.0.0"
|
|
25
19
|
},
|
|
20
|
+
"license": "Apache-2.0",
|
|
26
21
|
"devDependencies": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"@framers/agentos": "0.1.47"
|
|
30
|
-
},
|
|
31
|
-
"license": "MIT",
|
|
32
|
-
"author": "Frame.dev",
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "https://github.com/framersai/agentos-extensions.git",
|
|
36
|
-
"directory": "registry/curated/safety/ml-classifiers"
|
|
37
|
-
},
|
|
38
|
-
"publishConfig": {
|
|
39
|
-
"access": "public"
|
|
22
|
+
"semantic-release": "^24.0.0",
|
|
23
|
+
"@semantic-release/github": "^11.0.0"
|
|
40
24
|
},
|
|
25
|
+
"homepage": "https://agentos.sh",
|
|
41
26
|
"scripts": {
|
|
42
|
-
"build": "tsc -
|
|
43
|
-
"
|
|
27
|
+
"build": "tsc && node scripts/fix-esm-imports.mjs",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
44
30
|
}
|
|
45
31
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const distDir = path.resolve(__dirname, '..', 'dist');
|
|
10
|
+
const SELF_PACKAGE_NAME = '@framers/agentos-ext-ml-classifiers';
|
|
11
|
+
|
|
12
|
+
function collectJsFiles(dirPath) {
|
|
13
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
14
|
+
const files = [];
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
files.push(...collectJsFiles(fullPath));
|
|
19
|
+
} else if (entry.isFile() && fullPath.endsWith('.js')) {
|
|
20
|
+
files.push(fullPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveSpecifier(filePath, specifier) {
|
|
27
|
+
if (specifier === SELF_PACKAGE_NAME || specifier.startsWith(`${SELF_PACKAGE_NAME}/`)) {
|
|
28
|
+
const relativeExportPath =
|
|
29
|
+
specifier === SELF_PACKAGE_NAME ? 'index' : specifier.slice(SELF_PACKAGE_NAME.length + 1);
|
|
30
|
+
const targetCandidates = [
|
|
31
|
+
path.resolve(distDir, `${relativeExportPath}.js`),
|
|
32
|
+
path.resolve(distDir, relativeExportPath, 'index.js'),
|
|
33
|
+
];
|
|
34
|
+
const targetPath = targetCandidates.find((candidate) => fs.existsSync(candidate));
|
|
35
|
+
if (!targetPath) {
|
|
36
|
+
return specifier;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let relativePath = path.relative(path.dirname(filePath), targetPath).replace(/\\/g, '/');
|
|
40
|
+
if (!relativePath.startsWith('.')) {
|
|
41
|
+
relativePath = `./${relativePath}`;
|
|
42
|
+
}
|
|
43
|
+
return relativePath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!specifier.startsWith('.')) {
|
|
47
|
+
return specifier;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hasKnownExtension = /\.(?:[cm]?js|json|node)$/i.test(specifier);
|
|
51
|
+
if (hasKnownExtension) {
|
|
52
|
+
return specifier;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const baseDir = path.dirname(filePath);
|
|
56
|
+
const asJs = path.resolve(baseDir, `${specifier}.js`);
|
|
57
|
+
if (fs.existsSync(asJs)) {
|
|
58
|
+
return `${specifier}.js`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const asIndex = path.resolve(baseDir, specifier, 'index.js');
|
|
62
|
+
if (fs.existsSync(asIndex)) {
|
|
63
|
+
return `${specifier}/index.js`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return specifier;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function rewriteSpecifiers(filePath) {
|
|
70
|
+
const original = fs.readFileSync(filePath, 'utf8');
|
|
71
|
+
let modified = original;
|
|
72
|
+
let changed = false;
|
|
73
|
+
|
|
74
|
+
const patterns = [
|
|
75
|
+
// Multi-line aware: `[\s\S]*?` matches anything including newlines, non-greedy, so
|
|
76
|
+
// imports with destructuring across multiple lines (e.g. `import { A,\n B\n} from './X'`)
|
|
77
|
+
// are caught. Anchored to start-of-line by `^` + `m` flag and terminated by the `from`
|
|
78
|
+
// keyword before a quote.
|
|
79
|
+
/(^\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"]+)(['"])/gm,
|
|
80
|
+
/(import\(\s*['"])([^'"]+)(['"]\s*\))/g
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
for (const pattern of patterns) {
|
|
84
|
+
modified = modified.replace(pattern, (match, prefix, specifier, suffix) => {
|
|
85
|
+
const rewritten = resolveSpecifier(filePath, specifier);
|
|
86
|
+
if (rewritten !== specifier) {
|
|
87
|
+
changed = true;
|
|
88
|
+
return `${prefix}${rewritten}${suffix}`;
|
|
89
|
+
}
|
|
90
|
+
return match;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (changed) {
|
|
95
|
+
fs.writeFileSync(filePath, modified, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function copyIfPresent(sourcePath, targetPath) {
|
|
100
|
+
if (!fs.existsSync(sourcePath)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
105
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Walk the dist tree after rewriting and verify every static/dynamic relative
|
|
111
|
+
* import has a resolvable extension. Catches the case where a previous build
|
|
112
|
+
* left partial dist state (e.g. rimraf failed because another process held
|
|
113
|
+
* files open) and the rewriter's regex didn't reach a stale file.
|
|
114
|
+
*
|
|
115
|
+
* Returns an array of {file, specifier, line} for each unfixed import.
|
|
116
|
+
*/
|
|
117
|
+
export function findUnfixedRelativeImports(distDir) {
|
|
118
|
+
const issues = [];
|
|
119
|
+
const jsFiles = collectJsFiles(distDir);
|
|
120
|
+
const patterns = [
|
|
121
|
+
/(^\s*(?:import|export)\s[\s\S]*?from\s+['"])([^'"]+)(['"])/gm,
|
|
122
|
+
/(import\(\s*['"])([^'"]+)(['"]\s*\))/g,
|
|
123
|
+
];
|
|
124
|
+
for (const file of jsFiles) {
|
|
125
|
+
const contents = fs.readFileSync(file, 'utf8');
|
|
126
|
+
for (const pattern of patterns) {
|
|
127
|
+
pattern.lastIndex = 0;
|
|
128
|
+
let m;
|
|
129
|
+
while ((m = pattern.exec(contents)) !== null) {
|
|
130
|
+
const specifier = m[2];
|
|
131
|
+
if (!specifier.startsWith('.')) continue;
|
|
132
|
+
if (/\.(?:[cm]?js|json|node)$/i.test(specifier)) continue;
|
|
133
|
+
// Look up the file's line number for a useful error message.
|
|
134
|
+
const lineNumber = contents.slice(0, m.index).split('\n').length;
|
|
135
|
+
issues.push({ file, specifier, line: lineNumber });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return issues;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Only run the main pipeline when invoked directly, not when this module is
|
|
143
|
+
// imported from a test or another script.
|
|
144
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
145
|
+
if (!fs.existsSync(distDir)) {
|
|
146
|
+
console.log('[agentos fix-esm-imports] dist directory missing; nothing to do.');
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const jsFiles = collectJsFiles(distDir);
|
|
151
|
+
for (const file of jsFiles) {
|
|
152
|
+
rewriteSpecifiers(file);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const copiedExtensionSecrets = copyIfPresent(
|
|
156
|
+
path.resolve(distDir, 'core', 'config', 'extension-secrets.json'),
|
|
157
|
+
path.resolve(distDir, 'config', 'extension-secrets.json'),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
console.log(`[agentos fix-esm-imports] Processed ${jsFiles.length} files under ${distDir}.`);
|
|
161
|
+
if (copiedExtensionSecrets) {
|
|
162
|
+
console.log('[agentos fix-esm-imports] Mirrored extension-secrets.json into dist/config for public package exports.');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const unfixed = findUnfixedRelativeImports(distDir);
|
|
166
|
+
if (unfixed.length > 0) {
|
|
167
|
+
console.error(
|
|
168
|
+
`[agentos fix-esm-imports] FAIL: ${unfixed.length} relative import${unfixed.length === 1 ? '' : 's'} ` +
|
|
169
|
+
`in dist still missing a file extension. This usually means a previous build ` +
|
|
170
|
+
`left partial state — try \`rm -rf dist && pnpm build\`.`,
|
|
171
|
+
);
|
|
172
|
+
for (const issue of unfixed.slice(0, 20)) {
|
|
173
|
+
const rel = path.relative(distDir, issue.file);
|
|
174
|
+
console.error(` ${rel}:${issue.line} -> ${issue.specifier}`);
|
|
175
|
+
}
|
|
176
|
+
if (unfixed.length > 20) {
|
|
177
|
+
console.error(` …and ${unfixed.length - 20} more.`);
|
|
178
|
+
}
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|